From bf9b606a619dc779570b5e931cdf1c2dc679ae06 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 29 Apr 2026 20:51:06 +0200 Subject: [PATCH] Wire Linear and Slack specialists and prompt routing. --- .../new_chat/prompts/base/tool_routing_private.md | 11 +++++++++-- .../new_chat/prompts/base/tool_routing_team.md | 11 +++++++++-- .../app/agents/new_chat/prompts/routing/linear.md | 4 +++- .../app/agents/new_chat/prompts/routing/slack.md | 4 +++- .../app/agents/new_chat/subagents/__init__.py | 4 ++++ .../app/agents/new_chat/subagents/config.py | 12 ++++++++++++ .../app/agents/new_chat/system_prompt.py | 5 +++++ .../agents/new_chat/test_specialized_subagents.py | 10 ++++++++-- 8 files changed, 53 insertions(+), 8 deletions(-) diff --git a/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_private.md b/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_private.md index ec667bf88..b8bb069e2 100644 --- a/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_private.md +++ b/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_private.md @@ -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. 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 - 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 - Knowledge base content (Notion, GitHub, files, notes) → automatically searched - Real-time public web data → call web_search - 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. diff --git a/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_team.md b/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_team.md index 48b7a990b..b081a2123 100644 --- a/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_team.md +++ b/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_team.md @@ -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. 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 - 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 - Knowledge base content (Notion, GitHub, files, notes) → automatically searched - Real-time public web data → call web_search - 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. diff --git a/surfsense_backend/app/agents/new_chat/prompts/routing/linear.md b/surfsense_backend/app/agents/new_chat/prompts/routing/linear.md index 8b1378917..2f1bfacd9 100644 --- a/surfsense_backend/app/agents/new_chat/prompts/routing/linear.md +++ b/surfsense_backend/app/agents/new_chat/prompts/routing/linear.md @@ -1 +1,3 @@ - + +**Linear:** Prefer the `task` tool with subagent **`linear_specialist`** when the user’s 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. + diff --git a/surfsense_backend/app/agents/new_chat/prompts/routing/slack.md b/surfsense_backend/app/agents/new_chat/prompts/routing/slack.md index 8b1378917..4b5d07a9a 100644 --- a/surfsense_backend/app/agents/new_chat/prompts/routing/slack.md +++ b/surfsense_backend/app/agents/new_chat/prompts/routing/slack.md @@ -1 +1,3 @@ - + +**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. + diff --git a/surfsense_backend/app/agents/new_chat/subagents/__init__.py b/surfsense_backend/app/agents/new_chat/subagents/__init__.py index 7d678ec79..bd1823b57 100644 --- a/surfsense_backend/app/agents/new_chat/subagents/__init__.py +++ b/surfsense_backend/app/agents/new_chat/subagents/__init__.py @@ -20,10 +20,14 @@ from .config import ( build_report_writer_subagent, build_specialized_subagents, ) +from .providers.linear import build_linear_specialist_subagent +from .providers.slack import build_slack_specialist_subagent __all__ = [ "build_connector_negotiator_subagent", "build_explore_subagent", + "build_linear_specialist_subagent", "build_report_writer_subagent", + "build_slack_specialist_subagent", "build_specialized_subagents", ] diff --git a/surfsense_backend/app/agents/new_chat/subagents/config.py b/surfsense_backend/app/agents/new_chat/subagents/config.py index b36d35fa0..78436f674 100644 --- a/surfsense_backend/app/agents/new_chat/subagents/config.py +++ b/surfsense_backend/app/agents/new_chat/subagents/config.py @@ -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.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: from deepagents import SubAgent @@ -419,6 +425,12 @@ def build_specialized_subagents( build_report_writer_subagent( 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( tools=tools, model=model, extra_middleware=extra_middleware ), diff --git a/surfsense_backend/app/agents/new_chat/system_prompt.py b/surfsense_backend/app/agents/new_chat/system_prompt.py index 56f838d7e..70634c65d 100644 --- a/surfsense_backend/app/agents/new_chat/system_prompt.py +++ b/surfsense_backend/app/agents/new_chat/system_prompt.py @@ -26,6 +26,9 @@ from .prompts.composer import ( 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 # raw default-instructions text directly). SURFSENSE_SYSTEM_INSTRUCTIONS_TEMPLATE = ( @@ -63,6 +66,7 @@ def build_surfsense_system_prompt( mcp_connector_tools=mcp_connector_tools, citations_enabled=True, 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, citations_enabled=citations_enabled, model_name=model_name, + connector_routing=_DEFAULT_CONNECTOR_ROUTING, ) diff --git a/surfsense_backend/tests/unit/agents/new_chat/test_specialized_subagents.py b/surfsense_backend/tests/unit/agents/new_chat/test_specialized_subagents.py index 0adb578ce..3035cc8e0 100644 --- a/surfsense_backend/tests/unit/agents/new_chat/test_specialized_subagents.py +++ b/surfsense_backend/tests/unit/agents/new_chat/test_specialized_subagents.py @@ -210,10 +210,16 @@ class TestConnectorNegotiatorSubagent: class TestBuildSpecializedSubagents: - def test_returns_three_specs(self) -> None: + def test_returns_five_specs(self) -> None: specs = build_specialized_subagents(tools=ALL_TOOLS) 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: specs = build_specialized_subagents(tools=ALL_TOOLS)