v2.4.1 - agent_id plumbing end-to-end
v2.4.1 — agent_id plumbing end-to-end
Date: 2026-07-28
Schema: v20 (was v19 in v2.3.0/v2.4.0)
Canonical tool count: 35 (unchanged — additive integration, no new tools)
Commit: a706646
What this release fixes
v2.3.0 added the agent_id column to agent_memory (Mem0 agent_id
semantics: the LLM that owns each memory). v2.4.0 wired
agent_memory into the vibe-loop (ContextRecap on session_start,
drift_judge enrichment on publish_vibe, PriorFindings on
research_topic) but used project-wide scope. As a result, when
multiple LLMs shared a project, each LLM's recap and judge
enrichment surfaced the OTHER LLM's decisions and findings —
exactly the cross-agent leakage the agent_id column was designed
to prevent.
v2.4.1 closes the plumbing. Every VLP integration point now has
a consistent agent_id resolution chain and forwards the resolved
agent_id to the Store filter.
Resolution priority chain (canonical, applies to all VLP integration points)
- Caller-supplied AgentID on the request —
session_start.AgentIDorpublish_vibe.AgentID. - projects.default_agent_id — set at tenant provisioning via
project_create(default_agent_id="..."). - Empty string — no agent filter; v2.4.0 backward compat.
Helper: resolveActiveAgentID(ctx, requested) in
internal/orchestration/agent_id.go. Best-effort: Store errors
swallowed, falls through to empty string. Never panic.
Migration v20
ALTER TABLE projects ADD COLUMN default_agent_id TEXT;Idempotent (SQLite ADD COLUMN without DEFAULT is a no-op on
re-apply; Postgres uses ADD COLUMN IF NOT EXISTS). No index
needed — read once per session_start / publish_vibe.
What v2.4.1 does NOT do (honest trade-off framing)
The empty-string fallback (priority 3) preserves v2.4.0 backward
compatibility. Projects that have NOT set projects.default_agent_id
retain v2.4.0 behavior: their ContextRecap and drift_judge
enrichment show ALL LLMs' memories, not just the active agent's.
This is a deliberate trade-off — the alternative (making empty
agent_id an error) would be a breaking change for v2.4.0 callers.
Operators who want per-LLM isolation must opt in by either:
- calling
project_create(default_agent_id="your-llm-id")at tenant provisioning, OR - passing
agent_idexplicitly on everysession_start/publish_vibecall.
Wire contract (additive)
dark_memory_project_create: gains optionaldefault_agent_idinput (max 128 chars); echoed on idempotent replay.dark_memory_session_start: gains optionalagent_idinput +active_agent_idoutput.ContextRecap.PinnedMemoriesandOpenTodosare filtered by the resolved agent_id.dark_memory_publish_vibe: gains optionalagent_idinput +active_agent_idoutput.drift_judgeenrichment is filtered by the resolved agent_id.
Store-layer change (the structural fix)
Store.ListAgentMemory now treats AgentID as an ADDITIVE
filter that composes with any scope (Project, Session,
Operator, Agent). In v2.3.0, AgentID was only applied when
scope=agent; v2.4.1 applies it whenever non-empty. When AgentID
is empty, the filter is not applied (v2.4.0 backward compat).
This is the structural fix that mitigates the cross-agent leakage
at the data plane. The orchestrator-level integration just plumbs
the resolved agent_id into the filter; the actual prevention
happens at the Store boundary.
Checksums (SHA-256)
dark-mem-mcp.exe 769089F791ADEDDC32E320D3EEC4B56B191632CCC99AE7802923AFE64F81879E 25611776 bytes
dark-mem-cli.exe B84B7D881BE55BACCCD7D26FF78BDEAC794BDC8002D5320F8FA2745A807C92A9 20588544 bytes
dark-mem-inspect.exe 0F50F51017A6B2F4640F8234CFF1655280A47A579D3A6A94FABB314CA5190DDA 20133376 bytes
Verified by dark-mem-inspect --version → dark-mem-inspect 2.4.1.
Drift governance
dark_memory_judge(eval_type=drift_judge) returned verdict=aligned
confidence=0.96 (evaluation 419). Approved for canonical artifact
publication.
drift_judge evolution:
- Eval 417:
needs_human0.74 (initial; silent-fallback concern) - Eval 418:
drift_detected0.86 (after 3 defensive tests but before Store-level test + honest framing) - Eval 419:
aligned0.96 (final — Store-level test + honest "mitigates the debt" framing + INV-10 justification for empty fallback)
Tests
8 new defensive tests across 2 test files:
Orchestrator-level (helper + VLP integration) in
tests/orchestration/agent_memory_v2_4_1_test.go (7 tests):
TestV241_ContextRecap_RespectsAgentIDTestV241_ContextRecap_NoAgentID_FallsBackToProjectWideTestV241_DriftJudge_EnrichesByActiveAgentIDTestV241_DefaultAgentID_ResolvesOnSessionStartTestV241_AgentID_SessionStart_CallerWinsOverProjectDefaultTestV241_AgentID_PublishVibe_CallerWinsOverProjectDefaultTestV241_AgentID_EmptyChain_NoPanic_Deterministic
Store-level (structural fix verification) in
tests/dual_driver/agent_memory_v2_4_1_test.go (1 test):
TestV241_Store_ListAgentMemory_AdditiveAgentIDFilter(3 assertions: project+agent_id filter, project+empty, agent+agent_id)
Full test suite green (verified pre + post all 8 tests).
Upgrade notes
- Wire contract is additive only. Existing v2.4.0 callers see no behavioral change.
- New
active_agent_idfield onsession_start/publish_vibeoutputs is best-effort and empty when no agent_id is configured. - All integrations inherit INV-10 (rows survive session close). Resolution chain is project-level + per-call; per-session isolation is a v2.4.x follow-up.
- The Store-layer additive filter is the structural fix; the orchestrator-level integration just plumbs the resolved agent_id.
Carrying forward from v2.4.0
- 35 canonical tools (no new tool in v2.4.1)
- Two behavior-default flips from v2.3.0 still apply:
scope=currentdefault isproject(wassessionpre-v2.3.0)bind_sessiondefault isfalse(was implicittruepre-v2.3.0)