Governed Memory Layer for Multi-Agent Shared State
Gated append-only memory, pending ambiguity, and canonical shared state.
concept
Shared memory needs a promotion boundary, not a larger notes table
Multi-agent systems need a common operational memory, but an append-only notes store is a weak boundary. A vague observation can become a durable issue. A paraphrased lifecycle update can close the wrong row. A malformed result record can persist long enough to mislead the next agent.
Governed Memory Layer is built around a stricter model: raw note text is not canonical state. A note is a candidate write. Before it becomes shared memory, the runtime must determine whether the note is meaningful, what entity it refers to, whether the mutation is structurally valid, and how it should project into the canonical tables read by the rest of the system.
Architectural boundary: separate note capture from shared-memory mutation, then represent ambiguity as pending state instead of silent corruption.
architecture
Promotion pipeline and persistence topology
The runtime is organized as a sequence of narrow gates. Agents can write notes into private working memory, but they cannot write canonical shared state directly. Every durable memory mutation flows through the promotion stack and then through a single persistence path.
+------------------------------------------------------------+
| AGENT / TOOL OUTPUT |
| Natural-language notes, observations, status updates |
+------------------------------------------------------------+
|
v
+------------------------------------------------------------+
| WORKING MEMORY |
| Private, per-agent, per-run note buffer |
| |
| Stores timestamped notes only. |
| No schema enforcement. No shared writes. |
+------------------------------------------------------------+
|
v
+------------------------------------------------------------+
| PROMOTION PIPELINE |
| |
| 1. Interpreter: should this note become shared memory? |
| 2. Resolver: what existing thing, if any, does it refer to?|
| 3. Validator: is this write structurally safe? |
| 4. Inputter: commit to ledger and project canonical state |
+------------------------------------------------------------+
|
+-----------------------------+
| |
v v
+------------------------------------------------+ +----------------------+
| EVENTS LEDGER | | PENDING QUEUE |
| append-only events_memory | | pending_memory_events|
| every committed canonical write is recorded | | unresolved but |
| first, even if projection fails | | important lifecycle |
| | | writes wait here |
+------------------------------------------------+ +----------------------+
|
v
+------------------------------------------------------------+
| DETERMINISTIC PROJECTOR |
| shared_memory_writer.py |
| |
| Updates shared_plan / shared_issues / shared_constraints |
| / shared_decisions / shared_results / shared_task_state |
| / shared_learnings |
+------------------------------------------------------------+
|
v
canonical shared memory
The four governance stages
- Interpretation — classify a note as meaningful shared state or reject it as noise, speculation, or insufficiently structured.
- Resolution — decide whether a lifecycle note safely binds to an existing canonical entity, creates a new one, or must remain provisional.
- Validation — enforce bucket, operation, payload, and lifecycle-target correctness deterministically before canonical mutation.
- Projection — persist the ledger event, then update canonical tables through deterministic SQL rather than free-form model output.
The key architectural claim is not that the interpreter is always right. It is that shared state is never mutated on the basis of model text alone. The model can propose meaning. It cannot directly define canonical memory.
implementation
What a governed write actually looks like
The most important interface in the system is the structured write request emitted after interpretation. From that point on, the pipeline becomes progressively more deterministic.
{
"decision": "accept",
"bucket": "issues",
"target_id": "pandas_import_blocker",
"operation": "resolve",
"payload": {},
"reference_text": "the earlier blocker",
"candidate_aliases": ["pandas error", "import blocker"],
"confidence": 0.84,
"rationale": "lifecycle update referring to an earlier open issue"
}
The resolver then compares that request against current canonical context: open issues, active constraints, and active decisions. If the reference clearly binds to one existing row, the write can commit. If it cannot safely identify the target, the system does not guess. It preserves the write in pending_memory_events and retries later when fresh canonical state exists.
{
"event_id": "uuid",
"bucket": "issues",
"target_id": "pandas_import_blocker",
"operation": "resolve",
"payload_json": "{}",
"raw_input": "The earlier blocker is fixed.",
"source_ref": "{ matched_target_id, candidate_matches, resolution_reason }",
"applied_successfully": 1
}
That ledger row is the durable source of truth. Canonical tables are downstream projections. This is why the system can recover from projection failures: the audit trail exists independently of whether the projector succeeded in that moment.
runtime
Core runtime responsibilities
The runtime is not a monolithic memory object. It is a set of narrow modules, each responsible for one boundary in the note-to-memory path.
WorkingMemory
Agent-local scratch space for a single run. It stores timestamped agent and tool_result notes, tracks whether they have been processed, and intentionally does not enforce schema, identity, or canonical semantics.
PromotionPipeline
The orchestrator. It pulls unpromoted notes, rebuilds fresh shared-memory context, runs interpretation, resolution, validation, and persistence, then retries pending writes after every successful canonical commit.
Resolver plus Validator
The resolver handles identity and lifecycle policy. The validator enforces deterministic structural rules such as known buckets, allowed operations, slug-like target IDs, and existence of lifecycle targets when needed.
Inputter
The sole persistence entry point. It writes to the event ledger first, then asks the deterministic projector to update canonical tables, preserving the committed event even if projection later fails.
SharedMemoryWriter plus SharedMemory
SharedMemoryWriter projects events into canonical tables with bucket-specific semantics. SharedMemory is the read API consumed by the rest of the system, so agents read current belief from canonical state rather than raw note text.
PendingMemoryQueue
Holds important writes that are not yet safe to commit. Instead of dropping ambiguous lifecycle updates or forcing a guess, the system preserves them as explicit pending work that can be retried later against fresher context.
Context is intentionally narrow: the interpreter and resolver do not receive the whole database. They work from open issues, active decisions, and active constraints so lifecycle reasoning stays focused on the buckets where ambiguity is most dangerous.
storage
Storage model and table roles
The persistence layer is simple on purpose. SQLite is the backing store, connections run in WAL mode, and the database path can be overridden through AGENT_MEMORY_DB_PATH. The important design choice is not the database engine itself. It is the separation between ledger, pending queue, and canonical projections.
| Surface | Purpose | Why It Exists |
|---|---|---|
events_memory |
Append-only ledger of committed canonical writes | Durability and audit. Every committed canonical mutation is recorded here first, even if projection later fails. |
pending_memory_events |
Deferred writes that matter but are not yet safe to commit | Uncertainty handling. Ambiguous lifecycle updates become explicit pending state rather than silent corruption. |
shared_* tables |
Canonical state agents and tools are expected to read | Operational state. Current belief is stored as deterministic projections rather than free-form notes. |
Reference memory is embedded inside canonical issue, constraint, and decision rows through reference_memory_json. That stored alias and phrase history is what helps the resolver reconnect later notes like “that earlier blocker” back to the same canonical object instead of creating duplicates or closing the wrong row.
semantics
Canonical bucket semantics
Each bucket has its own projection contract. Some behave like current state, some behave like append-only history, and some have lifecycle transitions that explicitly retire earlier rows.
| Bucket | Operations | Projection Rule |
|---|---|---|
plan |
upsert |
Single canonical row in practice, keyed by main; content is replaced and version increments. |
constraints |
upsert, invalidate |
Represents currently active restrictions; lifecycle moves rows between active and invalidated state. |
issues |
upsert, resolve |
Tracked problems and blockers; canonical rows stay open until a bound lifecycle write resolves them. |
decisions |
append, invalidate |
Choices accumulate over time; invalidation projects to superseded state rather than deleting history. |
results |
append |
Append-only measurements or outcome records; multiple rows may coexist for the same task or experiment. |
task_state |
upsert |
Latest state is selected by task_id; optimized for current operational status rather than event history. |
learnings |
append |
Append-only reusable knowledge stored as active canonical rows. |
This mix is intentional. Plans and task state behave like current truth. Results and learnings behave like durable historical accumulation. Issues, constraints, and decisions sit in the middle, because they need lifecycle operations without losing identity over time.
identity
The hardest part is identity continuity across time
Structural validation is comparatively easy. The difficult problem is preserving the identity of a thing across changing language. A system might capture an issue today as pandas_import_blocker, then receive tomorrow’s note phrased only as “that earlier blocker.” If the system binds too aggressively, it closes the wrong row. If it binds too weakly, it creates duplicates and leaves stale active state behind.
The resolver uses explicit target IDs, explicit snake_case slugs in note text, token overlap, interpreter-provided alias hints, and stored reference memory to score candidate matches. But when the match is still weak, the architecture chooses delay over false certainty.
Uncertainty becomes explicit state instead of silent corruption
When a lifecycle note cannot be bound confidently to an existing canonical row, the system does not force a guess. It records the unresolved write in the pending queue and retries later after canonical state changes. That turns ambiguity from a hidden failure mode into a visible, inspectable backlog.
The append-only ledger, reference memory, pending queue, and deterministic projector all exist to support that one choice. The system would be simpler without those parts, but it would also be far easier to silently corrupt shared state.
flows
End-to-end write flows
There are three operational paths worth understanding: normal commit, provisional deferral, and pending replay.
Working memory yields an unpromoted note. The pipeline interprets it, resolves identity, validates structure, writes an event into events_memory, projects canonical state through SharedMemoryWriter, then marks the event as successfully applied.
If a lifecycle write matters but cannot be safely attached to an existing canonical row, the pipeline does not reject it as noise and does not commit it as fact. It stores the serialized request in pending_memory_events so the uncertainty remains visible and recoverable.
After every successful canonical commit, the runtime runs a bounded retry loop over retryable pending items. A newly committed canonical row can make an older ambiguous note resolvable, which means pending work is not dead data. It is deferred resolution debt the system actively revisits.
- Agents never write canonical tables directly. All durable writes pass through the governed pipeline and the Inputter.
- Every committed mutation enters the ledger first. Canonical tables are projections, not the durability backbone.
- Projection is deterministic and bucket-specific. Each bucket has explicit write semantics instead of generic text-to-state mutation.
- Ambiguity is preserved, not collapsed. The pending queue exists so uncertain lifecycle updates do not silently become wrong facts.
scope
Limitations and current scope
The repository is intentionally concentrated on the note-to-memory boundary. It is a disciplined memory runtime, not a full production multi-agent platform.
The following are current scope limits, not accidental omissions:
- Identity-specialized resolver logic exists only for issues, constraints, and decisions.
- Reference memory is currently stored only on those same lifecycle-sensitive buckets.
- Classification quality still depends partly on the live interpreter model and prompt.
- Replay exists in development tooling, not yet as a standalone production repair command.
- No full runtime orchestration layer, service API, or UI is included yet.
- The implementation is synchronous and SQLite-based rather than distributed.