Skip to content

Architecture

Cue is a deterministic Bun + TypeScript runner. Headless coding agents sit inside pipeline stages. Routing, gating, retries, and label transitions are plain code — never ask the model something a script can decide (for example, whether tests passed).

GitHub is the state store: agent:* labels are the state machine, issue comments carry the plan, draft PRs are the output.

Layout

src/
├── cli.ts              # entrypoint + label definitions; builds the real StageContext
├── action.ts           # nextAction (label → stage) — shared by pipeline and prompts
├── pipeline.ts         # runIssue (failure → agent:failed), process loop
├── cleanup.ts          # merged/closed PRs → agent:done / agent:failed + worktree removal
├── stages/
│   ├── context.ts      # StageContext — DI bundle every stage receives
│   ├── triage.ts       # read-only plan generation; PLAN_MARKER
│   ├── replan.ts       # plan revision from human comments (has WebSearch)
│   ├── dev.ts          # worktree implementation + gate + draft PR
│   ├── revise.ts       # human PR feedback → worktree + gate; review re-runs after
│   └── review.ts       # JSON verdict + bounded fix loop
├── adapters/
│   ├── types.ts        # AgentAdapter / AgentRunOptions (access, webSearch, bashAllowlist) / AgentResult
│   ├── base.ts         # JsonlAdapter: shared env scrub + exec + JSONL parse + progress loop
│   ├── registry.ts     # ADAPTERS: name → { make, defaultModels }
│   ├── summarize.ts    # shared tool-input summarizer (adapters + dashboard transcript)
│   ├── usage.ts        # shared token extractor: per-adapter usage → one disjoint TokenUsage
│   ├── antigravity.ts  # agy -p --output-format stream-json --dangerously-skip-permissions
│   ├── claude.ts       # claude -p --output-format stream-json --verbose; maps access → --allowedTools
│   └── codex.ts        # codex exec --json; sandbox read-only / workspace-write; --search
├── server.ts           # cue ui: Bun.serve, SSE, process/run triggers
├── github.ts           # typed wrapper over the gh CLI
├── worktree.ts         # git worktree per issue; bootstraps empty repos
├── gates.ts            # deterministic test/lint runner (sh -c in the worktree)
├── specs.ts            # opt-in knowledge layer: specs dir + .cue/learnings.md, presence-detected
├── exec.ts             # THE ONLY place Bun.spawn is called
├── platform.ts         # POSIX/Windows personality: gate shell, agent env allowlist
├── config.ts           # valibot schema + resolveConfig
├── scaffold.ts         # creates/tops up the target's .cue/ (config, prompts, gitignore)
├── configure.ts        # cue init wizard: @clack/prompts behind an injectable Ask seam
├── spinner.ts          # single-slot ora frame; disabled when stdout is not a TTY
├── upgrade.ts          # cue upgrade: checksum-verified in-place binary update
├── embedded.ts         # prompt embedding for compiled binaries (with { type: "file" })
└── log.ts              # transcripts + cost under <target>/.cue/runs/<issue>/
prompts/                # packaged default role prompts
ui/                     # dashboard SPA, built to ui/build/client
tests/                  # one file per module + integration.test.ts

Invariants

These are load-bearing. Tests encode most of them.

  • All subprocess execution goes through Exec in src/exec.ts. Never call Bun.spawn anywhere else — that is what makes every module testable.
  • Lean dependencies. The CLI's only runtime deps are valibot, consola, ora, and @clack/prompts. The dashboard is a separate package (ui/package.json); its deps never enter the CLI. Do not add packages to either without discussion.
  • Label names are exact: agent:ready, agent:planned, agent:approved, agent:replan, agent:in-dev, agent:in-review, agent:revise, agent:done, agent:failed, agent:stop.
  • Plan-comment marker is exactly <!-- cue:plan --> (PLAN_MARKER in stages/triage.ts). Dev and replan find plans by the newest comment containing it.
  • Branch naming: agent/issue-<number>.
  • The GitHub token must never reach agent subprocesses. The shared adapter base (JsonlAdapter.run) scrubs the env down to an OS allowlist plus the adapter's own API keys — never another provider's credentials. Every adapter test asserts GH_TOKEN is absent.
  • Agents never run git/gh side effects. The runner owns commit, push, PR creation, and labels. Cue never merges and never touches the base branch.
  • Humans gate two moments: plan approval and PR merge. Do not automate either.
  • Issue bodies and comments are untrusted input. Every prompt states this.

How processing works

  1. cleanup reconciles PRs that have been merged or closed since the last run.
  2. nextAction maps the issue's labels to a stage (triage / replan / dev / revise).
  3. runIssue invokes that stage. On throw, it comments the error and applies agent:failed.
  4. Stages emit through ctx.onEvent. The CLI prints events; cue ui also broadcasts them over SSE.

runIssue is the single place that turns a stage error into an issue comment plus agent:failed.

Adapters

Codex is the default adapter and runs via codex exec --json; read-only stages use Codex's read-only sandbox and implementation stages use workspace-write. Antigravity runs via agy -p --output-format stream-json --dangerously-skip-permissions. Claude Code remains available via claude -p --output-format stream-json --verbose. All adapters scrub their environments, retaining only their own authentication variables and never GH_TOKEN. If a CLI update breaks an adapter, check that CLI's --help output first.

Releases

Push a v* tag. .github/workflows/release.yml runs scripts/build-binaries.sh, which:

  1. Builds the dashboard
  2. Regenerates the UI embed manifest from ui/build/client
  3. Compiles per-target binaries into dist/
  4. Restores the committed empty manifest stub

Release assets are dist/cue-* plus checksums.txt. install.sh and install.ps1 at the repo root are the checksum-verified installers.

Prompts embed via with { type: "file" } imports in src/embedded.ts. New disk assets (prompts, UI output) must join this embedding path or compiled installs break. Never commit a generated UI manifest.

More contributor workflow: Contributing.

Released under the MIT License.