@oneharness/sdk 0.3.22

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,1914 @@
1
+ {
2
+ "run_report": {
3
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
4
+ "title": "RunReport",
5
+ "description": "The top-level `run` report written to stdout.",
6
+ "type": "object",
7
+ "properties": {
8
+ "batch": {
9
+ "description": "Same-prefix batch metadata when this run fanned **one** harness over more\nthan one prompt; `null` on an ordinary run. Its presence is the signal a\nconsumer keys on to read each result's own `prompt`.",
10
+ "anyOf": [
11
+ {
12
+ "$ref": "#/$defs/BatchReport"
13
+ },
14
+ {
15
+ "type": "null"
16
+ }
17
+ ]
18
+ },
19
+ "bypass_permissions": {
20
+ "description": "Back-compat convenience: `true` exactly when `permission_mode` is\n`bypass`. Retained so existing consumers keep working; new consumers\nshould read `permission_mode`.",
21
+ "type": "boolean"
22
+ },
23
+ "config_files": {
24
+ "description": "Config files that shaped this run, in layering order (user first,\nproject last); empty under `--no-config` or when none exist.",
25
+ "type": "array",
26
+ "items": {
27
+ "type": "string"
28
+ }
29
+ },
30
+ "dry_run": {
31
+ "type": "boolean"
32
+ },
33
+ "fallback": {
34
+ "description": "Fallback-mode metadata when this run drove the selected harnesses in\npriority order, stopping at the first that ran (`--run-mode fallback`);\n`null` on a parallel run (and under `--print-command`, where nothing\nexecutes). Its presence tells a consumer that `results` holds only the\nharnesses actually *attempted* — the fallen-through ones in order, then\nthe one that ran — not every selected harness.",
35
+ "anyOf": [
36
+ {
37
+ "$ref": "#/$defs/FallbackReport"
38
+ },
39
+ {
40
+ "type": "null"
41
+ }
42
+ ]
43
+ },
44
+ "fork": {
45
+ "description": "Whether the resumed session was forked (`--fork`) rather than appended to.\n`false` unless `--resume` was given with `--fork`.",
46
+ "type": "boolean"
47
+ },
48
+ "history_file": {
49
+ "description": "The history session file this run streamed normalized records to\n(absolute); `null` when history was not enabled (or under `--print-command`,\nwhere nothing runs). The programmatic handle a consumer captures to read the\nsession back later with `oneharness history show`.",
50
+ "type": [
51
+ "string",
52
+ "null"
53
+ ]
54
+ },
55
+ "mock_rules": {
56
+ "description": "The parsed `--mock-rules` ruleset this run was intercepted with; `null`\nwhen no mocking was requested. Present so a consumer can tell a mocked\nrun's report from a clean one without out-of-band state."
57
+ },
58
+ "model": {
59
+ "description": "The effective top-level model: the first of the fan-out `models` list when\none was given, else the single configured/CLI model, else `null`. Each\nresult's own `model` is authoritative on a fan-out run.",
60
+ "type": [
61
+ "string",
62
+ "null"
63
+ ]
64
+ },
65
+ "models": {
66
+ "description": "The model fan-out list this run multiplied over (repeated `--model` /\nconfig `models`), or `null` on an ordinary single-model run. Its presence\nis the signal a consumer keys on to read each result's own `model`: in\n`parallel` mode `results` holds one entry per (harness, model) pair; in\n`fallback` mode the pairs were tried in priority order (harness-major,\nmodel-minor).",
67
+ "type": [
68
+ "array",
69
+ "null"
70
+ ],
71
+ "items": {
72
+ "type": "string"
73
+ }
74
+ },
75
+ "oneharness_version": {
76
+ "type": "string"
77
+ },
78
+ "permission_mode": {
79
+ "description": "The normalized approval mode requested for this run (see the README\nsupport matrix). Each harness maps it to its own mechanism.",
80
+ "$ref": "#/$defs/PermissionMode"
81
+ },
82
+ "prompt": {
83
+ "description": "The prompt sent. On an ordinary run this is *the* prompt every result\nshares; on a **batch** run (see `batch`) it repeats the first prompt for\nback-compat, and each result's own `prompt` field is authoritative.",
84
+ "type": "string"
85
+ },
86
+ "results": {
87
+ "type": "array",
88
+ "items": {
89
+ "$ref": "#/$defs/RunResult"
90
+ }
91
+ },
92
+ "resume": {
93
+ "description": "The session id being continued, when `--resume` was passed; else `null`.",
94
+ "type": [
95
+ "string",
96
+ "null"
97
+ ]
98
+ },
99
+ "schema": {
100
+ "description": "The JSON Schema applied to this run (structured output), or `null` when\nnone was requested. Echoed so a consumer sees the exact constraint each\nresult was validated against."
101
+ },
102
+ "schema_max_retries": {
103
+ "description": "Maximum retries allowed per harness under the validate/retry loop; `null`\nwhen no schema was requested.",
104
+ "type": [
105
+ "integer",
106
+ "null"
107
+ ],
108
+ "format": "uint32",
109
+ "minimum": 0
110
+ },
111
+ "schema_version": {
112
+ "type": "string"
113
+ },
114
+ "session": {
115
+ "description": "The uniform session handle in play (`--session <name>`), or `null` when\nnone was requested. Lets a consumer thread one stable name across turns\ninstead of extracting each harness's native session id. Distinct from the\nlow-level `resume` field above, which echoes an explicit `--resume` id.",
116
+ "anyOf": [
117
+ {
118
+ "$ref": "#/$defs/SessionReport"
119
+ },
120
+ {
121
+ "type": "null"
122
+ }
123
+ ]
124
+ },
125
+ "spy_file": {
126
+ "description": "The spy-log path the mock hook appended tool-call records to (absolute);\n`null` when none was requested.",
127
+ "type": [
128
+ "string",
129
+ "null"
130
+ ]
131
+ }
132
+ },
133
+ "required": [
134
+ "batch",
135
+ "bypass_permissions",
136
+ "config_files",
137
+ "dry_run",
138
+ "fallback",
139
+ "fork",
140
+ "history_file",
141
+ "mock_rules",
142
+ "model",
143
+ "models",
144
+ "oneharness_version",
145
+ "permission_mode",
146
+ "prompt",
147
+ "results",
148
+ "resume",
149
+ "schema",
150
+ "schema_max_retries",
151
+ "schema_version",
152
+ "session",
153
+ "spy_file"
154
+ ],
155
+ "$defs": {
156
+ "ActionEvent": {
157
+ "description": "One normalized action a harness took, harness-agnostic so a single consumer\nassertion works across harnesses. Every field is always serialized (null when\nabsent) so the shape is stable, mirroring the `usage` contract.",
158
+ "type": "object",
159
+ "properties": {
160
+ "index": {
161
+ "description": "Position of this event within the run, so \"≤ N tool calls\" and \"did X\nbefore Y\" are expressible from a stable ordering (also array order).",
162
+ "type": "integer",
163
+ "format": "uint",
164
+ "minimum": 0
165
+ },
166
+ "input": {
167
+ "description": "Structured, tool-shaped arguments (the command string, the file path),\nso a consumer asserts on specific args without re-parsing; `null` when the\nevent carries none (e.g. a `tool_result`)."
168
+ },
169
+ "kind": {
170
+ "description": "The kind of event: `tool_call` (the model invoked a tool) or\n`tool_result` (the observation returned to the model). Left open for\nfuture kinds rather than an enum, so a new shape never breaks the field.",
171
+ "type": "string"
172
+ },
173
+ "name": {
174
+ "description": "Normalized tool name where knowable (e.g. `bash`, `Edit`); `null` for a\n`tool_result`, or when the harness did not name the tool.",
175
+ "type": [
176
+ "string",
177
+ "null"
178
+ ]
179
+ },
180
+ "output": {
181
+ "description": "The result/observation text, when the trace exposes it; `null` otherwise.",
182
+ "type": [
183
+ "string",
184
+ "null"
185
+ ]
186
+ }
187
+ },
188
+ "required": [
189
+ "index",
190
+ "input",
191
+ "kind",
192
+ "name",
193
+ "output"
194
+ ]
195
+ },
196
+ "BatchReport": {
197
+ "description": "Metadata for a same-prefix batch run (one harness, N prompts sharing a\ncacheable prefix). Present on [`RunReport::batch`] only in that mode.",
198
+ "type": "object",
199
+ "properties": {
200
+ "forked": {
201
+ "description": "Whether the fan-out actually **forked** the warm-up's session to reuse its\ncached prefix (`min-tokens` on a fork-capable harness whose warm-up exposed\na session id). `false` for `speed`, for `min-tokens` on a harness that\ncannot fork, or when the warm-up exposed no session to fork. When `true`,\nthe fan-out results' `command` carries the resume/fork flags and their\n`usage.cache_read_tokens` reflect the reused prefix.",
202
+ "type": "boolean"
203
+ },
204
+ "prompt_count": {
205
+ "description": "How many prompts were run (equals `results.len()`).",
206
+ "type": "integer",
207
+ "format": "uint",
208
+ "minimum": 0
209
+ },
210
+ "strategy": {
211
+ "description": "How the prompts were scheduled across the parallel runner.",
212
+ "$ref": "#/$defs/BatchStrategy"
213
+ }
214
+ },
215
+ "required": [
216
+ "forked",
217
+ "prompt_count",
218
+ "strategy"
219
+ ]
220
+ },
221
+ "BatchStrategy": {
222
+ "description": "How a batch of same-prefix prompts is scheduled across the parallel runner.\nAlso accepted as a CLI value (`--batch-strategy`, parsed in the `oneharness`\nbinary) — the parsing lives there so this core crate stays free of `clap`.",
223
+ "oneOf": [
224
+ {
225
+ "description": "Fire all prompts at once (a single wave): minimum wall-clock. Every call\nmay race to *write* the shared prefix to the provider cache (none exists\nyet), so this optimizes latency, not tokens. The default.",
226
+ "type": "string",
227
+ "const": "speed"
228
+ },
229
+ {
230
+ "description": "Warm-then-fan: run one prompt first to *write* the shared prefix to cache,\nwait for it, then fan the rest out concurrently so they *read* the warmed\nprefix instead of each re-writing it. Optimizes cost at some wall-clock\nexpense.",
231
+ "type": "string",
232
+ "const": "min-tokens"
233
+ }
234
+ ]
235
+ },
236
+ "FailureKind": {
237
+ "description": "The normalized, closed set of failure reasons oneharness can classify from a\nharness's output. It is the single source for the `failure_kind` contract\nvalue: serialized as the snake_case token a consumer reads in the report\n(`auth`, `rate_limit`, `model_not_found`, `quota`, `tool_deferred`), so the\nwire shape is unchanged — modeling it as an enum keeps a misspelled or\ninvalid kind unrepresentable and gives every producer/consumer (classifier,\n`is_failure`, the fallback fall-through rule, the report, history) one\ndefinition to share instead of scattered string literals.",
238
+ "oneOf": [
239
+ {
240
+ "description": "Authentication / authorization rejected the request (401/403, missing or\ninvalid credentials).",
241
+ "type": "string",
242
+ "const": "auth"
243
+ },
244
+ {
245
+ "description": "Rate limited (429, too many requests) — a transient condition of an\notherwise working, authenticated harness.",
246
+ "type": "string",
247
+ "const": "rate_limit"
248
+ },
249
+ {
250
+ "description": "The requested model was not found / is invalid — a configuration mistake.",
251
+ "type": "string",
252
+ "const": "model_not_found"
253
+ },
254
+ {
255
+ "description": "Out of quota / credits, or a billing problem — a provisioning failure.",
256
+ "type": "string",
257
+ "const": "quota"
258
+ },
259
+ {
260
+ "description": "The harness deferred a builtin tool call instead of executing it, so a\nclean-exit run did no useful work (Claude Code bridge deployments; issue\n#1114). The only kind that can appear on a `status: ok` run.",
261
+ "type": "string",
262
+ "const": "tool_deferred"
263
+ }
264
+ ]
265
+ },
266
+ "FallThrough": {
267
+ "description": "One candidate a fallback run fell through, with the reason it could not run.",
268
+ "type": "object",
269
+ "properties": {
270
+ "harness": {
271
+ "description": "Canonical harness id.",
272
+ "type": "string"
273
+ },
274
+ "reason": {
275
+ "description": "Short reason token (`not-installed` / `spawn-error` / `auth` / `quota` /\n`model-not-found` / `rate-limit`).",
276
+ "type": "string"
277
+ }
278
+ },
279
+ "required": [
280
+ "harness",
281
+ "reason"
282
+ ]
283
+ },
284
+ "FallbackReport": {
285
+ "description": "Metadata for a fallback run (harnesses tried in priority order until one\nruns). Present on [`RunReport::fallback`] only in that mode. The per-harness\ndetail lives in `results`; this block summarizes the outcome so a consumer\nneed not re-derive it from statuses.",
286
+ "type": "object",
287
+ "properties": {
288
+ "fell_through": {
289
+ "description": "The candidates fallen through because they could not run the task at all,\nin priority order, each with why (`not-installed`, `spawn-error`, `auth`,\n`quota`, and — on a model fan-out — `model-not-found` / `rate-limit`; see\n[`crate::domain::fallback::startup_failure_reason`]).",
290
+ "type": "array",
291
+ "items": {
292
+ "$ref": "#/$defs/FallThrough"
293
+ }
294
+ },
295
+ "ran": {
296
+ "description": "The harness that actually ran the task (the run stopped there), or `null`\nwhen no candidate could run at all — every one was a startup failure.",
297
+ "type": [
298
+ "string",
299
+ "null"
300
+ ]
301
+ }
302
+ },
303
+ "required": [
304
+ "fell_through",
305
+ "ran"
306
+ ]
307
+ },
308
+ "OutputFormat": {
309
+ "description": "How a harness emits its result, which decides how `text` is extracted.\n\nAlso accepted as a CLI value (`--output-format`, parsed in the `oneharness`\nbinary) and a config-file value (`output_format`, via `Deserialize`). The\nCLI parsing lives in the binary so this core crate stays free of `clap`.",
310
+ "oneOf": [
311
+ {
312
+ "description": "Plain text on stdout; `text` is the trimmed stdout.",
313
+ "type": "string",
314
+ "const": "text"
315
+ },
316
+ {
317
+ "description": "A single JSON document on stdout.",
318
+ "type": "string",
319
+ "const": "json"
320
+ },
321
+ {
322
+ "description": "Line-delimited JSON events on stdout.",
323
+ "type": "string",
324
+ "const": "stream-json"
325
+ }
326
+ ]
327
+ },
328
+ "PermissionMode": {
329
+ "description": "The unified approval mode, from least to most autonomy. A harness may not\nsupport every value (see [`crate::domain::harness::HarnessSpec::mode`]); the\ncommand layer refuses an unsupported one before spawning, never silently\ndowngrading it.",
330
+ "oneOf": [
331
+ {
332
+ "description": "No mutations — the agent may read but not edit files or run commands —\n*without* the plan workflow: it just does whatever read-only work the task\nallows. Mapped to each harness's strongest per-run no-mutation enforcement\n(Codex's read-only sandbox, Claude's deny rules, Copilot's `--deny-tool`,\nCursor's `ask` mode). The enforced counterpart to [`Self::Plan`].",
333
+ "type": "string",
334
+ "const": "read-only"
335
+ },
336
+ {
337
+ "description": "Read and propose only — like [`Self::ReadOnly`], but additionally engages\nthe harness's *plan* workflow: research the task and write a plan rather\nthan act. Supported only where a native plan mode exists (Claude/Qwen/\nCursor/Copilot, OpenCode's `plan` agent); harnesses with no plan workflow\n(Codex, Goose, Crush) reject it — use [`Self::ReadOnly`] for those.",
338
+ "type": "string",
339
+ "const": "plan"
340
+ },
341
+ {
342
+ "description": "The harness's normal ask flow, mapped to its cleanest *non-interactive*\nvariant so a headless run never blocks waiting for input (Claude's\n`dontAsk` deny-and-continue, Goose's fail-closed `approve`, Copilot's\nauto-deny). Where no such variant exists the harness's [`ModeHeadless`]\nis [`ModeHeadless::Hangs`].",
343
+ "type": "string",
344
+ "const": "default"
345
+ },
346
+ {
347
+ "description": "Auto-approve file edits, still gate shell/commands. (Claude's\n`acceptEdits`, Qwen's `auto-edit`.)",
348
+ "type": "string",
349
+ "const": "edit"
350
+ },
351
+ {
352
+ "description": "Auto-approve operations the harness deems safe (a classifier), gate the\nrisky ones. (Claude's `auto`, Qwen's `auto`, Goose's `smart_approve`.)",
353
+ "type": "string",
354
+ "const": "auto"
355
+ },
356
+ {
357
+ "description": "Approve everything — no prompts, no gating. The headless default, because\nan unattended agent otherwise hangs on the first approval. Every harness\nsupports it. (Claude's `bypassPermissions`, Codex's\n`--dangerously-bypass-approvals-and-sandbox`, Qwen/Crush `--yolo`, …)",
358
+ "type": "string",
359
+ "const": "bypass"
360
+ }
361
+ ]
362
+ },
363
+ "RunResult": {
364
+ "description": "One harness's entry in the report.",
365
+ "type": "object",
366
+ "properties": {
367
+ "available": {
368
+ "description": "Whether that binary was found.",
369
+ "type": "boolean"
370
+ },
371
+ "bin": {
372
+ "description": "The binary name or path oneharness resolved and would invoke.",
373
+ "type": "string"
374
+ },
375
+ "command": {
376
+ "description": "The exact argv oneharness built (argv[0] is the binary).",
377
+ "type": "array",
378
+ "items": {
379
+ "type": "string"
380
+ }
381
+ },
382
+ "duration_ms": {
383
+ "description": "Wall-clock duration of the run; `null` when not executed.",
384
+ "type": [
385
+ "integer",
386
+ "null"
387
+ ],
388
+ "format": "uint128",
389
+ "minimum": 0
390
+ },
391
+ "error": {
392
+ "description": "Human-readable problem + suggested action; `null` on success.",
393
+ "type": [
394
+ "string",
395
+ "null"
396
+ ]
397
+ },
398
+ "events": {
399
+ "description": "Best-effort normalized tool-call / action events the harness took (shell\ncommands, file edits, tool uses), in order — so consumers can assert on\n*behavior*, not just the final `text`. `null` when the harness's output\nexposes no machine-readable trace (a plain-text harness, or Claude Code's\nsingle-document `json` result), distinct from `[]` — an empty array is not\ncurrently emitted; absence is signalled by `null` + a null `events_source`.\nNever fabricated. See [`crate::domain::events`].",
400
+ "type": [
401
+ "array",
402
+ "null"
403
+ ],
404
+ "items": {
405
+ "$ref": "#/$defs/ActionEvent"
406
+ }
407
+ },
408
+ "events_source": {
409
+ "description": "How `events` was recovered (e.g. `json:opencode-parts`,\n`stream-json:content-blocks`), parallel to `text_source`; `null` when no\nevents were found. Lets a consumer tell \"harness doesn't support it\" from\n\"no tools were used.\"",
410
+ "type": [
411
+ "string",
412
+ "null"
413
+ ]
414
+ },
415
+ "exit_code": {
416
+ "description": "Process exit code; `null` when not run, timed out, or signalled.",
417
+ "type": [
418
+ "integer",
419
+ "null"
420
+ ],
421
+ "format": "int32"
422
+ },
423
+ "failure_kind": {
424
+ "description": "Best-effort failure reason; `null` when unclassified. Distinct from\n`status`, which records oneharness's relationship to the process. Two\nfamilies: coarse reasons for a non-zero run (`auth`, `rate_limit`,\n`model_not_found`, `quota`), and `tool_deferred` — a run that exited\n*cleanly* but only deferred a builtin tool call instead of executing it\n(Claude Code bridge/managed deployments), so it did no useful work. The\ndeferred case is the only `failure_kind` that can appear on a `status: ok`\nrun, and it also marks the run as failed for exit-code purposes.\nSerialized as its snake_case token (see [`FailureKind`]).",
425
+ "anyOf": [
426
+ {
427
+ "$ref": "#/$defs/FailureKind"
428
+ },
429
+ {
430
+ "type": "null"
431
+ }
432
+ ]
433
+ },
434
+ "failure_kind_source": {
435
+ "description": "Where `failure_kind` was read (`stderr`/`stdout`); `null` when absent.",
436
+ "type": [
437
+ "string",
438
+ "null"
439
+ ]
440
+ },
441
+ "harness": {
442
+ "description": "Canonical harness id (e.g. `claude-code`).",
443
+ "type": "string"
444
+ },
445
+ "model": {
446
+ "description": "The model this result ran with (the value oneharness put on the harness's\nmodel flag), or `null` when no model was requested and the harness used its\nown default. On a **model fan-out** run (`RunReport::models`), this is what\ndistinguishes results that share a harness — each entry is one (harness,\nmodel) pair. The model is also visible in `command`; this field surfaces it\nwithout parsing the argv.",
447
+ "type": [
448
+ "string",
449
+ "null"
450
+ ]
451
+ },
452
+ "output_format": {
453
+ "$ref": "#/$defs/OutputFormat"
454
+ },
455
+ "prompt": {
456
+ "description": "The prompt this result ran, set only on a **batch** run (one harness\nfanned over N prompts), where each result has its own prompt. `null` on an\nordinary run, where the single top-level `prompt` applies to every result.",
457
+ "type": [
458
+ "string",
459
+ "null"
460
+ ]
461
+ },
462
+ "schema_attempts": {
463
+ "description": "Structured-output run only: how many times this harness was invoked under\nthe validate/retry loop (1 + retries). `null` when no schema was\nrequested or the harness did not run.",
464
+ "type": [
465
+ "integer",
466
+ "null"
467
+ ],
468
+ "format": "uint32",
469
+ "minimum": 0
470
+ },
471
+ "schema_error": {
472
+ "description": "Structured-output run only: the validation errors from the final attempt,\njoined for display; `null` when valid or no schema was requested.",
473
+ "type": [
474
+ "string",
475
+ "null"
476
+ ]
477
+ },
478
+ "schema_valid": {
479
+ "description": "Structured-output run only: whether `structured` conformed to the schema\non the final attempt. `null` when no schema was requested (or the harness\ndid not run); `false` when a schema was requested but the result never\nconformed (including \"no JSON found\").",
480
+ "type": [
481
+ "boolean",
482
+ "null"
483
+ ]
484
+ },
485
+ "session_id": {
486
+ "description": "Best-effort harness session id for continuation; `null` when none is\nexposed. Surfaced for a consumer to thread into `--resume`, and consumed\nby oneharness itself when `--session` is in play (it is captured into the\nsession store to back the uniform handle — see [`RunReport::session`]).",
487
+ "type": [
488
+ "string",
489
+ "null"
490
+ ]
491
+ },
492
+ "status": {
493
+ "$ref": "#/$defs/Status"
494
+ },
495
+ "stderr": {
496
+ "description": "Raw captured stderr (empty for skipped/planned).",
497
+ "type": "string"
498
+ },
499
+ "stdout": {
500
+ "description": "Raw captured stdout (empty for skipped/planned).",
501
+ "type": "string"
502
+ },
503
+ "structured": {
504
+ "description": "Structured-output run only: the JSON value extracted from the final\nanswer and validated against the requested schema. `null` when no schema\nwas requested, or when no JSON value could be extracted. Carries the\nlast-attempted value even when it failed validation, so a consumer can\nsee what the harness produced."
505
+ },
506
+ "text": {
507
+ "description": "Best-effort final assistant text; `null` when extraction is impossible.",
508
+ "type": [
509
+ "string",
510
+ "null"
511
+ ]
512
+ },
513
+ "text_source": {
514
+ "description": "How `text` was extracted (e.g. `json:result`, `raw`); `null` when absent.",
515
+ "type": [
516
+ "string",
517
+ "null"
518
+ ]
519
+ },
520
+ "usage": {
521
+ "description": "Best-effort token/cost accounting; every field is `null` when the harness\ndoes not report it. Always present so consumers can read a stable shape.",
522
+ "$ref": "#/$defs/Usage"
523
+ },
524
+ "usage_source": {
525
+ "description": "How `usage` was read (e.g. `json`); `null` when nothing was found.",
526
+ "type": [
527
+ "string",
528
+ "null"
529
+ ]
530
+ }
531
+ },
532
+ "required": [
533
+ "available",
534
+ "bin",
535
+ "command",
536
+ "duration_ms",
537
+ "error",
538
+ "events",
539
+ "events_source",
540
+ "exit_code",
541
+ "failure_kind",
542
+ "failure_kind_source",
543
+ "harness",
544
+ "model",
545
+ "output_format",
546
+ "prompt",
547
+ "schema_attempts",
548
+ "schema_error",
549
+ "schema_valid",
550
+ "session_id",
551
+ "status",
552
+ "stderr",
553
+ "stdout",
554
+ "structured",
555
+ "text",
556
+ "text_source",
557
+ "usage",
558
+ "usage_source"
559
+ ]
560
+ },
561
+ "SessionPhase": {
562
+ "description": "Whether a run starts a new named session or continues an existing one.",
563
+ "oneOf": [
564
+ {
565
+ "description": "No stored token yet — the harness runs fresh and its emitted session id is\ncaptured for next time.",
566
+ "type": "string",
567
+ "const": "create"
568
+ },
569
+ {
570
+ "description": "A stored token exists — the run resumes it.",
571
+ "type": "string",
572
+ "const": "continue"
573
+ }
574
+ ]
575
+ },
576
+ "SessionReport": {
577
+ "description": "The uniform session handle for a run (`--session`). Present on\n[`RunReport::session`] only when `--session <name>` was requested.",
578
+ "type": "object",
579
+ "properties": {
580
+ "name": {
581
+ "description": "The caller's stable handle (`--session <name>`, sanitized for the store).",
582
+ "type": "string"
583
+ },
584
+ "phase": {
585
+ "description": "Whether this run created the named session (no prior token) or continued\nan existing one.",
586
+ "$ref": "#/$defs/SessionPhase"
587
+ },
588
+ "store_file": {
589
+ "description": "The session store file backing the handle (absolute); the programmatic\nhandle to the persisted state.",
590
+ "type": [
591
+ "string",
592
+ "null"
593
+ ]
594
+ },
595
+ "token": {
596
+ "description": "The harness native token now bound to the name: the id resumed on a\ncontinue, or the id captured on a create. `null` only when a create run\nexposed no session id (the handle then cannot be continued — a warning is\nemitted), or under `--print-command` on a create (nothing ran).",
597
+ "type": [
598
+ "string",
599
+ "null"
600
+ ]
601
+ }
602
+ },
603
+ "required": [
604
+ "name",
605
+ "phase",
606
+ "store_file",
607
+ "token"
608
+ ]
609
+ },
610
+ "Status": {
611
+ "description": "The outcome of attempting to run one harness.",
612
+ "oneOf": [
613
+ {
614
+ "description": "Spawned and exited 0.",
615
+ "type": "string",
616
+ "const": "ok"
617
+ },
618
+ {
619
+ "description": "Spawned and exited non-zero.",
620
+ "type": "string",
621
+ "const": "nonzero"
622
+ },
623
+ {
624
+ "description": "Killed after exceeding the per-harness timeout.",
625
+ "type": "string",
626
+ "const": "timeout"
627
+ },
628
+ {
629
+ "description": "The binary was resolved but could not be executed.",
630
+ "type": "string",
631
+ "const": "spawn-error"
632
+ },
633
+ {
634
+ "description": "The binary was not found, so the harness was not run.",
635
+ "type": "string",
636
+ "const": "skipped"
637
+ },
638
+ {
639
+ "description": "`--print-command`: the command was built but not executed.",
640
+ "type": "string",
641
+ "const": "planned"
642
+ }
643
+ ]
644
+ },
645
+ "Usage": {
646
+ "description": "Normalized token/cost accounting. Every field is best-effort and independently\nnullable: a harness may report tokens but not dollar cost (cost is commonly\nabsent on subscription auth), or report nothing at all (plain-text harnesses).",
647
+ "type": "object",
648
+ "properties": {
649
+ "cache_read_tokens": {
650
+ "description": "Prompt tokens served from the provider's prompt cache (a cheap read of a\npreviously-written prefix), when the harness reports them. `None` when the\nharness does not surface cache counts — never `0` as a guess.",
651
+ "type": [
652
+ "integer",
653
+ "null"
654
+ ],
655
+ "format": "uint64",
656
+ "minimum": 0
657
+ },
658
+ "cache_write_tokens": {
659
+ "description": "Prompt tokens written to the provider's prompt cache (a.k.a. cache\ncreation), when the harness reports them. `None` when not surfaced.",
660
+ "type": [
661
+ "integer",
662
+ "null"
663
+ ],
664
+ "format": "uint64",
665
+ "minimum": 0
666
+ },
667
+ "cost_usd": {
668
+ "description": "Total cost in USD, when the harness reports it (often absent on\nsubscription auth, where there is no per-call dollar figure).",
669
+ "type": [
670
+ "number",
671
+ "null"
672
+ ],
673
+ "format": "double"
674
+ },
675
+ "input_tokens": {
676
+ "description": "Prompt/input tokens billed, when the harness reports them.",
677
+ "type": [
678
+ "integer",
679
+ "null"
680
+ ],
681
+ "format": "uint64",
682
+ "minimum": 0
683
+ },
684
+ "output_tokens": {
685
+ "description": "Completion/output tokens billed, when the harness reports them.",
686
+ "type": [
687
+ "integer",
688
+ "null"
689
+ ],
690
+ "format": "uint64",
691
+ "minimum": 0
692
+ }
693
+ },
694
+ "required": [
695
+ "cache_read_tokens",
696
+ "cache_write_tokens",
697
+ "cost_usd",
698
+ "input_tokens",
699
+ "output_tokens"
700
+ ]
701
+ }
702
+ }
703
+ },
704
+ "run_options": {
705
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
706
+ "title": "RunOptions",
707
+ "description": "Options accepted by `OneHarness.run()` in the published Node SDK.\n\nUnknown fields are rejected because the SDK cannot forward an option it does\nnot understand. This differs deliberately from output contracts, whose Zod\nschemas preserve unknown fields for forward compatibility.",
708
+ "type": "object",
709
+ "properties": {
710
+ "bins": {
711
+ "type": "object",
712
+ "additionalProperties": {
713
+ "type": "string"
714
+ }
715
+ },
716
+ "cwd": {
717
+ "type": "string"
718
+ },
719
+ "env": {
720
+ "type": "object",
721
+ "additionalProperties": {
722
+ "type": "string"
723
+ }
724
+ },
725
+ "events": {
726
+ "type": "boolean"
727
+ },
728
+ "fork": {
729
+ "type": "boolean"
730
+ },
731
+ "harnesses": {
732
+ "type": "array",
733
+ "items": {
734
+ "type": "string"
735
+ }
736
+ },
737
+ "history": {
738
+ "type": "boolean"
739
+ },
740
+ "historyDir": {
741
+ "type": "string"
742
+ },
743
+ "historyName": {
744
+ "type": "string"
745
+ },
746
+ "mode": {
747
+ "$ref": "#/$defs/PermissionMode"
748
+ },
749
+ "models": {
750
+ "type": "array",
751
+ "items": {
752
+ "type": "string"
753
+ }
754
+ },
755
+ "prompt": {
756
+ "description": "The user message sent to the selected harnesses.",
757
+ "type": "string",
758
+ "minLength": 1
759
+ },
760
+ "reasoning": {
761
+ "type": "string"
762
+ },
763
+ "resume": {
764
+ "type": "string"
765
+ },
766
+ "session": {
767
+ "type": "string"
768
+ },
769
+ "system": {
770
+ "type": "string"
771
+ },
772
+ "timeoutSeconds": {
773
+ "type": "integer",
774
+ "format": "uint64",
775
+ "minimum": 0
776
+ }
777
+ },
778
+ "additionalProperties": false,
779
+ "required": [
780
+ "prompt"
781
+ ],
782
+ "$defs": {
783
+ "PermissionMode": {
784
+ "description": "The unified approval mode, from least to most autonomy. A harness may not\nsupport every value (see [`crate::domain::harness::HarnessSpec::mode`]); the\ncommand layer refuses an unsupported one before spawning, never silently\ndowngrading it.",
785
+ "oneOf": [
786
+ {
787
+ "description": "No mutations — the agent may read but not edit files or run commands —\n*without* the plan workflow: it just does whatever read-only work the task\nallows. Mapped to each harness's strongest per-run no-mutation enforcement\n(Codex's read-only sandbox, Claude's deny rules, Copilot's `--deny-tool`,\nCursor's `ask` mode). The enforced counterpart to [`Self::Plan`].",
788
+ "type": "string",
789
+ "const": "read-only"
790
+ },
791
+ {
792
+ "description": "Read and propose only — like [`Self::ReadOnly`], but additionally engages\nthe harness's *plan* workflow: research the task and write a plan rather\nthan act. Supported only where a native plan mode exists (Claude/Qwen/\nCursor/Copilot, OpenCode's `plan` agent); harnesses with no plan workflow\n(Codex, Goose, Crush) reject it — use [`Self::ReadOnly`] for those.",
793
+ "type": "string",
794
+ "const": "plan"
795
+ },
796
+ {
797
+ "description": "The harness's normal ask flow, mapped to its cleanest *non-interactive*\nvariant so a headless run never blocks waiting for input (Claude's\n`dontAsk` deny-and-continue, Goose's fail-closed `approve`, Copilot's\nauto-deny). Where no such variant exists the harness's [`ModeHeadless`]\nis [`ModeHeadless::Hangs`].",
798
+ "type": "string",
799
+ "const": "default"
800
+ },
801
+ {
802
+ "description": "Auto-approve file edits, still gate shell/commands. (Claude's\n`acceptEdits`, Qwen's `auto-edit`.)",
803
+ "type": "string",
804
+ "const": "edit"
805
+ },
806
+ {
807
+ "description": "Auto-approve operations the harness deems safe (a classifier), gate the\nrisky ones. (Claude's `auto`, Qwen's `auto`, Goose's `smart_approve`.)",
808
+ "type": "string",
809
+ "const": "auto"
810
+ },
811
+ {
812
+ "description": "Approve everything — no prompts, no gating. The headless default, because\nan unattended agent otherwise hangs on the first approval. Every harness\nsupports it. (Claude's `bypassPermissions`, Codex's\n`--dangerously-bypass-approvals-and-sandbox`, Qwen/Crush `--yolo`, …)",
813
+ "type": "string",
814
+ "const": "bypass"
815
+ }
816
+ ]
817
+ }
818
+ }
819
+ },
820
+ "history_lookup": {
821
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
822
+ "title": "HistoryLookup",
823
+ "description": "The session selector accepted by `OneHarness.history()` in the published Node\nSDK.\n\nA lookup must select a session, so the contract is a union of the only two\nways to do that: ask for the most recent one ([`HistoryLookupByLast`]), or\nname one ([`HistoryLookupBySession`]). A lookup that selects nothing — `{}`,\n`{\"session\": \"\"}`, or `{\"last\": false}` — matches neither variant and fails\nat the boundary, so `history()` never has to re-check for one.\n\nThe variants overlap on purpose, and the order is load-bearing: an untagged\nenum takes the first match, so `Last` coming first is what gives `last: true`\npriority over a name. `{\"session\": \"x\", \"last\": true}` satisfies *both*\nvariants and resolves to `Last`, selecting the most recent session rather\nthan `x`; `{\"session\": \"x\", \"last\": false}` fails `Last` and resolves to\n`Session`. That is exactly the reading of the `if (last) … else if (session)`\nrule this replaces, now decided by the union rather than re-derived after\nparsing. Zod resolves its generated union in the same order, so the Node SDK\nagrees with Rust by construction.\n\nBecause `Last` ignores the name it carries, that name is a plain `String`:\n`{\"session\": \"\", \"last\": true}` stays valid and still selects the most recent\nsession, as it always has. Only a name that actually selects has to be\nnon-empty.",
824
+ "anyOf": [
825
+ {
826
+ "description": "Select the most recent session.",
827
+ "$ref": "#/$defs/HistoryLookupByLast"
828
+ },
829
+ {
830
+ "description": "Select the session named by `session`.",
831
+ "$ref": "#/$defs/HistoryLookupBySession"
832
+ }
833
+ ],
834
+ "$defs": {
835
+ "HistoryLookupByLast": {
836
+ "description": "A [`HistoryLookup`] that selects the most recent session.",
837
+ "type": "object",
838
+ "properties": {
839
+ "allProjects": {
840
+ "type": "boolean"
841
+ },
842
+ "historyDir": {
843
+ "type": "string"
844
+ },
845
+ "last": {
846
+ "description": "Select the most recent session. Only `true` selects, so this variant\naccepts no other value.",
847
+ "type": "boolean",
848
+ "const": true
849
+ },
850
+ "project": {
851
+ "type": "string"
852
+ },
853
+ "session": {
854
+ "description": "A name may accompany `last: true` — it is what the caller would have\nlooked up otherwise — but `last` takes priority, so it never selects.\nIt is therefore unconstrained: an empty name is meaningless here rather\nthan invalid, so `{\"session\": \"\", \"last\": true}` stays accepted.",
855
+ "type": "string"
856
+ }
857
+ },
858
+ "additionalProperties": false,
859
+ "required": [
860
+ "last"
861
+ ]
862
+ },
863
+ "HistoryLookupBySession": {
864
+ "description": "A [`HistoryLookup`] that names its session.",
865
+ "type": "object",
866
+ "properties": {
867
+ "allProjects": {
868
+ "type": "boolean"
869
+ },
870
+ "historyDir": {
871
+ "type": "string"
872
+ },
873
+ "last": {
874
+ "description": "Whether the most recent session was asked for instead. An ordinary\n`bool`, so a caller holding a `boolean` can pass it straight through.\n`true` here also satisfies [`HistoryLookup::Last`], which the union tries\nfirst — so a lookup that reaches this variant always meant the name.",
875
+ "type": "boolean"
876
+ },
877
+ "project": {
878
+ "type": "string"
879
+ },
880
+ "session": {
881
+ "description": "The oneharness-derived session name recorded by `run --history`. This is\nthe name that selects, so it must be non-empty.",
882
+ "type": "string",
883
+ "minLength": 1
884
+ }
885
+ },
886
+ "additionalProperties": false,
887
+ "required": [
888
+ "session"
889
+ ]
890
+ }
891
+ }
892
+ },
893
+ "history_list_options": {
894
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
895
+ "title": "HistoryListOptions",
896
+ "description": "Options accepted by `OneHarness.historyList()` in the published Node SDK.",
897
+ "type": "object",
898
+ "properties": {
899
+ "allProjects": {
900
+ "type": "boolean"
901
+ },
902
+ "historyDir": {
903
+ "type": "string"
904
+ },
905
+ "project": {
906
+ "type": "string"
907
+ }
908
+ },
909
+ "additionalProperties": false
910
+ },
911
+ "history_record": {
912
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
913
+ "title": "HistoryRecord",
914
+ "description": "One harness run, normalized and frozen for the history log. Serialized as one\nJSONL line per harness run, appended as the run finalizes. Carries only the\nnormalized cross-harness signals — no raw stdout/stderr.",
915
+ "type": "object",
916
+ "properties": {
917
+ "duration_ms": {
918
+ "type": [
919
+ "integer",
920
+ "null"
921
+ ],
922
+ "format": "uint128",
923
+ "minimum": 0
924
+ },
925
+ "events": {
926
+ "description": "Best-effort normalized tool-call events; `null` when the harness exposes\nno machine-readable trace.",
927
+ "type": [
928
+ "array",
929
+ "null"
930
+ ],
931
+ "items": {
932
+ "$ref": "#/$defs/ActionEvent"
933
+ }
934
+ },
935
+ "exit_code": {
936
+ "type": [
937
+ "integer",
938
+ "null"
939
+ ],
940
+ "format": "int32"
941
+ },
942
+ "failure_kind": {
943
+ "description": "Best-effort classified failure reason (see [`FailureKind`]); `null` when\nunclassified.",
944
+ "anyOf": [
945
+ {
946
+ "$ref": "#/$defs/FailureKind"
947
+ },
948
+ {
949
+ "type": "null"
950
+ }
951
+ ]
952
+ },
953
+ "harness": {
954
+ "description": "Canonical harness id (e.g. `claude-code`).",
955
+ "type": "string"
956
+ },
957
+ "model": {
958
+ "description": "The effective top-level model for the run, if any.",
959
+ "type": [
960
+ "string",
961
+ "null"
962
+ ]
963
+ },
964
+ "name": {
965
+ "description": "The human-meaningful session name (see [`session_name`]); repeated on\nevery record so a reader can resolve a session by name from any line.",
966
+ "type": "string"
967
+ },
968
+ "permission_mode": {
969
+ "description": "The normalized approval mode requested for the run.",
970
+ "$ref": "#/$defs/PermissionMode"
971
+ },
972
+ "project": {
973
+ "description": "The project directory the run operated in (the real path, not the\non-disk slug), so the list view can show where a session ran.",
974
+ "type": "string"
975
+ },
976
+ "prompt": {
977
+ "description": "The prompt this harness run received (its own, on a batch run; else the\nrun's single prompt).",
978
+ "type": "string"
979
+ },
980
+ "schema_version": {
981
+ "type": "string"
982
+ },
983
+ "session": {
984
+ "description": "The oneharness session id this run belongs to (the history file's stem).",
985
+ "type": "string"
986
+ },
987
+ "session_id": {
988
+ "description": "The harness's own continuation id, when it exposed one; `null` otherwise.",
989
+ "type": [
990
+ "string",
991
+ "null"
992
+ ]
993
+ },
994
+ "status": {
995
+ "$ref": "#/$defs/Status"
996
+ },
997
+ "text": {
998
+ "description": "Best-effort final assistant text; `null` when extraction was impossible.",
999
+ "type": [
1000
+ "string",
1001
+ "null"
1002
+ ]
1003
+ },
1004
+ "text_source": {
1005
+ "description": "How `text` was extracted; `null` when absent.",
1006
+ "type": [
1007
+ "string",
1008
+ "null"
1009
+ ]
1010
+ },
1011
+ "timestamp": {
1012
+ "description": "RFC3339 UTC instant the record was written (append time).",
1013
+ "type": "string"
1014
+ },
1015
+ "usage": {
1016
+ "description": "Best-effort token/cost accounting (every field `null` when unreported).",
1017
+ "$ref": "#/$defs/Usage"
1018
+ }
1019
+ },
1020
+ "required": [
1021
+ "duration_ms",
1022
+ "events",
1023
+ "exit_code",
1024
+ "failure_kind",
1025
+ "harness",
1026
+ "model",
1027
+ "name",
1028
+ "permission_mode",
1029
+ "project",
1030
+ "prompt",
1031
+ "schema_version",
1032
+ "session",
1033
+ "session_id",
1034
+ "status",
1035
+ "text",
1036
+ "text_source",
1037
+ "timestamp",
1038
+ "usage"
1039
+ ],
1040
+ "$defs": {
1041
+ "ActionEvent": {
1042
+ "description": "One normalized action a harness took, harness-agnostic so a single consumer\nassertion works across harnesses. Every field is always serialized (null when\nabsent) so the shape is stable, mirroring the `usage` contract.",
1043
+ "type": "object",
1044
+ "properties": {
1045
+ "index": {
1046
+ "description": "Position of this event within the run, so \"≤ N tool calls\" and \"did X\nbefore Y\" are expressible from a stable ordering (also array order).",
1047
+ "type": "integer",
1048
+ "format": "uint",
1049
+ "minimum": 0
1050
+ },
1051
+ "input": {
1052
+ "description": "Structured, tool-shaped arguments (the command string, the file path),\nso a consumer asserts on specific args without re-parsing; `null` when the\nevent carries none (e.g. a `tool_result`)."
1053
+ },
1054
+ "kind": {
1055
+ "description": "The kind of event: `tool_call` (the model invoked a tool) or\n`tool_result` (the observation returned to the model). Left open for\nfuture kinds rather than an enum, so a new shape never breaks the field.",
1056
+ "type": "string"
1057
+ },
1058
+ "name": {
1059
+ "description": "Normalized tool name where knowable (e.g. `bash`, `Edit`); `null` for a\n`tool_result`, or when the harness did not name the tool.",
1060
+ "type": [
1061
+ "string",
1062
+ "null"
1063
+ ]
1064
+ },
1065
+ "output": {
1066
+ "description": "The result/observation text, when the trace exposes it; `null` otherwise.",
1067
+ "type": [
1068
+ "string",
1069
+ "null"
1070
+ ]
1071
+ }
1072
+ },
1073
+ "required": [
1074
+ "index",
1075
+ "input",
1076
+ "kind",
1077
+ "name",
1078
+ "output"
1079
+ ]
1080
+ },
1081
+ "FailureKind": {
1082
+ "description": "The normalized, closed set of failure reasons oneharness can classify from a\nharness's output. It is the single source for the `failure_kind` contract\nvalue: serialized as the snake_case token a consumer reads in the report\n(`auth`, `rate_limit`, `model_not_found`, `quota`, `tool_deferred`), so the\nwire shape is unchanged — modeling it as an enum keeps a misspelled or\ninvalid kind unrepresentable and gives every producer/consumer (classifier,\n`is_failure`, the fallback fall-through rule, the report, history) one\ndefinition to share instead of scattered string literals.",
1083
+ "oneOf": [
1084
+ {
1085
+ "description": "Authentication / authorization rejected the request (401/403, missing or\ninvalid credentials).",
1086
+ "type": "string",
1087
+ "const": "auth"
1088
+ },
1089
+ {
1090
+ "description": "Rate limited (429, too many requests) — a transient condition of an\notherwise working, authenticated harness.",
1091
+ "type": "string",
1092
+ "const": "rate_limit"
1093
+ },
1094
+ {
1095
+ "description": "The requested model was not found / is invalid — a configuration mistake.",
1096
+ "type": "string",
1097
+ "const": "model_not_found"
1098
+ },
1099
+ {
1100
+ "description": "Out of quota / credits, or a billing problem — a provisioning failure.",
1101
+ "type": "string",
1102
+ "const": "quota"
1103
+ },
1104
+ {
1105
+ "description": "The harness deferred a builtin tool call instead of executing it, so a\nclean-exit run did no useful work (Claude Code bridge deployments; issue\n#1114). The only kind that can appear on a `status: ok` run.",
1106
+ "type": "string",
1107
+ "const": "tool_deferred"
1108
+ }
1109
+ ]
1110
+ },
1111
+ "PermissionMode": {
1112
+ "description": "The unified approval mode, from least to most autonomy. A harness may not\nsupport every value (see [`crate::domain::harness::HarnessSpec::mode`]); the\ncommand layer refuses an unsupported one before spawning, never silently\ndowngrading it.",
1113
+ "oneOf": [
1114
+ {
1115
+ "description": "No mutations — the agent may read but not edit files or run commands —\n*without* the plan workflow: it just does whatever read-only work the task\nallows. Mapped to each harness's strongest per-run no-mutation enforcement\n(Codex's read-only sandbox, Claude's deny rules, Copilot's `--deny-tool`,\nCursor's `ask` mode). The enforced counterpart to [`Self::Plan`].",
1116
+ "type": "string",
1117
+ "const": "read-only"
1118
+ },
1119
+ {
1120
+ "description": "Read and propose only — like [`Self::ReadOnly`], but additionally engages\nthe harness's *plan* workflow: research the task and write a plan rather\nthan act. Supported only where a native plan mode exists (Claude/Qwen/\nCursor/Copilot, OpenCode's `plan` agent); harnesses with no plan workflow\n(Codex, Goose, Crush) reject it — use [`Self::ReadOnly`] for those.",
1121
+ "type": "string",
1122
+ "const": "plan"
1123
+ },
1124
+ {
1125
+ "description": "The harness's normal ask flow, mapped to its cleanest *non-interactive*\nvariant so a headless run never blocks waiting for input (Claude's\n`dontAsk` deny-and-continue, Goose's fail-closed `approve`, Copilot's\nauto-deny). Where no such variant exists the harness's [`ModeHeadless`]\nis [`ModeHeadless::Hangs`].",
1126
+ "type": "string",
1127
+ "const": "default"
1128
+ },
1129
+ {
1130
+ "description": "Auto-approve file edits, still gate shell/commands. (Claude's\n`acceptEdits`, Qwen's `auto-edit`.)",
1131
+ "type": "string",
1132
+ "const": "edit"
1133
+ },
1134
+ {
1135
+ "description": "Auto-approve operations the harness deems safe (a classifier), gate the\nrisky ones. (Claude's `auto`, Qwen's `auto`, Goose's `smart_approve`.)",
1136
+ "type": "string",
1137
+ "const": "auto"
1138
+ },
1139
+ {
1140
+ "description": "Approve everything — no prompts, no gating. The headless default, because\nan unattended agent otherwise hangs on the first approval. Every harness\nsupports it. (Claude's `bypassPermissions`, Codex's\n`--dangerously-bypass-approvals-and-sandbox`, Qwen/Crush `--yolo`, …)",
1141
+ "type": "string",
1142
+ "const": "bypass"
1143
+ }
1144
+ ]
1145
+ },
1146
+ "Status": {
1147
+ "description": "The outcome of attempting to run one harness.",
1148
+ "oneOf": [
1149
+ {
1150
+ "description": "Spawned and exited 0.",
1151
+ "type": "string",
1152
+ "const": "ok"
1153
+ },
1154
+ {
1155
+ "description": "Spawned and exited non-zero.",
1156
+ "type": "string",
1157
+ "const": "nonzero"
1158
+ },
1159
+ {
1160
+ "description": "Killed after exceeding the per-harness timeout.",
1161
+ "type": "string",
1162
+ "const": "timeout"
1163
+ },
1164
+ {
1165
+ "description": "The binary was resolved but could not be executed.",
1166
+ "type": "string",
1167
+ "const": "spawn-error"
1168
+ },
1169
+ {
1170
+ "description": "The binary was not found, so the harness was not run.",
1171
+ "type": "string",
1172
+ "const": "skipped"
1173
+ },
1174
+ {
1175
+ "description": "`--print-command`: the command was built but not executed.",
1176
+ "type": "string",
1177
+ "const": "planned"
1178
+ }
1179
+ ]
1180
+ },
1181
+ "Usage": {
1182
+ "description": "Normalized token/cost accounting. Every field is best-effort and independently\nnullable: a harness may report tokens but not dollar cost (cost is commonly\nabsent on subscription auth), or report nothing at all (plain-text harnesses).",
1183
+ "type": "object",
1184
+ "properties": {
1185
+ "cache_read_tokens": {
1186
+ "description": "Prompt tokens served from the provider's prompt cache (a cheap read of a\npreviously-written prefix), when the harness reports them. `None` when the\nharness does not surface cache counts — never `0` as a guess.",
1187
+ "type": [
1188
+ "integer",
1189
+ "null"
1190
+ ],
1191
+ "format": "uint64",
1192
+ "minimum": 0
1193
+ },
1194
+ "cache_write_tokens": {
1195
+ "description": "Prompt tokens written to the provider's prompt cache (a.k.a. cache\ncreation), when the harness reports them. `None` when not surfaced.",
1196
+ "type": [
1197
+ "integer",
1198
+ "null"
1199
+ ],
1200
+ "format": "uint64",
1201
+ "minimum": 0
1202
+ },
1203
+ "cost_usd": {
1204
+ "description": "Total cost in USD, when the harness reports it (often absent on\nsubscription auth, where there is no per-call dollar figure).",
1205
+ "type": [
1206
+ "number",
1207
+ "null"
1208
+ ],
1209
+ "format": "double"
1210
+ },
1211
+ "input_tokens": {
1212
+ "description": "Prompt/input tokens billed, when the harness reports them.",
1213
+ "type": [
1214
+ "integer",
1215
+ "null"
1216
+ ],
1217
+ "format": "uint64",
1218
+ "minimum": 0
1219
+ },
1220
+ "output_tokens": {
1221
+ "description": "Completion/output tokens billed, when the harness reports them.",
1222
+ "type": [
1223
+ "integer",
1224
+ "null"
1225
+ ],
1226
+ "format": "uint64",
1227
+ "minimum": 0
1228
+ }
1229
+ },
1230
+ "required": [
1231
+ "cache_read_tokens",
1232
+ "cache_write_tokens",
1233
+ "cost_usd",
1234
+ "input_tokens",
1235
+ "output_tokens"
1236
+ ]
1237
+ }
1238
+ }
1239
+ },
1240
+ "history_records": {
1241
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
1242
+ "title": "Array_of_HistoryRecord",
1243
+ "type": "array",
1244
+ "items": {
1245
+ "$ref": "#/$defs/HistoryRecord"
1246
+ },
1247
+ "$defs": {
1248
+ "ActionEvent": {
1249
+ "description": "One normalized action a harness took, harness-agnostic so a single consumer\nassertion works across harnesses. Every field is always serialized (null when\nabsent) so the shape is stable, mirroring the `usage` contract.",
1250
+ "type": "object",
1251
+ "properties": {
1252
+ "index": {
1253
+ "description": "Position of this event within the run, so \"≤ N tool calls\" and \"did X\nbefore Y\" are expressible from a stable ordering (also array order).",
1254
+ "type": "integer",
1255
+ "format": "uint",
1256
+ "minimum": 0
1257
+ },
1258
+ "input": {
1259
+ "description": "Structured, tool-shaped arguments (the command string, the file path),\nso a consumer asserts on specific args without re-parsing; `null` when the\nevent carries none (e.g. a `tool_result`)."
1260
+ },
1261
+ "kind": {
1262
+ "description": "The kind of event: `tool_call` (the model invoked a tool) or\n`tool_result` (the observation returned to the model). Left open for\nfuture kinds rather than an enum, so a new shape never breaks the field.",
1263
+ "type": "string"
1264
+ },
1265
+ "name": {
1266
+ "description": "Normalized tool name where knowable (e.g. `bash`, `Edit`); `null` for a\n`tool_result`, or when the harness did not name the tool.",
1267
+ "type": [
1268
+ "string",
1269
+ "null"
1270
+ ]
1271
+ },
1272
+ "output": {
1273
+ "description": "The result/observation text, when the trace exposes it; `null` otherwise.",
1274
+ "type": [
1275
+ "string",
1276
+ "null"
1277
+ ]
1278
+ }
1279
+ },
1280
+ "required": [
1281
+ "index",
1282
+ "input",
1283
+ "kind",
1284
+ "name",
1285
+ "output"
1286
+ ]
1287
+ },
1288
+ "FailureKind": {
1289
+ "description": "The normalized, closed set of failure reasons oneharness can classify from a\nharness's output. It is the single source for the `failure_kind` contract\nvalue: serialized as the snake_case token a consumer reads in the report\n(`auth`, `rate_limit`, `model_not_found`, `quota`, `tool_deferred`), so the\nwire shape is unchanged — modeling it as an enum keeps a misspelled or\ninvalid kind unrepresentable and gives every producer/consumer (classifier,\n`is_failure`, the fallback fall-through rule, the report, history) one\ndefinition to share instead of scattered string literals.",
1290
+ "oneOf": [
1291
+ {
1292
+ "description": "Authentication / authorization rejected the request (401/403, missing or\ninvalid credentials).",
1293
+ "type": "string",
1294
+ "const": "auth"
1295
+ },
1296
+ {
1297
+ "description": "Rate limited (429, too many requests) — a transient condition of an\notherwise working, authenticated harness.",
1298
+ "type": "string",
1299
+ "const": "rate_limit"
1300
+ },
1301
+ {
1302
+ "description": "The requested model was not found / is invalid — a configuration mistake.",
1303
+ "type": "string",
1304
+ "const": "model_not_found"
1305
+ },
1306
+ {
1307
+ "description": "Out of quota / credits, or a billing problem — a provisioning failure.",
1308
+ "type": "string",
1309
+ "const": "quota"
1310
+ },
1311
+ {
1312
+ "description": "The harness deferred a builtin tool call instead of executing it, so a\nclean-exit run did no useful work (Claude Code bridge deployments; issue\n#1114). The only kind that can appear on a `status: ok` run.",
1313
+ "type": "string",
1314
+ "const": "tool_deferred"
1315
+ }
1316
+ ]
1317
+ },
1318
+ "HistoryRecord": {
1319
+ "description": "One harness run, normalized and frozen for the history log. Serialized as one\nJSONL line per harness run, appended as the run finalizes. Carries only the\nnormalized cross-harness signals — no raw stdout/stderr.",
1320
+ "type": "object",
1321
+ "properties": {
1322
+ "duration_ms": {
1323
+ "type": [
1324
+ "integer",
1325
+ "null"
1326
+ ],
1327
+ "format": "uint128",
1328
+ "minimum": 0
1329
+ },
1330
+ "events": {
1331
+ "description": "Best-effort normalized tool-call events; `null` when the harness exposes\nno machine-readable trace.",
1332
+ "type": [
1333
+ "array",
1334
+ "null"
1335
+ ],
1336
+ "items": {
1337
+ "$ref": "#/$defs/ActionEvent"
1338
+ }
1339
+ },
1340
+ "exit_code": {
1341
+ "type": [
1342
+ "integer",
1343
+ "null"
1344
+ ],
1345
+ "format": "int32"
1346
+ },
1347
+ "failure_kind": {
1348
+ "description": "Best-effort classified failure reason (see [`FailureKind`]); `null` when\nunclassified.",
1349
+ "anyOf": [
1350
+ {
1351
+ "$ref": "#/$defs/FailureKind"
1352
+ },
1353
+ {
1354
+ "type": "null"
1355
+ }
1356
+ ]
1357
+ },
1358
+ "harness": {
1359
+ "description": "Canonical harness id (e.g. `claude-code`).",
1360
+ "type": "string"
1361
+ },
1362
+ "model": {
1363
+ "description": "The effective top-level model for the run, if any.",
1364
+ "type": [
1365
+ "string",
1366
+ "null"
1367
+ ]
1368
+ },
1369
+ "name": {
1370
+ "description": "The human-meaningful session name (see [`session_name`]); repeated on\nevery record so a reader can resolve a session by name from any line.",
1371
+ "type": "string"
1372
+ },
1373
+ "permission_mode": {
1374
+ "description": "The normalized approval mode requested for the run.",
1375
+ "$ref": "#/$defs/PermissionMode"
1376
+ },
1377
+ "project": {
1378
+ "description": "The project directory the run operated in (the real path, not the\non-disk slug), so the list view can show where a session ran.",
1379
+ "type": "string"
1380
+ },
1381
+ "prompt": {
1382
+ "description": "The prompt this harness run received (its own, on a batch run; else the\nrun's single prompt).",
1383
+ "type": "string"
1384
+ },
1385
+ "schema_version": {
1386
+ "type": "string"
1387
+ },
1388
+ "session": {
1389
+ "description": "The oneharness session id this run belongs to (the history file's stem).",
1390
+ "type": "string"
1391
+ },
1392
+ "session_id": {
1393
+ "description": "The harness's own continuation id, when it exposed one; `null` otherwise.",
1394
+ "type": [
1395
+ "string",
1396
+ "null"
1397
+ ]
1398
+ },
1399
+ "status": {
1400
+ "$ref": "#/$defs/Status"
1401
+ },
1402
+ "text": {
1403
+ "description": "Best-effort final assistant text; `null` when extraction was impossible.",
1404
+ "type": [
1405
+ "string",
1406
+ "null"
1407
+ ]
1408
+ },
1409
+ "text_source": {
1410
+ "description": "How `text` was extracted; `null` when absent.",
1411
+ "type": [
1412
+ "string",
1413
+ "null"
1414
+ ]
1415
+ },
1416
+ "timestamp": {
1417
+ "description": "RFC3339 UTC instant the record was written (append time).",
1418
+ "type": "string"
1419
+ },
1420
+ "usage": {
1421
+ "description": "Best-effort token/cost accounting (every field `null` when unreported).",
1422
+ "$ref": "#/$defs/Usage"
1423
+ }
1424
+ },
1425
+ "required": [
1426
+ "duration_ms",
1427
+ "events",
1428
+ "exit_code",
1429
+ "failure_kind",
1430
+ "harness",
1431
+ "model",
1432
+ "name",
1433
+ "permission_mode",
1434
+ "project",
1435
+ "prompt",
1436
+ "schema_version",
1437
+ "session",
1438
+ "session_id",
1439
+ "status",
1440
+ "text",
1441
+ "text_source",
1442
+ "timestamp",
1443
+ "usage"
1444
+ ]
1445
+ },
1446
+ "PermissionMode": {
1447
+ "description": "The unified approval mode, from least to most autonomy. A harness may not\nsupport every value (see [`crate::domain::harness::HarnessSpec::mode`]); the\ncommand layer refuses an unsupported one before spawning, never silently\ndowngrading it.",
1448
+ "oneOf": [
1449
+ {
1450
+ "description": "No mutations — the agent may read but not edit files or run commands —\n*without* the plan workflow: it just does whatever read-only work the task\nallows. Mapped to each harness's strongest per-run no-mutation enforcement\n(Codex's read-only sandbox, Claude's deny rules, Copilot's `--deny-tool`,\nCursor's `ask` mode). The enforced counterpart to [`Self::Plan`].",
1451
+ "type": "string",
1452
+ "const": "read-only"
1453
+ },
1454
+ {
1455
+ "description": "Read and propose only — like [`Self::ReadOnly`], but additionally engages\nthe harness's *plan* workflow: research the task and write a plan rather\nthan act. Supported only where a native plan mode exists (Claude/Qwen/\nCursor/Copilot, OpenCode's `plan` agent); harnesses with no plan workflow\n(Codex, Goose, Crush) reject it — use [`Self::ReadOnly`] for those.",
1456
+ "type": "string",
1457
+ "const": "plan"
1458
+ },
1459
+ {
1460
+ "description": "The harness's normal ask flow, mapped to its cleanest *non-interactive*\nvariant so a headless run never blocks waiting for input (Claude's\n`dontAsk` deny-and-continue, Goose's fail-closed `approve`, Copilot's\nauto-deny). Where no such variant exists the harness's [`ModeHeadless`]\nis [`ModeHeadless::Hangs`].",
1461
+ "type": "string",
1462
+ "const": "default"
1463
+ },
1464
+ {
1465
+ "description": "Auto-approve file edits, still gate shell/commands. (Claude's\n`acceptEdits`, Qwen's `auto-edit`.)",
1466
+ "type": "string",
1467
+ "const": "edit"
1468
+ },
1469
+ {
1470
+ "description": "Auto-approve operations the harness deems safe (a classifier), gate the\nrisky ones. (Claude's `auto`, Qwen's `auto`, Goose's `smart_approve`.)",
1471
+ "type": "string",
1472
+ "const": "auto"
1473
+ },
1474
+ {
1475
+ "description": "Approve everything — no prompts, no gating. The headless default, because\nan unattended agent otherwise hangs on the first approval. Every harness\nsupports it. (Claude's `bypassPermissions`, Codex's\n`--dangerously-bypass-approvals-and-sandbox`, Qwen/Crush `--yolo`, …)",
1476
+ "type": "string",
1477
+ "const": "bypass"
1478
+ }
1479
+ ]
1480
+ },
1481
+ "Status": {
1482
+ "description": "The outcome of attempting to run one harness.",
1483
+ "oneOf": [
1484
+ {
1485
+ "description": "Spawned and exited 0.",
1486
+ "type": "string",
1487
+ "const": "ok"
1488
+ },
1489
+ {
1490
+ "description": "Spawned and exited non-zero.",
1491
+ "type": "string",
1492
+ "const": "nonzero"
1493
+ },
1494
+ {
1495
+ "description": "Killed after exceeding the per-harness timeout.",
1496
+ "type": "string",
1497
+ "const": "timeout"
1498
+ },
1499
+ {
1500
+ "description": "The binary was resolved but could not be executed.",
1501
+ "type": "string",
1502
+ "const": "spawn-error"
1503
+ },
1504
+ {
1505
+ "description": "The binary was not found, so the harness was not run.",
1506
+ "type": "string",
1507
+ "const": "skipped"
1508
+ },
1509
+ {
1510
+ "description": "`--print-command`: the command was built but not executed.",
1511
+ "type": "string",
1512
+ "const": "planned"
1513
+ }
1514
+ ]
1515
+ },
1516
+ "Usage": {
1517
+ "description": "Normalized token/cost accounting. Every field is best-effort and independently\nnullable: a harness may report tokens but not dollar cost (cost is commonly\nabsent on subscription auth), or report nothing at all (plain-text harnesses).",
1518
+ "type": "object",
1519
+ "properties": {
1520
+ "cache_read_tokens": {
1521
+ "description": "Prompt tokens served from the provider's prompt cache (a cheap read of a\npreviously-written prefix), when the harness reports them. `None` when the\nharness does not surface cache counts — never `0` as a guess.",
1522
+ "type": [
1523
+ "integer",
1524
+ "null"
1525
+ ],
1526
+ "format": "uint64",
1527
+ "minimum": 0
1528
+ },
1529
+ "cache_write_tokens": {
1530
+ "description": "Prompt tokens written to the provider's prompt cache (a.k.a. cache\ncreation), when the harness reports them. `None` when not surfaced.",
1531
+ "type": [
1532
+ "integer",
1533
+ "null"
1534
+ ],
1535
+ "format": "uint64",
1536
+ "minimum": 0
1537
+ },
1538
+ "cost_usd": {
1539
+ "description": "Total cost in USD, when the harness reports it (often absent on\nsubscription auth, where there is no per-call dollar figure).",
1540
+ "type": [
1541
+ "number",
1542
+ "null"
1543
+ ],
1544
+ "format": "double"
1545
+ },
1546
+ "input_tokens": {
1547
+ "description": "Prompt/input tokens billed, when the harness reports them.",
1548
+ "type": [
1549
+ "integer",
1550
+ "null"
1551
+ ],
1552
+ "format": "uint64",
1553
+ "minimum": 0
1554
+ },
1555
+ "output_tokens": {
1556
+ "description": "Completion/output tokens billed, when the harness reports them.",
1557
+ "type": [
1558
+ "integer",
1559
+ "null"
1560
+ ],
1561
+ "format": "uint64",
1562
+ "minimum": 0
1563
+ }
1564
+ },
1565
+ "required": [
1566
+ "cache_read_tokens",
1567
+ "cache_write_tokens",
1568
+ "cost_usd",
1569
+ "input_tokens",
1570
+ "output_tokens"
1571
+ ]
1572
+ }
1573
+ }
1574
+ },
1575
+ "history_list": {
1576
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
1577
+ "title": "Array_of_HistorySessionSummary",
1578
+ "type": "array",
1579
+ "items": {
1580
+ "$ref": "#/$defs/HistorySessionSummary"
1581
+ },
1582
+ "$defs": {
1583
+ "HistorySessionSummary": {
1584
+ "description": "A one-line summary of a session, for `oneharness history list`. Read from the\nsession's records: `name`/`project`/`started` come from the first record,\n`harnesses` is the distinct set across all records.",
1585
+ "type": "object",
1586
+ "properties": {
1587
+ "harnesses": {
1588
+ "description": "The distinct harness ids the session touched, in first-seen order.",
1589
+ "type": "array",
1590
+ "items": {
1591
+ "type": "string"
1592
+ }
1593
+ },
1594
+ "id": {
1595
+ "description": "The session id (the file stem), unique and sortable by start time.",
1596
+ "type": "string"
1597
+ },
1598
+ "name": {
1599
+ "description": "The human-meaningful session name (non-unique).",
1600
+ "type": "string"
1601
+ },
1602
+ "path": {
1603
+ "description": "The absolute path of the session file.",
1604
+ "type": "string"
1605
+ },
1606
+ "project": {
1607
+ "description": "The project directory the run operated in.",
1608
+ "type": "string"
1609
+ },
1610
+ "record_count": {
1611
+ "description": "How many harness-run records the session holds.",
1612
+ "type": "integer",
1613
+ "format": "uint",
1614
+ "minimum": 0
1615
+ },
1616
+ "started": {
1617
+ "description": "The RFC3339 UTC start time (first record's timestamp); empty if unknown.",
1618
+ "type": "string"
1619
+ }
1620
+ },
1621
+ "required": [
1622
+ "harnesses",
1623
+ "id",
1624
+ "name",
1625
+ "path",
1626
+ "project",
1627
+ "record_count",
1628
+ "started"
1629
+ ]
1630
+ }
1631
+ }
1632
+ },
1633
+ "list_report": {
1634
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
1635
+ "title": "ListReport",
1636
+ "type": "object",
1637
+ "properties": {
1638
+ "harnesses": {
1639
+ "type": "array",
1640
+ "items": {
1641
+ "$ref": "#/$defs/HarnessInfo"
1642
+ }
1643
+ },
1644
+ "schema_version": {
1645
+ "type": "string"
1646
+ }
1647
+ },
1648
+ "required": [
1649
+ "harnesses",
1650
+ "schema_version"
1651
+ ],
1652
+ "$defs": {
1653
+ "HarnessInfo": {
1654
+ "type": "object",
1655
+ "properties": {
1656
+ "default_bin": {
1657
+ "type": "string"
1658
+ },
1659
+ "display": {
1660
+ "type": "string"
1661
+ },
1662
+ "example_command": {
1663
+ "description": "The argv oneharness would build, with placeholders, so the adapter's\nshape is visible without running anything.",
1664
+ "type": "array",
1665
+ "items": {
1666
+ "type": "string"
1667
+ }
1668
+ },
1669
+ "fork_reuses_cache": {
1670
+ "description": "Whether a forked run reuses the parent session's prompt-cache prefix, so a\nfork-based `--batch-strategy min-tokens` run actually reduces tokens. `true`\nonly for Claude Code today; `false` (incl. OpenCode, whose fork re-sends the\nprefix cold) means `min-tokens` only orders the calls (no saving).",
1671
+ "type": "boolean"
1672
+ },
1673
+ "id": {
1674
+ "type": "string"
1675
+ },
1676
+ "install_hint": {
1677
+ "type": "string"
1678
+ },
1679
+ "mock_rewrite": {
1680
+ "description": "The input-rewrite verdict shape `oneharness mock <id>` speaks for this\nharness (`claude-nested`, `crush-flat`, `opencode-shim`); `null` when\nthe harness has no verified rewrite — a rewrite rule for it is then a\nloud usage error (see the README mock support matrix).",
1681
+ "type": [
1682
+ "string",
1683
+ "null"
1684
+ ]
1685
+ },
1686
+ "modes": {
1687
+ "description": "The approval modes (`--mode`) this harness can express, each with its\nheadless behavior. Modes not listed are unsupported for the harness.",
1688
+ "type": "array",
1689
+ "items": {
1690
+ "$ref": "#/$defs/ModeInfo"
1691
+ }
1692
+ },
1693
+ "output_format": {
1694
+ "$ref": "#/$defs/OutputFormat"
1695
+ },
1696
+ "session_capable": {
1697
+ "description": "Whether `run --session <name>` is supported — a uniform, caller-owned\nhandle oneharness maps to the harness's native session id. `true` only for\nharnesses that expose a session id headlessly; `false` means `--session` is\na loud usage error (there is no id to bind a name to).",
1698
+ "type": "boolean"
1699
+ },
1700
+ "supports_allowed_tools": {
1701
+ "description": "Whether the unified allow/deny rule lists and hooks table can be synced\ninto that file (see the README support matrix).",
1702
+ "type": "boolean"
1703
+ },
1704
+ "supports_denied_tools": {
1705
+ "type": "boolean"
1706
+ },
1707
+ "supports_fork": {
1708
+ "description": "Whether `run --resume <session> --fork` is supported — branching a new\nsession from the resumed one. `false` means it resumes linearly only.",
1709
+ "type": "boolean"
1710
+ },
1711
+ "supports_hooks": {
1712
+ "type": "boolean"
1713
+ },
1714
+ "supports_mock_deny": {
1715
+ "description": "Whether `oneharness mock <id>` can express a pre-tool *deny* for this\nharness (the same protocol `oneharness gate` speaks).",
1716
+ "type": "boolean"
1717
+ },
1718
+ "supports_native_schema": {
1719
+ "description": "Whether `run --schema` is delivered through a native structured-output\nflag for this harness (Claude Code's `--json-schema`). `false` means the\nportable prompt-based path is used — structured output works either way;\noneharness always validates and retries.",
1720
+ "type": "boolean"
1721
+ },
1722
+ "supports_prompt_stdin": {
1723
+ "description": "Whether a large user prompt can be delivered off the argv (piped to the\nharness's stdin) so it never trips the OS argument ceiling (`E2BIG`).",
1724
+ "type": "boolean"
1725
+ },
1726
+ "supports_reasoning": {
1727
+ "description": "Whether `run --reasoning <effort>` can be delivered on the argv for this\nharness (Claude Code's `--effort`, Codex's `model_reasoning_effort`).\n`false` means it has no headless reasoning flag — a reasoning request is\nthen a loud usage error (effort is provider/model config there).",
1728
+ "type": "boolean"
1729
+ },
1730
+ "supports_resume": {
1731
+ "description": "Whether `run --resume <session>` is supported for this harness.",
1732
+ "type": "boolean"
1733
+ },
1734
+ "supports_system_file": {
1735
+ "description": "Whether a large system prompt can be delivered off the argv via a file\nflag (Claude Code's `--append-system-prompt-file`). `false` does not mean a\nlarge system is unhandled — for a harness whose system rides the prompt it\ntravels on stdin with the prompt; see the README large-prompt matrix.",
1736
+ "type": "boolean"
1737
+ },
1738
+ "sync_file": {
1739
+ "description": "The project-scoped config file `oneharness sync` writes for this\nharness; `null` when it has none (sync settings are then rejected).",
1740
+ "type": [
1741
+ "string",
1742
+ "null"
1743
+ ]
1744
+ }
1745
+ },
1746
+ "required": [
1747
+ "default_bin",
1748
+ "display",
1749
+ "example_command",
1750
+ "fork_reuses_cache",
1751
+ "id",
1752
+ "install_hint",
1753
+ "mock_rewrite",
1754
+ "modes",
1755
+ "output_format",
1756
+ "session_capable",
1757
+ "supports_allowed_tools",
1758
+ "supports_denied_tools",
1759
+ "supports_fork",
1760
+ "supports_hooks",
1761
+ "supports_mock_deny",
1762
+ "supports_native_schema",
1763
+ "supports_prompt_stdin",
1764
+ "supports_reasoning",
1765
+ "supports_resume",
1766
+ "supports_system_file",
1767
+ "sync_file"
1768
+ ]
1769
+ },
1770
+ "ModeHeadless": {
1771
+ "description": "How a harness honors one [`PermissionMode`] in a *headless* run — the\nclassification that lets oneharness refuse a hang instead of waiting it out.",
1772
+ "oneOf": [
1773
+ {
1774
+ "description": "Never blocks: the harness denies-and-continues, fails closed, or runs\nfully autonomously. Safe to spawn unattended.",
1775
+ "type": "string",
1776
+ "const": "clean"
1777
+ },
1778
+ {
1779
+ "description": "May block on an interactive approval prompt that a headless run cannot\nanswer (the harness has no clean non-interactive variant of this mode).\nThe command layer refuses this combination unless `--permit-prompts` is\nset, turning a silent hang into a loud, immediate error.",
1780
+ "type": "string",
1781
+ "const": "hangs"
1782
+ }
1783
+ ]
1784
+ },
1785
+ "ModeInfo": {
1786
+ "description": "One supported approval mode for a harness, with its headless behavior, in\n`oneharness list`. A [`PermissionMode`] absent from a harness's array is\nunsupported for it (a `--mode` request would be refused).",
1787
+ "type": "object",
1788
+ "properties": {
1789
+ "headless": {
1790
+ "description": "`\"clean\"` (never blocks headless) or `\"hangs\"` (would block on an\napproval prompt; refused without --permit-prompts).",
1791
+ "$ref": "#/$defs/ModeHeadless"
1792
+ },
1793
+ "mode": {
1794
+ "$ref": "#/$defs/PermissionMode"
1795
+ }
1796
+ },
1797
+ "required": [
1798
+ "headless",
1799
+ "mode"
1800
+ ]
1801
+ },
1802
+ "OutputFormat": {
1803
+ "description": "How a harness emits its result, which decides how `text` is extracted.\n\nAlso accepted as a CLI value (`--output-format`, parsed in the `oneharness`\nbinary) and a config-file value (`output_format`, via `Deserialize`). The\nCLI parsing lives in the binary so this core crate stays free of `clap`.",
1804
+ "oneOf": [
1805
+ {
1806
+ "description": "Plain text on stdout; `text` is the trimmed stdout.",
1807
+ "type": "string",
1808
+ "const": "text"
1809
+ },
1810
+ {
1811
+ "description": "A single JSON document on stdout.",
1812
+ "type": "string",
1813
+ "const": "json"
1814
+ },
1815
+ {
1816
+ "description": "Line-delimited JSON events on stdout.",
1817
+ "type": "string",
1818
+ "const": "stream-json"
1819
+ }
1820
+ ]
1821
+ },
1822
+ "PermissionMode": {
1823
+ "description": "The unified approval mode, from least to most autonomy. A harness may not\nsupport every value (see [`crate::domain::harness::HarnessSpec::mode`]); the\ncommand layer refuses an unsupported one before spawning, never silently\ndowngrading it.",
1824
+ "oneOf": [
1825
+ {
1826
+ "description": "No mutations — the agent may read but not edit files or run commands —\n*without* the plan workflow: it just does whatever read-only work the task\nallows. Mapped to each harness's strongest per-run no-mutation enforcement\n(Codex's read-only sandbox, Claude's deny rules, Copilot's `--deny-tool`,\nCursor's `ask` mode). The enforced counterpart to [`Self::Plan`].",
1827
+ "type": "string",
1828
+ "const": "read-only"
1829
+ },
1830
+ {
1831
+ "description": "Read and propose only — like [`Self::ReadOnly`], but additionally engages\nthe harness's *plan* workflow: research the task and write a plan rather\nthan act. Supported only where a native plan mode exists (Claude/Qwen/\nCursor/Copilot, OpenCode's `plan` agent); harnesses with no plan workflow\n(Codex, Goose, Crush) reject it — use [`Self::ReadOnly`] for those.",
1832
+ "type": "string",
1833
+ "const": "plan"
1834
+ },
1835
+ {
1836
+ "description": "The harness's normal ask flow, mapped to its cleanest *non-interactive*\nvariant so a headless run never blocks waiting for input (Claude's\n`dontAsk` deny-and-continue, Goose's fail-closed `approve`, Copilot's\nauto-deny). Where no such variant exists the harness's [`ModeHeadless`]\nis [`ModeHeadless::Hangs`].",
1837
+ "type": "string",
1838
+ "const": "default"
1839
+ },
1840
+ {
1841
+ "description": "Auto-approve file edits, still gate shell/commands. (Claude's\n`acceptEdits`, Qwen's `auto-edit`.)",
1842
+ "type": "string",
1843
+ "const": "edit"
1844
+ },
1845
+ {
1846
+ "description": "Auto-approve operations the harness deems safe (a classifier), gate the\nrisky ones. (Claude's `auto`, Qwen's `auto`, Goose's `smart_approve`.)",
1847
+ "type": "string",
1848
+ "const": "auto"
1849
+ },
1850
+ {
1851
+ "description": "Approve everything — no prompts, no gating. The headless default, because\nan unattended agent otherwise hangs on the first approval. Every harness\nsupports it. (Claude's `bypassPermissions`, Codex's\n`--dangerously-bypass-approvals-and-sandbox`, Qwen/Crush `--yolo`, …)",
1852
+ "type": "string",
1853
+ "const": "bypass"
1854
+ }
1855
+ ]
1856
+ }
1857
+ }
1858
+ },
1859
+ "detect_report": {
1860
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
1861
+ "title": "DetectReport",
1862
+ "type": "object",
1863
+ "properties": {
1864
+ "detected": {
1865
+ "type": "array",
1866
+ "items": {
1867
+ "$ref": "#/$defs/DetectInfo"
1868
+ }
1869
+ },
1870
+ "schema_version": {
1871
+ "type": "string"
1872
+ }
1873
+ },
1874
+ "required": [
1875
+ "detected",
1876
+ "schema_version"
1877
+ ],
1878
+ "$defs": {
1879
+ "DetectInfo": {
1880
+ "type": "object",
1881
+ "properties": {
1882
+ "available": {
1883
+ "type": "boolean"
1884
+ },
1885
+ "bin": {
1886
+ "type": "string"
1887
+ },
1888
+ "id": {
1889
+ "type": "string"
1890
+ },
1891
+ "path": {
1892
+ "type": [
1893
+ "string",
1894
+ "null"
1895
+ ]
1896
+ },
1897
+ "version": {
1898
+ "type": [
1899
+ "string",
1900
+ "null"
1901
+ ]
1902
+ }
1903
+ },
1904
+ "required": [
1905
+ "available",
1906
+ "bin",
1907
+ "id",
1908
+ "path",
1909
+ "version"
1910
+ ]
1911
+ }
1912
+ }
1913
+ }
1914
+ }