Project  ·  Research Build  ·  2026

Governed Memory Layer for Multi-Agent Shared State

A SQLite-backed memory runtime that lets agents write raw notes privately, then promotes only validated, resolvable, canonical state into shared memory.

Type
Research build
Runtime
WorkingMemory -> PromotionPipeline -> Inputter
Storage
events_memory + shared_* + pending_memory_events
Safety Rule
Ambiguous lifecycle writes are deferred, not guessed
4
GOVERNANCE STAGES
7
CANONICAL MEMORY BUCKETS
3
PERSISTENCE SURFACES
1
OFFICIAL WRITE PATH
// motivation

The problem is not storing notes. It is deciding what a note is allowed to mean.

Multi-agent systems need some kind of shared state. The naive version is obvious: let every agent append whatever it thinks matters into a common store and hope the useful state rises above the noise. In practice, that fails in a very specific way. Bad memory is durable. A vague note becomes a tracked issue. A paraphrased lifecycle update closes the wrong row. A malformed result record survives long enough to mislead the next agent.

Governed Memory Layer is built around a stricter view: raw note text is not canonical state. A note is only a candidate write. Before it becomes durable shared memory, the system has to decide whether it is meaningful, what it refers to, whether the write is structurally safe, and how that write should project into the canonical tables everyone else reads.

The core design move: separate note capture from shared-memory mutation, then turn ambiguity into explicit pending state instead of silent corruption.

// architecture

High-level topology

The system is deliberately split into narrow stages. Agents can write notes into private working memory, but they cannot write canonical shared state directly. All durable memory changes flow through a governed 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

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.

// Example: a note becomes a structured write request
{
  "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.

// Example: what gets durably recorded on commit
{
  "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 one giant memory object. It is a set of narrow modules, each responsible for one boundary in the note-to-memory path.

01

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.

02

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.

03

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.

04

Inputter

The only official 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.

05

SharedMemoryWriter plus SharedMemory

SharedMemoryWriter projects events into canonical tables with bucket-specific semantics. SharedMemory is the read API the rest of the system is supposed to consume, so agents read current belief from canonical state rather than raw note text.

06

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 wins 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.

ARCHITECTURAL INVARIANT

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.

N1

Normal commit flow

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.

N2

Provisional flow

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.

N3

Pending replay flow

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.


// 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: