# Memory Banking

**How finished turns from every harness become Markdown memory — and what reads it**

---

## In one paragraph

A **turn** is one prompt you send to a harness plus everything its agent does until it stops and hands control back to you. When a turn finishes, HarnessLink writes a short Markdown **bank entry**: your prompt, the tools the agent called, and its final answer. Entries live in a per-project folder on your disk and upload to HarnessLink Cloud like any other memory file. Recall, briefs, context hooks and the MCP memory tools all read them.

The **HarnessLink service** produces them by reading each harness's transcripts on disk — omp, pi, Claude Code, Codex and Antigravity. This is **autobank**. `superpi cloud setup` turns it on for every one of those harnesses it finds, and the first pass for a harness banks its last 30 days. Cursor is not supported. Entries that HarnessLink's old built-in agent banked in-process stay in the tree and keep syncing; nothing produces new ones.

## Background and origin

HarnessLink already runs an append-only memory-gateway pattern: a `MemorySyncUploader` background process scans the memories tree (`~/.superpi/agent/memories/`), uploads every `.md` file it finds to the cloud D1 store, and the reverse `pull-memories` command rehydrates the local tree from the cloud. This upload/pull pipeline already works and ships zero server changes for storage — any `.md` file placed in the right directory is picked up automatically.

The memory banking feature builds on that foundation by adding a new write path: when an agent turn settles, a Markdown artifact representing that turn is appended to the project-scoped memory tree. No changes to the upload infrastructure, the D1 schema, or the pull endpoint are required.

---

## Layout

All files live inside the existing memories root (`getMemoriesDir()`, default `~/.superpi/agent/memories/`). Project directories use the same slug encoding as the existing pipeline:

```
<memoriesRoot>/
  <project-slug>/          ← e.g. --Users-me-Workspace-myapp--
    bank/
      <file-safe-ISO>--<slug>.md   ← one entry per finished turn (append-only)
    MAIN.md                        ← index controller
    summaries/
      <YYYY-MM-DD>.md              ← daily roll-up (additive, never deleted)
    state/
      <harness>--<sessionId>.md    ← latest todos / plan / checkpoints of one harness session
```

The project is the turn's working directory, so a turn from Claude Code and a turn from omp in the same folder land in the same project.

### Bank entry file

Each file is a self-contained Markdown artifact with YAML frontmatter:

```markdown
---
id: 2026-08-28T10-30-00-000Z
ts: "2026-08-28T10:30:00.000Z"
title: "Fix the authentication bug"
tags:
  - bash
  - read
  - write
cwd: "/Users/me/Workspace/myapp"
session_id: "01929fae..."
harness: claude-code
turn_key: "claude-code:01929fae...:4b1c..."
source: autobank:claude-code
---

## Prompt

The user's request (capped at 2 000 chars).

## Actions

- bash: {"command":"npm test"}
- write: {"path":"/src/auth.ts","content":"..."}

## Result

The final assistant response (capped at 4 000 chars).
```

**File-safe ISO** replaces `:` and `.` with `-` so filenames work on all filesystems. The double-dash `--` separator distinguishes the timestamp prefix from the slug, since the timestamp itself contains only single dashes after the replacement. The time is when the turn finished, not when it was banked, so a backfilled entry sorts where it happened.

**`harness`, `turn_key` and `source`.** Autobank entries carry `harness: <id>`, `turn_key: <harness>:<sessionId>:<turnId>` and `source: autobank:<harness>`. Older entries from HarnessLink's old built-in agent carry `source: superpi-autobank` and no `harness` or `turn_key`.

**Per-file byte cap**: each entry is checked against `MEMORY_UPLOAD_MAX_BYTES` (1 MB) — the same ceiling the `MemorySyncUploader` enforces — and the Result section is trimmed if the file would exceed it. In practice, the character caps (2 000 / 4 000 chars) keep entries well under 10 KB.

### MAIN.md

An index controller file per project. Human-written notes above the marker comments are preserved verbatim across every regeneration:

```markdown
# Project Memory

Add context, architecture notes, or reminders here.
Everything above the markers is untouched by the banker.

<!-- superpi:index:start -->
- 2026-08-28T10:30:00.000Z — Fix the authentication bug (bank/2026-08-28T10-30-00-000Z--fix-the-authentication-bug.md)
- 2026-08-27T14:00:00.000Z — Add user preferences endpoint (bank/...)
<!-- superpi:index:end -->
```

The index is rebuilt newest-first, capped at 200 rows, once per autobank pass for each project that pass touched.

### Daily summaries

Each project's past days that have bank entries but no summary file are compacted into `summaries/YYYY-MM-DD.md`:

```markdown
# 2026-08-27

- Fix the authentication bug [bash, read, write]
- Add user preferences endpoint [bash]
```

Compaction is purely additive: existing summary files are never touched — the bank is append-only by design.

### Work state (`state/`)

For each harness session, autobank also keeps one file with that session's latest **todos**, **plan** and **checkpoints**: `state/<harness>--<sessionId>.md`. Frontmatter: `harness`, `session_id`, `cwd`, `updated` (time of the newest change), `kinds`. Sections:

- `## Todos` — `- [x]` done, `- [~]` in progress, `- [!]` blocked,
  `- [-]` abandoned, `- [ ]` pending
- `## Plan`
- `## Checkpoints` — the last 10

| Harness | Read from |
| --- | --- |
| omp, pi | the todo tool's results and manual todo edits, `<session>/local/PLAN.md`, checkpoint/rewind calls |
| Claude Code | `~/.claude/tasks/<session>/*.json`, legacy `~/.claude/todos/…`, `TodoWrite` calls, `~/.claude/plans/<slug>.md`, `ExitPlanMode` plans |
| Codex | `update_plan` calls (not those made inside code-mode `exec` scripts) |

The file is checked whenever the session's transcript grows, rewritten only when its content changes, and never deleted. Like bank entries, it uploads through the normal memory sync.

---

## When banking fires

### Autobank

The HarnessLink service runs an **autobank pass** when it starts, every 30 seconds, every 2 seconds while a backfill is still draining, and once in every `superpi cloud sync`. A pass reads up to about 8 MB of new transcript per harness. It only reads files; it never writes to a harness's store.

| Harness | Transcripts read | A turn is finished when |
| --- | --- | --- |
| omp | `~/.omp/agent/sessions/<dir>/*.jsonl` | the assistant stops (`stop`, `length`, `aborted`), the next prompt arrives, or the session exits. An `error` stop waits, because a retry continues the turn |
| pi | `~/.pi/agent/sessions/<dir>/*.jsonl` | same as omp |
| Claude Code | `~/.claude/projects/<slug>/<uuid>.jsonl` | a `turn_duration` record, the next real prompt, or an interrupt. A plain `end_turn` stop is not enough on its own, because a Stop hook can still continue the turn |
| Codex | `~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl` | `task_complete`. Interrupted turns (`turn_aborted`) are never banked |
| Antigravity | `~/.gemini/antigravity-cli/conversation_summaries.db` and `history.jsonl` | — see below |

**Quiet transcripts.** For omp, pi and Claude Code, a turn that has an answer and no tool call still pending is also banked once its transcript has been quiet for 5 minutes, so the last turn of a session is not lost. A turn still waiting on a tool is never banked this way; after 7 days of silence it is dropped. Codex turns settle only on `task_complete`.

**Antigravity** keeps no readable per-turn transcript, so it gets **one entry per conversation**: the conversation title, its first prompt, and a result of step count, last activity and up to the last 20 prompts. The entry is dated by the conversation's first prompt. When the conversation's step count or last-modified time changes, the same entry is rewritten in place. Child conversations, conversations with no workspace folder, and conversations last modified before the backfill window are skipped.

**Not banked:** subagent turns (omp/pi `<session>/` subagent files, Claude Code `subagents/` and sidechain lines, Codex subagent threads), turns a branched or forked omp/pi session copies from its parent session (they are banked once, from the parent), and **Cursor**, which is not supported.

**Backfill.** The first pass after a harness is turned on banks the turns that finished in the **last 30 days**, then follows new turns. Transcripts not modified in those 30 days are not read. `superpi cloud autobank backfill --days=N` widens the window and re-reads every transcript once.

**Exactly once.** Each turn is banked at most once, keyed by its `turn_key`, across restarts, log rotation and backfills. A pass stopped in the middle of a write loses no turns: another pass takes over its reservation after 10 minutes. Read positions, unfinished turns and banked keys live in `~/.superpi/agent/harness-autobank.db`.

**Switches.** `cloud.autobank` in `config.yml` lists the harnesses with autobank on (`superpi cloud autobank enable|disable <id>`; setup turns it on for every detected one). On a machine set up before autobank existed the key is absent, and autobank follows `cloud.harnesses` (the harnesses already captured), so updating HarnessLink turns it on without re-running setup; once the key is written — by setup, `enable` or `disable` — that list is used, even when empty. Autobank works locally: it does not depend on any sync category, and its files upload through the normal `memories` category. Turning a harness off — with `disable`, by removing it from `cloud.autobank`, or with `memory.autoBank: false` — pauses it. Entries already banked stay. When it is turned back on, it banks the last 30 days, not everything from the time it was off.

### Trivial-turn filter

For every harness except Antigravity, a turn is skipped when:

- No tool calls were made **and** the result text is shorter than 200 characters.

This excludes brief conversational exchanges that carry no durable signal — simple acknowledgements, one-line answers, and error recoveries — while banking every meaningful working session. Antigravity conversations are always banked.

### The master switch

`memory.autoBank` (boolean, default `true`) is the master switch. Setting it to `false` in `config.yml` stops all banking and `state/` snapshots for every harness. It is read on every autobank pass, so it takes effect without a restart.

### Safety invariants

- **Banking never breaks a session.** Autobank runs in the HarnessLink service, outside every
  harness, and only reads their files. Errors are caught and logged; a disk-full or permission error produces a log warning, not a crash.
- **New entries reach the cloud on the next memory sync** of the HarnessLink service, or at
  once with `superpi cloud sync`.

---

## Why zero server changes

The `MemorySyncUploader` scans the entire memories tree for `.md` files and uploads any that are new or changed. `bank/*.md` and `summaries/*.md` are plain Markdown files in the same directory tree. No new file categories, no schema migrations, no API additions are needed. The cloud store receives them on the next scan, and `pull-memories` delivers them to any other machine that pulls.

The MAIN.md index file is also in the tree and reaches the cloud, giving remote sessions a human-readable overview of the project's banked history.

## Consumers of the bank

Banking is the compression step; four surfaces consume it, so the distilled record replaces raw transcripts wherever context is re-established:

- **Recall** — `superpi cloud recall <query>` and `GET /v1/memories/index/search` run the
  shared FTS core over bank entries (own project + `linked.txt` projects by default).
- **Context brief** — `superpi cloud brief` (`src/cloud/brief.ts`) assembles a budgeted brief
  from the human part of `MAIN.md`, the newest ten bank entries, the harness's own memory files (`raw_memories.md`, newest rollout summary) and an optional recall section from the local index below. Sections are whole-or-dropped under `--budget`.
- **Resume broker** — `superpi resume` offers the brief beside native resume once a
  transcript's estimated replay cost reaches 50 000 tokens (`src/cli/resume-broker.ts`).
- **MCP server** — `superpi mcp` exposes `memory_search`, `memory_get`, `memory_bank` and
  `context_brief` (plus the aliases `recall_memory` and `record_learning`) so any coding harness wired to the server can search, fetch and bank memory inline without leaving the session (`src/mcp/server/superpi-server.ts`; `src/cloud/brief-wiring.ts`). The full tool list is in USAGE.md.
- **Context hooks** — `superpi context-hook` injects the brief at session start and up to
  three matching bank entries on each prompt in Claude Code, Codex and omp.
- **Local memory index** — `~/.superpi/agent/memory-index.db` (`src/memories/local-index.ts`,
  ADR-43): SQLite FTS5 over the whole tree, a bank entry split into title, tags, prompt, step-log digest and result. The service keeps it current from its file watcher; `memory_search`, the brief's recall and the hooks read it first, and `memory_get` reads the file on this machine before asking the cloud. See USAGE.md.

Nothing here changes how banking writes; these are readers of the same tree.

---

## Implementation files

| File | Role |
| --- | --- |
| `packages/superpi/src/memories/banker.ts` | Core implementation: entries, `MAIN.md`, summaries, `state/` files |
| `packages/superpi/src/cloud/harness-autobank.ts` | Autobank: the pass over each harness's transcripts, cursors, backfill |
| `packages/superpi/src/cloud/harness/turn-parsers.ts` | Per-harness turn and work-state parsers |
| `packages/superpi/src/cloud/autobank-cli.ts` | `superpi cloud autobank` |
| `packages/superpi/src/cloud/config.ts` | `memory.autoBank` master switch (read from `config.yml`, default true) |
| `packages/superpi/test/memory-banker.test.ts` | Unit tests |
