Gabriel Koerich › Orch

Senior Engineer · AI Workflows · Solana / DeFi

Agents

Once routed, agents run in full agentic mode with tool access:

  • Claude: -p (non-interactive), --permission-mode bypassPermissions (autonomous) or acceptEdits (supervised), --output-format json, system prompt via --append-system-prompt
  • Codex: exec subcommand with --sandbox workspace-write -c 'approval_policy="never"' (autonomous) or --dangerously-bypass-approvals-and-sandbox (full access); -c 'sandbox_workspace_write.network_access=true'; system+agent prompt combined
  • OpenCode: opencode run with --dangerously-skip-permissions and combined prompt
  • Grok: grok --output-format streaming-messages-json, --permission-mode bypassPermissions (autonomous) or acceptEdits (supervised), system prompt via --system-prompt-override, task prompt via --prompt-file. Task runs use --sandbox off because the workspace profile cannot write a worktree git dir that lives outside the cwd. Deny rules still block rm and git push.
  • Kimi / MiniMax: spawned via their CLIs with the prompt as stdin; combined system+task prompt

Agents execute inside an isolated git worktree created for the task (not your main repo), so they can read project files, edit code, and run commands.

tmux Runner

Agent sessions run inside tmux sessions. Orch spawns the agent CLI as the tmux session shell, so tmux provides the PTY. There is no external PTY process or portable_pty crate. Prompt files are written to per-attempt directories for auditability.

Agent Output

The agent writes a JSON file to ~/.orch/state/{repo}/tasks/{id}/attempts/{n}/output.json. Task metadata (branch, worktree, agent, model, attempts, memory, etc.) is stored in the unified SQLite database at ~/.orch/orch.db. The expected agent output format:

{
  "status": "done|in_progress|blocked|needs_review",
  "summary": "what was done",
  "reason": "why blocked/needs_review",
  "accomplished": ["list of completed items"],
  "remaining": ["list of remaining items"],
  "blockers": ["list of blockers"],
  "files_changed": ["list of modified files"],
  "needs_help": false,
  "delegations": [{"title": "...", "body": "...", "labels": [], "suggested_agent": "codex"}]
}

PATH Configuration

When orch runs as a service (e.g. via brew services), agents start with a minimal PATH that may not include tools like bun, anchor, cargo, or solana. There are two ways to fix this:

Option 1: Create ~/.path (recommended)

Create a ~/.path file that exports your development tool paths:

# ~/.path
export PATH="/opt/homebrew/bin:$PATH"
export PATH="$HOME/.bun/bin:$PATH"
export PATH="$HOME/.cargo/bin:$PATH"
export PATH="$HOME/.local/bin:$PATH"

Orch sources this file before launching agents, so any tool on your PATH will be available to agents when the runner is configured to inherit the shell environment.

Option 2: Default fallback

If ~/.path doesn't exist, orch automatically prepends these well-known directories (only when they exist on disk):

  • /opt/homebrew/bin
  • /opt/homebrew/sbin
  • /usr/local/bin
  • $HOME/.cargo/bin
  • $HOME/.local/bin
  • $HOME/.bun/bin

Safety Rules

Agents are constrained by rules in the system prompt and by runner-enforced tool allowlists/denylists:

  • No rm: --disallowedTools blocks rm — agents must use trash (macOS) or trash-put (Linux)
  • No commits to main: agents must always work in feature branches
  • GitHub issue linking: if a task has a linked issue, the agent receives the issue reference for branch naming and PR linking
  • Cost-conscious sub-agents: agents are instructed to use cheap models for routine sub-agent work

OS Sandbox

With workflow.sandbox: os, task and review agents run inside Seatbelt on macOS or bubblewrap on Linux, through the sandbox-jail crate. A project sets workflow.sandbox: false in its .orch.yml to run its agents without it.

The agentPaths
May writeIts worktree. In the git dir, the objects, the task's branch, remote refs and the task's own worktree dir. The temp dirs. The home's top-level dirs but .config, .claude, .ssh, .gnupg, bin, Library and .local, and inside those Library/Caches, Library/Logs, .local/share and .local/state
May not writeEverything else, among it every file at the top of the home and any new one there, ~/.claude.json, ~/.config, the agents' settings, every PATH dir inside the home such as ~/.cargo/bin, cargo's config.toml and gradle's init scripts, the git dir's hooks, config and other branches, the hooks and config of every other repository found up to four levels below a writable home dir when the policy is built, the worktree's .git file and .orch.yml, every main checkout but its .git, and ~/.orch but the task's own worktree (and orch.db for job tasks)
May not readCredentials: ~/.ssh, ~/.aws, ~/.gnupg, ~/.config/gh, ~/.docker/config.json, .netrc, .npmrc and the like. The login keychain stays readable and security stays runnable, since Claude reads its login through them. An agent can read any item whose access list trusts security, as it can today without the sandbox
May not connect toA unix socket outside the worktree and $TMPDIR: tmux's, Docker's, the ssh-agent's. On macOS, any service but the basic ones and the keychain's, so open cannot start an app outside the sandbox
May not run (macOS)launchctl and setuid binaries such as sudo

The agent's environment drops TMUX, TMUX_PANE and SSH_AUTH_SOCK. The network stays open. Codex runs with its own sandbox off, since Seatbelt cannot nest. shipcheck cannot start its own sandbox inside this one either, so a sandboxed agent's prompt has it run the project's checks itself, and the push hook runs shipcheck outside.

When the sandbox cannot start, the task blocks with the reason and no agent runs. A path the agent needs and cannot write shows up as Operation not permitted in its output.

Worktrees

Orch creates worktrees before launching agents. Agents do NOT create worktrees themselves.

Path: ~/.orch/worktrees/<project>/<branch>/ — worktrees are always placed under ~/.orch/worktrees with a project/branch layout. Worktrees live in orch home directory (ORCH_HOME, default ~/.orch) and are safe to remove by the cleanup process once branches are merged.

Steps:

  1. gh issue develop <issue> --base main --name <branch> — registers branch with GitHub
  2. git branch <branch> main — creates branch from main
  3. git worktree add <path> <branch> — creates worktree
  4. Agent runs inside the worktree directory

After an agent finishes, orch handles all git operations: pushing the branch, creating the PR, and linking it to the issue. Agents only commit — they do not push or create PRs. The runner injects GH_TOKEN into the environment for read-only operations (e.g., checking CI status), but agents should not call GitHub write APIs directly. Attribution footers (for example: Created by claude[bot] via Orch) are added to issue and PR comments so it's clear which agent produced the content.

Context Enrichment

Every agent receives a rich context built from multiple sources:

ContextSourceWhen
System promptprompts/agent_system.mdAlways
Task detailsSQLite store (~/.orch/orch.db)Always
Agent profileRouter-generated role, tools, and constraintsAlways
Error historySQLite storeOn retries
Last errorTask store last_error fieldOn retries
GitHub issue commentsGitHub APIIf issue linked
Prior run contextcontexts/task-{id}.mdOn retries
Tool call summaries.orch/tools-{id}.jsonOn retries
Repo treegit ls-filesAlways
Project instructionsCLAUDE.md + AGENTS.md + README.md, a file read once when one links to the otherIf files exist
Parent/sibling contextParent task summary + accomplishedIf child task
Git diffUncommitted changesOn retries

Error Handling

When a task fails:

  1. Error is recorded in last_error and history
  2. The failed agent or model gets a cooldown, and the task is re-routed (see Cooldowns)
  3. A structured comment is posted on the linked GitHub issue with an attribution footer (for example: Created by claude[bot] via Orch) so it's clear which agent produced the comment
  4. After workflow.max_attempts (default 5) the task goes to blocked, and orch removes any forced agent:* label so /retry routes it freely
orch task retry <id>       # reset a task to new (a done task is refused)
orch task unblock <id>     # reset a blocked task to new
orch task unblock all      # reset all blocked tasks to new

Cooldowns

A failed run puts the agent or the model in a cooldown, and the router skips it until the cooldown ends. Cooldowns are stored in SQLite and survive a restart.

FailureCooldown
One-off model failure: empty or cut off output, an unparseable review, an opaque provider errorThat model, 5 minutes, then 15 minutes
Model not found, not supported or region blockedThat model, 4 hours, growing to 7 days
Other model-unavailable errorThat model, 5 minutes, growing to 4 hours
Billing cycle exhausted on a known modelThat model, 4 hours, growing to 7 days
Generic agent failureThat agent, 5 minutes, growing to 4 hours
Out of creditsThat agent, 1 hour, growing to 8 hours
Org-level disabledThat agent, 2 hours, growing to 8 hours
Billing cycle exhausted, model unknownThat agent, 24 hours, growing to 7 days
Rate limit with a "try again at" timeUntil that time
SilenceThat model for engine.silence_cooldown (1 hour, 4 hours after 5 silences in 24 hours), and the agent for 120 seconds
Review timeoutNone, the task goes back to needs_review

A success resets the agent's failure counts, so the next failure starts from the base again.

An agent is degraded, and skipped, when all its models are cooled or it has dispatch.degraded_rate_limit_threshold (3) rate-limit rows in the last dispatch.health_check_window_hours (6). Only rows of type rate count. Failed runs and timeouts keep their own cooldowns and do not count.

orch cooldown list          # active cooldowns and degraded agents
orch cooldown clear <key>   # clear one cooldown (claude or claude:sonnet) and that agent's rate-limit window
orch cooldown clear --all   # clear every cooldown and every agent's rate-limit window

The running service picks a clear up on its next tick. That includes the GitHub 5xx circuit breaker: a cleared cooldown:github:5xx closes it and empties its error window.

Codex Sandbox Notes

Codex runs under the exec subcommand. The sandbox flag depends on workflow.permissions.mode and workflow.permissions.sandbox:

ModeSandboxFlags
autonomous(any)--sandbox workspace-write -c 'approval_policy="never"'
supervisedworkspace-write-c 'approval_policy="on-request"' --sandbox workspace-write
autonomousfull-access--dangerously-bypass-approvals-and-sandbox

The runner also sets:

  • -c 'sandbox_workspace_write.network_access=true' (must precede exec — placing it after silently leaves the sandbox network-blocked)
  • -c 'shell_environment_policy.inherit=all' (so bun, cargo, etc. work)

--full-auto is not used — it was deprecated in Codex 0.128.0. Do not reintroduce it.