Slates
Slates
Slates
A slate is a persistent memory store declared at config time. It is composed of folders of plain-text files, each file carrying typed metatags. A slate survives across ladder invocations: a ladder writes to a slate on call N, reads from it on call N+1. The author declares what gets written and what gets retrieved; there is no implicit context-stuffing. Memory in LADR is programmable, not magical.
Why slates exist
Every published reasoning strategy in the literature is stateless. Tree-of-Thoughts, reflexion, self-consistency, self-refine — they all start each invocation from scratch. Whatever the strategy learned on call 1 is gone by call 2. The model has no memory of having reasoned about this user, this task, this domain before.
That's not for lack of trying. It's because no reasoning strategy has had a memory primitive to use. LangChain has vector stores, but those are embeddings soup — opaque, unstructured, hard to debug. Agent frameworks have "memory" but it's just chat history. None of it is the structured, schema'd, declared-at-config-time, programmatically-writable memory that a reasoning strategy needs to compound across calls.
Slates are that primitive. They give the ladder author:
- Declaration over magic. The author declares what folders exist, what files exist, what types the metatags carry. The runtime enforces the schema. No more "what did the LLM decide to remember?" surprises.
- Structured writes. A step can declare "after I run, extract the JSON score from my output and append it to the trace folder." The runtime does the extraction, validates against the schema, writes only what conforms.
- Structured reads. A step can declare "read the lessons file at the start so I see what failed last time." The runtime reads and injects it as a field.
- Persistence across calls. The same ladder invoked twice by the same user sees the same slate. Learning accumulates.
- Cross-ladder sharing. A slate declared in one ladder can be referenced by another via cross-ladder jumps. One ladder's lessons can inform another.
Without slates, every ladder is a one-shot program. With slates, a ladder becomes a long-running reasoning agent that compounds knowledge over time.
Slate anatomy
A slate declaration in YAML:
slates:
- title: Project Memory # required - unique within the config
folders: # required - at least one
- name: facts # required - unique within the slate
tokenLimit: 500 # required - per-file token cap
access: readWrite # readWrite (default) | read
evictionPolicy: FIFO # FIFO (default) | LRU | reject
metatags: # optional - folder-level schema
- { name: summary, type: string }
- { name: tags, type: 'string[]' }
files: # optional - declared files
- name: core.md # file name; unique within folder
init: blank # blank or literal content
metatags: # file-level extension to folder schema
- { name: confidence, type: number }
- { name: source, type: string }
- name: scratch.md
init: blankThe slate hierarchy:
- A slate has a title and at least one folder.
- A folder has a name, a per-file token limit, an access policy, an eviction policy, optional metatags (the folder's base schema), and optional pre-declared files.
- A file has a name, an initial content (
blankor literal), and optional metatags (extending the folder's schema for this file specifically).
The strict parser rejects unknown keys at every level. Adding a new field is a schema change, not a silent extension.
Effective schema
Every file has an effective schema computed as the union of its folder's metatags and its own metatags. If a metatag with the same name exists at both levels, the file-level declaration wins.
slates:
- title: Project Memory
folders:
- name: facts
tokenLimit: 500
metatags: # applies to ALL files in this folder
- { name: summary, type: string }
- { name: tags, type: 'string[]' }
files:
- name: core.md
init: blank
metatags: # extends folder schema for THIS file only
- { name: confidence, type: number }
- { name: source, type: string }
- name: scratch.md
init: blank
# no file-level metatags; effective schema = folder schema onlyFor core.md, the effective schema is { summary, tags, confidence, source } (folder + file).
For scratch.md, the effective schema is { summary, tags } (folder only).
slateRead and slateWrite operations validate against the effective schema. Reading a metatag that isn't in the file's effective schema (and isn't a system metatag) fails validation. Writing one is also caught.
Folder access policies
Every folder has an access policy controlling whether the ladder can write to it.
| Policy | Read | Write |
|---|---|---|
readWrite (default) | Yes | Yes |
read | Yes | No |
There is no write-only policy. It was considered and rejected as a footgun: a folder the ladder can write to but cannot read provides no observable state, which makes debugging impossible.
A ladder that tries to write to a read folder gets a runtime error: folder "X" is read-only. The step fails; the rest of the ladder may still run depending on its error-handling config.
Typical pattern: declare two parallel folders, one read-only for stable reference data (canonical facts, embeddings) and one read-write for accumulated state (lessons, scratch, in-progress work).
slates:
- title: Knowledge Base
folders:
- name: canonical # read-only; set up out-of-band
tokenLimit: 2000
access: read
files:
- { name: facts.md, init: "" }
- name: working # read-write; ladder appends here
tokenLimit: 500
access: readWrite
evictionPolicy: FIFO
files:
- { name: notes.md, init: blank }Eviction policies
Every folder declares a tokenLimit per file. When a write would push a file past its limit, the runtime applies the folder's eviction policy.
| Policy | Behavior on overflow |
|---|---|
FIFO (default) | Oldest content (from the top of the file) is removed until the new content fits. The most recent content survives. |
LRU | Least-recently-read content is removed. The runtime tracks read counts per file. Useful for folders where some entries are accessed often and others never. |
reject | The write fails. The file is unchanged. Useful when you never want silent data loss; pair with explicit cleanup logic in the ladder. |
Eviction is per-file, not per-folder. A folder with three files at 80% capacity each is fine; the limit applies to each file individually.
Eviction preserves content boundaries when possible. FIFO eviction starts at a newline character so partial lines aren't left dangling. The runtime does not split JSON structures mid-object; if eviction would corrupt a JSON array, it removes whole entries.
Token counting currently uses a rough approximation (chars / 4). A real tokenizer is planned. The approximation is conservative; a file that fits under the limit today will continue to fit under the real tokenizer.
Two-tier storage
Slates use a write-behind cache pattern with two tiers.
Hot tier (during execution). When a ladder instance starts, the runtime loads the relevant slates into the hot tier. Every slateRead, slateWrite, and retrieve operation hits the hot tier — sub-millisecond latency. The runtime never touches the persistent store mid-execution.
Cold tier (persistent). After execution (or on step boundaries), the runtime flushes hot-tier state back to the persistent store. This is what users see when they browse the slate outside the ladder: durable, human-readable, diffable files.
| Deployment | Hot tier | Cold tier |
|---|---|---|
| Reference impl | In-process in-memory map | None (or optional filesystem for inspection) |
| Production cloud | Redis | Database / object storage |
| Local-first desktop (planned) | In-process in-memory map | Filesystem .md files (Obsidian vault) |
The flush point matters for crash recovery:
- Flush on step boundary (recommended). After each step completes, the runtime flushes slate state. Hot path during a step stays hot-only (fast). Crash recovery re-runs from the last completed step.
- Flush on instance complete (alternative). Simpler but worse crash recovery. If the ladder crashes mid-execution, all slate writes are lost.
The two-tier architecture is invisible to the ladder author. slateRead and slateWrite target the hot tier; the runtime handles the cold tier behind the scenes. The same YAML works identically across deployments.
Slate scope and lifetime
Slates persist across ladder invocations. That's the whole point. But they have a defined scope:
- Per-user. A slate belongs to one user. Different users invoking the same ladder see different slates. Cross-user sharing via published slates is planned.
- Per-ladder. Each ladder has its own slate namespace. Two different ladders cannot share a slate directly (they can exchange data via cross-ladder jumps and slate parameters).
- Per-session (optional). A user can open multiple sessions for the same ladder. Whether sessions share a slate or have isolated slates is configurable per ladder. See Stateful Sessions for the session API and lifecycle.
The runtime guarantees:
- Single-writer per call. Within one execution, only one ladder instance writes to a given slate. No concurrent writers, no race conditions.
- Cross-execution consistency. Last-write-wins across separate executions. If two sessions write to the same file, the second write wins. A planned update adds vector clocks and a user-visible diff tool for finer-grained conflict resolution.
- Atomicity per write. A single
slateWriteeither fully succeeds or has no effect. The runtime does not leave partially-written state on a validation failure.
Slates are explicitly declared. There is no global "memory" or implicit context-stuffing. If a ladder does not declare a slate, it has no memory across calls. This is by design: the author controls exactly what the ladder remembers.
Validation rules
Slate declarations are validated at config load time. The full rule set:
Slate-level:
titleis non-empty.- Slate titles are unique within the config.
- Every slate has at least 1 folder.
Folder-level:
nameis non-empty.- Folder names are unique within the slate.
tokenLimitis positive.- Metatag types are valid (
string,string[],number,boolean,object). - Metatag names are non-empty and unique within the folder.
type: objectmetatags declare an inlineschema.
File-level:
nameis non-empty.- File names are unique within the folder.
- File-level metatag types are valid.
- File-level metatag names are unique within the file.
Reference rules (checked across the whole config):
- Every
slateRead.from.slatereferences a declared slate. - Every
slateRead.from.folderreferences a declared folder in that slate. - Every
slateRead.from.file(if specified) references a declared file in that folder. - Every
slateRead.from.metatag(if specified) is in the file's effective schema or is a system metatag. - Every
slateWrite.to.slateandretrieve.to.slatereference declared slates and folders.
Every error includes the slate title, folder name, and (if relevant) file name so the author can locate the problem in the config.
Common patterns
Accumulated lessons (reflexion memory). A single-folder slate for structured lessons learned across calls.
slates:
- title: Reflexion Memory
folders:
- name: lessons
tokenLimit: 500
access: readWrite
evictionPolicy: FIFO
metatags:
- { name: severity, type: number }
- { name: tags, type: 'string[]' }
files:
- { name: failures.md, init: blank }On every failed verification, the ladder extracts a structured lesson and appends it. On the next call, generate reads the lessons file and uses it to avoid repeating the same mistake.
Maintained entity store (knowledge graph). A slate file using mergeByKey to maintain a structured store of entities.
slates:
- title: Entity Store
folders:
- name: registry
tokenLimit: 2000
access: readWrite
files:
- { name: entities.md, init: "[]" } # JSON array literalA slateWrite with on: mergeByKey, key: id adds or updates entities by their id field. The file remains a valid JSON array across writes; the runtime loads, merges, and writes back.
Read-only reference data. A folder the ladder reads from but never writes to, populated out-of-band.
slates:
- title: Knowledge Base
folders:
- name: docs
tokenLimit: 5000
access: read
files:
# Populated via direct file upload, Obsidian sync, or another ladder's writes
- { name: api_reference.md, init: blank }
- { name: architecture.md, init: blank }The ladder reads from this folder via slateRead or retrieve but cannot write to it. Use it for canonical knowledge that should not be modified at runtime.
Two-tier working + canonical. Combine read-only and read-write folders in one slate.
slates:
- title: Project Memory
folders:
- name: canonical
tokenLimit: 5000
access: read
- name: working
tokenLimit: 500
access: readWrite
evictionPolicy: LRUThe ladder reasons against canonical (stable facts) and writes intermediate state to working (LRU-evicted to bound size). On the next call, working may have changed but canonical is still intact.