mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-08 20:25:19 +02:00
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:
parent
2db4ad479e
commit
1d2519730e
30 changed files with 82 additions and 34 deletions
|
|
@ -1,213 +0,0 @@
|
|||
"""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.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.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",
|
||||
]
|
||||
|
|
@ -24,7 +24,9 @@ from typing import Any
|
|||
from langchain.agents.middleware import AgentMiddleware, AgentState
|
||||
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.path_resolver import DOCUMENTS_ROOT, safe_filename
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
|
|||
|
|
@ -45,16 +45,18 @@ from sqlalchemy import delete, select, update
|
|||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
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.feature_flags import get_flags
|
||||
from app.agents.shared.filesystem_selection import FilesystemMode
|
||||
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
|
||||
from app.agents.shared.path_resolver import (
|
||||
DOCUMENTS_ROOT,
|
||||
parse_documents_path,
|
||||
safe_folder_segment,
|
||||
virtual_path_to_doc,
|
||||
)
|
||||
from app.agents.shared.state_reducers import _CLEAR
|
||||
from app.agents.shared.receipt import Receipt, make_receipt
|
||||
from app.db import (
|
||||
AgentActionLog,
|
||||
|
|
|
|||
|
|
@ -45,9 +45,11 @@ from app.agents.multi_agent_chat.shared.date_filters import (
|
|||
parse_date_or_datetime,
|
||||
resolve_date_range,
|
||||
)
|
||||
from app.agents.multi_agent_chat.shared.state.filesystem_state import (
|
||||
SurfSenseFilesystemState,
|
||||
)
|
||||
from app.agents.shared.feature_flags import get_flags
|
||||
from app.agents.shared.filesystem_selection import FilesystemMode
|
||||
from app.agents.shared.filesystem_state import SurfSenseFilesystemState
|
||||
from app.agents.shared.path_resolver import (
|
||||
PathIndex,
|
||||
build_path_index,
|
||||
|
|
|
|||
|
|
@ -33,8 +33,10 @@ from langchain_core.messages import SystemMessage
|
|||
from langgraph.runtime import Runtime
|
||||
from sqlalchemy import select
|
||||
|
||||
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.path_resolver import (
|
||||
DOCUMENTS_ROOT,
|
||||
PathIndex,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ participate in the verification teaching from
|
|||
``multi_agent_chat/subagents/shared/snippets/verifiable_handle.md`` those
|
||||
tools now also need to write a :class:`Receipt` into the parent's
|
||||
``state['receipts']`` list (declared on
|
||||
:class:`~app.agents.shared.filesystem_state.SurfSenseFilesystemState`
|
||||
:class:`~app.agents.multi_agent_chat.shared.state.filesystem_state.SurfSenseFilesystemState`
|
||||
and backed by the append reducer).
|
||||
|
||||
:func:`with_receipt` wraps both behaviours: it returns the tool payload as
|
||||
|
|
@ -51,7 +51,7 @@ def with_receipt(
|
|||
"""Return a Command that ships ``payload`` as a ToolMessage AND appends ``receipt``.
|
||||
|
||||
The append happens via the ``_list_append_reducer`` on the ``receipts``
|
||||
field of :class:`~app.agents.shared.filesystem_state.SurfSenseFilesystemState`,
|
||||
field of :class:`~app.agents.multi_agent_chat.shared.state.filesystem_state.SurfSenseFilesystemState`,
|
||||
so concurrent subagent batches (item 4 in the plan) won't clobber each
|
||||
other's receipts.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,239 +0,0 @@
|
|||
"""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",
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue