Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

orcast

English · 简体中文

Multi-agent orchestration, as code. Works anywhere coding agents run — Orca is the first supported host. A coordinator writes a plan in plain JavaScript, runs it with orcast run, and gets a JSON result back. Under the hood the plan spawns worker agent terminals in the active worktree, delegates tasks to them, validates their structured replies, and collects everything into one return value.

The idea

orcast is driven by a coordinator agent — itself an LLM. When it faces a task that needs several agents, it does not run a fixed, pre-built pipeline. It writes the orchestration as code, on the spot, in plain JavaScript, then runs it. The workers it spawns are themselves LLM agents. So there are two levels of LLM — one that authors the plan, and many that execute the delegated work — and the plan is a program tailored to the task, not a static config.

              a task that needs several agents
                            │
                            ▼
   ┌─────────────────────────────────────────────────┐
   │  COORDINATOR  ── an LLM agent                    │
   │                                                  │
   │  reads SKILL.md, then WRITES a plan in JS —      │
   │  the orchestration itself is LLM-authored code:  │
   │                                                  │
   │    export default async function plan({ agent,   │
   │      parallel, pipeline }) { ... }               │
   └────────────────────────┬────────────────────────┘
                            │  orcast run review
                            ▼
   ┌─────────────────────────────────────────────────┐
   │  PLAN PROCESS  ── runs the JavaScript            │
   │  agent()  ·  parallel()  ·  pipeline()           │
   └────┬───────────────────┬───────────────────┬─────┘
        │ spawn             │ spawn             │ spawn
        ▼                   ▼                   ▼
   ┌─────────┐         ┌─────────┐         ┌─────────┐
   │ WORKER  │         │ WORKER  │         │ WORKER  │  ← each is an
   │  (LLM)  │         │  (LLM)  │         │  (LLM)  │    LLM agent
   │ do task │         │ do task │         │ do task │    (codex/pi/omp/
   │ report  │         │ report  │         │ report  │     claude/kimi)
   └────┬────┘         └────┬────┘         └────┬────┘
        │      worker_done (JSON, schema-checked)     │
        └───────────────────┼───────────────────┬────┘
                            ▼
              plan collects + validates + returns
                            │
                            ▼
                  JSON result  →  stdout

What each side outputs: the coordinator LLM outputs the plan (the orchestration program); each worker LLM outputs a structured JSON result. orcast is the glue that runs the plan, spawns the workers, and routes results back.

SKILL.md is what triggers this — it teaches the coordinator agent to author and run a plan instead of doing the work sequentially itself. It is loaded into the model's context when the skill is used (keep it lean). This README, install.mjs, and tests/ are for humans/tooling and never enter that context.

Quickstart

1. Install (Node-only, cross-platform — installs into Claude Code, Cline, codex, Kimi Code, pi):

node skills/orcast/install.mjs

2. Create a project Plan (or pass --global for ~/.orcast/plans):

orcast plan init review

Edit .orcast/plans/review.plan.mjs:

export const manifest = { apiVersion: 1 }

export default async function plan({ agent, parallel, input }) {
  const findings = await parallel(
    ['auth', 'db', 'api'].map((area) => () =>
      agent(
        `GOAL: review the ${area} layer for boundary bugs.
CONTEXT: repo is the current worktree; read-only.
CONSTRAINTS: do not edit files.
DONE-CRITERIA: return JSON { area, findings: [] }`,
        { agentType: 'codex', schema: { required: ['area', 'findings'] } },
      ),
    ),
  )
  return { task: input.task, findings: findings.filter(Boolean) }
}

3. Run it and read the JSON it prints:

orcast run review "review current changes"

That's the whole loop: write a Plan → orcast run it → the Plan's return value is printed to stdout as JSON. That JSON is your result.

Plans may be project-local, global, or stored in named custom sources:

orcast plan source add team /srv/team/plans --global
orcast plan init team:security-review
orcast plan list
orcast run team:security-review

Unqualified names resolve project first, then global. Every invocation gets a unique run ID; use orcast runs --active and control persistent Actor Plans by that ID.

Writing a plan

A Plan is just JavaScript — loops, conditionals, whatever. orcast run injects the primitives, input, project context, run identity, and workflow helpers; aggregate however you like and return one value.

  • agent(brief, opts) = one worker. The brief must be self-contained: a fresh worker has none of your context, so always spell out GOAL / CONTEXT / CONSTRAINTS / DONE-CRITERIA. The report-back instruction is appended for you.
  • schema: { required: [...] } validates the worker's JSON reply. A reply that omits a key is bounced back to the worker with the reason so it can fix and resend — you never get a malformed result.
  • agentTypecodex | pi | omp | claude | kimi (default codex). effort and timeoutMs are optional per call.
  • Aggregate and return one value — that is what gets printed. Multiple workers → collect with parallel / pipeline and return the array.
  • Pass artifacts, not bulk prompts. Every worker's complete result is retained under <project>/.orcast/artifacts/<run-id>/nodes/<node-id>/result.json. Use artifacts.input(name, result) in downstream briefs instead of stringifying a previous worker result.
  • Waiting is your job, not the skill's. Run a short plan in the foreground and read its output; run a long one in the background with your own harness and read the output when it finishes. While a plan runs, don't interact with the worker terminals — their prompts and reports are owned by the plan.

Primitives

Primitive Meaning
agent(brief, { schema?, agentType?, effort?, timeoutMs? }) one worker; resolves to its validated JSON payload
parallel(thunks) barrier — awaits all; a failed child becomes null
pipeline(items, ...stages) each item flows through the stages independently (no barrier between stages)
phase(title) / log(msg) progress bookkeeping + a status broadcast
budget count-based .workers() / .rounds() (token budget is out of scope)
workflow(subPlan, args) run a sub-plan inline (one level of nesting)
artifacts format prompt-safe references to durable worker results

Full API: references/api.md.

Recipes

Ready-made patterns in references/plan-recipes.md:

  • Fan-out reviewparallel(dims.map(d => () => agent(...))).
  • Adversarial verify — pipeline each finding into N refuters; keep it only if the majority fail to refute.
  • Judge panelparallel proposers → score → synthesize the winner.
  • Loop-until-dry — a real while loop that spawns finder rounds, dedups against a seen-set, stops after K empty rounds, bounded by budget.

How it works

  1. Spawnagent() launches the worker with a bare command (codex, not ENV=x codex) so Orca recognizes the agent and applies its own launch preset. Concurrency is capped per plan by budget.maxWorkers (default 3). Every Run has its own runtime, so concurrent plans keep independent limits and failure domains.
  2. Route — how the worker learns where to report, chosen by probing the orca CLI once: env (ORCAST_REPORT_TO / ORCAST_NODE via terminal create --env, preferred) or, as a fallback, --to <coordinator> --node <n> pre-filled into the report command.
  3. Deliver the brief — wait for Orca's public tui-idle condition and a stable terminal surface; a fresh terminal that times out may be closed and recreated before any input is sent. After composing and sending Enter separately, OrCast must observe the agent leave idle (or receive a real report) before recording brief_dispatched. API acceptance without a started turn records brief_unconfirmed and fails without resending a possibly submitted prompt.
  4. Report — the worker writes its complete JSON to its project-local run/node artifact directory. orcast report --result-file <file> sends only a path, byte count, and checksum through the broker socket.
  5. Collect — the broker verifies the owned path and checksum, reads and validates the JSON against the node's schema, and resolves agent(). Route/Actor prompts receive only small summaries and artifact references. The plan's return is printed as JSON, then the process exits.

Durability & safety: every Run owns an append-only journal (journal.jsonl) that records each event. Managed Runs receive reports through their private socket and do not create or consume a coordinator terminal. A single-instance lock guarantees one broker per Run runtime. A worker whose terminal is gone fails its node (→ null in parallel) so the plan never hangs. Immutable, content-addressed releases let active Runs finish on their original code while newly installed code serves new Runs. There is no default time limit — pass timeoutMs on an agent() to opt in.

Module map (lib/)

Module Role
plan-registry.mjs project/global/custom Plan source discovery, init, and resolution
plan-runner.mjs portable Plan loader and injected API/runtime context
flow.mjs coordinator entry: run(plan) — lock, journal, engine+daemon, print JSON, exit
engine.mjs scheduler + router + worker lifecycle (spawn, deliver, schema feedback, liveness)
daemon.mjs worker liveness loop + optional legacy coordinator-inbox adapter
primitives.mjs the six primitives over the engine
orca.mjs thin, injectable wrapper over the orca CLI (spawn, send, terminal I/O, --env probe)
report.mjs worker-side helper: resolve routing (env or flags) and send worker_done
journal.mjs append-only event log — single source of truth + message de-dup
lock.mjs atomic single-instance lock (mkdir-based, steals stale holders by pid)
paths.mjs shared on-disk locations, keyed off ORCA_WORKTREE_ID
self.mjs resolve the coordinator's own terminal handle from ORCA_TERMINAL_HANDLE

Install

Node-only, cross-platform (macOS / Linux / Windows — no shell required):

node skills/orcast/install.mjs            # install into every detected runtime (claude, cline, codex, kimi, pi)
node skills/orcast/install.mjs --claude   # only Claude Code (~/.claude/skills)
node skills/orcast/install.mjs --cline    # only Cline       (~/.cline/skills)
node skills/orcast/install.mjs --codex    # only codex       (~/.agents/skills)
node skills/orcast/install.mjs --kimi     # only Kimi Code   ($KIMI_CODE_HOME/skills or ~/.kimi-code/skills)
node skills/orcast/install.mjs --pi       # only pi           (~/.pi/agent/skills)
node skills/orcast/install.mjs --to <dir> # install into <dir>/orcast

It copies SKILL.md, lib/, and references/ into <skills>/orcast/ (not the tests, READMEs, or installer) and removes any previous install under the old orca-flow name. Re-run after editing the source to push the update.

Test

node skills/orcast/tests/run-tests.mjs    # unit tests plus real Codex CLI + isolated local mock-model E2E

Layout

orcast/                   repo root
  README.md               English docs (this file)
  README.zh-CN.md         Chinese docs
  skills/
    orcast/               the skill
      SKILL.md            agent-facing instructions (loaded into model context)
      install.mjs         cross-platform installer
      lib/                the implementation (see module map)
      references/         api.md, plan-recipes.md (loaded on demand by the agent)
      tests/              unit tests + run-tests.mjs runner

Requirements

  • Node — present wherever the Orca runtime runs.
  • Orca's orchestration experimental feature enabled.

License

MIT