diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/builder/sections/registry_subagents.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/builder/sections/registry_subagents.py
index 90f4cc2d6..191e86d33 100644
--- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/builder/sections/registry_subagents.py
+++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/builder/sections/registry_subagents.py
@@ -21,7 +21,6 @@ def build_registry_subagents_section(
"\n\n"
"These specialists are registered for **task** (routes without a matching connector are omitted).\n"
f"{bullets}\n"
- "The runtime may also offer a general-purpose **task** helper with your tools in a separate context.\n"
"Pick the specialist by **name**. Put full instructions in the task prompt; they do not see this thread.\n"
"\n"
)
diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/markdown/main_agent_tool_routing.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/markdown/main_agent_tool_routing.md
index 5b0fbea89..a3f0f7305 100644
--- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/markdown/main_agent_tool_routing.md
+++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/markdown/main_agent_tool_routing.md
@@ -1,12 +1,11 @@
-Use **task** for any work beyond your direct SurfSense tools. Two builtin
-specialists are always available:
+Use **task** for any work beyond your direct SurfSense tools. The
+**knowledge_base** specialist is always available:
- **knowledge_base** — owns the user's workspace (documents and folders). Route
here whenever the user wants to create, read, edit, search, organise, or
remove a document or folder (e.g. *"save these notes to my KB"*, *"find my Q2
roadmap"*, *"rename this folder"*).
-- **general_purpose** — ad-hoc multi-step work that doesn't fit any specialist.
The connector specialists listed in `` (later in this
prompt) cover calendar, mail, chat, tickets, third-party documents,
diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/file_intent.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/file_intent.py
deleted file mode 100644
index 5ff65aa12..000000000
--- a/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/file_intent.py
+++ /dev/null
@@ -1,11 +0,0 @@
-"""File-intent classifier that gates strict write contracts."""
-
-from __future__ import annotations
-
-from langchain_core.language_models import BaseChatModel
-
-from app.agents.new_chat.middleware import FileIntentMiddleware
-
-
-def build_file_intent_mw(llm: BaseChatModel) -> FileIntentMiddleware:
- return FileIntentMiddleware(llm=llm)
diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/permissions/__init__.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/permissions/__init__.py
deleted file mode 100644
index 4f2228170..000000000
--- a/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/permissions/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-"""Permission rulesets fanned out to parent / general-purpose / subagent stacks."""
-
-from __future__ import annotations
-
-from .context import PermissionContext, build_permission_context
-from .middleware import build_full_permission_mw
-
-__all__ = [
- "PermissionContext",
- "build_full_permission_mw",
- "build_permission_context",
-]
diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/permissions/context.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/permissions/context.py
deleted file mode 100644
index e121421a0..000000000
--- a/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/permissions/context.py
+++ /dev/null
@@ -1,107 +0,0 @@
-"""Derive shared permission context once; fan out to all three stack layers.
-
-The context carries:
-- ``rulesets``: full ask/deny/allow rules for the main-agent permission middleware.
-- ``general_purpose_interrupt_on``: ``ask`` rules mirrored as deepagents
- ``interrupt_on`` so HITL still triggers from inside ``task`` runs (subagents
- bypass the main-agent permission middleware).
-- ``subagent_deny_mw``: a deny-only ``PermissionMiddleware`` instance shared
- across the general-purpose and registry subagent stacks.
-"""
-
-from __future__ import annotations
-
-from collections.abc import Sequence
-from dataclasses import dataclass
-
-from langchain_core.tools import BaseTool
-
-from app.agents.new_chat.feature_flags import AgentFeatureFlags
-from app.agents.new_chat.filesystem_selection import FilesystemMode
-from app.agents.new_chat.middleware import PermissionMiddleware
-from app.agents.new_chat.permissions import Rule, Ruleset
-from app.agents.new_chat.tools.registry import BUILTIN_TOOLS
-
-from ..flags import enabled
-
-
-@dataclass(frozen=True)
-class PermissionContext:
- rulesets: list[Ruleset]
- general_purpose_interrupt_on: dict[str, bool]
- subagent_deny_mw: PermissionMiddleware | None
-
-
-def build_permission_context(
- *,
- flags: AgentFeatureFlags,
- filesystem_mode: FilesystemMode,
- tools: Sequence[BaseTool],
- available_connectors: list[str] | None,
-) -> PermissionContext:
- is_desktop_fs = filesystem_mode == FilesystemMode.DESKTOP_LOCAL_FOLDER
- permission_enabled = enabled(flags, "enable_permission")
-
- rulesets: list[Ruleset] = []
- if permission_enabled or is_desktop_fs:
- rulesets.append(
- Ruleset(
- rules=[Rule(permission="*", pattern="*", action="allow")],
- origin="surfsense_defaults",
- )
- )
- if is_desktop_fs:
- rulesets.append(
- Ruleset(
- rules=[
- Rule(permission="rm", pattern="*", action="ask"),
- Rule(permission="rmdir", pattern="*", action="ask"),
- Rule(permission="move_file", pattern="*", action="ask"),
- Rule(permission="edit_file", pattern="*", action="ask"),
- Rule(permission="write_file", pattern="*", action="ask"),
- ],
- origin="desktop_safety",
- )
- )
-
- tool_names_in_use = {t.name for t in tools}
-
- if permission_enabled:
- available_set = set(available_connectors or [])
- synthesized: list[Rule] = []
- for tool_def in BUILTIN_TOOLS:
- if tool_def.name not in tool_names_in_use:
- continue
- rc = tool_def.required_connector
- if rc and rc not in available_set:
- synthesized.append(
- Rule(permission=tool_def.name, pattern="*", action="deny")
- )
- if synthesized:
- rulesets.append(Ruleset(rules=synthesized, origin="connector_synthesized"))
-
- general_purpose_interrupt_on: dict[str, bool] = {
- rule.permission: True
- for rs in rulesets
- for rule in rs.rules
- if rule.action == "ask" and rule.permission in tool_names_in_use
- }
-
- deny_rulesets = [
- Ruleset(
- rules=[r for r in rs.rules if r.action == "deny"],
- origin=rs.origin,
- )
- for rs in rulesets
- ]
- deny_rulesets = [rs for rs in deny_rulesets if rs.rules]
-
- subagent_deny_mw: PermissionMiddleware | None = (
- PermissionMiddleware(rulesets=deny_rulesets) if deny_rulesets else None
- )
-
- return PermissionContext(
- rulesets=rulesets,
- general_purpose_interrupt_on=general_purpose_interrupt_on,
- subagent_deny_mw=subagent_deny_mw,
- )
diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/permissions/middleware.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/permissions/middleware.py
deleted file mode 100644
index 704a26fb3..000000000
--- a/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/permissions/middleware.py
+++ /dev/null
@@ -1,10 +0,0 @@
-"""Main-agent permission middleware (full ask/deny/allow rules)."""
-
-from __future__ import annotations
-
-from app.agents.new_chat.middleware import PermissionMiddleware
-from app.agents.new_chat.permissions import Ruleset
-
-
-def build_full_permission_mw(rulesets: list[Ruleset]) -> PermissionMiddleware | None:
- return PermissionMiddleware(rulesets=rulesets) if rulesets else None
diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/resilience/__init__.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/resilience/__init__.py
index 92596b771..377f93964 100644
--- a/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/resilience/__init__.py
+++ b/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/resilience/__init__.py
@@ -1,4 +1,4 @@
-"""Resilience middleware shared as the same instances across parent / general-purpose / registry."""
+"""Resilience middleware shared as the same instances across parent / registry."""
from __future__ import annotations
diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/stack.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/stack.py
index 754f4d1b8..dc9c27b68 100644
--- a/surfsense_backend/app/agents/multi_agent_chat/middleware/stack.py
+++ b/surfsense_backend/app/agents/multi_agent_chat/middleware/stack.py
@@ -23,9 +23,6 @@ from app.agents.multi_agent_chat.subagents import (
build_subagents,
get_subagents_to_exclude,
)
-from app.agents.multi_agent_chat.subagents.builtins.general_purpose.agent import (
- build_subagent as build_general_purpose_subagent,
-)
from app.agents.multi_agent_chat.subagents.builtins.knowledge_base.agent import (
build_subagent as build_knowledge_base_subagent,
)
@@ -56,10 +53,6 @@ from .shared.compaction import build_compaction_mw
from .shared.kb_context_projection import build_kb_context_projection_mw
from .shared.memory import build_memory_mw
from .shared.patch_tool_calls import build_patch_tool_calls_mw
-from .shared.permissions import (
- build_full_permission_mw,
- build_permission_context,
-)
from .shared.resilience import build_resilience_bundle
from .shared.todos import build_todos_mw
from .subagent.extras import build_subagent_extras
@@ -87,34 +80,14 @@ def build_main_agent_deepagent_middleware(
disabled_tools: list[str] | None = None,
) -> list[Any]:
"""Ordered middleware for ``create_agent`` (None entries already stripped)."""
- permissions = build_permission_context(
- flags=flags,
- filesystem_mode=filesystem_mode,
- tools=tools,
- available_connectors=available_connectors,
- )
resilience = build_resilience_bundle(flags)
- # Single instance threaded into both the main-agent stack and the general-purpose subagent.
memory_mw = build_memory_mw(
user_id=user_id,
search_space_id=search_space_id,
visibility=visibility,
)
- general_purpose_subagent = build_general_purpose_subagent(
- llm=llm,
- tools=tools,
- backend_resolver=backend_resolver,
- filesystem_mode=filesystem_mode,
- search_space_id=search_space_id,
- user_id=user_id,
- thread_id=thread_id,
- permissions=permissions,
- resilience=resilience,
- memory_mw=memory_mw,
- )
-
knowledge_base_subagent = build_knowledge_base_subagent(
llm=llm,
backend_resolver=backend_resolver,
@@ -122,14 +95,12 @@ def build_main_agent_deepagent_middleware(
search_space_id=search_space_id,
user_id=user_id,
thread_id=thread_id,
- permissions=permissions,
resilience=resilience,
)
subagents_registry: list[SubAgent] = []
try:
subagent_extras = build_subagent_extras(
- permissions=permissions,
resilience=resilience,
)
subagents_registry = build_subagents(
@@ -145,15 +116,14 @@ def build_main_agent_deepagent_middleware(
[s["name"] for s in subagents_registry],
)
except Exception:
- # Degrade to general-purpose-only rather than aborting the turn:
+ # Degrade to KB-only rather than aborting the turn:
# one bad subagent dep should not deny the user a response.
logging.exception(
- "Subagents registry build failed; falling back to general-purpose only"
+ "Subagents registry build failed; falling back to knowledge_base only"
)
subagents_registry = []
subagents: list[SubAgent] = [
- general_purpose_subagent,
knowledge_base_subagent,
*subagents_registry,
]
@@ -209,7 +179,6 @@ def build_main_agent_deepagent_middleware(
resilience.retry,
resilience.fallback,
build_repair_mw(flags=flags, tools=tools),
- build_full_permission_mw(permissions.rulesets),
build_doom_loop_mw(flags),
build_action_log_mw(
flags=flags,
diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/subagent/extras.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/subagent/extras.py
index 46dca8a81..687f7d36c 100644
--- a/surfsense_backend/app/agents/multi_agent_chat/middleware/subagent/extras.py
+++ b/surfsense_backend/app/agents/multi_agent_chat/middleware/subagent/extras.py
@@ -2,27 +2,23 @@
Registry subagents are scoped to one domain (deliverables, research, memory,
connectors, MCP) and never read or write the SurfSense filesystem — that
-capability belongs to the main agent and is delegated to the general-purpose
-subagent as an escape hatch. Keeping FS off the registry stacks avoids
-polluting their tool surface with FS tools they never act on.
+capability belongs to the ``knowledge_base`` subagent. Keeping FS off the
+registry stacks avoids polluting their tool surface with FS tools they
+never act on.
"""
from __future__ import annotations
from typing import Any
-from ..shared.permissions import PermissionContext
from ..shared.resilience import ResilienceBundle
from ..shared.todos import build_todos_mw
def build_subagent_extras(
*,
- permissions: PermissionContext,
resilience: ResilienceBundle,
) -> list[Any]:
extras: list[Any] = [build_todos_mw()]
- if permissions.subagent_deny_mw is not None:
- extras.append(permissions.subagent_deny_mw)
extras.extend(resilience.as_list())
return extras
diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/general_purpose/__init__.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/general_purpose/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/general_purpose/agent.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/general_purpose/agent.py
deleted file mode 100644
index 1c3c44f12..000000000
--- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/general_purpose/agent.py
+++ /dev/null
@@ -1,105 +0,0 @@
-"""General-purpose subagent for the multi-agent main agent."""
-
-from __future__ import annotations
-
-from collections.abc import Sequence
-from typing import Any, cast
-
-from deepagents import SubAgent
-from deepagents.middleware.patch_tool_calls import PatchToolCallsMiddleware
-from deepagents.middleware.subagents import GENERAL_PURPOSE_SUBAGENT
-from langchain_anthropic.middleware import AnthropicPromptCachingMiddleware
-from langchain_core.language_models import BaseChatModel
-from langchain_core.tools import BaseTool
-
-from app.agents.multi_agent_chat.middleware.shared.anthropic_cache import (
- build_anthropic_cache_mw,
-)
-from app.agents.multi_agent_chat.middleware.shared.compaction import (
- build_compaction_mw,
-)
-from app.agents.multi_agent_chat.middleware.shared.file_intent import (
- build_file_intent_mw,
-)
-from app.agents.multi_agent_chat.middleware.shared.filesystem import (
- build_filesystem_mw,
-)
-from app.agents.multi_agent_chat.middleware.shared.patch_tool_calls import (
- build_patch_tool_calls_mw,
-)
-from app.agents.multi_agent_chat.middleware.shared.permissions import (
- PermissionContext,
-)
-from app.agents.multi_agent_chat.middleware.shared.resilience import (
- ResilienceBundle,
-)
-from app.agents.multi_agent_chat.middleware.shared.todos import build_todos_mw
-from app.agents.new_chat.filesystem_selection import FilesystemMode
-from app.agents.new_chat.middleware import MemoryInjectionMiddleware
-
-NAME = "general-purpose"
-
-
-def build_subagent(
- *,
- llm: BaseChatModel,
- tools: Sequence[BaseTool],
- backend_resolver: Any,
- filesystem_mode: FilesystemMode,
- search_space_id: int,
- user_id: str | None,
- thread_id: int | None,
- permissions: PermissionContext,
- resilience: ResilienceBundle,
- memory_mw: MemoryInjectionMiddleware,
-) -> SubAgent:
- """Deny + resilience inserts encapsulated here so the orchestrator never mutates the list."""
- middleware: list[Any] = [
- build_todos_mw(),
- memory_mw,
- build_file_intent_mw(llm),
- build_filesystem_mw(
- backend_resolver=backend_resolver,
- filesystem_mode=filesystem_mode,
- search_space_id=search_space_id,
- user_id=user_id,
- thread_id=thread_id,
- ),
- build_compaction_mw(llm),
- build_patch_tool_calls_mw(),
- build_anthropic_cache_mw(),
- ]
-
- if permissions.subagent_deny_mw is not None:
- patch_idx = next(
- (
- i
- for i, m in enumerate(middleware)
- if isinstance(m, PatchToolCallsMiddleware)
- ),
- len(middleware),
- )
- middleware.insert(patch_idx, permissions.subagent_deny_mw)
-
- resilience_mws = resilience.as_list()
- if resilience_mws:
- cache_idx = next(
- (
- i
- for i, m in enumerate(middleware)
- if isinstance(m, AnthropicPromptCachingMiddleware)
- ),
- len(middleware),
- )
- for offset, mw in enumerate(resilience_mws):
- middleware.insert(cache_idx + offset, mw)
-
- spec: dict[str, Any] = {
- **GENERAL_PURPOSE_SUBAGENT,
- "model": llm,
- "tools": tools,
- "middleware": middleware,
- }
- if permissions.general_purpose_interrupt_on:
- spec["interrupt_on"] = permissions.general_purpose_interrupt_on
- return cast(SubAgent, spec)
diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/agent.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/agent.py
index 52b2c97c4..bf6ec6753 100644
--- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/agent.py
+++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/agent.py
@@ -11,7 +11,6 @@ from __future__ import annotations
from typing import Any, cast
from deepagents import SubAgent
-from deepagents.middleware.patch_tool_calls import PatchToolCallsMiddleware
from langchain_anthropic.middleware import AnthropicPromptCachingMiddleware
from langchain_core.language_models import BaseChatModel
@@ -30,9 +29,6 @@ from app.agents.multi_agent_chat.middleware.shared.kb_context_projection import
from app.agents.multi_agent_chat.middleware.shared.patch_tool_calls import (
build_patch_tool_calls_mw,
)
-from app.agents.multi_agent_chat.middleware.shared.permissions import (
- PermissionContext,
-)
from app.agents.multi_agent_chat.middleware.shared.resilience import (
ResilienceBundle,
)
@@ -55,10 +51,9 @@ def build_subagent(
search_space_id: int,
user_id: str | None,
thread_id: int | None,
- permissions: PermissionContext,
resilience: ResilienceBundle,
) -> SubAgent:
- """Deny + resilience inserts encapsulated here so the orchestrator never mutates the list."""
+ """Resilience inserts encapsulated here so the orchestrator never mutates the list."""
description = read_md_file(__package__, "description").strip()
if not description:
description = (
@@ -86,17 +81,6 @@ def build_subagent(
build_anthropic_cache_mw(),
]
- if permissions.subagent_deny_mw is not None:
- patch_idx = next(
- (
- i
- for i, m in enumerate(middleware)
- if isinstance(m, PatchToolCallsMiddleware)
- ),
- len(middleware),
- )
- middleware.insert(patch_idx, permissions.subagent_deny_mw)
-
resilience_mws = resilience.as_list()
if resilience_mws:
cache_idx = next(