f2f010a350
* refactor(gateway): create module for launching async/bg agents * refactor(memory): refactor worker launch around source context & output deltas * refactor(gateway): generalize async/bg module * refactor(memory): revamp worker launching * feat(memory): add observation linking * test(memory): remove redundant test branches * fix(memory): make 'supersedes' relation directional * fix(memory): don't create empty project observation dirs * fix(memory): schedule direct observations for linking * fix(cli): wait for observation linker before shutdown * fix(memory): block arbitrary writes to /memories * fix(linker): remove `linked_by` attribute from frontmatter * refactor(linker): rename base relationship to `comlpements` * fix(cli): bump worker wait to 2m * feat(tools): catch malformed tool calls & retry * feat(status): add linking result to statusbar * fix(linker): don't launch linker when observations are disabled * fix(memory): use posix paths * fix(watcher): call abort hook on error status * fix(watcher): delete thread on failed run creation * fix(watcher): preserve url * fix(observation): record session_id, drop unused fields * fix(memory): reject unsupported worker source types * refactor(backends): shared memory backend builder * fix(scheduler): resolve linker inputs outside lock * fix(memory): dont launch workers / record observations without thread_id * feat(memory): include related observations in tool results * fix(memory): skip malformed observation frontmatter * revert(tools): drop tool error handling changes from this PR * fix(memory): serialize observation link writes * fix(memory): queue observations written by aborted workers * fix(memory): track observation linker launch handoff * fix(memory): resolve cross-project related observations * fix(status): avoid recounting reason-only link updates * fix(memory): avoid rereading file for content * fix(linker): use neutral prose for bidirectional reasons * test(memory): coverage for aborted/failed launches * test(memory): cleanup & helpers * feat(linker): add observations index hint
141 lines
4.6 KiB
Python
141 lines
4.6 KiB
Python
"""Middleware that schedules post-run EvoMemory workers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import logging
|
|
from functools import cache
|
|
from pathlib import Path
|
|
|
|
from langchain.agents.middleware.types import AgentMiddleware, AgentState
|
|
from langgraph.runtime import Runtime
|
|
|
|
from .. import paths as _paths
|
|
from ..memory.launch import (
|
|
alaunch_memory_worker,
|
|
launch_memory_worker,
|
|
launch_observation_linker,
|
|
)
|
|
from ..memory.scheduler import MemoryScheduler
|
|
from ..memory.source_context import build_memory_source_context
|
|
from ..memory.types import MemorySourceType
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@cache
|
|
def default_memory_scheduler() -> MemoryScheduler:
|
|
return MemoryScheduler(launch_linker=launch_observation_linker)
|
|
|
|
|
|
class EvoMemoryLifecycleMiddleware(AgentMiddleware):
|
|
"""Schedule post-turn and post-subagent memory workers."""
|
|
|
|
name = "evomemory_lifecycle"
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
memory_dir: str | Path,
|
|
workspace_dir: str | Path | None = None,
|
|
project_id: str,
|
|
source_type: MemorySourceType,
|
|
source_agent: str,
|
|
memory_scheduler: MemoryScheduler | None = None,
|
|
) -> None:
|
|
self._memory_dir = Path(memory_dir).expanduser()
|
|
self._workspace_dir = Path(
|
|
_paths.WORKSPACE_ROOT if workspace_dir is None else workspace_dir
|
|
).expanduser()
|
|
self._project_id = project_id
|
|
self._source_type = source_type
|
|
self._source_agent = source_agent
|
|
self._memory_scheduler = (
|
|
memory_scheduler
|
|
if memory_scheduler is not None
|
|
else default_memory_scheduler()
|
|
)
|
|
|
|
def after_agent(
|
|
self,
|
|
state: AgentState[object],
|
|
runtime: Runtime,
|
|
) -> dict[str, object] | None:
|
|
context = build_memory_source_context(
|
|
state=state,
|
|
runtime=runtime,
|
|
memory_dir=self._memory_dir,
|
|
workspace_dir=self._workspace_dir,
|
|
project_id=self._project_id,
|
|
source_type=self._source_type,
|
|
source_agent=self._source_agent,
|
|
)
|
|
if context is not None:
|
|
try:
|
|
run = launch_memory_worker(
|
|
context,
|
|
on_worker_finished=self._memory_scheduler.record_worker_finished,
|
|
on_worker_aborted=self._memory_scheduler.record_worker_aborted,
|
|
)
|
|
if run is None:
|
|
self._memory_scheduler.flush_ready()
|
|
except Exception:
|
|
logger.warning("Failed to launch EvoMemory worker", exc_info=True)
|
|
self._memory_scheduler.flush_ready()
|
|
else:
|
|
self._memory_scheduler.flush_ready()
|
|
return None
|
|
|
|
async def aafter_agent(
|
|
self,
|
|
state: AgentState[object],
|
|
runtime: Runtime,
|
|
) -> dict[str, object] | None:
|
|
context = build_memory_source_context(
|
|
state=state,
|
|
runtime=runtime,
|
|
memory_dir=self._memory_dir,
|
|
workspace_dir=self._workspace_dir,
|
|
project_id=self._project_id,
|
|
source_type=self._source_type,
|
|
source_agent=self._source_agent,
|
|
)
|
|
if context is not None:
|
|
try:
|
|
run = await alaunch_memory_worker(
|
|
context,
|
|
on_worker_finished=self._memory_scheduler.record_worker_finished,
|
|
on_worker_aborted=self._memory_scheduler.record_worker_aborted,
|
|
)
|
|
if run is None:
|
|
await asyncio.to_thread(self._memory_scheduler.flush_ready)
|
|
except Exception:
|
|
logger.warning("Failed to launch EvoMemory worker", exc_info=True)
|
|
await asyncio.to_thread(self._memory_scheduler.flush_ready)
|
|
else:
|
|
await asyncio.to_thread(self._memory_scheduler.flush_ready)
|
|
return None
|
|
|
|
|
|
def create_memory_lifecycle_middleware(
|
|
memory_dir: str | None = None,
|
|
*,
|
|
workspace_dir: str | Path | None = None,
|
|
project_id: str,
|
|
source_type: MemorySourceType,
|
|
source_agent: str,
|
|
memory_scheduler: MemoryScheduler | None = None,
|
|
) -> EvoMemoryLifecycleMiddleware:
|
|
"""Build the post-run EvoMemory lifecycle middleware."""
|
|
|
|
if memory_dir is None:
|
|
memory_dir = str(_paths.MEMORIES_DIR)
|
|
return EvoMemoryLifecycleMiddleware(
|
|
memory_dir=memory_dir,
|
|
workspace_dir=workspace_dir,
|
|
project_id=project_id,
|
|
source_type=source_type,
|
|
source_agent=source_agent,
|
|
memory_scheduler=memory_scheduler,
|
|
)
|