two stores
What is worth a page, and what is only worth remembering
The wiki is edited, linked and committed. The memory is written once and searched later. Keeping them separate is the whole point — and the boundary is drawn in prose, not in code.
Most systems that give a model long-term state pick one substrate. This one runs two, with different write disciplines, different retrieval mechanics and different tolerances for noise. A fact about a person belongs in a page that a human will read and correct. A note that a service was restarted on Tuesday belongs in a store that nobody curates.
The rule is stated three times in the source — once in the tool description, once in the instruction block sent to every client, and once more in a patch script that inserted it there. Its repetition is the enforcement mechanism.
Do NOT save transient/one-off details (readings, countdowns, runtime status) — those go to memory_retain only.
src/server.ts:96 · src/tools/wiki_append.ts:98
Three verbs, one service
The memory tools are a thin, opinionated client over an HTTP API. The base address is configurable, defaults to a loopback service, and no credential is attached — the trust boundary is the loopback interface. Every operation is preceded by a bank check.
| Tool | Request | Notable arguments | Returns |
|---|---|---|---|
memory_recall |
POST …/banks/<bank>/memories/recall |
query; limit 1–50, default 10; optional tags; bank default personal |
The matched facts, most relevant first |
memory_retain |
POST …/banks/<bank>/memories |
text ≥ 3 characters; optional tags for later filtering |
The service's raw acknowledgement |
memory_reflect |
POST …/banks/<bank>/reflect |
query; budget ∈ low / medium / high, default low |
A synthesised answer rather than raw matches |
The recall / reflect distinction is stated plainly in the source: “recall returns raw matched
facts, reflect returns a synthesized narrative” (src/tools/memory.ts:145). The
first is evidence; the second is an opinion about the evidence, and it costs a reasoning budget
to produce.
Token discipline, applied twice
A recall request is a negotiation about size. The client computes a token ceiling from the requested result count, asks the service to omit three expensive sections, and then — because the service may include them anyway — deletes them from the response before handing it to the model.
include handling, and
says so by implementing both.
Version pins written as comments
Three comments in memory.ts record exactly which API shape the client was written
against. They read as scar tissue from a migration, and they are the only version documentation
in the repository:
// Hindsight 0.4.x: banks are created/updated via PUT /banks/{bank_id}, not POST /banks // Hindsight 0.4.x RetainRequest: { items: [{ content, tags? }], async? } // Hindsight 0.4.x ReflectRequest: { query, budget?, max_tokens?, include? }
The retain client sends items but never async; the reflect client
sends query and budget but neither max_tokens nor
include. The comments describe the full contract, the code uses the part it
needs.
Bank creation is a best-effort side effect
ensureBank runs before every recall, retain and reflect. It lists banks, returns
early if the target is present, and otherwise issues a PUT to create it. The entire
function is wrapped in a try whose catch block contains only the comment
/* best-effort */. If the memory service is down, the failure surfaces from the real
operation that follows, with a message the model can act on — not from a setup step it did not
ask for.
Banks as namespaces
The default bank is configurable and named personal. Every tool takes an optional
bank argument, so an agent can partition memory by context without any server-side
configuration — the partition is created on first write. Separate work memory is reached
through the federation layer instead, as prefixed tools from a downstream server; see
the tool surface.
The boundary is advisory — and that is the interesting part
Nothing stops an agent from calling memory_retain with a durable biographical fact,
or wiki_append with a runtime status line. The schemas are nearly identical: both
take a string and optional tags or links. What separates them is a rule written in English, in
three places, aimed at a reader capable of following it.
This is a real architectural position, and it has a cost. The wiki's guard rails are executable — similarity routing, de-duplication, a lock — because the wiki is the store where a bad write is expensive to undo. Memory has no guard rails because a bad write there is merely noise, and noise is what reranking is for. The system spends its correctness budget where mistakes are permanent.
Avoid storing one-off transactional details. Focus on durable facts, preferences, decisions.
src/tools/memory.ts:106 — memory_retain description
Note that this instruction sits inside memory_retain itself, the store with no
curation. Even the loose side of the split asks the model to exercise judgement — the difference
is that here, nothing checks.
No memory contents, bank names beyond the documented default, service addresses or ports appear on this site.