ContributingDraft
Vyre Memory everywhere
The user asked for IQ to be "really smart and designed really well and implemented across every surface". This page is the map. It lists each surface, what IQ does there today, what it should do, and the gap. The gaps are ranked by value for cost at the end, with an owner for each.
One rule holds on every surface: one ask path. A question goes to memory.ask (ADR 0034) and
comes back as an answer with its sources, or "not sure yet" with what memory does know. No surface
writes its own question rules, its own prompt or its own model choice. The hard rules come with it:
- Grounded or abstain. An answer names the sessions or facts it stands on. Code checks every name, number and path against what it cites. A surface never shows an answer without its sources one tap away.
- Source trust. Personal facts come only from the user's own words about their life, never from Claude's turns, tool output, pasted text or a Vyre dev session (core/memory/personal/trust.js).
- Determinism. The same question over the same memory gives the same answer, confidence and sources. Replies are kept by the prompt's hash.
- The graph is first-class. People, projects and decisions stay visible and editable wherever IQ shows an answer: a source links to the turn, and a person links to the Memory view.
- Caps in plan terms. Questions have their own daily share of the person's Claude plan (about
150 questions at the default share). At the cap
memory.askreturnslimited: trueand amessagein plan terms, and every surface shows that message. No surface ever shows a dollar figure (cost_usdis internal): nothing is a charge, the reads run on the person's Claude plan.
The contract every surface uses
memory.ask { question, context?: { project?, thread? }, project_cwds?, stream?, id? }
-> { id?, answer, confidence, abstained, known[], sources[{ session, seq, name, quote, ts }],
via: fact|retrieval|null, latency_ms, cost_usd, limited?, message? }
events (stream: true):
memory.thinking { id, stage: understanding|searching|reading|checking }
memory.answered { id, abstained, limited }
A surface makes its own id (letters, digits, _, -), sends stream: true, and shows a thinking
state from the first memory.thinking event. The events never carry the question or the answer.
A personal fact answers in under 50 ms with no model. Otherwise the fast model reads 8 passages:
about 3.8 s at p50 through claude -p today (measured on the open world), and under 3 s once
sessions' warm quick session lands (work/sessions, not on main yet).
How it looks is app-design's: an "Answer" variant of the result card (docs/design/system/components/result-card), confirmed 28 Sep. It uses the result card's shell, with no header row and never a primary action. Sources are source chips (a 1px strong rule, no fill, a mono 12 meta line). Confidence is plain text in the meta line, never a colour. The abstain and limited states use the same shell and padding, never a red or amber treatment. Above the search hits there is a 12 px gap (space-3), full width. The shape every surface shares:
- The answer, one or two sentences, in the user's words for names and files.
- One line under it: how sure ("confidence 0.8") and where from ("from 2 sessions", or "from what you have said" for a personal fact).
- Up to three sources: the session's name, when, and the quoted words. A tap opens that turn
(
recall.threadatseq). "More" shows the rest. - Abstained: "Not sure yet." then "What memory does know:" and the
knownlines, if any. Never an empty card. - Limited: the
messageas it came, with its link to Settings.
The stream cutover (0.1.1)
The lead's decision (28 Sep, docs/design/interaction.md): memory.ask { stream: true } is the
default wherever it is called, from one shared cutover, coordinated here. No surface builds an
interim path.
- The contract is final as on work/memory-iq:
memory.ask { question, stream: true, id, context?, screen? }. The surface makes the id, shows a thinking state from the firstmemory.thinking { id, stage }, and draws the reply:{ id, answer, answer_id, confidence, abstained, known, sources: [{ session, seq, role, name, quote, ts }], via, limited?, message? }.memory.answered { id, abstained, limited }closes it. Events carry no question or answer. - Cutover: 30 Sep 2026, in the integrator's first 0.1.1 batch. memory-iq's 0.1.1 sha lands
first in that batch; the Capsule (capsule-pro), the Deck and PWA card (chat-core), and the phone's
Find card (mobile) land in the same batch, built against work/memory-iq until then. A surface
that meets an older vyred (no
idin the reply) draws the reply without the thinking stages. - Owners: capsule-pro (the Capsule), chat-core (Deck chat and PWA, paused: this spec), the Deck and mobile (Find cards, paused: this spec), app-design (the Answer card, interaction.md section 6). The correction gesture ("Wrong?") ships on each surface as it relaunches.
Surface by surface
The Capsule
- Today. The Capsule calls
memory.relevantandrecall.searchitself (local/capsule/native/Sources/Host/CapsuleModel.swift:729-762), then starts a lean session whose system prompt iscomposeIq(core/sessions/iq-prompt.js) over those facts. Said.swift keeps its own question rules and showsmemory.answer's sureness. Nothing checks the answer against its sources, session transcripts are only raw hits, and there is no "what memory does know". - Should. When the box decides the words are a question (cohesion item 5), the Capsule calls
memory.ask { question, stream: true, id, context: { project } }and draws the result card above. "Ask Claude instead" stays as the second action, for a question memory cannot answer. Said.swift and its rules go. - Gap. The largest one: the Capsule is where the user asks most, and it runs a second answer path. Owner: capsule-pro (active), with sessions for retiring the capsule purpose's IQ prompt. memory-iq's part (streaming, the id, the cap message) is built.
Chat
- Today. The per-prompt hook (harness/hooks/hook.js,
harness.enrich) adds up to five graph facts frommemory.relevantto each prompt, marked as memory with their source. The assistant can callmemory_askover MCP (every module tool is offered), but nothing told it when, so it said "I don't remember" about things memory holds. - Should.
memory.ask's description now tells the assistant to ask it before saying it cannot remember something from earlier sessions, and to name the session it cites (built).- about.md (core/about) gains one line: "For a question about your past work or life, call memory_ask and name the session it cites." Static, so it costs nothing per prompt.
- The hook, for the user's own interactive session only (not an agent, not a project-scoped
session), adds a personal fact when the prompt asks one (
memory.answer's rules, no model, under 50 ms): "what's my wife's name, and book us a table" arrives with "Your wife is Juno (from what you have said)". Never a model call per prompt. - When the assistant cites memory in its answer, the Deck's chat renders "(from <session>)" as a link to that turn.
- Gap. 1 is built. 2 and 3 are small and belong to the harness (cc-plugin). 4 belongs to chat.
A module caller (
module:harness) passes memory's personal check, so the hook must decide scope itself: it sendsagentorproject_cwdswhenever the session is not the user's own.
The Deck (Find, Memory view, Now)
- Today. Find (deck/views/find.js:595-597) shows
recall.searchhits andmemory.relevantfacts: search results, never an answer. The Memory view browses and edits the graph (memory.graph,memory.facts,memory.why). Now shows what memory learned today. The Ask view usesmemory.relevantfor hints while typing. - Should. Find shows an IQ card on top when the words are a question (they end in "?" or start with who, what, when, where, which, why, how, did, do, is, was), asked on Enter or after 700 ms without typing, never per key. The search hits stay below it, unchanged. The Memory view gets the same card from its search box, and each source links to the turn. Now needs nothing from IQ.
- Gap. Medium: one card component, one call, debounce. Owner: the Deck (app-design has the result card). Built once in chat-core so the PWA gets it too.
The phone (Find)
- Today. iOS FindView.swift:607-621 calls
recall.search, thenmemory.relevantwith amemory.factsfallback. Android Find.kt follows the same grammar. Search results only. - Should. The same IQ card as the Deck, same question test, same debounce, called over the
tailnet as the owner (
tailnet:<login>passes memory's personal check). Events may not reach the phone, so it shows a plain thinking state until the reply. - Gap. Medium, twice (iOS and Android). Owner: mobile.
The CLI (recall, memory, why)
- Today.
vyre memory askcalledmemory.answer: personal facts only, no sessions.vyre recallsearches turns.vyre whyshows where a fact came from. - Should.
vyre memory askis IQ: the answer, "confidence 0.8 · from 2 sessions", three sources (--sourcesfor all), "not sure yet" with what memory knows, the cap message, exit 1 when there is no answer, and--jsonfor scripts. - Gap. Built (this branch). A vyred without
memory.askstill answers from personal facts.
Suggest and predictive text
- Today. suggest (core/suggest) ranks agents, projects, threads, accounts and times per key.
memory.suggestexisted but nothing asked it. - Should. The people and things memory knows complete as the user types: "my wi" offers "wife" with "Juno" beside it, "Jun" offers "Juno". Personal names only on the user's own surfaces. IQ answers are never suggestions: they come on pause, through the card.
- Gap. Built: memory offers
memory.suggestto suggest at start and again on the newsuggest.readyevent, in the offer'sitemsshape. One line in core/suggest (the event), listed in memory-iq's changed contracts.
The assistant's own system prompt
- Today. about.md (core/about/index.js) holds up to 600 characters of
memory.profilelines (work, places, preferences) and the agents and projects, read at SessionStart. No personal people facts, and nothing says memory can be asked. - Should. The same, plus the one line about
memory_ask(see Chat). People facts stay out of about.md: they enter a prompt only when the prompt asks about them. - Gap. Small. Owner: cc-plugin (core/about).
Teammates' notes
- Today. A teammate's notes file is its own memory of record and is deliberately not fed into
memory (ADR 0031). Corrections go through
learn.add. - Should. Notes stay out of personal facts: they are an agent's words, and source trust says
so. A teammate asks project history with
memory_ask, scoped to its projects by the guard, so it never reads personal facts or another client's sessions. Its brief gains one line saying so. - Gap. Small. Owner: teammates.
Project graphs: a session that joins a project later
Cohesion writes the product spec. This is what IQ needs from it and what it costs.
- Today. Memory's rooms already follow projects:
project.created,project.changed,thread.pickedandthread.unpickedmark the rooms stale, and the next pass files a picked session's facts in that project's room (the ids come fromprojects.listpicks). Personal facts never move: they are the user's, not a project's. - Gap. IQ's retrieval scopes a project by its folders.
recall.searchhasproject_cwdsand no session filter, so a session picked into a project from outside its folders is in the project's graph but not in the passages IQ reads for that project, and a project-scoped agent cannot read it. - Should.
recall.searchtakessessions: string[]next toproject_cwds, and a turn matches if either holds.memory.retrieveandmemory.askscope a project by its folders plus its picked session ids. A session unpicked leaves at the next pass. An agent's grants follow the same union. - Built (398f6156).
recall.search { sessions }keeps those sessions as well as the folders' (keywords and meaning both); only modules and the person's surfaces may name sessions, so a model cannot widen its scope. memory.retrieve and memory.ask scope a project by its folders plus its picked threads. Cohesion's spec decides the user-facing words.
Host to server: the Mac's sessions build the box's graph
Federation owns the transport (its plan: "3. Session sync") and memory-iq the indexing. Agreed with federation on 28 Sep. ADR 0008 is amended (approved by the user: a per-device switch, off by default, in the settings hub, turned on by a person). e2e approved the direction with conditions, now in ADR 0008: the box names the machine from the verified peer and keeps its own copy of the switch; UUID file names, temp and rename, no symlinks, a quota; trust is the lower of the folder rule and mac-sync; the secret scrub runs at ingest; every derived row carries its machine so the delete is complete; unpairing deletes too. It is 0.2 work.
- Consent. One switch per Mac in the settings hub (ADR 0035), off by default, turned on only by
a person. Vyre's own folders,
<home>/quickand folders the person excludes never leave the Mac. Turning it off, unpairing, or replacing the device deletes nothing: what came from it is the person's (ADR 0008, amendment item 5). "Delete everything that came from <device>" is the person's own action: federation deletes<home>/synced/<machine>/and emitssync.deleted { machine }, and memory drops that machine's Recall rows and everything derived, and says how much went (memory.forgot);memory.deviceis the preview. - What moves. Raw session files (JSONL), encrypted in transit over the tailnet, into
<home>/synced/<machine>/in the Mac's own projects layout. The file's own folder (cwd) is kept as it was: source trust keys off it, never off the synced path. - Labels. A synced session is
source: "mac-sync"with its machine; the live federated read stayssource: "mac"(ADR 0021). When both exist for one session id, the catalogue shows the live one; IQ and the graph always read the resident copy. - Dedupe. One file per session id, replaced whole when it changes. A shrunk or compacted file is a rewrite: Recall sees fewer turns and memory reads that session again, as today.
- What is indexed where. Recall on the box adds the synced root to its transcript folders:
keywords and embeddings (box CPU), the graph (rules) and personal facts (the model reader) all
build there. When a Mac syncs, that Mac's own model reader turns off (it reads the hub setting),
so no turn is paid for twice; the Mac keeps its own index for offline search, and its surfaces ask
the box's
memory.ask, falling back to the Mac's own. - Load. Batches only while the Mac is idle, never more often than every 60 s.
- Cost. Transport and storage: text, tens of MB for a year of sessions. Embeddings and the graph: box CPU, no money. The model reader: a one-time backfill of about $1.50 to $2.60 in reported usage, then at most $0.25 a day, on the box's Claude login (never API dollars since ec097430). With no login on the box, reading waits and nothing else breaks.
- Size. Federation L (the long pole: upload service, consent, delete). memory-iq M (synced root in Recall, labels, delete by machine, reader location, a two-machine eval fixture).
Correct IQ where it appears
The user: "Make it so IQ can be updated where it appears, so you can update it and it remembers that for the future and improves." Built on work/memory-iq (95b2b891).
- One tool, extended.
memory.correct { answer: <answer_id>, action, object? }. Everymemory.askreply, a "not sure" too, carriesanswer_id(the same answer to the same question has the same id). The person's own surfaces (Deck, CLI, Capsule, local), or their phone or paired device with a person session (a passkey, ADR 0032), with no Touch ID (the no-nag rule); agents never. Graph corrections follow the same rule (e2e, 28 Sep).replacewithobject(the right answer, the person's words): the same question gets it at once (via: "corrected", source "your correction"), no model call. For a personal answer the text is also told to memory (memory.remember, highest trust), so "who is my wife" has it too.wrong: that answer is never given to that question again; IQ says "You said ... is wrong." and tries again when memory learns something new.forget: the personal facts behind it stop counting, and the turns it cited never ground an answer again.memory.uncorrect { fix }undoes any of them, told note included.
- It remembers. A correction is the person's own words: it outranks every other source, keeps its history, and takes effect on the next ask (the correction is read before any kept reply).
- It improves. The fixes are the person's local log, in their own vyred's database, never in the
repo or an eval:
memory.corrections { answers: true }andmemory.stats().iqgive "you corrected 3 answers this week" by kind of question (people, who, date, file, decision, bug, config, personal). eval-iq--fixapplies a person's correction to every wrong answer on the synthetic worlds and asks again: open 20 of 20 now right, sealed 38 of 38, none regressed. - The card (app-design, 28 Sep; folded into result-card's Answer variant after the RC). "Wrong?"
is quiet label text at the end of the meta line ("confidence 0.8 · from 2 sessions · Wrong?"),
underlined on hover, never a chip or a button. A tap grows the row in place, never a sheet or a
modal: two small ghost buttons ("That's wrong", "Forget this"), then a one-line text field
prefilled with the answer. Enter sends the edit, Esc collapses with nothing sent, nothing fires on
blur. A "not sure" card has no "Wrong?": the field is always open under it, empty, with the
placeholder "Know it? Tell me". After a fix the answer changes in place, and the meta line reads
"you corrected this · Undo", with no colour. Undo stays for as long as the card is on screen. A
corrected reply has
known: [], and its one source ("your correction") is not drawn as a chip. On the phone it is the same inside the Find row; buttons stack only when both do not fit at 44 pt. - Specs for the paused owners.
- chat (Deck chat and the PWA through chat-core): the same three choices on an IQ card inside a
conversation, calling
memory.correct { answer }; "Undo" callsmemory.uncorrect { fix }. - pwa: nothing of its own beyond chat-core's card.
- mobile: Find's card gets the same line and choices. Without a person session the reply is
person_session_required: offer the passkey sign-in, then send again.
- chat (Deck chat and the PWA through chat-core): the same three choices on an IQ card inside a
conversation, calling
An agent corrects memory from the person's own words
The user: when he tells an agent in chat "no, my wife is Juno", the agent must be able to fix memory. Built on work/memory-iq (core/memory/iq/heard.js), 0.1.1, for e2e's review.
- Evidence or nothing. An agent (a model's caller:
mcp,mcp:thread:,mcp:agent:) callsmemory.correctas the person would, plusfrom_turn: { seq }: the person's turn that says it. The thread is the one vyred verified for the call (meta.thread, from the session key or the agent key); a thread named in the input is ignored. Memory asks the switchboard who wrote that turn (threads.said, below), and applies only when all of these hold (e2e's review, 28 Sep):- the switchboard says
role: "user"andby: "person"; - the turn is fresh: one of the person's latest three in the thread (
back0 to 2) and at most 10 minutes old, never history; - the turn names what is corrected: a word of the fact's subject or relation, or of the IQ question;
- the correction is in the person's words, injected blocks removed: every name, number or content word of the new value (replace, add), or, for wrong, forget and ended, a "no" within six words of the old value's words ("I'm not sure" is not "wrong");
- that turn has not made a correction already, and the thread has made fewer than 3 this hour.
- the switchboard says
- Applied as the person's. It goes through the same path as a correction from the Deck:
highest trust, told to memory for a personal answer, attributed
heard:<thread>#<seq>, undoable withmemory.uncorrect. It emitsmemory.updated { by: "agent", thread, fix | correction }so the person's surfaces show "Memory updated · Undo". No Touch ID and no confirm: they said it. - Everything else waits. No verified thread, no
from_turn, a turn that is not the person's (tool output, a web page, a file, a launch brief, another agent), a stale turn, a turn that names something else, a value not in their words (a paraphrase), a project-scoped agent, or an action only the person takes (confirm, merge, split): kept as a suggestion (memory_iq_suggested), never applied. The agent gets{ applied: false, suggestion: { id, why } }. The same suggestion again is the same row. At most 5 wait per thread and 50 in all; past that nothing more is kept (dropped: true), so an agent in a loop never fills "waiting on you". A suggestion expires after 14 days, or at once when what it targets changes before the person decides; expired ones stay listed and never apply. The person lists them (every field plain text) withmemory.corrections { suggested: true }, accepts withmemory.correct { suggestion }and dismisses withmemory.uncorrect { suggestion }; an agent can do neither. The same listing'sheardrows show what agents applied from the person's words this week ("what is my wife's name: Your wife is Juno"), each with its undo, for a "Kit changed ... · Undo" row on every surface. - Tests (core/memory/iq/heard.test.js): a tool result saying "the user's wife is X", another agent quoting a web page, a launch brief, an injected system reminder, pasted content, a paraphrase, another thread's turn, no thread, no from_turn and a project-scoped agent are all kept as suggestions; the person's own turn is applied and answers at once; undo restores; an agent cannot accept a suggestion.
- Needs from the switchboard (sessions): threads.said { thread, seq }, internal (modules only),
->
{ role: "user"|"assistant"|"tool", by: "person"|"agent"|"module"|"program", text, ts, back }, from its own records, never from the caller.backcounts the person's turns after this one in the thread (0 for their latest).by: "person"only for a prompt the person typed: in their own interactive terminal session (notclaude -p), or sent withthreads.sendby a person caller after the daemon's downgrade (cli, local, deck, capsule, or tailnet or device with a person session). Neverby: "person", whatever the transcript says (e2e's list, 28 Sep):- tool_result entries;
isMetaandisCompactSummaryentries;isSidechainturns (a subagent's prompt is written by the parent model);- slash-command expansions, skill and hook text (UserPromptSubmit context, system reminders);
!shell output and<local-command-stdout>;- inbox deliveries: threads.post (teammates' results), threads.notice, and any queued row whose kind is not a person's send;
- a launch brief, and threads.send from an agent or a module. When in doubt, it is not the person. This list goes into its doc and its tests. Until it exists, every agent correction waits as a suggestion, which is safe.
- Needs from waiting (cohesion): a fifth source,
memory(kindmemory), read frommemory.corrections { suggested: true }, refreshed onmemory.suggested. The title stays generic ("An agent suggests a memory update"), never the value: it reaches the lock screen.
Ranked
Value is how often the user meets it and how wrong things are today. Cost is the work to build and test it.
| Rank | Gap | Owner | Cost | Value | State |
|---|---|---|---|---|---|
| 1 | The Capsule answers through memory.ask, streamed; Said.swift goes | capsule-pro, sessions | M | highest | memory-iq side built; spec sent |
| 2 | The assistant knows to ask memory (tool description) | memory-iq | S | high | built |
| 3 | about.md line: ask memory_ask, name the session | cc-plugin | S | high | spec sent |
| 4 | vyre memory ask is IQ | memory-iq | S | medium | built |
| 5 | Predictive text knows memory's names | memory-iq (+1 line in suggest) | S | medium | built |
| 6 | Chat hook adds a personal fact when asked, no model | cc-plugin (harness) | S | medium | spec sent (0.1.1) |
| 7 | Deck Find and Memory view IQ card | deck, app-design | M | high | spec sent (0.1.1) |
| 8 | Phone Find IQ card | mobile | M | medium | spec sent (0.1.1) |
| 9 | Chat renders "(from <session>)" as a link | chat | S | medium | spec sent (0.1.1) |
| 10 | Teammates' brief: memory_ask for project history | teammates | S | low | built (work/teammates 7eb7ffb8; reaches teammates with core/team step 3) |
| 11 | Project graphs: IQ reads a project's picked sessions, not only its folders | memory-iq, recall | S | high | built (398f6156) |
| 15 | Sessions start knowing the project (memory.today in the brief) | memory-iq, harness | S | medium | built (df22ca0c) |
| 12 | Mac sessions build the box's graph (consent, dedupe, one reader) | federation, memory-iq | L+M | highest for a box user | contract agreed; ADR 0008 amended; 0.2 |
| 14 | An agent corrects from the person's own words; anything else waits | memory-iq; threads.said: sessions; waiting: cohesion | S+S+S | high | memory side built; e2e review asked |
| 13 | Correct IQ where it appears, remembered | memory-iq; card: capsule-pro, app-design, chat, mobile | S+M | high | tool, CLI and eval built; card specs sent |
What makes IQ smarter, whatever the surface
These are memory-iq's own, measured on the synthetic worlds before they ship:
- Prompt v2 (each passage names its project; answer exactly what was asked). Open world: accuracy 0.778 and 1 confident-wrong, against v1 re-recorded the same day at 0.767 and 2. One question apart on 90 is within the model's run-to-run spread, so it is not a measured win yet.
- The second look for answers about people (ADR 0034 phase 4).
- Latency. 3.8 s p50 through
claude -p. The warm quick session in work/sessions is the fix. - "Before" for places: a past place is answered with the current place's confidence (sealed place-history).
Specs for the owners
Each owner builds against memory.ask as it is on work/memory-iq (streaming and id from 6adfc4b6).
Tests use the fixtures and temp homes only; under node --test memory has no model, so a question
that no personal fact answers comes back abstained, which is the state to test.
- capsule-pro. On a question, call
memory.ask { question, stream: true, id: "cap_<n>", context: { project } }. Show the thinking state from the firstmemory.thinkingwith that id, the stage as a word ("searching", "reading"). Draw the result card. A source tap opens the turn. Abstained: "Not sure yet." plusknown, then "Ask Claude instead".limited: showmessage. Remove Said.swift's question rules and thememory.relevantpluscomposeIqpath. Where the box has nomemory.ask(no_such_tool), keep today's path. - sessions. Once the Capsule has moved, the capsule purpose's IQ prompt (core/sessions/iq-prompt.js) is used only by old Capsules; retire it a release later. Keep the quick session for purpose memory warm: it takes IQ from 3.8 s to under 3 s.
- cc-plugin (paused). core/about: add "For a question about your past work or life, call
memory_ask and name the session it cites." to about.md, inside the 600-character budget.
harness.enrich: when the session is the user's own and interactive (no agent, all projects), and
memory.answer { q: prompt }returnskind: "fact"at confidence 0.5 or more, add its answer as one memory line "(from what you have said)". Otherwise nothing new. No model call per prompt. - Deck and app-design. Find: the IQ card above the hits for question-shaped words, on Enter or
after 700 ms still, one call per settled question, the previous one's events ignored by
id. Memory view: the same card from its search box. Put the card in chat-core so the PWA gets it. - mobile (paused). iOS FindView and Android Find: the same card over the tailnet, a plain thinking state (no events), the same question test and debounce.
- chat (paused). Render "(from <session name>)" in an assistant message as a link to that
session when the session exists in
recall. - teammates. One line in a teammate's brief: "For what was decided or done before in your projects, call memory_ask; it sees only your projects." Notes stay out of memory.