End to end
This page shows how the parts fit together, from serving a session to driving it with agents.
The model
Section titled “The model”A session has exactly one writer, which is a lambo serve process that holds the session’s lease. Readers query the store directly and never open a writer.
Writes flow through one in-memory graph. They land in a write-behind log and flush to the durable store on an interval, so durability is eventual, not immediate. Call lambo_stats on the writer to see the current lag. Each flush goes out as a few batched statements rather than one per change. A burst of writes then costs a handful of round trips instead of one per change.
Canonization runs in the background. It promotes concepts to canonical facts when they earn it from structural evidence, not when an agent declares them important.
Every concept written this way keeps its embedding, so memory your agents produce is recallable by meaning and not only by keyword, on a store that supports vector search. See What recall searches.
Text is checked on the way in. Lambo refuses control characters other than tab and newline. It also refuses invisible formatting characters such as bidi overrides and zero-width spaces. Nothing a reviewer cannot see reaches the graph. See What Lambo accepts in text.
A walkthrough you can run
Section titled “A walkthrough you can run”This uses SQLite and the fixture embedder, so it needs no external services. A released binary already carries both, so you only have to point the config at them. If you build from source instead, include --features store-sqlite.
Write the config
[store]kind = "sqlite"path = "./lambo.db"[embedder]kind = "fixture"dim = 1024Provision the schema
Terminal window lambo --config lambo.toml provisionsqlite schema provisioned (init_schema, idempotent)Skip this step only if you use the in-memory store.
Write some memory
Terminal window lambo --config lambo.toml derive --session demo --agent agent-a \--content "user schema" --kind entity \--concept "auth middleware:entity" \--concept "must stay backward compatible:constraint" \--parent-of "auth middleware:user schema"lambo --config lambo.toml record-action --session demo --agent agent-a \--action "created migrations/003.sql" \--produces "migrations/003.sql" \--depends-on "user schema"Read it back
Terminal window lambo --config lambo.toml recall --session demo --query "update user schema"lambo --config lambo.toml inspect --session demo --focus "user schema"lambo --config lambo.toml stats --session demo
Drive the same session over MCP
Section titled “Drive the same session over MCP”Start the writer, and connect an MCP client to it.
lambo --config lambo.toml serve --session demo --agent agent-aPoint your client at that command over stdio. See Installation for the mcpServers block.
To watch the wire yourself, pipe JSON-RPC frames into the server, one per line. This handshake, tool listing, and single call is what a client does for you.
{ echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"probe","version":"0.1.0"}}}' echo '{"jsonrpc":"2.0","method":"notifications/initialized"}' sleep 1 echo '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' sleep 1 echo '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"lambo_derive","arguments":{"agent_id":"agent-a","concepts":[{"content":"stdio wire check","concept_type":"entity"}]}}}' sleep 1} | lambo --config lambo.toml serve --session wire-demo --agent agent-aThe initialize reply names the session and carries the server’s instructions, tools/list returns the seven tools with their schemas, and the call returns the derive summary.
{"content":[{"type":"text","text":"derived 1 concept(s): 1 created, 0 matched existing"}],"isError":false}Stop the server with Ctrl-C or a clean disconnect. Lambo flushes whatever has not reached the store yet and releases the lease, even on a signal. Because a flush is batched, even a heavy burst written moments before you stop drains quickly rather than trickling out one change at a time.
To watch the session in a browser while the writer runs, add a read-only lambo serve-web window on the same store:
lambo --config lambo.toml serve-web --session demoIt serves on port 7710 (loopback, unauthenticated by default) and only reads. It shows live recall, the canonization feed, and durable counts. It never contends for the writer lease. See Command line for its flags.
What recall searches
Section titled “What recall searches”Recall has three legs: keyword matching, vector similarity, and expansion through the graph edges around what it found. The vector leg needs a store with vector search.
| Store | Keyword | Vector | Graph expansion |
|---|---|---|---|
| CockroachDB | Yes | Yes | Yes |
| SQLite | Yes | Yes | Yes |
| In-memory | Yes | No | Yes |
Concepts your agents write through derive and record-action store their embedding along with their text. On a store with vector search, that means recall finds memory the swarm wrote itself by meaning, not just memory you loaded in bulk ahead of time. This matters most for the swarm pattern below, where many agents write the same idea in different words.
The walkthrough above uses SQLite, so it exercises all three. On SQLite the vector leg is an exact scan rather than an index lookup, which is the right trade while a session’s concept count stays small and returns exact nearest neighbours rather than approximate ones. The process serving a session (lambo serve, or an embedded Memory) runs that scan over the vectors it already holds in memory, so recall and derive never read vectors back from the database and see a concept as soon as it is written, before it is flushed. A separate reader such as lambo recall or lambo serve-web scans the database, with the same ranking. The in-memory store has no vector leg at all: recall there cannot match on meaning under any embedder.
The walkthrough exercises the vector leg mechanically, not semantically. Its config is [embedder] kind = "fixture", and the fixture embedder is deterministic, not semantic: it hashes the normalized phrase to a pseudo-random unit vector, so two phrasings of the same idea are near-orthogonal unless they happen to fall in its one hardcoded family. Running the walkthrough proves vectors are written, queried, and ranked; it does not show a semantic hit, and the scores you see for non-keyword matches will be flat. For recall by meaning, point [embedder] at a real embedder — kind = "bge_m3" against a local llama.cpp server. A captured run of exactly that, where a query sharing no vocabulary with a derived concept recalls it, is in evidence/mooshik-f-sqlite-bge/ (that link resolves once the lambo-for-mooshik branch merges; the directory is committed there today).
One asymmetry to know when you write your own queries: derive embeds the concept with its context — the string "{content} — {origin}" — while recall embeds your query text bare. So a query is compared against the framed string, not against the concept’s content alone. With a real embedder this costs a little similarity and the ranking survives it; with the fixture embedder, which has no notion of similarity, it is the difference between a hit and a miss.
Readers and the writer
Section titled “Readers and the writer”While a serve process owns a session, the command line read verbs still work against it, because they go to the store rather than to the writer.
What they cannot see is the writer’s own state. A reader reports the counts and says which figures are writer-only.
session 'demo' (reader snapshot)nodes=7 edges=12 concepts=5 canonical=0epoch=0flush_lag=n/a log_depth=n/a daemon_cycles=n/a canonization_cycles=n/anote: flush_lag / log_depth / daemon_cycles / canonization_cycles are writer-only; this is a reader processFor flush lag and write-log depth, ask the writer through the lambo_stats tool.
One writer, enforced across processes
Section titled “One writer, enforced across processes”A second command line write on the same session is refused while a serve holds the lease. The refusal names the holder, so you know which process to stop.
A second serve is different, and is not refused: over stdio it connects to the holder and forwards your calls, so the session still has exactly one writer while every client gets full access. See MCP tools for that. Over HTTP a second serve is refused like a command line write.
$ lambo --config lambo.toml derive --session demo --agent agent-b --content "x" --kind entitylambo derive: conflict: session demo is already held by another writer (agent-a@host#10492) — it acquired the single-writer lease 66s ago; the lease was last refreshed 6s ago and expires in 39s unless the holder refreshes it again. ...$ echo $?1If a holder crashes, its lease expires and the next writer takes the session. The old holder is then fenced: its further writes are refused and its un-flushed tail is dropped rather than allowed to overwrite the new holder.
Run a swarm
Section titled “Run a swarm”Run one lambo serve writer for the session, then have many small agents each write with a single command line call.
lambo --config lambo.toml derive --session demo --agent agent-7 \ --content "retry budget is 3" --kind constraintBecause each agent call is one deterministic line, a small local model can drive it reliably, and no tool schema takes up the model’s context. Canonization collapses the duplicate observations that many agents produce into single canonical facts.
For coordination, use lambo_reserve from the writer before editing a shared concept. A reservation taken by a one-shot command line call ends when that command exits, so the durable coordination point is the writer. See Command line.
See Installation, MCP tools, Command line, Library API, and Configuration.