trace-canvas
README.md
<p align="center">
<a href="README.md"><img alt="English (this page)" src="https://img.shields.io/badge/English-15181C?style=for-the-badge"></a>
<a href="README.zh-CN.md"><img alt="简体中文" src="https://img.shields.io/badge/%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87-0072CE?style=for-the-badge"></a>
</p>
<p align="center">
<img src="site/favicon.svg" width="96" alt="Trace Canvas logo: a red line with a blue and a green line forking from it, stations drawn as white circles">
</p>
<h1 align="center">Trace Canvas</h1>
<p align="center"><b>A map both people and agents can read.</b></p>
<p align="center">
<a href="https://github.com/CallSymmetryEcho/trace-canvas/releases"><img alt="Latest release" src="https://img.shields.io/github/v/release/CallSymmetryEcho/trace-canvas?label=release&color=0072CE"></a>
<a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-00A651"></a>
<a href="#just-the-page"><img alt="Node.js 18.17 or newer" src="https://img.shields.io/badge/node-%E2%89%A5%2018.17-339933?logo=nodedotjs&logoColor=white"></a>
<a href="package.json"><img alt="No dependencies" src="https://img.shields.io/badge/dependencies-0-00A0B0"></a>
<a href="#mcp-tools"><img alt="MCP server" src="https://img.shields.io/badge/MCP-server-8E3A96"></a>
<a href="#several-agents-on-one-machine-claude-code-codex-gemini-cli"><img alt="Works with Claude Code and Codex" src="https://img.shields.io/badge/works%20with-Claude%20Code%20%C2%B7%20Codex-E4002B"></a>
<a href="https://github.com/CallSymmetryEcho/trace-canvas/commits/main"><img alt="Last commit" src="https://img.shields.io/github/last-commit/CallSymmetryEcho/trace-canvas?color=5B6B7F"></a>
</p>
<p align="center">
<a href="https://trace-canvas.bin-lian.com"><b>Promo site</b></a> ·
<a href="https://github.com/CallSymmetryEcho/trace-canvas/releases/tag/v0.1.0">Videos</a> ·
<a href="docs/why.md">Why it exists</a> ·
<a href="CHANGELOG.md">Changelog</a>
</p>
Every AI coding session becomes a line on one transit map: your instructions, edits, commits and decisions are its stations, forks and merges are drawn like git, and your agents read the same map to pick up where another left off. Runs locally, needs only Node.js 18.17+, MIT.
**Install: paste this into Claude Code** (or Codex):
> Install Trace Canvas: clone https://github.com/CallSymmetryEcho/trace-canvas.git into ~/trace-canvas, then run `node ~/trace-canvas/bin/trace-canvas.mjs setup --user` (add `--agents codex` if Codex is installed).
**Or by hand:**
```bash
git clone https://github.com/CallSymmetryEcho/trace-canvas.git ~/trace-canvas
node ~/trace-canvas/bin/trace-canvas.mjs setup --user # hooks, MCP tools and the /trace-canvas skill
```
**Then start a new Claude Code session and type:**
```
/trace-canvas put all my sessions on the canvas
```
It puts this project's past sessions on the map and opens it at <http://localhost:4777/>. From then on every new session lands on it by itself.
**Updating:** `trace-canvas update` pulls the newest version into the clone (a fast-forward; it stops if your own changes are in the way) and restarts the page server. The page checks for a newer version at start and twice a day and offers it under **More** (the button gets a dot); `trace-canvas update --check` only says whether there is one.

## What you see
A transit map of your work. When you work with AI agents, ideas and tasks run in parallel and scatter across sessions. Trace Canvas keeps a trace of them and shows how they relate:
- A **thread** is one piece of work in progress, like a git branch. It is drawn as a line.
- A **record** is a station on that line, like a commit: an idea ○, a task □, agent output ◆, a decision ◉ or a note •.
- **Fork** a new thread from any record and **merge** it back when it is done. Forks and merges are drawn as interchanges.
- The **inbox** holds loose ideas until you file them.
**One entry point for your agents.** Agents can read the same map: through the MCP tools `trace_list_threads` and `trace_read_thread`, a new Claude Code session catches up on all your projects in seconds instead of rereading old chats. Hooks and the `/trace-canvas` skill keep the map up to date while you work.

Videos: [2-minute tutorial](https://github.com/CallSymmetryEcho/trace-canvas/releases/tag/v0.1.0) in English and Chinese.
Promo site: [trace-canvas.bin-lian.com](https://trace-canvas.bin-lian.com) ([`site/`](site/README.md)), one page with a tour along a growing branch graph, the feature clips and the videos (preview with `python3 -m http.server -d site`).

*Branches: running sessions sit as live tips on top of their lines. The orange one waits for you; once approved it turns green, your decision appears dark and the agent's result in the line's colour. Made-up data.*
In the Branches view, **Show** next to the lines' legend keeps only the kinds you pick (say decisions and notes): other records drop out, the lines stay whole and the matching stations get a halo; the canvas and the timeline dim the rest instead. The legend keeps the lines on one row (**All N** opens them). Click a line, or its chip, to bring it forward and open its panel; click it again to let go. Columns are reused the way `git log --graph` does it, so a line that has ended gives its column to a later one, and a fork sits next to the line it came from.
Four views of the same data:
| View | What it shows |
| --- | --- |
| Canvas | One card per thread with a mini line map. Drag, zoom, draw labeled links, and select several threads to merge them. |
| Branches | A git graph: every record in time order, one lane per thread, forks and merges as curves. |
| Timeline | A time axis (day / week / month). One row per thread; the band shows when it was active. |
| Agents | The running sessions as a tree: who started whom, and each session's subagents (see [Agent view](#agent-view-who-started-whom)). |

*Timeline: day, week and month. Each band shows when a line was active; dashed curves are forks and merges. Made-up data.*
The page is in English by default. Switch with the **中文 / EN** button in the header. **More → Theme** switches between matching the system, light, dark and Mist, a soft blue-grey palette that also tones down the lines' colours.
**What is it good for?** Three scenarios with made-up data (four agents building an app in parallel, three weeks of a PhD student's life, one analysis spread over three agents and two computers), the advantages for vibe coding and AI collaboration, and the open problems: [docs/why.md](docs/why.md).
## Just the page
To look around before setting anything up: `node bin/trace-canvas.mjs` in the clone starts the page at <http://localhost:4777/> (no `npm install`). Click **Load example data** on the empty page for a first look, then **Clear** it from the header. Data is stored in `~/.trace-canvas/data.json`.
The `trace-canvas` command: `setup --user` links it into `~/.local/bin` or `~/bin` when one of them is on your PATH. Otherwise it prints an alias line with your clone's path, like this one for a clone in `~/trace-canvas`:
```bash
echo "alias trace-canvas='node \"$HOME/trace-canvas/bin/trace-canvas.mjs\"'" >> ~/.zshrc
```
## Three ways Claude Code fills the canvas
| | When it happens | What you do |
| --- | --- | --- |
| **Hooks** | Automatically, at key moments of every session | Nothing |
| **`/trace-canvas` skill** | When you ask | Type `/trace-canvas`, or say "put this session on the canvas" |
| **MCP tools** | Whenever Claude decides it fits, or the skill calls them | Nothing; Claude uses them |
One command, run from this folder, sets up all three.
For every project on this computer, run once:
```bash
node bin/trace-canvas.mjs setup --user
```
This adds the hooks to `~/.claude/settings.json`, registers the MCP server with `claude mcp add --scope user`, links the skill into `~/.claude/skills/`, and lets the capture, log and read tools run without asking. Merging and importing always ask first. Use `--project <dir>` instead of `--user` for one project only; Claude Code then asks once, in that project, to approve the `trace-canvas` MCP server.
### Hooks: automatic traces
| When | What gets logged |
| --- | --- |
| The first instruction of a session | a **task** with the full prompt |
| A turn that changed files | **agent output**: the first sentence of Claude's reply, the files changed and the to-dos completed |
| `git commit` | **agent output**: `Commit 1a2b3c4: message` |
| `git push` | a **note**: `Pushed main` |
| `gh pr create` | a **decision** with the pull request link |
| `git merge <branch>` | a **merge** on the current thread; the branch thread is marked merged |
| The session ends | a **note** summarizing duration, prompts, files and commits, with `claude --resume <id>` |
Records go to one thread per repository and branch. Working on `main` in a repository `my-thesis` logs to `my-thesis`. Switching to a branch `lit-review` forks a new thread `my-thesis · lit-review` from where you were, so parallel sessions on different branches land on different lines. Turns that only answer a question are not logged. Files written from the shell (`>`, `tee`, `sed -i`, …) count as changes.
**History from before.** A repository's new line starts where the repository started, not at the first session the hooks saw. In the background it takes in the default branch's commits (first parent, so the line stays one line; the newest 500, with the older ones as one record where the line begins) and, as one record each, the Claude Code and Codex sessions that ran in the repository on this machine; they also join the line's sessions, so they can be resumed. A branch line takes the commits only that branch has. A commit already on the canvas is not taken in twice: by its sha, or by its subject and author time once a rebase has given it a new sha. Records get the same ids on every machine, so syncing does not double them. Afterwards the line catches up on its own (at most every 10 minutes, when a session there gets an instruction). For lines from before this, `trace-canvas history` lists what each line would take in, `trace-canvas history --all` takes it in, and `trace-canvas history <line> --off` leaves a line out.
### `/trace-canvas`: put this session on the canvas
| You type | What happens |
| --- | --- |
| `/trace-canvas` | Claude writes 2–6 highlights of this session (decisions, outcomes, next steps), a short summary and a name, and adds them together with the key moments read from the session transcript. |
| `/trace-canvas sessions` | Lists this project's past sessions and which are already on the canvas, and offers to add any of them. |
| `/trace-canvas merge <threads…>` | Merges sessions or threads. Claude confirms the sources, the target and the mode with you first. |
| `/trace-canvas open` | Starts the page and opens it. |
| `/trace-canvas log <text>` | Adds one record. |
Plain language works too: "put this session on the canvas", "把这次会话放进画布", "merge the two survey sessions".
Capturing is safe to repeat: records already on the canvas are skipped. If the hooks already logged the session, the capture completes that thread; ask for "its own line" (`separate`) to pull the session out into a thread of its own.
### MCP tools
| Tool | What it does |
| --- | --- |
| `trace_capture_session` | Puts the current session (or a past one by `session_id`) on the canvas, with the highlights, summary and name Claude passes |
| `trace_list_sessions` | Lists Claude Code sessions of a project from their transcripts |
| `trace_list_threads` | Lists threads with status, record count, last update; `query` searches |
| `trace_read_thread` | Returns the records on one thread |
| `trace_merge` | Merges any number of threads, in `merge` or `combine` mode (below) |
| `trace_fork` | Branches a new thread off a record or a thread's newest record; `bind: true` puts the calling session on it |
| `trace_log` | Adds one record |
| `trace_import_data` | Merges another data set: an export, `data.json` from another computer, or JSON lines |
| `trace_live` | Lists the sessions running now, with status, activity and thread |
| `trace_bind` | Puts a running session (the current one by default) on a thread |
| `trace_tasks` | Returns the goal and the to-dos of the line the session works on (`thread` for another, `all: true` for every line), with how many are done |
| `trace_task_done` | Ticks a to-do (or reopens it) and records who ticked it |
| `trace_where` | Looks things up in the location map (`~/agent-map`): repositories, data folders, NAS paths, and which machine has them where |
| `trace_open` | Starts the page if needed and returns its address |
## Live sessions
The local page shows which Claude Code sessions are running right now. A bar under the header lists them, the ones waiting for you first:
| Status | When |
| --- | --- |
| **Working** (green) | Claude is acting; the bar shows the current step, for example `Edit hook.mjs` or `$ npm test` |
| **Needs you** (orange) | A permission prompt, a question for you (`AskUserQuestion`), a plan to approve, or a question from an MCP server |
| **Idle** (grey) | The turn is done; the bar shows the first sentence of Claude's reply |
In every view, stations you wrote (your instructions and decisions) are drawn dark with a person mark, and agents' stations in the line's colour with a star mark, so you can see who moved each line forward. In the Branches view, a session working on a line right now sits at the top of that line's lane as a pulsing tip (green working, orange needs you), joined to the line's newest record; click it for the session panel. Each card shows how many sessions are running on its thread. Click a session to see its repository, branch, folder and latest instruction, copy `claude --resume <id>`, or put it on another thread. A session the Claude desktop app runs also has **Open the chat** (↗ in the Agent view), which takes you straight to it in the app; a Codex session opens in the Codex app, and one running in Terminal or iTerm2 on a Mac has **Go to its terminal**, which brings its tab to the front. A session lands on its repository/branch line by itself; to bind it by hand, pick a thread in the page, run `trace-canvas bind "<thread>"`, or ask Claude (`trace_bind`). From then on its records go to that thread too.
A session stays on its line. The desktop app ends sessions and resumes them under the same id; the line each session is on is kept in the canvas data (not in the session's own state), so a resumed session goes on where it was and does not log its first instruction again. Each thread panel lists **Sessions on this line**, running ones first, each with a button that copies the command that resumes it from its folder (`cd … && claude --resume <id>`, `codex resume <id>`). The same list comes from `trace-canvas resume "<thread>"` and from `trace_read_thread`, so you or another agent can pick a line's session up again instead of starting a new one. When you combine lines, the line you keep takes over the folded lines' repository key and ids, so sessions in that folder, bound or not, land on it instead of on a re-created folder line.

Live status needs the SessionStart, PreToolUse, Notification and SubagentStart/Stop hooks, which `setup` adds (run it again after updating). It lives in small files in `~/.trace-canvas/live/` (one per session and one per subagent), apart from your records. A session drops off when it ends, or after 12 hours without activity. Only the local page shows live sessions.
### Agent view: who started whom
The **Agents** tab draws the running agents as a tree, left to right. Each session starts the sessions to its right; the small dashed cards are its subagents (Explore, general-purpose, forks, …), green while they run. A session that started others but is no longer running stays in the chain as a dashed "not running" card, and the tree that holds a session needing you comes first. Click a session for the same panel as in the live bar, now with who started it, the sessions it started and its subagents with their last message.


*A session waiting for approval turns green once approved, a subagent finishes and fades, and the panel shows who started the session. Made-up data.*
Subagents come from the SubagentStart/Stop hooks and the `.meta.json` file Claude Code keeps next to each subagent transcript. Which session started which comes from the Claude desktop app's own session records (`~/Library/Application Support/Claude/claude-code-sessions/` on macOS, `~/.config/Claude/claude-code-sessions/` on Linux, `%APPDATA%\Claude\claude-code-sessions\` on Windows), read-only and best-effort: that format belongs to the app and may change, and without the desktop app every session simply starts its own tree. `TRACE_CANVAS_DESKTOP_DIR` points it somewhere else. The made-up sessions in the screenshot come from `node docs/scenarios/agents.mjs <data dir> <desktop dir>`.
## Goals and to-dos: working with agents asynchronously
**Goals** in the header opens one list of what you want to achieve and how far along it is, without adding anything to the main page:
| | What it is | Where it lives |
| --- | --- | --- |
| **Goal** | The outcome a line is working towards | The line's "Goal and context" field |
| **To-do** □ | A concrete next step that you or an agent can take, ticked when done | A task record on the line |
| **Progress** | What actually happened | Agent output, commits, decisions |
| **Inbox** | Loose ideas you have not thought through yet | The inbox |
The list groups to-dos by line, shows each line's goal, a progress bar (done/total) and whether a session is working on it right now. Tick a to-do, add one or click a line's goal to edit it right there; on a phone, tap a to-do to edit it. The first instruction of each session, which the hooks log as a task, is a record of what was asked, so it stays out of the list; next steps Claude writes when capturing a session do show up.
Agents use the same list. At the start of work they read the goal and the open to-dos with `trace_tasks`; when they finish one they call `trace_task_done`, and the list shows who ticked it. So you can write down what you want, let agents work while you are away, and come back to see where things stand. New ideas get on the list as you say them: with every message, the hook tells the agent which line it works on and what is already open, and the agent adds each new goal or next step with `trace_log` (`kind: "task"`); the same open to-do twice stays one. From the command line: `trace-canvas tasks` and `trace-canvas done "<to-do>"` (`--agent Codex` for a script or another agent).

*Agents tick to-dos off on the shared list, one by Claude Code and one by Codex, and the line's progress bar moves forward. Made-up data.*
**Lines that have gone quiet.** A line nobody has touched for three times its usual gap (at least three days), with no session on it, gets a small dot on **Goals** and a chip under **Gone quiet** at the top of the list; nothing pops up. Click the chip for one sentence and three answers: a line with no open to-dos is asked whether it is finished (**Done**, **Parked**, **Still on it**), one with open to-dos whether to park it. **Still on it** counts as activity, so it asks again only after another quiet spell.
## Several agents on one machine: Claude Code, Codex, Gemini CLI
Claude Code, Codex and Gemini CLI can share the canvas and split the work through to-dos:
```bash
node bin/trace-canvas.mjs setup --user --agents codex,gemini
```
This adds the hooks to `~/.codex/hooks.json` and `~/.gemini/settings.json` (after any hooks already there, with a backup) and registers the MCP server with each CLI. Codex runs hooks inside its `workspace-write` sandbox, which blocks writes outside the project, so setup also adds `~/.trace-canvas` to `writable_roots` under `[sandbox_workspace_write]` in `~/.codex/config.toml` (with a backup). Start Codex once afterwards and trust the new hooks when it asks. CLIs that are not installed are skipped.
For Codex, setup also links the skill into `~/.agents/skills/trace-canvas`. Invoke it with `$trace-canvas`, for example `$trace-canvas tasks`. To add just the skill to an existing installation, run `node bin/trace-canvas.mjs install-skill --user --agents codex`. With `--project <dir>`, that command installs it into `<dir>/.agents/skills/trace-canvas`. The skill stays linked to this clone, so pulling updates refreshes it too.
- **Who does what:** each to-do in **Goals** has a "who does it" choice (Claude Code, Codex, Gemini CLI or anyone). Agents read their own with `trace_tasks` (`mine: true`) and tick them with `trace_task_done`, and the list shows who ticked each one.
- **Hand it over:** **Hand to it** next to an assigned to-do copies a command, and so does `trace-canvas hand "<to-do>" --to codex`. The command starts that agent in the line's folder with `TRACE_CANVAS_THREAD` set, so the new session lands on the line. On macOS and on a Linux desktop, **Open in Terminal** next to it runs the command in a new terminal window (Terminal on macOS; on Linux `$TERMINAL`, else Konsole, GNOME Terminal, kitty and the like, on the desktop's display even when the server was started over ssh). An agent can also take its next to-do by itself: `/trace-canvas pick` (`$trace-canvas pick` in Codex).
- **Dispatch:** tell one agent to dispatch (`/trace-canvas dispatch`, also from the Claude app on your phone) and each line's new to-dos go to the agent working on that line: the session that last did work there (a session merely ending there, logging to-dos for it from another line, or bound to another line since does not count, and one that only logged a result there from another line counts only when the line's own sessions have not worked there). Worked on from several machines, a running session comes first (this machine's before another's), then this machine's latest, then the latest anywhere. A to-do for a particular machine (`trace-canvas dispatch --on bsuperstation`, or `on` when it is logged) goes to that machine's latest session on the line, or a new one started there over ssh. A running Claude Code session gets a message from that agent, a stopped session is resumed with its to-dos, and a line nobody works on gets a new session (Claude Code with Remote Control, so it shows in the Claude app); on macOS and on a Linux desktop each opens in a new terminal window. New means nobody has it and that agent does not know it yet: you logged it (on the page, the phone or the command line) or another session did. Once handed out, a to-do has its agent and is not handed out again (`--all` also takes the ones the line's agent logged itself). Name a to-do instead of a line to hand out just that one, whoever logged it. A to-do assigned to an agent (Claude Code, Codex or Gemini CLI) goes only to that agent's sessions on the line, or a new one of it, and keeps its assignee; automated sessions such as a Codex `<heartbeat>` never count as the line's agent. From the command line: `trace-canvas dispatch [line|to-do…]`, with `--print` to only print the commands (nothing is assigned or saved).
- **Back to the conversation:** a record an agent logs keeps the session it was logged in. Its panel shows it under **Logged in** (open the chat or copy the resume command), and `trace_read_thread` gives it as `from`.
- **A head start:** when any of the three starts a session, the hook tells it which line it works on, the line's goal, the to-dos assigned to it, who else is working there, and (with the location map) where things are on this machine.
- Records, live tips and the Agent view show which agent did what. Each session gets its own resume command: `claude --resume`, `codex resume` or `gemini --resume`.
Codex has no notification event, so a permission request stands in for "needs you". Checked against real Codex 0.164 sessions (`test/fixtures/codex/`): prompts, shell commands, `apply_patch` edits, commits, permission requests, the context at session start and ticking a to-do over MCP. Gemini CLI support follows its documented hook events and will be checked against a current `@google/gemini-cli`.
## Several machines, one canvas
Your laptop, a workstation and a lab PC can share one canvas through a private git repository all of them can reach. Give each machine a name and the repository's clone:
```bash
node bin/trace-canvas.mjs setup --user --machine laptop --map ~/agent-map
node bin/trace-canvas.mjs sync
```
Each machine writes only its own `canvas/<machine>.jsonl` there: every line, record, link and session with the time it last changed. Syncing pulls, takes the newest version of each document from the other machines' files, writes this machine's file and pushes it. Run it with `trace-canvas sync` or **More → Sync with other machines**; once a machine has synced by hand it also runs in the background after a session ends and every 2 minutes while the page server runs, so what one machine logs shows on the other within a couple of minutes (an open page refreshes by itself). Without a connection the sync says so and pushes next time.
- **What travels:** a session's first instruction goes as its title only, and public IP addresses and secret-looking text are blanked. Your own data keeps everything.
- **One repository, several lines:** `trace-canvas folder <line> <folder>` gives a folder of a repository to a line (say `proposal/` to the proposal, `cv/` to the job search): sessions working in it go there, the deepest claim winning, and the rest stay on the repository's line. The claim is kept as the repository's name and a path inside it, so it holds on every machine. A session bound to a line by hand stays there.
- **One repository, one line:** a repository's line is named after its remote, so the same repository is one line on every machine, whatever its folder is called. Lines that belong to a repository get the same id everywhere; an old id stays as an alias, so bindings and tools that use it still work.
- **Whose is what:** a group on the canvas names the other machines that wrote on its lines (**from bsuperstation**; this machine is never named, and single cards stay plain); in **Branches** each station from another machine has its name next to the agent; a line's panel marks each machine's latest session (**bsuperstation · latest**) when the line was worked on from more than one, a record from another machine says so in its panel, sessions show their machine, and their resume command runs over `ssh -t <machine>` in the login shell there (plain ssh has no `claude` or `codex` on its PATH), so name machines after their ssh aliases. To reach a Mac this way, turn on **System Settings → General → Sharing → Remote Login → ⓘ → Allow full disk access for remote users**: without it, macOS keeps ssh sessions out of Desktop and Documents, where projects usually live.
- **Running elsewhere, live:** turn it on under **More → Other machines' sessions** (it is off by default and offered once the canvas knows another machine). While a page is open, the server then reads each other machine's running sessions every 15 seconds with `ssh <machine> trace-canvas live --json` (keys, no password: it uses `BatchMode`), so the live bar shows them with their machine's name; their panel offers the ssh resume command. **Dispatch** also reaches a line whose agent is on another machine: a stopped session is resumed there over ssh; one running there is left to you, since a message cannot reach it. Each machine needs the `trace-canvas` command, which `setup --user` makes.
- **A new machine:** if the repository also holds a location map (`tools/where.py`, which knows where code, data and NAS folders live on each machine), a session there is told where things are, `trace_where` and `trace-canvas where <words>` look things up, and a `bootstrap.sh` next to it can set a machine up in one go. `setup` refuses a name another computer already writes under.

## See what changed
A commit station opens with a **Changes** list: every file the commit touched, the biggest change first, with the lines added and removed; lock files, build output and binary files are folded at the end. Click a file for its diff. **Compare with** picks another commit station on the line or in the same repository and shows what differs between the two. An agent's turn that changed files points to the session's next commit.
Nothing is copied into the canvas: a record keeps the commit's sha and its repository, and the page asks git when you open it, so the data file stays small and the diff always matches the repository. If the repository is not on this computer or the commit is gone (rewritten history), the panel says so. Agents get the same sha and repository from `trace_read_thread` and can run git themselves.

## Groups and arranging
Put lines that belong together in a group: select them (**Select**) and press **Group** with a name, or type the group in a line's panel. On the canvas a group is a frame around its cards; drag its label to move the whole group, or click it to rename it, arrange just that group, or ungroup it (the lines stay). Lines forked from a grouped line join its group, also the branch lines the hooks make. **Arrange**, next to **Fit**, lays the canvas out: the inbox top left, one block per group, each line followed by the lines forked from it, lines without a group last. It can be undone for ten seconds from the message it shows. **Focus mode** narrows every view to one group: the canvas, the branches, the timeline and the goals show only its lines, the live bar only the sessions on them, and the menus that pick a line list only them. Pick the group under **More → Focus on a group…** or with **Focus on this group** in its panel; a bar under the header names it, and it stays on, also after a reload, until you press **Leave focus**. **Folding** is the other way round: fold the groups you rarely use (**Fold this group** in its panel, or under **More → Focus on a group…**) and they stay out of every view, the canvas keeping a small chip for each that unfolds it on a click, until you unfold them; focus still reaches a folded group. **Arrange** leaves folded groups out of the layout and stacks their chips after the last group. From the command line: `trace-canvas group <thread>… --name <group>` (`--clear` takes lines out); `trace_list_threads` and `trace_read_thread` tell agents each line's group.

## Merging sessions and threads
Several sessions about the same piece of work can become one line, in two ways:
- **Merge** (the one to use) keeps every source line, marks it merged and draws the merge into the target, like git. Nothing is lost and it can be undone. A merged line then folds into its target: the canvas shows the target's card with **N merged in**, its panel links each merged line, and the Branches view still draws its history (More → Show merged threads lays them all out again).
- **Combine** moves every record onto the target in time order and removes the source threads; their name, colour and goal are gone and it cannot be undone. Links to them now point to the target. The canvas no longer offers it; agents use it only when you ask for it by name, and the command line keeps `--combine`.
The target can be an existing thread or a new one. Three places to do it:
- **On the canvas**: Shift-click cards (or turn on **Select** in the toolbar, handy on touch screens). The bar lists the lines you picked; the first one is kept and the others go into it (click another to keep that one, or pick another line or a new one under **into**), and a line under the buttons says what goes where. Then **Merge**.
- **In Claude Code**: `/trace-canvas merge`, or ask in your own words.
- **On the command line**: `trace-canvas merge "Survey A" "Survey B" --into "Survey analysis" --combine`.
Data from another computer (for example a workstation) merges with `trace-canvas import other-data.json`. Threads with the same repository/branch or session key, or the same name, become one line, and records already present are skipped.

*On the canvas: a close look at one card (dark stations are yours, coloured ones the agents'), then Shift-click two lines, pick the target and **Merge**. Made-up data.*
## Tuning the hooks
Set these in `~/.trace-canvas/config.json` or as environment variables:
| Setting | Env variable | Values | Default |
| --- | --- | --- | --- |
| `lang` | `TRACE_CANVAS_LANG` | `en`, `zh`: language of records the hooks write | `en` (switching the page language also switches this) |
| `group` | `TRACE_CANVAS_GROUP` | `branch`, `repo`, `session`: what one thread stands for | `branch` |
| `prompts` | `TRACE_CANVAS_PROMPTS` | `first`, `long` (60+ characters), `all`, `none` | `first` |
| `turns` | `TRACE_CANVAS_TURNS` | `changes` (turns that changed something), `all`, `none` | `changes` |
`TRACE_CANVAS_DISABLE=1` turns the hooks off for one session. `TRACE_CANVAS_HOME` moves the data folder. `TRACE_CANVAS_DEBUG=1` writes every hook event to `~/.trace-canvas/debug.log`.
## Logging from other agents and scripts
Any agent, script or cron job can log one line:
```bash
node bin/trace-canvas.mjs log "Finished the literature table" \
--thread "Proposal" --kind agent --agent Codex --link https://example.com/doc
```
Agents without shell access (ChatGPT, for example) can end their answer with JSON lines. Paste the whole output into the box at the bottom of the page and press Enter, or run `trace-canvas import file.jsonl`:
```json
{"thread": "Proposal", "kind": "agent", "title": "Outlined chapter 2", "body": "Next: figures", "agent": "ChatGPT"}
```
## Retro: what took the most back-and-forth
A simple change can still take a dozen rounds with an agent when one detail is missing, and the next time it starts from zero. **Retro** in a line's panel (or `trace-canvas retro <thread>`, or `/trace-canvas retro`) reads the line's Claude Code and Codex conversations on this machine and has your own `claude` (or `codex`) command line group them into pieces of work, matched to the line's to-dos where it can. Each piece gets its rounds (your messages), the time between them (a gap counts at most 30 minutes), the tokens its replies used, how often you corrected the agent and in how many sessions it came up, and they are ranked by rounds per unit of difficulty, so a simple thing that took many rounds comes first.
For each piece with many rounds it names what was missing, quotes the sentence that finally made it clear and suggests a rule. Nothing is written until you press **Write** on a rule and pick where it goes: **project rules** (the project's CLAUDE.md and AGENTS.md, under "Rules from retros"), **this line** (a decision on the line), or **long-term memory** ([Theatrum](https://github.com/CallSymmetryEcho/Theatrum) when it is installed, otherwise `~/.claude/CLAUDE.md`). The ranking stays on the line as a note. From the command line: `trace-canvas retro --apply <record> <n> [--layer project|line|memory]`.
Which conversations: the line's own sessions; a line that has none (its records were all logged from another line) uses the sessions that logged on it. A session that ran on another machine is read there over ssh, by that machine's own `trace-canvas prompts`, and analysed here with the rest. When nothing can be read, Retro says why: no session at all, or which machine it could not reach or whose trace-canvas is too old.
Which messages: by default those since the line's last retro, up to now, so each retro picks up where the last one stopped. **Retro** opens a small form with that range filled in; change **From** and **To** to look at part of it, or press **Everything**. On the command line: `trace-canvas retro <line> [--since <time> | --all] [--until <time>]`. The note says which range it read.
## What Trace Canvas costs in tokens
**About 1%, and it does not grow with your canvas.** The records are written by hooks that run outside the model, so tracing itself costs no tokens. What enters a session is short and capped: a few lines when it starts (its line, goal, your to-dos, where things are), one line with every message (its line and at most eight open to-dos), and the `trace_*` answers when an agent asks, which cover the session's own line unless it asks for more. That text stays in the context and is read again on later calls, so the cost follows the number of messages in a session, not the size of the canvas. `trace-canvas tokens` reads your recent Claude Code transcripts and shows each session's share.
## AI tidy-up
If the `claude` command is installed, **AI tidy-up** in the header asks Claude to file inbox records into threads, suggest links between threads and point out threads that look stalled. Nothing changes until you tick the suggestions and apply them. It runs `claude -p` on your own account; the hooks are switched off for that call.
## Commands
```text
Page
trace-canvas [serve] [--port 4777] [--no-open] Start the page and open it
Records
trace-canvas log "title" [--thread T] [--kind K] [--body B] [--link URL] [--agent A]
trace-canvas capture [--session ID] [--project DIR] [--thread T] [--separate] [--all]
trace-canvas sessions [--project DIR] List Claude Code sessions of a project
trace-canvas merge <thread>... --into <thread> [--combine]
trace-canvas fork <record|thread> [--name N] [--bind] Branch a new thread off another
trace-canvas hand <to-do|thread> --to claude|codex|gemini Assign it and print the command that starts the agent
trace-canvas dispatch [line|to-do...] [--all] [--print] Hand each line's new to-dos (or the to-dos named) to the agent working on it and wake it
trace-canvas import <file|-> Merge an export, another data.json, or JSON lines
trace-canvas history [line...] [--all] [--off] A repository's line takes in its earlier commits and past sessions (alone: list only)
Goals and to-dos
trace-canvas tasks [--thread T] [--all] Each thread's goal and open to-dos
trace-canvas done <to-do> [--thread T] [--agent A] [--undo]
Tick a to-do by id or title
Live sessions
trace-canvas live Running sessions: status, activity, thread
trace-canvas bind <thread> [--session ID] Put a running session on a thread (default: this one)
trace-canvas bind --unbind [--session ID] Back to its repository/branch line
trace-canvas resume <thread> The line's sessions, newest first, each with the command that resumes it
trace-canvas group <thread>... --name G | --clear Put lines in a group (on the canvas they sit in one frame)
trace-canvas retro <thread> Rank the line's work by back-and-forth and suggest rules
trace-canvas tokens [--days 7] How much of recent Claude Code sessions' tokens went to Trace Canvas
trace-canvas retro --apply <record> <n> [--layer project|line|memory] Write one of them
Setup (--user for every project, --project DIR for one)
trace-canvas setup Hooks + MCP server + skill
trace-canvas install-hooks | install-mcp | install-skill | uninstall-hooks
trace-canvas mcp Run the MCP server on stdio (Claude Code starts this)
trace-canvas lang <en|zh> Language of records the hooks write
trace-canvas where Show where the data is stored
trace-canvas phone Open the canvas on your phone (Tailscale and a QR code; see docs/phone.md)
trace-canvas where <words> [--machine M] Look things up in the location map (~/agent-map)
trace-canvas sync Sync the canvas with your other machines through the location map
trace-canvas update [--check] Get the newest version (git pull in the clone) and restart the page server; --check only looks
```
## How it works

```text
app/index.html The page. Works against the local server, a claude.ai artifact database, or browser storage.
bin/trace-canvas.mjs Command line entry point.
skill/SKILL.md The /trace-canvas skill.
src/server.mjs Local server on 127.0.0.1: page, JSON API, live updates over server-sent events.
src/mcp.mjs MCP server (stdio JSON-RPC, no dependencies).
src/hook.mjs Claude Code hook handler.
src/live.mjs Live sessions: one status file per running session and per subagent, written by the hooks.
src/desktop.mjs Which session started which, read from the Claude desktop app's session records.
src/transcript.mjs Reads Claude Code session transcripts (~/.claude/projects/…/<session>.jsonl).
src/ops.mjs Capturing sessions, merging threads, merging data sets.
src/git-events.mjs Reads commits, pushes, merges and pull requests from shell commands and their output.
src/store.mjs The data file. A directory lock keeps parallel processes from overwriting each other.
src/install.mjs Adds or removes hooks, the MCP server, the skill and permissions.
src/demo.mjs Example data in English and Chinese.
test/ node --test suites (npm test), with transcript fixtures.
docs/architecture.mjs Draws the architecture diagram (English and Chinese SVG).
docs/scenarios/ Made-up scenario data and the script that renders their screenshots.
```
Data model, stored as one JSON file:
```text
threads/<id> name, color, status (active | parked | done | merged), parent, forkOf, mergedInto, x, y, note,
key (repo, repo@branch or session:<id>)
commits/<id> thread (or "inbox"), kind (idea | task | agent | decision | note | fork | merge), title, body,
links[], agent, ts, parents[], done, mergeOf?, session?
edges/<id> from, to, label
```
The server only listens on 127.0.0.1, checks the `Host` header, and requires an `X-Trace-Canvas` header on every write, so other websites open in your browser cannot change your data.
Run the tests with `npm test` in this folder.
## License
MIT, see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues