Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Tome orchestrates agent harnesses. Go from having a single harness working on your project to an army of harnesses all coordinating together!

You don’t have to write it yourself: with the tome plugin, your coding agent writes it for you.

Tome gives you powerful primitives triggers, workers, worktrees, queues, remote nodes (ssh) and an event bus. Put them together and you can build anything from a single test fixer to a whole software factory.

Every agent runs in a tmux, cmux or herdr session, so you can watch what it’s doing and step in.

Quick start

Install tome and start its daemon:

curl -fsSL https://raw.githubusercontent.com/banyango/tome/main/install.sh | sh
tome daemon start

Then follow the quickstart to add a workflow and watch it fire, and install the tome plugin so your agent can write the next one.

Features

  • Triggers. Runs can start when files change, on a cron schedule, when a message arrives, or when another workflow finishes.
  • Workflows The base object of tome. Create workflows in a prompt and tome will track each step. Figure out where you failed, and where you could restart a workflow.
  • Queues. Create a queue and push events to it, have workers pull off the queue as they work.
  • Event Bus. Use an event bus to push events for any number of workers to listen to.
  • Any agent. Tome is totally agent agnostic, run an orchestrator in Claude and a worker in Pi. Anything you want.
  • Remote Nodes. Use ssh to send events and run workflows on other tome instances.

Learn more

  • Create workflows with an agent shows how to have your agent write, run and fix workflows.
  • Remote nodes shows how to add a machine, run workflows on it and forward events to it.
  • Recipes has workflows for common jobs. Ask your agent to adapt one to your project.
  • The guides cover every option, and How it works explains the pieces.
  • Run tome --help, or tome <command> --help, for every command and flag.
  • For agents, the docs are also published as plain text: llms.txt is an index, and llms-full.txt is every page in one file.

Install

One command

curl -fsSL https://raw.githubusercontent.com/banyango/tome/main/install.sh | sh

The script:

  • detects your OS and CPU (macOS or Linux, on arm64 or x86_64),
  • downloads the matching build from the latest GitHub release,
  • checks its SHA-256 checksum, and installs nothing if it doesn’t match,
  • puts tome in ~/.local/bin. It needs no sudo.

If ~/.local/bin isn’t on your PATH, the script says so and shows the line to add.

VariableEffect
TOME_INSTALL_DIRInstall here instead of ~/.local/bin.
TOME_VERSIONInstall a specific release, like v0.1.0, instead of the latest.
curl -fsSL https://raw.githubusercontent.com/banyango/tome/main/install.sh | TOME_VERSION=v0.1.0 sh

To upgrade, run the script again. It overwrites the old binary.

Release builds are self-contained: DuckDB is compiled in, so there is nothing else to install.

Build from source

You need a Rust toolchain.

git clone https://github.com/banyango/tome
cd tome
cargo build --release --features bundled

The binary is target/release/tome. --features bundled compiles DuckDB into it, which takes a few minutes. Without the feature, the build links a prebuilt DuckDB library that sits next to the binary.

Check it works

tome --version
tome --help

Prerequisites

To run a workflow you need two more things:

  • A terminal multiplexer: tmux, cmux or herdr. tome starts each agent in its own session so you can watch it.
  • An agent harness: a command-line coding agent. Claude Code works out of the box. Other agents can be added in config; see Sessions and backends.

git is also needed for workflows that give workers their own worktrees.

Next: Quickstart. To have your coding agent install tome and the tome plugin for you, see Install tome (for agents).

Quickstart

In this walkthrough you set up a workflow that runs on its own: whenever you add a feature file to your project, agents investigate the codebase and break the feature into tasks. You need tmux, Claude Code, and tome installed, and a git repository with some code in it.

1. Start the daemon

The daemon watches for triggers and starts runs. Start it in the background:

tome daemon start
tome daemon status

status prints whether it is running, and exits with code 3 if it isn’t. To have it start at login, run tome daemon install once. See Operating tome.

2. Add the workflow

tome looks for workflows in .tome/workflows in your project. Create a starter file:

cd my-project
tome workflow new plan-feature

That writes .tome/workflows/plan-feature.md. Replace its contents with this:

---
name: plan-feature
description: When a feature file is added, investigate the codebase and break the feature into tasks.
mode: orchestrated
triggers:
  - manual
  - file: "features/*/feature.md"
    on: [created]
    debounce: 5s
---
A feature was just written down. Work out how it fits this codebase and
break it into tasks.

New feature files (empty for a manual run):

{{trigger.paths}}

## Find features
Each path above looks like `features/<slug>/feature.md`. If there are no
paths, use every directory under `features/` that has a `feature.md` but no
`tasks.md`. Skip any feature that already has a `tasks.md`. If nothing is
left, finish the run successfully and say so.

## Investigate
Create a group named `investigate`. For each feature, spawn an agent worker
in it named `investigate-<slug>`. Tell it to read the feature file, then
find the code the feature touches: the modules and entry points, the tests
that cover them, and any similar feature that was built before. It must not
change any files. It reports as its summary the files that will change, the
patterns to follow, and any risks or open questions. Wait for the group.

## Break down
For each feature, write `features/<slug>/tasks.md` from the feature file and
its investigator's summary. Give each task a `### <N>. <title>` heading, a
`**Blocked by:**` line (task numbers, or `none`), and a short paragraph on
what changes, where, and how to tell it's done. Keep each task small enough
for one agent to implement and test as a single commit. List open questions
at the end.

## Hand off
For each feature that got a `tasks.md`, publish an event so the next
workflow can pick it up:

    tome publish feature.planned <slug>

## Finish
Finish the run with each feature and its number of tasks. If an
investigator failed, name its feature and fail the run.

Here you paste a finished workflow so you can see one work. For your own workflows, ask your coding agent to write them; see Create workflows with an agent.

The frontmatter holds the trigger: a feature.md created under features/. The body is plain English, split into steps under ## headings. tome never parses it; an orchestrator agent reads it. {{trigger.paths}} is replaced with the new files when the run starts.

Check it, and confirm the daemon has picked up the trigger:

tome validate plan-feature
tome triggers ls

3. Write down a feature

Describe a small change you actually want in this project. For example:

mkdir -p features/export-csv
cat > features/export-csv/feature.md <<'EOF'
# Export to CSV

Add a `--csv` flag to the export command. It writes the same rows as the
default output, as CSV with a header line.
EOF

That’s all you do. A few seconds later the daemon starts a run:

tome runs list

The run works like this:

  1. tome starts an orchestrator agent in a tmux session and gives it the workflow, with features/export-csv/feature.md filled in.
  2. The orchestrator reports each step, and spawns a worker agent named investigate-export-csv.
  3. The worker reads the code the feature touches and reports what it found, without changing anything.
  4. The orchestrator writes features/export-csv/tasks.md and publishes a feature.planned event.

4. Watch it

Each agent runs in its own tmux session. In another terminal:

tmux ls
tmux attach -t <session>

You can type into an agent’s session at any time, to answer a question or steer it. Detach with Ctrl-b d.

To see a run’s steps, history and logs:

tome runs show <id>
tome runs logs <id>

When the run succeeds, read features/export-csv/tasks.md. Change anything you disagree with; it’s your plan now.

5. Keep going

Every feature file you add from now on gets the same treatment. Nothing subscribes to feature.planned yet, so for now that event goes nowhere. Add the implement-feature recipe and each planned feature is built too, one agent and one branch per task. To build the feature you just planned, publish the event again yourself:

tome publish feature.planned export-csv

To try a trigger without touching files, fire it by hand:

tome triggers fire plan-feature --path features/export-csv/feature.md

You can also start any workflow yourself with tome run <name>. It stays attached and prints progress until the run ends.

Next

Recipes

Workflows to copy into .tome/workflows/ and adapt. Each is a real file under docs/examples/ in the repo, and CI runs tome validate on each one. They’re written for a Rust project. Rather than editing one by hand, ask your coding agent to adapt it, for example “adapt the fix-failing-tests tome recipe to this repo”; see Create workflows with an agent.

RecipeStarts when
Plan a feature, then build ita feature file is added, then when its plan is ready
Fix failing testsa source file changes
Review a branch in parallelyou run it
Implement, then reviewa message arrives, then when the first run succeeds
Split work across branchesyou run it
A weekday-morning checka schedule
Keep one agent on callmessages arrive
Tidy docs as you writea doc file changes
A software factorya request, an alert, a schedule, or another workflow

Plan a feature, then build it

Two workflows chained through the message bus. You add features/<slug>/feature.md. plan-feature has an agent investigate the codebase, writes tasks.md, and publishes feature.planned:

---
name: plan-feature
description: When a feature file is added, investigate the codebase and break the feature into tasks.
mode: orchestrated
triggers:
  - manual
  - file: "features/*/feature.md"
    on: [created]
    debounce: 5s
---
A feature was just written down. Work out how it fits this codebase and
break it into tasks.

New feature files (empty for a manual run):

{{trigger.paths}}

## Find features
Each path above looks like `features/<slug>/feature.md`. If there are no
paths, use every directory under `features/` that has a `feature.md` but no
`tasks.md`. Skip any feature that already has a `tasks.md`. If nothing is
left, finish the run successfully and say so.

## Investigate
Create a group named `investigate`. For each feature, spawn an agent worker
in it named `investigate-<slug>`. Tell it to read the feature file, then
find the code the feature touches: the modules and entry points, the tests
that cover them, and any similar feature that was built before. It must not
change any files. It reports as its summary the files that will change, the
patterns to follow, and any risks or open questions. Wait for the group.

## Break down
For each feature, write `features/<slug>/tasks.md` from the feature file and
its investigator's summary. Give each task a `### <N>. <title>` heading, a
`**Blocked by:**` line (task numbers, or `none`), and a short paragraph on
what changes, where, and how to tell it's done. Keep each task small enough
for one agent to implement and test as a single commit. List open questions
at the end.

## Hand off
For each feature that got a `tasks.md`, publish an event so the next
workflow can pick it up:

    tome publish feature.planned <slug>

## Finish
Finish the run with each feature and its number of tasks. If an
investigator failed, name its feature and fail the run.

implement-feature subscribes to that event and implements each task with its own agent on its own branch. Tasks that don’t block each other run in parallel, and a blocked task branches from the task it waits for:

---
name: implement-feature
description: Implement a planned feature, one agent and one branch per task.
mode: orchestrated
params:
  base: {type: string, default: main, description: "Branch the worktrees start from"}
triggers:
  - on: feature.planned
concurrency: 1
---
A feature has a task plan. Implement it.

Feature slug: {{trigger.payload}}

## Order
Read `features/<slug>/feature.md` and `tasks.md`. If the slug is empty, fail
the run and say to fire it with
`tome triggers fire implement-feature --payload <slug>`. Work out the order
from each task's "Blocked by" line.

## Implement
Create a group named `tasks`. A task is ready once every task it's blocked
by has finished successfully. Start each ready task as an agent worker in
the group, named `task-<N>`, on its own worktree:

    tome worker spawn --name task-<N> --group tasks --worktree --base <base> --prompt "<the task>"

`<base>` is {{params.base}} for a task that isn't blocked, and the branch of
its blocker otherwise. If it has several blockers, branch from one and tell
the worker to merge the others' branches first.

Tell each worker to read `feature.md` and `tasks.md`, implement only its
task, run the tests and fix failures, commit as `<slug>-<N>: <title>`, and
report done with a summary of any choices it made. Keep starting tasks as
their blockers finish. Don't start a task whose blocker failed.

## Finish
Don't merge anything: the branches are for review. Finish the run with each
task's branch and its worker's summary. If any task failed or was blocked,
fail the run and name them.

Nothing is merged, so you review the branches and merge them yourself. To re-run just the build for a feature, publish the event by hand:

tome publish feature.planned export-csv

Fix failing tests

Every time you save a Rust file, run the tests. If they fail, an agent fixes them on its own branch and leaves the branch for you to review.

---
name: fix-tests
description: Whenever Rust source changes, run the tests and fix any failures on a branch.
mode: orchestrated
triggers:
  - manual
  - file: "src/**/*.rs"
    debounce: 10s
    while_running: queue
concurrency: 1
---
These files just changed:

{{trigger.paths}}

## Test
Spawn a command worker named `tests` that runs `cargo test`, and wait for it.
If it succeeded, finish the run successfully.

## Fix
Spawn an agent worker named `fixer` with its own worktree. Tell it to make the
failing tests pass without changing what the tests check, and to commit its
work. Give it the tail of the test output from the `tests` worker's summary.
Wait for it. If it failed, fail the run and say why.

## Report
Don't merge the branch. Finish the run with the `fixer` branch name and a
one-line description of the fix as the summary, so it can be reviewed.

The fixer works in a worktree under .tome/, which is never watched, so its edits don’t start another run. concurrency: 1 and while_running: queue mean saves made during a run are collected into one follow-up run.

Review a branch in parallel

One reviewer agent per changed file, all at once, combined into one report.

---
name: fan-out
description: Review every file changed on a branch in parallel, then combine the reviews.
mode: orchestrated
params:
  base: {type: string, default: main, description: "Compare the current branch to this one"}
concurrency: 1
---
Review the files changed since {{params.base}}, one worker per file.

## Split
List the files that changed between {{params.base}} and HEAD. If there are
none, finish the run successfully and say so.

## Review
Create a group named `reviews`. For each changed file, spawn an agent worker
in that group, named `review-<n>`, told to review only that file and to
report its findings as its summary. Then wait for the group.

## Combine
Read every member's summary from the group's status. Write one report to
`review.md`, most serious findings first. If any reviewer failed, list it in
the report and carry on. Finish the run successfully with the path to the
report.
tome run fan-out --param base=main

Implement, then review

Two workflows chained through the message bus. Publishing a request starts implement, and every implement run that succeeds starts review.

---
name: implement
description: Implement one requested feature at a time.
triggers:
  - on: feature.requested
concurrency: 1
---
Implement this feature:

{{trigger.payload}}

## Implement
Make the change on a new branch and commit it.

## Finish
Finish the run successfully, with the branch name as the summary.
---
name: review
description: Review the change that a successful implement run made.
triggers:
  - on: tome.run.implement.succeeded
---
Review the change made by this run. The event says which run it was and
gives its summary:

{{trigger.payload}}

## Review
Read the branch named in the summary and write your findings to `review.md`.
tome publish feature.requested "Add a --verbose flag to the export command"

Anything that can run a shell command can publish: a git hook, CI, a script, or another agent.

Split work across branches

Give it a list of tasks. Each gets its own agent on its own branch, and the orchestrator merges them and runs the tests.

---
name: fan-in-worktrees
description: Do independent tasks on separate branches, then merge the branches.
mode: orchestrated
params:
  tasks: {type: string, description: "One task per line"}
  base: {type: string, default: main}
---
Do each of these tasks on its own branch, then merge the results.

{{params.tasks}}

## Implement
Create a group `tasks`. For each task, spawn an agent worker in it with its
own worktree, branching from {{params.base}}:

    tome worker spawn --name task-<n> --group tasks --worktree --base {{params.base}} --prompt "<the task>"

Tell each worker to commit its work to its branch before it reports done.
Wait for the group.

## Merge
Merge the branch of every worker that succeeded into the current branch, one
at a time, and resolve conflicts as they come. Run the project's tests after
the last merge. Fail the run if a worker failed, or if the tests fail, and
say which.
tome run fan-in-worktrees --param tasks="$(cat tasks.txt)"

A weekday-morning check

A cron trigger, with a manual entry to show you can also run it by hand.

---
name: nightly
description: A weekday-morning check of the open branches.
triggers:
  - manual
  - cron: "0 9 * * 1-5"
    params: {base: main}
params:
  base: {type: string, default: main}
---
Started at {{trigger.time}} for the schedule {{trigger.scheduled}}.

## Check
List the branches that are ahead of {{params.base}} and say which have gone
stale.

Keep one agent on call

One long-lived run takes work items as they arrive, instead of starting a run for each.

---
name: watcher
description: Keep one run going and hand it new work as messages arrive.
triggers:
  - manual
  - on: work.new
    to: running-or-new
concurrency: 1
---
Handle work items as they come in. This run is meant to stay alive.

Start with:

{{trigger.payload}}

## Loop
Do the item. Then pull the next message from your `events-$TOME_RUN_ID` queue with
`tome queue pull events-$TOME_RUN_ID --wait 5m`, ack it, and do that item. When the queue
is empty after the wait, finish the run successfully.
tome publish work.new "Rename the config loader to settings"

Tidy docs as you write

A file trigger that fixes only the files that changed, so it never triggers itself.

---
name: doc-check
description: Check the docs whenever a Markdown file under docs/ changes.
triggers:
  - file: "docs/**/*.md"
    on: [created, modified]
    debounce: 5s
    ignore: ["docs/drafts/**"]
    while_running: queue
---
These files changed:

{{trigger.paths}}

## Check
Read each changed file and fix broken links and typos. Do not touch any file
that isn't on the list, so this run doesn't start another one.

A software factory

Four workflows that pass work to each other on the message bus, so a change goes from request to production with a person involved only for risky changes. It’s modelled on the “agentic software factory” that The Pragmatic Engineer described inside OpenAI.

WorkflowStarts onPublishes
factory-buildfactory.requestedfactory.ship or factory.review-needed
factory-deployfactory.shipalert.outage if the rollout regresses
factory-perfevery hour, or alert.latencyfactory.requested for each fix
factory-sevbotalert.outage or incident.questionincident.proposed, incident.answer, factory.requested

factory-build builds the change, loops until CI and four specialist reviewers pass, and sorts it into low or high risk:

---
name: factory-build
description: Build a requested change, loop until CI and the reviewers pass, then ship it or ask an engineer.
mode: orchestrated
params:
  base: {type: string, default: main, description: "Branch the change starts from"}
  max_attempts: {type: int, default: 3, description: "Fix rounds before giving up"}
triggers:
  - manual
  - on: factory.requested
concurrency: 1
---
Build this change:

{{trigger.payload}}

## Implement
Spawn an agent worker named `builder` on its own worktree, branching from
{{params.base}}. Give it the request above. Tell it to use whatever context
it needs (the code and its docs, and any Slack, Notion or metrics tools its
harness has), to commit its work, and to report the branch and what it
changed as its summary. Wait for it. If it failed, fail the run. Its branch
is now the change's branch.

## CI
Spawn a command worker named `ci-<n>` that runs `cargo build && cargo test`
in the change branch's worktree, and wait for it. If it failed, spawn an
agent worker `fix-<n>` on a new worktree branching from the change's branch,
with the tail of the output from the CI worker's summary. Tell it to commit a
fix. Its branch becomes the change's branch; run CI again. Stop after
{{params.max_attempts}} rounds and fail the run with the last output.

## Review
Create a group named `reviews`. Spawn four agent workers in it, each told to
review the change's branch against {{params.base}} from one angle only:
`review-data` (schemas, migrations, queries), `review-infra` (config,
deploys, resource use), `review-cloud` (cloud services and permissions) and
`review-security` (auth, secrets, input handling). Each must not change any
files. It reports its findings as its summary, each marked `blocking` or
`minor`, and ends with a risk of `low` or `high`. Wait for the group.

If any reviewer has a blocking finding, send those findings to a new fix
worker the same way, go back to CI, and review again. This counts
towards the same {{params.max_attempts}} rounds.

## Classify
The change is low-risk only if every reviewer said `low` and nothing is
blocking. Write the reviews and the verdict to `.tome/factory/<branch>.md`.

## Hand off
If the change is low-risk, ship it:

    tome publish factory.ship <branch>

Otherwise ask for an engineer and don't ship:

    tome publish factory.review-needed "<branch>: <why it is high-risk>"

## Finish
Finish the run successfully with the branch, the verdict and where the
review file is.

A low-risk change goes straight to factory-deploy. A high-risk one waits for an engineer, who reviews the branch and ships it by hand:

tome publish factory.ship <branch>

factory-deploy merges and deploys it, then rolls it out behind a flag while it watches a dashboard it made for the change:

---
name: factory-deploy
description: Roll out a shipped branch behind a feature flag and watch it until it is fully live.
mode: orchestrated
triggers:
  - on: factory.ship
concurrency: 1
---
Deploy this branch: {{trigger.payload}}

Deploys use `./scripts/deploy` and feature flags use `./scripts/flag`. Change
these to your own tools.

## Merge
Merge the branch into main and push. If the merge conflicts, fail the run:
the change needs to go back through `factory-build`.

## Deploy
Run `./scripts/deploy main` as a command worker and wait for it. Fail the
run if it fails.

## Dashboard
Make a dashboard for the metrics this change could move: its endpoints'
latency and error rate, and anything the review file in `.tome/factory/`
calls out. Record where it is.

## Roll out
Turn the change's flag on for 1%, then 10%, 50% and 100%. At each stage,
wait 10 minutes and check the dashboard against the hour before. If latency
or errors get worse, turn the flag off, then publish what you saw:

    tome publish alert.outage "<branch>: <what regressed, and the dashboard>"

and fail the run.

## Finish
Finish the run successfully with the branch, the dashboard and the final
flag state.

factory-perf looks for latency regressions and sends each fix back to factory-build, which closes the loop:

---
name: factory-perf
description: Look for latency regressions, drop duplicates, and request fixes for the real ones.
mode: orchestrated
triggers:
  - manual
  - cron: "0 * * * *"
  - on: alert.latency
concurrency: 1
---
Look for latency regressions in production. An alert that started this run,
if any:

{{trigger.payload}}

## Detect
Compare the last hour's latency for each service with the same hour last
week. List every endpoint whose p95 is more than 20% slower. Add the alert
above if it isn't on the list already.

## Dedupe
Drop anything already in `.tome/factory/perf-open.md`, and merge
regressions that share a cause, such as endpoints slowed by one deploy. If
nothing is left, finish the run successfully and say so.

## Investigate
Create a group named `perf`. For each regression, spawn an agent worker in
it named `perf-<n>`, told to find the cause from the metrics, traces and
recent commits, without changing any files, and to report the cause and a
proposed fix as its summary. Wait for the group.

## Request fixes
For each regression with a proposed fix, add it to
`.tome/factory/perf-open.md` and hand it to the factory:

    tome publish factory.requested "<the regression, its cause and the proposed fix>"

## Finish
Finish the run with each regression and whether a fix was requested.

factory-sevbot keeps one run going for an incident. More alerts and people’s questions reach it on its events-<run id> queue:

---
name: factory-sevbot
description: Investigate an outage, propose mitigations, and answer questions until it is resolved.
mode: orchestrated
triggers:
  - manual
  - on: alert.outage
    to: running-or-new
  - on: incident.question
    to: running-or-new
concurrency: 1
---
There is an incident. The first report:

{{trigger.payload}}

Keep a log in `.tome/factory/incident-{{run.id}}.md`, and add to it as you go.

## Investigate
Create a group named `investigate`. Spawn agent workers in it: one reads the
metrics and alerts, one reads the logs, and one reads the deploys and
commits from the last day. None of them changes anything. Each reports what
it found, with times, as its summary. Wait for the group.

## Mitigate
From the summaries, write down the most likely cause and a ranked list of
mitigations, such as turning off a flag or rolling back a deploy. Don't
apply any of them. Publish the top one so an engineer can act on it:

    tome publish incident.proposed "<cause>: <mitigation>"

## Answer
Pull from your `events-$TOME_RUN_ID` queue with `tome queue pull events-$TOME_RUN_ID --wait 15m`. An
event on `alert.outage` is more evidence: add it to the log and look again
at the cause. An event on `incident.question` is a question: answer it in the
log and publish the answer on `incident.answer`. Ack each one. Keep going
until an event says the incident is resolved, or the queue has been empty
for an hour.

## Finish
Write a timeline and a summary at the top of the log. If a fix is needed,
publish it:

    tome publish factory.requested "<the fix, and a link to the log>"

Finish the run with the path to the log.
tome publish factory.requested "Add a --since flag to the export command"
tome publish incident.question "Is the EU region affected?"
tome publish incident.question "Resolved: rolled back the uploader deploy"

Some things to know before you use it:

  • Context comes from the harness. Slack, Notion, metrics and deploy tools are whatever MCP servers and CLIs your agents have. The ./scripts/deploy and ./scripts/flag commands are placeholders.
  • Alerts need a bridge. tome doesn’t receive webhooks. Point your alerting at a small script, or a cron job, that runs tome publish alert.outage "...".
  • Loops stop themselves. A rollout that regresses goes deploy → sevbot → build → deploy, and each hop adds one to the event’s depth. Events deeper than 8 aren’t delivered, so the factory gets about two tries at fixing its own outage before a person has to step in. Each factory-perf run on the hourly schedule starts again at depth 0.
  • Runs aren’t timed out. The deploy and sevbot runs say in their bodies when to stop, because tome doesn’t enforce timeout.

Next

Write a workflow covers every frontmatter option, and Triggers and the message bus covers every trigger field.

Install tome (for agents)

This page is for a coding agent that has been asked to install tome. Do the steps in order, and check each one before moving on. If a check fails, stop and tell the user what failed and what you saw.

The plain-text version of this page is at https://banyango.github.io/tome/md/agents/install.md.

1. Check whether tome is already installed

tome --version

If this prints a version, tome is installed. Skip to step 4, unless the user asked for an upgrade.

2. Check the platform

uname -sm

tome supports macOS (Darwin) and Linux, on arm64/aarch64 or x86_64. On anything else, stop and tell the user. Building from source is their call; see Install.

3. Run the installer

curl -fsSL https://raw.githubusercontent.com/banyango/tome/main/install.sh | sh

It needs no sudo. It downloads the latest release, checks its SHA-256 checksum and puts tome in ~/.local/bin. If the checksum doesn’t match, it installs nothing and exits with an error. Report that to the user; don’t retry around it.

Only if the user asks for them:

  • TOME_VERSION=v0.1.0: a specific release instead of the latest.
  • TOME_INSTALL_DIR=<dir>: another directory instead of ~/.local/bin.

Set them on sh, not curl:

curl -fsSL https://raw.githubusercontent.com/banyango/tome/main/install.sh | TOME_VERSION=v0.1.0 sh

4. Check it’s on the PATH

command -v tome && tome --version

If command -v prints nothing, the install directory isn’t on PATH. The installer printed the line to add. Tell the user; don’t edit their shell profile unless they ask. To finish this session, call the binary by its full path, ~/.local/bin/tome.

5. Check the prerequisites

command -v tmux || echo "no tmux"
command -v claude || echo "no claude"
command -v git || echo "no git"
  • tome needs a terminal multiplexer: tmux, cmux or herdr. If the user works in cmux or herdr, tmux is optional. Ask if you can’t tell.
  • The default agent harness is Claude Code (claude). Other agents can be configured; see Sessions and backends.
  • git is needed for workflows that give workers their own worktrees.

Tell the user what’s missing. Installing tmux or an agent CLI changes their system, so ask first.

6. Start the daemon

tome daemon start
tome daemon status

status exits with code 3 if the daemon isn’t running. If the user runs cmux, the daemon has to be started from a terminal inside cmux, or sessions won’t open there.

To have it start at login, tome daemon install registers a launchd (macOS) or systemd --user (Linux) service. Only do that if the user asks.

7. Install the tome plugin

The tome plugin teaches agents to write, run and debug tome workflows. If you’re Claude Code, install it for every project:

claude plugin marketplace add banyango/tome
claude plugin install tome@tome

If the user asked for it in this project only, add --scope project to both commands. That writes .claude/settings.json; tell the user to commit it.

Check it:

claude plugin details tome

The output lists Skills (1) tome. The plugin loads in the next session, or after the user runs /reload-plugins.

If you aren’t Claude Code but you read Agent Skills, download the skill into your own skills directory instead:

curl -fsSL https://raw.githubusercontent.com/banyango/tome/main/plugin/skills/tome/SKILL.md \
  -o <skills-dir>/tome/SKILL.md

If you don’t read skills, skip this step and say so in your report.

8. Report back

Tell the user:

  • the version from tome --version, and where the binary is,
  • anything missing from step 5,
  • whether the daemon is running,
  • whether the plugin (or skill) was installed, and that it loads in a new session.

Next, they can follow the Quickstart.

Install the tome plugin

The tome plugin for Claude Code teaches your agent how to write, validate, run and debug tome workflows. It holds one skill, tome, in the Agent Skills format. The agent loads it when a task mentions tome or .tome/workflows, or you can call it with /tome:tome.

The plugin doesn’t install tome itself. Do that first: Install. Install tome (for agents) installs tome and the plugin together.

Claude Code

In a Claude Code session, add the tome marketplace and install the plugin:

/plugin marketplace add banyango/tome
/plugin install tome@tome

Or from your shell:

claude plugin marketplace add banyango/tome
claude plugin install tome@tome

Run /reload-plugins, or start a new session, to load it. To check it’s there, run claude plugin details tome, or ask for something like “write a tome workflow that runs the tests every night”.

To install it for everyone working in one repository, run this there once and commit the .claude/settings.json it writes:

claude plugin marketplace add banyango/tome --scope project
claude plugin install tome@tome --scope project

Updates

The plugin follows the tome repository, so each change to the skill is a new version. To update:

/plugin marketplace update tome

or claude plugin update tome@tome from your shell, then start a new session. To update on its own, turn on auto-update: run /plugin, go to Marketplaces, select tome, and select Enable auto-update.

Once it’s installed, see Create workflows with an agent for how to use it.

Other agents

Any agent that reads Agent Skills can use the skill without the plugin. Download SKILL.md into a tome/ folder in that agent’s skills directory; its docs say where that is:

curl -fsSL https://raw.githubusercontent.com/banyango/tome/main/plugin/skills/tome/SKILL.md \
  -o <skills-dir>/tome/SKILL.md

Run the same curl again to upgrade.

Codex

Codex can use the plugin from the repository’s marketplace. Add it from the repository root:

codex plugin marketplace add .

Enable the plugin for this project in .codex/config.toml:

[plugins."tome@tome"]
enabled = true

Restart Codex to load it. The repository marketplace is available to the project, and the project config enables tome.

OpenCode

OpenCode reads the existing tome skill through the repository’s opencode.json; start OpenCode from this repository and load tome with its skill tool. To install it globally instead, copy plugin/skills/tome to ~/.config/opencode/skills/tome.

An agent that doesn’t support skills can read the same material as plain text. Point it at llms-full.txt, or paste the file below into its instructions.

Asking an agent to install it

Paste this into Claude Code:

Install the tome plugin: run `claude plugin marketplace add banyango/tome`
and `claude plugin install tome@tome`, then tell me to run /reload-plugins.

What’s in it

The skill covers checking the install, the daemon, workflow files and their frontmatter, triggers and the message bus, running and inspecting runs, and fixing common failures. The full file:

---
name: tome
description: Write, run and debug tome workflows. Use when the user mentions tome, `.tome/workflows`, a workflow that should start on a file change, a schedule or a message, or wants several agents to work in parallel in tmux or cmux.
---

# tome

tome runs repeatable agentic workflows. A workflow is a Markdown file with YAML
frontmatter: the frontmatter says what starts it, and the body describes the
steps in plain English. The tome daemon starts one agent for each run in a
tmux, cmux or herdr session. By default (`mode: single`) that agent does the whole
workflow itself; with `mode: orchestrated` it is an orchestrator that spawns
worker agents.

Full docs as one plain-text file: https://banyango.github.io/tome/llms-full.txt

## Before you start

```sh
tome --version          # installed? If not: https://banyango.github.io/tome/md/agents/install.md
tome daemon status      # exit code 3 means it isn't running
tome daemon start
```

Run commands from inside the project. Project workflows are found by looking
upwards from the current directory.

## Ask before you write a workflow

Don't guess at how a workflow should behave. A wrong guess costs the user a
run: too many agents for their machine, a param they don't understand, or
work that stops halfway. Before writing a new workflow, or changing how one
splits up work, ask the user the questions below that the request and the
repo don't already answer. Ask them together, in one message, with a
suggested answer for each, and use your question tool if you have one.

1. **What starts it?** Run by hand, a file change, a schedule, or a message
   from another workflow.
2. **How many agents at once?** Each agent is a full harness session, and
   most laptops handle 1 or 2 well. Suggest 1. Never start more workers at a
   time than the user said.
3. **How is the work split?** One agent doing every item in turn, one agent
   per item, or a fixed number of agents taking items from a queue. With a
   limit of 1 or 2, one agent per item, started in turn, is usually simplest.
   One agent doing everything is `mode: single` (the default), and the
   questions about worker names don't apply. Anything that spawns workers is
   `mode: orchestrated`.
4. **Where do changes go?** Straight into the current branch, one branch per
   item, and whether the orchestrator merges after the tests pass or leaves
   branches for review.
5. **When does it stop?** After one item, after N items, or when nothing is
   left.
6. **How do you check the work?** The test, type and lint commands, if you
   can't find them in the repo.
7. **What should the workers be called?** A worker's name is what the user
   sees in its tab title (`tome: <workflow> #<run> / <name>`), in
   `tome runs show`, and in its default branch name. Suggest a
   pattern that fits how the work is split, with an example from this repo:
   - one worker per item: the item's id and a short slug, such as
     `t004-renderer` or `fix-login-redirect`
   - a pool taking from a queue: its role and a number, such as `builder-1`
   - one worker per angle or role: the role, such as `review-security` or
     `tests`

   Names are letters, digits, `_` and `-`, at most 64 characters and unique
   in a run. Keep them under about 25 characters so tab titles stay readable.
8. **How should branches be named?** Ask only if the workflow makes git
   branches, through worker worktrees or by telling an agent to branch.
   Look at the repo's branches (`git branch -a`) first and suggest the
   convention it already uses. The choices are usually:
   - tome's default, `tome/<run>/<worker>`: unique for every run, and easy to
     find and clean up with `tome gc`
   - the team's convention, such as `feature/<slug>`, `task/<id>-<slug>` or
     `<user>/<slug>`, when the branches become pull requests

   Pass the name with `--branch` on `tome worker spawn --worktree` or
   `tome worktree create`. Creating a branch that already exists fails, so a
   custom pattern needs something unique in it, such as the task id, or
   `{{run.id}}` when the same item can come round again.

9. **Which model should the agents use?** Ask, and don't pick for them: the
   choice trades cost and speed against quality. A single-agent workflow
   takes one answer, set with `defaults.model`. For an orchestrated one, ask
   about the workers and the orchestrator separately, since the orchestrator mostly coordinates and
   often does fine on a cheaper model while workers doing the hard work may
   want a stronger one. Suggest the model this session is running, or say
   that leaving it out uses the harness's own default. Set the answers with
   `defaults.model` (workers) and `defaults.orchestrator_model`, and a single
   worker can differ with `tome worker spawn --model <m>`. Use the names the
   harness understands (`opus`, `sonnet`, `haiku` for `claude`). A custom
   harness only takes a model if its command includes `{{model}}` or it has
   `model_flag` (see the docs), so `tome validate` and `tome run` refuse one
   that can't. When adding a harness, prefer putting `{{model}}` directly in
   its command where the CLI expects the model; that keeps the model handling
   visible with the other arguments and avoids a separate `model_flag` setting.
   `{{model}}` expands to an empty string when no model is set, so use
   `model_flag` instead when the model is optional and the CLI rejects an
   empty model argument. For example:

   ```yaml
   harnesses:
     codex:
       command: ["codex", "--model", "{{model}}", "{{prompt}}"]
   ```

Then say back, in two or three lines, what the workflow will do (for example,
"one agent at a time, one branch per task, merged when tests pass, stops when
no task is ready, branches named `task/<id>-<slug>`, sonnet for the workers") before you write it. For the first run, suggest a small
one, such as a single item, so the user sees it work before it does
everything.

Turn the answers into params with real defaults, so the next run needs no
questions, and write the worker and branch naming into the step that spawns
workers, with `--name` and `--branch` in the example command:

- Use `type: int` for numbers, and a default that's safe on its own, such as
  `workers: {type: int, default: 1}`.
- Don't use sentinel values like `0` for "no limit". Pick a real default, or
  leave the limit out.
- tome doesn't cap workers for you. Write the cap into the body, in the step
  that spawns them: "Never have more than {{params.workers}} workers running.
  Start the next when one finishes."

## Write a workflow

Workflows live in `.tome/workflows/<name>.md` (one project) or
`~/.tome/workflows/<name>.md` (every project).

```sh
tome workflow ls                # what can `tome run` see? (--global for ~/.tome only)
tome workflow new my-workflow   # writes a starter file
tome validate my-workflow       # always run after editing; exit code 2 on errors
```

```markdown
---
name: my-workflow                 # required: letters, digits, _ and -
description: One line for listings.
mode: single                      # single (default): one agent does it all;
                                  # orchestrated: an orchestrator spawns workers
params:
  target:
    type: string                  # string (default), int, float, bool
    default: src                  # no default = required
triggers:
  - manual
  - file: "features/*/feature.md"
    on: [created]
---
Say what the run is for. Use {{params.target}}, {{run.id}} and, for
triggered runs, {{trigger.paths}}, {{trigger.payload}} and so on.

## First step
What to do, what counts as done, and what happens if it fails.

## Second step
...
```

Rules that matter:

- The body is read by the run's agent, not parsed. `##` headings become
  the steps reported in `tome runs show`.
- To track a run in the user's own terms, write "Set status to <Label>" in a
  step's text. The agent runs `tome step status "<Label>"`, and the run's
  custom status follows its active step (`custom` in `tome runs show`).
- For each step, say what done looks like and what to do on failure. Without
  that, a failed step fails the run.
- A workflow that delegates needs `mode: orchestrated`. In the default
  `mode: single`, `tome worker`, `tome group` and `tome worktree create` are
  refused, and `tome validate` warns when the body mentions them.
- To fan out, set `mode: orchestrated` and tell the orchestrator to create a group, spawn named workers in
  it (optionally each on its own git worktree), and wait for the group.
- `tome validate` rejects unknown frontmatter keys and `{{placeholders}}` that
  name no param.
- Other frontmatter: `defaults` (`backend`, `harness`, `orchestrator_harness`,
  `model`, `orchestrator_model`, `layout`, `start_timeout`), `concurrency` with `on_conflict: queue|reject`,
  and `orchestrator` (extra instructions for the orchestrator only). The
  `orchestrator_*` keys only apply to `mode: orchestrated`.

## Queues

A worker does one job and reports once. Its session closes after
`tome worker done`, so a worker that pulls from a queue stops after one
message unless its prompt tells it to loop. When a worker should work
through a queue, its prompt has to say all of this:

```markdown
Work through the queue `tasks` until it is closed and empty:
1. `tome queue pull tasks --wait 1m`. Exit 3 with status `closed` means
   there's nothing left: go to step 4. Status `empty` means pull again.
2. Do the item, then `tome queue ack <id>`.
3. Go back to step 1. Don't report yet.
4. Report once, with every item and its result.
```

And the orchestrator has to `tome queue close tasks` after its last push, or
the workers wait forever. A closed queue can't be reopened, so a workflow
that loops and pushes more items later needs a new queue name each time
round, such as `tasks-<round>`.

Queues belong to the project, not the run: they outlive runs, and a run, a
worker or the user at a shell can push to and pull from the same queue.
Names are shared across runs, so pick ones that won't collide. `tome queue
peek <q>` shows what's waiting without claiming it.

If each item gets its own worker anyway, don't use a queue: put the item in
the worker's prompt.

## Triggers

```yaml
triggers:
  - manual                        # documentation only
  - file: "docs/**/*.md"          # on, debounce, ignore, while_running
  - cron: "0 9 * * 1-5"           # five fields, local time
  - on: review.requested          # a bus topic; * = one word, ** = one or more at the end
    to: new                       # new | running | running-or-new
```

- A run that edits files its own file trigger watches starts another run. Use
  `ignore:`, `on: [created]` or `while_running: mute`.
- tome publishes `tome.run.<workflow>.started|succeeded|failed|cancelled`.
  Subscribe to one to chain workflows. Subscriptions work in project
  workflows only.

```sh
tome publish feature.planned my-slug      # publish to the bus
tome publish some.topic "..." --dry-run   # which workflows would get it
tome triggers ls                          # armed triggers
tome triggers fire my-workflow --dry-run  # test without touching files
tome triggers fire my-workflow --path docs/a.md
tome triggers fire my-workflow --payload "hello"
```

## Run and inspect

```sh
tome run my-workflow --param target=lib   # attached; Ctrl-C cancels
tome run my-workflow --detach             # prints the run id
tome runs list --status running
tome runs show <id>                       # steps, workers, worktrees, sessions
tome runs logs <id> --tail 100
tome run cancel <id>
tome run resume <id> --detach             # pick up a failed or cancelled run
tome run resume <id> --start-at "Review"  # tell its agent where to start
tome query "select id, workflow_name, status, reason from runs order by id desc limit 5"
```

Add `--json` to any command, or set `TOME_OUTPUT=json`, for machine-readable
output. Prefer it when you parse results.

`tome run resume <id>` starts a new run linked to a failed or cancelled one. It
reuses the old run's workflow snapshot, params, mode and worktrees, and takes
back its parked trigger delivery. Its agent gets a "Resuming" prompt section
with the steps already done and where to start, so finished steps aren't
redone. Workers aren't relaunched. Each run can be resumed once; if the new run
fails, resume that one (`tome runs show` gives `resumed_from` and
`resumed_as`). Scripts and workflows can use it to automate recovery, for
example by resuming a run that failed with `daemon_restart`. If you are asked
to design a workflow, keep its steps safe to redo, since a resumed agent may
redo the step that was interrupted.

## Other machines (nodes)

Nodes are machines reached over SSH, listed under `nodes:` in
`~/.tome/config.yaml` (`tome node add <name> <ssh-destination>`). Add
`--on <node>` to run a daemon command there. A workflow is resolved by name on
the node, so commit and pull it there first.

```sh
tome node ls                               # reachable? version? daemon?
tome node check mini                       # what's wrong, and the fix
tome run build --on mini --detach          # `build` as the node resolves it
tome runs show mini:12                     # <node>:<id> names a node's run
tome runs list --nodes                     # every machine, with a NODE column
tome session view mini:12                  # attach to its tmux session
tome publish --on mini deploy.requested "1.4.2"   # hand work to another machine
```

To forward a project's events to a node, add rules to its
`.tome/config.yaml`:

```yaml
bus:
  forward:
    - topic: tome.run.build.succeeded
      to: mini            # or [mini, ci]
```

Agent commands (`ready`, `step`, `worker`, `run finish`) refuse `--on`. So do
local ones (`validate`, `workflow`, `layout`, `node`). Inside a run,
`TOME_NODE` is ignored.

## When something goes wrong

- `agent_exited` / `orchestrator_exited` / `worker_exited`: the agent's session ended without
  reporting. Read `tome runs logs <id>`, fix the cause, then
  `tome run resume <id>` to carry on rather than start over.
- `daemon_restart`: the daemon stopped mid-run. `tome run resume <id>`.
- Harness won't open: run `tome harness validate <name> --model <model>` to
  check the config, executable on this shell's `PATH`, model forwarding, and
  the expanded launch command. It prints a preview without starting the agent.
- `tome worker ... isn't available: this run is a single-agent run`: add
  `mode: orchestrated` to the workflow's frontmatter.
- "never made a first tome call": the harness started too slowly. Raise
  `defaults.start_timeout`, or set it to `off`.
- Workflow not found: run `tome validate` with no name from inside the project.
- Trigger not firing: check `tome daemon status`, `tome triggers ls`, then
  `tome triggers fire <workflow> --dry-run`.
- Stuck bus delivery: `tome events ls`, then `tome events retry` or `remove`.
- A node can't be reached (exit 3): `tome node check <node>`.

Don't guess at flags: `tome --help` and `tome <command> --help` list them all.

Create workflows with an agent

Don’t write workflows by hand. Describe what you want to your coding agent, and let it write the file with the tome plugin.

A workflow’s body is instructions for another agent, so an agent is good at writing it. Its skill knows the frontmatter, the trigger fields, how workers, queues and worktrees fit together, and the mistakes that make a run stall or loop. It asks you the questions a workflow depends on, then writes the file and runs tome validate on it. You review the result and run it.

The rest of the guides are reference for what the agent writes. Read them when you want to check its work or understand a run, not before you start.

Set up

  1. Install tome and start the daemon with tome daemon start.

  2. Install the tome plugin in Claude Code:

    /plugin marketplace add banyango/tome
    /plugin install tome@tome
    

    Install tome (for agents) does both steps for you. Other agents can use the skill on its own.

  3. Run /reload-plugins, or start a new session in your project, so it loads.

Ask for a workflow

Say what should happen and what starts it. You don’t need tome’s terms. For example:

Write a tome workflow that runs the tests whenever I change a file under
src/, and has an agent fix anything that fails.
Every weekday at 9am, list branches with no commits in two weeks and write
them to stale-branches.md.
When I add features/<slug>/feature.md, have an agent read the code, break
the feature into tasks, and write them next to it. Then build each task on
its own branch.
Take the "Review a branch in parallel" recipe from the tome docs and adapt it
to this repo.

Starting from a recipe works well: the agent changes the test commands, paths and branch names to fit your project.

Answer its questions

Before it writes anything, the agent asks about what the request and the repo don’t already say:

  • what starts the workflow: you, a file change, a schedule or a message
  • how many agents may run at once. Each is a full agent session, and most laptops handle 1 or 2. Start with 1.
  • how the work is split: one agent for everything, one per item, or a few taking items from a queue
  • where changes go: the current branch, or one branch per item, and whether they’re merged or left for review
  • when it stops, and which commands check the work
  • what to call the workers and their branches

It suggests an answer for each, so you can accept most of them. Then it says back in a few lines what the workflow will do. If that’s not what you meant, say so now: it’s cheaper than a wrong run.

Your answers become params with defaults, such as workers: {type: int, default: 1}, so the next run needs no questions and you can change them with --param.

Review what it wrote

The agent writes .tome/workflows/<name>.md and runs tome validate on it. Read the file before the first run. It’s short, and most of it is plain English. Check that:

  • the trigger matches what you asked for. A file trigger on files the run itself edits starts another run each time.
  • the body never has more workers running than you said
  • each step says what done looks like and what happens if it fails
  • the commands it runs are the ones your project uses

Ask the agent to fix anything that’s off, rather than editing the file yourself. It re-validates after each change.

Try it small

Have the agent start a small first run, such as a single item, and watch it:

tome run my-workflow --param limit=1
tome runs show <id>

For a triggered workflow, fire the trigger by hand instead of waiting for it:

tome triggers fire my-workflow --dry-run
tome triggers fire my-workflow --path src/lib.rs

Every agent runs in a tmux, cmux or herdr session you can watch and steer.

Change it

When a run does something you didn’t want, tell the agent what happened and what you want instead:

The tome run 12 of fix-tests started a second run when it edited src/. Stop
that.
Let fix-tests run two workers at once, and open a PR instead of merging.

The agent can read the run’s steps and logs with tome runs show and tome runs logs, so give it the run id. It changes the workflow, validates it, and tells you what changed. Commit the file when you’re happy with it, so the workflow runs the same way for everyone on the project.

When to edit by hand

Small, obvious changes are fine to make yourself: a default, a cron time, a path in a trigger. Run tome validate <name> afterwards. For anything that changes how the work is split or when the workflow starts, ask the agent.

If you don’t use a coding agent, Write a workflow has every frontmatter option and Recipes has files to copy.

Write a workflow

This page is the reference for workflow files. You rarely need to write one yourself: ask your coding agent, with the tome plugin installed, and it writes and validates the file for you. See Create workflows with an agent. Use this page to check what it wrote, or to make a small change by hand.

A workflow is a Markdown file with YAML frontmatter. Put it in .tome/workflows/ for one project or ~/.tome/workflows/ for all of them. tome workflow new <name> writes a starter file in the right place, and tome validate [name] checks it.

Every example on this page is a real file under docs/examples/ in the repo, and CI runs tome validate on each one.

The smallest workflow

Only name is required.

---
name: minimal
---
Say hello, then finish the run.

Everything after the closing --- is the body. You write it in plain English. The run’s agent reads it and decides what to do, in what order. tome never parses it, except to fill in placeholders and to check them.

Steps

Use ## Headings to name the steps. The run’s agent reports each one to tome as it starts and finishes, and you see them in tome runs show. Steps are not a rigid script: the text can tell the agent to retry, loop or skip, and it will.

Say what each step does, what counts as done, and what should happen if it fails. If nothing says otherwise, a failed step fails the run.

Custom statuses

To track where a run is in your own terms, tell the agent to set a status in the step’s text:

## Review
Set status to InReview.
Read the diff and leave comments.

The agent runs tome step status "InReview", which labels the running step. The run’s custom status follows its active step, separate from the run’s own status (queued, running, …). Both show in tome runs show: custom on the run and a CUSTOM column for each step, or custom_status in JSON.

  • Labels are free-form and kept exactly as written, including case. Several steps can share one.
  • Starting a step that has no status clears the run’s custom status.
  • When steps run in parallel, the run takes the status of the earliest-started step that is still running, which is the parent or group step.
  • When no step is running, tome step status --step "<name>" "<status>" sets the run’s custom status directly. After the run finishes or fails, the last one stays.

Frontmatter fields

tome validate rejects keys it doesn’t know, and points to the line.

name

Required. Letters, digits, _ and -. It’s what you pass to tome run.

description

One line, shown in listings.

mode

single (the default) or orchestrated.

  • single: tome starts one agent that does the whole workflow itself. It can report steps, use queues and publish events, but it can’t spawn workers, groups or worktrees; those commands are refused.
  • orchestrated: tome starts an orchestrator that hands the work to workers. Use it for any workflow that fans out.

tome validate warns when a single workflow’s body talks about workers, worktrees or fanning out, and when it sets keys only an orchestrator uses (orchestrator_harness, orchestrator_model, and the orchestrator and workers placement blocks). See Workflows, runs and steps.

params

Named inputs, filled in from --param key=value on tome run, from a trigger’s params:, or from the default. Each param has:

  • type: string (the default), int, float or bool
  • default: used when nothing sets it. A param without a default is required.
  • description
---
name: summarize
description: Summarize a file into a short note.
params:
  file: {type: string, description: "The file to summarize"}
  words: {type: int, default: 100, description: "Length limit"}
  bullets: {type: bool, default: false}
---
Summarize `{{params.file}}` in at most {{params.words}} words.
Use bullets: {{params.bullets}}. This is run {{run.id}}.

## Read
Read the file.

## Summarize
Write the summary to `summary.md` and finish the run.
tome run summarize --param file=notes.txt --param words=50

In the body, {{params.<name>}} inserts a value and {{run.id}} inserts the run’s id. A placeholder that names no param is an error at validation time. In a workflow started by a trigger, {{trigger.<field>}} inserts details of the event; see Triggers and the message bus.

defaults

Workflow-wide settings.

---
name: defaults-tour
description: Every key under `defaults`.
mode: orchestrated              # orchestrator_* keys apply to orchestrated runs
defaults:
  backend: tmux                 # tmux or cmux
  harness: claude               # agent CLI for workers
  orchestrator_harness: claude  # agent CLI for the orchestrator
  timeout: 30m
  on_failure: notify
  start_timeout: 2m             # or `off`
  layout: split                 # where sessions open; see the sessions guide
---
Do the work described in the steps below.

## Work
Do it.
KeyMeaning
backendtmux, cmux or herdr. Falls back to TOME_BACKEND, then config, then cmux inside cmux, herdr inside herdr, and tmux otherwise.
harnessThe agent CLI for a single run’s agent and for workers. Defaults to claude. Defined in config; see Sessions and backends.
orchestrator_harnessThe agent CLI for the orchestrator (mode: orchestrated only). Defaults to harness. Workers run it too unless harness is set.
modelThe model a single run’s agent and workers run, such as opus. Passed to the harness; unset uses the harness’s own default. Override for one worker with tome worker spawn --model.
orchestrator_modelThe model the orchestrator runs (mode: orchestrated only). Defaults to model.
layoutWhere sessions open. A name (tab, split, workspace) or a block of settings; see Sessions and backends.
start_timeoutHow long the run’s agent and each agent worker have to make their first tome call. A duration like 90s or 2m, or off. The default is 60s. If it passes, tome nudges the agent once and fails it after a second wait.
provider_optsOptions for a provider when the run uses --on <provider>, a map of names to values. --provider-opt overrides them, and they override defaults.provider_opts in config.
timeoutA duration, like 30m, or a number of seconds. tome accepts and records it, but doesn’t enforce a run timeout today.
on_failureA string, accepted and recorded. tome doesn’t act on it today; say in the body what should happen on failure.

tome run my-workflow --harness <name> --model <model> overrides the harness and model for that run, including orchestrator_harness and orchestrator_model. Workers inherit the run’s choices unless overridden with tome worker spawn --harness or --model. The workflow file stays unchanged. See Harnesses for setup.

concurrency and on_conflict

concurrency: N limits how many runs of this workflow go at once. Runs are unlimited by default. When the limit is reached, on_conflict: queue (the default) makes the new run wait, and on_conflict: reject refuses it.

orchestrator

Extra instructions for the orchestrator only, added to its prompt. Use it for how the orchestrator should behave, and keep the body for what the workflow does.

---
name: one-at-a-time
description: Never run twice at once; extra runs wait their turn.
concurrency: 1
on_conflict: queue     # or `reject` to refuse a run when one is going
orchestrator: |
  Keep your messages to the user short. Ask before deleting anything.
---
Do the work.

## Work
Do it.

triggers

A list of rules that start the workflow or signal its runs. This example has one of each kind:

---
name: triggers-tour
description: One of each kind of trigger.
params:
  reason: {type: string, default: manual}
triggers:
  - manual
  - file: "src/**/*.rs"
    on: [created, modified]
    debounce: 5s
    ignore: ["target/**"]
    while_running: queue
    params: {reason: files}
  - cron: "0 9 * * 1-5"
    params: {reason: schedule}
  - on: review.requested
    params: {reason: message}
---
Started because of: {{params.reason}}.

## Work
Do it.

Every field of a trigger is explained in Triggers and the message bus.

Check, then run

tome validate my-workflow
tome run my-workflow --param key=value

tome validate with no name checks every workflow it can see and shows which project workflows override global ones. It exits with code 2 if anything is wrong, and prints each problem with its line number.

For every command and flag, run tome --help.

Triggers and the message bus

Triggers start a workflow without you running tome run, or hand work to a run that is already going. They are listed under triggers: in the frontmatter, and the daemon has to be running to act on them. The examples on this page are files under docs/examples/ that CI validates.

triggers:
  - manual                       # documentation only: `tome run` always works
  - file: "docs/**/*.md"         # files changed
  - cron: "0 9 * * 1-5"          # a schedule
  - on: review.requested         # a message on the bus

Every trigger can also take:

FieldMeaning
tonew (the default) starts a run. running signals runs that are going. running-or-new signals them if any exist, and starts one if not.
paramsValues for the workflow’s params. A required param must be filled here, or tome validate fails.

File triggers

---
name: doc-check
description: Check the docs whenever a Markdown file under docs/ changes.
triggers:
  - file: "docs/**/*.md"
    on: [created, modified]
    debounce: 5s
    ignore: ["docs/drafts/**"]
    while_running: queue
---
These files changed:

{{trigger.paths}}

## Check
Read each changed file and fix broken links and typos. Do not touch any file
that isn't on the list, so this run doesn't start another one.
FieldMeaning
fileA glob. In a project workflow it is relative to the project root. In a global workflow, use an absolute path or one starting with ~/.
oncreated, modified, or both (the default).
debounceChanges within this window, counted from the last change, become one event. The default is 2s.
ignoreGlobs to skip. .git/, .tome/ and gitignored paths are never watched.
while_runningWhat to do with changes that arrive while a run of this workflow is active: queue (the default) waits and starts one more run afterwards, merging later batches into it. parallel starts a run for each batch, subject to concurrency. mute drops them.

In the body, {{trigger.paths}} lists the changed files, and {{trigger.event}} is the kind of event.

tome has no loop guard. A run that edits files that its own trigger watches makes another run. Avoid that with ignore:, with on: [created], or with while_running: mute, or tell the workflow not to touch those files, as the example does.

Cron triggers

---
name: nightly
description: A weekday-morning check of the open branches.
triggers:
  - manual
  - cron: "0 9 * * 1-5"
    params: {base: main}
params:
  base: {type: string, default: main}
---
Started at {{trigger.time}} for the schedule {{trigger.scheduled}}.

## Check
List the branches that are ahead of {{params.base}} and say which have gone
stale.

A cron expression has five fields and uses local time. Times that pass while the daemon is stopped are skipped, not made up. In the body, {{trigger.scheduled}} is the time that was scheduled and {{trigger.time}} is when it fired.

The message bus

Every project has a message bus. Anything can publish to a topic, and any project workflow can subscribe to it.

tome publish review.requested "PR 42: add retry to the uploader"
tome publish review.requested - < details.txt      # payload from stdin
tome publish review.requested "..." --dry-run      # show which workflows would get it

A topic is words separated by dots. A subscription is a pattern: * stands for one word, and ** at the end stands for one or more. review.* matches review.requested but not review.requested.urgent, while review.** matches both.

Every workflow that subscribes to a matching topic gets its own delivery of the event, and it is durable. If the daemon is down when you publish, the deliveries wait, and they are picked up when the daemon starts. What a delivery does depends on to::

  • new starts one run per event. concurrency limits how many run at once, and the rest queue.
  • running gives the event to the runs that are going, on their events-<run id> queue, and nudges the orchestrator to look. If no run is going, the delivery is marked done.
  • running-or-new does one or the other.

The event reaches a new run in the body as {{trigger.topic}}, {{trigger.payload}}, {{trigger.event_id}} and {{trigger.sender}}.

This example receives requests one at a time. concurrency: 1 makes extra ones wait:

---
name: implement
description: Implement one requested feature at a time.
triggers:
  - on: feature.requested
concurrency: 1
---
Implement this feature:

{{trigger.payload}}

## Implement
Make the change on a new branch and commit it.

## Finish
Finish the run successfully, with the branch name as the summary.

Keeping a run alive

Set to: running-or-new and the run gets later events on its events-<run id> queue instead of starting more runs:

---
name: watcher
description: Keep one run going and hand it new work as messages arrive.
triggers:
  - manual
  - on: work.new
    to: running-or-new
concurrency: 1
---
Handle work items as they come in. This run is meant to stay alive.

Start with:

{{trigger.payload}}

## Loop
Do the item. Then pull the next message from your `events-$TOME_RUN_ID` queue with
`tome queue pull events-$TOME_RUN_ID --wait 5m`, ack it, and do that item. When the queue
is empty after the wait, finish the run successfully.

Chaining workflows

tome publishes an event when a run changes state: tome.run.<workflow>.started, .succeeded, .failed and .cancelled, with a JSON payload. On herdr, .blocked is published when an agent is waiting for input, with the run, workflow, session and pane in the payload. Subscribe to one to make a workflow follow another:

---
name: review
description: Review the change that a successful implement run made.
triggers:
  - on: tome.run.implement.succeeded
---
Review the change made by this run. The event says which run it was and
gives its summary:

{{trigger.payload}}

## Review
Read the branch named in the summary and write your findings to `review.md`.

With implement above, publishing feature.requested starts an implement run, and every successful one starts a review.

Chains can loop by accident, so each event records its depth: 0 when you published it, and one more than the event that started the run that published it. Events deeper than 8 are refused. tome validate warns if a workflow subscribes to its own lifecycle events.

Subscriptions work in project workflows only, because a global workflow has no project bus. The tome. prefix is reserved for tome’s events.

Testing a trigger

tome triggers ls                          # armed triggers and when each last fired
tome triggers fire my-workflow --dry-run  # what would happen, with params resolved
tome triggers fire my-workflow            # fire the first trigger that isn't `manual`
tome triggers fire my-workflow --index 2  # a specific trigger (0-based)
tome triggers fire watcher --payload "hello"   # a topic trigger, with a test event
tome triggers fire doc-check --path docs/a.md  # a file trigger, with a changed path
tome triggers disable                     # pause a project's triggers
tome triggers enable

--payload sends a test event to that one workflow only. It is recorded with the sender test.

Looking at the bus

tome events ls                     # topics, and each subscriber's backlog
tome events show <topic>           # events that aren't settled yet
tome events retry <event>          # hand a failed delivery out again
tome events remove <event>         # drop a pending or failed delivery

A delivery is pending, claimed, done, failed or dropped. Resuming a failed run that a topic event started (tome run resume <id>) takes its parked delivery back instead of handing it to a fresh run. tome gc clears events once all their deliveries are settled.

Workers, worktrees and fan-out

The orchestrator does the work of a run by starting workers. This guide covers the commands it uses, and how to write a workflow that uses them. You don’t usually run these yourself. You describe what you want in the workflow body, and the orchestrator knows the commands. The examples on this page are files under docs/examples/ that CI validates.

Workers need an orchestrator, so a workflow that uses them sets mode: orchestrated in its frontmatter. In the default mode: single, the run’s one agent does the work itself, and tome worker, tome group and tome worktree create are refused. See Workflows, runs and steps.

Workers

# an agent worker: gets a prompt and reports back
tome worker spawn --name reviewer --prompt "Review src/lib.rs and report what you find"

# a command worker: everything after `--` is the command
tome worker spawn --name tests -- cargo test

An agent worker starts through a harness (--harness picks one, otherwise the workflow’s defaults.harness, otherwise the one its orchestrator runs). It runs in its own session, and it finishes by calling tome worker done --summary "..." or tome worker fail --summary "...". If it exits without doing either, it is marked failed. A command worker is done when it exits 0, and its summary is the exit code and the last lines of output.

Only the orchestrator starts workers. Workers can’t start workers, so if a task needs splitting, a worker says so in its summary and the orchestrator decides.

Then:

tome worker status [name]    # status, summary, branch and worktree
tome worker wait <name>      # block until it finishes, then print its status
tome worker kill <name>      # stop it; it is marked cancelled

Sessions are closed when a worker finishes. --keep-open keeps one open so you can read the output.

Workers get these environment variables: TOME_RUN_ID, TOME_WORKER_ID, TOME_OUTPUT=json and, with a worktree, TOME_WORKTREE. That is why the commands above need no ids.

Here is a workflow that runs the tests as a command worker and starts an agent only if they fail:

---
name: test-then-fix
description: Run the tests as a command worker, and start an agent to fix any failures.
mode: orchestrated
---
Run the test suite and fix what breaks.

## Test
Spawn a command worker named `tests` that runs `cargo test`, and wait for it.
If it succeeded, finish the run successfully.

## Fix
Spawn an agent worker named `fixer` on its own worktree, told to make the
failing tests pass and to commit its work. Give it the tail of the test
output from the `tests` worker's summary. Wait for it. If it failed, fail the
run.

## Verify
Merge the `fixer` branch, run the tests again as a command worker, and finish
the run with the result.

Worktrees

A worker started with --worktree gets its own git worktree of the project, on a new branch named tome/<run>/<name>. It starts from --base (a branch or commit), or from the current HEAD commit. Uncommitted changes in your checkout are not included.

tome worker spawn --name task-1 --worktree --base main --prompt "Add a --verbose flag"

--branch names the branch instead, to follow your team’s convention or to open a pull request from it. The branch must not exist yet, so put something unique in the name, such as a task id:

tome worker spawn --name t004-renderer --worktree --base main --branch task/004-renderer --prompt "..."

Say how to name branches in the workflow body, and the orchestrator passes --branch for you.

The worker’s working directory is the worktree, and it commits its changes to that branch. tome doesn’t merge anything for you. The orchestrator merges the branches with git, which is why prompts should tell workers to commit before they report done.

tome worktree create <name> [--base <ref>] [--branch <name>] makes a worktree that isn’t tied to a worker.

Worktrees stay after a run ends, so you can look at the branches. A run resumed with tome run resume adopts the old run’s worktrees, so tome worktree create <name>, or a worker spawned with --worktree under its old name, gets the old worktree back with its work. tome gc removes them with old runs, and deletes a branch only if it was merged into its base.

Fan-out and fan-in

Fan-out is starting many workers at once. Fan-in is waiting for them and combining what they did. A group ties the two together:

tome group create reviews --fail-fast     # optional; a group is also created on first use
tome worker spawn --group reviews --name review-1 --prompt "..."
tome worker spawn --group reviews --name review-2 --prompt "..."
tome group wait reviews                   # closes the group and blocks until every member is finished
tome group status reviews                 # every member's result

--fail-fast makes the first failure cancel the other members. tome group close stops a group taking new members.

Example: one reviewer per changed file, then one report.

---
name: fan-out
description: Review every file changed on a branch in parallel, then combine the reviews.
mode: orchestrated
params:
  base: {type: string, default: main, description: "Compare the current branch to this one"}
concurrency: 1
---
Review the files changed since {{params.base}}, one worker per file.

## Split
List the files that changed between {{params.base}} and HEAD. If there are
none, finish the run successfully and say so.

## Review
Create a group named `reviews`. For each changed file, spawn an agent worker
in that group, named `review-<n>`, told to review only that file and to
report its findings as its summary. Then wait for the group.

## Combine
Read every member's summary from the group's status. Write one report to
`review.md`, most serious findings first. If any reviewer failed, list it in
the report and carry on. Finish the run successfully with the path to the
report.

When workers change code, give each its own worktree, then merge in a final step:

---
name: fan-in-worktrees
description: Do independent tasks on separate branches, then merge the branches.
mode: orchestrated
params:
  tasks: {type: string, description: "One task per line"}
  base: {type: string, default: main}
---
Do each of these tasks on its own branch, then merge the results.

{{params.tasks}}

## Implement
Create a group `tasks`. For each task, spawn an agent worker in it with its
own worktree, branching from {{params.base}}:

    tome worker spawn --name task-<n> --group tasks --worktree --base {{params.base}} --prompt "<the task>"

Tell each worker to commit its work to its branch before it reports done.
Wait for the group.

## Merge
Merge the branch of every worker that succeeded into the current branch, one
at a time, and resolve conflicts as they come. Run the project's tests after
the last merge. Fail the run if a worker failed, or if the tests fail, and
say which.

Queues

Use a queue when workers and the orchestrator need to pass work around while they run, instead of at the end. A queue belongs to the project, so it also works from your shell, and a run can leave messages for the next one.

tome queue push tasks "refactor the parser"     # add a message (`-` reads stdin)
tome queue pull tasks --wait 30s                # claim one; exit 3 if there is none
tome queue peek tasks                           # look at what is waiting, claiming nothing
tome queue ack <id>                             # remove it once handled
tome queue close tasks                          # no more messages; pulls report `closed` when drained
tome queue ls                                   # the project's queues and their counts

Outside a run, tome queue uses the project of the directory you are in. Inside a run it uses the run’s project.

A message is text up to about 1 MiB. Put bigger data in a file and send its path. A claimed message that is never acked goes back on the queue when the worker that claimed it finishes, so another worker can pick it up.

A worker reports once, and its session closes when it does, so a worker only works through a queue if its prompt tells it to loop. Say it in the prompt: pull, do the item, ack, and pull again until pull reports closed, then report once. pull exits 3 in two cases, and the JSON status tells them apart: empty means nothing is there yet, and closed means nothing more will come. That is why the orchestrator has to close the queue after its last push. A closed queue stays closed for good, so a workflow that pushes more work in a later round needs a new queue name for that round. Queues outlive runs, so pick names that won’t collide with another run’s, such as tasks-<round>. Workflows that don’t need to stop workers can skip close and have them stop on empty instead.

If every item gets its own worker, you don’t need a queue. Put the item in the worker’s prompt.

Limits and tips

  • A worker’s summary is the main channel back to the orchestrator. Ask for a useful one.
  • There is no limit on how many workers run at once. Each agent worker is a full harness session, so say in the workflow how many your machine can take, for example “never have more than {{params.workers}} workers running”, with a param that defaults to 1 or 2.
  • Worker names must be unique in a run. Without --name, they are w1, w2 and so on. A name shows in the worker’s tab title, in tome runs show and in its branch, so say in the workflow how to name workers, for example “name each worker t<id>-<short title>”.

Sessions and backends

Every agent tome starts runs in a session on a backend. This guide covers choosing the backend, adding agent harnesses, and deciding where sessions open.

Choosing a backend

tome supports tmux, cmux and herdr. It picks one for a run in this order:

  1. defaults.backend in the workflow
  2. the TOME_BACKEND environment variable
  3. backend: in .tome/config.yaml (project) or ~/.tome/config.yaml (global)
  4. cmux, if the daemon runs inside cmux; otherwise herdr, if the daemon or caller is inside herdr; otherwise tmux

tmux

Sessions are ordinary tmux sessions on your default tmux server. List them with tmux ls and attach with tmux attach -t <name>. tmux doesn’t need to be running when you start a run.

cmux

Sessions are tabs, splits and workspaces in the cmux app. cmux only lets its own processes control it, so start the daemon from a terminal inside cmux: tome daemon start. The daemon inherits access from there. A daemon started elsewhere can’t open sessions in cmux.

herdr

Sessions open as tabs, splits and workspaces in the herdr app. tome talks to herdr over its local socket, found from HERDR_SOCKET_PATH, HERDR_SESSION, then herdr.session in ~/.tome/config.yaml, then the default socket (~/.config/herdr/herdr.sock), in that order.

To pin a herdr session, set it in the global config (it isn’t accepted in a project’s config):

herdr:
  session: work

Things that differ from the other backends:

  • Unavailable. If herdr isn’t running, a run fails with backend_unavailable, and you get a notification. A daemon that can’t reach herdr later doesn’t treat that as the agent exiting; it checks again.
  • Agent status. The daemon reads each agent’s status from herdr. tome runs show and tome worker status list it in an AGENT column. When an agent is waiting for input it shows blocked since <time>.
  • Blocked agents. tome publishes tome.run.<workflow>.blocked (see Triggers) when an agent becomes blocked. If it stays blocked for 5 seconds you get a herdr notification, repeated at most once a minute. Notifications also announce a run’s end. Set TOME_NOTIFY=off to turn them off.
  • Attaching. tome session view focuses the session’s workspace in herdr. Herdr sessions on another node can’t be viewed over SSH.

Harnesses

A harness is a command template that tome uses to launch an agent CLI with a prompt. claude (Claude Code) is built in: it runs claude --allowedTools "Bash(tome:*)" -- "<prompt>", so the agent can call tome without asking you each time.

To add or change harnesses, put a harnesses: block in ~/.tome/config.yaml or the project’s .tome/config.yaml:

harnesses:
  claude-opus:
    command: ["claude", "--model", "opus", "--allowedTools", "Bash(tome:*)", "--", "{{prompt}}"]
  aider:
    command: aider --message-file {{prompt_file}}
  codex:
    command: ["codex", "{{prompt}}"]
    model_flag: --model

A harness chooses the agent CLI; backend chooses the terminal multiplexer that hosts it. The codex harness can run inside tmux, cmux or herdr. Install the agent CLI and complete its sign-in before using it with tome. Installing the tome plugin teaches the agent how to use tome; configure its launch command here as well.

For workflows that need answers from you, keep the interactive codex command shown above and state the required questions in the workflow body: “Ask the user these questions and wait for their answers before continuing. Do not infer answers or treat silence as approval.” Attach to the agent’s session to answer. Codex’s --ask-for-approval setting controls command execution approvals; on-request leaves those decisions to the model. It doesn’t require the agent to ask workflow questions. The example leaves the approval policy to your Codex configuration.

A list is an argv: one element per argument, with no shell involved. A string is run with sh -c, with each value shell-quoted. The variables are:

VariableValue
{{prompt}}the agent’s instructions
{{prompt_file}}a file that holds them
{{run_id}}the run’s id
{{session}}the session’s name
{{cwd}}the directory the agent starts in
{{model}}the workflow’s model for this agent, or empty

Models

A workflow picks a model with defaults.model (a single run’s agent, and workers) and defaults.orchestrator_model (the orchestrator, defaulting to model). tome passes it to the harness in one of two ways:

  • model_flag, for a harness whose command is a list: tome inserts <flag> <model> right after the program. The built-in claude harness has model_flag: --model.
  • {{model}}, a variable you put in the command wherever the agent wants the model. It is empty when no model is set.
harnesses:
  codex:
    command: ["codex", "{{prompt}}"]
    model_flag: --model
  aider:
    command: aider --message-file {{prompt_file}} --model {{model}}

A harness that does neither refuses a model, and tome run and tome worker spawn say so. If you redefine claude in config, add model_flag: --model again, or the model can’t reach it. tome worker spawn --model <m> overrides the workflow’s model for one worker.

A project harness replaces a global one of the same name. Pick a harness in a workflow with defaults.harness (a single run’s agent, and workers) and defaults.orchestrator_harness (the orchestrator), or per worker with tome worker spawn --harness. A worker with no explicit harness runs its orchestrator’s.

To change the harness or model for one run without editing the workflow:

tome run my-workflow --harness codex
tome run my-workflow --harness codex --model <model-name>

--harness overrides both defaults.harness and defaults.orchestrator_harness; --model overrides both defaults.model and defaults.orchestrator_model. These choices apply to the main agent and workers. A worker’s explicit --harness or --model still takes precedence. Each run flag overrides only its own setting: changing the harness keeps the workflow’s models unless you also pass --model. Use a model accepted by the selected agent CLI.

Any agent works as long as it can run tome commands. An agent must call tome ready first and tome worker done or fail last, and tome tells it so in its prompt.

If a harness won’t open, run tome harness validate <name> to check its configuration, find its executable on the current PATH, and see the command tome will launch. Add --model <name> to check model forwarding too. This prints a preview without starting the agent; the agent itself opens inside the configured session backend.

tome harness validate                 # every known harness, including claude
tome harness validate codex
tome harness validate codex --model <model-name>

Validation checks whether tome can forward a model, not whether the agent provider supports it. For a shell command template it checks sh and shell syntax; check the agent executable separately. It doesn’t check sign-in or launch an agent. The command exits with code 2 if a check fails and supports --json for structured results.

Placement

Placement is where a session opens. It has these settings:

SettingValues
layouttab (the default) or split
workspaceproject (the default: one shared workspace for the project), focused (whatever is focused when the run starts), own (a new workspace for each session), or a name, which becomes the workspace <project>-<name>
split.directionright, left, down or up
split.sizea percentage like 30%, or a number of cells
fromthe pane a new tab or split opens from: orchestrator, last, first, or caller (cmux or herdr, for the run’s agent or orchestrator: the pane where you ran tome run)
presetthe name of a preset to build on

layout: workspace still works and means workspace: own. In tmux, a workspace is a tmux session, and a tab is a window.

Presets

A preset is a named bundle of placement settings. tab, split and workspace are built in. Define more in config:

layout_presets:
  wide:
    layout: split
    split: { direction: right, size: 40% }

tome layout presets lists all of them. Settings next to preset: override the preset’s own.

Setting placement

Give a run its placement on the command line:

tome run my-workflow --preset wide
tome worker spawn --name review-1 --layout split --direction down --size 30% --prompt "..."

Or in the workflow. Under defaults.layout, agent sets a single run’s agent’s placement, orchestrator sets the orchestrator’s, workers sets the workers’, and a workers list matches each worker’s name against a glob, first match wins:

---
name: review-layout
description: Reviewers open as splits next to the orchestrator; everything else below.
mode: orchestrated
defaults:
  backend: tmux
  harness: claude
  layout:
    preset: split
    split: { size: 30% }
    orchestrator: { layout: tab }
    workers:
      - match: "review-*"
        layout: split
        split: { direction: right }
        from: orchestrator
      - match: "*"
        layout: split
        split: { direction: down }
---
Review the change with one worker per file.

## Review
Spawn workers named `review-<n>`, one per changed file, and wait for them.

Each setting resolves on its own, from the most specific source down. A source that sets only direction leaves every other setting to the next one.

OrderFor workersFor the orchestratorFor a single run’s agent
1tome worker spawn flagstome run flagstome run flags
2the first matching rule under workersthe orchestrator blockthe agent block
3the workers block
4tome run flags

After that, all of them use defaults.layout and its preset, then TOME_LAYOUT, the project’s .tome/config.yaml, the global config, and the built-in default. By default the orchestrator gets a tab in the project workspace, and so does each worker. In cmux and herdr the first session takes the new workspace’s first tab, so there’s no empty tab beside it; the workspace closes with its last tab. In cmux the project workspace is titled after the run that opens it (<project>-<workflow>-<run id>, such as tome-cli-build-42); runs that join it later leave the title alone. With tmux, each worker instead splits below the run’s last pane (split, direction: down, from: last), so a run reads as one tab, unless it sets a workspace without a layout, since the orchestrator isn’t there to split from. Ask for splits with --layout split or layout: split.

from: caller is for the run’s agent or orchestrator only. A worker’s rule can’t set it, and tome worker spawn refuses it.

tome runs show <id> shows where each session ended up, and which source each setting came from, plus any warnings. Split sizes are best effort.

Moving a session

A live session can be moved without restarting it. Settings you don’t give stay as they were:

tome session move 12/orchestrator --layout split --direction right
tome session move 12/review-1 --workspace own

A single-agent run’s main session is <run>/agent, such as 12/agent.

Operating tome

The daemon

tome daemon start      # in the background
tome daemon status     # prints pid, version and uptime; exit code 3 if it isn't running
tome daemon stop
tome daemon run        # in the foreground, for debugging or a service manager

tome start and tome stop are shorthands for tome daemon start and tome daemon stop.

To keep it running across logins and reboots, register it as a service:

tome daemon install     # launchd on macOS, systemd --user on Linux
tome daemon uninstall

The service restarts the daemon if it crashes. tome daemon stop is a clean exit, so it stays stopped.

If the daemon restarts, runs that were in progress fail with the reason daemon_restart, since nothing was watching them.

State and logs are in ~/.tome: the DuckDB database, the daemon’s log (daemon.log) and each run’s logs (runs/<id>/). Set TOME_HOME to use another directory.

With cmux, start the daemon from a terminal inside cmux; see Sessions and backends.

Starting and stopping runs

tome run my-workflow --param key=value   # attached: streams progress, Ctrl-C cancels
tome run my-workflow --detach            # print the run id and return
tome run my-workflow --harness codex --model <model-name>
tome run cancel <id>                     # kill its sessions; worktrees are kept

--harness and --model override the workflow’s choices for the main agent and workers, including the orchestrator. Configure the harness first; see Sessions and backends.

Resuming a failed run

A run that failed or was cancelled can be picked up where it stopped, instead of starting over:

tome run resume <id>                          # attached, like tome run
tome run resume <id> --detach                 # print the new run's id and return
tome run resume <id> --start-at "the Review step, but rerun the tests first"
tome run resume <id> --layout tab             # placement flags; without any, the old run's

Resuming starts a new run that is linked to the old one. The old run is left as it was. The new run:

  • uses the old run’s workflow snapshot, params and mode, so later edits to the workflow file don’t reach it. {{run.id}} in the workflow still means the old run’s id, so branch and file names made from it match.
  • follows the workflow’s concurrency and on_conflict rules like any new run.
  • gets a Resuming section in its agent’s prompt. It lists each step’s latest outcome across the earlier runs, how those runs ended and where to start. Without --start-at, that is the step that failed or was interrupted, or else the step after the last one that finished. --start-at passes your own words to the agent as they are. The agent is told to skip finished steps and to check what an interrupted step left behind before redoing it.
  • adopts the old run’s worktrees that still exist, with the same paths and branches. tome worktree create <name> with a name the old run used returns the same worktree, as does spawning a worker with --worktree under its old name. The Resuming section lists the adopted worktrees and any that are gone.
  • for an orchestrated run, tells the orchestrator how each old worker and group ended, including workers that were cut off mid-task. Workers aren’t relaunched. The orchestrator spawns the ones it still needs.
  • takes back the old run’s trigger delivery if the old run was started by a topic event that is now parked (failed). The delivery settles when the new run ends, so you don’t also need tome events retry.

Only failed and cancelled runs can be resumed, and each run only once. If the resumed run fails too, resume that one; the error for the old id names the newest run of the chain. A run whose project directory is gone can’t be resumed. tome runs show links the chain both ways with resumed_from and resumed_as (resumes run N and resumed as run N in human output).

A workflow or script can automate recovery the same way, for example by resuming a run once when it fails with tome run resume <id> --detach --json.

Looking at runs

A run has its own status (queued, running, succeeded, failed or cancelled) and, if the workflow sets one, a custom status such as InReview that follows the step it is on. tome runs show lists the custom status as custom on the run and in a CUSTOM column for each step. See Custom statuses.

tome runs list                            # newest first
tome runs list --status running
tome runs list --workflow my-workflow --limit 50
tome runs show <id>                       # status, custom status, steps, history, workers, worktrees, sessions, resume links
tome runs show <id> --snapshot            # the workflow as it was when the run started
tome runs logs <id> --tail 100            # each session's log
tome runs logs <id> --step Review

Add --json to any command for machine-readable output. Setting TOME_OUTPUT=json does it for every command.

Querying

tome query runs one read-only SQL statement against the run database:

tome query "select id, workflow_name, status, reason from runs order by id desc limit 5"
tome query "select run_id, name, status, summary from workers where status = 'failed'"
tome query "describe runs"

The tables are runs, steps, step_events, workers, worker_events, worker_groups, worktrees, sessions, queues, queue_messages, logs, bus_events, deliveries, trigger_fires and projects. Only select, with, describe and similar statements are allowed.

Cleaning up

Nothing is deleted on its own. tome gc deletes finished runs older than an age, with their logs and worktrees:

tome gc --older-than 14d --dry-run    # show what would go
tome gc --older-than 14d

Ages look like 12h, 7d or 2w. A resume chain (a run and the runs that resumed it) is collected as one: only once its newest run is finished and older than the age, and then all of it. A worker’s branch is deleted only if it was merged into its base. Unmerged branches are kept, and gc lists them. Bus events are cleared once all their deliveries are settled.

Removing workflows

tome workflow rm my-workflow            # the project's workflow
tome workflow rm my-workflow --global   # the one in ~/.tome/workflows

There’s no prompt. The file is deleted, and runs and their logs stay. The command refuses while a run of the workflow is running or queued. --force overrides that, and is also how to remove one while the daemon is down.

Triggers

tome triggers ls shows armed triggers, and tome triggers disable and enable pause and resume a project’s. See Triggers and the message bus.

Troubleshooting

“tome daemon is not running”. Run tome daemon start.

A run fails with orchestrator_exited or worker_exited. The agent’s session ended without reporting. Read its log with tome runs logs <id>. Common causes are a harness that can’t start (a wrong command, or not logged in) and an agent that was closed by hand. Once the cause is fixed, tome run resume <id> picks the run up where it stopped.

Runs failed with daemon_restart. The daemon stopped while they were running. Resume each with tome run resume <id>.

A run fails because an agent “never made a first tome call”. The agent didn’t start in time. tome nudges it once, then fails it. If your harness is slow to start, raise defaults.start_timeout in the workflow, or set it to off.

Sessions don’t open in cmux. The daemon has to be started from inside a cmux terminal. Stop it and start it again from there.

A harness won’t start or refuses a model. Run tome harness validate <name> --model <model-name> to check configuration, executable availability and model forwarding without launching the agent. Omit --model when none is selected. For shell templates, check the agent executable separately; validation checks sh and shell syntax. Complete the agent CLI’s sign-in, and make sure the daemon’s PATH includes it.

A workflow isn’t found. tome validate lists every workflow tome can see. Project workflows are looked up from the current directory upwards, so run commands from inside the project.

tome validate fails. It names the file and line. A frontmatter key it doesn’t know, a {{placeholder}} that names no param, and a param without a default that a trigger doesn’t fill are the usual causes.

A trigger isn’t firing. Check that the daemon is running and that tome triggers ls shows the trigger, then try tome triggers fire <workflow> --dry-run.

A file trigger keeps starting runs. The workflow is probably editing files its own trigger watches. Add ignore:, on: [created] or while_running: mute.

Something is stuck in the bus. tome events ls shows backlogs, and tome events retry or remove fixes a delivery.

For every command and flag, run tome --help or tome <command> --help.

Remote nodes

A node is another machine with tome on it that you can reach over SSH: a Mac mini on the desk, a build box, a cloud VM. With one set up, the commands you use here work there too. Add --on <node> to start a run on it, watch it, list its runs or publish to its bus. A project can also forward its events to a node, so a run finishing here starts a workflow there.

tome doesn’t run a server for this. Each command runs ssh <node> tome rpc --stdio, which talks to the node’s daemon (and starts that daemon if it isn’t running). Your SSH keys, ~/.ssh/config and jump hosts all work as they do for ssh.

Adding a node

The node needs tome installed, the same version as here, and a key-based SSH login. Then:

tome node add mini kyle@mini.local

This adds the node to ~/.tome/config.yaml and checks it. If tome isn’t on the node’s PATH for a non-interactive shell (common with Homebrew or ~/.cargo/bin), give its path:

tome node add mini kyle@mini.local --tome /opt/homebrew/bin/tome

The config entry looks like this:

# ~/.tome/config.yaml
nodes:
  mini:
    ssh: kyle@mini.local        # anything `ssh` accepts, including a Host from ~/.ssh/config
    tome: /opt/homebrew/bin/tome  # optional; default `tome`
    projects:                   # optional; where projects are on the node
      my-app: ~/src/my-app

Node names use a-z, 0-9, - and _. local is reserved: it means this machine.

tome node ls             # every node: reachable, version, daemon, round trip
tome node check mini     # step by step, with a fix for the first thing that fails
tome node rm mini

tome node check checks SSH, then tome on the node, then the version, then the node’s daemon, then whether the current project is there. When something fails it prints what to fix. To keep the node’s daemon running across reboots, run tome daemon install on the node.

Running commands on a node

tome run build --on mini              # resolve and run `build` on mini, attached
tome run build --on mini --detach
tome runs list --on mini
tome runs show mini:12                # `<node>:<id>` names a run on a node
tome runs logs mini:12 --tail 50
tome run cancel mini:12
tome daemon status --on mini
tome triggers ls --on mini
tome events ls --on mini

Set TOME_NODE=mini to make --on mini the default for a shell. --on local overrides it.

These commands only work on this machine, so they refuse --on: validate, harness validate, workflow …, layout …, node …, plugin …, and daemon start|run|install|uninstall. Run them on the node over ssh. The commands agents use inside a run (ready, step, worker, run finish and so on) refuse it too, because they act on the run the agent is in.

Which project

A command run inside a project acts on the node’s copy of that project. tome looks for it in two places, in order:

  1. nodes.<node>.projects.<directory name> in the config.
  2. The same path relative to your home directory. ~/src/my-app here is ~/src/my-app there.

If neither has a .tome/ directory, the command exits 4 and says what to set.

Which workflow

tome run <name> --on mini resolves the name on the node, using the node’s project and global workflows, and prints the path it used. tome doesn’t copy workflow files. To run your latest changes on the node, commit them and pull them there. When the node’s checkout is at a different commit, or has uncommitted changes, tome prints a warning and starts the run anyway. If the workflow exists here but not on the node, the error says so. Workflow paths and --from caller don’t work with --on.

An attached remote run keeps going if the SSH connection drops. Check on it with tome runs show mini:<id>.

Watching a remote run

tome session view mini:12            # the orchestrator's session (or the single agent's)
tome session view mini:12/w1         # a worker's
tome run build --on mini --view      # start it, then open its session

When the node uses tmux, tome attaches to its session with ssh -t. Inside cmux, that happens in a new tab, and you can place the tab with --preset, --layout, --workspace, --direction and --size. Anywhere else, it runs in the current terminal. You can’t view a remote cmux session this way, because cmux is a desktop app. A remote herdr session can’t be viewed over SSH either; add the node with herdr machine add instead.

Listing runs everywhere

tome runs list --nodes

This lists runs from this machine and every node, with a NODE column. --limit applies per node. Nodes that can’t be reached are listed as warnings on stderr, and the command still exits 0.

Forwarding events

A project can forward bus events to nodes in its .tome/config.yaml:

bus:
  forward:
    - topic: tome.run.build.succeeded   # a topic pattern, as in `on:`
      to: mini                          # a node, or a list of them
    - topic: deploy.*
      to: [mini, ci]

Each matching event gets a delivery to each node, listed in tome events show as → mini. The daemon sends it by publishing the event to the same topic on the node’s copy of the project. The node’s on: triggers then handle it.

  • Waiting for the node. If the node can’t be reached, the delivery stays pending and is retried with backoff (at most a minute apart) for 24 hours. After that it becomes failed and you get a notification. tome events retry and tome events remove work on these deliveries too: --workflow mini names the forward to mini.
  • Unknown nodes. If a rule names a node that isn’t in this machine’s config, the delivery fails straight away and you get a notification. tome validate warns about such rules.
  • Signing in. A daemon run as a launchd or systemd service may not see your ssh-agent. If the first attempt can’t authenticate, you get a notification. Set an IdentityFile for the node in ~/.ssh/config.
  • On the node, the sender is node <origin> (<original sender>), for example node laptop (run 12). The event’s depth carries over, so the limit of 8 also applies to chains that cross machines. An event is never forwarded back to the machine it came from.
  • Built-in events. tome.* events keep their topic, so on: tome.run.build.succeeded on the node works as if the run had been there. Their JSON payload gets a node field naming the machine the run was on.

An agent can also hand work to another machine directly:

tome publish --on mini deploy.requested "build 1.4.2 is ready"

Machines made on demand

--on also takes a provider: a plugin that makes a machine for the run and releases it afterwards.

tome run build --on cloud-vm --provider-opt size=large

tome registers the machine as an ephemeral node named <provider>-<id>, runs the node checks on it once, runs the workflow there and releases the machine whatever happens. A machine that fails its check is released and the command exits 3 with the fix for the failing step. tome runs list keeps a record of each released machine’s run as <provider>:<id> (released).

With --keep, the machine stays and shows up as an ephemeral node (tome node ls), so --on cloud-vm-<id> works until you release it with the provider’s tools. tome node rm only forgets it. A provider name can’t be used for a node: tome node add refuses it.

Errors

ExitMeaning
1The node runs a different protocol version. The message says which side to upgrade.
2--on on a command that only works here, or --on and a <node>:<id> reference that disagree.
3The node can’t be reached, or tome isn’t installed on it. Run tome node check <node>. For a provider: it had no machine ready in time, or the machine failed its check.
4The project or workflow isn’t on the node.

Plugins

A plugin is a folder with a plugin.yaml manifest and its own executables. Plugins let features that don’t belong in the tome binary, such as a cloud VM provisioner, live outside it. A plugin can add subcommands, hooks, workflows and harnesses.

Plugins are installed by you, for the whole machine, into ~/.tome/plugins/<name>/. There are no project-level plugins, so a repo you clone can never add code that tome runs. Without plugins installed, tome behaves exactly as before.

Installing

tome plugin install ./my-plugin                       # a folder
tome plugin install https://github.com/me/plugin.git  # a git URL
tome plugin install git@github.com:me/plugin.git#v1.2 # a branch, tag or commit after `#`

Install checks the manifest and copies the folder (or clones the repo) into ~/.tome/plugins/<name>/. It runs no plugin code and has no build step, so a plugin that needs building ships its built binaries.

It refuses with exit code 2 when:

  • the manifest is invalid, or names an unknown hook (worker.place is reserved and reported as “not yet supported”);
  • the plugin speaks a different plugin protocol than this tome, or its tome: range doesn’t include this version. The error says which side to upgrade;
  • a file the manifest names (a subcommand’s executable, a workflow) is missing, outside the folder, or not usable;
  • a subcommand, provider, workflow or harness name is already taken by a built-in command, a configured node, one of your workflows or harnesses, or another plugin. The error names both sources.

Managing

tome plugin ls               # name, state, version, hooks, and any problems
tome plugin update [name]    # pull git installs (all of them without a name)
tome plugin disable <name>
tome plugin enable <name>
tome plugin rm <name>

tome plugin ls shows each plugin as enabled, disabled, incompatible or invalid. A plugin that stops fitting after a tome upgrade stays installed and shows as incompatible, with a hint naming which side to upgrade. A name that clashes after the fact, because you added a workflow with the same name or an update brought one in, is listed under the table.

tome plugin update pulls git installs. If the new version is invalid, incompatible or clashes with a name, tome puts the old version back and reports the reason. Plugins installed from a path are skipped; remove and install them again.

Plugins belong to the machine, so these commands refuse --on (exit 2). Run them on the node over ssh.

The manifest

name: cloudvm               # the folder name; lowercase letters, digits, `_` and `-`
version: 0.1.0              # optional
description: Cloud VMs      # optional
protocol: 1                 # the plugin protocol this plugin speaks (this tome speaks 1)
tome: ">=0.7, <1"           # optional range of tome versions
command: bin/cloudd         # the process that handles hooks and providers
subcommands:
  cloud: bin/cloud          # `tome cloud …`; a path inside the plugin folder
hooks:                      # a list of names, or a map with options
  provide: { timeout: 10m }
  release:
  run.before_start: { on_error: allow, timeout: 30s }
  run.finished:
providers: [cloud-vm]       # names usable as `--on cloud-vm`
workflows: [provision]      # workflows/provision.md in the plugin folder
harnesses:                  # same entries as `harnesses:` in config.yaml
  mine: ["mine-agent", "{{prompt}}"]

command is the path of the plugin’s long-running process, inside the plugin folder. It is required when the plugin declares hooks or providers. A plugin that only adds subcommands, workflows or harnesses needs none.

Hooks: provide, release, run.before_start, run.started, run.finished, daemon.started and daemon.stopping. providers needs both provide and release, and the other way round. on_error (allow or reject, default reject) only applies to run.before_start.

Workflows use the usual format, and the name in the frontmatter must match the name listed in the manifest. Names must be unique across your own definitions and every plugin; tome never silently picks one.

A plugin owns its configuration and secrets. tome has no config schema for plugins and doesn’t store their secrets.

Subcommands

A subcommand listed under subcommands: runs as tome <name> …. tome replaces itself with the plugin’s executable, so the terminal passes straight through and the plugin can be interactive. The executable gets the rest of the arguments, plus these environment variables:

VariableValue
TOME_PLUGINThe plugin’s name.
TOME_PLUGIN_DIRThe plugin’s folder.
TOME_PLUGIN_DATAA folder the plugin may keep its own state in (~/.tome/plugins/<name>/data). It survives updates.

The plugin calls back into tome through the ordinary CLI. Subcommand names can’t be those of built-in commands.

The process and its protocol

When the daemon starts, it starts each enabled plugin that has hooks, running its command as one long-lived child. The two sides speak newline-delimited JSON-RPC 2.0 over the child’s stdin and stdout. Write nothing else to stdout; use stderr for logs, which go to ~/.tome/plugins/<name>/plugin.log.

  1. tome sends initialize first, with protocol, tome_version, plugin and state_dir. The plugin answers {"protocol": 1} within 10 seconds. A different protocol marks the plugin incompatible and it isn’t restarted.
  2. tome then sends one request per hook, with the hook’s name as the method, and waits for the reply up to the hook’s timeout (10 seconds unless the manifest says otherwise).
  3. A request that takes a while may send {"method": "progress", "params": {"id": <request id>, "message": "…"}} notifications first. For provide they’re shown to the person who ran the command.
  4. The plugin can send tome one request: notify with title and message, which shows a desktop notification, prefixed with the plugin’s name.

If the plugin exits, tome restarts it with a growing delay. After five failures in a row, the plugin is marked failed and you’re notified. A call to a plugin that isn’t running fails at once, so a broken plugin never holds up tome. tome plugin ls shows the live state as running, starting, restarting, failed or incompatible, with the last error and the log path under the table.

Hooks

HookWhenThe plugin’s reply
run.before_startBefore a run starts or resumes.Required; see below.
run.startedAfter a run has started.Ignored.
run.finishedWhen a run ends.Ignored.
daemon.startedOnce, when the daemon is up.Ignored.
daemon.stoppingWhen the daemon shuts down.Ignored.
provide, releaseSee Providers.

The notification hooks never wait for the plugin. Their requests carry run_id, workflow, project and node; run.started adds params and cause, and run.finished adds status (succeeded, failed or cancelled), reason and summary. daemon.started carries pid, version, socket and home.

run.before_start

Every plugin that declares it is asked, in order, before a run starts. The request has workflow, workflow_path, project, params, env, cause, resumes (the run’s id when this is a resume) and node. The reply is one of:

{"action": "allow"}
{"action": "reject", "reason": "over budget"}
{"action": "modify", "params": {"size": "large"}, "env": {"REGION": "eu"}}
  • reject refuses the run with exit code 1 and the reason.
  • modify changes the run’s params and adds environment variables for its sessions. Each plugin sees the run as the ones before it left it. Names starting with TOME_ are reserved. A changed param must still be valid for the workflow. Changes to a resumed run are ignored.
  • If the plugin fails or times out, the run is rejected, unless the hook says on_error: allow.

What a plugin changed is stored with the run and shown by tome runs show: the params before and after, and the names (never the values) of the variables it set.

Checking a plugin

tome plugin check cloudvm

Checks, in order: the manifest, compatibility, the files it names, name clashes, and that the process starts and answers initialize. It stops at the first failure, says how to fix it and exits 2. It starts a separate process for the check, so it works whether or not the daemon is running.

Notifications

tome notify <title> <message> shows a notification on this machine through the daemon, by the same path a plugin’s notify request takes. It is for scripts as well as plugins.

Harnesses

tome harness ls lists every harness tome knows and where each comes from: built in, your config, or a plugin.

Providers

A provider makes a machine on demand, runs a workflow on it and throws it away. A plugin declares the provider names it handles, and --on accepts them as if they were nodes:

tome run build --on cloud-vm
tome run build --on cloud-vm --provider-opt size=large --provider-opt region=eu

--on <name> is a provider when an active plugin declares that name under providers:. Otherwise it’s a node, as before. A provider name can’t also be a configured node: plugin install and tome node add both refuse the clash.

What tome does

  1. Calls the plugin’s provide hook and waits until the machine is ready. The plugin may send progress messages, which tome prints to stderr. The wait is limited by the hook’s timeout (default 10 minutes).
  2. Registers the machine as an ephemeral node called <provider>-<id> and runs the same checks as tome node check, once.
  3. Runs the workflow there like on any node, attached.
  4. Reads the run’s final status, summary and the tail of its logs from the machine and stores them locally (see Records).
  5. Calls release, whatever the outcome, and forgets the node.

If provide fails or times out, or the machine fails its check, tome calls release and exits 3. For a failed check the message says which step failed and how to fix it. Pressing Ctrl-C while waiting releases the machine too. A release that fails is reported on stderr and as a notification, and is not retried. Its timeout is the hook’s timeout, 120 seconds by default.

Options for provide

--provider-opt KEY=VALUE is repeatable and later ones win. Defaults come from defaults.provider_opts, in the workflow’s frontmatter or in config.yaml:

# ~/.tome/config.yaml or a project's .tome/config.yaml
defaults:
  provider_opts:
    size: small
    region: eu

The command line beats the workflow, which beats config. Values may be strings, numbers or booleans, and reach the plugin as strings. --provider-opt without a provider is an error.

What the plugin implements

provide receives {provider, opts, workflow, project} and returns when the machine is ready:

{"id": "i-0abc", "ssh": "root@203.0.113.7", "tome": "/usr/local/bin/tome", "projects": {"my-app": "/work/my-app"}}

id (letters, digits, ., _, -; up to 64 characters) and ssh are required. tome and projects are optional and mean what they do for a node. The machine needs tome installed, the project in place, and a key-based login.

release receives:

FieldMeaning
provider, idWhat to release. id is null if provide never answered.
causefinished, provide_failed, check_failed or interrupted.
optsThe options provide got.
run{run, status, summary, reason} of the run, or null if it never started.
logsWhere the logs are: node, ssh, paths (log files on the machine) and stub (the local record). null if there was no run.

Release must tolerate being called for a machine that is already gone.

Keeping the machine

--keep skips the record and the release, and leaves the machine registered as an ephemeral node so you can look around:

tome run build --on cloud-vm --detach --keep
tome runs list --on cloud-vm-i-0abc
tome runs show cloud-vm-i-0abc:12

--detach and --view need --keep, because releasing a machine under a run that is still going would kill it. tome never releases a kept machine. Release it yourself with the provider’s own tools. tome node rm <name> only makes tome forget it.

Records of released machines

tome writes one JSON file per released machine into ~/.tome/released/: the run’s status, summary and the last 40 lines of each step’s log. tome runs list shows them after the live runs as <provider>:<id> (released), and the released array in --json output has the details. --status, --workflow and --limit apply to them. They aren’t listed when you use --on.

Ephemeral nodes live in ~/.tome/ephemeral/ while a machine exists.

Workflows, runs and steps

Workflow

A workflow is a Markdown file that describes a process. It has two parts:

  • Frontmatter: YAML between --- lines. It names the workflow and sets parameters, defaults, triggers and limits.
  • Body: plain English. tome never parses it. An agent reads it.

Workflows live in two places:

LocationScope
.tome/workflows/ in a projectthat project
~/.tome/workflows/every project

If a project workflow and a global one share a name, the project one wins. That makes copying a workflow to a new project a matter of copying one file. tome workflow new <name> writes a starter file, tome workflow ls lists the ones you can run, and tome validate checks your files and reports problems with line numbers.

Step

A step is a named unit of work: a ## Heading in the workflow body, written in plain English. Steps are not a fixed script. The run’s agent decides the order, and it can branch, loop or retry as the text says.

The run’s agent reports each step to tome with tome step start "<name>", then tome step done or tome step fail. That is how a step’s status and history end up in the run’s record. When the workflow says to set a status, the agent runs tome step status "<status>", and the run’s custom status follows its active step; see Custom statuses.

Mode: one agent or an orchestrator

Each run is carried out by one main agent. tome starts it in its own visible session, gives it a built-in prompt (the tome commands it may use and its duties), and gives it the workflow body. The frontmatter’s mode says which kind of agent that is:

modeThe run’s agentCan it delegate?
single (the default)one agent (session role agent) that does the whole workflow itselfno: tome worker, tome group and tome worktree create are refused
orchestratedan orchestrator that coordinatesyes: it spawns workers, groups and worktrees

Either way the agent reports steps and must end the run with tome run finish. Both can use queues, tome publish and tome events. A single agent runs defaults.harness and defaults.model; an orchestrator runs defaults.orchestrator_harness and defaults.orchestrator_model, which fall back to those.

Use single unless the workflow fans out to workers. Set mode: orchestrated when it does. tome validate warns when a single workflow’s body talks about workers, worktrees or fanning out, or when it sets keys only an orchestrator uses.

If the agent exits without finishing, the run fails with the reason agent_exited (orchestrator_exited for an orchestrator).

Run

A run is one execution of a workflow. Start one with tome run <workflow>, or let a trigger start it. A run has a status:

StatusMeaning
queuedwaiting for a free slot
runningthe run’s agent is working
succeededthe agent finished it successfully
failedthe agent failed it, or it stopped without finishing
cancelledsomeone cancelled it

A failed or cancelled run can be resumed with tome run resume <id>. That starts a new run from the old one’s saved workflow, and its agent is told what the earlier run finished and where to start. See Resuming a failed run.

concurrency in the frontmatter caps how many runs of a workflow go at once. When it is full, on_conflict: queue (the default) makes a new run wait, and on_conflict: reject refuses it.

When the run starts, tome fills in the placeholders in the body: {{params.<name>}}, {{run.id}} and, for runs started by a trigger, {{trigger.<field>}}. The filled-in workflow is saved with the run.

Run state is the only thing tome stores. It lives in an embedded DuckDB database with each run’s steps, history and logs. Inspect it with tome runs, or query it with tome query.

Files on disk and git worktrees carry data between steps.

The daemon

The daemon is a long-running tome process. It:

  • starts runs, and launches each run’s orchestrator,
  • records the step progress the orchestrator reports,
  • watches for triggers (file changes, cron times, messages) and starts or signals runs,
  • creates and watches the sessions that agents run in,
  • recovers when it restarts: runs that were in progress are failed.

The daemon does not decide what a workflow does. It doesn’t decide step order either. The orchestrator does that.

Every tome command that touches runs talks to the daemon over a local socket in ~/.tome. If the daemon isn’t running, those commands stop with an error that says how to start it.

tome daemon start     # in the background
tome daemon status    # exit code 3 if it isn't running
tome daemon stop
tome daemon install   # start at login (launchd on macOS, systemd --user on Linux)

tome start and tome stop are shorthands for tome daemon start and tome daemon stop.

A daemon that crashes is restarted by the login service. tome daemon stop is a clean exit, so it stays stopped.

When it stops, the daemon refuses new requests, and attached tome run or watch commands end with a “shutting down” error. It gives its background work up to 5 seconds to finish, then closes the database. Anything still stuck after that, such as a terminal multiplexer that doesn’t respond, is abandoned. Runs it left in progress are failed when the daemon next starts.

With cmux, start the daemon from a terminal inside cmux. cmux only lets its own processes control it, and the daemon inherits that from where it started. See Backends.

See Operating tome for more.

Triggers and the message bus

Triggers

A trigger is a rule in a workflow’s frontmatter that makes the daemon react to something. The reaction is to start a new run, or to signal runs of that workflow that are already going.

There are four kinds:

KindFires when
manualnever on its own. It documents that you start the workflow by hand; tome run always works.
filefiles matching a glob are created or modified. Changes are batched into one event.
crona five-field cron time arrives, in local time. Times missed while the daemon was down are skipped.
onan event is published on a topic that matches a pattern.

to: says what a trigger does: new (the default) starts a run, running signals runs that are going, and running-or-new signals if any run is going and starts one otherwise. A trigger can also set params: for the run it starts. Event details are available in the body as {{trigger.*}} placeholders.

tome has no loop guard. A run that changes files its own trigger watches starts another run. Use ignore:, on: [created], or while_running: mute to prevent that.

The message bus

Each project has a message bus. Anything can publish an event to a topic, and any workflow can subscribe to a topic with an on: trigger.

  • A topic is dot-separated words, like review.requested. The tome. prefix is reserved.
  • A subscription is a pattern. * matches one segment and a trailing ** matches one or more.
  • An event has text as its payload, up to about 1 MiB, and a sender: you, a run, a worker, or tome itself.
  • Each subscribing workflow gets its own durable delivery of every matching event. A delivery is pending, claimed, done, failed or dropped.

Events publish with tome publish <topic> <text>.

tome publishes events too: tome.run.<workflow>.started, .succeeded, .failed and .cancelled, plus .blocked on herdr. Subscribing to these lets one workflow chain onto another.

A run that was started by an event carries its depth, one more than the event that started it. Events deeper than 8 aren’t delivered, which stops two workflows from triggering each other forever.

Event subscriptions work in project workflows only.

See Triggers and the message bus for examples.

Primitives

Primitives are the building blocks the orchestrator uses to split work up. Each is a tome command; the orchestrator calls them, and you can too.

Worker

A worker is an agent or a command that the orchestrator starts to do part of a run’s work, in its own session.

  • An agent worker gets a prompt and starts through a harness. It reports back with tome worker done or tome worker fail, each with a summary. If it exits without reporting, it counts as failed.
  • A command worker runs a shell command. Exit code 0 is done and anything else is failed.

Only the orchestrator starts workers, so the workflow needs mode: orchestrated. Workers can’t start workers. A worker has a name that is unique in the run, and a status: pending, running, done, failed or cancelled.

Worktree

A worktree is a git worktree for a worker, so it can change files without touching your checkout or other workers. It gets its own branch, named tome/<run>/<worker> unless you name it, which starts from a base you choose. When the worker finishes, its branch holds its commits, and the orchestrator merges the branches it wants.

Group

A group tracks a set of workers together. Add workers to a group when you start them, then wait for the whole group. This is how fan-out works: start many workers, then fan in when they are all done. A group can fail fast, so the first failure cancels the other members.

Queue

A queue is a named list of messages that belongs to the project. Runs, workers and you can all push messages onto it and pull them off, and it outlives any run. A message is text up to about 1 MiB. Pulling claims a message, and acking removes it; a message that was claimed but never acked goes back on the queue when the worker that claimed it finishes. tome queue peek shows what is waiting without claiming anything.

Signals from outside a run, such as a trigger that signals a running run, arrive on that run’s events-<run id> queue.

See Workers, worktrees and fan-out.

Backends

A backend is the terminal multiplexer tome uses as its user interface. tome starts every agent in a session on a backend, so you can watch it, type into it, and attach from anywhere.

tome supports three backends:

BackendSessions are
tmuxtmux sessions, panes and windows. Attach with tmux attach -t <name>.
cmuxtabs, splits and workspaces in the cmux app.
herdrworkspaces, tabs and splits in the herdr app.

A run’s backend comes from, in order: defaults.backend in the workflow, the TOME_BACKEND environment variable, backend: in .tome/config.yaml or ~/.tome/config.yaml, then cmux if the daemon runs inside cmux, herdr if it runs inside herdr, and tmux otherwise. Herdr is also chosen when tome run is started from a herdr pane, even if the daemon isn’t inside herdr. tome finds the herdr server from HERDR_SOCKET_PATH, HERDR_SESSION, then herdr.session in ~/.tome/config.yaml, then the default socket (~/.config/herdr/herdr.sock), in that order. See Sessions and backends.

Sessions

A session is one terminal that runs an agent or a command. The orchestrator and each worker get their own. When an agent finishes, its session closes, unless the worker was started with --keep-open.

Where a session opens is its placement: a layout (tab or split, or a workspace of its own), a workspace, a split direction and size, and an anchor pane. Placement can be set on the command line, in presets, and in the workflow. See Sessions and backends.

Harnesses

An agent starts through a harness: a command template that launches an agent CLI with a prompt. Claude Code (claude) is built in. Others are defined in config, so tome isn’t tied to one agent. Workflows pick one with defaults.harness, and can give the orchestrator a different one with defaults.orchestrator_harness.

For example, configure a codex harness and use tome run my-workflow --harness codex to select it for one run. --model selects a model for that run. Both flags apply to the main agent and workers, including the orchestrator in an orchestrated workflow. The harness runs on the chosen tmux or cmux backend. See Harnesses for configuration and validation.