refactor(agents): colocate 8 main-agent-only middleware as per-concept folders

Each main-agent-only middleware now lives in its own folder under
main_agent/middleware/<concept>/ with builder.py (flag-gated construction)
+ middleware.py (the impl), re-exported via __init__.py. This kills the
cross-folder hop into agents/shared/middleware and keeps each middleware's
two responsibilities (build vs behavior) as colocated siblings.

Moved (impl from shared/middleware, builder from main_agent/middleware):
action_log, anonymous_document, context_editing, doom_loop, knowledge_tree,
noop_injection, otel_span, tool_call_repair.

Impls moved verbatim (git rename, no body edits) so behavior is unchanged.
Builders now import from the local .middleware sibling. stack.py import
paths updated for the 3 renamed folders; shared middleware barrel trimmed;
tests repointed (imports + patch targets).
This commit is contained in:
CREDO23 2026-06-05 11:42:58 +02:00
parent fbd5ccc35a
commit 9493519c61
33 changed files with 149 additions and 83 deletions

View file

@ -0,0 +1,10 @@
"""Action-log middleware: audit row per tool call (impl + builder)."""
from .builder import build_action_log_mw
from .middleware import ActionLogMiddleware, ToolDefinition
__all__ = [
"ActionLogMiddleware",
"ToolDefinition",
"build_action_log_mw",
]

View file

@ -4,10 +4,10 @@ from __future__ import annotations
import logging
from app.agents.shared.feature_flags import AgentFeatureFlags
from app.agents.shared.middleware import ActionLogMiddleware
from app.agents.multi_agent_chat.shared.middleware.flags import enabled
from app.agents.shared.feature_flags import AgentFeatureFlags
from .middleware import ActionLogMiddleware
def build_action_log_mw(

View file

@ -0,0 +1,9 @@
"""Anonymous-document middleware: Redis hydration, cloud only (impl + builder)."""
from .builder import build_anonymous_doc_mw
from .middleware import AnonymousDocumentMiddleware
__all__ = [
"AnonymousDocumentMiddleware",
"build_anonymous_doc_mw",
]

View file

@ -3,7 +3,8 @@
from __future__ import annotations
from app.agents.shared.filesystem_selection import FilesystemMode
from app.agents.shared.middleware import AnonymousDocumentMiddleware
from .middleware import AnonymousDocumentMiddleware
def build_anonymous_doc_mw(

View file

@ -0,0 +1,15 @@
"""Context-editing middleware: spill + clear-tool-uses passes (impl + builder)."""
from .builder import build_context_editing_mw
from .middleware import (
ClearToolUsesEdit,
SpillingContextEditingMiddleware,
SpillToBackendEdit,
)
__all__ = [
"ClearToolUsesEdit",
"SpillToBackendEdit",
"SpillingContextEditingMiddleware",
"build_context_editing_mw",
]

View file

@ -10,15 +10,15 @@ from langchain_core.tools import BaseTool
from app.agents.multi_agent_chat.main_agent.context_prune.prune_tool_names import (
safe_exclude_tools,
)
from app.agents.multi_agent_chat.shared.middleware.flags import enabled
from app.agents.shared.feature_flags import AgentFeatureFlags
from app.agents.shared.middleware import (
from .middleware import (
ClearToolUsesEdit,
SpillingContextEditingMiddleware,
SpillToBackendEdit,
)
from app.agents.multi_agent_chat.shared.middleware.flags import enabled
def build_context_editing_mw(
*,

View file

@ -0,0 +1,9 @@
"""Doom-loop middleware: detect repeated identical tool calls (impl + builder)."""
from .builder import build_doom_loop_mw
from .middleware import DoomLoopMiddleware
__all__ = [
"DoomLoopMiddleware",
"build_doom_loop_mw",
]

View file

@ -2,10 +2,10 @@
from __future__ import annotations
from app.agents.shared.feature_flags import AgentFeatureFlags
from app.agents.shared.middleware import DoomLoopMiddleware
from app.agents.multi_agent_chat.shared.middleware.flags import enabled
from app.agents.shared.feature_flags import AgentFeatureFlags
from .middleware import DoomLoopMiddleware
def build_doom_loop_mw(flags: AgentFeatureFlags) -> DoomLoopMiddleware | None:

View file

@ -0,0 +1,9 @@
"""Knowledge-tree middleware: <workspace_tree> injection, cloud only (impl + builder)."""
from .builder import build_knowledge_tree_mw
from .middleware import KnowledgeTreeMiddleware
__all__ = [
"KnowledgeTreeMiddleware",
"build_knowledge_tree_mw",
]

View file

@ -5,7 +5,8 @@ from __future__ import annotations
from langchain_core.language_models import BaseChatModel
from app.agents.shared.filesystem_selection import FilesystemMode
from app.agents.shared.middleware import KnowledgeTreeMiddleware
from .middleware import KnowledgeTreeMiddleware
def build_knowledge_tree_mw(

View file

@ -0,0 +1,9 @@
"""Noop-injection middleware: provider-compat _noop tool (impl + builder)."""
from .builder import build_noop_injection_mw
from .middleware import NoopInjectionMiddleware
__all__ = [
"NoopInjectionMiddleware",
"build_noop_injection_mw",
]

View file

@ -2,10 +2,10 @@
from __future__ import annotations
from app.agents.shared.feature_flags import AgentFeatureFlags
from app.agents.shared.middleware import NoopInjectionMiddleware
from app.agents.multi_agent_chat.shared.middleware.flags import enabled
from app.agents.shared.feature_flags import AgentFeatureFlags
from .middleware import NoopInjectionMiddleware
def build_noop_injection_mw(flags: AgentFeatureFlags) -> NoopInjectionMiddleware | None:

View file

@ -0,0 +1,9 @@
"""OTel-span middleware: spans on model and tool calls (impl + builder)."""
from .builder import build_otel_mw
from .middleware import OtelSpanMiddleware
__all__ = [
"OtelSpanMiddleware",
"build_otel_mw",
]

View file

@ -2,10 +2,10 @@
from __future__ import annotations
from app.agents.shared.feature_flags import AgentFeatureFlags
from app.agents.shared.middleware import OtelSpanMiddleware
from app.agents.multi_agent_chat.shared.middleware.flags import enabled
from app.agents.shared.feature_flags import AgentFeatureFlags
from .middleware import OtelSpanMiddleware
def build_otel_mw(flags: AgentFeatureFlags) -> OtelSpanMiddleware | None:

View file

@ -20,6 +20,24 @@ from langchain_core.language_models import BaseChatModel
from langchain_core.tools import BaseTool
from langgraph.types import Checkpointer
from app.agents.multi_agent_chat.shared.middleware.anthropic_cache import (
build_anthropic_cache_mw,
)
from app.agents.multi_agent_chat.shared.middleware.compaction import build_compaction_mw
from app.agents.multi_agent_chat.shared.middleware.kb_context_projection import (
build_kb_context_projection_mw,
)
from app.agents.multi_agent_chat.shared.middleware.memory import build_memory_mw
from app.agents.multi_agent_chat.shared.middleware.patch_tool_calls import (
build_patch_tool_calls_mw,
)
from app.agents.multi_agent_chat.shared.middleware.permissions import (
build_permission_mw,
)
from app.agents.multi_agent_chat.shared.middleware.resilience import (
build_resilience_middlewares,
)
from app.agents.multi_agent_chat.shared.middleware.todos import build_todos_mw
from app.agents.multi_agent_chat.subagents import (
build_subagents,
get_subagents_to_exclude,
@ -31,12 +49,15 @@ from app.agents.multi_agent_chat.subagents.builtins.knowledge_base.agent import
from app.agents.multi_agent_chat.subagents.builtins.knowledge_base.ask_knowledge_base_tool import (
build_ask_knowledge_base_tool,
)
from app.agents.multi_agent_chat.subagents.shared.middleware.middleware_stack import (
build_subagent_middleware_stack,
)
from app.agents.shared.feature_flags import AgentFeatureFlags
from app.agents.shared.filesystem_selection import FilesystemMode
from app.db import ChatVisibility
from .action_log import build_action_log_mw
from .anonymous_doc import build_anonymous_doc_mw
from .anonymous_document import build_anonymous_doc_mw
from .busy_mutex import build_busy_mutex_mw
from .checkpointed_subagent_middleware import (
SurfSenseCheckpointedSubAgentMiddleware,
@ -51,21 +72,10 @@ from .kb_persistence import build_kb_persistence_mw
from .knowledge_priority import build_knowledge_priority_mw
from .knowledge_tree import build_knowledge_tree_mw
from .noop_injection import build_noop_injection_mw
from .otel import build_otel_mw
from .otel_span import build_otel_mw
from .plugins import build_plugin_middlewares
from .repair import build_repair_mw
from .skills import build_skills_mw
from app.agents.multi_agent_chat.shared.middleware.anthropic_cache import build_anthropic_cache_mw
from app.agents.multi_agent_chat.shared.middleware.compaction import build_compaction_mw
from app.agents.multi_agent_chat.shared.middleware.kb_context_projection import build_kb_context_projection_mw
from app.agents.multi_agent_chat.shared.middleware.memory import build_memory_mw
from app.agents.multi_agent_chat.shared.middleware.patch_tool_calls import build_patch_tool_calls_mw
from app.agents.multi_agent_chat.shared.middleware.permissions import build_permission_mw
from app.agents.multi_agent_chat.shared.middleware.resilience import build_resilience_middlewares
from app.agents.multi_agent_chat.shared.middleware.todos import build_todos_mw
from app.agents.multi_agent_chat.subagents.shared.middleware.middleware_stack import (
build_subagent_middleware_stack,
)
from .tool_call_repair import build_repair_mw
def build_main_agent_deepagent_middleware(

View file

@ -0,0 +1,9 @@
"""Tool-call-repair middleware: fix miscased/unknown tool names (impl + builder)."""
from .builder import build_repair_mw
from .middleware import ToolCallNameRepairMiddleware
__all__ = [
"ToolCallNameRepairMiddleware",
"build_repair_mw",
]

View file

@ -6,10 +6,10 @@ from collections.abc import Sequence
from langchain_core.tools import BaseTool
from app.agents.shared.feature_flags import AgentFeatureFlags
from app.agents.shared.middleware import ToolCallNameRepairMiddleware
from app.agents.multi_agent_chat.shared.middleware.flags import enabled
from app.agents.shared.feature_flags import AgentFeatureFlags
from .middleware import ToolCallNameRepairMiddleware
# deepagents-built-in tool names the repair pass treats as known.
_DEEPAGENT_BUILTIN_TOOL_NAMES: frozenset[str] = frozenset(

View file

@ -1,23 +1,10 @@
"""Middleware components for the SurfSense new chat agent."""
"""Shared middleware components for the SurfSense chat agents."""
from app.agents.shared.middleware.action_log import (
ActionLogMiddleware,
ToolDefinition,
)
from app.agents.shared.middleware.anonymous_document import (
AnonymousDocumentMiddleware,
)
from app.agents.shared.middleware.busy_mutex import BusyMutexMiddleware
from app.agents.shared.middleware.compaction import (
SurfSenseCompactionMiddleware,
create_surfsense_compaction_middleware,
)
from app.agents.shared.middleware.context_editing import (
ClearToolUsesEdit,
SpillingContextEditingMiddleware,
SpillToBackendEdit,
)
from app.agents.shared.middleware.doom_loop import DoomLoopMiddleware
from app.agents.shared.middleware.kb_persistence import (
KnowledgeBasePersistenceMiddleware,
commit_staged_filesystem_state,
@ -25,39 +12,20 @@ from app.agents.shared.middleware.kb_persistence import (
from app.agents.shared.middleware.knowledge_search import (
KnowledgePriorityMiddleware,
)
from app.agents.shared.middleware.knowledge_tree import (
KnowledgeTreeMiddleware,
)
from app.agents.shared.middleware.memory_injection import (
MemoryInjectionMiddleware,
)
from app.agents.shared.middleware.noop_injection import NoopInjectionMiddleware
from app.agents.shared.middleware.otel_span import OtelSpanMiddleware
from app.agents.shared.middleware.permission import PermissionMiddleware
from app.agents.shared.middleware.retry_after import RetryAfterMiddleware
from app.agents.shared.middleware.tool_call_repair import (
ToolCallNameRepairMiddleware,
)
__all__ = [
"ActionLogMiddleware",
"AnonymousDocumentMiddleware",
"BusyMutexMiddleware",
"ClearToolUsesEdit",
"DoomLoopMiddleware",
"KnowledgeBasePersistenceMiddleware",
"KnowledgePriorityMiddleware",
"KnowledgeTreeMiddleware",
"MemoryInjectionMiddleware",
"NoopInjectionMiddleware",
"OtelSpanMiddleware",
"PermissionMiddleware",
"RetryAfterMiddleware",
"SpillToBackendEdit",
"SpillingContextEditingMiddleware",
"SurfSenseCompactionMiddleware",
"ToolCallNameRepairMiddleware",
"ToolDefinition",
"commit_staged_filesystem_state",
"create_surfsense_compaction_middleware",
]

View file

@ -10,9 +10,11 @@ import pytest
from langchain_core.messages import ToolMessage
from langchain_core.tools import tool
from app.agents.multi_agent_chat.main_agent.middleware.action_log.middleware import (
ActionLogMiddleware,
ToolDefinition,
)
from app.agents.shared.feature_flags import AgentFeatureFlags
from app.agents.shared.middleware.action_log import ActionLogMiddleware
from app.agents.shared.middleware.action_log import ToolDefinition
@dataclass
@ -58,7 +60,7 @@ def _disabled_flags() -> AgentFeatureFlags:
def patch_get_flags():
def _patch(flags: AgentFeatureFlags):
return patch(
"app.agents.shared.middleware.action_log.get_flags",
"app.agents.multi_agent_chat.main_agent.middleware.action_log.middleware.get_flags",
return_value=flags,
)
@ -360,7 +362,7 @@ class TestActionLogDispatch:
patch_get_flags(_enabled_flags()),
patch("app.db.shielded_async_session", side_effect=lambda: factory()),
patch(
"app.agents.shared.middleware.action_log.adispatch_custom_event",
"app.agents.multi_agent_chat.main_agent.middleware.action_log.middleware.adispatch_custom_event",
dispatch_mock,
),
):
@ -395,7 +397,7 @@ class TestActionLogDispatch:
patch_get_flags(_enabled_flags()),
patch("app.db.shielded_async_session", side_effect=_exploding_session),
patch(
"app.agents.shared.middleware.action_log.adispatch_custom_event",
"app.agents.multi_agent_chat.main_agent.middleware.action_log.middleware.adispatch_custom_event",
dispatch_mock,
),
):

View file

@ -7,7 +7,7 @@ from typing import Any
import pytest
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
from app.agents.shared.middleware.context_editing import (
from app.agents.multi_agent_chat.main_agent.middleware.context_editing.middleware import (
SpillToBackendEdit,
_build_spill_placeholder,
)

View file

@ -5,7 +5,10 @@ from __future__ import annotations
import pytest
from langchain_core.messages import AIMessage
from app.agents.shared.middleware.doom_loop import DoomLoopMiddleware, _signature
from app.agents.multi_agent_chat.main_agent.middleware.doom_loop.middleware import (
DoomLoopMiddleware,
_signature,
)
pytestmark = pytest.mark.unit

View file

@ -5,7 +5,7 @@ from __future__ import annotations
import pytest
from langchain_core.messages import AIMessage, HumanMessage
from app.agents.shared.middleware.noop_injection import (
from app.agents.multi_agent_chat.main_agent.middleware.noop_injection.middleware import (
NOOP_TOOL_NAME,
NoopInjectionMiddleware,
_last_ai_has_tool_calls,

View file

@ -8,7 +8,7 @@ from unittest.mock import MagicMock
import pytest
from langchain_core.messages import AIMessage, ToolMessage
from app.agents.shared.middleware.otel_span import (
from app.agents.multi_agent_chat.main_agent.middleware.otel_span.middleware import (
OtelSpanMiddleware,
_annotate_model_response,
_annotate_tool_result,
@ -206,13 +206,13 @@ class TestMiddlewareIntegration:
duration_calls: list[dict[str, Any]] = []
token_calls: list[dict[str, Any]] = []
monkeypatch.setattr(
"app.agents.shared.middleware.otel_span.ot_metrics.record_model_call_duration",
"app.agents.multi_agent_chat.main_agent.middleware.otel_span.middleware.ot_metrics.record_model_call_duration",
lambda duration_ms, **attrs: duration_calls.append(
{"duration_ms": duration_ms, **attrs}
),
)
monkeypatch.setattr(
"app.agents.shared.middleware.otel_span.ot_metrics.record_model_token_usage",
"app.agents.multi_agent_chat.main_agent.middleware.otel_span.middleware.ot_metrics.record_model_token_usage",
lambda **attrs: token_calls.append(attrs),
)
@ -257,11 +257,11 @@ class TestMiddlewareIntegration:
errors: list[str] = []
monkeypatch.setattr(
"app.agents.shared.middleware.otel_span.ot_metrics.record_tool_call_error",
"app.agents.multi_agent_chat.main_agent.middleware.otel_span.middleware.ot_metrics.record_tool_call_error",
lambda *, tool_name: errors.append(tool_name),
)
monkeypatch.setattr(
"app.agents.shared.middleware.otel_span.ot_metrics.record_tool_call_duration",
"app.agents.multi_agent_chat.main_agent.middleware.otel_span.middleware.ot_metrics.record_tool_call_duration",
lambda *args, **kwargs: None,
)

View file

@ -5,10 +5,10 @@ from __future__ import annotations
import pytest
from langchain_core.messages import AIMessage
from app.agents.multi_agent_chat.main_agent.tools.invalid_tool import INVALID_TOOL_NAME
from app.agents.shared.middleware.tool_call_repair import (
from app.agents.multi_agent_chat.main_agent.middleware.tool_call_repair.middleware import (
ToolCallNameRepairMiddleware,
)
from app.agents.multi_agent_chat.main_agent.tools.invalid_tool import INVALID_TOOL_NAME
pytestmark = pytest.mark.unit

View file

@ -9,7 +9,9 @@ contract cannot silently regress.
from __future__ import annotations
from app.agents.shared.middleware.knowledge_tree import KnowledgeTreeMiddleware
from app.agents.multi_agent_chat.main_agent.middleware.knowledge_tree.middleware import (
KnowledgeTreeMiddleware,
)
from app.agents.shared.path_resolver import DOCUMENTS_ROOT