Skip to content
rev0Docs

The auto-runner

The runner polls To Do for tickets assigned to claude-code (REV0_ASSIGNEE) and runs an agent in each ticket’s working directory, up to Max concurrent agents at once (Settings > Workspace > Board: 8 once setup has run, seeded by REV0_MAX_PARALLEL before that). It starts with serve --with-runner, or on its own with rev0 run. Every variable named here is also in Configuration.

  • auto: permission_mode=acceptEdits and the full standard toolset (Read, Edit, Write, Bash, web search and fetch, subagents, skills, the kanban tools). The agent implements and calls finish_ticket or request_action.
  • plan_required, flow_plan and spa_plan: permission_mode=plan, so no edits. The agent submits a plan and stops in Pending Action for your approval; accepting it in the UI re-queues the ticket.

Accepting a plan also switches the ticket to auto: the plan is agreed, so from then on the agent is implementing rather than iterating on it, and the Mode picker says so. Want a second plan inside the same ticket? Pick a plan mode again (or ask the agent, which asks for the switch itself): that re-arms the sign-off, and the next run plans the remaining work in the mode it planned in the first time. Picking a mode never moves or starts the ticket, it only says what the next turn does, so start that turn yourself with a reply or by moving the ticket to To Do.

When the agent reaches for a tool outside its pre-approved set, the run parks for you; see Approve a tool request. Extra MCP servers are covered in Connect an MCP client, and running on Codex or Cursor in the engines guide.

The full transcript is streamed and stored on the ticket, and the agent’s session id is saved for resume. There is no per-run turn cap: a run ends when the agent reaches a terminal action (finish_ticket or request_action) or when a human presses Stop.

Spend is what bounds a run. Before each agent turn the runner checks two ceilings, and a breach parks the ticket in Pending Action instead of starting the turn:

  • the board’s daily, weekly and monthly budgets, when Enforce spend budgets is on (Settings > Agents > Spend budgets, off by default, overridable per board);
  • the ticket’s own Spend limit (in the ticket toolbar, blank by default), against everything the ticket has spent so far.

Neither stops a turn already running, so a single long turn can still overshoot. See Track usage and cap spend. Context stays in bounds through the CLI’s own auto-compact plus the board’s proactive compaction before any resume (REV0_COMPACT_THRESHOLD, default 0.75).

Each ticket can pick which model the agent runs with, overriding the board default (seeded by REV0_MODEL). Choose it on the create form, in the ticket toolbar next to the mode, with the CLI (--model), or with the update_ticket MCP tool. The presets carry a proper model name backed by a pinned id (the Claude models, and the GPT models, which run on Codex); Custom version / id… takes any other pinned model id. The board default is Default model in Settings > Agents > Agent and model defaults. Models the installed CLIs report are added to the list by themselves. Blank means “use the default”. Subtickets inherit the parent’s model unless overridden. Newer models also get a small system prompt append from config/model_prompting.json, following the official prompting guide.

Each ticket can also pick how hard the agent reasons, mirroring the model override. The levels are each engine’s own effort names: off, low, medium, high, xhigh and max for Claude Code, plus minimal and ultra for Codex, listed per model in config/thinking_levels.json. More thinking means deeper reasoning but slower, more expensive runs, and a level a run’s model does not support is clamped down. Set it on the create form, in the ticket toolbar next to the model, or with the update_ticket MCP tool; set the board default with Default thinking level in Settings > Agents > Model & thinking (or seed it with REV0_THINKING_LEVEL). Blank means “use the board default”, and a blank board default leaves the CLI’s own thinking behaviour untouched. Subtickets inherit the parent’s level unless overridden.

WebFetch is blocked by the anti-bot protection on most retailers and many modern sites, which return 403, 503 or an empty JavaScript shell, so it cannot read live prices, ratings or product links. So every agent gets a real headless Chromium through the Playwright MCP server, on by default. The server is version-pinned and its Chromium is installed once at setup (install.sh or the Docker image), so nothing reinstalls per run. The safe browser_* tools (navigate, snapshot, click, type, evaluate, …) are pre-approved so a browse never parks on a permission prompt; the sensitive ones (browser_file_upload, browser_run_code_unsafe) still ask on first use.

Terminal window
# It just works after install.sh. To turn it off on a RAM or disk constrained runner:
export REV0_BROWSER=0 # then restart the runner

Each ticket’s browser is its own computer. In the default browser_session_mode (ticket, Settings > Connections > Built in browser) the browser tools do not launch a throwaway Chromium per run: they attach over CDP to one the BOARD starts for the ticket, the first time its agent browses. It keeps a persistent profile, so a login survives the run, the agent’s next turn and a board restart. In the ticket view it is a small monitor icon that streams nothing until you click it, even when the ticket parks on a sign in wall: a park is not consent to stream. From the panel you watch Chrome’s screencast and drive it (click, type, address bar) while no run is live, then Hand back with a normal reply. The Chromium is a board background job, so it shows in Running tasks and is stopped with the others at In Review / Done; its profile is deleted when the ticket is archived or deleted. Installed Google Chrome is preferred over Playwright’s build (some sign in pages refuse “Chrome for Testing”); REV0_COMPUTER_CHROME=/path/to/chrome picks another. Known ceiling: the CDP port is on loopback with no auth, the same trust boundary as the agent’s own shell on that host. isolated restores the old browser per run.

Extensions on the computer. computer_extensions (Agent Browser Extensions under Settings > Connections, browser-only) lists unpacked extension folders, a password manager being the usual one. Pasting a Chrome Web Store link there downloads the extension into <data root>/extensions/<id> (browser-only POST /config/extensions/install, which fetches the CRX from Google’s update endpoint and unpacks it; the same link again updates it in place) and adds that folder. The board loads them into every computer at every launch over the debugging port (--enable-unsafe-extension-debugging plus Extensions.loadUnpacked, which Google Chrome still honours after dropping --load-extension), and the panel’s Extensions button lists them and opens their pages. The computer always sends a normal Chrome user agent and runs with --disable-blink-features=AutomationControlled: headless Chrome otherwise reports navigator.webdriver = true, and Cloudflare’s “Verify you are human” check then loops forever. Two agent hooks go with it, on whenever the built-in browser is: browser results have secret-looking textbox values redacted (inline and in saved snapshots), and browser_navigate to chrome-extension:// is refused. See Use a password manager on a ticket’s computer.

To customize the browser (a different version, extra flags, a non-default profile), add a playwright entry in REV0_MCP_CONFIG or the Settings MCP server list: a same-named entry overrides the built-in default. See config/extra_mcp.example.json. The shopping-research skill uses the browser_* tools to gather product data and renders a ranked, clickable HTML card grid as a ticket artifact.

Three independent switches cut what a run spends. All are off by default.

Noisy commands (test runs, builds, find, git, logs) spend full tokens on repetitive output. REV0_BASH_COMPRESS=1 enables a PostToolUse hook that shrinks what the model reads from Bash results: it collapses repeated lines into a (xN) count, strips excess whitespace, and keeps the head and tail of very large outputs while preserving known test-runner failures. It works on the already-captured output and never changes what the command did (exit codes and the interrupted flag are untouched). scripts/measure_bash_compression.py reports the token delta on a fixture corpus.

A complementary, source-level reducer. When enabled, a PreToolUse Bash hook rewrites a safe single command (for example git status) to its rtk equivalent (rtk git status), so rtk filters that command’s output before it reaches the model. Only simple commands whose program is in config/rtk.json’s allow-list are rewritten; pipelines, redirects, compound commands, sudo, env, cd and unknown programs pass through untouched, and rtk transparently runs any subcommand it does not optimize, so exit codes never change. It pairs with the compressor above: rtk at the source for known commands, the compressor as the safety net for the rest. rtk only transforms shell output; it never proxies the API, so it cannot shrink a model’s context window.

  • Turn it on with rtk token reduction in Settings > Automation > Board features (rtk_enabled) or with REV0_RTK_ENABLED=1. Precedence: Settings, then the environment, then config/rtk.json enabled, then off.
  • Install the binary once (it has no pip package): uv run rev0 install-rtk fetches the pinned, checksum-verified prebuilt binary into <repo>/.rtk/bin (falling back to brew or cargo). Re-running is a no-op when the pinned version is present. Installing is separate from the toggle: flipping rtk_enabled never installs.
  • Tune the pinned version and supported_commands in config/rtk.json (path overridable with REV0_RTK_CONFIG). scripts/measure_rtk_savings.py reports the token delta over a corpus of read-only commands so you can decide before enabling it broadly.

A behavioural reducer. When enabled, the runner appends ponytail’s “lazy senior developer” rules to the agent’s system prompt: the YAGNI decision ladder (does it need to exist, reuse what is already here, stdlib, native feature, installed dependency, one line, minimum code), deletion over addition, the shortest correct diff. Where rtk trims a command’s output, ponytail shrinks what the agent builds in the first place. It touches only the system prompt.

  • Turn it on in Settings > Agents > ponytail (ponytail_enabled, overridable per board) or with REV0_PONYTAIL_ENABLED=1. Precedence: Settings, then the environment, then config/ponytail.json enabled, then off.
  • Pick the intensity (ponytail_level, or REV0_PONYTAIL_LEVEL): lite names the lazier option and lets the agent choose, full enforces the ladder (default), ultra is a YAGNI extremist.
  • No install needed: the rules ship in config/ponytail_skill.md, so the append works offline. uv run rev0 install-ponytail optionally clones the updatable upstream plugin into <repo>/.ponytail, which the runner prefers when present. Override the config path with REV0_PONYTAIL_CONFIG.