orbit-runner
v0.8.1
Published
Turn agent-ready Orbit tasks into headless Claude Code sessions on your own Claude subscription.
Readme
orbit-runner
Turn agent-ready Orbit tasks into headless Claude Code sessions on your own Claude subscription — no Anthropic API key. Flag a task in Orbit and the runner picks it up, spins up a session in an isolated git worktree, and streams the work back to Orbit's Agents and Orchestration pages.
Published to npm as orbit-runner. One command to set up:
bunx orbit-runner@latest init # or: npm i -g orbit-runner && orbit-runner init@latest matters with bunx: without it, bunx can run a stale cached copy
instead of the newest published version.
init verifies your key, maps your Orbit projects to local repos, and — unless
you pass --no-plugin — sets up the Orbit plugin + MCP in your interactive
Claude Code (user scope), so /orbit:tasks and the workflow skill are available
everywhere. Pass the key non-interactively with --key, or let the Orbit web
app hand you the full line from Settings → Agents → Add runner.
Commands
| Command | What it does |
| --- | --- |
| init | Interactive setup: key, project→repo map, and (opt-out) Claude Code wiring. --key <key>, --no-plugin. |
| doctor | Check the environment: claude present, config valid, key works, repos trusted + allowlisted. |
| start | Start the daemon. --project, --foreground, --follow, --model, --effort. |
| stop / status / logs | Manage and observe running instances. |
The URL is fixed (https://api.orbitagents.dev); set ORBIT_API_URL only for
local development against a dev API.
How it works
- Discovery — polls
get_next_taskper mapped project: the highest-priority agent-ready, unclaimed, unblocked, conflict-free task. - Isolation — creates a git worktree on
orbit/<task>offorigin/HEAD, so Orbit's GitHub integration links the branch and PR to the task. - Self-contained sessions — spawns
claude -p … --mcp-config … --strict-mcp-config, so each session gets Orbit's MCP tools without relying on any global install. - Presence — POSTs
/api/runner/heartbeat(@orbit/shared'srunnerHeartbeatSchema) so the fleet strip shows slots, status, and pauses. - Usage limits — pauses new dispatch until the reported reset, leaving
running sessions alone. When a headless session is out of quota it exits in
seconds without a parseable banner, so a backstop also pauses after
auto_pause_after_no_startssessions in a row exit before claiming their task. Such exits are recorded asno start, notfailed.
Config
~/.config/orbit-runner/config.json (same format as the reference Python
runner, so existing files keep working). Managed by init; edit by hand for
advanced fields (max_slots, poll_interval_seconds, rate_limit_pause_minutes,
task_retry_cooldown_minutes, auto_pause_after_no_starts, skip_permissions,
per-project model/effort, alternate repos).
Session permissions
Headless sessions can't answer permission prompts, so any tool not on the repo's
committed .claude/settings.json allowlist is denied (see allowlist.ts;
init scaffolds a conservative starter). Set "skip_permissions": true to pass
--dangerously-skip-permissions to every session instead, auto-approving all
tools. That removes the "denied a tool it needed" failure mode, but a worktree is
isolation, not a sandbox — sessions share the host's files, credentials, and
network. Only enable on a machine and repos you trust (ideally a VM/container),
and never run the runner as root (the flag refuses root). doctor flags it when on.
Development
bun run dev -- doctor # run the CLI from source
bun run typecheck
bun run build # bundle to dist/ (bundles @orbit/shared)Status:
init,doctor, the Orbit client, and the dispatch daemon (start/stop/status/logs, including background daemonization, worktree isolation, heartbeats, and usage-limit pauses) are implemented. Ported fromscripts/orbit-runner/orbit-runner.pyin orbitagents/plugin, with two additions: sessions are handed Orbit's MCP inline (--mcp-config … --strict-mcp-config) so they need no global install, and the runner reports presence to the fleet strip. Not yet ported:service install(launchd/systemd) for start-at-login, and a--configflag for alternate config paths.
