Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

memory

The conversation’s memory, for the model: one tool with verbs, as the person’s chat commands have them. recall restores, for the current turn, earlier material that the model’s context holds only as a reference, a marker, a summary, or a fact. note records a fact the model wants kept, as the model’s, below the person’s and a tool’s. task proposes the conversation’s task and its objective, never over the person’s or a caller’s. It reads only the conversation’s own record and the audit log it refers to, writes only the conversation’s facts, runs no command, and needs no approval. This is phase 4c of the layered-context proposal (“Recall”, decisions D2, D8, and D12), widened from a recall tool to memory on 2026-09-30.

It is not the Mac’s memory: system_info’s memory topic reports RAM. The tool’s description says so, and the eval checks that questions about RAM still go to system_info (below).

Off by default. Context checkpoint 2 scored fewer answers with memory than without it on every model it measured (the on-device model, granite4.1:8b, and gemma4:12b), so since ADR 0057 a conversation gets it only when you turn it on or name it. Turn it on for every conversation given every tool:

wisp config set context.memory true

or /config set context.memory on in chat, or "context": { "memory": true } in ~/.wisp/config.json; it applies from the next conversation. With it on, everything on this page behaves as it did before ADR 0057.

When a conversation has it

Conversationmemory
Every tool (no --tool, MCP respond without tools, chat), context.memory off (the default)No
Every tool, context.memory: trueYes, as a built-in tool
A named list (--tool read_file, MCP tools: ["run_command"])Only when the list names memory, whatever context.memory says
No tools (--no-tools, tools: [])No
tools.disabled: ["memory"] in ~/.wisp/config.jsonNo, anywhere: the tool is not registered, so a list that names it is refused

An explicit list is exactly that list, so the MCP git thread of AGENTS.md (tools: ["run_command"]) stays run_command alone, and --tool read_file --tool memory has memory with the setting off. A conversation without memory is not given the system prompt’s rule about it, and its references say call it again to see it for a tool that only reads, and its output is not repeated; do not run it again to see it for a command or an edit instead of naming a recall; the facts, the running summary, and the references themselves are the same. You can still see any earlier output with /show and /inspect context, which cost the model nothing. With memory, the tool is there from the first turn: a note needs nothing stored, and a recall before anything is stored says the first turn is all in view.

Arguments

NameTypeRequiredMeaning
requeststringyesA verb and its object: recall entry 7, recall turn 3, recall task, recall summary, recall fact codename, recall output 3f9a2c1b, summarise entry 7, condense entry 7: why did it fail, note entity release codename = BLUE HERON, or task fix the CI build; objective: swift test passes. A recall takes a filter (matching PATTERN, lines A-B, head N, tail N) and a later page adds from line N.

One text argument, because the references and markers the model reads already spell a recall (to see it: memory "recall entry 7", (showed the person the read_file output, entry 7), and the entries named in facts’ sources, such as from tool read_file, turn 2, entry 4), and a small model copies a phrase more reliably than it fills optional fields. Every tool’s schema is in every request (decision D4 measured them), so the schema has one field. The first word is the verb; remember is taken as note, and a request with no verb is a recall, so entry 7 alone works. task followed by text proposes the task (below); task alone recalls it.

Tool definition as the model sees it (description, then the argument’s guide):

This conversation's memory, not the Mac's RAM (that is system_info): recall earlier material in full, or note a fact to keep.
request: "recall entry 7", "recall turn 3", "recall task", "recall fact codename", or "note entity release codename = BLUE HERON".

It costs 110 tokens in every request’s instructions, measured with tokenCount(for:) on the on-device model on 2026-10-01 for this text (recall alone cost 103). The task verb is not among the guide’s examples: one added on 2026-10-01 (“task fix the CI build; objective: swift test passes”) took the definition to 123 tokens in every conversation with memory, and was dropped on 2026-10-03; the verb works as before (below).

recall

After recallRestores
entry 7, 7, output of entry 7Store entry 7: a tool output, a reply, a prompt, or tool calls. A tool output the model was given only in part is restored whole from the output store, with the stored output’s line numbers (the header says the whole output from the output store).
output 3f9a2c1bThe whole stored output with that id, the one a held-back note names (ADR 0050): reachable in the turn that produced it, before its entry exists.
… matching PATTERN, … lines 100-200, … head 50, … tail 50The same, filtered first: the lines matching a regular expression (case-insensitive; a literal when it does not compile), a range, the first or the last lines; each line carries its number in the whole, the header says how many of how many were kept, and the pages continue the filter.
turn 3Every stored entry of turn 3, in order, each under a line naming it.
task, the taskThe task’s versions, oldest first, then the prompt the conversation began with, in full.
summaryThe running summary’s versions, newest first, with the turns each added.
c12, fact c12The versions of what fact c12 is about.
fact tests, codename, CIThe versions of the facts whose subject, name, or value the words match, at most four subjects.
… from line 60The same, from line 60.

A header saying what was found and where its content was read from, the lines of one page, then a last line that names the next page or says it ended:

entry 4: read_file output, turn 2 at 14:05:12, from the audit log
arguments: {"path": "/work/harbour/docs/overview.md"}
1	# harbour sync: design overview
…
77	leaves the previous manifest intact and the next run repeats only the unfinished work.
[end of file]
[end of what recall found]

A page that is not the last ends [more: memory "recall entry 4 from line 60"]. A fact’s history names each version’s id, source, time, value, state, and the entries it came from:

fact "ci": 1 subject
tests ci: 2 versions, oldest first:
- c3 [tool run_command, turn 2, entry 9] at 11:02:10: failed (exit status 1); superseded by c9
- c9 [tool run_command, turn 11, entry 30] at 11:07:40: passed; current
memory "recall entry N" shows what a fact came from.
[end of what recall found]

When nothing matches, one line starting memory: says why and what there is: the range of entries or turns stored, or the subjects the conversation knows. A turn’s own entries are not stored until it ends; the model has them whole until then.

Where the content comes from. The audit log is the one verbatim record (decision D8), and the store refers into it: each entry names the audit events that recorded it (tool.result for an output, prompt, response, tool.call). A recall reads an entry’s content from that event. The store’s own copy of the entry, which it keeps in memory (and in the .store sidecar for dropped entries) so that composing never reads the audit files, is the fallback for an entry the audit does not hold: text the model wrote before a tool call, which no event records on its own; an entry whose event has rotated out of the files kept; or a conversation with audit.enabled: false. The header says which (from the audit log, from the conversation's store).

After its turn. What a recall returns is an ordinary tool output: whole in its own turn, and from the next turn on a reference like any other ([output of entry 40 not repeated: memory at …; to see it: memory "recall entry 40"]), so restored material ages out and is never carried in full again.

summarise

summarise entry 7
summarise output 3f9a2c1b overview

The notable lines of a stored entry or output, or with overview an overview, by exact readers and no model (ADR 0050). The notable summary leads with what matters given how the output ended: for a build or test output in a format triage reads, the errors grouped by file or test, the first of each with its line and the lines after it and the later ones in the same place counted under it, the warnings tallied, and the test failures; for a log, its severities and the rare or severe messages; for JSON, its shape; else the lines that mention an error, else the first and last lines. Each point cites its line (L154), the stored output’s numbering, so a recall from that line lands on it. The overview gives the size, how it ended (a command’s exit status, else the last line), the kinds of line counted, the lines that look like headings with their numbers, and the first and last lines. Both say what no reader explained, so the model knows what condense could still answer, and both fit the budget.

notable lines (exact readers, no model): 2,340 lines, 187 KiB; exit status 1 (failed)
2 errors in 1 place; the first of each, root causes first:
L154 error at Sources/B.swift:42:9: cannot find 'frob' in scope (+1 more in Sources/B.swift)
  L155:     frob(1)
  L156:     ^~~~
1 warning (Sources/A.swift ×1)
1 failure-looking line no reader explained, such as: error: fatalError: 1 of 3 targets failed

condense

condense entry 7: why did the link step fail

Answers the question after the colon from the whole of a stored entry or output, by a chunked pass of the conversation’s own model: the output is cut into chunks within the budget (at most 48; the result says when the output had more), each chunk is asked the question in a fresh tool-less session of the same model and told to answer nothing here when it has nothing, the answers that say something are joined in order, and when they do not fit the budget the model is asked once more to combine them. The result names the question, the lines and chunks read, and that the model wrote it; a chunk that fails is counted. Each chunk is one of wisp’s own side calls (ADR 0053, amended 2026-10-11): a model that can think is asked not to, whatever the conversation or the model’s settings choose, and its thinking is not recorded as the turn’s, as the fact distiller, the running summary, and triage are run. It is the one verb that costs model calls, in proportion to the output’s size, and only when asked. Without a model attached (an agent made directly, tests) it says so and points at summarise and the filters. Paging alone is not enough for a long output: a model reading page after page can lose the earlier pages to condensing, so this asks about all of it at once.

note

note SUBJECT NAME = VALUE

SUBJECT is a subject kind the model may note, NAME what the fact is about, and VALUE its value, as the person’s /fact SUBJECT [NAME] = VALUE is written. The forms a small model writes are read too: SUBJECT: NAME = VALUE, and SUBJECT NAME: VALUE with no =. Quotes around the value are dropped.

RuleWhat happens
Sourcemodel, method noted: the lowest precedence, so a person’s or a tool’s fact on the same subject and name wins, and a disagreement is shown as a conflict (D2). A newer note or distilled fact on the same identity is a new version of the model’s.
Subject kindsThose the distiller may use: task, decision, preference, entity, tests, workdir, branch, and any the config adds with distil not false. file, service, and machine are the tools’ to record. Another subject is refused with the list.
NameRequired, except for kinds with one fact per conversation (task, workdir, branch).
Temporal classThe kind’s. A note of a permanent kind (decision, preference, entity) is a proposal held by the conversation until the person keeps it with /fact ID permanent: only the person admits a permanent fact (D2).
WhenKept when the call is made and recorded as a fact when the turn ends, with the turn’s number; it reaches the model’s facts from the next request. The person sees it in the note chat prints after the turn, as for any new fact.
Bound12 notes a turn, as one distillation keeps at most 12 facts; the value is cut to 200 characters.
Facts offWith facts.enabled: false, a note is refused; recall still works.

The result says what was kept, or why not, in a form the model can act on:

noted: entity release codename = BLUE HERON (a proposal until the person keeps it)
noted: tests ci = green
error: no subject weather to note; use one of task, decision, preference, entity, tests, workdir, branch, such as note entity release codename = BLUE HERON
error: say which entity it is before the =, such as note entity release codename = BLUE HERON
error: write note SUBJECT NAME = VALUE, such as note entity release codename = BLUE HERON
error: at most 12 notes a turn; the rest were not kept

In facts’ lines a noted fact’s source reads model, noted, turn 4.

task

task THE TASK; objective: WHAT DONE LOOKS LIKE

Proposes the conversation’s task as the model’s fact (subject task, source model, method noted), with its objective after objective: (also . Objective: or , objective:), kept as one value, THE TASK; objective: WHAT DONE LOOKS LIKE. It is recorded when the turn ends and shown next to each request from the next one (decision D6). The task the person set with /task, or an MCP caller with respond’s task, is never replaced: the request is refused and the result names the task as it stands. task alone, and recall task, recall the task. A task counts towards the turn’s 12 notes, and is cut to 300 characters.

noted the task: add a --dry-run flag to harbour sync; objective: sync prints the plan and copies nothing
error: the person set the task, and it stays: fix the CI build
error: write task THE TASK; objective: WHAT DONE LOOKS LIKE

With the per-request assessment on, the model’s task is also inferred before each request in chat (context-management.md, “The assessment per request”); a task the model proposed either way is the model’s, method noted here and inferred there.

Audit

Each call is a tool.call and tool.result like any tool’s, and a context.memory event with the request and its action. A recall names what was restored: the target (output for a stored output, with its id as output), the entries, facts, and summary versions, the audit events read, the filter applied, and whether the content came from the audit log, the store, or both. A summary names its target and which summary (notable, overview); a condense names its target and the chunks the model read, and a failure when it had no model, no question, or chunks that failed. A note names the subject, name, value, and class kept, or the failure; a task names the value kept or the failure (pinned when the person or a caller set it); each note or task kept is also a fact.recorded (method noted) when its turn ends (logging.md).

Limits

LimitDefaultConfigure
Bytes of lines per recall pageThe tool-output budget (an eighth of the model’s window, 4 to 64 KiB), as read_file’s page, plus the header and the last lineFollows the model’s window
Line lengthA line longer than a page is cut to itsame
Chunks one condense reads48, each within the budgetFixed (MemoryTool.condenseChunks)
How long stored outputs stay readable30 days after the session’s last use, then swept at a later session’s startcontext.outputDays (0 keeps them)
Subjects per fact query4Fixed (Recall.factGroups)
Notes per turn12Fixed (Memory.notesPerTurn)
A note’s value200 charactersFixed (Memory.valueCharacters)
ScopeThis conversation’s record and facts, and the session’s and shared stores’ facts it seesnot configurable

Evaluation

Measured in the context eval’s recalling and noting scenarios (ContextEvalTests, MemoryStrategy); the figures are in the proposal, “Memory, 2026-09-30”. Context checkpoint 2 compared the whole stack with and without memory on its sustained scenario, three runs on each of three models, and found it no help: 4.3 against 5.3 of 10 on the on-device model, 7.3 against 8.0 on granite4.1:8b, and 8.0 against 9.3 on gemma4:12b (ADR 0057). SystemInfoEvalTests offers memory beside system_info and run_command and counts the turns that call it on a question about the Mac: on 2026-09-30, on the on-device model, none of 16 did.

Implementation

MemorySource and Memory (the verb, the note’s form and rules) in harness/Sources/WispCore/Session/Memory.swift; Recall (what a recall names, gathering and paging the material, pure) in Session/Recall.swift; MemoryTool in harness/Sources/WispCore/Tools/MemoryTool.swift; and AuditLog.event(_:) with the AuditReader sinks in Audit/AuditLog.swift. The agent publishes its store, facts, subject kinds, and turn to the tool before every request and records the turn’s notes when it ends (Agent.memory). Tested in MemoryTests, RecallTests, AuditReadBackTests, and MemoryEvalTests.