@agentskit/harness 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,41 @@
1
+ # ADR-0027: Keep-pushing loop as composition over Orca
2
+
3
+ - Status: Accepted
4
+ - Date: 2026-09-11
5
+
6
+ ## Context
7
+
8
+ Teams want a coding-agent loop that drains one person's Linear queue around the clock: pick a ticket, freeze a
9
+ verifiable contract, run a worker in an isolated worktree, review, merge, repeat — while never exceeding the
10
+ machine, never guessing when acceptance criteria are missing, and surviving provider usage limits. The harness already
11
+ owns the deterministic pieces (dispatch ledger, machine pressure, delivery gates, evidence). Orca already owns
12
+ scheduling, worktrees, terminals, Linear access and the visibility surface. Nothing owned the composition.
13
+
14
+ ## Decision
15
+
16
+ 1. The loop lives in this package as **Adapter + Composition** (`src/adapters/{command,orca-cli,providers,linear-orca,
17
+ github-cli,code-review}.ts`, `src/loop/*`). The kernel is unchanged and never imports it (`pnpm test:boundaries`).
18
+ 2. Every project-specific value lives in `loop.config.yaml` (zod-validated). The code carries no repository, person,
19
+ path or model id. Env variable **names** may appear in config; values never do.
20
+ 3. External systems are reached only through argv-based, timeout-bounded process execution injected as a
21
+ `CommandRunner`. Adapters never spawn a shell; composition supplies the real runner; tests supply fakes.
22
+ 4. Orca is the single scheduler and visibility surface: two automations (`<prefix>-tick`, `<prefix>-deliver`), each
23
+ gated by a read-only `--precheck` that exits 0 only when work exists, run an agent prompt whose sole job is to
24
+ invoke `ak-harness loop <stage>`. No cron, launchd or daemon of our own.
25
+ 5. Model routing is tiered per role (`orchestrator`, `reviewer`, `builder`, `watcher`); within a tier the first
26
+ provider with usage available wins. Usage comes from `orca account list`; an auth/quota failure marks the provider
27
+ cooling down (exponential, never before the reset Orca reports) and the next candidate is tried.
28
+ 6. Autonomy envelope: the loop may claim tickets, freeze contracts, dispatch workers, nudge them, review, squash-merge on
29
+ a clean review plus green checks, move Linear states and clean worktrees. It must not merge PRs touching
30
+ `selfEditPaths`, must not force-push or bypass approvals, must not recover leases (human only, ADR-0021), and must
31
+ escalate with one deduplicated comment when a contract has no executable acceptance criterion or when fix rounds
32
+ are exhausted.
33
+
34
+ ## Consequences
35
+
36
+ - `yaml` and `zod` become runtime dependencies of the package.
37
+ - The loop is verifiable offline: every stage has fixture-driven tests and `--dry-run` prints the exact argv.
38
+ - Provider CLIs (`claude`, `codex`, `grok`, `opencode`) are configuration, not code; adding one is a YAML entry with a
39
+ TUI template, an optional headless template and an `agentskit-review` provider id.
40
+ - Windows is supported through Node's process model; WSL is detected and capped to one worker because host
41
+ antivirus load is invisible from the distro.
package/docs/LOOP.md ADDED
@@ -0,0 +1,195 @@
1
+ # Keep-pushing loop (`ak-harness loop`)
2
+
3
+ The loop drains one person's Linear queue through Orca worktrees, 24/7, with role-based model routing and
4
+ usage-aware fallback. The harness supplies the deterministic parts (claims, machine pressure, gates, evidence);
5
+ Orca supplies scheduling, worktrees, terminals, Linear access and visibility. Everything project-specific lives in
6
+ `loop.config.yaml` — switching project or person is a config change, never a code change.
7
+
8
+ Status: **complete (phases 1–5).** Config, doctor, adapters, the dispatch tick, the deliver stage and the Orca
9
+ automation installer are in place. See [ADR-0027](ADR-0027-keep-pushing-loop.md).
10
+
11
+ ## Commands
12
+
13
+ ```bash
14
+ ak-harness loop validate -f loop.config.yaml # schema check + effective config + hash
15
+ ak-harness loop doctor -f loop.config.yaml --json # readiness report (exit 1 when a blocking check fails)
16
+ ak-harness loop doctor --no-probe # skip provider probe commands
17
+ ak-harness loop precheck tick # exit 0 when a slot, a builder and a candidate exist (Orca --precheck)
18
+ ak-harness loop tick --dry-run --max 1 # plan the next dispatch, print the exact orca argv, write nothing
19
+ ak-harness loop tick # dispatch up to <free slots> workers
20
+ ak-harness loop contract ENG-123 [--refresh|--dry-run] # freeze or show the orchestrator contract for one issue
21
+ ak-harness loop precheck deliver # exit 0 when a dispatched issue is in flight
22
+ ak-harness loop deliver [--dry-run] [--issue ENG-123] # drive dispatched workers to merge
23
+ ak-harness loop install [--yes|--force|--skip-rehearsal|--skip-local-config|--dry-run|--plain] # guided: overlay → checks → rehearsal → confirm
24
+ ak-harness loop uninstall [--dry-run] # remove them
25
+ ak-harness loop status # what Orca knows: enabled, trigger, provider, latest run
26
+ ak-harness loop hook # one status line for a SessionStart hook; never mutates
27
+ ak-harness loop debrief [--issue ENG-123] [--since 24h] # human-facing: what is in flight, held, escalated
28
+ ak-harness loop watch [--issue ENG-123] [--once] [--interval 30] # poll delivery/PR; DONE|FAILED|ACTION_REQUIRED
29
+ ak-harness loop retro [--since 7d] [--json|--learnings] # weekly digest + calibration suggestions
30
+ ```
31
+
32
+ ### Debrief and watch
33
+
34
+ `loop debrief` is the human companion to the automations: a read-only Markdown (or `--json`) snapshot of
35
+ in-flight dispatches, delivery phase (waiting for PR / review / fix round / ready to merge), holds that need a
36
+ person, recent escalations and provider cooldowns. It does not call Orca or `gh` — only the loop state directory —
37
+ so it is safe to run from a SessionStart hook or a chat agent that needs context before acting.
38
+
39
+ `loop watch` polls `delivery.json` (and optionally the live PR via `gh`) and prints line-oriented events:
40
+
41
+ | Event | Meaning |
42
+ |---|---|
43
+ | `DONE` | Delivery finished as merged (or the PR is MERGED) |
44
+ | `FAILED` | stuck / abandoned / failed / PR closed without merge |
45
+ | `ACTION_REQUIRED` | held for a human, incomplete review twice, or fix-round findings |
46
+ | `PROGRESS` | still moving (waiting for PR, review pending, …) |
47
+
48
+ Use `--once` for a single snapshot; omit it to block until a terminal outcome (or `--timeout <seconds>`).
49
+
50
+ ## Running 24/7 with Orca
51
+
52
+ `loop install` is guided and rendered with Ink when stdin/stdout are terminals (plain lines otherwise). When no
53
+ `loop.config.local.yaml` exists next to the config it first offers to create one: it lists the Linear team members from
54
+ Orca, asks whose queue this machine drains and, optionally, how much RAM to keep free and the worker ceiling, then
55
+ writes the gitignored overlay and reloads. It then runs the doctor and the automation-environment checks (harness and review CLIs on
56
+ PATH, `gh auth status`, checkout registered in Orca, queue owner), stops on any failed check unless `--force`,
57
+ offers a dry-run tick rehearsal (one orchestrator call, nothing written), lists the exact automations it will
58
+ create, and asks for confirmation before touching Orca. `--yes` accepts every prompt for scripted setups; without
59
+ a TTY the command refuses unless `--yes` or `--dry-run` is given; `--plain` keeps the old check-free behaviour.
60
+
61
+ It creates two automations in the Orca runtime, each bound to the main checkout as an existing workspace
62
+ with session reuse:
63
+
64
+ | Automation | Trigger | Precheck (plain command, exit 0 = run) | Prompt |
65
+ |---|---|---|---|
66
+ | `<prefix>-tick` | `schedule.tick` | `<harnessCommand> loop stage tick -f <config>` (runs the tick, exits 1) | never reached under `runner: precheck` |
67
+ | `<prefix>-deliver` | `schedule.deliver` | `<harnessCommand> loop stage deliver -f <config>` (runs deliver, exits 1) | never reached under `runner: precheck` |
68
+
69
+ With the default `schedule.runner: precheck` the **precheck command is the stage itself** (`ak-harness loop stage
70
+ tick|deliver`): it runs the harness, prints the JSON report and always exits 1, so Orca records every run as
71
+ `skipped_precheck` with the report in `precheckResult.stdout` and never opens an agent session. This is deliberate:
72
+ Orca caps a precheck at 600 s, so `contract.timeoutMs` (default 300 s) plus dispatch must fit one run; an Orca-launched agent starts in bypass-permissions mode and waits for a human to accept the warning, which leaks one
73
+ stuck session per run. `schedule.runner: agent` keeps the legacy behaviour for providers that run unattended.
74
+ Runs and their output are visible in Orca's Automations view and via `loop status`.
75
+
76
+ `schedule.harnessCommand` must resolve inside Orca's environment: install the harness globally
77
+ (`npm i -g @agentskit/harness`) or set an absolute command. `loop install` warns when it cannot find it on this shell's PATH.
78
+
79
+ Optional SessionStart hook for Claude Code (`.claude/settings.json` of the target project):
80
+
81
+ ```json
82
+ { "hooks": { "SessionStart": [{ "hooks": [{ "type": "command", "command": "ak-harness loop hook -f loop.config.yaml", "timeout": 10 }] }] } }
83
+ ```
84
+
85
+ It prints `loop: installed (2/2, last run …)` or the install command; it never installs or changes anything.
86
+
87
+ ## Deliver
88
+
89
+ For every issue the loop dispatched (`<stateDir>/issues/<id>/dispatch.json`) and has not finished:
90
+
91
+ | Situation | Action |
92
+ |---|---|
93
+ | No PR, worker terminal alive and active | wait |
94
+ | No PR, idle ≥ `workerIdleTimeoutMin` | one check-in via `terminal send`; idle again after that → **stuck**: lease released, issue → `returnState` + `blocked`, worktree kept |
95
+ | No PR, terminal gone (> 5 min after dispatch) | **stuck** as above |
96
+ | PR touches `selfEditPaths` | **held**: one PR comment, no review, no merge |
97
+ | PR conflicting | rebase instruction to the worker, once per head (does not count as a fix round) |
98
+ | CI red | failing check names to the worker; counts as a fix round |
99
+ | CI pending / required check missing | wait |
100
+ | CI green, head not reviewed | `agentskit-review --pr … --block <minSeverity> --result … [--post]` with the first available reviewer candidate |
101
+ | Review findings ≥ floor | findings to the worker; counts as a fix round; same head is never re-reviewed |
102
+ | Fix rounds exhausted (`maxFixRounds`) | **blocked**: Linear comment + label + `returnState`, PR comment, lease released, worktree and PR kept |
103
+ | Review clean, `merge.auto` | `gh api PUT …/merge` with `sha=<reviewed head>` (GitHub refuses if the head moved) → Linear attach + comment + `doneState`, worktree removed when `cleanupWorktree` |
104
+ | PR merged outside the loop | same completion path |
105
+ | PR closed without merge | **abandoned**: lease released, issue → `returnState`, worktree kept |
106
+
107
+ State lives in `<stateDir>/issues/<id>/delivery.json` (reviews per head, fix rounds, nudges) and every decision is
108
+ appended to `<stateDir>/events.ndjson`. `--dry-run` reports the decision for each issue without touching anything.
109
+
110
+ ## One tick
111
+
112
+ 1. **Intake** — `orca linear list-issues` once per configured state, filtered and ordered locally; issues already
113
+ leased, linked to a live worktree or sitting on their branch are *busy* and skipped.
114
+ 2. **Admit** — `assessSlots` (machine) and the harness dispatch ledger (`<stateDir>/coordination`) decide how many
115
+ workers may start; a claim is atomic per issue/worktree/branch.
116
+ 3. **Contract** — the orchestrator (first available candidate across `models.orchestrator` tiers) reads the issue and
117
+ the repository read-only and returns a JSON contract between `<<<LOOP_CONTRACT … LOOP_CONTRACT>>>`. Issue text is
118
+ wrapped as untrusted data. An auth/quota failure on one candidate marks it cooling down and the next is tried.
119
+ Contracts are cached under `<stateDir>/issues/<id>/contract.json` while the issue is unchanged.
120
+ 4. **Dispatch or escalate** — a contract with at least one executable outcome and no blocking ambiguity becomes a
121
+ worker: `orca worktree create --linear-issue <url> --base-branch <base> --no-parent` (no `--agent`: Orca's agent
122
+ launcher runs Claude in bypass-permissions mode and waits for a human), then `orca terminal create --command
123
+ "<providers.<id>.tui>"`, `terminal wait --for tui-idle`, and the brief goes in through `terminal send`. The issue
124
+ moves to `In Progress` and one dedicated comment is posted. Otherwise the loop posts one
125
+ `needs-info` comment (deduped by contract digest), adds the label, and moves on without consuming a slot.
126
+
127
+ Every side effect is recorded in `<stateDir>/events.ndjson`; `dispatch.json` per issue links the worktree,
128
+ terminal handle, provider/model and lease for the deliver stage.
129
+
130
+ Start from [`loop.config.example.yaml`](../loop.config.example.yaml) at the package root.
131
+
132
+ ## What the doctor checks
133
+
134
+ | Check | Source | Blocking |
135
+ |---|---|---|
136
+ | `orca.version` / `orca.runtime` | `orca --version`, `orca status --json` | yes — the loop cannot run without a reachable Orca runtime at or above `orca.minVersion` |
137
+ | `provider.<id>` | PATH lookup, `orca account list` usage windows, `orca agent hooks status`, env key names, optional probe, cooldown store | no — reported per provider |
138
+ | `routing.<role>` | tiers from `models.<role>` filtered by provider availability | yes — a role with no available provider blocks |
139
+ | `machine.slots` | `sampleMachine` + `adaptiveConcurrency`, free RAM reserve, WSL cap, running worktrees | no — 0 free slots is a warning, not a failure |
140
+ | `linear.queue` | `orca linear list-issues` per configured state, filtered and ordered locally | yes — an unreachable Linear blocks |
141
+
142
+ ## Model routing
143
+
144
+ `models.<role>` is a list of tiers; each tier is a list of `provider/model`. Tiers are tried in order and, inside a
145
+ tier, providers in declaration order. The first provider that is **available** wins. A provider is available when
146
+ its binary is on PATH, its auth is not known to be missing, no Orca usage window is at or above
147
+ `cooldown.exhaustedPercent`, it is not cooling down, and its optional probe passed.
148
+
149
+ Providers authenticate through their own CLI login (`claude login`, `codex login`, `grok login`); only providers
150
+ declared `auth: api-key` need an environment variable, and the loop never reads its value. Orca has no per-run model flag; the chosen model is rendered into `providers.<id>.tui` (for example
151
+ `codex -m {model} --full-auto`) and launched in the worker terminal.
152
+
153
+ When a provider runs out of usage the loop records a cooldown in `<stateDir>/provider-cooldowns.json`:
154
+ `initialMin` doubling up to `maxMin`, never earlier than the reset instant Orca reported.
155
+
156
+ ## Machine slots
157
+
158
+ `maxAgents = max(floor, min(adaptiveConcurrency(ceiling), ramBound, wslCap?))` where `ceiling` defaults to
159
+ `cpus / 2`, `ramBound` fits `agentRssMb` agents into free RAM minus `minFreeRamGb`, and the WSL cap applies only
160
+ inside a WSL distro (host Defender load is invisible there). The loop never lowers a running worker; it only
161
+ decides whether to start another.
162
+
163
+ ## Boundaries
164
+
165
+ Adapters (`src/adapters/command.ts`, `orca-cli.ts`, `providers.ts`, `linear-orca.ts`) depend on kernel contracts
166
+ only and receive a `CommandRunner`; they never spawn a shell. Composition (`src/loop/*`) supplies the real
167
+ runner and wires adapters together. See `docs/MODULE-BOUNDARIES.md`.
168
+
169
+ ## Security notes
170
+
171
+ - `loop.config.yaml` holds env variable **names**, never values.
172
+ - Every external call is argv-based with a timeout; `ok: false` envelopes fail closed.
173
+ - Linear text is data. Later phases render it into worker briefs inside delimiters and never execute it.
174
+
175
+ ## Continuous improvement: `loop retro`
176
+
177
+ `ak-harness loop retro --since 7d` reads `<stateDir>/events.ndjson`, every `issues/<id>/{contract,dispatch,delivery}.json`,
178
+ `provider-cooldowns.json` and (unless `--no-orca`) the Orca run records of the two automations, and prints a Markdown
179
+ digest:
180
+
181
+ - **Numbers** — contracts frozen, escalation rate, dispatches by provider, merged/blocked/stuck/abandoned/in flight,
182
+ review outcomes, fix rounds, median dispatch→merge, cooldowns, Orca runs (idle/work/timed out, durations).
183
+ - **Problems** — escalation reasons grouped by shape, blocked/stuck/abandoned issues.
184
+ - **What worked** — merged issues with worker, lead time and fix rounds.
185
+ - **Adjustments — project** — rule-based suggestions about the target project, each with its evidence and the
186
+ `loop.config.yaml` knob to turn (`escalation-rate`, `review-floor`, `fix-rounds`, `stuck-workers`, `review-incomplete`,
187
+ `provider-cooldowns`, `idle-loop`, `lead-time`, or `steady`).
188
+ - **Adjustments — harness** — defects or limitations of the library itself, derived from events the loop only emits
189
+ when its own machinery misbehaved (`worker-relaunch`, `dispatch-failures`, `contract-failures`, `merge-refusals`,
190
+ `review-tool-errors`, `stage-timeout`), with a pointer to the harness issue tracker. `--target project|harness`
191
+ filters one side.
192
+
193
+ The headings follow the harness retro grammar, so `--learnings` prints `LearningRecord`s in `proposed` state; a human
194
+ promotes them with `promoteLearnings` (ADR-0019 keeps that decision human). The calibration loop is: read the digest →
195
+ change one knob in `loop.config.yaml` → next digest measures the effect.
@@ -9,7 +9,7 @@ does not claim that the proposed 0.4.0 boundary has already been accepted.
9
9
 
10
10
  | Class | Meaning | Dependency rule |
11
11
  | --- | --- | --- |
12
- | Kernel | Deterministic contracts, state transitions, policy and metric projections | May use Node standard library and other kernel modules; must not import adapters, provider SDKs, CLI composition, or credentials. |
12
+ | Kernel | Deterministic contracts, state transitions, policy and metric projections | May use Node standard library and other kernel modules; must not import adapters, provider SDKs, CLI composition, credentials, or UI libraries (`ink`/`react` are composition-only). |
13
13
  | Execution support | Local process, filesystem, event, evidence, and run lifecycle plumbing | May depend on the kernel; must expose provenance and fail closed at trust boundaries. |
14
14
  | Adapter | Provider-specific or external-system integration | May depend on kernel contracts; must not be imported by kernel modules. |
15
15
  | Composition | Package and CLI entry points | May compose kernel, execution support, and adapters; consumers use `src/index.ts`. |
@@ -73,6 +73,30 @@ modules, not a provider dependency.
73
73
  | `src/adapters/agent.ts` | Adapter | Structured coding-agent execution with bounded timeout and failure classification | `errors`, `resilience`, `adapter-contract` | Agent/provider callback supplied by caller |
74
74
  | `src/adapters/orca.ts` | Adapter | Safe, idempotent Orca dispatch plan | `errors`, `hash`, `preflight` | Orca CLI arguments; no execution |
75
75
  | `src/adapters/tracking.ts` | Adapter | Idempotent provider-neutral tracking transition | `errors`, `hash` | User-supplied Linear/GitHub/etc. handler |
76
+ | `src/adapters/command.ts` | Adapter | Shell-free command runner seam, PATH executable lookup, Orca JSON envelope parsing | stdlib | Command runner supplied by composition or tests |
77
+ | `src/adapters/orca-cli.ts` | Adapter | Typed `orca … --json` calls: version/status/account/agent hooks, worktree ps/create/set/rm, terminal list/send/wait/read, automations list/create/edit/remove/run | `errors`, `command` | Orca CLI via the injected runner |
78
+ | `src/adapters/providers.ts` | Adapter | Coding-agent CLI availability: binary, auth, Orca usage windows, exhaustion, cooldown, optional probe | `command` | PATH, env var names, `orca account list` payload |
79
+ | `src/adapters/linear-orca.ts` | Adapter | Linear queue via `orca linear list-issues`, issue detail, status/comment/label/attach writes with deterministic `--write-id`, `TrackingAdapter` over Orca | `errors`, `hash`, `orca-cli`, `tracking`, `command` | Linear through the Orca CLI |
80
+ | `src/adapters/github-cli.ts` | Adapter | Pull request snapshots, check classification, self-edit path guard, optimistic squash-merge and comments via `gh` | `errors`, `command` | GitHub through the `gh` CLI |
81
+ | `src/adapters/code-review.ts` | Adapter | `agentskit-review` argv, `--result` parsing, severity floor, worker-facing findings | `command` (type-only) | AgentsKit code review CLI via runner |
82
+ | `src/loop/config.ts` | Composition | `loop.config.yaml` schema (zod), loading, model reference parsing | `errors`, `hash`, `model-policy`, `yaml`, `zod` | Project config file |
83
+ | `src/loop/process.ts` | Composition | Real shell-free `CommandRunner` with timeout and output caps | stdlib, `command` (type-only) | Child processes |
84
+ | `src/loop/slots.ts` | Composition | Machine slot assessment: adaptive concurrency, RAM reserve, WSL cap, floor | stdlib, `machine`, `config` | Host CPU/memory |
85
+ | `src/loop/routing.ts` | Composition | Tiered role → provider/model selection over provider availability | `model-policy`, `providers` (type-only), `config` | None |
86
+ | `src/loop/cooldown.ts` | Composition | Provider cooldown store with exponential backoff | stdlib, `providers` | `<stateDir>/provider-cooldowns.json` |
87
+ | `src/loop/doctor.ts` | Composition | Loop readiness report: Orca, providers, routing, slots, workers, queue | adapters, `config`, `cooldown`, `routing`, `slots` | Orca CLI via runner |
88
+ | `src/loop/contract.ts` | Composition | Task contract schema, orchestrator prompt (issue text as untrusted data), marked-JSON parsing, dispatchability assessment, candidate fallback with auth/quota classification, contract cache | `errors`, `hash`, `resilience`, `doc-bridge`, `config`, `routing` (type-only), `zod` | Headless coding-agent CLI via runner |
89
+ | `src/loop/brief.ts` | Composition | Worker prompt: frozen contract + repository rules + protected paths + done signal | `contract`, `config`, `linear-orca` (type-only) | None |
90
+ | `src/loop/tick.ts` | Composition | One keep-pushing tick: intake, admit (slots + dispatch ledger claim), contract, dispatch into an Orca worktree, Linear transition, escalation, precheck | adapters, `coordination`, `errors`, `hash`, `brief`, `config`, `contract`, `cooldown`, `doctor`, `routing`, `slots` | Orca CLI + Linear via runner; `<stateDir>` files |
91
+ | `src/loop/deliver.ts` | Composition | Deliver stage per dispatched issue: PR detection, self-edit hold, conflict/CI/review fix rounds via terminal, review at head, optimistic squash-merge, Linear Done, cleanup, stuck/abandoned escalation | adapters, `coordination`, `errors`, `config`, `cooldown`, `doctor`, `routing`, `tick` | Orca, Linear, GitHub, agentskit-review via runner; `<stateDir>` files |
92
+ | `src/loop/install.ts` | Composition | Orca automation specs (`<prefix>-tick`, `<prefix>-deliver`) with read-only prechecks, idempotent create/edit by name, uninstall, status and the SessionStart hook line | `command`, `orca-cli`, `providers`, `errors`, `config`, `cooldown`, `doctor`, `routing` | Orca automations via runner |
93
+ | `src/loop/guided-install.ts` | Composition | Interactive install: doctor + environment preflight, dry-run rehearsal, confirmation, install, status; readline IO injected | `command`, `orca-cli`, `config`, `doctor`, `install`, `tick`, stdlib readline | Terminal prompts; Orca via runner |
94
+ | `src/loop/local-config.ts` | Composition | Per-machine overlay wizard: Linear team members via Orca, queue owner and machine tuning answers, YAML rendering and reload | `orca-cli`, `config`, `yaml` | Orca via runner; writes `loop.config.local.yaml` |
95
+ | `src/loop/retro.ts` | Composition | Retro digest over `events.ndjson`, per-issue state, cooldowns and Orca runs; rule-based calibration suggestions; Markdown that `parseRetro` can lift into learnings | `orca-cli`, `hash`, `learning`, `config`, `contract`, `cooldown`, `deliver`, `install`, `tick` | Orca via runner (optional); `<stateDir>` files |
96
+ | `src/loop/debrief.ts` | Composition | Human-facing read-only snapshot of what the loop is working on (in-flight, holds, escalations, cooldowns) | `config`, `contract`, `cooldown`, `deliver`, `retro`, `tick` | `<stateDir>` files only |
97
+ | `src/loop/watch.ts` | Composition | Poll delivery (+ optional live PR) and emit DONE/FAILED/ACTION_REQUIRED/PROGRESS for agents or humans | `command`, `github-cli`, `config`, `deliver`, `tick` | optional `gh` via runner; `<stateDir>` files |
98
+ | `src/loop/ui/components.tsx` | Composition | Ink components: check rows, sections, banner, spinner, select/confirm/text prompts | `ink`, `react`, `doctor` (type-only) | Terminal |
99
+ | `src/loop/ui/terminal.tsx` | Composition | `createRichIO`: Ink-backed IO for TTYs with a plain-text fallback | `ink`, `react`, `components`, `guided-install` (type-only) | Terminal |
76
100
 
77
101
  ## Allowed dependency directions and exceptions
78
102
 
@@ -130,6 +154,10 @@ Orca, and tracking. No adapter implementation is re-exported wholesale.
130
154
  | Doc Bridge | `src/adapters/doc-bridge.ts` | Reads a local index | Keep behind `ContextProvider`; measure context hit/quality separately. |
131
155
  | Orca | `src/adapters/orca.ts` | None; produces argv and lifecycle projections only | Keep lease/worktree/issue-lock/SHA planning provider-neutral; execution belongs to the orchestrator. |
132
156
  | Linear/GitHub/other tracker | `src/adapters/tracking.ts` callback | Caller-owned network mutation | Require idempotency key and explicit tracking authorization. |
157
+ | Orca CLI (loop) | `src/adapters/orca-cli.ts`, `src/adapters/linear-orca.ts` via `CommandRunner` | Read-only `--json` calls in the doctor; dispatch/mutation arrive in later loop phases | Argv only, never a shell string; every call bounded by a timeout; envelope `ok:false` fails closed. |
158
+ | Coding-agent CLIs | `src/adapters/providers.ts` | PATH lookup and optional probe command | Env keys are names only; usage comes from Orca, never from provider SDKs. |
159
+ | GitHub | `src/adapters/github-cli.ts` via `CommandRunner` | `gh pr view/list`, `gh api PUT …/merge` with `sha=<reviewed head>`, `gh pr comment` | Merge is refused by GitHub when the head moved; every call argv-based and bounded. |
160
+ | AgentsKit code review | `src/adapters/code-review.ts` via `CommandRunner` | `agentskit-review --pr … --result <file> [--post]` | Exit codes 0/1/2 plus the private result file decide clean/findings/incomplete; the floor is `--block`. |
133
161
  | Process runtime | `src/execution/runtime.ts` | Starts child processes | Execution support; policy and evidence gates remain kernel decisions. |
134
162
  | Docker runtime | `src/execution/runtime.ts` | Starts Docker containers | Optional sandbox selected by config, never a mandatory kernel dependency. |
135
163
  | LLM provider/model | Caller/plugin | Provider call and token spend | Bind provider/model in experiment metadata; do not embed SDKs in kernel. |