Lemmalog — external working memory
lemmalog is a Datalog engine exposed as MCP tools. It is your working
memory: assertions with provenance and confidence, derived consequences
computed by rules, hypotheses with lifecycles — persistent across agents
and context resets.
The division of labor: the engine owns state and consequence; you own
perception and choice; every choice's outcome returns to the engine.
A claim that isn't in the engine doesn't exist — nothing durable lives in
your context.
Setup
- The
lemmalog_* tools must be registered (see the README of the
lemmalog repo). If they are absent, tell the user the one-line
registration command and continue without memory — never block the task.
- Persistence across sessions exists if
LEMMALOG_MCP_PATH was set at
registration; lemmalog_save forces a snapshot. Snapshots carry rule
batches too — installed analyses survive restarts under their batch ids.
- No MCP access (sub-agents that don't inherit MCP connections, scripts,
cron)?
lemmalog-cli works on the SAME snapshot:
LEMMALOG_MCP_PATH=... lemmalog-cli observe --facts 'S --rel--> O',
plus query/retract/context/why/rules/dump. Mutations are visible to
the MCP server on its next load and vice versa — but the two hold
separate in-process copies, so don't write from both at once: hand
sub-agents the CLI and keep the parent on it too, or have the parent
only read while a sub-agent writes.
The discipline
- Assert as you verify. The moment you confirm something — a call
edge, a config value, a decision, a step completed — assert it:
S --rel[conf]--> O. Tag read-and-verified facts [1.0] and inferences
[0.4]–[0.7]. Do not omit the tag on anything you expect to derive over:
the default is 0.9 and confidence is a product down the proof chain, so four
hops of "verified" facts land at 0.66 and a deep closure decays to noise.
Anchor evidence with located(Entity, "file:line") (or any stable
reference) so provenance survives derivation — source references are valid
entity tokens as long as they contain no spaces. Never assert what you
haven't checked.
- Install rules when a pattern repeats. If you ask the same shape of
question twice, write the Datalog for it: transitive closures, guard
tracking, status rollups,
count/min/max/sum aggregates. Rules
are experiments: one named batch per analysis idea, validated on
install (rejections are spec feedback), backfilled against everything
already asserted, lemmalog_uninstall when the idea dies.
- Query before re-reasoning. Multi-hop, transitive, or
not-X-reachable questions go through rules and
lemmalog_query —
never mental closure, and never re-deriving what a prior agent derived.
For grounded answering, lemmalog_context retrieves the question-relevant
facts plus their verbatim source episodes under a token budget, with an
attribution contrast (which subjects hold facts on the topic — a
question-mentioned party with zero topic facts is a false-premise
signal) and, for current-state questions, the latest value per slot
with supersessions as history — use it instead of lemmalog_dump when
preparing answers; selection beats dumping.
lemmalog_why before trusting any derived fact. The proof tree
shows which asserted edges carry it; a chain is only as good as its
lowest-confidence edge. Re-verify the weakest edges against their
located anchors.
- Hypotheses have lifecycles.
H --hypothesis--> claim,
H --status--> proposed|supported|refuted|validated (supersedes),
H --evidence--> ref (accumulates).
Test counterfactuals with lemmalog_what_if — temporary facts,
answered goal, store untouched.
- Correct by retracting. When you learn an asserted fact was
wrong — not changed, wrong —
lemmalog_retract it: the response
lists every derived conclusion that died with it, so the repair is
visible, not silent. (A value that merely changed is re-asserted
under the same relation; see "State that changes".) After a context
reset or another agent's turn, lemmalog_changes with your last
epoch resyncs you without re-reading the store.
- Reconcile vocabulary, don't enforce it. Name things naturally;
when two names mean one thing,
local --alias_of[conf]--> canonical
via lemmalog_canonicalize. Conflicts surface as alias_conflict
facts; they never silently merge.
- Decide from queries. Derive candidate views — unexplored items,
blocked-by-what, what-needs-attention — and choose among them. The
queries propose; you dispose (including off-list when judgment says
so). Then assert the decision so state stays complete.
- Report from the engine. Final deliverables render from queries and
why trees, not from memory. A conclusion's confidence is the product
of its edges (the engine multiplies down the proof chain) — deep
derivations need high-confidence inputs to stay believable.
Schema conventions
The only shared vocabulary (everything else: invent precisely, and assert
describes(Relation, "one-line meaning") so others discover it):
All asserted through the line protocol (the predicate forms below are
descriptions, not assertable syntax):
kernel_func --located--> vm/vm_map.c:3052 % evidence anchor (multi-valued)
works_at --describes--> person is employed at % self-documenting schema
hyp_1 --hypothesis--> claim in plain words % lifecycle-tracked claim
hyp_1 --status--> proposed % supersedes on change:
% proposed|supported|refuted|validated
hyp_1 --evidence--> vm_map.c:3052 % multi-valued: accumulates
decision_7 --decision--> chose scope X because Y
Evidence objects take a bare source reference (space-free) or a
punctuation-free phrase; spaces plus punctuation read as leaked prose
and are dropped. Symmetric quotes around subjects/objects are stripped
("mean field" lands as mean field) — multi-word values are fine up
to 8 words; compress longer prose into a short name or split it.
State that changes
Values, quantities, and sets evolve — assert them so the engine can
maintain them (these conventions are what the update policy and the
aggregates need):
- Update by re-asserting the same relation. When a value changes,
assert the new value under the SAME relation name: the policy
supersedes the old fact automatically. Never invent a synonym
relation for the new value (
uses → switched_to) — that leaves
both values open, and every current-state query gets flaky. If you
need the history, the superseded fact is still queryable by its
validity interval.
- Bare numbers are integers.
launch --monthly_cost--> 120 (never
$120 or 120 dollars) — digit-only objects feed sum/count
aggregates and </>= comparisons. Mixed forms are opaque symbols.
- Bare dates order correctly.
moved_on with YYYY-MM-DD (or
YYYY-MM) objects; derive orderings with a rule
(earlier(A, B) :- on(A, D1), on(B, D2), D1 < D2) rather than
judging from prose.
- Evolving sets: one fact per item, plus lifecycle verbs. Track a
watchlist/checklist as
added(X) per item and watched(X)/done(X)
when consumed; current membership is then a rule —
pending(X) :- added(X), !watched(X). — not something you recount.
- Conditional preferences stay conditional.
prefers_when(user, lively, with_friends) — never assert the
condition itself as a fact unless the source says it holds now.
Grammar
- Bare capitalized words are variables — quote entity names:
reports_to("Alice", Y), never reports_to(Alice, Y).
- Fact line protocol:
S --rel[conf]--> O, one per line.
- An asserted fact is
current(S, rel, O), not rel(S, O). Rule bodies
match the triple: reaches(X, Y) :- current(X, depends_on, Y). Writing
depends_on(X, Y) instead installs cleanly, reports a backfill count, and
then derives nothing — the failure is silent, so check a new rule with one
lemmalog_query before building on it.
- Rule syntax:
head(X, Y) :- atom(X, Y), X \= Z. with !atom negation,
now(T), comparisons, arithmetic; aggregates only in heads.
- Time is bitemporal, and the two clocks are separate.
ts on
lemmalog_observe is the valid-from time of the facts in that call;
now(T) in a rule is the reader's present, synced to the wall clock on
every read. So backdating a batch is safe — it dates those facts without
hiding anything asserted after them. Omit ts unless the facts really
are about the past, and give one call one coherent timestamp rather than
mixing eras in a single batch.
- Errors are actionable: every
isError result carries the offending
input, the reason, and a hint — fix and resend; lemmalog_observe
reports dropped lines with reasons, so a zero-add result is loud, not
silent.
Anti-patterns
- Guesses as untagged facts (tag low confidence or don't assert).
- Batching assertions to the end (assert as you verify).
- Encoding your judgment as rules (queries inform; you decide).
- Trusting derived facts without
why.
- Re-deriving in context what the engine already closes.
- Letting two names for one thing drift (alias them).
- Renaming a relation when its value changes (supersede, don't fork).
- Numbers or dates buried in prose objects ("about $50", "last March")
— bare values are what the engine can aggregate and order.
Boundary
Stays in your head: in-flight reading, semantic judgment.
Must land in the engine: conclusions, state changes, decisions — before
you move on — and dead ends most of all: a searched-and-ruled-out
avenue is the most valuable thing a future agent can inherit (X --dead_end--> why it failed, where confirmed). Assert them in bulk —
one observe call, one line each; fifty at once is fine.
Scope honesty: for a short task that fits one context window, working
memory in your head is cheaper — lemmalog pays when state must outlive
a window, span agents, or survive a restart. A single-session audit
with four items to track is overhead; a multi-day investigation or a
swarm reading each other's dead-ends is the payoff case.
1---2name: lemmalog3description: Externalize working memory and logical state into the lemmalog Datalog engine (MCP). Use for ANY multi-step task where state should outlive one context window or span agents: long investigations, debugging sessions, audits, multi-agent searches, systematic explorations, planning with many interdependent constraints, anything needing provenance for its conclusions. Trigger when lemmalog_ MCP tools are available and the task involves accumulating verified facts, tracking hypotheses or status over time, or repeatedly re-deriving the same relationships.4---56# Lemmalog — external working memory78`lemmalog` is a Datalog engine exposed as MCP tools. It is your working9memory: assertions with provenance and confidence, derived consequences10computed by rules, hypotheses with lifecycles — persistent across agents11and context resets.1213**The division of labor:** the engine owns state and consequence; you own14perception and choice; every choice's outcome returns to the engine.15A claim that isn't in the engine doesn't exist — nothing durable lives in16your context.1718## Setup1920- The `lemmalog_*` tools must be registered (see the README of the21 lemmalog repo). If they are absent, tell the user the one-line22 registration command and continue without memory — never block the task.23- Persistence across sessions exists if `LEMMALOG_MCP_PATH` was set at24 registration; `lemmalog_save` forces a snapshot. Snapshots carry rule25 batches too — installed analyses survive restarts under their batch ids.26- No MCP access (sub-agents that don't inherit MCP connections, scripts,27 cron)? `lemmalog-cli` works on the SAME snapshot:28 `LEMMALOG_MCP_PATH=... lemmalog-cli observe --facts 'S --rel--> O'`,29 plus query/retract/context/why/rules/dump. Mutations are visible to30 the MCP server on its next load and vice versa — but the two hold31 separate in-process copies, so don't write from both at once: hand32 sub-agents the CLI and keep the parent on it too, or have the parent33 only read while a sub-agent writes.3435## The discipline36371. **Assert as you verify.** The moment you confirm something — a call38 edge, a config value, a decision, a step completed — assert it:39 `S --rel[conf]--> O`. Tag read-and-verified facts `[1.0]` and inferences40 `[0.4]`–`[0.7]`. Do not omit the tag on anything you expect to derive over:41 the default is 0.9 and confidence is a product down the proof chain, so four42 hops of "verified" facts land at 0.66 and a deep closure decays to noise.43 Anchor evidence with `located(Entity, "file:line")` (or any stable44 reference) so provenance survives derivation — source references are valid45 entity tokens as long as they contain no spaces. Never assert what you46 haven't checked.472. **Install rules when a pattern repeats.** If you ask the same shape of48 question twice, write the Datalog for it: transitive closures, guard49 tracking, status rollups, `count`/`min`/`max`/`sum` aggregates. Rules50 are experiments: one named batch per analysis idea, validated on51 install (rejections are spec feedback), backfilled against everything52 already asserted, `lemmalog_uninstall` when the idea dies.533. **Query before re-reasoning.** Multi-hop, transitive, or54 not-X-reachable questions go through rules and `lemmalog_query` —55 never mental closure, and never re-deriving what a prior agent derived.56 For grounded answering, `lemmalog_context` retrieves the question-relevant57 facts plus their verbatim source episodes under a token budget, with an58 attribution contrast (which subjects hold facts on the topic — a59 question-mentioned party with zero topic facts is a false-premise60 signal) and, for current-state questions, the latest value per slot61 with supersessions as history — use it instead of `lemmalog_dump` when62 preparing answers; selection beats dumping.634. **`lemmalog_why` before trusting any derived fact.** The proof tree64 shows which asserted edges carry it; a chain is only as good as its65 lowest-confidence edge. Re-verify the weakest edges against their66 `located` anchors.675. **Hypotheses have lifecycles.** `H --hypothesis--> claim`,68 `H --status--> proposed|supported|refuted|validated` (supersedes),69 `H --evidence--> ref` (accumulates).70 Test counterfactuals with `lemmalog_what_if` — temporary facts,71 answered goal, store untouched.726. **Correct by retracting.** When you learn an asserted fact was73 wrong — not changed, wrong — `lemmalog_retract` it: the response74 lists every derived conclusion that died with it, so the repair is75 visible, not silent. (A value that merely changed is re-asserted76 under the same relation; see "State that changes".) After a context77 reset or another agent's turn, `lemmalog_changes` with your last78 epoch resyncs you without re-reading the store.797. **Reconcile vocabulary, don't enforce it.** Name things naturally;80 when two names mean one thing, `local --alias_of[conf]--> canonical`81 via `lemmalog_canonicalize`. Conflicts surface as `alias_conflict`82 facts; they never silently merge.838. **Decide from queries.** Derive candidate views — unexplored items,84 blocked-by-what, what-needs-attention — and choose among them. The85 queries propose; you dispose (including off-list when judgment says86 so). Then assert the decision so state stays complete.879. **Report from the engine.** Final deliverables render from queries and88 `why` trees, not from memory. A conclusion's confidence is the product89 of its edges (the engine multiplies down the proof chain) — deep90 derivations need high-confidence inputs to stay believable.9192## Schema conventions9394The only shared vocabulary (everything else: invent precisely, and assert95`describes(Relation, "one-line meaning")` so others discover it):9697All asserted through the line protocol (the predicate forms below are98descriptions, not assertable syntax):99100```text101kernel_func --located--> vm/vm_map.c:3052 % evidence anchor (multi-valued)102works_at --describes--> person is employed at % self-documenting schema103hyp_1 --hypothesis--> claim in plain words % lifecycle-tracked claim104hyp_1 --status--> proposed % supersedes on change:105 % proposed|supported|refuted|validated106hyp_1 --evidence--> vm_map.c:3052 % multi-valued: accumulates107decision_7 --decision--> chose scope X because Y108```109110Evidence objects take a bare source reference (space-free) or a111punctuation-free phrase; spaces plus punctuation read as leaked prose112and are dropped. Symmetric quotes around subjects/objects are stripped113(`"mean field"` lands as `mean field`) — multi-word values are fine up114to 8 words; compress longer prose into a short name or split it.115116## State that changes117118Values, quantities, and sets evolve — assert them so the engine can119maintain them (these conventions are what the update policy and the120aggregates need):121122- **Update by re-asserting the same relation.** When a value changes,123 assert the new value under the SAME relation name: the policy124 supersedes the old fact automatically. Never invent a synonym125 relation for the new value (`uses` → `switched_to`) — that leaves126 both values open, and every current-state query gets flaky. If you127 need the history, the superseded fact is still queryable by its128 validity interval.129- **Bare numbers are integers.** `launch --monthly_cost--> 120` (never130 `$120` or `120 dollars`) — digit-only objects feed `sum`/`count`131 aggregates and `<`/`>=` comparisons. Mixed forms are opaque symbols.132- **Bare dates order correctly.** `moved_on` with `YYYY-MM-DD` (or133 `YYYY-MM`) objects; derive orderings with a rule134 (`earlier(A, B) :- on(A, D1), on(B, D2), D1 < D2`) rather than135 judging from prose.136- **Evolving sets: one fact per item, plus lifecycle verbs.** Track a137 watchlist/checklist as `added(X)` per item and `watched(X)`/`done(X)`138 when consumed; current membership is then a rule —139 `pending(X) :- added(X), !watched(X).` — not something you recount.140- **Conditional preferences stay conditional.**141 `prefers_when(user, lively, with_friends)` — never assert the142 condition itself as a fact unless the source says it holds now.143144## Grammar145146- Bare capitalized words are **variables** — quote entity names:147 `reports_to("Alice", Y)`, never `reports_to(Alice, Y)`.148- Fact line protocol: `S --rel[conf]--> O`, one per line.149- **An asserted fact is `current(S, rel, O)`, not `rel(S, O)`.** Rule bodies150 match the triple: `reaches(X, Y) :- current(X, depends_on, Y).` Writing151 `depends_on(X, Y)` instead installs cleanly, reports a backfill count, and152 then derives nothing — the failure is silent, so check a new rule with one153 `lemmalog_query` before building on it.154- Rule syntax: `head(X, Y) :- atom(X, Y), X \= Z.` with `!atom` negation,155 `now(T)`, comparisons, arithmetic; aggregates only in heads.156- Time is bitemporal, and the two clocks are separate. `ts` on157 `lemmalog_observe` is the **valid-from** time of the facts in that call;158 `now(T)` in a rule is the reader's present, synced to the wall clock on159 every read. So backdating a batch is safe — it dates those facts without160 hiding anything asserted after them. Omit `ts` unless the facts really161 are about the past, and give one call one coherent timestamp rather than162 mixing eras in a single batch.163- Errors are actionable: every `isError` result carries the offending164 input, the reason, and a hint — fix and resend; `lemmalog_observe`165 reports dropped lines with reasons, so a zero-add result is loud, not166 silent.167168## Anti-patterns169170- Guesses as untagged facts (tag low confidence or don't assert).171- Batching assertions to the end (assert as you verify).172- Encoding your judgment as rules (queries inform; you decide).173- Trusting derived facts without `why`.174- Re-deriving in context what the engine already closes.175- Letting two names for one thing drift (alias them).176- Renaming a relation when its value changes (supersede, don't fork).177- Numbers or dates buried in prose objects ("about $50", "last March")178 — bare values are what the engine can aggregate and order.179180## Boundary181182Stays in your head: in-flight reading, semantic judgment.183Must land in the engine: conclusions, state changes, decisions — before184you move on — and dead ends most of all: a searched-and-ruled-out185avenue is the most valuable thing a future agent can inherit (`X186--dead_end--> why it failed, where confirmed`). Assert them in bulk —187one observe call, one line each; fifty at once is fine.188189Scope honesty: for a short task that fits one context window, working190memory in your head is cheaper — lemmalog pays when state must outlive191a window, span agents, or survive a restart. A single-session audit192with four items to track is overhead; a multi-day investigation or a193swarm reading each other's dead-ends is the payoff case.