Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
2e21cd3
Plan: stage-bound flow runtime (ADR 0018 + implementation plan)
adamw Jun 23, 2026
7215051
feat(json): JsonData givens for stage-result primitives, Option, List…
adamw Jun 23, 2026
38a46ca
feat(plan): JsonData for SessionId and PlanLike
adamw Jun 23, 2026
502449a
fix(plan): simplify/harden PlanLike JsonData per review
adamw Jun 23, 2026
9688a3d
feat(flow): FlowControl + InStage capability types
adamw Jun 23, 2026
6840f24
refactor(flow,tools): move InStage to tools module; tidy capability t…
adamw Jun 23, 2026
546294e
feat(progress): progress log model (ProgressHeader/StageEntry/Progres…
adamw Jun 23, 2026
7291b74
feat(progress): OS-backed ProgressStore with upsert + deterministic path
adamw Jun 23, 2026
5c30f4c
feat(flow): injection-safe branch naming (slug, issue, fromText)
adamw Jun 23, 2026
1172734
feat(flow): resumable committing stages + flow lifecycle
adamw Jun 23, 2026
b0113f9
fix(flow): robust teardown, drop dead featureBranch, lifecycle tests
adamw Jun 23, 2026
3d1d524
docs(flow): accurate teardown comments; swallow cosmetic cleanup-comm…
adamw Jun 23, 2026
3053a89
refactor(plan): retire persistence; single always-briefed Plan (ADR 0…
adamw Jun 23, 2026
d80558e
fix(plan): guard empty-brief taskPrompt; restore SessionId codec test
adamw Jun 23, 2026
c167a66
chore(sdd): record F2 resequence in progress ledger
adamw Jun 23, 2026
f1c4301
chore: gitignore agent scratch (.superpowers, .claude/worktrees); unt…
adamw Jun 23, 2026
8e4f958
Final-review fixes: exit-free runFlow seam + crash→resume test; cleanups
adamw Jun 23, 2026
7e22b21
feat(llm): generic llm.cheap + leading-model FlowContext.llm accessor
adamw Jun 23, 2026
fc5d27b
refactor(llm): cheap overrides return concrete sub-trait (chainable)
adamw Jun 23, 2026
f5841db
feat(session): persist session records in the log + pure llm.session …
adamw Jun 23, 2026
e15466d
refactor(session): single-traversal resume lookup; clarify session() …
adamw Jun 23, 2026
03c2024
style(session): scalafmt Session.scala
adamw Jun 23, 2026
315eb1c
feat(session): best-effort sessionExists probes per backend (R22)
adamw Jun 24, 2026
41c3601
fix(session): validate session id in probes (path/regex/URL injection…
adamw Jun 24, 2026
b208ce8
feat(session): runSeeded — prime seed + progress preamble when sessio…
adamw Jun 24, 2026
450a9f2
fix(session): runSeeded prompt composition (no bare separator) + tigh…
adamw Jun 24, 2026
97ae817
docs(examples): convert example flows to stage/session API (ADR 0018 §3)
adamw Jun 24, 2026
eb1a899
fix(flow): leading model as FlowContext selector; examples compile ag…
adamw Jun 24, 2026
9a357aa
fix(examples): checkpoint comment stages; tidy Triage doc/field; lead…
adamw Jun 24, 2026
ed52f7e
feat(session): persist + rehydrate client→server map; server-id resum…
adamw Jun 24, 2026
08deabf
test(session): cover serverId no-clobber; assert gemini probes server…
adamw Jun 24, 2026
3363d44
feat(gh): idempotent createPr + upsertComment (R24); examples use them
adamw Jun 24, 2026
756dfd3
test(gh): assert idempotency call paths; dedup; tidy GhPrListJson + m…
adamw Jun 24, 2026
88729b3
feat(flow): recovery hardening — validate untrusted header (R32/R30/R…
adamw Jun 24, 2026
2bfdc35
feat(flow): shortenPrompt default + llm commit messages (R13) + throw…
adamw Jun 24, 2026
446ef1a
test(flow): de-tautologize R5 failure-keep assertion; cover shortenPr…
adamw Jun 24, 2026
b7c01d8
feat(capabilities): gate mutating tool methods with (using InStage) (…
adamw Jun 24, 2026
5b7bce6
feat(capabilities): gate addWorktree/removeWorktree with (using InStage)
adamw Jun 24, 2026
8f8d45f
docs: rewrite README for the stage/session runtime + authoring rules …
adamw Jun 24, 2026
89cc6f5
docs: fix README capability accuracy (llm.session/runSeeded) + Pi exa…
adamw Jun 24, 2026
b6849ac
docs(examples): normalize dep version pins to one snapshot
adamw Jun 24, 2026
6b0a57d
docs(adr): R24 reconciled to head-only PR matching (orca creates one …
adamw Jun 24, 2026
e99b361
fix: ADR-0018 audit findings (default-branch protection, double-brief…
adamw Jun 24, 2026
c9360cb
test(recovery): exercise the protected-branch path with a lowercase d…
adamw Jun 24, 2026
3f98062
refactor(flow): extract llm.cheapOneShot; dedup branch-naming + commi…
adamw Jun 24, 2026
66902ab
refactor(flow): single slug-segment predicate shared by producer + va…
adamw Jun 24, 2026
94c5196
refactor(backend): factor sessionExists guard/catch/serverFor into Ll…
adamw Jun 24, 2026
89920f5
refactor(tools): extract uniform fail() for unrecoverable git/gh CLI …
adamw Jun 24, 2026
bcf975d
refactor(tools): extract gh/git wire DTOs to GhJson.scala
adamw Jun 24, 2026
5900da8
refactor(runner): extract flow setup/teardown/recovery into FlowLifec…
adamw Jun 24, 2026
03f1f32
test: extract shared GitRepo temp-repo fixture (orca.testkit)
adamw Jun 24, 2026
df7df8d
fix(claude): persist + rehydrate the session claim so resume uses --r…
adamw Jun 25, 2026
f2653bc
fix(flow): strip markdown fences from cheap-model replies; neutral se…
adamw Jun 25, 2026
4877ffb
fix(codex): don't pass --sandbox/--full-auto to 'exec resume'
adamw Jun 25, 2026
6cbbd54
docs: tighten README + collapse deep reference behind <details>
adamw Jun 25, 2026
1bd72d4
docs: move runtime internals to AGENTS.md; keep README user-facing
adamw Jun 25, 2026
3548d87
feat(capabilities): friendly @implicitNotFound for the in-stage gate
adamw Jun 25, 2026
ec7dc4d
feat(capabilities): friendly @implicitNotFound for FlowControl/FlowCo…
adamw Jun 25, 2026
dcadc77
docs: drop ADR requirement-number refs from code comments
adamw Jun 25, 2026
0e7ed3c
refactor: require an explicit leadModel; examples/docs use <lead>.cheap
adamw Jun 25, 2026
dd6c76b
feat(llm): withCheapModel + provider-matched opencode cheap; close re…
adamw Jun 25, 2026
413802f
fix(opencode): never throw resolving cheap; README opencode cheap note
adamw Jun 25, 2026
e5ff592
feat(flow): stay on the feature branch by default; returnToStartBranc…
adamw Jun 25, 2026
1f8f695
chore: address leftover TODOs (virtual threads, visibility, call-clas…
adamw Jun 26, 2026
7840757
feat(flow): backend-agnostic 'agent' accessor for the leading harness
adamw Jun 26, 2026
d171de4
refactor: rename LlmTool hierarchy + Llm* types to Agent; orca.llm ->…
adamw Jun 27, 2026
7977ccd
refactor(flow): drop misleading FlowContext.llm; consistent 'agent' n…
adamw Jun 27, 2026
873e152
refactor(flow): agent as an ordinary FlowContext accessor (flow[B] + …
adamw Jun 27, 2026
f5841f8
refactor(agents): scope/clarify the public Agent surface; surface wit…
adamw Jun 27, 2026
8ab1e9e
docs/api: review-pass clarity fixes + codex/gemini flow overrides
adamw Jun 27, 2026
80d3010
refactor(backend): dedupe the stream-backend drivers
adamw Jun 27, 2026
503f3b2
refactor(session): rename the resume "server id" vocabulary to resume…
adamw Jun 27, 2026
69c091a
refactor(review): thread reviewAndFixLoop state instead of a captured…
adamw Jun 27, 2026
a0cc980
refactor(tools): centralise git/gh recoverable-failure classification
adamw Jun 27, 2026
fbd879e
feat(session): warn on a seed-divergent session resume
adamw Jun 27, 2026
ca70d6c
refactor(plan,usage): drop the dead Plan parser; document the Usage c…
adamw Jun 27, 2026
23b114d
refactor(backend): structured-concurrency conversation runtime
adamw Jun 27, 2026
99032f9
refactor(opencode): structure the serve drains as Ox forks
adamw Jun 28, 2026
2ffcef7
fix(opencode): tree-kill the serve process on shutdown
adamw Jun 28, 2026
f67686e
refactor(subprocess): drop dead sendSigIntTree; harden + test tree-kill
adamw Jun 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,7 @@ openspec/

# Sandcat
.devcontainer
.sandcat/settings.local.json
.sandcat/settings.local.json
# agent scratch (SDD ledger/reports, transient worktrees)
.superpowers/
.claude/worktrees/
99 changes: 70 additions & 29 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,76 @@ listed in the README.
```
orca/
├── build.sbt / project/
├── tools/ # tool interfaces + os-backed impls + structured I/O + event bus
├── flow/ # FlowContext, stage/fail; orca.review (ReviewTypes, ReviewLoop, Reviewers); orca.bug; orca.plan
├── claude/ # Claude Code backend + DefaultClaudeTool + DefaultLlmCall
├── codex/ # Codex backend (codex exec --json over stdio)
└── runner/ # flow() entry + DefaultFlowContext + terminal layer
├── tools/ # tool traits + os-backed impls (git/gh/fs), LLM SPI + session registry, InStage, events, subprocess
├── flow/ # stage/display/fail + FlowContext/FlowControl; orca.{plan,review,pr,progress}
├── claude/ codex/ gemini/ opencode/ pi/ # one module per coding-agent backend
└── runner/ # flow() entry, DefaultFlowContext, FlowLifecycle, terminal UI
```

Dependency graph:

```
tools (standalone)
├── flow → tools
├── claude → tools
├── codex → tools
└── runner → tools + flow + claude + codex
├── flow → tools
├── claude / codex / gemini /
│ opencode / pi → tools
└── runner → tools + flow + all five backends
```

The runner module owns the `flow` entry point (`package orca`) and wires
defaults via `DefaultFlowContext` (`package orca.runner`). Its terminal UI
lives in its own sub-package, `orca.runner.terminal`, so swapping it for a
Slack or HTTP equivalent is a matter of substituting one `Interaction` at
the call site rather than rewiring modules.

Only the user-facing surface lives in `package orca` (the `flow` entry
point, the tool traits, the accessors, `JsonData`, `OrcaArgs`).
Implementations live in focused subpackages: `orca.tools.fs` /
`orca.tools.git` / `orca.tools.github` (os-backed tool impls),
`orca.tools.claude` / `orca.tools.codex` (LLM backends), `orca.subprocess`
(subprocess shim), `orca.io` (structured-I/O plumbing), `orca.runner` /
`orca.runner.terminal` (wiring and terminal UI).
defaults via `DefaultFlowContext` (`package orca.runner`); the stage
setup/teardown/recovery state machine is `orca.runner.FlowLifecycle`. The
terminal UI lives in `orca.runner.terminal`, behind an `Interaction`, so
swapping it for a Slack or HTTP equivalent is one substitution at the call
site rather than rewiring modules.

The user-facing surface lives in `package orca` (the `flow` entry, the tool
accessors — including `agent`, the backend-agnostic leading-agent accessor (an
ordinary `FlowContext` member typed `Agent[ctx.LeadB]`, where `flow[B]` captures
the selector's backend tag into the `FlowContext { type LeadB }` member) —
`stage`/`display`/`fail`, `JsonData`, `OrcaArgs`). Implementations
live in focused subpackages: `orca.tools` (os-backed git/gh/fs impls + their
traits), `orca.agents` + `orca.backend` (LLM SPI, `SessionRegistry`, conversation
driver), `orca.subprocess` (subprocess shim), `orca.events` (event bus), one
`orca.tools.<backend>` per coding agent, and `orca.runner` / `orca.runner.terminal`
(wiring + terminal UI). The flow module adds `orca.{plan,review,pr,progress}`.

## The stage-bound runtime

The flow runtime is specified in [ADR 0018](adr/0018-stage-bound-flow-runtime.md)
read it before touching `stage`, the progress log, or sessions. The invariants
most easily broken:

- **Capability gating.** Three compile-time capabilities gate side effects:
`FlowContext` (reads + emit; thread-safe), `FlowControl <: FlowContext`
(authority to start a stage; thread-affine), and the opaque `InStage` token
(in `tools`, `package orca`). Every mutating tool method — git writes,
`fs.write`, `gh` writes, every `agent.*.run` — takes `(using InStage)`, which
only a `stage` body mints. Don't relax this: don't mint `InStage.unsafe`
outside the runtime (`Flow` / `FlowLifecycle` / `Session`), and don't drop a
`(using InStage)` to "make it compile" — thread it up to the nearest stage.
`orcacaps.InStageNegativeTest` pins that a mutation outside a stage fails to
compile.

- **Progress log + recovery.** A run commits `.orca/progress-<hash>.json` (hash =
prompt, so the path is branch-independent) with one entry per completed stage;
a re-run replays recorded entries and skips them. The header is untrusted on
load — `orca.progress.RecoveryCheck` validates it (safe ref, prompt-hash match,
protected-branch refusal) before any destructive git op.

- **Sessions.** `SessionRegistry` has two shapes: `ClaimedOnce` (claude/pi — the
client id IS the wire id) and `ClientToServer` (codex/gemini/opencode — a
server-minted id learned from the protocol). The id persisted for resume is the **resume wire
id** — uniformly "the id to put on the wire when resuming" (`Dispatch.wireId`):
for codex/gemini/opencode a server-thread id, for claude the client id itself,
for pi `None` (ephemeral). A backend whose sessions survive a process restart
MUST wire **both** `resumeWireId` (so the runtime records it in the log) and
`registerSession` (so `rehydrateSessions` re-claims it on resume) — claude and
codex each shipped a resume bug from getting this wrong. Note `ClaimedOnce` is
not enough on its own: claude overrides `resumeWireId` to persist (durable
on-disk sessions), pi does **not** (its temp-dir sessions can't survive).
`sessionExists` is a best-effort, non-destructive probe; when it can't confirm a
live session the flow re-seeds, the uniform fallback that holds on every backend.

## Build and test

Expand Down Expand Up @@ -73,20 +113,21 @@ Some tests shell out to real external tools and skip by default:

```bash
ORCA_INTEGRATION=1 sbt test
ORCA_INTEGRATION=1 sbt "claude/testOnly orca.claude.ClaudeIntegrationTest"
ORCA_INTEGRATION=1 sbt "claude/testOnly orca.tools.claude.ClaudeIntegrationTest"
ORCA_INTEGRATION=1 sbt "tools/testOnly orca.tools.OsGitHubIntegrationTest"
ORCA_INTEGRATION=1 sbt "runner/testOnly orca.runner.terminal.ScalaCliSmokeTest"
```

| Suite | Needs |
|---|---|
| `ClaudeIntegrationTest` | `claude` authenticated |
| `{Claude,Codex,Gemini,Opencode,Pi}IntegrationTest` (one per `orca.tools.<backend>`) | that backend's CLI authenticated |
| `OsGitHubIntegrationTest` | `gh` authenticated |
| `ScalaCliSmokeTest` | `scala-cli`; runs `sbt publishLocal` internally |

Unit tests use in-memory fakes (`StubCliRunner`, `FakeLlmTool`,
`FakeCliProcess`, `TestFlowContext`) — no network, no real filesystem
outside of `os.temp.dir()`.
Unit tests use in-memory fakes (`StubCliRunner` / `SpawnStubCliRunner`,
`FakeAgent`, `FakePipedCliProcess`, `TestFlowContext` / `TestFlowControl`) and
the shared `orca.testkit.GitRepo` temp-repo fixture (published via `tools %
test->test`) — no network, no real filesystem outside `os.temp.dir()`.

### Iterating quickly

Expand Down Expand Up @@ -140,9 +181,9 @@ sbt publishLocal
```

Installs `org.virtuslab::orca:0.0.14` plus its transitive modules
(`orca-tools`, `orca-flow`, `orca-claude`, `orca-codex`) into
`~/.ivy2/local` so a flow script with `//> using repository ivy2Local` can
resolve them.
(`orca-tools`, `orca-flow`, and the five backends
`orca-{claude,codex,gemini,opencode,pi}`) into `~/.ivy2/local` so a flow script
with `//> using repository ivy2Local` can resolve them.

For an iteration loop while hacking on Orca itself, run sbt in one
terminal with a `~` watch-and-publish:
Expand Down
Loading
Loading