From 714c5ffea9cdabfcb5e8f5ae7e0d97505f890bd9 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Fri, 5 Jun 2026 10:56:37 +0200 Subject: [PATCH] refactor(agents): group tool-outcome receipts into multi_agent_chat/shared/receipts/ receipt.py (Receipt model + make_receipt) and receipt_command.py (with_receipt Command helper) are a tight pair used only by MAC subagent tools, the graph state, and the kb_persistence middleware -- no external code imports them (the streaming tool_end handler only references them in a docstring). Move both into a dedicated receipts/ package (receipts/receipt.py + receipts/command.py) and repoint importers. No behavior change; import-all + receipt/deliverable unit tests stay green. --- .../app/agents/multi_agent_chat/shared/receipts/__init__.py | 0 .../shared/receipts/command.py} | 2 +- .../{shared => multi_agent_chat/shared/receipts}/receipt.py | 0 .../agents/multi_agent_chat/shared/state/filesystem_state.py | 4 ++-- .../subagents/builtins/deliverables/tools/generate_image.py | 4 ++-- .../subagents/builtins/deliverables/tools/podcast.py | 4 ++-- .../subagents/builtins/deliverables/tools/report.py | 4 ++-- .../subagents/builtins/deliverables/tools/resume.py | 4 ++-- .../builtins/deliverables/tools/video_presentation.py | 4 ++-- .../subagents/connectors/gmail/tools/send_email.py | 4 ++-- .../subagents/connectors/notion/tools/delete_page.py | 4 ++-- .../app/agents/shared/middleware/kb_persistence.py | 2 +- .../app/tasks/chat/streaming/handlers/tool_end.py | 2 +- 13 files changed, 19 insertions(+), 19 deletions(-) create mode 100644 surfsense_backend/app/agents/multi_agent_chat/shared/receipts/__init__.py rename surfsense_backend/app/agents/{shared/receipt_command.py => multi_agent_chat/shared/receipts/command.py} (97%) rename surfsense_backend/app/agents/{shared => multi_agent_chat/shared/receipts}/receipt.py (100%) diff --git a/surfsense_backend/app/agents/multi_agent_chat/shared/receipts/__init__.py b/surfsense_backend/app/agents/multi_agent_chat/shared/receipts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/app/agents/shared/receipt_command.py b/surfsense_backend/app/agents/multi_agent_chat/shared/receipts/command.py similarity index 97% rename from surfsense_backend/app/agents/shared/receipt_command.py rename to surfsense_backend/app/agents/multi_agent_chat/shared/receipts/command.py index 97496c8c7..13ec63f0a 100644 --- a/surfsense_backend/app/agents/shared/receipt_command.py +++ b/surfsense_backend/app/agents/multi_agent_chat/shared/receipts/command.py @@ -24,7 +24,7 @@ from typing import Any from langchain_core.messages import ToolMessage from langgraph.types import Command -from app.agents.shared.receipt import Receipt +from app.agents.multi_agent_chat.shared.receipts.receipt import Receipt def _content_to_text(payload: dict[str, Any] | str) -> str: diff --git a/surfsense_backend/app/agents/shared/receipt.py b/surfsense_backend/app/agents/multi_agent_chat/shared/receipts/receipt.py similarity index 100% rename from surfsense_backend/app/agents/shared/receipt.py rename to surfsense_backend/app/agents/multi_agent_chat/shared/receipts/receipt.py diff --git a/surfsense_backend/app/agents/multi_agent_chat/shared/state/filesystem_state.py b/surfsense_backend/app/agents/multi_agent_chat/shared/state/filesystem_state.py index 894f4eae4..be24ab910 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/shared/state/filesystem_state.py +++ b/surfsense_backend/app/agents/multi_agent_chat/shared/state/filesystem_state.py @@ -30,6 +30,7 @@ from typing import Annotated, Any, NotRequired from deepagents.middleware.filesystem import FilesystemState from typing_extensions import TypedDict +from app.agents.multi_agent_chat.shared.receipts.receipt import Receipt from app.agents.multi_agent_chat.shared.state.reducers import ( _add_unique_reducer, _dict_merge_with_tombstones_reducer, @@ -37,7 +38,6 @@ from app.agents.multi_agent_chat.shared.state.reducers import ( _list_append_reducer, _replace_reducer, ) -from app.agents.shared.receipt import Receipt class PendingMove(TypedDict, total=False): @@ -190,7 +190,7 @@ class SurfSenseFilesystemState(FilesystemState): Each mutating tool (deliverables, every connector, KB writes via the persistence middleware) wraps its native return into a - :class:`~app.agents.shared.receipt.Receipt` + :class:`~app.agents.multi_agent_chat.shared.receipts.receipt.Receipt` and returns it under the ``"receipt"`` key alongside its existing payload. The subagent's tool-call middleware folds the receipt into this list, and ``_return_command_with_state_update`` in diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/generate_image.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/generate_image.py index d7105f903..0672fda4c 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/generate_image.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/generate_image.py @@ -11,8 +11,8 @@ from litellm import aimage_generation from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession -from app.agents.shared.receipt import make_receipt -from app.agents.shared.receipt_command import with_receipt +from app.agents.multi_agent_chat.shared.receipts.command import with_receipt +from app.agents.multi_agent_chat.shared.receipts.receipt import make_receipt from app.config import config from app.db import ( ImageGeneration, diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/podcast.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/podcast.py index 298257799..ef4bdfc09 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/podcast.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/podcast.py @@ -16,9 +16,9 @@ from langchain_core.tools import tool from langgraph.types import Command from sqlalchemy.ext.asyncio import AsyncSession +from app.agents.multi_agent_chat.shared.receipts.command import with_receipt +from app.agents.multi_agent_chat.shared.receipts.receipt import make_receipt from app.agents.shared.deliverable_wait import wait_for_deliverable -from app.agents.shared.receipt import make_receipt -from app.agents.shared.receipt_command import with_receipt from app.db import Podcast, PodcastStatus, shielded_async_session logger = logging.getLogger(__name__) diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/report.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/report.py index f12ca8a90..5db43bf55 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/report.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/report.py @@ -12,8 +12,8 @@ from langchain_core.messages import HumanMessage from langchain_core.tools import tool from langgraph.types import Command -from app.agents.shared.receipt import make_receipt -from app.agents.shared.receipt_command import with_receipt +from app.agents.multi_agent_chat.shared.receipts.command import with_receipt +from app.agents.multi_agent_chat.shared.receipts.receipt import make_receipt from app.db import Report, shielded_async_session from app.services.connector_service import ConnectorService from app.services.llm_service import get_document_summary_llm diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/resume.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/resume.py index ad16b7ba7..05359c515 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/resume.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/resume.py @@ -14,8 +14,8 @@ from langchain_core.messages import HumanMessage from langchain_core.tools import tool from langgraph.types import Command -from app.agents.shared.receipt import make_receipt -from app.agents.shared.receipt_command import with_receipt +from app.agents.multi_agent_chat.shared.receipts.command import with_receipt +from app.agents.multi_agent_chat.shared.receipts.receipt import make_receipt from app.db import Report, shielded_async_session from app.services.llm_service import get_document_summary_llm diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/video_presentation.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/video_presentation.py index 5407c8834..a04413fd5 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/video_presentation.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/video_presentation.py @@ -17,9 +17,9 @@ from langchain_core.tools import tool from langgraph.types import Command from sqlalchemy.ext.asyncio import AsyncSession +from app.agents.multi_agent_chat.shared.receipts.command import with_receipt +from app.agents.multi_agent_chat.shared.receipts.receipt import make_receipt from app.agents.shared.deliverable_wait import wait_for_deliverable -from app.agents.shared.receipt import make_receipt -from app.agents.shared.receipt_command import with_receipt from app.db import VideoPresentation, VideoPresentationStatus, shielded_async_session logger = logging.getLogger(__name__) diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/gmail/tools/send_email.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/gmail/tools/send_email.py index 0680e51cb..57255be05 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/gmail/tools/send_email.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/gmail/tools/send_email.py @@ -10,11 +10,11 @@ from langchain_core.tools import tool from langgraph.types import Command from sqlalchemy.ext.asyncio import AsyncSession +from app.agents.multi_agent_chat.shared.receipts.command import with_receipt +from app.agents.multi_agent_chat.shared.receipts.receipt import make_receipt from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.self_gated import ( request_approval, ) -from app.agents.shared.receipt import make_receipt -from app.agents.shared.receipt_command import with_receipt from app.services.gmail import GmailToolMetadataService logger = logging.getLogger(__name__) diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/notion/tools/delete_page.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/notion/tools/delete_page.py index c98b25811..d6e1a9a69 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/notion/tools/delete_page.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/notion/tools/delete_page.py @@ -6,11 +6,11 @@ from langchain_core.tools import tool from langgraph.types import Command from sqlalchemy.ext.asyncio import AsyncSession +from app.agents.multi_agent_chat.shared.receipts.command import with_receipt +from app.agents.multi_agent_chat.shared.receipts.receipt import make_receipt from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.self_gated import ( request_approval, ) -from app.agents.shared.receipt import make_receipt -from app.agents.shared.receipt_command import with_receipt from app.connectors.notion_history import NotionAPIError, NotionHistoryConnector from app.services.notion.tool_metadata_service import NotionToolMetadataService diff --git a/surfsense_backend/app/agents/shared/middleware/kb_persistence.py b/surfsense_backend/app/agents/shared/middleware/kb_persistence.py index 771c30dad..8c1c551fa 100644 --- a/surfsense_backend/app/agents/shared/middleware/kb_persistence.py +++ b/surfsense_backend/app/agents/shared/middleware/kb_persistence.py @@ -45,6 +45,7 @@ from sqlalchemy import delete, select, update from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.asyncio import AsyncSession +from app.agents.multi_agent_chat.shared.receipts.receipt import Receipt, make_receipt from app.agents.multi_agent_chat.shared.state.filesystem_state import ( SurfSenseFilesystemState, ) @@ -57,7 +58,6 @@ from app.agents.shared.path_resolver import ( safe_folder_segment, virtual_path_to_doc, ) -from app.agents.shared.receipt import Receipt, make_receipt from app.db import ( AgentActionLog, Chunk, diff --git a/surfsense_backend/app/tasks/chat/streaming/handlers/tool_end.py b/surfsense_backend/app/tasks/chat/streaming/handlers/tool_end.py index 2ff810447..e0be1ddbd 100644 --- a/surfsense_backend/app/tasks/chat/streaming/handlers/tool_end.py +++ b/surfsense_backend/app/tasks/chat/streaming/handlers/tool_end.py @@ -26,7 +26,7 @@ def _unwrap_command_output(raw_output: Any) -> Any: """Replace a ``Command`` from a tool return with its inner ``ToolMessage``. Tools that participate in receipt-style state writes (see - ``app.agents.shared.receipt_command.with_receipt``) return a + ``app.agents.multi_agent_chat.shared.receipts.command.with_receipt``) return a ``Command(update={"messages": [ToolMessage(...)], "receipts": [...]})``. LangChain's ``on_tool_end`` event surfaces that ``Command`` verbatim as ``data.output``, which the rest of this handler can't introspect: it has