mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-07 23:02:39 +02:00
Tune memory visibility and disable generic MCP supervisor routing.
This commit is contained in:
parent
f7ddb36c97
commit
5bc33626b9
7 changed files with 139 additions and 57 deletions
|
|
@ -39,7 +39,8 @@ _CONNECTOR_TYPE_TO_EXPERT_ROUTE: dict[str, str] = {
|
||||||
"CLICKUP_CONNECTOR": "clickup",
|
"CLICKUP_CONNECTOR": "clickup",
|
||||||
"SLACK_CONNECTOR": "slack",
|
"SLACK_CONNECTOR": "slack",
|
||||||
"AIRTABLE_CONNECTOR": "airtable",
|
"AIRTABLE_CONNECTOR": "airtable",
|
||||||
"MCP_CONNECTOR": "generic_mcp",
|
# generic_mcp route intentionally disabled for now.
|
||||||
|
# "MCP_CONNECTOR": "generic_mcp",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Ordering when appending MCP-only routes (no native registry slice for these types).
|
# Ordering when appending MCP-only routes (no native registry slice for these types).
|
||||||
|
|
@ -49,7 +50,8 @@ MCP_ONLY_ROUTE_KEYS_IN_ORDER: tuple[str, ...] = (
|
||||||
"jira",
|
"jira",
|
||||||
"clickup",
|
"clickup",
|
||||||
"airtable",
|
"airtable",
|
||||||
"generic_mcp",
|
# generic_mcp intentionally disabled for now.
|
||||||
|
# "generic_mcp",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,41 @@ from __future__ import annotations
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
|
|
||||||
import app.agents.multi_agent_chat.expert_agent.builtins.memory as memory_pkg
|
import app.agents.multi_agent_chat.expert_agent.builtins.memory as memory_pkg
|
||||||
|
from langchain.agents import create_agent
|
||||||
from langchain_core.language_models import BaseChatModel
|
from langchain_core.language_models import BaseChatModel
|
||||||
from langchain_core.tools import BaseTool
|
from langchain_core.tools import BaseTool
|
||||||
|
|
||||||
from app.agents.multi_agent_chat.core.agents import build_domain_agent
|
from app.agents.multi_agent_chat.core.prompts import read_prompt_md
|
||||||
|
from app.db import ChatVisibility
|
||||||
|
|
||||||
|
_PRIVATE_VISIBILITY_POLICY = (
|
||||||
|
"This thread is private. Store user-specific long-lived preferences, facts, and instructions."
|
||||||
|
)
|
||||||
|
_TEAM_VISIBILITY_POLICY = (
|
||||||
|
"This thread is shared with the search space. Store only team-appropriate shared preferences,"
|
||||||
|
" facts, and instructions that are safe for all members to inherit."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def build_memory_domain_agent(llm: BaseChatModel, tools: Sequence[BaseTool]):
|
def _render_memory_prompt(thread_visibility: ChatVisibility | None) -> str:
|
||||||
"""Compiled memory domain-agent graph."""
|
template = read_prompt_md(memory_pkg.__name__, "domain_prompt")
|
||||||
return build_domain_agent(
|
policy = (
|
||||||
llm,
|
_TEAM_VISIBILITY_POLICY
|
||||||
tools,
|
if thread_visibility == ChatVisibility.SEARCH_SPACE
|
||||||
prompt_package=memory_pkg.__name__,
|
else _PRIVATE_VISIBILITY_POLICY
|
||||||
prompt_stem="domain_prompt",
|
)
|
||||||
|
return template.replace("{{MEMORY_VISIBILITY_POLICY}}", policy)
|
||||||
|
|
||||||
|
|
||||||
|
def build_memory_domain_agent(
|
||||||
|
llm: BaseChatModel,
|
||||||
|
tools: Sequence[BaseTool],
|
||||||
|
*,
|
||||||
|
thread_visibility: ChatVisibility | None = None,
|
||||||
|
):
|
||||||
|
"""Compiled memory domain-agent graph."""
|
||||||
|
return create_agent(
|
||||||
|
llm,
|
||||||
|
system_prompt=_render_memory_prompt(thread_visibility),
|
||||||
|
tools=list(tools),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1 +1,56 @@
|
||||||
You are the memory domain agent. Use the update_memory tool only when the user explicitly asks to remember something, or when saving durable preferences and facts that should persist across sessions. Do not store secrets unless the user requests it. Respond concisely after updating memory.
|
You are the SurfSense memory operations sub-agent.
|
||||||
|
You receive delegated instructions from a supervisor agent and return structured results for supervisor synthesis.
|
||||||
|
|
||||||
|
<goal>
|
||||||
|
Persist durable preferences/facts/instructions with `update_memory` while avoiding transient or unsafe storage.
|
||||||
|
</goal>
|
||||||
|
|
||||||
|
<visibility_scope>
|
||||||
|
{{MEMORY_VISIBILITY_POLICY}}
|
||||||
|
</visibility_scope>
|
||||||
|
|
||||||
|
<available_tools>
|
||||||
|
- `update_memory`
|
||||||
|
</available_tools>
|
||||||
|
|
||||||
|
<tool_policy>
|
||||||
|
- Save only durable information with future value.
|
||||||
|
- Do not store transient chatter.
|
||||||
|
- Do not store secrets unless explicitly instructed.
|
||||||
|
- If memory intent is unclear, return `status=blocked` with the missing intent signal.
|
||||||
|
</tool_policy>
|
||||||
|
|
||||||
|
<out_of_scope>
|
||||||
|
- Do not execute non-memory tool actions.
|
||||||
|
- Do not store irrelevant, transient, or speculative information.
|
||||||
|
</out_of_scope>
|
||||||
|
|
||||||
|
<safety>
|
||||||
|
- Prefer minimal-memory writes over over-collection.
|
||||||
|
- Never claim memory was updated unless `update_memory` succeeded.
|
||||||
|
</safety>
|
||||||
|
|
||||||
|
<failure_policy>
|
||||||
|
- On tool failure, return `status=error` with concise recovery steps.
|
||||||
|
- When intent is ambiguous, return `status=blocked` with required disambiguation fields.
|
||||||
|
</failure_policy>
|
||||||
|
|
||||||
|
<output_contract>
|
||||||
|
Return **only** one JSON object (no markdown/prose):
|
||||||
|
{
|
||||||
|
"status": "success" | "partial" | "blocked" | "error",
|
||||||
|
"action_summary": string,
|
||||||
|
"evidence": {
|
||||||
|
"memory_updated": boolean,
|
||||||
|
"memory_category": "preference" | "fact" | "instruction" | null,
|
||||||
|
"stored_summary": string | null
|
||||||
|
},
|
||||||
|
"next_step": string | null,
|
||||||
|
"missing_fields": string[] | null,
|
||||||
|
"assumptions": string[] | null
|
||||||
|
}
|
||||||
|
Rules:
|
||||||
|
- `status=success` -> `next_step=null`, `missing_fields=null`.
|
||||||
|
- `status=partial|blocked|error` -> `next_step` must be non-null.
|
||||||
|
- `status=blocked` due to missing required inputs -> `missing_fields` must be non-null.
|
||||||
|
</output_contract>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
"""Registry-backed memory tools (long-term personal or team memory)."""
|
"""Registry-backed memory tools (long-term user or team memory)."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ def _compile_supervisor_chat_blocking(
|
||||||
llm,
|
llm,
|
||||||
tools=routing_tools,
|
tools=routing_tools,
|
||||||
checkpointer=checkpointer,
|
checkpointer=checkpointer,
|
||||||
|
thread_visibility=thread_visibility,
|
||||||
middleware=middleware,
|
middleware=middleware,
|
||||||
context_schema=SurfSenseContextSchema,
|
context_schema=SurfSenseContextSchema,
|
||||||
)
|
)
|
||||||
|
|
@ -161,6 +162,7 @@ async def create_multi_agent_chat(
|
||||||
include_deliverables=thread_id is not None,
|
include_deliverables=thread_id is not None,
|
||||||
mcp_tools_by_route=mcp_tools_by_route,
|
mcp_tools_by_route=mcp_tools_by_route,
|
||||||
available_connectors=resolved_connectors,
|
available_connectors=resolved_connectors,
|
||||||
|
thread_visibility=thread_visibility,
|
||||||
)
|
)
|
||||||
|
|
||||||
fs_sel = filesystem_selection or FilesystemSelection()
|
fs_sel = filesystem_selection or FilesystemSelection()
|
||||||
|
|
@ -168,7 +170,10 @@ async def create_multi_agent_chat(
|
||||||
|
|
||||||
if not surfsense_stack:
|
if not surfsense_stack:
|
||||||
return build_supervisor_agent(
|
return build_supervisor_agent(
|
||||||
llm, tools=routing_tools, checkpointer=checkpointer
|
llm,
|
||||||
|
tools=routing_tools,
|
||||||
|
checkpointer=checkpointer,
|
||||||
|
thread_visibility=thread_visibility,
|
||||||
)
|
)
|
||||||
|
|
||||||
return await asyncio.to_thread(
|
return await asyncio.to_thread(
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,8 @@ _ROUTE_REQUIRES_ANY: dict[str, frozenset[str]] = {
|
||||||
"jira": frozenset({"JIRA_CONNECTOR"}),
|
"jira": frozenset({"JIRA_CONNECTOR"}),
|
||||||
"clickup": frozenset({"CLICKUP_CONNECTOR"}),
|
"clickup": frozenset({"CLICKUP_CONNECTOR"}),
|
||||||
"airtable": frozenset({"AIRTABLE_CONNECTOR"}),
|
"airtable": frozenset({"AIRTABLE_CONNECTOR"}),
|
||||||
"generic_mcp": frozenset({"MCP_CONNECTOR"}),
|
# generic_mcp route intentionally disabled for now.
|
||||||
|
# "generic_mcp": frozenset({"MCP_CONNECTOR"}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ from typing import Any
|
||||||
from langchain_core.language_models import BaseChatModel
|
from langchain_core.language_models import BaseChatModel
|
||||||
from langchain_core.tools import BaseTool
|
from langchain_core.tools import BaseTool
|
||||||
|
|
||||||
|
from app.db import ChatVisibility
|
||||||
from app.agents.multi_agent_chat.expert_agent.builtins.deliverables import (
|
from app.agents.multi_agent_chat.expert_agent.builtins.deliverables import (
|
||||||
build_deliverables_tools,
|
build_deliverables_tools,
|
||||||
build_deliverables_domain_agent,
|
build_deliverables_domain_agent,
|
||||||
|
|
@ -67,31 +68,34 @@ from app.agents.multi_agent_chat.routing.route_connector_gate import include_con
|
||||||
|
|
||||||
_MCP_ONLY_ROUTE_DESCRIPTIONS: dict[str, str] = {
|
_MCP_ONLY_ROUTE_DESCRIPTIONS: dict[str, str] = {
|
||||||
"linear": (
|
"linear": (
|
||||||
"Route Linear work (issues, projects, cycles, documents) via MCP to the Linear sub-agent. "
|
"Use for Linear issue/project work: find/create issues, update status/assignees, review project progress, and inspect cycles."
|
||||||
"Pass a clear natural-language task."
|
|
||||||
),
|
),
|
||||||
"slack": (
|
"slack": (
|
||||||
"Route Slack search and channel/thread reads via MCP to the Slack sub-agent. "
|
"Use for Slack channel communication: read channel/thread history, summarize conversations, and post replies."
|
||||||
"Pass a clear natural-language task."
|
|
||||||
),
|
),
|
||||||
"jira": (
|
"jira": (
|
||||||
"Route Jira issues and projects via MCP to the Jira sub-agent. "
|
"Use for Jira issue/project workflows: search issues, inspect fields, update tickets, and move work through workflow states."
|
||||||
"Pass a clear natural-language task."
|
|
||||||
),
|
),
|
||||||
"clickup": (
|
"clickup": (
|
||||||
"Route ClickUp tasks via MCP to the ClickUp sub-agent. Pass a clear natural-language task."
|
"Use for ClickUp task management: find tasks/lists, update task fields, and track execution progress."
|
||||||
),
|
),
|
||||||
"airtable": (
|
"airtable": (
|
||||||
"Route Airtable bases and records via MCP to the Airtable sub-agent. "
|
"Use for Airtable structured data operations: locate bases/tables and create/read/update records."
|
||||||
"Pass a clear natural-language task."
|
|
||||||
),
|
|
||||||
"generic_mcp": (
|
|
||||||
"Route user-defined MCP (stdio) server tools to the custom MCP sub-agent. "
|
|
||||||
"Pass a clear natural-language task."
|
|
||||||
),
|
),
|
||||||
|
# generic_mcp intentionally disabled for now.
|
||||||
|
# "generic_mcp": (
|
||||||
|
# "Use as a fallback for custom connected app tasks not covered by a named specialist. "
|
||||||
|
# "Do not use if another specialist clearly matches."
|
||||||
|
# ),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _memory_route_description(thread_visibility: ChatVisibility | None) -> str:
|
||||||
|
if thread_visibility == ChatVisibility.SEARCH_SPACE:
|
||||||
|
return "Use for storing durable team memory: shared team preferences, conventions, and long-lived team facts."
|
||||||
|
return "Use for storing durable user memory: personal preferences, instructions, and long-lived user facts."
|
||||||
|
|
||||||
|
|
||||||
def build_supervisor_routing_tools(
|
def build_supervisor_routing_tools(
|
||||||
llm: BaseChatModel,
|
llm: BaseChatModel,
|
||||||
*,
|
*,
|
||||||
|
|
@ -99,6 +103,7 @@ def build_supervisor_routing_tools(
|
||||||
include_deliverables: bool = True,
|
include_deliverables: bool = True,
|
||||||
mcp_tools_by_route: dict[str, list[BaseTool]] | None = None,
|
mcp_tools_by_route: dict[str, list[BaseTool]] | None = None,
|
||||||
available_connectors: list[str] | None = None,
|
available_connectors: list[str] | None = None,
|
||||||
|
thread_visibility: ChatVisibility | None = None,
|
||||||
) -> list[BaseTool]:
|
) -> list[BaseTool]:
|
||||||
"""Build supervisor routing tools: builtins first, then connector experts (same pattern for all).
|
"""Build supervisor routing tools: builtins first, then connector experts (same pattern for all).
|
||||||
|
|
||||||
|
|
@ -112,8 +117,7 @@ def build_supervisor_routing_tools(
|
||||||
``mcp_tools_by_route`` maps route keys to MCP tools merged into the matching expert subgraph.
|
``mcp_tools_by_route`` maps route keys to MCP tools merged into the matching expert subgraph.
|
||||||
|
|
||||||
When ``available_connectors`` is set (searchable connector strings, same shape as ``new_chat``),
|
When ``available_connectors`` is set (searchable connector strings, same shape as ``new_chat``),
|
||||||
a vendor route is registered only if the connector is available **or** MCP tools are present for
|
a connector-backed route is registered only if its required searchable connector type is available.
|
||||||
that route.
|
|
||||||
"""
|
"""
|
||||||
if registry_dependencies is None:
|
if registry_dependencies is None:
|
||||||
return routing_tools_from_specs([])
|
return routing_tools_from_specs([])
|
||||||
|
|
@ -127,22 +131,22 @@ def build_supervisor_routing_tools(
|
||||||
DomainRoutingSpec(
|
DomainRoutingSpec(
|
||||||
tool_name="research",
|
tool_name="research",
|
||||||
description=(
|
description=(
|
||||||
"Route web search, page scraping, and SurfSense documentation help to the "
|
"Use for external research: find sources on the web, extract evidence, and answer documentation questions."
|
||||||
"research sub-agent. Pass a clear natural-language task."
|
|
||||||
),
|
),
|
||||||
domain_agent=research_agent,
|
domain_agent=research_agent,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
memory_tools = build_memory_tools(registry_dependencies)
|
memory_tools = build_memory_tools(registry_dependencies)
|
||||||
memory_agent = build_memory_domain_agent(llm, memory_tools)
|
memory_agent = build_memory_domain_agent(
|
||||||
|
llm,
|
||||||
|
memory_tools,
|
||||||
|
thread_visibility=thread_visibility,
|
||||||
|
)
|
||||||
specs.append(
|
specs.append(
|
||||||
DomainRoutingSpec(
|
DomainRoutingSpec(
|
||||||
tool_name="memory",
|
tool_name="memory",
|
||||||
description=(
|
description=_memory_route_description(thread_visibility),
|
||||||
"Route saving long-term facts and preferences (personal or team memory) to the "
|
|
||||||
"memory sub-agent. Pass a clear natural-language task."
|
|
||||||
),
|
|
||||||
domain_agent=memory_agent,
|
domain_agent=memory_agent,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -154,8 +158,7 @@ def build_supervisor_routing_tools(
|
||||||
DomainRoutingSpec(
|
DomainRoutingSpec(
|
||||||
tool_name="deliverables",
|
tool_name="deliverables",
|
||||||
description=(
|
description=(
|
||||||
"Route structured outputs (reports, podcasts, video presentations, resumes, "
|
"Use for creating final artifacts: reports, podcasts, video presentations, resumes, and images."
|
||||||
"images) to the deliverables sub-agent. Pass a clear natural-language task."
|
|
||||||
),
|
),
|
||||||
domain_agent=deliverables_agent,
|
domain_agent=deliverables_agent,
|
||||||
),
|
),
|
||||||
|
|
@ -171,8 +174,7 @@ def build_supervisor_routing_tools(
|
||||||
DomainRoutingSpec(
|
DomainRoutingSpec(
|
||||||
tool_name="calendar",
|
tool_name="calendar",
|
||||||
description=(
|
description=(
|
||||||
"Route Google Calendar work to the Calendar sub-agent. "
|
"Use for calendar planning and scheduling: check availability, read event details, create events, and update events."
|
||||||
"Pass a clear natural-language task."
|
|
||||||
),
|
),
|
||||||
domain_agent=calendar_agent,
|
domain_agent=calendar_agent,
|
||||||
),
|
),
|
||||||
|
|
@ -185,8 +187,7 @@ def build_supervisor_routing_tools(
|
||||||
DomainRoutingSpec(
|
DomainRoutingSpec(
|
||||||
tool_name="confluence",
|
tool_name="confluence",
|
||||||
description=(
|
description=(
|
||||||
"Route Confluence page work to the Confluence sub-agent. "
|
"Use for Confluence knowledge pages: search/read existing pages, create new pages, and update page content."
|
||||||
"Pass a clear natural-language task."
|
|
||||||
),
|
),
|
||||||
domain_agent=confluence_agent,
|
domain_agent=confluence_agent,
|
||||||
),
|
),
|
||||||
|
|
@ -199,8 +200,7 @@ def build_supervisor_routing_tools(
|
||||||
DomainRoutingSpec(
|
DomainRoutingSpec(
|
||||||
tool_name="discord",
|
tool_name="discord",
|
||||||
description=(
|
description=(
|
||||||
"Route Discord work (channels, messages) to the Discord sub-agent. "
|
"Use for Discord communication: read channel/thread messages, gather context, and send replies."
|
||||||
"Pass a clear natural-language task."
|
|
||||||
),
|
),
|
||||||
domain_agent=discord_agent,
|
domain_agent=discord_agent,
|
||||||
),
|
),
|
||||||
|
|
@ -213,7 +213,7 @@ def build_supervisor_routing_tools(
|
||||||
DomainRoutingSpec(
|
DomainRoutingSpec(
|
||||||
tool_name="dropbox",
|
tool_name="dropbox",
|
||||||
description=(
|
description=(
|
||||||
"Route Dropbox file work to the Dropbox sub-agent. Pass a clear natural-language task."
|
"Use for Dropbox file storage tasks: browse folders, read files, and manage Dropbox file content."
|
||||||
),
|
),
|
||||||
domain_agent=dropbox_agent,
|
domain_agent=dropbox_agent,
|
||||||
),
|
),
|
||||||
|
|
@ -228,8 +228,7 @@ def build_supervisor_routing_tools(
|
||||||
DomainRoutingSpec(
|
DomainRoutingSpec(
|
||||||
tool_name="gmail",
|
tool_name="gmail",
|
||||||
description=(
|
description=(
|
||||||
"Route Gmail-related work to the Gmail sub-agent. "
|
"Use for Gmail inbox actions: search/read emails, draft or update replies, send messages, and trash emails."
|
||||||
"Pass a clear natural-language task."
|
|
||||||
),
|
),
|
||||||
domain_agent=gmail_agent,
|
domain_agent=gmail_agent,
|
||||||
),
|
),
|
||||||
|
|
@ -242,8 +241,7 @@ def build_supervisor_routing_tools(
|
||||||
DomainRoutingSpec(
|
DomainRoutingSpec(
|
||||||
tool_name="google_drive",
|
tool_name="google_drive",
|
||||||
description=(
|
description=(
|
||||||
"Route Google Drive file work to the Google Drive sub-agent. "
|
"Use for Google Drive document/file tasks: locate files, inspect content, and manage Drive files or folders."
|
||||||
"Pass a clear natural-language task."
|
|
||||||
),
|
),
|
||||||
domain_agent=google_drive_agent,
|
domain_agent=google_drive_agent,
|
||||||
),
|
),
|
||||||
|
|
@ -256,8 +254,7 @@ def build_supervisor_routing_tools(
|
||||||
DomainRoutingSpec(
|
DomainRoutingSpec(
|
||||||
tool_name="luma",
|
tool_name="luma",
|
||||||
description=(
|
description=(
|
||||||
"Route Luma calendar events (list, read, create) to the Luma sub-agent. "
|
"Use for Luma event operations: list events, inspect event details, and create new events."
|
||||||
"Pass a clear natural-language task."
|
|
||||||
),
|
),
|
||||||
domain_agent=luma_agent,
|
domain_agent=luma_agent,
|
||||||
),
|
),
|
||||||
|
|
@ -270,7 +267,7 @@ def build_supervisor_routing_tools(
|
||||||
DomainRoutingSpec(
|
DomainRoutingSpec(
|
||||||
tool_name="notion",
|
tool_name="notion",
|
||||||
description=(
|
description=(
|
||||||
"Route Notion page work to the Notion sub-agent. Pass a clear natural-language task."
|
"Use for Notion workspace pages: create pages, update page content, and delete pages."
|
||||||
),
|
),
|
||||||
domain_agent=notion_agent,
|
domain_agent=notion_agent,
|
||||||
),
|
),
|
||||||
|
|
@ -283,8 +280,7 @@ def build_supervisor_routing_tools(
|
||||||
DomainRoutingSpec(
|
DomainRoutingSpec(
|
||||||
tool_name="onedrive",
|
tool_name="onedrive",
|
||||||
description=(
|
description=(
|
||||||
"Route Microsoft OneDrive file work to the OneDrive sub-agent. "
|
"Use for OneDrive file storage tasks: browse folders, read files, and manage OneDrive file content."
|
||||||
"Pass a clear natural-language task."
|
|
||||||
),
|
),
|
||||||
domain_agent=onedrive_agent,
|
domain_agent=onedrive_agent,
|
||||||
),
|
),
|
||||||
|
|
@ -297,8 +293,7 @@ def build_supervisor_routing_tools(
|
||||||
DomainRoutingSpec(
|
DomainRoutingSpec(
|
||||||
tool_name="teams",
|
tool_name="teams",
|
||||||
description=(
|
description=(
|
||||||
"Route Microsoft Teams work (channels, messages) to the Teams sub-agent. "
|
"Use for Microsoft Teams communication: read channel/thread messages, gather context, and post replies."
|
||||||
"Pass a clear natural-language task."
|
|
||||||
),
|
),
|
||||||
domain_agent=teams_agent,
|
domain_agent=teams_agent,
|
||||||
),
|
),
|
||||||
|
|
@ -312,7 +307,7 @@ def build_supervisor_routing_tools(
|
||||||
continue
|
continue
|
||||||
desc = _MCP_ONLY_ROUTE_DESCRIPTIONS.get(
|
desc = _MCP_ONLY_ROUTE_DESCRIPTIONS.get(
|
||||||
route_key,
|
route_key,
|
||||||
f"Route {route_key} MCP work to the {route_key} sub-agent. Pass a clear natural-language task.",
|
f"Use for {route_key} tasks related to that system's core work objects and workflows.",
|
||||||
)
|
)
|
||||||
specs.append(
|
specs.append(
|
||||||
DomainRoutingSpec(
|
DomainRoutingSpec(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue