refactor(agents): move MAC graph-state schema into multi_agent_chat/shared/state/

filesystem_state.py (the multi-agent graph state) and state_reducers.py
(its merge reducers) are consumed only by multi_agent_chat (filesystem
tools/middleware, kb projection, and the MAC-only shared middleware) plus
two unit tests -- no external app code. Relocate them into a dedicated
multi_agent_chat/shared/state/ package (filesystem_state.py + reducers.py)
and repoint every importer.

No behavior change; import-all + the full unit/middleware + unit/agents
suites (1066 tests) stay green.
This commit is contained in:
CREDO23 2026-06-05 10:54:15 +02:00
parent 2db4ad479e
commit 1d2519730e
30 changed files with 82 additions and 34 deletions

View file

@ -7,8 +7,10 @@ from typing import Any
from deepagents import FilesystemMiddleware
from langchain_core.tools import BaseTool
from app.agents.multi_agent_chat.shared.state.filesystem_state import (
SurfSenseFilesystemState,
)
from app.agents.shared.filesystem_selection import FilesystemMode
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
from app.agents.shared.sandbox import is_sandbox_enabled
from ..system_prompt import build_system_prompt

View file

@ -11,7 +11,9 @@ from typing import TYPE_CHECKING
from langchain.tools import ToolRuntime
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
from app.agents.multi_agent_chat.shared.state.filesystem_state import (
SurfSenseFilesystemState,
)
from app.agents.shared.path_resolver import DOCUMENTS_ROOT
from ..shared.paths import TEMP_PREFIX, basename

View file

@ -7,8 +7,10 @@ from typing import TYPE_CHECKING
from langchain.tools import ToolRuntime
from app.agents.multi_agent_chat.shared.state.filesystem_state import (
SurfSenseFilesystemState,
)
from app.agents.shared.filesystem_selection import FilesystemMode
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
from app.agents.shared.middleware.multi_root_local_folder_backend import (
MultiRootLocalFolderBackend,
)

View file

@ -10,7 +10,9 @@ from langchain_core.messages import ToolMessage
from langchain_core.tools import BaseTool, StructuredTool
from langgraph.types import Command
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
from app.agents.multi_agent_chat.shared.state.filesystem_state import (
SurfSenseFilesystemState,
)
from app.agents.shared.path_resolver import DOCUMENTS_ROOT
from ...middleware.async_dispatch import run_async_blocking

View file

@ -11,7 +11,9 @@ from langchain_core.messages import ToolMessage
from langchain_core.tools import BaseTool, StructuredTool
from langgraph.types import Command
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
from app.agents.multi_agent_chat.shared.state.filesystem_state import (
SurfSenseFilesystemState,
)
from app.agents.shared.middleware.kb_postgres_backend import KBPostgresBackend
from ...middleware.async_dispatch import run_async_blocking

View file

@ -14,7 +14,9 @@ from typing import TYPE_CHECKING
from daytona.common.errors import DaytonaError
from langchain.tools import ToolRuntime
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
from app.agents.multi_agent_chat.shared.state.filesystem_state import (
SurfSenseFilesystemState,
)
from app.agents.shared.sandbox import (
_evict_sandbox_cache,
delete_sandbox,

View file

@ -7,7 +7,9 @@ from typing import TYPE_CHECKING, Annotated
from langchain.tools import ToolRuntime
from langchain_core.tools import BaseTool, StructuredTool
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
from app.agents.multi_agent_chat.shared.state.filesystem_state import (
SurfSenseFilesystemState,
)
from ...middleware.async_dispatch import run_async_blocking
from .description import select_description

View file

@ -9,7 +9,9 @@ from deepagents.backends.utils import validate_path
from langchain.tools import ToolRuntime
from langchain_core.tools import BaseTool, StructuredTool
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
from app.agents.multi_agent_chat.shared.state.filesystem_state import (
SurfSenseFilesystemState,
)
from app.agents.shared.middleware.kb_postgres_backend import KBPostgresBackend
from ...middleware.async_dispatch import run_async_blocking

View file

@ -8,7 +8,9 @@ from deepagents.backends.utils import validate_path
from langchain.tools import ToolRuntime
from langchain_core.tools import BaseTool, StructuredTool
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
from app.agents.multi_agent_chat.shared.state.filesystem_state import (
SurfSenseFilesystemState,
)
from app.agents.shared.middleware.kb_postgres_backend import paginate_listing
from ...middleware.async_dispatch import run_async_blocking

View file

@ -11,7 +11,9 @@ from langchain_core.messages import ToolMessage
from langchain_core.tools import BaseTool, StructuredTool
from langgraph.types import Command
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
from app.agents.multi_agent_chat.shared.state.filesystem_state import (
SurfSenseFilesystemState,
)
from app.agents.shared.path_resolver import DOCUMENTS_ROOT
from ...middleware.async_dispatch import run_async_blocking

View file

@ -8,10 +8,12 @@ from langchain.tools import ToolRuntime
from langchain_core.messages import ToolMessage
from langgraph.types import Command
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
from app.agents.multi_agent_chat.shared.state.filesystem_state import (
SurfSenseFilesystemState,
)
from app.agents.multi_agent_chat.shared.state.reducers import _CLEAR
from app.agents.shared.middleware.kb_postgres_backend import KBPostgresBackend
from app.agents.shared.path_resolver import DOCUMENTS_ROOT
from app.agents.shared.state_reducers import _CLEAR
if TYPE_CHECKING:
from ...middleware import SurfSenseFilesystemMiddleware

View file

@ -11,7 +11,9 @@ from langchain_core.messages import ToolMessage
from langchain_core.tools import BaseTool, StructuredTool
from langgraph.types import Command
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
from app.agents.multi_agent_chat.shared.state.filesystem_state import (
SurfSenseFilesystemState,
)
from ...middleware.async_dispatch import run_async_blocking
from ...middleware.mode import is_cloud

View file

@ -7,7 +7,9 @@ from typing import TYPE_CHECKING
from langchain.tools import ToolRuntime
from langchain_core.tools import BaseTool, StructuredTool
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
from app.agents.multi_agent_chat.shared.state.filesystem_state import (
SurfSenseFilesystemState,
)
from ...middleware.path_resolution import current_cwd
from .description import select_description

View file

@ -10,7 +10,9 @@ from langchain_core.messages import ToolMessage
from langchain_core.tools import BaseTool, StructuredTool
from langgraph.types import Command
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
from app.agents.multi_agent_chat.shared.state.filesystem_state import (
SurfSenseFilesystemState,
)
from app.agents.shared.middleware.kb_postgres_backend import KBPostgresBackend
from ...middleware.async_dispatch import run_async_blocking

View file

@ -12,10 +12,12 @@ from langchain.tools import ToolRuntime
from langchain_core.messages import ToolMessage
from langgraph.types import Command
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
from app.agents.multi_agent_chat.shared.state.filesystem_state import (
SurfSenseFilesystemState,
)
from app.agents.multi_agent_chat.shared.state.reducers import _CLEAR
from app.agents.shared.middleware.kb_postgres_backend import KBPostgresBackend
from app.agents.shared.path_resolver import DOCUMENTS_ROOT
from app.agents.shared.state_reducers import _CLEAR
if TYPE_CHECKING:
from ...middleware import SurfSenseFilesystemMiddleware

View file

@ -9,7 +9,9 @@ from langchain.tools import ToolRuntime
from langchain_core.tools import BaseTool, StructuredTool
from langgraph.types import Command
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
from app.agents.multi_agent_chat.shared.state.filesystem_state import (
SurfSenseFilesystemState,
)
from ...middleware.async_dispatch import run_async_blocking
from ...middleware.mode import is_cloud

View file

@ -13,10 +13,12 @@ from langchain.tools import ToolRuntime
from langchain_core.messages import ToolMessage
from langgraph.types import Command
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
from app.agents.multi_agent_chat.shared.state.filesystem_state import (
SurfSenseFilesystemState,
)
from app.agents.multi_agent_chat.shared.state.reducers import _CLEAR
from app.agents.shared.middleware.kb_postgres_backend import KBPostgresBackend
from app.agents.shared.path_resolver import DOCUMENTS_ROOT
from app.agents.shared.state_reducers import _CLEAR
from ...middleware.path_resolution import current_cwd
from ...shared.paths import is_ancestor_of

View file

@ -9,7 +9,9 @@ from langchain.tools import ToolRuntime
from langchain_core.tools import BaseTool, StructuredTool
from langgraph.types import Command
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
from app.agents.multi_agent_chat.shared.state.filesystem_state import (
SurfSenseFilesystemState,
)
from ...middleware.async_dispatch import run_async_blocking
from ...middleware.mode import is_cloud

View file

@ -11,7 +11,9 @@ from langchain_core.messages import ToolMessage
from langchain_core.tools import BaseTool, StructuredTool
from langgraph.types import Command
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
from app.agents.multi_agent_chat.shared.state.filesystem_state import (
SurfSenseFilesystemState,
)
from ...middleware.async_dispatch import run_async_blocking
from ...middleware.mode import is_cloud

View file

@ -9,7 +9,9 @@ from langchain.agents.middleware import AgentMiddleware, AgentState
from langchain_core.messages import SystemMessage
from langgraph.runtime import Runtime
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
from app.agents.multi_agent_chat.shared.state.filesystem_state import (
SurfSenseFilesystemState,
)
from app.agents.shared.middleware.knowledge_search import _render_priority_message
from app.utils.perf import get_perf_logger

View file

@ -0,0 +1,213 @@
"""LangGraph state schema additions used by the SurfSense filesystem agent.
This schema extends deepagents' upstream :class:`FilesystemState` with the
extra fields needed to implement Postgres-backed virtual filesystem semantics:
* ``cwd`` current working directory (per-thread checkpointed).
* ``staged_dirs`` pending mkdir requests (cloud only).
* ``staged_dir_tool_calls`` sidecar map ``path -> tool_call_id`` for staged dirs.
* ``pending_moves`` pending move_file requests (cloud only).
* ``pending_deletes`` pending ``rm`` requests (cloud only).
* ``pending_dir_deletes`` pending ``rmdir`` requests (cloud only).
* ``doc_id_by_path`` virtual_path -> Document.id, populated by lazy reads.
* ``dirty_paths`` paths whose state file content differs from DB.
* ``dirty_path_tool_calls`` sidecar map ``path -> latest tool_call_id`` for
dirty paths; used to bind the per-path snapshot to an action_id.
* ``kb_priority`` top-K priority hints rendered into a system message.
* ``kb_matched_chunk_ids`` internal hand-off for matched-chunk highlighting.
* ``kb_anon_doc`` Redis-loaded anonymous document (if any).
* ``tree_version`` bumped by persistence; invalidates the tree render cache.
* ``workspace_tree_text`` pre-rendered ``<workspace_tree>`` body for the turn.
Tools mutate these fields ONLY via ``Command(update=...)`` returns; the
reducers in :mod:`app.agents.multi_agent_chat.shared.state.reducers` handle merging.
"""
from __future__ import annotations
from typing import Annotated, Any, NotRequired
from deepagents.middleware.filesystem import FilesystemState
from typing_extensions import TypedDict
from app.agents.multi_agent_chat.shared.state.reducers import (
_add_unique_reducer,
_dict_merge_with_tombstones_reducer,
_int_counter_merge_reducer,
_list_append_reducer,
_replace_reducer,
)
from app.agents.shared.receipt import Receipt
class PendingMove(TypedDict, total=False):
"""A staged move_file operation pending end-of-turn commit.
``tool_call_id`` is optional for backward compatibility with checkpoints
written before the snapshot/revert pipeline was wired up; new entries
always include it so the persistence body can resolve an action_id.
"""
source: str
dest: str
overwrite: bool
tool_call_id: str
class PendingDelete(TypedDict, total=False):
"""A staged ``rm`` or ``rmdir`` operation pending end-of-turn commit.
``tool_call_id`` is required for new entries (it's the binding key used
by :class:`KnowledgeBasePersistenceMiddleware` to find the matching
:class:`AgentActionLog` row and bind the snapshot to it). Marked
``total=False`` only to tolerate older checkpoint payloads.
"""
path: str
tool_call_id: str
class KbPriorityEntry(TypedDict, total=False):
path: str
score: float
document_id: int | None
title: str
mentioned: bool
class KbAnonDoc(TypedDict, total=False):
"""In-memory anonymous-session document loaded from Redis."""
path: str
title: str
content: str
chunks: list[dict[str, Any]]
class SurfSenseFilesystemState(FilesystemState):
"""Filesystem state used by the SurfSense agent (cloud + desktop).
Extends deepagents' :class:`FilesystemState` (which provides ``files``)
with cloud-mode staging fields and search-priority hints. All extra fields
are reducer-backed so that ``Command(update=...)`` payloads merge cleanly
across agent steps and across checkpoints.
"""
cwd: NotRequired[Annotated[str, _replace_reducer]]
"""Current working directory.
Defaults to ``"/documents"`` in cloud mode and ``"/"`` (or first mount) in
desktop mode. Initialized once per thread by ``KnowledgeTreeMiddleware``.
"""
staged_dirs: NotRequired[Annotated[list[str], _add_unique_reducer]]
"""mkdir paths staged for end-of-turn folder creation (cloud only)."""
staged_dir_tool_calls: NotRequired[
Annotated[dict[str, str], _dict_merge_with_tombstones_reducer]
]
"""``path -> tool_call_id`` sidecar for ``staged_dirs``.
Used by :class:`KnowledgeBasePersistenceMiddleware` to bind the
:class:`FolderRevision` snapshot to the originating ``mkdir`` action.
Kept separate from ``staged_dirs`` (which stays a unique-string list)
to avoid breaking ``_add_unique_reducer`` semantics.
"""
pending_moves: NotRequired[Annotated[list[PendingMove], _list_append_reducer]]
"""move_file ops staged for end-of-turn commit (cloud only)."""
pending_deletes: NotRequired[Annotated[list[PendingDelete], _list_append_reducer]]
"""``rm`` ops staged for end-of-turn ``DELETE FROM documents`` (cloud only).
Each entry is a dict ``{"path": ..., "tool_call_id": ...}``. Per-path
uniqueness is enforced inside the commit body, not the reducer (we keep
``tool_call_id`` per occurrence so snapshot binding works).
"""
pending_dir_deletes: NotRequired[
Annotated[list[PendingDelete], _list_append_reducer]
]
"""``rmdir`` ops staged for end-of-turn ``DELETE FROM folders`` (cloud only).
Same shape as :data:`pending_deletes`. Commit body re-verifies the
folder is empty (in-DB AND with this turn's pending changes accounted
for) before issuing the DELETE.
"""
doc_id_by_path: NotRequired[
Annotated[dict[str, int], _dict_merge_with_tombstones_reducer]
]
"""virtual_path -> ``Document.id`` for lazily loaded files.
Populated on first read of a KB document. Used by edit_file/move_file/
aafter_agent to map paths back to a real DB row. ``None`` values delete
the key (tombstones).
"""
dirty_paths: NotRequired[Annotated[list[str], _add_unique_reducer]]
"""Paths whose ``state["files"]`` content has been modified this turn."""
dirty_path_tool_calls: NotRequired[
Annotated[dict[str, str], _dict_merge_with_tombstones_reducer]
]
"""``path -> latest tool_call_id`` sidecar for ``dirty_paths``.
The persistence body coalesces multiple writes/edits to the same path
into one snapshot per turn. This map captures the most-recent
``tool_call_id`` so the resulting :class:`DocumentRevision` is bound
to the latest action_id (the one the user is most likely to revert).
"""
kb_priority: NotRequired[Annotated[list[KbPriorityEntry], _replace_reducer]]
"""Top-K priority hints rendered as a system message before the user turn."""
kb_matched_chunk_ids: NotRequired[Annotated[dict[int, list[int]], _replace_reducer]]
"""Internal: ``Document.id`` -> list of matched chunk IDs from hybrid search."""
kb_anon_doc: NotRequired[Annotated[KbAnonDoc | None, _replace_reducer]]
"""Anonymous-session document loaded from Redis (read-only, no DB row)."""
tree_version: NotRequired[Annotated[int, _replace_reducer]]
"""Monotonically increasing counter; bumped when commits change the KB tree."""
workspace_tree_text: NotRequired[Annotated[str, _replace_reducer]]
"""Pre-rendered ``<workspace_tree>`` body; shared with subagents to skip re-render."""
billable_calls: NotRequired[Annotated[dict[str, int], _int_counter_merge_reducer]]
"""Per-subagent ``task(...)`` invocation counter, summed across the turn.
Incremented by ``task_tool.py`` each time a subagent invocation
completes (single- or batch-mode). The orchestrator can read this map
to self-limit when a runaway loop sends the same specialist 20 calls
in a row; the runtime emits a soft warning ToolMessage once the
cumulative count crosses :data:`DEFAULT_SUBAGENT_BILLABLE_THRESHOLD`.
Cleared by checkpoint rollover (i.e. per turn).
"""
receipts: NotRequired[Annotated[list[Receipt], _list_append_reducer]]
"""Structured Receipt handles emitted by mutating subagent tools this turn.
Each mutating tool (deliverables, every connector, KB writes via the
persistence middleware) wraps its native return into a
:class:`~app.agents.shared.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
``checkpointed_subagent_middleware/task_tool.py`` carries the list up
to the parent automatically (``"receipts"`` is not in
``EXCLUDED_STATE_KEYS``).
Append-only across the turn; cleared by checkpoint rollover. The
orchestrator reads it via the ``<verification>`` teaching to confirm
side-effecting subagent claims (see ``shared/snippets/verifiable_handle.md``).
"""
__all__ = [
"KbAnonDoc",
"KbPriorityEntry",
"PendingDelete",
"PendingMove",
"SurfSenseFilesystemState",
]

View file

@ -0,0 +1,239 @@
"""Reducers and sentinels for SurfSense filesystem state.
These reducers back the extra state fields used by the cloud-mode filesystem
agent (`cwd`, `staged_dirs`, `pending_moves`, `dirty_paths`, `doc_id_by_path`,
`kb_priority`, `kb_matched_chunk_ids`, `kb_anon_doc`, `tree_version`).
Tools mutate these fields ONLY via `Command(update={...})` returns; the
reducers are responsible for merging successive updates atomically and for
honouring an explicit reset sentinel (`_CLEAR`) so that a single update can
both reset and reseed a list (used by `move_file` / `aafter_agent`).
The sentinel is intentionally a plain string constant rather than a custom
object so that LangGraph's checkpointer (which serializes raw `Command.update`
deltas via ``ormsgpack`` BEFORE reducers are applied) can round-trip writes
that contain it. The token uses a NUL-bracketed form that cannot collide with
any real virtual path, document title, or dict key produced by the agent.
"""
from __future__ import annotations
from typing import Any, Final, TypeVar
_CLEAR: Final[str] = "\x00__SURFSENSE_FILESYSTEM_CLEAR__\x00"
"""Reset sentinel; pass it inside a list/dict update to request a reset.
For list reducers: ``[_CLEAR, *items]`` resets the field then appends ``items``.
For dict reducers: ``{_CLEAR: True, **items}`` resets the field then merges ``items``.
Because the value is a plain string with embedded NUL bytes, it is natively
serializable by ``ormsgpack`` (used by LangGraph's PostgreSQL checkpointer)
yet still distinct from any real path / key produced by application code.
"""
T = TypeVar("T")
def _replace_reducer[T](left: T | None, right: T | None) -> T | None:
"""Replace `left` outright with `right`. ``None`` on the right is honored as a reset."""
return right
def _is_clear(value: Any) -> bool:
return isinstance(value, str) and value == _CLEAR
def _add_unique_reducer(
left: list[Any] | None,
right: list[Any] | None,
) -> list[Any]:
"""Append items from ``right`` to ``left`` while preserving uniqueness.
Semantics:
- If ``right`` is ``None`` or empty, return ``left`` unchanged.
- If ``right`` contains the ``_CLEAR`` sentinel anywhere, the result is
reseeded with only the items that appear AFTER the LAST occurrence of
``_CLEAR`` (deduplicated, preserving first-seen order). This gives a
single-update "reset and reseed" capability.
- Otherwise, items from ``right`` are appended to ``left`` (order preserved
from first seen) while skipping values that are already present.
"""
if right is None:
return list(left or [])
if not right:
return list(left or [])
last_clear = -1
for index, item in enumerate(right):
if _is_clear(item):
last_clear = index
if last_clear >= 0:
seed: list[Any] = []
seen: set[Any] = set()
for item in right[last_clear + 1 :]:
if _is_clear(item):
continue
try:
if item in seen:
continue
seen.add(item)
except TypeError:
if item in seed:
continue
seed.append(item)
return seed
base = list(left or [])
try:
seen: set[Any] = set(base)
except TypeError:
seen = set()
for item in right:
if _is_clear(item):
continue
try:
if item in seen:
continue
seen.add(item)
except TypeError:
if item in base:
continue
base.append(item)
return base
def _list_append_reducer(
left: list[Any] | None,
right: list[Any] | None,
) -> list[Any]:
"""Append items from ``right`` to ``left`` preserving order and duplicates.
Honours the ``_CLEAR`` sentinel exactly like :func:`_add_unique_reducer`,
but does NOT deduplicate. Used for queues whose ordering and duplicate
occurrences matter (e.g. ``pending_moves``).
"""
if right is None:
return list(left or [])
if not right:
return list(left or [])
last_clear = -1
for index, item in enumerate(right):
if _is_clear(item):
last_clear = index
if last_clear >= 0:
return [item for item in right[last_clear + 1 :] if not _is_clear(item)]
base = list(left or [])
base.extend(item for item in right if not _is_clear(item))
return base
def _dict_merge_with_tombstones_reducer(
left: dict[Any, Any] | None,
right: dict[Any, Any] | None,
) -> dict[Any, Any]:
"""Merge ``right`` into ``left`` with two extra capabilities:
* Keys whose value is ``None`` are removed from the merged result
(tombstone semantics, matching the deepagents file-data reducer).
* The special key ``_CLEAR`` (with any truthy value) resets ``left`` to
``{}`` before merging the remaining keys from ``right``. This makes it
possible to atomically clear and reseed the dictionary in a single
update.
"""
if right is None:
return dict(left or {})
if _CLEAR in right or any(_is_clear(k) for k in right):
result: dict[Any, Any] = {}
for key, value in right.items():
if _is_clear(key):
continue
if value is None:
result.pop(key, None)
continue
result[key] = value
return result
if left is None:
return {key: value for key, value in right.items() if value is not None}
result = dict(left)
for key, value in right.items():
if value is None:
result.pop(key, None)
else:
result[key] = value
return result
def _int_counter_merge_reducer(
left: dict[str, int] | None,
right: dict[str, int] | None,
) -> dict[str, int]:
"""Merge ``right`` into ``left`` by **summing** per-key integer counters.
Used for state fields that accumulate counts across multiple updates
within the same turn (e.g. per-subagent ``billable_calls``). Unknown
keys are added; existing keys are summed. ``_CLEAR`` sentinels reset
the accumulator the same way the other reducers do, so the orchestrator
can wipe the counter at end-of-turn if needed.
"""
if right is None:
return dict(left or {})
if _CLEAR in right or any(_is_clear(k) for k in right):
result: dict[str, int] = {}
for key, value in right.items():
if _is_clear(key):
continue
if not isinstance(value, int):
continue
result[key] = result.get(key, 0) + value
return result
base = dict(left or {})
for key, value in right.items():
if not isinstance(value, int):
continue
base[key] = base.get(key, 0) + value
return base
def _initial_filesystem_state() -> dict[str, Any]:
"""Default empty values for SurfSense filesystem state fields.
Consumers should always treat these fields as ``state.get(key) or
DEFAULT`` so that fresh threads (without checkpointed state) work
correctly.
"""
return {
"cwd": "/documents",
"staged_dirs": [],
"staged_dir_tool_calls": {},
"pending_moves": [],
"pending_deletes": [],
"pending_dir_deletes": [],
"doc_id_by_path": {},
"dirty_paths": [],
"dirty_path_tool_calls": {},
"kb_priority": [],
"kb_matched_chunk_ids": {},
"kb_anon_doc": None,
"tree_version": 0,
}
__all__ = [
"_CLEAR",
"_add_unique_reducer",
"_dict_merge_with_tombstones_reducer",
"_initial_filesystem_state",
"_int_counter_merge_reducer",
"_list_append_reducer",
"_replace_reducer",
]