zero-memory
Concepts

Open loops

Task and open-question memories that stay in the task buffer until they are closed, with a soft briefing TTL.

An open loop is a memory of kind task or open-question: a piece of unfinished work or an unanswered question. Unlike ordinary memories, which compete for relevance in retrieval, recent open loops surface in a dedicated section of every briefing of their scope. One precondition: a briefing shows a project's loops only once the session is attached to that project (a project hint, an inherited thread, or explicit scopes). An unattached briefing narrows to the personal layer, so the hand-off lands when the receiving session names the same project. Closed loops disappear from briefings but stay in history.

Transport is not a hand-off

Because one server backs all your machines, projects, and agents, memory already works as a transport: a fact written on machine A is visible in the next briefing on machine B. What ordinary memories lack is the semantics of a hand-off: "surface at the recipient, persist until handled, disappear once done." Open loops add exactly that lifecycle and nothing else. The same mechanics covers two everyday cases. One is the inter-machine hand-off, "check log X on machine Y": record it on one machine, and the agent on the other machine sees it at session start. The other is the unfinished thread, "we never settled question Z": the question keeps resurfacing until a session answers it.

Open loops are a lifecycle dimension, not a topic. Kinds in zero-memory describe how the system should behave toward a memory, and open loops behave differently: short-lived, priority-surfaced, and excluded from long-term knowledge machinery.

Surfacing, ageing out, and closing

build_context returns active loops in a dedicated open_loops field, separate from (and in addition to) the normal top-k memories, with a total count. Session hooks render them as a distinct "Open loops" section, newest first, with an "N more" counter; old loops get a staleness badge by age. The server caps the list at 10 loops. In the hook briefing the section holds its own share of the channel budget, so a long block of standing rules cannot crowd it out, and a loop too long to fit whole arrives as a one-line stub with its id instead of ending the section. A loop attached to a card that the briefing's board lines name, or promoted into such a card, is shown through that card instead of on its own. The section is worded as recorded open work — data, not instructions.

Depth follows the topic, selection does not. All ten arrive whatever the briefing is about, but the loops nearest the topic are inlined in full while the rest carry a headline. Nearest is a rank rather than a bar, so a scope holding a single open loop always gets that one in full. A loop's relevance is judged against its whole text, so one whose live part sits past its opening is still recognized. Nothing can be pushed out of sight by being off-topic; being off-topic only makes a loop shorter on that particular briefing.

The briefing TTL is soft. By default, loops older than 14 days stop appearing in open_loops and its total, and the instance knob zm.open_loop_ttl_days changes that window. Aging out does not close or invalidate the memory: it remains recallable, visible in the dashboard, and closable by id. It does not vanish from the briefing without a trace either. build_context also returns open_loops_aged, the number of open loops past the window that sit on no card, and the hook briefing ends the section with one line saying how many there are and how to list them. The line appears even when no loop is inside the window. A loop promoted into a live card or attached to one is not counted, because the board already reaches it.

The close_loop tool closes a loop the moment it is done or answered. The memory is invalidated (add-only, so it is kept in history and audited) and stops surfacing. When the closure itself is a fact worth keeping ("done: deployed the fix, root cause was …"), remember with a supersedes link to the loop closes it too, organically. The agent on the receiving machine can close a loop after completing it.

Explicit closing depends on the finishing agent's discipline, so the hygiene cycle carries a safety net. It rolls up evidence that a loop's work was completed, and a conservative judge (high confidence gate) may close it: reversibly, with a successor link and an audit trail, and with a guard against re-judging the same loop. Reopening a loop with restore_memory holds it open against the evidence that exists at that moment: the judge takes it up again only once newer evidence arrives, so a person's "not done" is never overruled by the very evidence it answered. A loop promoted to a card is never judged: its work was handed over, and the card now carries it.

Two exclusions keep loops out of the knowledge machinery. They never become rules incubator candidates, and they are kept out of long-term knowledge metrics, because they are work items rather than durable knowledge.

A hand-off, not a status report

A loop is for a one-off hand-off or a question: written once, closed once. The running status of ongoing work (the branch, what is done, what comes next) is different. It changes every session, so a status kept in a loop gets re-cut again and again, each version retiring the one before. That status belongs on the work's card as a note (card_log note). There the next session is offered it together with the card, and the notes accumulate as the card's history instead of replacing each other.

The remember response points this out at the moment it happens. When a task write retires an earlier task loop, whether through a supersedes link or the same-session collapse, the response carries status_hint. It says which cut of the loop this is and where the next update goes: the card the conversation is bound to, by its label, or, when there is none, an instruction to run card promote_loop on the loop. A first cut gets no hint. Neither does a re-cut open-question, which adds evidence to the question rather than reporting progress.

A loop is a pointer, not a payload. Logs and dumps do not belong in memory. A task carries a path or URL to the artifact plus a distilled summary of what to look at; the content guard enforces this on the write path like any other memory.

Creating and closing a loop

A loop is created and closed through MCP tools. An agent calls them; there is nothing to type into a terminal. Arguments to remember:

{
  "content": "Check the watcher warnings in /var/log/... on the build machine — summary: repeated reconnects after resume",
  "kind": "task"
}

Outside a session there IS a command, the quick-capture CLI:

zm --task "verify the migration applied cleanly on the staging database"

Close it when done. Arguments to close_loop:

{ "memory_id": "mem_..." }

Or store the outcome with remember plus a supersedes link, which closes the loop as a side effect. close_loop refuses non-loop kinds; use forget for a wrong or outdated regular memory.

In the dashboard, the memories feed filters by kind, so task and open-question give you a live list of open and historical loops.

Dashboard memories feed filtered to kind task/open-question, showing an active and a closed loop

Deliberate limits of the loop model

Closing is invalidation, never deletion. The store is add-only; a closed loop remains queryable history with its audit trail. Hard deletion is reserved for its own purpose and is not part of the loop lifecycle.

There is no automatic creation. The transcript extractor deliberately cannot produce task or open-question memories, because its schema is narrowed to exclude them. Auto-extracted loops that nobody closes would be noise, so loops are created only intentionally.

Addressing is conventional. A loop targets its audience by scope plus naming the machine or agent in the text; strict per-agent addressing is a later concern (see Agent principals).

See Briefing for where open loops appear at session start, and Session receipt for the end-of-session counter of loops created and closed.

On this page