Imported from ZinkLu/Orca-Orchestration (
AGENTS.md). Install upstream withnpx skills add ZinkLu/Orca-Orchestration. Copyright stays with the author.
AGENTS.md
Guidance for agents working in this repo (editing the viewer/tooling or the skill doc). For using the skill to build DAGs, read skill/SKILL.md and README.md instead. CLAUDE.md is a symlink to this file — edit here, don't replace the link.
Project shape
Two independent modules in one npm-workspaces repo (workspaces: [server, web]):
skill/SKILL.md— a published agent skill (frontmatter + prose). Teaches an external agent to build an Orca orchestration task DAG via theorcaCLI. No code; edit it like a doc, keep frontmatter intact.server/+web/— theorca-dagviewer: an Express API + React Flow SPA that visualizes a Run's DAG and runs a self-driven coordinator loop. Compiles to a single portable binary (dist/orca-dag).
This repo is a thin, heavily-commented wrapper over the orca CLI. Every Orca quirk is documented in server/src/orca.ts comments — read them before touching orchestration code.
Commands
npm install
npm run dev # BOTH server (:8787) + web (:5173) via concurrently; vite proxies /api → :8787
npm run dev:server # tsx watch src/index.ts (server only)
npm run dev:web # vite (web only)
npm run build # web only: tsc -b && vite build → web/dist
npm run build:binary # web build → embed as base64 in server/src/generated/webAssets.ts → bun --compile → dist/orca-dag
npm run build:npm # web build → esbuild the server → dist-npm/ (the publishable `orca-dag` package; Node only, no Bun)
npm start # server (tsx) against web/dist on disk; needs `npm run build` first for UI
- No
lint,test, ortypecheckscripts exist. Typecheck manually:- web:
npm run build -w web(runstsc -bfirst, fails fast on type errors) - server:
npx tsc -p server/tsconfig.json --noEmit
- web:
build:binaryrequiresbunon PATH (not declared as a dependency). Cross-compile withTARGET=bun-linux-x64 npm run build:binary.- Binary runtime env:
PORT(8787),NO_OPEN=1(skip browser),WORKSPACE_DIR(overridesactiveworktree),ORCA_WORKTREE(defaultactive).
Distribution
Two artifacts ship from this repo, and neither is an Orca plugin — Orca's plugin panels are srcdoc iframes under default-src 'none'; connect-src 'none', so a panel can't fetch the viewer's API at all, and TabContentType is a closed union with no registry for third-party panes. Orca also deliberately ships no scheduler ("A Run is a namespace and home inbox. It never schedules or places workers."), so an external coordinator like this one is the intended shape, not a workaround.
npx orca-dagis the whole install.server/src/skill.tswritesskill/SKILL.mdinto every agent skills directory that exists under$HOMEbefore the server listens, so the skill half and the viewer half arrive together. It must stay best-effort: never throw, never rewrite an unchanged file, and never write through a symlinked skill directory (that's theln -s "$PWD/skill"recipe — following it would clobber someone's working copy).--no-skill/ORCA_DAG_NO_SKILL=1opts out.orca-dag uninstall(server/src/uninstall.ts) is the mirror image and has to stay that way — anything a future startup step writes outside the workspace must get a matching removal here, or the install becomes a one-way door. It sharesAGENT_SKILL_DIRS/SKILL_NAMEwithskill.tsso the two can't drift. It unlinks symlinked skill dirs rather than following them, keeps.orca-dag.config.jsonunless--purge(it's real user work: harness/model picks and canvas layout), and closes every terminal whose title starts withCOORDINATOR_TITLE— a crashed viewer otherwise leaves one bound to the Run, fencing the user's own agent.- Subcommands are dispatched at the top of
index.ts, before the express app is built, souninstalland--helpnever bind a port, create an Orca terminal, or install the skill on their way out. Keep new subcommands in that block. - The skill also installs through the community skills CLI straight from this repo:
npx skills add ZinkLu/Orca-Orchestration --skill orca-dag. Discovery keys offskill/SKILL.md's frontmatter —scripts/check-skill.mjsguards it in CI (don't shell out toskills add . --listthere; it prompts when no agent is detected and hangs). - The binary embeds the skill too (
SKILL_MDin the generatedwebAssets.ts, next toWEB_ASSETS) — it has no package directory to read from, and a single downloaded file has to behave like the npm package. - The viewer publishes as the
orca-dagnpm package (npx orca-dag), plus standalone Bun binaries attached to the GitHub release for people without Node.scripts/build-npm.mjsstagesdist-npm/; the bundle lands atdist/server/index.mjson purpose —index.ts's disk fallback looks injoin(__dirname, "..", "..", "web", "dist"), which only resolves inside the package at that depth. Moving either path breaks the SPA silently (API still answers, UI 404s), which is exactly what the CI smoke test checks. - Inside Orca, the viewer surfaces via
orca tab create --url(already howopenBrowserworks) — a real Electron browser tab with no CSP restrictions. - Releasing is one command:
npm run release <version>(scripts/release.mjs) — it refuses a dirty tree, a non-mainbranch, a bad semver or an existing tag, runs the checks, then tags and pushes..github/workflows/release.ymltakes it from there. The tag is the version of record —PKG_VERSIONoverrides the stagedpackage.json, so the repo's own version never needs bumping in a commit. .github/workflows/ci.ymltypechecks both packages, stages the package, and boots the packed tarball on plain Node.release.ymlpublishes to npm with provenance and cross-compiles every binary target from one Linux runner.NPM_TOKENmust be a Granular Access Token or a classic Automation token. A classic Publish token — whatnpm token createmints — fails in CI on a 2FA-enabled account withE403 … Two-factor authentication or granular access token with bypass 2fa enabled is required. Verified the hard way on the v0.1.0 tag: the binaries job succeeded and the publish job did not.- A failed publish is recoverable without burning a version. npm rejects the whole request, so the version stays unclaimed — fix the credential and
gh run rerun <run-id> --failed. The rerun checks out the same tag, so it publishes exactly the tagged tree regardless of what has since landed on main. Don't cut a new version for a credential error.
Architecture gotchas
- The viewer is the scheduler. Orca ships no scheduler by design (
run/run-stop/coordinator-start/coordinator-stopare retired no-ops).server/src/coordinator.tsowns the dispatch loop: each tick starts a worker for everyreadytask up tomaxConcurrency. - Authority model shapes the whole server. Reads (
task-list/gate-list) need only--run <id>(any process). Mutations (dispatch/gate-resolve/task-create/worker-start) require the caller to be the live Orca terminal bound to the Run, proven via--from <handle>. So the server keeps its own "orca-dag coordinator" Orca terminal for pane identity (ensureCoordinatorTerminalinorca.ts). asCoordinator(index.ts) never reuses the loop's terminal for one-off mutations — it creates a throwaway, uniquely-titled terminal, binds, acts, closes. Reusing the loop's terminal would fence and then kill a running coordinator. Preserve this pattern.dispatch --injectdoes not reliably submit the preamble into the agent TUI.startLegacyWorkersleeps ~2s then sends an Enter; a stray Enter on already-submitted input is an intentional no-op. Don't "fix" this.- opencode bypasses
worker-startentirely (coordinator.tsroutes harnessopencodestraight to legacy).worker-start --agent opencodeopens the TUI but never lands the injected preamble (orca #9951), sostartOpencodeWorkeropens a bare shell, mints a tracking dispatch (for a realdispatch_id), fetches the preamble, and runsopencode run --auto "$(cat <preamble-file>)".--autois mandatory — opencode's default permission policy auto-rejects tool calls (e.g. writing outside the project) and silently kills the task. Verified end-to-end 2026-08-10. - Per-node model override (
modelByTaskin.orca-dag.config.json) is only wired for harnesses that support it: opencode (opencode run -m <provider/model>, enumerable viaopencode models→listModelsinorca.ts), and claude/codex/cursor (worker-start --model <plain model>, no enumerable list so the UI uses free-text). Everything else ignores the field. The dispatch loop threads the model throughstartSupervisedWorker/startOpencodeWorker(serialized as an arg for opencode, quoted for shell safety). - Viewer must run inside an Orca-managed worktree —
orca terminal create --worktreeneeds the cwd to be registered (orca repo add/orca worktree), elseselector_not_found. - Requires Orca ≥ 1.4.160 (the Run/Task/Dispatch contract, PR #9925, 2026-07-29). Older Orca lacks
run-create/worker-start.
Web app gotchas
web/src/harness.tsis the single config store (useSyncExternalStore). The server-side.orca-dag.config.jsonis the source of truth; localStorage is only a one-time migration source plus a write-through mirror. Writes debounce 250ms beforePUT /api/config.- Hydration order matters:
App.tsxgatesRunPicker's auto-pick behind ahydratedflag — if the picker fell back to "newest Run" beforeinitConfig()resolved, it would overwrite the stored Run choice. - The UI polls
GET /api/dagevery 2s. Manually dragged nodes keep their positions across refreshes (only untouched nodes follow auto-layout; ↻ Re-layout bumpsreorgNonceto clear drags). Layout algorithms live inweb/src/layout.ts(dagre layered LR/TB + Fruchterman–Reingold force). - The crayon look is SVG
feTurbulencefilters defined once inApp.tsx(#crayon*,#pencil-edge*,#crayon-fill), with tuned seeds/regions — e.g.pencil-edgeusesuserSpaceOnUsewith an oversized region so perfectly horizontal edges don't collapse the filter to nothing. The comments explain each knob; read them before retuning.DoodleSelect.tsxreplaces native<select>s to keep the style.
Generated / runtime artifacts (never edit, never commit)
server/src/generated/webAssets.ts— written bybuild:binaryonly, gitignored, deleted in itsfinally. Don't create by hand..orca-dag.config.json(workspace root) — viewer config (per-node harness, default harness, concurrency, layout, last runId). Written byconfig.tsvia tmp+rename. Orca tasks have no metadata field, so this file is the viewer's own store.dist-npm/— the staged npm package, rebuilt from scratch byscripts/build-npm.mjs(itrm -rfs the dir first). Never hand-edit; thepackage.jsonin there is generated.dist/,web/dist/,node_modules/,*.tsbuildinfo— all gitignored.
Orca integration constraints (also in skill/SKILL.md)
orca orchestration reset --taskshas NO--runscope — it wipes the entire local orchestration DB, every Run at once. To redo one DAG, create a new Run (run-create); do not reset.task-updateonly changes--status/--result— no edit to spec/title/deps, no delete-single-task. "Changing a task" = rebuild the DAG.- Gates are Run-scoped, but the flag differs by direction:
gate-list(read) takes--run <id>;gate-resolve(mutation) takes NO--run— it resolves Run scope from the bound--from <handle>(plus a globally-unique--id). The viewer binds the coordinator terminal to the Run first viarun-use, then resolves by--from. HARNESS_LAUNCHinorca.tsonly hasclaude --dangerously-skip-permissionsverified. Other harnesses need their own autonomous flag added and verified, and only matter on the legacy path (custom commands oragent_unconfiguredfromworker-start).
Conventions
- Rich "why" comments are the norm for anything touching Orca — the quirks are non-obvious and the comments are load-bearing. Match this style; don't strip context when editing.
- All docs and user-facing strings are English (the project is open source; translated 2026-08-11). Keep new UI text, server errors, and docs in English.
README_zh.mdis the Simplified Chinese mirror ofREADME.md— update both when changing README content. - TypeScript is
strictin both packages; web also enforcesnoUnusedLocals/noUnusedParameters. ESM everywhere ("type": "module", ES2022).
