Wire Linear and Slack specialists and prompt routing.

This commit is contained in:
CREDO23 2026-04-29 20:51:06 +02:00
parent 41cb4a567b
commit bf9b606a61
8 changed files with 53 additions and 8 deletions

View file

@ -5,12 +5,19 @@ say "I don't see it in the knowledge base" or ask the user if they want you to c
Ignore any knowledge base results for these services. Ignore any knowledge base results for these services.
When to use which tool: When to use which tool:
- Linear (issues) → list_issues, get_issue, save_issue (create/update) - Linear (issues, teams, users, projects when MCP exposes them) → hosted Linear MCP read tools (e.g. `list_issues`, `get_issue`, `list_teams`, `list_users`, …) and `save_issue` for create/update; native SurfSense Linear issue tools when present. For **multi-step Linear-only** work (several reads, structured evidence), delegate with the `task` tool to subagent **`linear_specialist`** instead of mixing unrelated tools.
- ClickUp (tasks) → clickup_search, clickup_get_task - ClickUp (tasks) → clickup_search, clickup_get_task
- Jira (issues) → getAccessibleAtlassianResources (cloudId discovery), getVisibleJiraProjects (project discovery), getJiraProjectIssueTypesMetadata (issue type discovery), searchJiraIssuesUsingJql, createJiraIssue, editJiraIssue - Jira (issues) → getAccessibleAtlassianResources (cloudId discovery), getVisibleJiraProjects (project discovery), getJiraProjectIssueTypesMetadata (issue type discovery), searchJiraIssuesUsingJql, createJiraIssue, editJiraIssue
- Slack (messages, channels) → slack_search_channels, slack_read_channel, slack_read_thread - Slack (messages, channels) → `slack_search_channels`, `slack_read_channel`, `slack_read_thread`, and other `slack_*` tools when connected. For **multi-step Slack-only** work, delegate with `task` to **`slack_specialist`**.
- Airtable (bases, tables, records) → list_bases, list_tables_for_base, list_records_for_table - Airtable (bases, tables, records) → list_bases, list_tables_for_base, list_records_for_table
- Knowledge base content (Notion, GitHub, files, notes) → automatically searched - Knowledge base content (Notion, GitHub, files, notes) → automatically searched
- Real-time public web data → call web_search - Real-time public web data → call web_search
- Reading a specific webpage → call scrape_webpage - Reading a specific webpage → call scrape_webpage
**`task` subagents (when to delegate):**
- **`linear_specialist`** — Linear-only investigations and tool use.
- **`slack_specialist`** — Slack-only investigations and tool use.
- **`connector_negotiator`** — **Cross-connector** chains (e.g. data from Slack then action in Linear).
- **`explore`** — Read-only KB + web research with citations.
- **`report_writer`** — Single `generate_report` deliverable.
</tool_routing> </tool_routing>

View file

@ -5,12 +5,19 @@ say "I don't see it in the knowledge base" or ask if they want you to check.
Ignore any knowledge base results for these services. Ignore any knowledge base results for these services.
When to use which tool: When to use which tool:
- Linear (issues) → list_issues, get_issue, save_issue (create/update) - Linear (issues, teams, users, projects when MCP exposes them) → hosted Linear MCP read tools (e.g. `list_issues`, `get_issue`, `list_teams`, `list_users`, …) and `save_issue` for create/update; native SurfSense Linear issue tools when present. For **multi-step Linear-only** work (several reads, structured evidence), delegate with the `task` tool to subagent **`linear_specialist`** instead of mixing unrelated tools.
- ClickUp (tasks) → clickup_search, clickup_get_task - ClickUp (tasks) → clickup_search, clickup_get_task
- Jira (issues) → getAccessibleAtlassianResources (cloudId discovery), getVisibleJiraProjects (project discovery), getJiraProjectIssueTypesMetadata (issue type discovery), searchJiraIssuesUsingJql, createJiraIssue, editJiraIssue - Jira (issues) → getAccessibleAtlassianResources (cloudId discovery), getVisibleJiraProjects (project discovery), getJiraProjectIssueTypesMetadata (issue type discovery), searchJiraIssuesUsingJql, createJiraIssue, editJiraIssue
- Slack (messages, channels) → slack_search_channels, slack_read_channel, slack_read_thread - Slack (messages, channels) → `slack_search_channels`, `slack_read_channel`, `slack_read_thread`, and other `slack_*` tools when connected. For **multi-step Slack-only** work, delegate with `task` to **`slack_specialist`**.
- Airtable (bases, tables, records) → list_bases, list_tables_for_base, list_records_for_table - Airtable (bases, tables, records) → list_bases, list_tables_for_base, list_records_for_table
- Knowledge base content (Notion, GitHub, files, notes) → automatically searched - Knowledge base content (Notion, GitHub, files, notes) → automatically searched
- Real-time public web data → call web_search - Real-time public web data → call web_search
- Reading a specific webpage → call scrape_webpage - Reading a specific webpage → call scrape_webpage
**`task` subagents (when to delegate):**
- **`linear_specialist`** — Linear-only investigations and tool use.
- **`slack_specialist`** — Slack-only investigations and tool use.
- **`connector_negotiator`** — **Cross-connector** chains (e.g. data from Slack then action in Linear).
- **`explore`** — Read-only KB + web research with citations.
- **`report_writer`** — Single `generate_report` deliverable.
</tool_routing> </tool_routing>

View file

@ -1 +1,3 @@
<linear_routing>
**Linear:** Prefer the `task` tool with subagent **`linear_specialist`** when the users request is **only about Linear** and may need several tool calls (list issues, inspect one issue, teams, users, statuses, comments, documents). Use **`connector_negotiator`** when Linear is one hop in a **multi-connector** workflow. Call Linear MCP tools directly from the parent when a **single** quick call is enough.
</linear_routing>

View file

@ -1 +1,3 @@
<slack_routing>
**Slack:** Prefer `task` with **`slack_specialist`** for **Slack-only** multi-step work (channels, threads, reads, writes that need approval in the specialist). Use **`connector_negotiator`** when Slack feeds another connector in one chain. Use direct `slack_*` tools from the parent for a **single** quick read or write when appropriate.
</slack_routing>

View file

@ -20,10 +20,14 @@ from .config import (
build_report_writer_subagent, build_report_writer_subagent,
build_specialized_subagents, build_specialized_subagents,
) )
from .providers.linear import build_linear_specialist_subagent
from .providers.slack import build_slack_specialist_subagent
__all__ = [ __all__ = [
"build_connector_negotiator_subagent", "build_connector_negotiator_subagent",
"build_explore_subagent", "build_explore_subagent",
"build_linear_specialist_subagent",
"build_report_writer_subagent", "build_report_writer_subagent",
"build_slack_specialist_subagent",
"build_specialized_subagents", "build_specialized_subagents",
] ]

View file

@ -22,6 +22,12 @@ from typing import TYPE_CHECKING, Any
from app.agents.new_chat.middleware.skills_backends import default_skills_sources from app.agents.new_chat.middleware.skills_backends import default_skills_sources
from app.agents.new_chat.permissions import Rule, Ruleset from app.agents.new_chat.permissions import Rule, Ruleset
from app.agents.new_chat.subagents.providers.linear import (
build_linear_specialist_subagent,
)
from app.agents.new_chat.subagents.providers.slack import (
build_slack_specialist_subagent,
)
if TYPE_CHECKING: if TYPE_CHECKING:
from deepagents import SubAgent from deepagents import SubAgent
@ -419,6 +425,12 @@ def build_specialized_subagents(
build_report_writer_subagent( build_report_writer_subagent(
tools=tools, model=model, extra_middleware=extra_middleware tools=tools, model=model, extra_middleware=extra_middleware
), ),
build_linear_specialist_subagent(
tools=tools, model=model, extra_middleware=extra_middleware
),
build_slack_specialist_subagent(
tools=tools, model=model, extra_middleware=extra_middleware
),
build_connector_negotiator_subagent( build_connector_negotiator_subagent(
tools=tools, model=model, extra_middleware=extra_middleware tools=tools, model=model, extra_middleware=extra_middleware
), ),

View file

@ -26,6 +26,9 @@ from .prompts.composer import (
detect_provider_variant, detect_provider_variant,
) )
# Optional routing fragments under ``prompts/routing/`` (see composer).
_DEFAULT_CONNECTOR_ROUTING: tuple[str, ...] = ("linear", "slack")
# Public re-exports for backwards compatibility (some legacy code reads the # Public re-exports for backwards compatibility (some legacy code reads the
# raw default-instructions text directly). # raw default-instructions text directly).
SURFSENSE_SYSTEM_INSTRUCTIONS_TEMPLATE = ( SURFSENSE_SYSTEM_INSTRUCTIONS_TEMPLATE = (
@ -63,6 +66,7 @@ def build_surfsense_system_prompt(
mcp_connector_tools=mcp_connector_tools, mcp_connector_tools=mcp_connector_tools,
citations_enabled=True, citations_enabled=True,
model_name=model_name, model_name=model_name,
connector_routing=_DEFAULT_CONNECTOR_ROUTING,
) )
@ -93,6 +97,7 @@ def build_configurable_system_prompt(
use_default_system_instructions=use_default_system_instructions, use_default_system_instructions=use_default_system_instructions,
citations_enabled=citations_enabled, citations_enabled=citations_enabled,
model_name=model_name, model_name=model_name,
connector_routing=_DEFAULT_CONNECTOR_ROUTING,
) )

View file

@ -210,10 +210,16 @@ class TestConnectorNegotiatorSubagent:
class TestBuildSpecializedSubagents: class TestBuildSpecializedSubagents:
def test_returns_three_specs(self) -> None: def test_returns_five_specs(self) -> None:
specs = build_specialized_subagents(tools=ALL_TOOLS) specs = build_specialized_subagents(tools=ALL_TOOLS)
names = [s["name"] for s in specs] # type: ignore[index] names = [s["name"] for s in specs] # type: ignore[index]
assert names == ["explore", "report_writer", "connector_negotiator"] assert names == [
"explore",
"report_writer",
"linear_specialist",
"slack_specialist",
"connector_negotiator",
]
def test_all_specs_have_unique_names(self) -> None: def test_all_specs_have_unique_names(self) -> None:
specs = build_specialized_subagents(tools=ALL_TOOLS) specs = build_specialized_subagents(tools=ALL_TOOLS)