Files
EvoScientist/EvoScientist/memory/agents/_factory.py
T
m4 087781556b fix(runtime): serve Config API in bootstrap and verify snapshot issuer by registered deployment set
Graph construction no longer raises on a bootstrap registry: build paths
bind a shared RegistryNotReadyChatModel placeholder that fails every call
with MODEL_REGISTRY_NOT_READY, so langgraph dev serves the Config API for
first-time configuration while run creation stays forbidden.

Run snapshot binding no longer compares configurable
'workspace_deployment_id' (the workspace-isolation scope id) against the
snapshot's issuing deployment — a mismatch that made every BFF run fail
with SNAPSHOT_NOT_FOUND. SnapshotService.get_for_run verifies thread_id
equality plus membership in the platform-registered deployment set
(local_deployment_id + webui_delegation_public_keys entries).

Blocking I/O moved off the event loop for langgraph dev's blockbuster:
Config API authentication (store mkdir/chmod, config.yaml read, jti
registration) and the message-budget snapshot read now run in threads,
with the immutable snapshot cached per run.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-21 20:21:32 +08:00

114 lines
3.3 KiB
Python

"""Shared construction helpers for background EvoMemory agents."""
from __future__ import annotations
from collections.abc import Iterable, Sequence
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from deepagents.backends.protocol import BackendProtocol
from langchain.agents.middleware.types import AgentMiddleware
from langchain_core.tools import BaseTool
from langgraph.graph.state import CompiledStateGraph
from pydantic import BaseModel
from ... import paths as _paths
MEMORY_AGENT_RECURSION_LIMIT = 100
MEMORY_MAINTENANCE_EXCLUDED_TOOLS = frozenset(
{
"edit_file",
"execute",
"task",
"write_file",
"write_todos",
}
)
@dataclass(frozen=True, slots=True)
class MemoryAgentPaths:
memory_dir: Path
workspace_dir: Path
def resolve_memory_agent_paths(
*,
memory_dir: str | Path | None = None,
workspace_dir: str | Path | None = None,
) -> MemoryAgentPaths:
"""Resolve the default workspace and memory roots for memory graphs."""
resolved_memory_dir = Path(
_paths.MEMORIES_DIR if memory_dir is None else memory_dir
).expanduser()
resolved_workspace_dir = Path(
_paths.WORKSPACE_ROOT if workspace_dir is None else workspace_dir
).expanduser()
return MemoryAgentPaths(
memory_dir=resolved_memory_dir,
workspace_dir=resolved_workspace_dir,
)
def memory_agent_middleware(
*extra_middleware: AgentMiddleware,
excluded_tools: Iterable[str] = MEMORY_MAINTENANCE_EXCLUDED_TOOLS,
) -> list[AgentMiddleware]:
"""Compose the standard middleware stack for unattended memory agents."""
from deepagents.middleware._tool_exclusion import _ToolExclusionMiddleware
from ...middleware.tool_error_handler import ToolErrorHandlerMiddleware
middleware: list[AgentMiddleware] = [
ToolErrorHandlerMiddleware(),
*extra_middleware,
]
excluded = frozenset(excluded_tools)
if excluded:
middleware.append(_ToolExclusionMiddleware(excluded=excluded))
return middleware
def build_memory_agent_graph(
*,
name: str,
system_prompt: str,
memory_dir: str | Path,
workspace_dir: str | Path,
tools: Sequence[BaseTool],
middleware: Sequence[AgentMiddleware],
recursion_limit: int = MEMORY_AGENT_RECURSION_LIMIT,
response_format: type[BaseModel] | None = None,
skills: list[str] | None = None,
backend: BackendProtocol | None = None,
) -> CompiledStateGraph:
"""Build a background memory graph with the shared model/backend wiring."""
from deepagents import create_deep_agent
from ...backends import build_memory_agent_backend
from ...EvoScientist import _compile_time_role_model
kwargs: dict[str, Any] = {}
if response_format is not None:
kwargs["response_format"] = response_format
if backend is None:
backend = build_memory_agent_backend(
workspace_dir=workspace_dir,
memory_dir=memory_dir,
)
agent = create_deep_agent(
name=name,
model=_compile_time_role_model("auxiliary"),
system_prompt=system_prompt,
tools=list(tools),
backend=backend,
middleware=list(middleware),
subagents=[],
skills=skills,
**kwargs,
)
return agent.with_config({"recursion_limit": recursion_limit})