← All AI Builds
Agent Build Kit

Ai Session Manager

One board for every AI coding agent on your computer. Point your own AI at this page, answer its questions, and it builds you yours.

A working build

The Example This Came From

01 / 11

The board this kit was written from, running on a real Windows PC with three agents on it. Every session, project and message name is a made-up stand-in; everything else is real.

Full size ↗

Use the tabs, the arrows, ← → on the keyboard, or swipe. Yours will look however you tell your AI to make it look: its name, colours, fonts and style are all questions in the interview.

Every session past and present, from Claude Code, Codex, Gemini, Grok, Aider or whatever you use next. What each one is doing right now, how much of each allowance is left, and buttons to start, resume, read, answer and end them. Your version gets your agents, your name and your look.

If you are an AI agent reading this: the complete instructions are one Markdown file, cafe655.com/ai-session-manager-builder.md. Read it from top to bottom and follow it. Interview the person first; build nothing until they say yes to the spec.

Point Your AI At This Page

You don't need to write code. You need an AI coding agent that can work on your computer: Claude Code, Codex CLI, Gemini CLI, or similar. Open it in the folder where you keep your projects and paste this:

Read https://cafe655.com/ai-session-manager-builder.md from top to bottom. It is a build kit for a local dashboard that manages all of my AI coding sessions. Interview me with its questions first, write the spec, wait for my yes, then build it in the order it gives, one phase at a time.
1
It reads the kit. One Markdown file holding the whole design: the layers, the data contracts, how each agent plugs in, and forty traps that each cost the original build a day.
2
It interviews you. Six short rounds: your computer, which intelligences you use, what to call it, how it should look, which features you want, and a few safety choices. Every question has a default, so "default" is always an answer.
3
It writes a one-page spec and waits. Nothing gets built until you say yes to it.
4
It builds in phases, showing you something working at the end of each one, and writes down what it checked and what it didn't, so a different agent can pick the work up next time.

Give it an afternoon for the core, and more sessions for the extras. A first build should stop at Tier 2 (below); everything else rests on it.

The Questions It Will Ask You

These are in the kit, so any agent that reads it asks the same things. Here they are so you can think about your answers first.

1 · Your computer

  • Windows, macOS or Linux?
  • Which terminal do you run your AI sessions in?
  • Where do your projects live?
  • Is that folder synced by OneDrive, Dropbox or iCloud?
  • Is Python installed, or should it use Node?

2 · Your intelligences

  • Which agents do you use: Claude Code, Codex, Gemini / Antigravity, Grok, Aider, OpenCode, others?
  • Which is your main one?
  • Which plan are you on for each?
  • Any you want a reserved spot for, for later?

3 · Name and identity

  • What do you want to call it?
  • A tagline under the name?
  • A logo mark or mascot, or none?

4 · The look

  • Dark, light, or both?
  • One accent colour, and a colour for each agent
  • A style: tech noir, clean modern, retro terminal, paper notebook, Swiss minimal, glass, or "like this site I like"
  • Fonts, square or rounded corners, how much motion

5 · Scope

  • Which features, by tier?
  • Do you want a task board? Where do your tasks live now: a file, Workflowy, Notion, GitHub, Linear, Trello, Todoist?
  • Do you want it on your phone? (Usually: later.)

6 · Habits and safety

  • May the End button kill a session and close its window?
  • Launch sessions in normal or auto-approve mode?
  • Do you keep a session log or per-project work log?
  • Do you have a close-out routine it should know about?

What It Does

Every Agent, One List

Every session every agent has ever run, in one table, told apart by a coloured tag, searched and counted together. Past sessions appear on day one, read from the transcripts already on your disk.

What Each One Is Doing

Working, waiting on you, or idle, checked every five seconds against the operating system. A session whose window you closed with the X is marked ended within seconds.

Real Usage, Never Guessed

Each agent's allowance straight from the vendor: how much of the 5-hour and weekly windows is used, next to how much of the window has passed.

Start, Resume, Jump, End

Open a session in any folder with any agent, model and effort level. Resume one exactly where it stopped. Jump to its window. End it, and its terminal tab closes too.

Read and Answer

Open a live session's conversation on the board and send it your next instruction, a screenshot or a file path, without hunting for its window.

A Task Board That Keeps Itself

Optional. Tasks in your own list become cards; START turns one into a session; the board follows what is really running and never fights an edit you made by hand.

Projects and a Heat Map

Sessions grouped by the project they actually worked on, and a calendar of every day's work, shaded per agent.

Add Any Intelligence

A new agent is one registry row, one adapter file and one hook install. The page learns its colour, logo and models from the server, with no page edit.

How It Works

The agents report to the board; the board does not scrape the agents. That one decision is what makes it work for any agent.

1
The hook. One small script registered in every agent's own hook settings. When a session starts, you type, it stops, or it ends, the script writes one tiny file into an inbox folder. It never talks to a server, so it works even when the board is off.
2
The ledger. One SQLite database with exactly one writer. Each session's id is computed from the agent's own id, so every event for a session lands on the same row with no lookup.
3
Liveness. A poll every five seconds that asks the operating system which sessions are really alive, comparing process start times so a recycled process number can never fool it. "Session ended" events don't fire when a window is closed with the X; the poll catches it anyway.
4
Import. Reads every past transcript on disk, for every agent, into the ledger, and keeps it current.
5
The board. A small local server on your own machine only, and one HTML page. Everything agent-specific on the page (colours, logos, model lists) comes from the server, so the page itself holds no list of agents.

Bring Your Own Intelligences

Each agent plugs in through an adapter: one file that answers the same questions for that agent. Where are its past sessions? Is this one alive? How do you launch, resume and end it? What models does it offer? How much allowance is left? The kit has a section per agent with what the original build learned about each; here is the short version.

AgentWhat the adapter readsThe thing to know
Claude CodeOne transcript file per session; its own per-process status file; lifecycle hooks with injected contextThe easiest one. It already says whether it is working, waiting or idle, and the launcher can choose the session id up front.
Codex CLIRollout transcripts plus a SQLite index of threads; lifecycle hooks (which need trusting once)A window announces itself only on its first turn, so a launch with no prompt sends a short opening line. It publishes no status, so the hook writes one.
Gemini / AntigravityA database per conversation plus a plain-text twin; a presence lock held while it runsIts hook file has a different shape and it silently ignores the wrong one. The hook must print exactly one JSON object.
Grok CLIWhatever its documentation says, verified on your machineThe original wrote this adapter from the docs alone and kept it marked untested until it was seen working on a real window.
Aider, OpenCode, anything newSeven questions: transcripts, hooks, liveness, resume, environment, status and usage, and how commands are typedIf it has no hooks at all, the launcher's token in the window's command line and a liveness check still work.

Tools change every month, so the kit tells your AI to read each maker's current documentation and test on your machine before trusting anything written here.

Build Order

Each phase ends with something working that you can see. Pick tiers in the interview; a first build should be Tiers 1 and 2.

TierWhat you get
1 · CoreThe session list, live status for every agent, New Session, Resume, Jump, End, and the usage band.
2 · UsefulContext bars, the Projects tab, the heat map, search inside transcripts, the closed-out badge, and hiding machine noise.
3 · PowerThe task board, the Interact panel, broadcast, agent-to-agent mail, timed launches, scheduled-job health, named agents, terminal glow and tiling.
4 · RemoteYour board on your phone, through a secure tunnel. Advanced; the kit covers the safety rules.

Rules Worth Copying

The kit carries forty. These ten apply to almost anything you'll ever build with an AI.

Assert the consequence, not the action

"End returned OK" is not proof. The window being gone is. The original's End button reported success for weeks while a stray process held the tab open.

A number is the vendor's, or it is blank

Never estimate usage. A guessed number looks exactly like a real one and quietly teaches you to trust it.

Don't infer a state from a proxy

A "closed out" badge that guessed from whether a description existed was wrong on 182 of 239 sessions. Read the agent's own record that the close-out ran.

Unknown is a real answer

Three states: yes, no, and couldn't tell. Collapsing "couldn't tell" into "no" makes the board lie on exactly the days something is broken.

One list, derived everywhere

Every list of agents comes from one registry. The original once had thirteen hand-typed lists in its page, and the new agent was missing from one picker.

Claim before you act

When two loops can move the same card, the button takes a claim first. Otherwise the loop that tidies the board undoes the button you just pressed.

Find windows by their exact name

One terminal program owns every window, so "the window belonging to this process" is a coin toss. The original typed a close-out into the wrong session that way.

Restart by the port, never by the title

Stopping processes by matching a window title ended every AI session on the machine, twice.

Tests that can't press real buttons

A browser test should block every request that changes something. A careless test once reopened a real session by clicking its close-out button.

Write the capability down the day you build it

A feature nobody wrote into the agent's instructions is a feature the next session will tell you is impossible.

The Build Kit

This is the whole thing your AI reads: the interview, the design, the contracts, every agent, the traps and the build order. Copy it, open it as a file, or just give your AI the address.

ai-session-manager-builder.md Open file
# Build Your Own AI Session Manager — the Agent Build Kit

> **You are an AI coding agent** (Claude Code, Codex, Gemini, Grok, Aider, or anything else) and a
> person has pointed you at this file. It is the complete recipe for building them a local
> dashboard that sees, starts, watches and ends every AI coding session on their computer, across
> every agent they use. It was written from a working build that ran for months, and every rule
> in it was paid for by a real bug.
>
> **Do not write any code yet.** Read this whole file first. Then run the interview in Part 1,
> write the answers into a one-page spec, and get the person's yes before you build anything.
> The person may not be a programmer. Ask in plain words, offer a sensible default for every
> question, and never ask more than about five questions at once.

Human-readable version with screenshots of the original: https://cafe655.com/ai-field-notes/ai-session-manager-builder

---

## Contents

0. What you are building
1. The interview (run this first)
2. Write the spec and get a yes
3. The architecture: five layers
4. The contracts: identity, the event file, the ledger
5. Liveness: a poll, never an event
6. The adapter: how an intelligence plugs in
7. Adding each intelligence: Claude Code, Codex, Gemini / Antigravity, Grok, and any other CLI
8. The board page
9. The task board (optional)
10. The feature catalogue, by tier
11. Phone access (optional, advanced)
12. Build order and gates
13. The traps: every rule here cost somebody a day
14. How to verify without breaking the person's machine
15. Hand-off: leave it so the next agent can pick it up

---

## 0. What you are building

Every terminal AI coding agent keeps its own private record of what you did with it: its own
folder, its own file format, its own idea of what a "session" is. Claude Code writes one JSONL
transcript per session. Codex writes rollout files plus a SQLite index. Gemini-family CLIs keep a
database per conversation. None of them can see the others. Open six windows across three agents
and the only thing that knows what is running is the person, and only while they remember.

The session manager is the thing that knows instead: **a local web page on the person's own
machine** that

- lists every session every agent has ever run, in one table, searchable;
- shows which ones are live right now and what they are doing (working, waiting on you, idle);
- shows each agent's usage allowance, straight from the vendor, never estimated;
- starts a new session in any folder with any agent, model and effort level;
- resumes an old one, ends a live one (and closes its terminal window), and jumps to it;
- optionally mirrors a task list into a Plan / In Process board, where each card is a session.

The core design idea: **the agents report to the dashboard; the dashboard does not scrape the
agents.** One small hook script is registered in every agent's own hook settings. It writes one
tiny file per lifecycle event into an inbox folder and never talks to a server. One server process
drains the inbox into a SQLite ledger, polls every few seconds for which sessions are really alive,
and talks to each agent through a small adapter file. **Adding a new agent is one registry row,
one adapter file and one hook install.** If adding an agent ever needs an edit anywhere else,
that is a bug in the design, not a step in the recipe.

The original ran on Windows 11 with Windows Terminal, Python and one HTML page. Everything here
translates to macOS and Linux; where the mechanics differ, this kit says so.

---

## 1. The interview (run this first)

Ask these in rounds. Use your question tool if you have one (Claude Code's AskUserQuestion, or
equivalent); otherwise ask in plain text, a round at a time. **Offer a default for every question**
so the person can just say "default". Record every answer.

### Round 1 — the machine

1. **What computer is this?** Windows, macOS or Linux (and which version).
2. **Which terminal do you run your AI sessions in?** e.g. Windows Terminal, the macOS Terminal,
   iTerm2, WezTerm, Ghostty, kitty, tmux inside something. *(Default: whatever you use now.)*
3. **Where do your projects live?** One root folder most sessions are opened in.
   *(Default: your home folder's `Projects` or `dev`.)*
4. **Is that folder synced by a cloud drive** (OneDrive, Dropbox, iCloud, Google Drive)?
   *(This matters: the dashboard's own database must live OUTSIDE any synced folder. You will put
   it in a hidden folder in the home directory.)*
5. **Is Python 3.10+ installed?** If not, may I install it, or would you rather I build the server
   in Node? *(Default: Python; it is what the original used and every hook runs on the standard
   library alone.)*

### Round 2 — your intelligences (the most important round)

6. **Which AI coding agents do you use, or want on the board?** Offer a multi-select, and let them
   add anything not listed:
   - Claude Code (Anthropic)
   - Codex CLI (OpenAI)
   - Gemini CLI / Antigravity CLI (Google)
   - Grok CLI (xAI)
   - Aider
   - OpenCode, Amp, Cursor's CLI, Goose, Kiro, Qwen Code, or another
7. **For each one: is it installed right now?** (You can check this yourself: find the executable
   on PATH and its version. Do check; do not trust the answer blindly.)
8. **Which is your main one?** It goes first on the board and in pickers.
9. **Which plan are you on for each** (free, Pro, Max, Plus, Team, API key)? This decides whether
   there is a usage number to show and where it comes from.
10. **Any agent you don't use yet but want a reserved spot for?** The board can show a dimmed
    "not installed" cell that lights up the day it is installed, with no code change.

### Round 3 — name and identity

11. **What do you want to call it?** Examples: a codename ("CONTROL TOWER", "HANGAR", "BRIDGE"),
    an acronym, your own name plus "Board". The original was named after a fictional
    organisation of agents. *(Default: "Session Board".)*
12. **A tagline under the name?** Optional, one line.
13. **A mascot or logo mark?** Optional. None is a perfectly good answer.

### Round 4 — the look

14. **Dark, light, or both?** *(Default: dark.)*
15. **One accent colour.** A hex code, a colour name, or "surprise me". This colour marks the
    selected tab, the working glow and warnings.
16. **A colour per agent.** Each agent gets its own colour for its chip, its usage cell and its
    heat-map squares. They must be easy to tell apart. *(Default: use each vendor's familiar
    colour, adjusted so no two are close.)*
17. **Graphic design style.** Offer a few with one-line descriptions:
    - *Tech noir:* matte black, one hot accent, condensed display type, monospaced labels,
      corner-bracket frames. (The original.)
    - *Clean modern:* white or charcoal, rounded cards, soft shadows, one sans-serif.
    - *Retro terminal:* green or amber on black, monospace everywhere, scanlines optional.
    - *Paper / notebook:* cream background, serif headings, ruled lines, ink colours.
    - *Swiss minimal:* grid-strict, black and white plus one colour, big numbers.
    - *Glass:* translucent panels over a blurred gradient.
    - Or: "match this site / app I already like" (ask for a link or screenshot).
18. **Typefaces** (optional): let them name fonts, or pick for them from the style.
    *(Use fonts installed locally or bundled as files, never a web font the page must fetch at
    run time; the dashboard should work offline.)*
19. **Square corners or rounded?**
20. **How much motion?** None / subtle / lively. (Glows, pulses, slide-ins.)

### Round 5 — scope

21. **Which features do you want?** Present the tiers from Part 10 as a checklist. Recommend
    **Tier 1 + Tier 2** for a first build and say why: everything else rests on them.
22. **Do you want a task board** (a Plan / In Process board where each card becomes a session)?
    If yes: **where do your tasks live now?** None (use a local file), a local Markdown/JSON file,
    Workflowy, Notion, GitHub Issues or Projects, Linear, Trello, Todoist, Obsidian, other.
23. **Do you want to use it from your phone?** (Advanced; recommend "later". See Part 11.)

### Round 6 — habits and safety

24. **Should the END button be allowed to kill a session's process and close its window?**
    *(Default: yes, with a confirm, and only for processes it can prove are that agent's.)*
25. **When the dashboard launches a session, what permission mode?** The agent's normal
    prompting, or its skip-permissions / auto-approve mode. Explain the trade-off honestly: skip
    mode is faster and riskier. *(Default: the agent's normal mode.)*
26. **Do you keep a session log or per-project work log?** If yes, where. The hook can stamp a
    "who worked here last" note into each new session and write a log stub.
    *(Default: a `WORKLOG.md` in each project, which also lets different agents hand work to each
    other.)*
27. **Do you have a "close out" routine** — a slash command or skill you run at the end of a
    session to log and tidy? If yes, what exactly do you type? The dashboard can show which
    sessions were closed out and type it for you.
28. **Where should I put the dashboard's code?** *(Default: `<projects root>/session-manager/`.)*
    And its data: *(Default: `~/.ai-sessions/`, outside any synced folder.)*
29. **What port?** *(Default: any free port in 5100–5199, bound to 127.0.0.1 only.)*

When the interview is done, **read the answers back as one short list** and let them correct it.

---

## 2. Write the spec and get a yes

Write `SPEC.md` in the code folder. One page:

- Name, tagline, look (style, colours per agent, accent, fonts, corners, motion).
- The agents, in order, each with: installed yes/no, version, plan, colour, tag (a short hashtag
  like `#claude`, 6 characters or fewer; it has to fit a narrow column).
- The features chosen, by tier.
- Task source, if any.
- Paths: code folder, data folder, project root, port.
- Safety answers (END, permission mode).
- What you will NOT build in this pass.

Show it. **Wait for a yes.** Then write two more files beside it and keep them true for the life
of the project:

- `CLAUDE.md` or `AGENTS.md` (whichever the person's main agent reads): the runbook. Contracts,
  how to run it, how to restart it, every trap you hit.
- `WORKLOG.md`: one dated, signed entry per session: what you did, what you *verified* versus only
  *assumed*, and what you left unfinished and why.

---

## 3. The architecture: five layers

```
agent CLI ──(its own hooks: SessionStart / UserPromptSubmit / Stop / SessionEnd)──▶ hook script
    │  writes ~/.ai-sessions/inbox/<event_id>.json  (write .tmp, then rename: atomic)
    │  never reads the database, never calls the server, stdlib only, under ~300 ms
    ▼
server (one process, 127.0.0.1 only, no console window)
    ├─ inbox drain, every 2 s ─────────▶ ledger.apply_event() ─▶ SQLite (the ONLY writer)
    ├─ liveness poll, every 5 s ───────▶ each adapter's live() / witness()
    ├─ history import, at start + every 15 min ─▶ each adapter's discover()
    ├─ usage cache, every 60 s ────────▶ each adapter's usage()
    ├─ task-board reconciler, every 20 s (optional)
    └─ HTTP API + one HTML page (the board)
```

1. **Hook** — one script, registered in every agent's own hook config. It turns an agent event
   into a file. That's all.
2. **Ledger** — one SQLite database with one writer. Every other part reads it or asks the writer.
3. **Liveness** — a poll that asks the operating system and each agent's own records who is alive.
4. **Import** — reads every past transcript on disk into the ledger, so history exists from day one.
5. **Board** — the HTTP API and the page.

Stack in the original: Python, Starlette + uvicorn, SQLite in WAL mode, one `index.html` with no
build step, standard-library-only hooks. Any equivalent works (Node + Fastify + better-sqlite3 is
a fine alternative). Keep the page a single file with no build step; it makes every later edit a
one-file change and it reloads without restarting the server if you serve it fresh from disk.

**Everything the manager writes goes in the data folder, never in the code folder, and never in a
cloud-synced folder.** SQLite in WAL mode inside a synced folder corrupts.

Data folder layout:

```
~/.ai-sessions/
  asm.sqlite            the ledger (name it after the person's project if they like)
  inbox/                events waiting to be drained
  processed/<date>/     drained events kept for audit;  processed/<date>/bad/ for rejects
  launches/             one .bat/.sh + .prompt.txt + .pid.json per launch
  agent-status/<agent>/<id>.json   status files for agents that publish none
  agents.json           registry snapshot the hooks read
  hook.log  server.log  server-crash.log  actions.log   (one line per destructive press)
```

---

## 4. The contracts

### 4.1 Identity: decided once, never changed

```
session_id = uuid5(FIXED_NAMESPACE, f"{agent}:{agent_session_id}")
```

Pick one namespace UUID at the start and never change it. Deterministic, so **the hook can mint
the id with the server down**, and every later event for the same agent session lands on the same
row without a lookup. One row per `(agent, agent_session_id)`. Any other tool that needs to point
at a session (alerts, a jump helper) uses the same formula.

### 4.2 The event file (hook → inbox), version 1

```json
{
  "v": 1,
  "event_id": "<uuid4>",
  "session_id": "<uuid5 per 4.1>",
  "agent": "claude | codex | gemini | ...",
  "event": "SessionStart | UserPromptSubmit | Stop | SessionEnd",
  "ts": "<ISO-8601 UTC with milliseconds>",
  "agent_session_id": "<from the hook's stdin>",
  "transcript_path": "<from stdin, or null>",
  "cwd": "<from stdin, else the launch folder env var, else null>",
  "source": "<SessionStart source or SessionEnd reason, or null>",
  "launch_token": "<env LAUNCH_TOKEN, or null>",
  "session_name": "<env SESSION_NAME, or null>",
  "agent_pid": "<pid of the nearest ancestor process whose exe is this agent's, or null>",
  "agent_proc_start": "<that process's creation time as an integer, or null>",
  "ancestors": [{"pid": 0, "exe": "", "created": 0}],
  "nested": false,
  "hook_ms": 12.5,
  "payload": {"...": "the raw stdin object, untouched"}
}
```

Rules: **anything the hook cannot compute is `null`; never omitted, never guessed.** The hook logs
one line per run, including failures. Write to `<event_id>.json.tmp` then rename, so the drain
never reads half a file. `nested` is true when two or more agent executables appear among the
ancestors (an agent started by an agent); nested sessions are hidden by default.

The hook also has two optional jobs at SessionStart:

- **Gate context.** Print the agent's own "inject context" shape so the new session starts
  knowing: its own session id, the project folder, who last worked on the project and when (from
  a "Last worked on" line at the top of the project's instructions file), and the newest two
  entries of the project's `WORKLOG.md`. Keep it under ~4 KB; it is truncated from the end.
- **Log stub.** If the person keeps a session log, write the entry for them (by an insertion
  rule, never at a fixed line number, and never twice).

### 4.3 The ledger

```
sessions(session_id PK, agent, agent_session_id, name, cwd, project, model, transcript_path,
         source, nested, pid_shared, harness, worker,
         started_at, last_seen, ended_at,
         status CHECK IN ('starting','live','ended','lost','killed'), end_reason,
         description, closeout_state, tags, archived,
         turns, first_message, summary, context_tokens, primary_folder,
         UNIQUE(agent, agent_session_id))
runs(run_id PK, session_id, pid, proc_start, launch_token, started_at, ended_at,
     UNIQUE(session_id, pid, proc_start))
events(id PK, session_id, agent, event, ts, payload, ingested_at)
launches(token PK, agent, cwd, name, requested_at, matched_session_id, matched_at, expired)
import_state(path PK, agent, size, mtime, session_id)
meta(key PK, value)            -- schema_version, last_boot
```

`runs` exists because one session can have several process lives (resume) and one process can
carry several session ids (Claude's `/clear` starts a new id in the same process).

**The only status transitions:**

| From | Event | To |
|---|---|---|
| none | SessionStart | `live` (and a `runs` row) |
| none | any other event, no row yet | row created from what the event carries |
| `starting` | SessionStart carrying its launch token | `live` |
| `live` | prompt / stop | `live`, `last_seen` updated |
| `live` | SessionEnd | `ended` |
| `live` | liveness finds no live process for any open run | `lost` |
| `live` | END pressed on the board | `killed` (claimed BEFORE the kill) |
| `ended` / `lost` / `killed` | SessionStart with source resume | `live` again, new run |

`killed` and `ended` are terminal against the poll. A duplicate `event_id` is ignored. The board
shows `killed` and `lost` both as **ENDED**; the difference goes in a tooltip. Never rename a
ledger status to make a friendlier word; map it at the last moment in the page.

**Flags an import may raise but never lower** (`nested`, `pid_shared`, `harness`, `worker`): the
hook watched the process; the import only read a file the process wrote. When they disagree, the
hook wins. An import never overwrites a non-null value with null.

---

## 5. Liveness: a poll, never an event

SessionEnd **does not fire** for a crash, a kill, a reboot, or the person clicking the window's X.
So liveness is a poll, every 5 seconds, of every session with an open run:

- **The PID-reuse guard is the heart of it.** Record the process's creation time when you first
  see it. On every check, compare the recorded time with the live process's real creation time.
  Operating systems recycle process ids; a different creation time is a different process.
  Anything created before the last boot is dead.
- A session with no live run and no SessionEnd becomes `lost`.
- Each agent contributes its own **witness** (its own truth about who is alive), and the poll
  falls back to the pid check when the witness has no opinion. Examples in Part 7.

On Windows, **never test liveness with `os.kill(pid, 0)`** in Python: on Windows that call
terminates the process. Use `OpenProcess` (via `ctypes` or `psutil`). On macOS/Linux,
`os.kill(pid, 0)` is the correct test.

---

## 6. The adapter: how an intelligence plugs in

### 6.1 One registry file is the only list of agents

`agents.toml` (or JSON). Every list of agents anywhere (server, hooks, page, phone, tests) is
derived from it. If you ever find yourself typing a list of agent names in a second place, stop:
make that place read the registry. The original found thirteen hand-written agent lists in the page
alone and had to replace them with one generated block.

```toml
[claude]
display        = "Claude Code"
display_short  = "CLAUDE"
tag            = "#claude"            # <= 6 characters
colour         = "#E10600"            # from the interview
order          = 1                    # left-to-right on the board
mark           = "marks/claude.svg"   # small monochrome currentColor SVG, inlined by the server
exe            = ["<full path or glob to the CLI executable>"]
hooks_file     = "~/.claude/settings.json"
hooks_kind     = "claude"             # which writer the hook installer uses
status_file    = false                # true = agent publishes no live status; our hook writes one
transcripts    = "~/.claude/projects"
resume         = ["--resume", "{id}"]
instructions_path = "CLAUDE.md"
closeout       = "/closeout"          # exact text to TYPE; measured on a live window
closeout_marker = "closeout"          # what proves in the transcript that it ran
harness_roots  = ["<temp dir>", "~/.ai-sessions"]   # sessions here are machine noise
env_markers    = ["CLAUDECODE", "CLAUDE_CODE_ENTRYPOINT"]  # scrubbed from child launches
untested       = false                # true = written from docs; UI dims it until proven
enabled        = true
```

The hooks read a **snapshot** of this (`~/.ai-sessions/agents.json`) because hooks must stay
standard-library-only and fast. The installer rewrites the snapshot every run; the server rewrites
it at start.

### 6.2 The protocol every adapter implements

```python
class Agent(Protocol):
    name: str
    def discover(self) -> Iterable[SessionRecord]        # every past session on disk
    def live(self) -> dict[str, LiveInfo]                  # what is alive right now
    def witness(self, session, runs) -> tuple[bool | None, str]  # None = no opinion, use pid
    def launch(self, cwd, prompt, name, *, model=None, effort=None) -> LaunchRecord
    def resume(self, agent_session_id, *, name=None, cwd=None, model=None, effort=None) -> LaunchRecord
    def end(self, session, runs) -> EndResult              # REFUSES when it cannot prove the process
    def focus(self, session) -> FocusResult                # bring its terminal tab forward
    def type_into(self, session, text) -> TypeResult       # real keystrokes into its tab
    def usage(self) -> UsageReading | None                 # the vendor's own number, never estimated
    def hooks_install(self) -> str                         # Installed | AlreadyInstalled | NotInstalled(reason)
    def models(self) -> list[ModelOption]                  # read from the agent's OWN list
    def efforts(self, model) -> list[str]
    def effort_args(self, effort) -> list[str]             # an unknown word adds NO flag
    def context_window(self, model) -> int
    def preview(self, transcript_path, n=8) -> list[dict]  # last n prose turns {role, text, ts}
    def activity(self, transcript_path) -> dict | None     # newest line of ANY kind: "running a command"
    def scan_transcript(self, path) -> ScanResult          # turns, first prompt, folders, closeout seen
    def search_transcript(self, path, needle) -> list[Hit]
    def env_markers(self) -> list[str]
```

**Anything an adapter cannot know returns the empty value, never a guess.** Model lists are read
from the agent itself (a cache file it maintains, a `models` command, its documented aliases), so
a new vendor model appears with no code edit.

### 6.3 The launcher (shared by every adapter)

Never let a shell parse the person's prompt. The shape that works:

1. Mint a `token`. Write the prompt to `launches/<token>.prompt.txt` (UTF-8).
2. Write a launch script (`.bat` on Windows, `.sh` elsewhere) that sets `LAUNCH_TOKEN`,
   `SESSION_NAME` and `LAUNCH_CWD`, sets the window/tab title to the session name, and runs a small
   Python shim.
3. The shim reads the prompt file and passes it to the agent as **one argv element**. It scrubs
   every agent's "you are nested" environment markers (the union of every registry row's
   `env_markers`) so a session launched from inside a session doesn't think it is a sub-agent.
4. The shim writes `launches/<token>.pid.json` with the agent's pid and creation time.
5. Open it in the person's terminal as a new tab or window (`wt new-tab` on Windows Terminal,
   `osascript` for Terminal/iTerm2, `tmux new-window`, `kitty @ launch`, `wezterm cli spawn`).

**Correlation:** the event carries `launch_token` (fast path); if the environment didn't survive,
the token is also in the wrapper's command line, so walk the new session's ancestors for it
(guaranteed path). Unmatched launches expire after 120 s.

On Windows: in a `.bat`, write `set "NAME=value"` with the quotes, and refuse `& ^ < > | % "` in
folder names with a plain sentence. A process created through WMI inherits no environment, so the
`set` lines must be inside the script.

---

## 7. Adding each intelligence

**Before writing any adapter, read the maker's current documentation** for hooks, transcripts,
resume and usage, and probe the installed version. Tools change monthly. What follows is what was
true for the original build; treat it as a map of where to look, and verify each fact on the
person's machine before coding against it.

### 7.1 Claude Code (the baseline)

- **Hooks:** `~/.claude/settings.json`, events SessionStart / UserPromptSubmit / Stop /
  SessionEnd; stdin carries `session_id`, `transcript_path`, `cwd`, `hook_event_name`. SessionStart
  accepts injected context as
  `{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"..."}}`.
- **Transcripts:** one JSONL per session under `~/.claude/projects/<folder-slug>/<uuid>.jsonl`.
- **Live status for free:** it keeps a per-process registry file (`~/.claude/sessions/<pid>.json`)
  carrying the process start time, kind, entrypoint and status (busy / idle / waiting). Use it as
  the witness. Only count `kind == interactive`; skip SDK entrypoints.
- **Launch:** you can mint the session id up front: `claude --session-id <uuid> -n "<name>"`, plus
  `--model`, `--effort`. Resume: `--resume <uuid>`.
- **Rename / tag:** append an entry to the transcript itself, so every other tool reading the
  transcript sees the same truth.
- **Context reading:** the newest assistant turn's usage block (input + cache creation + cache
  read). A `/compact` writes no usage number: stop scanning at the compact-summary entry and
  report 0, or the bar shows the context that was just discarded.
- **Closeout proof:** the assistant entry records which skill ran; typing the words proves nothing.
- **Usage:** the plan's 5-hour and 7-day windows are available from the vendor's usage endpoint
  using the OAuth token the CLI already stores. Read the token fresh from its file on every call,
  use it in memory only, never write or refresh it (refreshing races the CLI's own refresh).
  Cache 60 s; back off on failure.
- **Trap:** the Claude desktop app runs many processes with the same executable *name*; guard END
  on the CLI's full *path*.

### 7.2 Codex CLI

- **Transcripts:** rollout JSONL files plus a SQLite index of threads under `~/.codex/`. Open the
  index read-only (`?mode=ro`), filter to the person's own threads.
- **It announces itself only on its first turn.** A window opened with no prompt fires no event
  and writes nothing. Fix: a prompt-less launch sends one short fixed opening line ("Say READY and
  wait"), and a poll adopts any window the manager launched that is provably open but unreported.
- **Resume** fires no hook until the first message, so mark the row `starting` and use the
  launch's pid file to open a run.
- **Publishes no live status:** have your hook write `agent-status/codex/<id>.json` (SessionStart →
  idle, prompt → busy, permission request → waiting, Stop → idle, End → removed).
- **Hooks need trust** inside Codex. An installer canary proves it: run a headless one-line prompt
  in a scratch folder and check a SessionStart event lands. Append new handlers; rewriting the file
  keeps already-trusted handlers trusted.
- **Effort is a config override, not a flag** (`-c model_reasoning_effort="high"`), and the levels
  differ per model. Models come from the cache file Codex itself maintains.
- **Usage:** the rate-limit windows are in the newest rollout's token-count events, but only while
  a turn runs; for a live number, the vendor's usage endpoint with the stored token.
- **A companion desktop app** may share its data folder and run many threads in one process: mark
  those `pid_shared`, use the file's modified time for liveness, and refuse END with a sentence.

### 7.3 Gemini CLI / Antigravity CLI (Google)

- **Different hook-file shape** (named hooks, flat lists per event) and it silently ignores a file
  in another agent's shape. One installer writer per shape.
- **Different event names**, and the event name may not be in the payload: pass it as a
  command-line argument per hook slot and translate.
- **The hook must print exactly ONE JSON object.** It reads the first object only; printing `{}`
  and then the real answer means the real answer is lost. Build once, print once.
- **No start event?** Synthesise SessionStart from the first prompt of a conversation (cheap test:
  does a status file exist yet). No end event? The liveness lock going free is the end.
- **It may force the hook's working directory to its own config folder.** Carry the real launch
  folder in `LAUNCH_CWD` and list the config folder as a harness root, or every session is filed
  under the config folder.
- **Liveness:** a presence lock file held while the conversation is live, and never deleted on
  exit. Ask the operating system whether the lock can be taken; never trust the file's existence.
- **Transcripts:** a per-conversation database (the original needed a small protobuf decoder) plus
  a plain JSONL twin; read the JSONL while the database is locked, which is exactly when it's live.
- **Usage:** a quota command with JSON output, in the original; weekly window only on free tiers.
- **First-run terms wizard** swallows keystrokes until a human clicks through it once.
- The "skip permissions" flag also auto-approves leaving any sandbox, so don't advertise a sandbox
  you can't keep.

### 7.4 Grok CLI (xAI)

The original wrote a full adapter from documentation alone and marked it `untested = true` until
the binary was installed, which proves the protocol can be implemented without the agent present.
Follow the generic recipe in 7.5, and keep `untested` true until launch, live and end have been
seen working on a real window.

### 7.5 Any other CLI (Aider, OpenCode, Amp, Goose, and whatever comes next)

Find out, in this order, and write each answer into the runbook:

1. **Transcripts:** where, what format, one file per session or one database?
2. **Hooks:** does it have lifecycle hooks? Their stdin/stdout shapes? If it has none: correlate by
   launch token in the wrapper's command line, and use a liveness witness only.
3. **Liveness truth:** pid file, lock file, writer lock on the transcript, or shared process?
4. **Resume syntax.**
5. **Environment variables it leaves in child shells** (these go in `env_markers`).
6. **Status and usage:** does it publish either? If not, write your own status file from the hook,
   and show an honest empty usage cell. Never estimate.
7. **How its commands/skills are triggered by typed text.** Measure the exact closeout string on a
   live window: some agents swallow a bare `/command` + Enter in an autocomplete picker and need
   trailing words.

Then: registry row, mark SVG, adapter, `hooks_install`, snapshot, and an offline conformance test
that ticks every box a script can see. Only then the live checks.

---

## 8. The board page

One HTML file. Everything agent-specific (chip colours, usage cells, heat-map ramps, legend,
picker chips, brand marks) is **generated from `GET /api/models`**, so the page holds no list of
agents.

Tabs in the original (pick per the interview):

- **Sprint / Board** (front page, if they chose a task board): Plan lane on the left, In Process
  lane on the right. A live card glows while working and pulses slowly while waiting on you.
- **Sessions:** every session, newest first. Columns: agent chip, name, status pill, Resume,
  closed-out badge, project, model, turns, context bar, created, modified. Click a row to expand
  it (first prompt, summary, rename, tag, archive). Search the names, and a DEEP toggle that
  searches inside the transcripts themselves.
- **Projects:** one tile per project the sessions actually worked on, with counts and last worked.
- **Heat map:** a calendar of turns per day, a colour ramp per agent derived from its colour, and
  a split square for days with more than one agent.
- **Admin:** settings (the Interact panel's look, feature switches).
- Optional: a **Routines** tab (Part 10, Tier 3) and a **Chief of Staff** tab.

A **usage band** across the top on every tab: round buttons (refresh, new session, open project
root, broadcast, mailbox), then one cell per agent: its mark, and per window (e.g. 5h, 7d) two thin
bars: **USED** (the vendor's percentage) and **TIME** (how much of the window has elapsed). Red
ahead of white means this pace will not last until the reset. Hover shows the numbers and the
reset time.

Page rules that save pain:

- The 5-second poll repaints only the status pills in place. Never re-render everything on a poll;
  it steals clicks mid-press and restarts animations.
- Re-fetch the board only when the server's digest of it changes, or after a button.
- A live row never offers anything that would open a second copy of a running session.
- One tooltip per element. Remove `title=` attributes and SVG `<title>` elements where you draw a
  custom hover card.
- Show an error sentence only when it *changes*.
- Confirmations are the page's own card, opened at the mouse pointer, with the pointer over the
  words and clear of the buttons. Every caller must `await` it (a forgotten `await` gets a truthy
  Promise and the destructive branch runs unasked).
- "No reading" is a real state, drawn as a dash. Never send 0 for "don't know".

---

## 9. The task board (optional)

Three lists in the person's task tool: **PLAN**, **IN PROCESS**, **COMPLETE**. The board shows two
lanes; Complete is a destination only. Each card's title carries tags in a fixed order:
`TITLE #project #agent #dropped`. The card's body is the prompt.

The source layer needs only eight operations, so any tool works: `read_board`, `find_task`,
`create`, `rename`, `move(to_list, top)`, `move_within(top|bottom)`, `set_agent`, `set_dropped`.
A local JSON file with three arrays is a perfectly good source (write to a temp file, then rename).

**The reconciler is a pure planner plus an applier.** `plan(live, board, links, claims) -> actions`
takes no clock, no network, no database: that is what makes it testable. Actions:

1. A real session with no card, **that has a title**, gets a card in In Process. No title (no
   name, no first message) → no card. Never mint "untitled" cards.
2. A card whose session is live but whose title or agent tag disagrees → rename.
3. A card in In Process whose session is gone → move to Plan, tag `#dropped`, keep the link (that is
   what makes Resume possible).
4. An unlinked card in In Process → adopt it only if exactly one live session plainly matches;
   otherwise drop it.
5. A dropped card whose session is live again → restore it, **unless the ledger already says that
   session ended.**

**Claim before you act.** START, RESUME and END each take an in-memory claim on the card *before*
anything moves, and the planner skips claimed cards. Without it, the loop that reconciles the board
undoes the button that just moved the card. Hold an END claim until the killed session has left
the liveness snapshot, not just until the card is filed.

The first reconciler pass after every server start is a **dry run printed to the log**; arm it
only after the person has seen one. Test against a separate scratch board first. Two managers
writing one board is the forbidden state; enforce it in code.

Buttons: **START** (Plan card → launch with the card's prompt, name = title), **RESUME** (same
agent only: one agent can't read another's transcript), **END** (kill, close the tab, file to
Complete), **SPRINT** (kill, send back to Plan as dropped, resumable), **DONE** (file to Complete,
touches no session), **PROMPT** (show the card's prompt), and a **closeout badge** with three
states: closed / open / unknown. Unknown never collapses into either.

---

## 10. The feature catalogue, by tier

Offer these in the interview. Build a tier fully (with its checks) before starting the next.

### Tier 1 — core (always)
- Hook + ledger + import + liveness; the Sessions tab; statuses for every agent.
- New Session: agent, model (served from the agent), effort, folder, name, prompt. Enter sends;
  Shift+Enter is a new line.
- Resume, Focus / Jump, End (with confirm; kills only a proven process; closes the tab).
- Usage band, never estimated.

### Tier 2 — the useful half
- Context bar per session (the agent's real window size per model), pulsing past a threshold.
- Projects tab: attribute a session to the project it **worked on** (folders named in the
  transcript), not the folder the window was opened in. Most people open everything in one parent
  folder; attributing by cwd piles every session onto one tile.
- Heat map.
- Deep transcript search, bounded by time, count and bytes, run off the main thread.
- Closeout badge: *closed* only when the agent's own transcript records that the closeout skill ran.
- Hide the noise: sub-agent threads, headless runs, sessions in temp or harness folders, and
  sessions where nothing happened. One definition, used by every surface.

### Tier 3 — power features
- **Task board** (Part 9).
- **Interact panel:** click a live card's name to read its conversation (prose only, polled while
  open) with an activity footer ("running a command · just now"), and reply to it without switching
  windows. Reply through a mail file the session reads, not by typing keystrokes: typing needs the
  foreground and one typist at a time; mail needs neither. Paste screenshots (saved by the hash of
  their bytes), attach a file or folder path via the OS's own chooser opened by the server.
- **Broadcast:** one message to every live session at once, with an untick list that never
  remembers last time's exclusions.
- **Agent mail + mailbox:** sessions message each other through a local mail folder; a read-only
  log of every message; a small badge on a card that just sent or received.
- **Timed launches:** "launch this in 1h 06m", with the countdown kept by the server (store a due
  time, never a duration; remove it from pending before firing it).
- **Routines tab:** one card per scheduled job on the PC, healthy / failed / late / manual /
  unknown, read only from each job's own log. The operating system's scheduler lies about success.
- **Named agents:** personas with their own role, model and memory, launched with the agent's own
  `--agent` mechanism; e.g. a chief of staff that briefs, a dev agent that runs missions, a
  PC-care agent.
- **Terminal integration:** a per-status glow on each session's terminal, window tiling, and
  highlighting the card of whichever terminal tab is in front.
- **Workers:** a session started by another session always gets a named card with an END button.
  No hidden agent may burn tokens unseen.

### Tier 4 — remote
- Phone access (Part 11).

---

## 11. Phone access (optional, advanced)

Recommend "later" unless the person already runs a website and a tunnel. The shape that is safe:

phone browser → a page on the person's own website, behind their existing sign-in → a same-origin
server route that holds a secret key → an **outbound-only tunnel** from the PC → a **separate gate
process** on its own loopback port → the real server on loopback. The browser never talks to the PC.

Rules: allow-list every path on **both** ends; **rebuild request bodies from named fields, never
forward them**; answer an identical 403 for every refusal a stranger could probe; compare keys in
constant time; nest timeouts (outer > middle > inner, or one tap becomes two sessions); size-cap
every field; resolve folders inside a configured root with a real path comparison
(`commonpath`, never `startswith`); keep window-raising and file-serving routes off the phone.
Put two-factor sign-in on every account in the chain. The key is the thing between the internet
and code execution on their machine; say so.

---

## 12. Build order and gates

Each phase ends with a gate: the checks pass, and you show the person something working. Record
each gate in `WORKLOG.md` as **Verified** (you ran it and saw it) or **Assumed** (you didn't).

1. **Spec** (Part 2). Gate: the person's yes.
2. **Ledger + hook for the main agent, offline.** Gate: an offline test feeds canned events (a
   start, a resume on the same id, a `/clear`, a crash, a duplicate file, a Stop before its Start,
   a nested session) and the ledger ends in the right state.
3. **Import + liveness + Sessions tab, read-only.** Gate: every past session appears; opening and
   closing a real session changes its status within 5 seconds, including closing the window with X.
4. **Launch / resume / end / focus for the main agent.** Gate: launched from the page, visibly
   named, resumed, ended, and its tab gone.
5. **Each additional agent** (Part 7): registry row, adapter, hooks, conformance test, then the
   live checks on a real window. Gate per agent.
6. **Usage band, context bars, projects, heat map, search, closeout.** Gate: numbers match what
   each agent itself shows.
7. **Task board** against a scratch board, dry runs first, then the real one. Gate: START, RESUME,
   END, SPRINT, DONE each exercised on a scratch card.
8. **The rest of the chosen features**, one at a time.
9. **Autostart** at login (Task Scheduler / launchd / systemd user unit), no console window.

Restart the server by **the process that owns the port** (find the pid listening on it, stop that
pid, start the same command line again). Never by matching a window title or a command-line
substring: that has killed every session on a machine, twice.

---

## 13. The traps

Each one was a real defect. The rule is what prevents it.

1. **Sessions never marked ended.** SessionEnd doesn't fire on crash, kill, reboot or the window's
   X. → Liveness is a poll; events are hints.
2. **The wrong process treated as the session.** Process ids are recycled. → Compare creation
   times on every check, and again on the handle you are about to kill.
3. **END killed the wrong thing.** A desktop app ran dozens of processes with the same exe name. →
   Guard on the full executable path.
4. **END reported success, the tab stayed open.** The shell's console host outlived it. → Assert
   the consequence, not the action: read the terminal's tab titles before and after, close the one
   exact match by its own Close button, refuse if two tabs share the name, and judge that
   ambiguity **before** the kill.
5. **Closeout typed into the wrong session.** One terminal process owns every window, so "the
   window of an ancestor process" is a coin toss. → Find terminal windows by exact unique title.
6. **A bare Enter landed in whatever window was in front.** → Refuse empty text before focusing
   anything.
7. **Hook fired, server missed it.** The server was down. → The hook mints the id itself and only
   writes a file.
8. **Hooks installed, never fired.** Wrong hook-file shape for that agent, parsed silently and
   ignored; or quotes around the command under `cmd /c`. → One writer per shape; no outer quotes;
   prove a real event lands.
9. **Gate text never arrived.** The hook printed two JSON objects; the agent read the first. →
   Print exactly one.
10. **Every session filed under a config folder.** The agent forced the hook's cwd. → Carry the
    launch folder in an env var; null beats a guess.
11. **Child sessions thought they were sub-agents / had no transcript / took the parent's name.**
    Inherited environment markers. → Scrub every agent's markers on every launch.
12. **Junk "untitled" cards flooding the board.** Headless helper runs, SDK entrypoints and scratch
    sessions were counted. → A `driven()` filter; never mint a card without a real title.
13. **An ENDed card bounced back onto the board.** The sweep saw the killed session in a 5-second-old
    snapshot and "restored" it. → Hold the END claim until it leaves the snapshot; never restore a
    card whose session the ledger calls ended.
14. **A second window opened on an error.** A retry caught `TypeError` to detect an old adapter
    signature, and a `TypeError` inside launch retried the whole launch. → Inspect the signature;
    never catch to detect.
15. **Wrong model on resume.** A default was guessed. → An absent key means "whatever it had".
16. **The closeout badge said CLOSED on 182 of 239 sessions that weren't.** It inferred closure from
    a written description. → Use the agent's own record that the skill ran; three states.
17. **The context bar said 13% right after `/compact`.** The scan walked past the compact. → Stop at
    the compact boundary and report 0.
18. **Hundreds of sessions filed under one project.** Attributed by cwd. → Attribute by what the
    transcript worked on; exclude skills every session runs; break ties alphabetically.
19. **A usage number went hours stale.** It was read from a transcript that only records it during
    a turn. → Read the vendor's live number; an expired window shows 0% and "unknown reset".
20. **Somebody estimated usage.** → Vendor's number or nothing. "Installed, publishes nothing" is
    its own honest state.
21. **The whole server froze during END.** Process and window walks ran on the event loop. → Run
    them in a worker thread; take claims before the await; never hold a lock across one.
22. **A folder with `&` in its name ran a stray command.** → Refuse shell metacharacters in launch
    folders with a sentence; prompts travel in a file, never on a command line.
23. **Launching stole the screen.** The terminal activates when it adopts a new window. → Record
    the foreground window before spawning and give focus back.
24. **A console window flashed per helper.** A windowless server's children get new consoles. →
    Spawn every helper with no window (`CREATE_NO_WINDOW` on Windows).
25. **SQLite corrupted.** WAL database in a synced folder. → Data outside any synced folder.
26. **Restarting the server killed the wrong process.** Matched by title. → By port owner only.
27. **A config change "didn't work".** It was read at import time. → Restart after changing
    anything read at startup, and say so.
28. **A timer could double-fire.** Fire-then-mark. → Remove from pending, then fire.
29. **Phone and PC countdowns disagreed.** Different clocks. → Send durations, never instants.
30. **The routines tab called a crashed app healthy.** "In the startup folder" isn't "running". →
    The process table is the truth for services; each job's own log is the truth for jobs.
31. **A monitor could break what it watched.** → Reader modules have no write path, and a test
    greps them for write calls.
32. **A picker never showed the new agent.** One picker's agent row was hard-coded. → Everything
    from the registry payload.
33. **The new agent's heat-map days were painted in another agent's colour.** The colour fallback
    was the first agent's. → Fallbacks are neutral grey.
34. **Resize corners vanished on rounded skins.** An overflow clip follows border-radius. → Put
    handles in their own overlay; test every corner of every skin.
35. **Clicks in the bottom-right corner did nothing.** An invisible toast container sat over them.
    → `pointer-events: none` on notification layers.
36. **A confirm ran its destructive branch unasked.** Missing `await`. → Every caller awaits.
37. **A browser test pressed a real button.** A scratch check clicked the status pill, which is
    Jump, and another pressed a closeout button on a real session. → **Browser checks block every
    non-GET request** at the network layer and click only elements they name exactly.
38. **A read-modify-write script emptied the runbook.** A failed write had already truncated the
    file. → Edit docs with an edit tool, never a script that reopens the file for writing.
39. **A capability was built and forgotten.** A session later said it was impossible. → Write every
    capability into each agent's always-on instructions file, in the same breath as building it.
40. **The person's dictation tool rewrote their request.** A layout pass was built to words they
    never said. → When a long dictated prompt reads oddly, ask which parts are theirs.

---

## 14. How to verify without breaking the person's machine

- **Offline verifiers first:** one per module, a temp database, temp folders, stubbed HTTP, a
  stubbed mail system. They run in seconds and touch nothing real.
- **Live checks use scratch folders and scratch cards,** deleted in a `finally`.
- **Browser checks** (Playwright or similar) run headless against the live board, **abort every
  non-GET request** at the route layer, and never click a status pill, END, CLOSEOUT or RESUME on a
  real session.
- **Size the check to the change.** A one-line edit gets a one-line check (read it back, parse it).
  Never run a broad suite to verify a narrow change.
- **Prove the consequence.** "END returned ok" is not proof; the window being gone is.
- Write every result into `WORKLOG.md` as Verified or Assumed.

---

## 15. Hand-off

When you finish a session of work on the manager:

1. Update the runbook (`CLAUDE.md` / `AGENTS.md`) so it is true: contracts, how to run and restart
   it, and every new trap.
2. Append a dated, signed `WORKLOG.md` entry: what you did, what you verified, what you assumed,
   what is unfinished and why. Write it for a reader who has only the file system: another agent
   may pick this up next, and it cannot see your conversation.
3. Put a one-line "Last worked on: <date> by <agent>" under the runbook's title.
4. Tell the person, in plain words, what now works and what they can try.

Build it well. They will be looking at it every day.