Skip to content

v2.4.1 - agent_id plumbing end-to-end

Choose a tag to compare

@nicourrutia98 nicourrutia98 released this 28 Jul 15:03

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)

  1. Caller-supplied AgentID on the request — session_start.AgentID or publish_vibe.AgentID.
  2. projects.default_agent_id — set at tenant provisioning via project_create(default_agent_id="...").
  3. 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_id explicitly on every session_start / publish_vibe call.

Wire contract (additive)

  • dark_memory_project_create: gains optional default_agent_id input (max 128 chars); echoed on idempotent replay.
  • dark_memory_session_start: gains optional agent_id input + active_agent_id output. ContextRecap.PinnedMemories and OpenTodos are filtered by the resolved agent_id.
  • dark_memory_publish_vibe: gains optional agent_id input + active_agent_id output. drift_judge enrichment 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 --versiondark-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_human 0.74 (initial; silent-fallback concern)
  • Eval 418: drift_detected 0.86 (after 3 defensive tests but before Store-level test + honest framing)
  • Eval 419: aligned 0.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_RespectsAgentID
  • TestV241_ContextRecap_NoAgentID_FallsBackToProjectWide
  • TestV241_DriftJudge_EnrichesByActiveAgentID
  • TestV241_DefaultAgentID_ResolvesOnSessionStart
  • TestV241_AgentID_SessionStart_CallerWinsOverProjectDefault
  • TestV241_AgentID_PublishVibe_CallerWinsOverProjectDefault
  • TestV241_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_id field on session_start / publish_vibe outputs 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=current default is project (was session pre-v2.3.0)
    • bind_session default is false (was implicit true pre-v2.3.0)