Back to Blog
Galen Guan

Inside Orca: When Agents Write the Code, Discipline Has to Become a Machine Gate

You fan one prompt across five coding agents. The first genuinely hard problem isn't merging their work — it's this: did the third one finish?

Is it thinking, waiting for your answer, or did it die ten minutes ago? All you have is a stream of bytes in a terminal. CLI agents expose no status API, no exit code to await (the process is still alive), no unified event stream. You end up staring at a spinner to see whether it's still spinning.

Every parallel-agent orchestrator stalls at this exact wall. stablyai/orca has the least dignified and most effective answer I've seen in an open-source project.

The numbers first

As of August 12, 2026:

Metric Value
Stars / Forks 43,205 / 3,014
Created 2026-03-17 (about five months)
Commits 8,490
Files 13,310 (TypeScript/TSX: 12,526)
Test files 5,421 .test. / .spec. files — 43% of all TS files
Release cadence v1.4.180, near-daily, with an rc channel
License MIT
Company Stably AI (YC W22); the other product line is Stably, an AI testing platform

The business model is bring-your-own-subscription: Orca itself is $0, no per-seat fee, and no proxy layer — it never touches your tokens, it just runs your own Claude / Codex / Cursor subscriptions locally. That structure means no short-term incentive to paywall features, and long-term sustainability riding on the parent company's testing business.

The top four contributors account for 6,653 commits, 78% of the total; with the CI bot, roughly 87%. This is open source, not shared governance.

Orca's layered architecture: clients, runtime RPC, main process, terminal daemon, four execution hosts

1. Status detection: three channels, degrading by reliability

src/main/agent-hooks/managed-agent-hook-registry.ts registers hook services for 14 agents — claude, openclaude, codex, gemini, antigravity, amp, cursor, droid, command-code, grok, copilot, hermes, devin, kimi. Each carries four lifecycle methods: install / remove / refreshManagedScripts / getStatus.

The three agent-status channels and their stdin contract traps

Channel 1 (primary): managed hooks. Orca writes scripts into the user's agent config, and those scripts post events back over loopback HTTP with a per-pane token:

ORCA_PANE_KEY / ORCA_AGENT_HOOK_PORT / ORCA_AGENT_HOOK_TOKEN
→ POST http://127.0.0.1:$PORT
  Header: X-Orca-Agent-Hook-Token: $TOKEN

The receiving end, agent-hooks/server.ts, is 2,907 lines.

Channel 2 (parasitic): hijacking Claude Code's statusLine. This one is clever, and the source comment states the motive outright:

Claude Code pipes rate_limits to the statusLine command on every turn; forwarding it gives Orca live usage without spending the OAuth usage endpoint's tight budget. Emits no stdout so the in-terminal status line stays visually unchanged.

Which means your Claude status line has already been intercepted, and you can't tell.

Channel 3 (fallback): parsing terminal output. Spinner frame recognition, pane-title matching. This is the brittle layer — the most frequent patch type in the August 2026 commit stream is exactly this: "treat Claude Code quarter-circle spinners as working," "detect a live OpenCode pane from its native OC | session title." One upstream character change breaks it again.

The real value is the blood in the hook scripts

In hook-stdin-contract.ts, POSIX and Windows follow opposite rules, and each carries an issue number:

# POSIX: own stdin before any no-op exit.
# Why: a stripped PATH must not stop a hook from consuming stdin, or the agent
# sees exit 127 and a broken pipe mid-write (#8110). `command -p` resolves from
# the shell's built-in default PATH, so it also survives hosts without /bin/cat
# (NixOS) and ignores a worktree-local `cat` that could capture the payload.
POSIX_HOOK_STDIN_READER = '{ command -p cat 2>/dev/null || cat; }'
:: Windows: check the env before touching stdin.
:: Why (#11549): missing Orca context means the hook ran outside an Orca pane,
:: where the caller may abandon stdin rather than close it — a read-to-EOF then
:: blocks forever and strands a visible window per hook event.
WINDOWS_HOOK_STDIN_READER = '"%SystemRoot%\System32\more.com"'

more.com is fully qualified because Windows searches the working directory before PATH — and hook payloads must never reach code sitting in the repo.

Then there's statusline throttling: Claude ticks the status line roughly three times a second while streaming, so the Windows variant computes a seconds-of-day stamp using only cmd builtins to avoid spawning findstr + curl on every tick — and it only writes the stamp when a post is certain, so skipped ticks never push the next allowed post further out.

And a detail you only learn by getting burned: PTYs outlive Orca restarts, which makes the port and token in their env stale. So every hook first calls an ORCA_AGENT_HOOK_ENDPOINT file to refresh them, falling back to PTY env only if that's missing.

My call: this layer is the one thing about Orca you cannot fork. The features are copyable. These few hundred platform corner cases were bought one issue number at a time.

2. Renaming one socket file takes seven steps

src/main/daemon/AGENTS.md is the highest-density document in this repository, by a wide margin. Its subject sounds trivial: how the terminal daemon hands over its canonical socket path.

The daemon endpoint handover protocol: seven steps and five traps already paid for

The root cause: net.Server.close() unlinks the pathname it bound with no ownership check. So a departing daemon deleted whichever socket then sat at the canonical path — including a live replacement's. The replacement stayed alive, still hosting every PTY, with no client able to reach it.

What the user sees: terminals that accept keystrokes and never run them.

Two invariants:

Only a daemon publishing itself onto the canonical endpoint may mutate that directory entry, and only by replacing an entry it has itself just proven dead.

No actor removes a name it did not create.

The protocol is seven steps: bind a private .p<hex> name → attempt an exclusive link → on EEXIST, connect to prove the incumbent dead → re-check the entry hasn't changed hands → probe once more → rename in one syscall → verify you kept it.

But the section worth more than the protocol is the one titled "Traps That Already Cost Us":

  • Never collapse "can't tell" into "dead." Only connected means occupied; only refused / missing prove death. A timeout or EPERM proves nothing and must decline — treating it as death deletes an endpoint still serving every terminal on the host.
  • link first, never an unconditional rename. rename replaces whatever it finds, which would let a starting daemon destroy a healthy one. link fails loudly and forces the liveness question.
  • rename, never unlink-then-link. The latter leaves the name absent between two calls; measured across a live handover it gapped on essentially every observation, where rename gapped on none in ~14,500 probes.
  • Do not identify an entry by birthtimeMs. Node documents it as sometimes holding the ctime, filesystems without a birth time report the epoch, and its granularity is often coarser than the events it must separate. Three attempts to patch around it produced three more defects; inode recycling is now settled by asking whether anything is serving.
  • Do not add a sweeper. Deciding whether someone else's leftover is safe to delete is the question this design retired; the last one produced five defects, including deleting a live listener's only pathname.

And the hardest line in the whole repo:

Seven review rounds against the older "launcher reclaims a dead process's name" shape produced twenty-three defects, all the same interleaving: a third party observing liveness at T and acting on the directory entry at T+1.

The doc then states its residual risk honestly: the final probe and the rename are two syscalls, and POSIX has no rename-if-target-is-inode-X. The harm is separately unreachable — a daemon never creates a session on an endpoint it no longer holds; it drains rather than serving on.

This document is worth more than the protocol it describes. It records not "what we did" but "what we tried, where each attempt died, and why not to try it again." In most teams that knowledge lives in three people's heads and evaporates when they leave.

3. It isn't a worktree GUI — it's an orchestration engine

src/main/runtime/orchestration/ was the biggest surprise of this teardown: a SQLite-backed coordinator–worker system.

CREATE TABLE runs / tasks / dispatch_contexts / messages / deliveries
CREATE TABLE worker_dispatches / worker_terminal_resources / worker_terminal_archives
CREATE TABLE federated_dispatches / federation_relay_items / remote_questions
CREATE TABLE mutation_receipts

Four decisions worth pulling out:

1) The coordinator is one of very few files granted an exemption. Its first line:

/* eslint-disable max-lines -- Why: the coordinator keeps message processing, task
   dispatch, gate handling, escalation, and convergence checking in one class so the
   polling loop can make atomic decisions across all these concerns without
   split-brain behavior. */

The root AGENTS.md explicitly forbids adding a max-lines disable. This file breaks the rule and writes the justification at the site of the break. Hard rules, justified exceptions — that's what a mature team looks like.

2) Workers are onboarded via a preamble. The coordinator generates a preamble injected into the worker's prompt, instructing it to report worker_done and heartbeats through the orca CLI. Heartbeat interval: 5 minutes; the coordinator's stale threshold: 10 minutes — with a comment explaining exactly why those two numbers: frequent enough to catch a hung worker within one tick, infrequent enough to avoid inbox spam.

3) Dispatch probes base drift first. If the worktree is behind its tracking remote, dispatch is refused (stale-base-refused) unless allow-stale-base: true is set. Then comes what I consider the most valuable comment in the repository:

Callers must NOT pre-populate this with empty data; the drift section is a loud-but-rare signal, and polluting it for fresh worktrees would train workers to ignore it.

That's alert fatigue treated as a first-class prompt-engineering constraint. Anyone who has built agent systems should feel the weight of it immediately — the "evidence must be real" rules in my Prime Agent harness teardown are the same law seen from another side.

4) Federation. Dispatches sync between the local runtime and remote hosts, with ack checkpoints, a relay queue, and schema version-skew migration. Orchestration itself crosses machines.

4. Mixed-version compatibility: silent frame drops are the real enemy

docs/reference/remote-wire-compatibility.md opens with the premise stated plainly: desktop clients and remote Orca runtimes update independently, so mixed versions are the normal state, not an edge case.

  • Rule 1: adding a new optional JSON field to an existing frame is safe (RPC params go through zod .strip(), stream frames through JSON.parse — both ignore unknown keys). But that safety holds only while every reader treats it as optional. The moment a newer client requires it, that's the same defect as removing a field, just discovered later.
  • Rule 2: a new stream opcode is not safe and must be capability-negotiated. Because:
const frame = decodeTerminalStreamFrame(bytes)
if (!frame) {
  return // silently dropped — the sender never learns
}

An unknown opcode decodes to null and is dropped without an error; the sender never finds out. To the user, the feature simply hangs — the worst kind of bug to chase.

This rule transplants directly into any system where two independently-released processes talk to each other.

5. The thesis: compile the discipline into gates

The four sections above are about hard problems solved. But if you take one thing away, make it this: once most of the code is written by agents, line-by-line human review stops being the quality mechanism, so every constraint has to sink into a machine-executable gate. Orca is the most thorough implementation of that idea I've seen in open source.

The line-count ratchet: it can only shrink

The comment in check-max-lines-ratchet.mjs explains the mechanism:

oxlint already fails any file that exceeds max-lines WITHOUT a suppression, so the only way a file grows past the budget is by adding a disable comment or a per-file bump in mobile/.oxlintrc.json. This check freezes the set of files currently allowed to do that and fails CI when a NEW bypass appears — existing over-limit files are grandfathered; new ones must split instead. The baseline may only shrink.

Default budgets: 300 lines for .ts, 400 for .tsx, 600 for .mjs, 800 for test files. The baseline currently grandfathers 351 files.

One charming detail: the ratchet excludes itself and its own test from scanning, because those files legitimately contain the directive text as data (regex, fixtures) — "the ratchet does not police itself."

74 reliability gates: a formal reliability ledger

config/reliability-gates.jsonc is the first thing of its kind I've seen in an open-source repo. Each gate is a structured record:

{
  "policy": {
    "maturityLevels": ["experimental", "soak", "blocking", "accepted-gap", "deprecated"],
    "blockingPromotion": {
      "minimumSoakRuns": 100,
      "minimumSoakDays": 14,
      "maximumUnexplainedFlakes": 0
    }
  }
}

Every gate must declare: invariant (what exactly must hold), oracle (how you'd falsify it), coveredPlatforms / coveredProviders (and, by omission, what isn't covered), motivatingLinks (the issue or PR that caused it), commands (how to run it), and assertionRefs (which specific assertions in which test files back it).

Three decisions stand out:

  • One maturity level is accepted-gap — known to be uncovered, and that's a deliberate decision rather than an oversight. Turning "we know there's a hole here" into a queryable record is far more honest than pretending full coverage.
  • Promotion to blocking requires 100 soak runs, 14 days, and zero unexplained flakes. Flakiness isn't "just rerun it" — it's a hard blocker on promotion.
  • coverageNotes spells out the evidence not yet collected, e.g. one gate states that deterministic store/controller tests cover local, direct-SSH and paired-runtime identity, while "live headed/headless paired-runtime and post-establish Electron IPC remain uncollected."

E2E assertions must target the DOM, never the store

tests/e2e/AGENTS.md contains a real incident worth quoting in full:

The 'create-worktree' modal key lived on in the activeModal union long after AddWorktreeDialog.tsx was deleted in #710, so store.openModal('create-worktree') + store.activeModal === 'create-worktree' round-trips succeeded against a modal that rendered nothing. That tautology is what let #1186 (React error #31 in StartFromField) ship — the store-layer test passed while the composer actively crashed for real users.

The conclusion becomes a hard rule: use the store to reach a state; use the DOM to prove the state is correct. A spec that both writes to the store and reads it back is asserting that Zustand's setter works, not that Orca works.

The same doc adds the inverse discipline: an E2E spec that merely calls store.getState().someAction(...) inside page.evaluate is a unit test paying a ~1.5s Electron launch for no extra coverage — go write a store-slice test instead.

The rest of the gates

  • Benchmarks as CI gates: startup time, idle CPU, main-thread jank, terminal typing latency, zustand selector fanout, worktree deletion, WSL git shell, hang-watchdog memory. Each has a bench: script and a budget check.
  • global-fetch-call-site-audit.test.ts: a test that audits every call site of global fetch. Steal this one — it converts "don't quietly add network calls" from a spoken norm into a red light in CI.
  • Triple localization validation: catalog consistency, extraction completeness, coverage — three independent scripts, all wired into pnpm lint.
  • Naming bans: AGENTS.md forbids helpers, utils, common, misc, shared-stuff — "they carry zero info and tend to become dumping grounds. If you find yourself reaching for helpers, the file probably has more than one responsibility."

Taken together, this is the full production landing of the idea I covered in the harness engineering guide: the quality of an agent's output is set by the scaffolding around it, not by the model.

6. Two things they built almost incidentally

AI Vault (166 files): a cross-agent session archaeology layer. Claude, Codex and Antigravity each leave local session files in completely different formats, so Orca wrote a parser for each (session-scanner-codex-parser.ts, session-scanner-antigravity-parser.ts, session-scanner-claude-subagents.ts, …) and unified them into one searchable, deletable, remotely-scannable index with concurrency and batching controls. For the first time, every agent conversation you've ever had has a single entry point.

The skills directory: Orca ships 8 skills for agents (orchestration, orca-cli, computer-use, orca-linear, orca-emulator, …). What's interesting is that skills/orchestration/SKILL.md declares itself a discovery stub:

This file is a discovery stub, not the usage guide. The full, version-matched Orca orchestration reference is served by the orca binary itself — kept out of this file on purpose so it can never drift from the binary that will actually run your commands.

That's a direct fix for how skill docs usually die (documentation and implementation evolving separately), and a pragmatic answer to the distribution problem I discussed in the agent skills ecosystem piece.

The same file also contains a rule that made me laugh out loud: on Linux, outside an Orca-managed terminal, never run bare orca — it normally resolves to the GNOME Orca screen reader (/usr/bin/orca) and starts speech on the user's machine.

7. How it compares

I picked these four dimensions because they determine where you hit a wall: execution location decides whether you can push work onto a bigger remote box; status reporting decides whether the orchestrator knows what happened; governance decides whether your PR gets merged; form factor decides what it costs to run.

Project Stars (2026-08-12) Form Execution location Status reporting Governance
Orca 43,205 Electron desktop + mobile + VPS local / WSL / SSH / ephemeral VM hooks + statusline + terminal parsing company-led, 4 people = 78%
Vibe Kanban 27,745 Rust, task kanban local process-level parent company shut down 2026-04, community-maintained
Claude Squad 8,285 Go, terminal TUI + tmux local tmux panes light community
Crystal 3,108 TS desktop local process-level small team
Conductor closed source native Mac local undisclosed commercial

My recommendation is unambiguous:

  • One Mac, only Claude Code and Codex, want polish → Conductor. Orca's surface area is wasted on you.
  • Want a task queue that slots into an existing process, don't want an IDE → Vibe Kanban, accepting that no commercial entity is pushing it anymore.
  • Need to push work to remote boxes, supervise from a phone, or run five vendors' agents at once → Orca is currently the only option. Nobody else does execution-location abstraction and status reporting to this depth simultaneously.

8. The costs (the honest part)

  1. src/main has 200+ flat top-level files, with absurd names like worktree-removal-session-partition-fencing.test.ts and powershell-osc133-bootstrap-windows-clm.test.ts. That's the inevitable output of banning utils/helpers plus a line ratchet — a codebase optimized for agents retrieves by filename, not by directory intuition. High barrier for human newcomers; but their newcomers are mostly agents anyway.
  2. The ratchet does not contain the core state machines. A 2,907-line hook server and a 555-line coordinator show complexity doesn't vanish under rules — it concentrates in the few places granted exemptions. The good news: those places genuinely deserve it.
  3. 14 agent integrations are a linearly growing tax. Each new agent = one hook service × four lifecycle methods × four script variants (POSIX / Windows / WSL / SSH), while upstream can change hook formats or spinner characters at any time. It's the most fragile surface they maintain.
  4. The Electron bill: roughly a 250 MB installer and 400–800 MB resident with a few agents running. That's a small share of total cost when five agents are running, but it's noticeable on modest hardware.
  5. Concentrated governance and a community PR backlog. The last 30 merged PRs all came from the four core contributors; the oldest still-open community PR dates to April 11 and hasn't moved in four months. There's a track-community-prs.yaml workflow, so they know — it just isn't solved.
  6. Hooks write into your global agent config. The install locks, owner identity checks and script-refresh machinery show care, but in an enterprise environment this still needs a compliance review.
  7. Daily shipping means routine regressions. The README says it outright: "we ship daily, so this list is perpetually behind." Teams should pin a version rather than track latest.

To be explicit: every conclusion here comes from reading the source and public data, not from long-term production use. The memory-footprint and regression claims above are cited from public reviews and the project's own statements, not numbers I measured.

9. What this means if you never touch Orca

Three takeaways you can lift directly:

1) Any "we agreed to do X" norm has a lifespan under three months unless it has a red light in CI. Orca's move is to attach a script to every norm: naming conventions get a lint plugin, file size gets a ratchet, reliability gets a ledger, outbound requests get a call-site audit. Rules that live only in CONTRIBUTING.md are ignored by agents — and by people.

2) Silent failure deserves to be hunted as its own category. Unknown opcodes dropped without error, hooks blocking forever on stdin, birthtimeMs returning the epoch on some filesystems, timeouts read as death — the most expensive traps Orca hit share one trait: nothing throws, something just quietly stops working. Asking "if the other side is older than me, how does this message vanish silently?" beats writing ten more unit tests.

3) Write down what you tried and why it failed. The value of that daemon AGENTS.md isn't the protocol — it's the five dead ends, each with its cause of death and its price tag (seven review rounds, twenty-three defects, five defects, 14,500 probes). The next person — new hire or new agent run — will not propose adding a sweeper. It's the cheapest knowledge preservation mechanism I've seen.

Conclusion

What makes Orca worth attention isn't its feature list; that gets copied within a quarter. It's that it demonstrates something: a four-person core team, using a fleet of agents, produced VS Code-scale surface area in five months — and the quality mechanism wasn't "review harder," it was compiling every formalizable norm into a CI red light.

If you're deciding whether to adopt it: remote execution, cross-vendor agents, supervision from a phone → install it, there's no substitute, but pin a version. Running one or two agents on a single machine → its complexity is a liability for you.

If you just want to learn something: read four files — src/main/daemon/AGENTS.md, docs/reference/remote-wire-compatibility.md, tests/e2e/AGENTS.md, and config/reliability-gates.jsonc. Forty minutes, and more nutritious than most engineering blogs.

References