219 files, 2.0 MB, untracked in souveraine/docs and existing nowhere else. The volume is at 100% with no snapshots.
97 lines
8.5 KiB
Markdown
97 lines
8.5 KiB
Markdown
---
|
|
description: Decision record — Souveraine abandons first-class memory_blocks; memfs files (with frontmatter) are the sole memory primitive
|
|
status: Accepted
|
|
date: 2026-05-07
|
|
authors: 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)
|