Routemark

Developer docs

How Routemark works, for people who check a claim before they trust it. Where this page and the tool disagree, the tool is right: routemark --help is the tool.

The idea in one minute

You work in Claude Code or Codex. That model stays in charge: it decides what to hand over, writes the task and reviews the result. Routemark gives it a command line for handing the task to a cheaper coding agent, and for taking the result back safely.

The agent never touches your repository. It works in a private copy. When it finishes, Routemark captures its result as one exact tree, runs your checks on that tree, and shows the exact diff. After the Controller's review, that tree and nothing else is merged. Whether it asks you first is between you and your Controller.

Routemark is a command-line tool and a set of skills. It is not a model, not a harness and not a service. Without a key it calls no service. With one, it sends the facts of the projects that name an organisation to that organisation's record.

The words

ControllerThe model in charge: Claude Code or Codex. It runs the Routemark commands for you.
HarnessA coding-agent program, such as OpenCode or Prime. It talks to its own provider under its own login.
DelegateA harness with a provider and a model, doing one task.
ProjectOne git repository that you have added to Routemark.
RunOne attempt at one task. It has an id, a record and a kept log.
TreeThe delegate's result, captured as one exact git tree. Checks, the review and the merge all work on that tree.
ChecksCommands your project runs to test itself, such as pytest -q.
ReviewThe Controller's verdict on one exact tree: pass, pass with findings, or fail.

The life of a run

Your Controller runs these for you. You can also run them by hand; the quickstart does exactly that once, with the output at each step.

1. Delegate

routemark delegate --project "$HOME/my-project" --task "$HOME/task.txt" --harness opencode --provider kimi-code-plan-global --model k3

The names in that line are an example: put your own project, task file, provider and model in. --harness, --provider and --model go together, all three or none. Left out, Routemark uses the last combination that worked on this project, or else on this machine.

It prints the run id before anything is spent. It then makes a private copy of the project, starts the harness in that copy and waits. When the run ends having done work, the same command judges it.

2. Judge

Judging captures the result as one exact tree, runs every check on that tree, scans what could merge for credentials, and prints the files changed, each check's result, the exact diff that would merge, and the merge command filled in.

3. Review

The Controller reads the diff and records a verdict on that tree. Nothing merges without a review on record, unless you waive it yourself.

4. Merge, or abandon

routemark merge merges exactly the tree that was judged. routemark abandon ends an attempt that will not merge and reclaims what it used. Either way the run's record stays.

Projects

A project is one git repository. Two repositories side by side are two projects, and a task across both is two delegations.

routemark project add "$HOME/my-project"

At a terminal it shows the one file, asks open or sandboxed, and asks before creating it. Run by your Controller it shows the file and creates nothing. --yes creates it without asking. The file is routemark.toml, at the top of the repository:

# Routemark project file. This is the only file a project needs.
containment = "open"    # what a delegate can reach: open or sandboxed
data_policy = "unrestricted"    # where this project's data may go

[checks]
commands = []    # how this project's checks run, for example ["pytest -q"]
containmentopen, the default: the delegate runs as you and can see what you can see. Or sandboxed: it can change only its own private copy. Set it when you add the project, with --containment sandboxed.
data_policyunrestricted is the only value this version honours. Routemark will not run a project with another value.
readableOptional, sandboxed projects only: folders outside the copy that a delegate, and the project's checks, may read and never write.
[checks] commandsThe commands that test this project. They run on the judged tree.

Routemark reads the file whether git tracks it or not. Commit it as you would any project configuration.

The task

The task is a file. It is either plain text, holding the task in your own words, or a contract named .toml, with task = "..." and, when you want them, scope, acceptance or allowed.

The task reaches the harness on standard input and never on its command line. A command line can be read by every user of the machine.

Routemark adds its own standing words to every task: deploy, publish or release nothing live; install nothing on the machine outside the copy; how to ask a question and wait; work only in the current directory. A sandboxed delegate is also told what the sandbox allows.

Carrying on from an earlier run

routemark delegate --after with an earlier run's id carries on from that run. The delegate is given the review's findings and continues the same session where the harness can. Add --instruction with a file to give it a new instruction of your own, for example after a run you stopped by hand.

Checks, and hidden checks

The project's own checks, from routemark.toml, judge the work. They run on the judged tree, not in your repository.

A hidden check is one the delegate is never shown and cannot alter. Give it with --hidden when you delegate. It is either a .toml file holding commands = ["..."], or a script, run by its #! line: sh, bash, zsh or python.

Keep a hidden check outside the project directory. A file inside it can be in the delegate's copy, and is in its way either way. Routemark says so and stops before anything is spent.

Hidden checks are the exception. They earn their place only for what the project's own checks cannot tell you.

Judging, reviewing, merging

The verdict

A verdict is pass, pass-with-findings or fail, and it belongs to one exact tree. It records the harness and model that reviewed. Routemark records what it is told there and proves none of it.

A finding either blocks a merge or does not. A finding that does not block can be accepted at merge time, with a reason; each one must be, or nothing merges.

You may ask for a second, adversarial review. It is kept beside the Controller's review and never replaces it, and a merge never reads it.

The merge

  • The branch has not moved: the judged tree becomes one new commit on the branch.
  • The branch has moved: the judged change is applied on top of the branch as it is now, in Routemark's own working area. The combined tree must pass the project's checks before it merges.
  • A check that did not pass: each check of the judged result that did not exit 0, or could not run, has to be accepted on the merge, with a reason: routemark merge --accept-check 2:"why". Until each is accepted nothing merges. A passed review does not cover it, and neither does waiving the review. The reasons are kept on the merge's record. After the branch has moved, the combined tree's checks count instead. Every one must pass, and none can be accepted.
  • A conflict: nothing merges and the files are named.
  • Work you have not committed: never overwritten. A merge that would overwrite it stops before anything changes and names the files.

Credentials

What could merge is scanned for credentials. A finding the delegate brought in holds the merge. One that was already in your project is reported and does not block. No credential's value is ever printed.

Two things only a person can do

Waiving a review and clearing a credential finding work only when a person runs them at a terminal. They work only when standard input is a terminal. A Controller is told to hand them to you.

  • routemark merge --waive-review, with who is waiving and why. It waives the review and nothing else: the credential checks still hold, and what merges is still exactly what was judged.
  • routemark merge --clear-credential, with the finding's number, a reason and who is clearing it. It clears that finding and nothing else: the review still has to pass or be waived. It also needs --tree, and every finding must be cleared.

While a run is going

routemark progressHow a run is going, or how it went. Facts only. It never locks, signals or stops a run.
routemark waitWaits until the run ends, or up to a bound you give, without signalling it. It exits 3, with the run still going, when the bound passes or as soon as the delegate asks a question.
routemark runsEvery run that has not finished, and what waits on your decision. For each it says whether its process is alive; "cannot tell" is a real answer.
routemark stopStops a running attempt. It signals only the process Routemark started for it, and records who stopped it.
routemark answerAnswers a question the delegate asked, from a file.

Nothing stops a run except a person, or the budget your Controller gave it. A run that looks stuck is reported, with the facts. A run that reaches its budget is held, never killed: it can be judged as it stands, carried on with a higher budget, or stopped.

Questions from the delegate

A delegate that cannot decide something writes its question as plain text into its ask folder, which it knows as $ROUTEMARK_ASK_DIR, and waits. routemark progress shows the question with a number, and routemark answer writes your answer where the delegate looks for it. A wait on that run returns as soon as the question is asked, and prints it with the routemark answer line filled in. The delegate reads an answer only while it is still waiting. If the run ended without one, routemark delegate --after carries it on with your answer as its instruction.

When a run fails

Routemark names no cause. It states what it can measure and quotes the rest as data: the exit code, whether the run did any work, the harness's own reported error, how its last step ended, what it said on standard error, and where the kept log is.

A run did work when it reported a session, a finished step or some usage. A run failed when its exit code was not 0, or it did no work at all, or its last call ended in the harness's own reported error.

A run whose review said fail is not a failure. It was sent back, and is counted on a line of its own.

For a run that failed, or one sent back, the Controller reads the facts, names the cause, and records whose it was:

delegateThe harness or model did the work badly.
contractThe task as written was the problem.
environmentA missing login, a rejected key, a used-up quota or a full disk: nobody's work going wrong.
routemarkA control of Routemark's stopped good work. That is a defect, and is counted as one.

routemark report opens with the delegated tasks: merged at the first attempt, merged after more than one, abandoned, and in flight. Beneath them it counts the attempts sent back by review and the attempts that failed, each on a line of its own, and says whose they were. It gives the share of all delegated attempts put down to Routemark or the task contract, with the number not yet attributed beside it. The charter's target is under 10%.

It also counts what a merge overrode: credential findings cleared, reviews waived and failed checks accepted. These are stated, none is called a defect, and they are outside the share.

A hard stop names the rule it applied, with an id such as MERGE-4. Quote the id when you report a problem.

What it cost

After the tasks and their attempts, routemark report gives your Controller's own usage per merged task: the tokens it spent on each delegated task, from the moment it started on the task to the merge. The figure is in fresh tokens, which are input, output and cache writes. Cache reads are shown beside it and never counted in it.

It is worked out once, when the task closes at the merge or at routemark direct end, from your Controller's own session records on this machine and Routemark's run records, and kept on the run's record. A task closed before that rule is worked out when you ask. It is never estimated. Where a session is gone or cannot be read, the figure is absent and says so.

The baseline

Beside it stands the same figure for work your Controller did itself. That is recorded with routemark direct begin before the work and routemark direct end after it. Nothing is netted and nothing is turned into money.

The console

routemark console

A page in your browser: what is waiting on you, what is running, what finished, and the track record. Without a key, it changes nothing of yours. It keeps Routemark's own index of closed tasks, in Routemark's home. Its Organisations page keeps the key you give it, and a note of what was sent. It ends when its terminal does. routemark console --no-open prints the address without opening a browser.

The sandbox

On a sandboxed project, the delegate and the project's checks run inside macOS's sandbox. Routemark checks that the sandbox holds on this machine before each piece of work starts. On another operating system a sandboxed project is refused when work starts.

What it fences

  • The delegate can change only its own private copy, and a private home and temporary folder Routemark makes beside it. It cannot write anywhere else.
  • It cannot read your files, apart from the two exceptions below.
  • It cannot run a server or listen on a port.
  • Your global instructions and skills reach it only where they are among the harness's own files below.

What it does not fence

  • The internet is open. The contents of the copy can still leave the machine. A token in your environment, or in the project itself, is not covered. On Pi or Prime, an OpenRouter, Anthropic or OpenAI key you exported for the run's provider stays outside the sandbox: the delegate is given a stand-in, unless you also export a subscription's token the harness takes first, such as ANTHROPIC_OAUTH_TOKEN: then both are inside.
  • The harness's own files (its settings, its code), and for some harnesses its login, can be read, because the harness needs them to start and to call a provider. Routemark never opens them.
  • What the project lists as readable can be read. That opens Routemark's fence only. The harness's own rule about paths outside the copy still applies to the delegate's tools.
  • This machine's localhost can be reached, and so can the services macOS offers to every program. Routemark closes the clipboard and does not claim the rest.
  • It fences reading and writing, not running. A program outside the readable folders can still start, but it is denied what it keeps under your home.

Some of macOS's own tools ignore the private temporary folder and ask for the system's own, which the sandbox does not grant. mktemp -d fails there. Keep temporary files inside the copy.

An open project

An open project is not sandboxed. The delegate can see what you can see, your files and your network alike. There the guarantees hold against mistakes and ordinary misbehaviour, not against a delegate deliberately attacking Routemark's own files.

The launcher

The launcher is a background service on macOS that starts each delegate for Routemark, instead of the Controller starting it.

It exists because of Codex. Codex runs its commands in a sandbox of its own, and macOS will not start a second sandbox inside it. So a Codex Controller reaches a sandboxed project through the launcher, or by running routemark delegate outside its sandbox with your approval.

routemark launcher install

This shows what it creates and asks before it creates anything. routemark launcher remove stops it and deletes what install made; running delegates are left alone. On Linux, and on Windows through WSL2, there is no launcher yet.

Where everything lives

Everything Routemark keeps is under one directory, ~/.routemark. Set ROUTEMARK_HOME to put it elsewhere; routemark home prints where it is. On Windows that directory is in your Linux home inside WSL2, not under C:\Users.

runs/One folder per run: its record and its kept log. The track record your reports are read from.
work/The private copies delegates work in.
skills/The copy of the skills your Controller reads.
used/The combinations that worked on this machine.
organisations/The key and receipts the console's Organisations page keeps.
closedThe index of closed tasks.

Outside it is only what you agree to:

  • the routemark.toml in each project you add;
  • the links routemark skills install --yes makes into your Controller, in ~/.claude/skills and ~/.codex/skills;
  • if you install the launcher, its entry in ~/Library/LaunchAgents and its log in ~/Library/Logs/routemark.

A project's routemark.toml may carry three more lines, for a team:

name = "orders-app"The project's name in the organisation's record, in place of its last folder, so a copy at another path is still the same project.
org = "northwind"The organisation this project's facts may go to; a project without it sends nothing, whatever key is on the machine.

task_text_in_summary = true sends each task's own words and its kind in words, instead of a hash.

routemark summary --json prints this machine's whole document of facts, and routemark summary --json --org "$ORG", where ORG is the name an org line gives, prints only what would be sent to that organisation. Each holds run ids, project names, the harness, provider and model, usage, outcomes, verdicts and the facts of a merge; never the task text (it leaves only where a project's own file says so), the contract, file names, diffs, a delegate's questions or logs. A key is put in at one place only, the Organisations page of routemark console: Check key, then Connect and start sending.

routemark disk says how much each project is using. routemark clean-up takes back what merged and abandoned attempts left behind; a run's record stays. Routemark never reaches into where a harness keeps its sessions. On a sandboxed project they live in the attempt's private home and go with it at merge or abandon. routemark disk --sessions lists them, with the harness's own delete command for each.

The skills

The Controller knows how to use Routemark because it reads Routemark's skills. Nine ship with it:

routemarkThe loop itself: whenever work might go to a delegate, or an attempt needs judging, reviewing, merging or abandoning.
routemark-setupWhen there is no suitable delegate on the machine, or you want to try something new.
routemark-fixWhen a delegation failed, or ended having done no work.
routemark-claude-codeFor Claude Code acting as the Controller.
routemark-codexFor Codex acting as the Controller.
routemark-opencodeSetting OpenCode up as a delegate, or after a run with it failed.
routemark-primeSetting Prime up as a delegate on an open project, or after a run with it failed.
routemark-skill-buildingBefore writing a new skill or changing one.
routemark-skill-reviewAfter writing a skill, before you are asked to link it.

The one ordering rule: install or upgrade Routemark, then run routemark skills install, and only then open a Controller session. An upgrade leaves the old copy of the skills in place until you run it again.

A harness is supported by a skill that knows how to install it, configure it, launch it, read it and diagnose it, plus a thin adapter in Routemark. Adding a harness means adding those two things.

Every command

Every command that starts or finishes a step prints the next one to run, filled in. --help on any of them says the rest.

homeSay where Routemark keeps its state.
discoverWhat has worked here, then what is on this machine.
tryAsk a harness for a one-word reply, to check a provider and model name. It calls the provider, so it costs one short prompt. Nothing requires it.
project addShow what adding a project creates, then create it.
checksRun a project's checks as they stand, where a judgement would run them; no run is made.
skillsinstall, remove and check the skills the Controller reads.
delegateHand one task to a delegate, which works in a private copy. A run that did work is then judged.
progressHow a run is going, or how it went.
waitWait on a run until it ends, or up to a bound.
runsEvery unfinished run, and what waits on your decision.
stopStop a running attempt.
answerAnswer a question a running delegate asked.
attributeSay whose a failed attempt, or one its review sent back, was.
judgeCapture a run's result as one exact tree and run every check on it.
verdictRecord who reviewed the captured tree and what they decided.
mergeMerge exactly the tree that was judged.
abandonEnd an attempt that will not merge, and reclaim it.
directbegin and end a piece of work the Controller does itself, so its cost is measured.
reportYour Controller's own usage per merged task, the baseline, attempts per merged task, what the delegates used, and whose the failures and the work sent back were.
consoleThe same, as a page in your browser.
summaryPrint the facts that would leave this machine for an organisation, as JSON.
diskWhat Routemark is using, per project.
clean-upReclaim what merged and abandoned attempts left behind.
launcherinstall or remove the background service that starts delegates on macOS.

Platforms and limits

  • macOS: open and sandboxed projects, and the launcher.
  • Linux: open projects only. No sandbox and no launcher yet.
  • Windows: through WSL2 only, as Linux. The project, Routemark, the harness and the Controller's own process all live on the Linux side; keep the project under your Linux home, never under /mnt/c. Measured: Claude Code's command line inside WSL2, the Claude desktop app with its session's environment set to WSL, and Codex's command line inside WSL2. A Controller on the Windows side can drive Routemark through wsl, with an approval at every command. Its own usage is not measured. The Windows walk says each step.
  • Controllers: Claude Code and Codex.
  • Delegates: Pi and Prime on open and sandboxed projects; OpenCode on open projects, and on sandboxed ones only up to 1.x: 2.0 runs on open projects only until OpenCode offers a way to run without listening on a port. A delegate paid by a subscription login runs on open projects only, except on OpenCode 1.x, which is given its saved logins; a subscription's token you export, such as ANTHROPIC_OAUTH_TOKEN, reaches a sandboxed Pi or Prime, inside the sandbox.
  • Models: Routemark picks none and concludes nothing from a harness's list of models. A model can work without being listed. A name is settled by trying it, with routemark try.
  • Codex as the Controller: its own sandbox stops a delegate harness writing its state and reaching the network, so it is started with both opened; the install steps give the command. It also asks your approval before every routemark merge, because its sandbox protects .git. Approving is correct. On Linux and in WSL2 it asks before every Routemark command that writes, the first delegate included, because its Linux sandbox makes your home read-only; approving is correct there too.
  • These are beta releases, numbered 0.1.0b.

Found something here that is wrong? Open an issue, with what you ran and what it printed. A way round one of the guarantees is reported privately: SECURITY.md says how.