Watch
1
0
Fork
You've already forked SouveraineOS
0
SouveraineOS/docs/substrate/MEMORY_BLOCKS_DECISION.md
Fimeg e480809c70 docs: rescue the agent-substrate tree out of a gitignored directory
219 files, 2.0 MB, untracked in souveraine/docs and existing nowhere else.
The volume is at 100% with no snapshots.
2026-07-26 12:11:50 -04:00

8.5 KiB

description status date authors
Decision record — Souveraine abandons first-class memory_blocks; memfs files (with frontmatter) are the sole memory primitive Accepted 2026-05-07 Casey, Opus

Memory Blocks vs. Memfs Files — Decision Record

Context

Letta's lineage has two memory primitives:

  • memory_blocks (v1) — labeled, length-capped, server-side records mutated via PATCH /v1/blocks/{id}. Multiple agents can attach the same block; mutations propagate on next compile.
  • memfs (v3) — git-backed filesystem with frontmatter. Agents read/write files via tool calls. History is the git log. Skills co-locate with policy.

Souveraine inherits both options through its Letta-adjacent design heritage. The question this doc answers: does Souveraine keep memory_block as a first-class API entity, or is the memfs file (with frontmatter) the only memory primitive?

agent_inventory::create() currently accepts a MemoryBlock { label, value, limit } argument and writes the value straight to system/{label}.md. The block concept exists at creation time but vanishes at rest. This decision either commits to that vanishing (Path 1) or revives blocks as a typed view (Path 2).

Input from Letta (Ezra, 2026-05-07)

Asked Ezra (Letta support agent) whether memory_blocks is vestigial v1 carryover. The answer surfaces real, non-overlapping capabilities that today's blocks have over memfs files:

  1. Shared blocks across agents. One block, many attached agents, mutations visible to all on next compile. Memfs has no shared-state primitive (LET-8217).
  2. API-first mutation. PATCH /v1/blocks/{id} is one HTTP call; clients skip the git commit/push dance.
  3. Selective subagent handoff. Subagent config's memoryBlocks: <list> passes blocks selectively. Memfs travels as a whole directory.
  4. Non-memfs agents still use blocks as the base mechanism — non-memfs path inherits the legacy block addon.
  5. limit enforcement. Works on the block API path. Memfs write path bypasses it (LET-8133 / #3241), so frontmatter limit: is advisory in Letta today.

Cameron's Our Next Phase (2026): blocks "may or may not be deprecated in favor of memfs." Direction is memfs-first for Letta Code agents, but blocks remain available pending Cameron's call.

Analysis — Does Souveraine Need These Capabilities?

Letta capability Souveraine need? Reason
Shared blocks across agents No Constitution Article I.1: Primary and Subconscious are one consciousness, two modes, not separate agents. Aster's symlink hack (Fimeg, Mar 25 2026) was needed in Letta because Aster was a separate agent. In Souveraine, "Aster" is an N+1 invocation of the same agent against the same memfs. The use case Letta blocks solve doesn't exist here.
API-first mutation Yes, but not as blocks The cron-into-memfs pattern (Fimeg writes weather, fastfetch, filesystem tree, daemon branch) needs an HTTP write path. Replicate as POST/PATCH /v1/agents/{id}/memory/{path} doing a memfs write + auto-commit. Same capability, no block primitive needed.
Selective subagent handoff Addressable at file level Constitution Article VIII pattern: subagent receives state.md + the files named in files_touched. That is selectivity by file path. Same effect as memoryBlocks: [list] without a separate block taxonomy.
Non-memfs path Irrelevant Memfs is mandatory in Souveraine. There is no non-memfs path to be backwards-compatible with.
limit enforcement Take it — fix it on our way through Souveraine controls the write path via core::memory::MemFS. Wire a check: if frontmatter declares limit: N, the memory write tool errors if the write exceeds N bytes/tokens. This closes Letta's LET-8133 gap as a side effect of the harness owning its tool dispatch.

Decision

Path 1: memfs-only. Souveraine has no first-class memory_block entity. Every durable memory artifact is a file under the agent's memfs git repo, with YAML frontmatter for schema. The MemoryBlock struct used by agent_inventory::create() is a creation-time convenience that writes to system/{label}.md and is not preserved as a typed entity afterward.

Implementation Implications

  1. No /v1/blocks API surface. The HTTP API exposes agents and memfs only.
  2. /recompile is memfs-flavored. It means "re-read system/ before assembling the next turn's prompt." Two variants:
    • Implicit: harness re-reads system/ at every turn-start (handles cron-driven updates automatically).
    • Explicit: memory recompile for the agent to force a refresh after editing its own system/ mid-conversation.
  3. Frontmatter limit: becomes load-bearing. The memory write tool enforces it (closing LET-8133). Today the memory module parses description and read_only; limit: is the next field to validate.
  4. Cron-into-memfs gets an HTTP endpoint. POST /v1/agents/{id}/memory/{path} (and PATCH for partial updates) lets external clients write to memfs without git CLI access. The endpoint does the commit. This replaces the use case for PATCH /v1/blocks/{id}.
  5. Agent creation simplifies. CreateAgentRequest.memory_blocks can become CreateAgentRequest.memfs_seed: HashMap<PathBuf, String> — a map of relative paths to initial content. Frontmatter is added on write.
  6. No block_id, no pinned, no limit field on a block struct. Frontmatter read_only: true does what pinned would. Frontmatter limit: does what the block limit field does, with harness enforcement.

Path 2 (rejected) — for the record

Keep MemoryBlock as a typed view over memfs files: block_id, label, limit, pinned, shared_with: Vec<AgentId>. /recompile re-renders blocks-with-limits into the system prompt header. Closer to Letta v1 ergonomics; gives the agent an explicit "this is a block, not just a file" signal.

Rejected because:

  • Doubles the abstraction surface (agents must learn both blocks and files).
  • The Constitution's diff-canary security model (docs/CONSCIOUSNESS_CYCLE.md § "Cloister Security Model") works on file paths and frontmatter — adding a parallel block taxonomy means writing the canary twice.
  • The capabilities blocks would unlock (shared state, selective handoff) are either not needed (Article I.1) or addressable without a new primitive.
  • Cameron's direction (Apr 2026): "memfs + skills is the correct abstraction." Souveraine inherits that judgment.

Consequences

Positive

  • Single memory primitive for the agent to learn and reason about.
  • The Cloister diff-canary has one schema layer (frontmatter), not two (frontmatter + block schema).
  • limit enforcement closes LET-8133 from day one.
  • Cron-into-memfs becomes a clean HTTP endpoint instead of a parallel /v1/blocks surface.
  • Skills + memfs (Cameron's recommended abstraction) is the only path; no temptation to encode workflow rules in blocks.

Negative

  • No drop-in compatibility with Letta agents that use memory_blocks exclusively. Migration to Souveraine requires writing block content into memfs files (Letta itself has no migration utility for this — gap noted in Fimeg.md Mar 25 2026).
  • Shared state between agents (if Souveraine ever grows beyond one-consciousness-two-modes) requires building a primitive Letta hasn't shipped (LET-8217). Not blocking; revisit if/when.

Open Questions

  1. Frontmatter limit: units. Bytes, characters, or tokens? Tokens are most useful but require a tokenizer at the harness layer. Tentative: characters for v1 (cheap), tokens later (when the model_router gets a tokenizer hook).
  2. Cron-API auth. POST /v1/agents/{id}/memory/{path} needs a credential model. Token in header? Per-agent API key? Deferred — not blocking N+1 work.
  3. Migration helper. Should souveraine have a CLI to import a Letta agent's blocks → memfs files? Probably yes, slated as a future utility once the agent format is stable.

References

  • docs/CONTEXT_CONSTITUTION.md — Articles I.1 (duality), V (memory physics), VIII (subagent file-level selectivity)
  • docs/CONSCIOUSNESS_CYCLE.md — Cloister Security Model (frontmatter as schema, diff as canary)
  • reference/Fimeg.md — Mar 25 2026: Aster symlink hack and cross-agent memfs gap; cron-into-memfs pattern
  • Ezra (Letta support, 2026-05-07) — distinct-capabilities answer cited above
  • Cameron, Our Next Phase (2026): blocks "may or may not be deprecated in favor of memfs"
  • LET-8133 / #3241: memfs write path bypasses block limit (Letta upstream)
  • LET-8217: shared-state primitive for memfs (Letta upstream, not currently scoped)