@agentskit/harness 0.7.0 → 0.9.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,68 @@
1
+ # ADR-0029: Loop resilience, brief pinning, worktree setup, effort routing, and GitHub intake
2
+
3
+ - Status: Accepted
4
+ - Date: 2026-09-12
5
+
6
+ ## Context
7
+
8
+ The 2026-09-11/12 pilot ran the keep-pushing loop (ADR-0027) unattended for ~7 hours and surfaced two production
9
+ defects and, separately, a review of `ColeMurray/background-agents` (an open-source clone of Ramp's Inspect
10
+ background-agent system) suggested transferable ideas already partially covered by this harness's own mechanisms.
11
+
12
+ Measured from `events.ndjson`: `classifyProviderFailure` did not recognise real Claude/Codex usage-limit phrasing
13
+ ("You've hit your session limit", "usage limit reached") or Grok's ("Overloaded", "temporarily limiting requests"),
14
+ so every such failure fell through to `other` and `onProviderFailure` never fired — no cooldown was ever recorded.
15
+ The same gap existed on the review path (`agentskit-review` exit classified without checking the same patterns).
16
+ Result: 19 unclassified `contract.failed` retries across 4 issues and 12 incomplete reviews over 7h, with no cap
17
+ anywhere — nothing counted consecutive failures on an issue or a stage, so a single misclassified error retried
18
+ forever.
19
+
20
+ ## Decision
21
+
22
+ 1. **Failure classification** (bug fix, no schema change): `classifyProviderFailure` (`src/loop/contract.ts`) checks
23
+ a `QUOTA_PATTERN` covering the phrasing above before falling back to the kernel's generic `classifyFailure`, and
24
+ `extractResetsAt` parses a relative (`resets in 3h`) or clock-time (`resets 10:40pm`) reset out of the message.
25
+ `deliver.ts`'s review-incomplete path applies the same classification and marks the **reviewer's own provider id**
26
+ (`ctx.reviewer.provider`, e.g. `codex`) cooling down — not the review-CLI transport id (`review.provider`, e.g.
27
+ `codex-cli`), which `rankModels`/`detectProviders` never look up. This was the second, related pilot bug.
28
+ 2. **Per-issue and per-stage auto-pause** (new `src/loop/resilience-state.ts`, Composition, no kernel change):
29
+ `resilience.maxConsecutiveFailures` (default 3) pauses a single issue — one deduplicated Linear comment, the
30
+ `resilience.pausedLabel` — after that many consecutive `contract.failed`/`worker.dispatch-failed` events; a
31
+ successful dispatch clears the counter. `resilience.stagePauseAfterRuns` does the same for a `loop stage
32
+ tick|deliver` run that *throws* (not a normal idle/ok/blocked report) repeatedly. Both are separate from, and do
33
+ not replace, the existing `blocked`/`stuck` escalation via `linear.excludeLabels` (ADR-0027 §6) — those already
34
+ self-exclude a terminal outcome; this closes the two pre-dispatch paths that had no ceiling at all.
35
+ 3. **Skills pinned into the worker brief** (new `src/loop/skills.ts`): `brief.skills` lists Markdown files, read
36
+ once at dispatch time, sha256-digested, truncated at `brief.maxSkillChars` with a visible note, and embedded in
37
+ a new brief section. A missing file fails the dispatch closed. This is deliberately a separate mechanism from
38
+ the existing Doc Bridge `contract.briefScopes` (path/title pointers resolved per issue) — skills are full pinned
39
+ content with a cryptographic digest recorded in `dispatch.json`, and a handoff never re-reads them, so editing a
40
+ skill file after dispatch cannot affect an in-flight worker.
41
+ 4. **Worktree setup command**: `project.setup.command` (argv, no shell) runs once between `orca worktree create`
42
+ and opening the worker terminal — e.g. `pnpm install --frozen-lockfile` — bounded by `project.setup.timeoutSec`
43
+ (reserved out of the tick time budget so it cannot itself blow the budget). `project.setup.required` (default
44
+ true) routes a failing/timing-out setup through the same worktree-cleanup and consecutive-failure path as any
45
+ other dispatch failure.
46
+ 5. **Reasoning effort per role**: `models.effort.<role>` is rendered into `tui`/`headless` only for a provider that
47
+ declares `providers.<id>.effortFlag`; a provider without one silently ignores it, so the feature is opt-in per
48
+ provider and safe to default-enable. `agentskit-review` has no such flag, so `models.effort.reviewer` is
49
+ recorded (for `loop retro` grouping and future use) without being wired into the review CLI call.
50
+ 6. **GitHub label intake** (new `src/loop/github-intake.ts`): `github.intakeLabel` lets a human ask the loop to
51
+ review a PR it never dispatched (tracked as `pr-<n>`, no Linear issue). It reuses the same checks/review/fix-round
52
+ decisions as a normal dispatch, but every nudge becomes a PR comment (no worker terminal exists) and
53
+ `github.reviewOnly` is pinned `true` in the schema — this loop merges only PRs it dispatched itself, never one it
54
+ was only asked to review, however clean that review comes back.
55
+
56
+ ## Consequences
57
+
58
+ - No kernel change; `pnpm test:boundaries` stays green. All six changes are Composition (`src/loop/*`) or a small
59
+ Adapter addition (`githubOpenPullRequests` gains `label?`, plus `githubLabelRemove`, in `src/adapters/github-cli.ts`).
60
+ - `loop.config.yaml` gains six new top-level/nested blocks (`resilience`, `brief`, `project.setup`, `models.effort`,
61
+ `providers.<id>.effortFlag`, `github`), all with safe defaults (`.prefault({})`) so an existing 0.8.0 config keeps
62
+ its exact prior behaviour unless a project opts in.
63
+ - New CLI surface: `ak-harness loop resume [issue] [--stage tick|deliver]` and `ak-harness loop paused`.
64
+ - `loop doctor` gained a `brief.skills` check; `loop retro`'s `dispatches.byProvider` now groups by
65
+ `provider/model@effort` when an effort was recorded.
66
+ - Explicitly out of scope (from `background-agents`): a remote control plane, sandboxed/remote worktrees
67
+ (Modal/E2B/Daytona), prebuilds/snapshots, a multiplayer web UI, and Slack/GitHub-App bots. This harness's runtime
68
+ stays Orca + local worktrees; none of the above changes that.
package/docs/LOOP.md CHANGED
@@ -84,6 +84,25 @@ Optional SessionStart hook for Claude Code (`.claude/settings.json` of the targe
84
84
 
85
85
  It prints `loop: installed (2/2, last run …)` or the install command; it never installs or changes anything.
86
86
 
87
+ ## Worker handoff
88
+
89
+ When a worker goes idle past `delivery.workerIdleTimeoutMin` (or its terminal disappears) **and** its provider is unavailable (exhausted usage, cooldown, missing binary), deliver does **not** immediately mark the issue stuck. Instead it:
90
+
91
+ 1. Picks the next available builder via the current routing mode (catalog/hybrid/…)
92
+ 2. Opens a **new terminal in the same worktree** (same Orca branch)
93
+ 3. Sends a continuation brief (`renderHandoffBrief`) — resume from `git status` / existing commits; do not recreate the branch
94
+ 4. Updates `dispatch.json` (`terminal`, `provider`, `model`) and appends `delivery.handoffs[]`
95
+
96
+ Config (`delivery.handoff`, enabled by default):
97
+
98
+ ```yaml
99
+ delivery:
100
+ handoff:
101
+ enabled: true
102
+ maxHandoffs: 2
103
+ onlyWhenProviderUnavailable: true
104
+ ```
105
+
87
106
  ## Deliver
88
107
 
89
108
  For every issue the loop dispatched (`<stateDir>/issues/<id>/dispatch.json`) and has not finished:
@@ -107,6 +126,25 @@ For every issue the loop dispatched (`<stateDir>/issues/<id>/dispatch.json`) and
107
126
  State lives in `<stateDir>/issues/<id>/delivery.json` (reviews per head, fix rounds, nudges) and every decision is
108
127
  appended to `<stateDir>/events.ndjson`. `--dry-run` reports the decision for each issue without touching anything.
109
128
 
129
+ ## GitHub label intake: reviewing PRs the loop never dispatched
130
+
131
+ The loop's normal queue is Linear issues; `github.intakeLabel` (default `loop:review`, set to `null` to disable)
132
+ lets a human ask it to review a PR it had nothing to do with — a contributor's PR, a manual branch, anything —
133
+ without filing a Linear issue for it. Every `deliver` run lists open PRs carrying the label
134
+ (`gh pr list --label <intakeLabel>`) and starts tracking any not seen before as `pr-<n>` under
135
+ `<stateDir>/issues/pr-<n>/intake.json` (`{ pr, headRef, source: 'github-label', addedAt }`); tracking is
136
+ idempotent, so discovery never re-adds a PR it already knows about.
137
+
138
+ An intake PR runs the same checks → review → fix-round decisions as a normal dispatch (see the table above), with
139
+ two differences forced by having no Linear issue and no worker terminal:
140
+
141
+ - Every nudge (conflict, red CI, review findings) is posted as a **PR comment** instead of sent to a worker
142
+ terminal — there is no worker to nudge.
143
+ - `github.reviewOnly` is a fixed guarantee, not a knob (the schema pins it to `true`): a clean review always ends
144
+ in **held**, commented as "merge is human", the label removed, and `finishedAt` recorded — this loop merges only
145
+ PRs it dispatched itself, never one it was only asked to review. If the label is removed on GitHub before the
146
+ loop finishes, it stops tracking the PR the same way (held, no further comments).
147
+
110
148
  ## One tick
111
149
 
112
150
  1. **Intake** — `orca linear list-issues` once per configured state, filtered and ordered locally; issues already
@@ -129,6 +167,68 @@ terminal handle, provider/model and lease for the deliver stage.
129
167
 
130
168
  Start from [`loop.config.example.yaml`](../loop.config.example.yaml) at the package root.
131
169
 
170
+ ## Resilience: auto-pause after repeated failures
171
+
172
+ Two failure paths have no natural ceiling elsewhere in the pipeline — contract generation failing on every
173
+ candidate, and a worktree/worker dispatch failing outright — because the issue never gets a worktree, a lease that
174
+ would otherwise expire, or a label that would exclude it from the queue. Left alone, a single misclassified or
175
+ persistent error (a quota message the classifier didn't recognise, a broken `orca worktree create`) retries every
176
+ tick forever. (The 2026-09-11/12 pilot logged 19 such retries across 4 issues in 7h before this existed.)
177
+
178
+ - `resilience.maxConsecutiveFailures` (default 3): after this many **consecutive** `contract.failed` or
179
+ `worker.dispatch-failed` events on the *same* issue, the loop stops retrying it: one deduplicated Linear comment
180
+ explaining why, the `resilience.pausedLabel` (default `loop:paused`), and the issue is skipped locally on every
181
+ later tick regardless of whether that label is in `linear.excludeLabels`. A successful dispatch clears the
182
+ counter. State lives in `<stateDir>/issues/<id>/failures.json` (`loop paused` lists every paused issue).
183
+ - **Resuming** an issue: remove the `loop:paused` label on Linear (the next tick notices via `list-issues` and
184
+ clears the local state itself) or run `ak-harness loop resume <issue>`, which also best-effort removes the label.
185
+ - `resilience.stagePauseAfterRuns` (default 3): a scheduled `loop stage tick|deliver` run that *throws* (a config or
186
+ adapter crash, not a normal idle/ok/blocked report) this many times in a row pauses that stage — `loop stage`
187
+ then short-circuits to a `{"status":"paused", ...}` report instead of running, so a crash loop cannot spend budget
188
+ or provider usage under Orca. `ak-harness loop resume --stage tick|deliver` clears it; a single successful run
189
+ clears it automatically. State lives in `<stateDir>/paused.json`.
190
+
191
+ Neither mechanism touches the existing `blocked`/`stuck` escalations (fix-round exhaustion, an idle worker with no
192
+ PR) — those already label the issue and route it out of the queue via `linear.excludeLabels`.
193
+
194
+ ## Skills pinned into the worker brief
195
+
196
+ `brief.skills` (default `[]`) lists Markdown files, relative to `project.root`, that every worker brief embeds
197
+ verbatim under a `## Skills (pinned)` section — house conventions the orchestrator's contract can reference but a
198
+ worker starting cold has no other way to see (e.g. `AGENTS.md`, `CLAUDE.md`, `docs/for-agents/INDEX.md`).
199
+
200
+ - Reading and hashing happens once, at dispatch time (`loadPinnedSkills`, `src/loop/skills.ts`): each file is
201
+ sha256-digested and truncated at `brief.maxSkillChars` (default 6000) with a visible `[truncated N chars]` note so
202
+ one large file cannot exhaust the brief budget. A configured path that does not exist or cannot be read **fails
203
+ the dispatch** (fail-closed) rather than silently sending a worker without guidance it was told it would have —
204
+ the same worktree-cleanup and consecutive-failure accounting as any other dispatch failure applies.
205
+ - The rendered brief is persisted to `<stateDir>/issues/<id>/brief.md`, and `dispatch.json` records `briefDigest`
206
+ (hash of the full brief) plus `skills: [{path, digest}]` — enough to prove after the fact exactly which revision
207
+ of a skill file a given worker saw.
208
+ - **Pinning is by design, not by accident:** a handoff (`renderHandoffBrief`) reuses the worktree/branch state, not
209
+ the original brief, and never re-reads `brief.skills` — so editing a skill file after dispatch affects only
210
+ *future* dispatches, never a worker (or its handoff) already in flight.
211
+ - `loop doctor` runs a `brief.skills` check confirming every configured file currently exists and is readable, so a
212
+ typo or a moved file surfaces before the next dispatch fails.
213
+
214
+ ## Worktree setup command
215
+
216
+ A freshly created Orca worktree is a bare checkout — no `node_modules`, no build output, nothing a worker can run
217
+ tests against until it installs dependencies itself, wasting the first several minutes of every dispatch on the
218
+ same shell commands. `project.setup.command` (unset by default; an argv array, e.g.
219
+ `[pnpm, install, --frozen-lockfile]` — no shell, so no `&&`/`|`) runs once in the new worktree between
220
+ `orca worktree create` and opening the worker's terminal.
221
+
222
+ - `project.setup.timeoutSec` (default 600) bounds the run; the loop's per-tick time budget already reserves this
223
+ much time before attempting a dispatch, so a configured setup command cannot itself blow the tick budget.
224
+ - `project.setup.required` (default `true`): a non-zero exit or a timeout removes the just-created worktree, never
225
+ opens a terminal, and fails the dispatch — recorded as a `worker.dispatch-failed` event and counted by the
226
+ per-issue consecutive-failure tracker above, exactly like a contract or worktree-create failure. Set it to
227
+ `false` to have a failing setup only log a note and still hand the worker its terminal.
228
+ - Every run (pass or fail) is recorded as a `worker.setup` event and, when the dispatch succeeds, as `setup:
229
+ {command, exitCode, durationMs, timedOut}` on `dispatch.json` — enough to see in `loop retro` whether a slow or
230
+ flaky setup command is costing more dispatches than it saves.
231
+
132
232
  ## What the doctor checks
133
233
 
134
234
  | Check | Source | Blocking |
@@ -138,6 +238,7 @@ Start from [`loop.config.example.yaml`](../loop.config.example.yaml) at the pack
138
238
  | `routing.<role>` | tiers from `models.<role>` filtered by provider availability | yes — a role with no available provider blocks |
139
239
  | `machine.slots` | `sampleMachine` + `adaptiveConcurrency`, free RAM reserve, WSL cap, running worktrees | no — 0 free slots is a warning, not a failure |
140
240
  | `linear.queue` | `orca linear list-issues` per configured state, filtered and ordered locally | yes — an unreachable Linear blocks |
241
+ | `brief.skills` | existence + readability of each `brief.skills` path under `project.root` | yes when any are unreadable — dispatch would fail closed anyway |
141
242
 
142
243
 
143
244
  ## Dynamic model routing
@@ -167,6 +268,22 @@ declared `auth: api-key` need an environment variable, and the loop never reads
167
268
  When a provider runs out of usage the loop records a cooldown in `<stateDir>/provider-cooldowns.json`:
168
269
  `initialMin` doubling up to `maxMin`, never earlier than the reset instant Orca reported.
169
270
 
271
+ ### Reasoning effort per role
272
+
273
+ `models.effort.<role>` (`low | medium | high | xhigh`; defaults: orchestrator/reviewer `high`, builder `medium`,
274
+ watcher `low`) is only applied for a provider that declares `providers.<id>.effortFlag` — a template such as
275
+ `-c model_reasoning_effort={effort}` (codex) or `--reasoning-effort {effort}` (grok); a provider without one
276
+ ignores it entirely, so leaving `effort` at its default is always safe. The flag (with `{effort}` substituted) is
277
+ appended to `tui` as literal text, and appended as its own argv elements (split on whitespace, since headless argv
278
+ is never shell-joined) to `headless`. `agentskit-review` has no reasoning-effort flag, so `models.effort.reviewer`
279
+ is not currently wired into the review CLI call — it is validated and recorded for symmetry and for a future
280
+ reviewer transport that supports it.
281
+
282
+ Whichever effort a dispatched builder actually used is recorded as `effort` on `dispatch.json` and the
283
+ `worker.dispatched` event; `loop retro`'s `dispatches.byProvider` groups by `provider/model@effort` (falling back
284
+ to plain `provider/model` for older events with no effort recorded) so a retro can tell a slow `gpt-5.6-luna@high`
285
+ run from a fast `@medium` one.
286
+
170
287
  ## Machine slots
171
288
 
172
289
  `maxAgents = max(floor, min(adaptiveConcurrency(ceiling), ramBound, wslCap?))` where `ceiling` defaults to
@@ -87,8 +87,11 @@ modules, not a provider dependency.
87
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
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
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 |
90
+ | `src/loop/resilience-state.ts` | Composition | Per-issue consecutive-failure counter + pause/resume, and per-stage (`tick`/`deliver`) crash-loop pause/resume | stdlib | `<stateDir>/issues/<id>/failures.json`, `<stateDir>/paused.json` |
91
+ | `src/loop/skills.ts` | Composition | `brief.skills` file loading: sha256 digest, truncation at `maxSkillChars`, fail-closed on a missing file | stdlib (`crypto`) | Local Markdown files under `project.root` |
92
+ | `src/loop/github-intake.ts` | Composition | GitHub label-based intake: discovers open PRs carrying `github.intakeLabel` and tracks them as `pr-<n>` (no Linear issue) | `github-cli` | GitHub via the injected runner; `<stateDir>/issues/pr-<n>/intake.json` |
93
+ | `src/loop/tick.ts` | Composition | One keep-pushing tick: intake, admit (slots + dispatch ledger claim), contract, optional worktree setup command, skills pinning, dispatch into an Orca worktree, Linear transition, escalation, per-issue failure tracking, precheck | adapters, `coordination`, `errors`, `brief`, `config`, `contract`, `cooldown`, `doctor`, `resilience-state`, `routing`, `skills`, `slots` | Orca CLI + Linear via runner; `<stateDir>` files |
94
+ | `src/loop/deliver.ts` | Composition | Deliver stage per dispatched issue and per label-intake PR: PR detection, self-edit hold, conflict/CI/review fix rounds via terminal (or PR comment for intake), review at head, optimistic squash-merge (dispatched issues only — intake is review + comment, never merge), Linear Done, cleanup, stuck/abandoned escalation | adapters, `coordination`, `errors`, `config`, `cooldown`, `doctor`, `github-intake`, `routing`, `tick` | Orca, Linear, GitHub, agentskit-review via runner; `<stateDir>` files |
92
95
  | `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
96
  | `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
97
  | `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` |
@@ -159,7 +162,7 @@ How these seams are wired into the keep-pushing loop (and what is still only a k
159
162
  | Linear/GitHub/other tracker | `src/adapters/tracking.ts` callback | Caller-owned network mutation | Require idempotency key and explicit tracking authorization. |
160
163
  | 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. |
161
164
  | 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. |
162
- | 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. |
165
+ | GitHub | `src/adapters/github-cli.ts` via `CommandRunner` | `gh pr view/list` (optionally `--label`), `gh api PUT …/merge` with `sha=<reviewed head>`, `gh pr comment`, `gh pr edit --remove-label` | Merge is refused by GitHub when the head moved; every call argv-based and bounded; label intake never calls the merge argv. |
163
166
  | 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`. |
164
167
  | Process runtime | `src/execution/runtime.ts` | Starts child processes | Execution support; policy and evidence gates remain kernel decisions. |
165
168
  | Docker runtime | `src/execution/runtime.ts` | Starts Docker containers | Optional sandbox selected by config, never a mandatory kernel dependency. |
@@ -9,6 +9,11 @@ project:
9
9
  baseBranch: main
10
10
  root: . # relative to this file
11
11
  stateDir: .codex/loop # ledger, cooldowns, contracts
12
+ setup:
13
+ # command: [pnpm, install, --frozen-lockfile] # argv (no shell), run once in a freshly created worktree
14
+ # before the worker terminal opens; unset = skip
15
+ timeoutSec: 600
16
+ required: true # failing/timing-out setup removes the worktree and counts as a dispatch failure
12
17
 
13
18
  orca:
14
19
  bin: orca # on Linux outside Orca terminals use orca-ide (bare `orca` is the GNOME screen reader)
@@ -57,6 +62,12 @@ models:
57
62
  maxMin: 240 # …up to 4 h
58
63
  probeBeforeReenable: true
59
64
  exhaustedPercent: 100
65
+ # Reasoning effort requested per role. Only applied for providers below that declare `effortFlag`; others ignore it.
66
+ effort:
67
+ orchestrator: high
68
+ reviewer: high # currently unused: agentskit-review has no reasoning-effort flag (see reviewProvider below)
69
+ builder: medium
70
+ watcher: low
60
71
  providers:
61
72
  claude:
62
73
  bin: claude
@@ -64,12 +75,14 @@ models:
64
75
  envKeys: [ANTHROPIC_API_KEY] # optional API-key fallback
65
76
  tui: "claude --model {model} --permission-mode auto"
66
77
  headless: [claude, -p, "{prompt}", --model, "{model}", --permission-mode, plan, --output-format, text]
78
+ # effortFlag: "--effort {effort}"
67
79
  codex:
68
80
  bin: codex
69
81
  auth: subscription
70
82
  envKeys: [OPENAI_API_KEY]
71
83
  tui: "codex -m {model} --full-auto"
72
84
  headless: [codex, exec, -m, "{model}", -s, read-only, --skip-git-repo-check, "{prompt}"]
85
+ # effortFlag: "-c model_reasoning_effort={effort}"
73
86
  opencode:
74
87
  bin: opencode
75
88
  auth: none
@@ -81,6 +94,7 @@ models:
81
94
  auth: subscription # `grok login` (OAuth) — no API key needed
82
95
  tui: "grok -m {model}"
83
96
  headless: [grok, -p, "{prompt}", -m, "{model}"]
97
+ # effortFlag: "--reasoning-effort {effort}"
84
98
 
85
99
  machine:
86
100
  floor: 1 # always allow one worker
@@ -112,6 +126,7 @@ delivery:
112
126
  requireChecks: true
113
127
  maxFixRounds: 2
114
128
  workerIdleTimeoutMin: 45
129
+ handoff: { enabled: true, maxHandoffs: 2, onlyWhenProviderUnavailable: true }
115
130
  selfEditPaths: [loop.config.yaml, ".github/**"] # PRs touching these never auto-merge
116
131
  ignoreChecks: [] # advisory check names that never block
117
132
  requiredChecks: [] # empty = every reported check must be green
@@ -153,6 +168,22 @@ contract:
153
168
  maxBriefReferences: 4
154
169
  contextProviders: [doc-bridge] # add rag when rag.enabled
155
170
 
171
+ github:
172
+ intakeLabel: loop:review # a PR with this label is reviewed even though the loop never dispatched it;
173
+ # null disables intake entirely
174
+ reviewOnly: true # fixed: intake PRs are always review + comment, never auto-merged
175
+
176
+ resilience:
177
+ maxConsecutiveFailures: 3 # pause an issue after this many consecutive contract/dispatch failures
178
+ pausedLabel: loop:paused # applied to the issue in Linear; remove it (or `loop resume <id>`) to retry
179
+ stagePauseAfterRuns: 3 # pause `loop stage tick|deliver` itself after this many consecutive thrown runs
180
+
181
+ brief:
182
+ skills: [] # e.g. [AGENTS.md, CLAUDE.md, docs/for-agents/INDEX.md] — paths relative to project.root,
183
+ # pinned verbatim into every worker brief (sha256-digested in dispatch.json); a
184
+ # missing file fails the dispatch instead of silently sending a worker without it
185
+ maxSkillChars: 6000 # per-file cap; longer files are truncated with a visible note
186
+
156
187
  schedule:
157
188
  tick: "*/5 * * * *"
158
189
  deliver: "*/10 * * * *"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentskit/harness",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Portable, evidence-backed development harness for coding agents.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -68,7 +68,7 @@
68
68
  "test:phase-executor": "pnpm typecheck && vitest run --config vitest.config.ts test/phase-executor.test.ts && node -e \"console.log(JSON.stringify({status:'passed',criteria:['phase-executor']}))\"",
69
69
  "test:artifacts": "pnpm typecheck && vitest run --config vitest.config.ts test/artifacts.test.ts && node scripts/verify-artifact-cli.mjs",
70
70
  "test:adapters": "pnpm typecheck && vitest run --config vitest.config.ts test/adapters.test.ts && node -e \"console.log(JSON.stringify({status:'passed',criteria:['adapters']}))\"",
71
- "test:loop": "pnpm typecheck && vitest run --config vitest.config.ts test/loop.test.ts test/loop-adapters.test.ts test/loop-tick.test.ts test/loop-deliver.test.ts test/loop-install.test.ts test/loop-guided-install.test.ts test/loop-retro.test.ts test/loop-debrief.test.ts test/loop-watch.test.ts && node -e \"console.log(JSON.stringify({status:'passed',criteria:['loop-config','loop-providers','loop-routing','loop-doctor','loop-adapters','loop-tick','loop-deliver','loop-install','loop-debrief','loop-watch']}))\"",
71
+ "test:loop": "pnpm typecheck && vitest run --config vitest.config.ts test/loop.test.ts test/loop-adapters.test.ts test/loop-tick.test.ts test/loop-deliver.test.ts test/loop-install.test.ts test/loop-guided-install.test.ts test/loop-retro.test.ts test/loop-debrief.test.ts test/loop-watch.test.ts test/loop-agent-registry.test.ts test/loop-memory.test.ts test/loop-contract-failure.test.ts test/loop-resilience-state.test.ts test/loop-skills.test.ts test/loop-github-intake.test.ts && node -e \"console.log(JSON.stringify({status:'passed',criteria:['loop-config','loop-providers','loop-routing','loop-doctor','loop-adapters','loop-tick','loop-deliver','loop-install','loop-debrief','loop-watch','loop-agent-registry','loop-memory','loop-contract-failure','loop-resilience-state','loop-skills','loop-github-intake']}))\"",
72
72
  "test:review": "pnpm typecheck && vitest run --config vitest.config.ts test/review.test.ts && node -e \"console.log(JSON.stringify({status:'passed',criteria:['review-delivery']}))\"",
73
73
  "test:quality": "pnpm typecheck && vitest run --config vitest.config.ts test/quality.test.ts && node -e \"console.log(JSON.stringify({status:'passed',criteria:['quality-matrix']}))\"",
74
74
  "test:eval-battery": "pnpm typecheck && vitest run --config vitest.config.ts test/eval-battery.test.ts test/issues-010-014.eval.test.ts test/optimization.test.ts && pnpm build >/dev/null && node scripts/verify-harness-eval-manifest.mjs && node -e \"console.log(JSON.stringify({status:'passed',criteria:['eval-battery','eval-manifest','eval-coverage']}))\"",
@@ -2,7 +2,7 @@
2
2
  "type": "agentskit-harness-release-manifest",
3
3
  "schemaVersion": 1,
4
4
  "package": "@agentskit/harness",
5
- "version": "0.7.0",
5
+ "version": "0.9.0",
6
6
  "channel": "latest",
7
7
  "sourceRevision": "b8cfe596e5f30f2c373b89f00f271079b296feaf",
8
8
  "requiredChecks": [
@@ -32,5 +32,5 @@
32
32
  "pilot-benchmark"
33
33
  ],
34
34
  "nextBaseline": "benchmarks/harness-0.4.0-baseline.json",
35
- "digest": "712347d25fac5a6b0819cf8ba60f11ba9dc37c8d9380fb7df9e834f711509a86"
35
+ "digest": "0c87f4ac00da532da6ab4110c8c860938c52965e0e2ef2261e79898eb9826ccc"
36
36
  }
package/release/notes.md CHANGED
@@ -1,3 +1,7 @@
1
+ # 0.8.0 release candidate
2
+
3
+ Worker handoff: continue in-flight tickets on the same Orca worktree/branch with another provider when usage runs out.
4
+
1
5
  # 0.7.0 release candidate
2
6
 
3
7
  Dynamic Orca-aware model routing (hybrid/dynamic/catalog) with remaining-usage ranking and a living model catalog (CLI + builtin + optional Artificial Analysis).