# 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](agents/skill.md), 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: ```sh title=Terminal curl -fsSL https://raw.githubusercontent.com/banyango/tome/main/install.sh | sh tome daemon start ``` Then follow the [quickstart](quickstart.md) to add a workflow and watch it fire, and [install the tome plugin](agents/skill.md) 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](guides/create-with-an-agent.md) shows how to have your agent write, run and fix workflows. - [Remote nodes](guides/nodes.md) shows how to add a machine, run workflows on it and forward events to it. - [Recipes](recipes.md) has workflows for common jobs. Ask your agent to adapt one to your project. - The [guides](guides/write-a-workflow.md) cover every option, and [How it works](concepts/workflows.md) explains the pieces. - Run `tome --help`, or `tome --help`, for every command and flag. - For agents, the docs are also published as plain text: [`llms.txt`](https://banyango.github.io/tome/llms.txt) is an index, and [`llms-full.txt`](https://banyango.github.io/tome/llms-full.txt) is every page in one file. --- # Install ## One command ```sh 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](https://github.com/banyango/tome/releases), - 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. | Variable | Effect | | --- | --- | | `TOME_INSTALL_DIR` | Install here instead of `~/.local/bin`. | | `TOME_VERSION` | Install a specific release, like `v0.1.0`, instead of the latest. | ```sh 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. ```sh 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 ```sh tome --version tome --help ``` ## Prerequisites To run a workflow you need two more things: - **A terminal multiplexer**: [tmux](https://github.com/tmux/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](https://claude.com/claude-code) works out of the box. Other agents can be added in config; see [Sessions and backends](guides/sessions.md). `git` is also needed for workflows that give workers their own worktrees. Next: [Quickstart](quickstart.md). To have your coding agent install tome and the tome plugin for you, see [Install tome (for agents)](agents/install.md). --- # 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](install.md), 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: ```sh 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](guides/operating.md). ## 2. Add the workflow tome looks for workflows in `.tome/workflows` in your project. Create a starter file: ```sh cd my-project tome workflow new plan-feature ``` That writes `.tome/workflows/plan-feature.md`. Replace its contents with this: ```markdown title=.tome/workflows/plan-feature.md --- 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//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-`. 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//tasks.md` from the feature file and its investigator's summary. Give each task a `### . ` 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](guides/create-with-an-agent.md). 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: ```sh tome validate plan-feature tome triggers ls ``` ## 3. Write down a feature Describe a small change you actually want in this project. For example: ```sh 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: ```sh 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: ```sh 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: ```sh 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`](recipes.md#plan-a-feature-then-build-it) 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: ```sh tome publish feature.planned export-csv ``` To try a trigger without touching files, fire it by hand: ```sh 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 - [Create workflows with an agent](guides/create-with-an-agent.md): describe what you want and let your agent write the workflow. - [Recipes](recipes.md): implementing planned features, fixing failing tests, reviewing branches, and more. - [Triggers and the message bus](guides/triggers.md): schedules, messages, and chaining workflows together. - [Write a workflow](guides/write-a-workflow.md): every frontmatter option. --- # 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](guides/create-with-an-agent.md). | Recipe | Starts when | | --- | --- | | [Plan a feature, then build it](#plan-a-feature-then-build-it) | a feature file is added, then when its plan is ready | | [Fix failing tests](#fix-failing-tests) | a source file changes | | [Review a branch in parallel](#review-a-branch-in-parallel) | you run it | | [Implement, then review](#implement-then-review) | a message arrives, then when the first run succeeds | | [Split work across branches](#split-work-across-branches) | you run it | | [A weekday-morning check](#a-weekday-morning-check) | a schedule | | [Keep one agent on call](#keep-one-agent-on-call) | messages arrive | | [Tidy docs as you write](#tidy-docs-as-you-write) | a doc file changes | | [A software factory](#a-software-factory) | a 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`: ```markdown title=.tome/workflows/plan-feature.md --- 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: ```markdown title=.tome/workflows/implement-feature.md --- 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: ```sh 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. ```markdown title=.tome/workflows/fix-tests.md --- 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. ```markdown title=.tome/workflows/fan-out.md --- 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. ``` ```sh 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`. ```markdown title=.tome/workflows/implement.md --- 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. ``` ```markdown title=.tome/workflows/review.md --- 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`. ``` ```sh 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. ```markdown title=.tome/workflows/fan-in-worktrees.md --- 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. ``` ```sh 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. ```markdown title=.tome/workflows/nightly.md --- 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. ```markdown title=.tome/workflows/watcher.md --- 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. ``` ```sh 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. ```markdown title=.tome/workflows/doc-check.md --- 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"](https://newsletter.pragmaticengineer.com/) that The Pragmatic Engineer described inside OpenAI. | Workflow | Starts on | Publishes | | --- | --- | --- | | `factory-build` | `factory.requested` | `factory.ship` or `factory.review-needed` | | `factory-deploy` | `factory.ship` | `alert.outage` if the rollout regresses | | `factory-perf` | every hour, or `alert.latency` | `factory.requested` for each fix | | `factory-sevbot` | `alert.outage` or `incident.question` | `incident.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: ```markdown title=.tome/workflows/factory-build.md --- 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: ```sh 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: ```markdown title=.tome/workflows/factory-deploy.md --- 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: ```markdown title=.tome/workflows/factory-perf.md --- 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: ```markdown title=.tome/workflows/factory-sevbot.md --- 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. ``` ```sh 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](guides/write-a-workflow.md) covers every frontmatter option, and [Triggers and the message bus](guides/triggers.md) 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 ```sh 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 ```sh 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](../install.md#build-from-source). ## 3. Run the installer ```sh 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`: ```sh curl -fsSL https://raw.githubusercontent.com/banyango/tome/main/install.sh | TOME_VERSION=v0.1.0 sh ``` ## 4. Check it's on the PATH ```sh 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 ```sh 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](../guides/sessions.md). - `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 ```sh 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](skill.md) teaches agents to write, run and debug tome workflows. If you're Claude Code, install it for every project: ```sh 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: ```sh 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: ```sh 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](../quickstart.md). --- # 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](https://agentskills.io) 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.md). [Install tome (for agents)](install.md) installs tome and the plugin together. ## Claude Code In a Claude Code session, add the tome marketplace and install the plugin: ```text /plugin marketplace add banyango/tome /plugin install tome@tome ``` Or from your shell: ```sh 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: ```sh 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: ```text /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](../guides/create-with-an-agent.md) 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: ```sh 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: ```sh codex plugin marketplace add . ``` Enable the plugin for this project in `.codex/config.toml`: ```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`](https://banyango.github.io/tome/llms-full.txt), or paste the file below into its instructions. ## Asking an agent to install it Paste this into Claude Code: ```text 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: ````markdown title=plugin/skills/tome/SKILL.md --- 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](../agents/skill.md). 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](../install.md) and start the daemon with `tome daemon start`. 2. [Install the tome plugin](../agents/skill.md) in Claude Code: ```text /plugin marketplace add banyango/tome /plugin install tome@tome ``` [Install tome (for agents)](../agents/install.md) 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: ```text Write a tome workflow that runs the tests whenever I change a file under src/, and has an agent fix anything that fails. ``` ```text Every weekday at 9am, list branches with no commits in two weeks and write them to stale-branches.md. ``` ```text 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. ``` ```text Take the "Review a branch in parallel" recipe from the tome docs and adapt it to this repo. ``` Starting from a [recipe](../recipes.md) 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: ```sh 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: ```sh 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: ```text The tome run 12 of fix-tests started a second run when it edited src/. Stop that. ``` ```text 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](write-a-workflow.md) has every frontmatter option and [Recipes](../recipes.md) 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](create-with-an-agent.md). 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. ```markdown title=.tome/workflows/minimal.md --- 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: ```markdown ## 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](workers.md). 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](../concepts/workflows.md#mode-one-agent-or-an-orchestrator). ### `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` ```markdown title=.tome/workflows/summarize.md --- 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. ``` ```sh 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](triggers.md). ### `defaults` Workflow-wide settings. ```markdown title=.tome/workflows/defaults-tour.md --- 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. ``` | Key | Meaning | | --- | --- | | `backend` | `tmux`, `cmux` or `herdr`. Falls back to `TOME_BACKEND`, then config, then cmux inside cmux, herdr inside herdr, and tmux otherwise. | | `harness` | The agent CLI for a single run's agent and for workers. Defaults to `claude`. Defined in config; see [Sessions and backends](sessions.md). | | `orchestrator_harness` | The agent CLI for the orchestrator (`mode: orchestrated` only). Defaults to `harness`. Workers run it too unless `harness` is set. | | `model` | The 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_model` | The model the orchestrator runs (`mode: orchestrated` only). Defaults to `model`. | | `layout` | Where sessions open. A name (`tab`, `split`, `workspace`) or a block of settings; see [Sessions and backends](sessions.md). | | `start_timeout` | How 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_opts` | Options for a [provider](plugins.md#providers) when the run uses `--on <provider>`, a map of names to values. `--provider-opt` overrides them, and they override `defaults.provider_opts` in config. | | `timeout` | A duration, like `30m`, or a number of seconds. tome accepts and records it, but doesn't enforce a run timeout today. | | `on_failure` | A 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](sessions.md#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. ```markdown title=.tome/workflows/one-at-a-time.md --- 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: ```markdown title=.tome/workflows/triggers-tour.md --- 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](triggers.md). ## Check, then run ```sh 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](../concepts/daemon.md) has to be running to act on them. The examples on this page are files under `docs/examples/` that CI validates. ```yaml 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: | Field | Meaning | | --- | --- | | `to` | `new` (the default) starts a run. `running` signals runs that are going. `running-or-new` signals them if any exist, and starts one if not. | | `params` | Values for the workflow's `params`. A required param must be filled here, or `tome validate` fails. | ## File triggers ```markdown title=.tome/workflows/doc-check.md --- 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. ``` | Field | Meaning | | --- | --- | | `file` | A glob. In a project workflow it is relative to the project root. In a global workflow, use an absolute path or one starting with `~/`. | | `on` | `created`, `modified`, or both (the default). | | `debounce` | Changes within this window, counted from the last change, become one event. The default is 2s. | | `ignore` | Globs to skip. `.git/`, `.tome/` and gitignored paths are never watched. | | `while_running` | What 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 ```markdown title=.tome/workflows/nightly.md --- 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. ```sh 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: ```markdown title=.tome/workflows/implement.md --- 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: ```markdown title=.tome/workflows/watcher.md --- 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: ```markdown title=.tome/workflows/review.md --- 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 ```sh 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 ```sh 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](../concepts/workflows.md#mode-one-agent-or-an-orchestrator). ## Workers ```sh # 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: ```sh 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: ```markdown title=.tome/workflows/test-then-fix.md --- 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. ```sh 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: ```sh 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`](operating.md#cleaning-up) 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: ```sh 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. ```markdown title=.tome/workflows/fan-out.md --- 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: ```markdown title=.tome/workflows/fan-in-worktrees.md --- 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. ```sh 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): ```yaml title=~/.tome/config.yaml 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](triggers.md#chaining-workflows)) 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`: ```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](../agents/skill.md) 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`](https://developers.openai.com/codex/cli/reference/) 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: | Variable | Value | | --- | --- | | `{{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. ```yaml 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: ```sh 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. ```sh 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: | Setting | Values | | --- | --- | | `layout` | `tab` (the default) or `split` | | `workspace` | `project` (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.direction` | `right`, `left`, `down` or `up` | | `split.size` | a percentage like `30%`, or a number of cells | | `from` | the 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`) | | `preset` | the 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: ```yaml 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: ```sh 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: ```markdown title=.tome/workflows/review-layout.md --- 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. | Order | For workers | For the orchestrator | For a single run's agent | | --- | --- | --- | --- | | 1 | `tome worker spawn` flags | `tome run` flags | `tome run` flags | | 2 | the first matching rule under `workers` | the `orchestrator` block | the `agent` block | | 3 | the `workers` block | | | | 4 | `tome 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: ```sh 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 ```sh 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: ```sh 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](sessions.md). ## Starting and stopping runs ```sh 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](sessions.md#harnesses). ## Resuming a failed run A run that failed or was cancelled can be picked up where it stopped, instead of starting over: ```sh 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](write-a-workflow.md#custom-statuses). ```sh 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: ```sh 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: ```sh 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 ```sh 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](triggers.md). ## 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: ```sh 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: ```sh tome node add mini kyle@mini.local --tome /opt/homebrew/bin/tome ``` The config entry looks like this: ```yaml # ~/.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. ```sh 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 ```sh 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 ```sh 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 ```sh 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`: ```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: ```sh tome publish --on mini deploy.requested "build 1.4.2 is ready" ``` ## Machines made on demand `--on` also takes a **provider**: a [plugin](plugins.md#providers) that makes a machine for the run and releases it afterwards. ```sh 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 | Exit | Meaning | |---|---| | `1` | The 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. | | `3` | The 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. | | `4` | The 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 ```sh 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 ```sh 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 ```yaml 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: | Variable | Value | | --- | --- | | `TOME_PLUGIN` | The plugin's name. | | `TOME_PLUGIN_DIR` | The plugin's folder. | | `TOME_PLUGIN_DATA` | A 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 | Hook | When | The plugin's reply | | --- | --- | --- | | `run.before_start` | Before a run starts or resumes. | Required; see below. | | `run.started` | After a run has started. | Ignored. | | `run.finished` | When a run ends. | Ignored. | | `daemon.started` | Once, when the daemon is up. | Ignored. | | `daemon.stopping` | When the daemon shuts down. | Ignored. | | `provide`, `release` | See [Providers](#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: ```json {"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 ```sh 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: ```sh 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](#records-of-released-machines)). 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`: ```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: ```json {"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](nodes.md#adding-a-node). The machine needs tome installed, the project in place, and a key-based login. `release` receives: | Field | Meaning | | --- | --- | | `provider`, `id` | What to release. `id` is `null` if `provide` never answered. | | `cause` | `finished`, `provide_failed`, `check_failed` or `interrupted`. | | `opts` | The options `provide` got. | | `run` | `{run, status, summary, reason}` of the run, or `null` if it never started. | | `logs` | Where 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: ```sh 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: | Location | Scope | | --- | --- | | `.tome/workflows/` in a project | that 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](../guides/write-a-workflow.md#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: | `mode` | The run's agent | Can it delegate? | | --- | --- | --- | | `single` (the default) | one **agent** (session role `agent`) that does the whole workflow itself | no: `tome worker`, `tome group` and `tome worktree create` are refused | | `orchestrated` | an **orchestrator** that coordinates | yes: it spawns [workers](primitives.md#worker), 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](triggers.md) start it. A run has a status: | Status | Meaning | | --- | --- | | `queued` | waiting for a free slot | | `running` | the run's agent is working | | `succeeded` | the agent finished it successfully | | `failed` | the agent failed it, or it stopped without finishing | | `cancelled` | someone 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](../guides/operating.md#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](primitives.md#worktree) 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](triggers.md) (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. ```sh 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](backends.md). See [Operating tome](../guides/operating.md) 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: | Kind | Fires when | | --- | --- | | `manual` | never on its own. It documents that you start the workflow by hand; `tome run` always works. | | `file` | files matching a glob are created or modified. Changes are batched into one event. | | `cron` | a five-field cron time arrives, in local time. Times missed while the daemon was down are skipped. | | `on` | an 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](../guides/triggers.md) 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](../guides/workers.md). --- # 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: | Backend | Sessions are | | --- | --- | | `tmux` | tmux sessions, panes and windows. Attach with `tmux attach -t <name>`. | | `cmux` | tabs, splits and workspaces in the cmux app. | | `herdr` | workspaces, 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](../guides/sessions.md#herdr). ## 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](../guides/sessions.md). ## 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](../guides/sessions.md#harnesses) for configuration and validation.