@oneharness/sdk 0.6.8 → 0.6.10

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.
@@ -27,6 +27,17 @@
27
27
  "type": "string"
28
28
  }
29
29
  },
30
+ "control": {
31
+ "description": "Out-of-band turn control for this run (`--control`), or `null` when none\nwas requested — which is every ordinary run. It records the socket a\nseparate `oneharness interrupt` process could address, the harness\nmechanism behind it, and each request served, so a consumer can tell an\ninterrupted turn from one that simply ended.",
32
+ "anyOf": [
33
+ {
34
+ "$ref": "#/$defs/ControlReport"
35
+ },
36
+ {
37
+ "type": "null"
38
+ }
39
+ ]
40
+ },
30
41
  "dry_run": {
31
42
  "type": "boolean"
32
43
  },
@@ -150,9 +161,14 @@
150
161
  "spy_file",
151
162
  "history_file",
152
163
  "config_files",
164
+ "control",
153
165
  "results"
154
166
  ],
155
167
  "$defs": {
168
+ "AbsolutePath": {
169
+ "description": "A filesystem path that is known to be absolute.\n\nControl addresses are handed to a *different process* — the socket path in\nthe report is how a supervisor finds the run — so a relative path is not a\nsmaller version of the right answer, it is a different file depending on who\nreads it. The type is the difference between \"we documented it as absolute\"\nand \"it is\".",
170
+ "type": "string"
171
+ },
156
172
  "ActionEvent": {
157
173
  "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
174
  "type": "object",
@@ -289,6 +305,240 @@
289
305
  }
290
306
  ]
291
307
  },
308
+ "ControlEvent": {
309
+ "description": "One control request the run handled, recorded in the report so a consumer\ncan tell an interrupted turn from one that simply ended.\n\nA sum type discriminated by `outcome`, so a record can never say \"served,\nand here is the refusal reason\": the reason exists exactly when there was a\nrefusal. Serialized flat — `{\"outcome\":\"served\",\"verb\":…,\"at\":…}` /\n`{\"outcome\":\"refused\",\"verb\":…,\"at\":…,\"reason\":…}`.",
310
+ "oneOf": [
311
+ {
312
+ "description": "The mechanism accepted the request.",
313
+ "type": "object",
314
+ "properties": {
315
+ "at": {
316
+ "description": "When the request was handled.",
317
+ "$ref": "#/$defs/UtcInstant"
318
+ },
319
+ "outcome": {
320
+ "type": "string",
321
+ "const": "served"
322
+ },
323
+ "verb": {
324
+ "description": "The verb requested.",
325
+ "$ref": "#/$defs/ControlVerb"
326
+ }
327
+ },
328
+ "required": [
329
+ "outcome",
330
+ "verb",
331
+ "at"
332
+ ]
333
+ },
334
+ {
335
+ "description": "The request was refused, with why.",
336
+ "type": "object",
337
+ "properties": {
338
+ "at": {
339
+ "$ref": "#/$defs/UtcInstant"
340
+ },
341
+ "outcome": {
342
+ "type": "string",
343
+ "const": "refused"
344
+ },
345
+ "reason": {
346
+ "$ref": "#/$defs/ControlReason"
347
+ },
348
+ "verb": {
349
+ "$ref": "#/$defs/ControlVerb"
350
+ }
351
+ },
352
+ "required": [
353
+ "outcome",
354
+ "verb",
355
+ "at",
356
+ "reason"
357
+ ]
358
+ }
359
+ ]
360
+ },
361
+ "ControlReason": {
362
+ "description": "Why a control request could not be served. Distinct reasons because a\nsupervisor reacts differently to each: `unsupported` is permanent for the\nharness, `not_running` means the dispatch is gone, `no_active_turn` means\nthe run is alive but between turns.",
363
+ "oneOf": [
364
+ {
365
+ "description": "The harness declares no [`ControlShape`] — the lever does not exist.",
366
+ "type": "string",
367
+ "const": "unsupported"
368
+ },
369
+ {
370
+ "description": "The run is alive but no turn is in flight right now.",
371
+ "type": "string",
372
+ "const": "no_active_turn"
373
+ },
374
+ {
375
+ "description": "No run is listening on the socket (never started, already exited, or a\nstale socket file left by an abnormally-terminated dispatch).",
376
+ "type": "string",
377
+ "const": "not_running"
378
+ }
379
+ ]
380
+ },
381
+ "ControlReport": {
382
+ "description": "The run report's `control` block: where the socket lived, which mechanism\nbacked it, and every request served over the run's lifetime.",
383
+ "type": "object",
384
+ "properties": {
385
+ "interrupts": {
386
+ "description": "Every control request served, in order.",
387
+ "type": "array",
388
+ "items": {
389
+ "$ref": "#/$defs/ControlEvent"
390
+ }
391
+ },
392
+ "mechanism": {
393
+ "description": "The harness mechanism backing it.",
394
+ "$ref": "#/$defs/ControlShape"
395
+ },
396
+ "socket": {
397
+ "description": "Absolute path of the socket this run listened on.",
398
+ "$ref": "#/$defs/AbsolutePath"
399
+ }
400
+ },
401
+ "required": [
402
+ "socket",
403
+ "mechanism",
404
+ "interrupts"
405
+ ]
406
+ },
407
+ "ControlShape": {
408
+ "description": "How a harness accepts an out-of-band interrupt for an in-flight turn.\n\nRegistry data on [`crate::domain::harness::HarnessSpec::control`], sourced\nfrom a live interrupt against the real CLI — never guessed. `None` there\nmeans `oneharness interrupt` is a loud usage error for the harness, never a\nsilent no-op: a supervisor that is told \"ok\" while the turn keeps running is\nworse off than one told the lever does not exist.",
409
+ "oneOf": [
410
+ {
411
+ "description": "Claude Code: the control frame rides the run process's **own stdin**\n(`-p --input-format stream-json`), so no sidecar server is needed. A\n`{\"type\":\"control_request\",…,\"request\":{\"subtype\":\"interrupt\"}}` line\naborts the turn with a `result` document; the session survives and a\nlater turn continues it with full context. Verified live: a plain user\nmessage written mid-turn is *silently dropped*, so the control frame is\nthe only mechanism that works.",
412
+ "type": "string",
413
+ "const": "claude-control-request"
414
+ },
415
+ {
416
+ "description": "Codex: `turn/interrupt {threadId,turnId}` over the `codex app-server`\nJSON-RPC stdio protocol, which oneharness spawns as the run's own child\nand drives the thread/turn lifecycle on — so the interrupt rides the same\nopen stdin and nothing is shared or pooled.",
417
+ "type": "string",
418
+ "const": "codex-app-server"
419
+ },
420
+ {
421
+ "description": "OpenCode: `POST /api/session/{id}/interrupt` against `opencode serve`.",
422
+ "type": "string",
423
+ "const": "opencode-http"
424
+ },
425
+ {
426
+ "description": "Goose and Copilot: the ACP `session/cancel` **notification** (no `id`)\nover their JSON-RPC stdio servers.",
427
+ "type": "string",
428
+ "const": "acp-cancel"
429
+ },
430
+ {
431
+ "description": "Crush: `POST /v1/workspaces/{id}/agent/sessions/{sid}/cancel` against\n`crush server`.",
432
+ "type": "string",
433
+ "const": "crush-http"
434
+ }
435
+ ]
436
+ },
437
+ "ControlVerb": {
438
+ "description": "The control verb a supervisor sends. Only `interrupt` exists today; the\nframe's `v` is what leaves room for `steer` later.",
439
+ "type": "string",
440
+ "enum": [
441
+ "interrupt"
442
+ ]
443
+ },
444
+ "ExecutionTelemetry": {
445
+ "description": "Measured execution telemetry for one harness run: when the invocation ran,\nand — when the harness's transcript supports it — how its wall clock split\nbetween provider latency and tool work.\n\nCarried from the runner/parser to the history writer *and*, since report\nschema `0.5`, serialized on [`RunResult::telemetry`]. Exposing it there is\nwhat lets a consumer read the numbers off the run it just made instead of\nre-opening the history file the same run wrote.\n\nInternally tagged by `source`, so the variant is a value a consumer switches\non rather than a shape it has to sniff. Every variant states only what was\nactually measured — there is no variant meaning \"no telemetry\"; that is a\n`null` field.",
446
+ "oneOf": [
447
+ {
448
+ "description": "The harness's own transcript carried a complete provider trace, so the\ninvocation bounds *and* the model/tool split are measured.",
449
+ "type": "object",
450
+ "properties": {
451
+ "finished_at": {
452
+ "type": [
453
+ "string",
454
+ "null"
455
+ ],
456
+ "maxLength": 24,
457
+ "minLength": 24,
458
+ "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])T([01]\\d|2[0-3]):[0-5]\\d:([0-5]\\d|60)\\.\\d{3}Z$"
459
+ },
460
+ "model_ms": {
461
+ "type": [
462
+ "integer",
463
+ "null"
464
+ ],
465
+ "format": "uint128",
466
+ "minimum": 0
467
+ },
468
+ "source": {
469
+ "type": "string",
470
+ "const": "provider_measured"
471
+ },
472
+ "started_at": {
473
+ "type": "string",
474
+ "maxLength": 24,
475
+ "minLength": 24,
476
+ "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])T([01]\\d|2[0-3]):[0-5]\\d:([0-5]\\d|60)\\.\\d{3}Z$"
477
+ },
478
+ "time_to_first_token_ms": {
479
+ "type": [
480
+ "integer",
481
+ "null"
482
+ ],
483
+ "format": "uint128",
484
+ "minimum": 0
485
+ },
486
+ "tool_ms": {
487
+ "type": [
488
+ "integer",
489
+ "null"
490
+ ],
491
+ "format": "uint128",
492
+ "minimum": 0
493
+ }
494
+ },
495
+ "required": [
496
+ "source",
497
+ "started_at",
498
+ "finished_at",
499
+ "model_ms",
500
+ "tool_ms",
501
+ "time_to_first_token_ms"
502
+ ]
503
+ },
504
+ {
505
+ "description": "The invocation bounds the runner observed for a run whose provider trace\nnever completed, with no model/tool split: a split read out of a\ntranscript that stopped mid-turn is not a measurement, but when the run\nitself started is, and it is what an operator reads off a failure. Typed\nas a [`UtcInstant`] so the one thing this variant claims to know cannot be\nempty or in some other offset by the time a record renders it.",
506
+ "type": "object",
507
+ "properties": {
508
+ "source": {
509
+ "type": "string",
510
+ "const": "partial_invocation"
511
+ },
512
+ "started_at": {
513
+ "type": "string"
514
+ }
515
+ },
516
+ "required": [
517
+ "source",
518
+ "started_at"
519
+ ]
520
+ },
521
+ {
522
+ "description": "The union of tool intervals observed at the stdout pipe, for a harness\nwhose transcript carries no provider trace to split. Not provider-measured\nand with no model-latency counterpart, which is what the `source` tag tells\na consumer; per-event provenance lives on `ActionEvent`.",
523
+ "type": "object",
524
+ "properties": {
525
+ "source": {
526
+ "type": "string",
527
+ "const": "stdout_observed"
528
+ },
529
+ "tool_ms": {
530
+ "type": "integer",
531
+ "format": "uint128",
532
+ "minimum": 0
533
+ }
534
+ },
535
+ "required": [
536
+ "source",
537
+ "tool_ms"
538
+ ]
539
+ }
540
+ ]
541
+ },
292
542
  "FailureKind": {
293
543
  "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.",
294
544
  "oneOf": [
@@ -563,6 +813,18 @@
563
813
  "structured": {
564
814
  "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."
565
815
  },
816
+ "telemetry": {
817
+ "description": "Measured execution telemetry for this run: the invocation bounds and, when\nthe harness's transcript carried a trace to read them from, the\nmodel/tool split. `null` when nothing was measured — never estimated.\nAdded in report schema `0.5`; before that a consumer had to re-read the\nhistory file for numbers the run itself already had.",
818
+ "anyOf": [
819
+ {
820
+ "$ref": "#/$defs/ExecutionTelemetry"
821
+ },
822
+ {
823
+ "type": "null"
824
+ }
825
+ ],
826
+ "default": null
827
+ },
566
828
  "text": {
567
829
  "description": "Best-effort final assistant text; `null` when extraction is impossible.",
568
830
  "type": [
@@ -607,6 +869,7 @@
607
869
  "model",
608
870
  "exit_code",
609
871
  "duration_ms",
872
+ "telemetry",
610
873
  "command",
611
874
  "output_format",
612
875
  "text",
@@ -694,6 +957,11 @@
694
957
  "type": "string",
695
958
  "const": "timeout"
696
959
  },
960
+ {
961
+ "description": "Terminated before it finished because the run was **cancelled** — a\nlibrary caller flipped its [`crate::io::cancel::CancelToken`], or the host\nreceived SIGINT/SIGTERM while [`crate::io::cancel::install_signal_cancel`]\nwas in force. Distinct from `timeout` (the harness was given its full\ndeadline and exceeded it) and from a consumer-driven streaming stop (which\nstays `ok`, because the consumer already got what it asked for). Any\noutput captured before the cancellation is still normalized, exactly as it\nis for a timeout.",
962
+ "type": "string",
963
+ "const": "cancelled"
964
+ },
697
965
  {
698
966
  "description": "The binary was resolved but could not be executed.",
699
967
  "type": "string",
@@ -791,6 +1059,10 @@
791
1059
  "cache_write_tokens",
792
1060
  "cost_usd"
793
1061
  ]
1062
+ },
1063
+ "UtcInstant": {
1064
+ "description": "An RFC 3339 timestamp in UTC (`Z`), in oneharness's canonical spelling.",
1065
+ "type": "string"
794
1066
  }
795
1067
  }
796
1068
  },
@@ -835,6 +1107,10 @@
835
1107
  }
836
1108
  ],
837
1109
  "$defs": {
1110
+ "AbsolutePath": {
1111
+ "description": "A filesystem path that is known to be absolute.\n\nControl addresses are handed to a *different process* — the socket path in\nthe report is how a supervisor finds the run — so a relative path is not a\nsmaller version of the right answer, it is a different file depending on who\nreads it. The type is the difference between \"we documented it as absolute\"\nand \"it is\".",
1112
+ "type": "string"
1113
+ },
838
1114
  "ActionEvent": {
839
1115
  "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.",
840
1116
  "type": "object",
@@ -971,6 +1247,240 @@
971
1247
  }
972
1248
  ]
973
1249
  },
1250
+ "ControlEvent": {
1251
+ "description": "One control request the run handled, recorded in the report so a consumer\ncan tell an interrupted turn from one that simply ended.\n\nA sum type discriminated by `outcome`, so a record can never say \"served,\nand here is the refusal reason\": the reason exists exactly when there was a\nrefusal. Serialized flat — `{\"outcome\":\"served\",\"verb\":…,\"at\":…}` /\n`{\"outcome\":\"refused\",\"verb\":…,\"at\":…,\"reason\":…}`.",
1252
+ "oneOf": [
1253
+ {
1254
+ "description": "The mechanism accepted the request.",
1255
+ "type": "object",
1256
+ "properties": {
1257
+ "at": {
1258
+ "description": "When the request was handled.",
1259
+ "$ref": "#/$defs/UtcInstant"
1260
+ },
1261
+ "outcome": {
1262
+ "type": "string",
1263
+ "const": "served"
1264
+ },
1265
+ "verb": {
1266
+ "description": "The verb requested.",
1267
+ "$ref": "#/$defs/ControlVerb"
1268
+ }
1269
+ },
1270
+ "required": [
1271
+ "outcome",
1272
+ "verb",
1273
+ "at"
1274
+ ]
1275
+ },
1276
+ {
1277
+ "description": "The request was refused, with why.",
1278
+ "type": "object",
1279
+ "properties": {
1280
+ "at": {
1281
+ "$ref": "#/$defs/UtcInstant"
1282
+ },
1283
+ "outcome": {
1284
+ "type": "string",
1285
+ "const": "refused"
1286
+ },
1287
+ "reason": {
1288
+ "$ref": "#/$defs/ControlReason"
1289
+ },
1290
+ "verb": {
1291
+ "$ref": "#/$defs/ControlVerb"
1292
+ }
1293
+ },
1294
+ "required": [
1295
+ "outcome",
1296
+ "verb",
1297
+ "at",
1298
+ "reason"
1299
+ ]
1300
+ }
1301
+ ]
1302
+ },
1303
+ "ControlReason": {
1304
+ "description": "Why a control request could not be served. Distinct reasons because a\nsupervisor reacts differently to each: `unsupported` is permanent for the\nharness, `not_running` means the dispatch is gone, `no_active_turn` means\nthe run is alive but between turns.",
1305
+ "oneOf": [
1306
+ {
1307
+ "description": "The harness declares no [`ControlShape`] — the lever does not exist.",
1308
+ "type": "string",
1309
+ "const": "unsupported"
1310
+ },
1311
+ {
1312
+ "description": "The run is alive but no turn is in flight right now.",
1313
+ "type": "string",
1314
+ "const": "no_active_turn"
1315
+ },
1316
+ {
1317
+ "description": "No run is listening on the socket (never started, already exited, or a\nstale socket file left by an abnormally-terminated dispatch).",
1318
+ "type": "string",
1319
+ "const": "not_running"
1320
+ }
1321
+ ]
1322
+ },
1323
+ "ControlReport": {
1324
+ "description": "The run report's `control` block: where the socket lived, which mechanism\nbacked it, and every request served over the run's lifetime.",
1325
+ "type": "object",
1326
+ "properties": {
1327
+ "interrupts": {
1328
+ "description": "Every control request served, in order.",
1329
+ "type": "array",
1330
+ "items": {
1331
+ "$ref": "#/$defs/ControlEvent"
1332
+ }
1333
+ },
1334
+ "mechanism": {
1335
+ "description": "The harness mechanism backing it.",
1336
+ "$ref": "#/$defs/ControlShape"
1337
+ },
1338
+ "socket": {
1339
+ "description": "Absolute path of the socket this run listened on.",
1340
+ "$ref": "#/$defs/AbsolutePath"
1341
+ }
1342
+ },
1343
+ "required": [
1344
+ "socket",
1345
+ "mechanism",
1346
+ "interrupts"
1347
+ ]
1348
+ },
1349
+ "ControlShape": {
1350
+ "description": "How a harness accepts an out-of-band interrupt for an in-flight turn.\n\nRegistry data on [`crate::domain::harness::HarnessSpec::control`], sourced\nfrom a live interrupt against the real CLI — never guessed. `None` there\nmeans `oneharness interrupt` is a loud usage error for the harness, never a\nsilent no-op: a supervisor that is told \"ok\" while the turn keeps running is\nworse off than one told the lever does not exist.",
1351
+ "oneOf": [
1352
+ {
1353
+ "description": "Claude Code: the control frame rides the run process's **own stdin**\n(`-p --input-format stream-json`), so no sidecar server is needed. A\n`{\"type\":\"control_request\",…,\"request\":{\"subtype\":\"interrupt\"}}` line\naborts the turn with a `result` document; the session survives and a\nlater turn continues it with full context. Verified live: a plain user\nmessage written mid-turn is *silently dropped*, so the control frame is\nthe only mechanism that works.",
1354
+ "type": "string",
1355
+ "const": "claude-control-request"
1356
+ },
1357
+ {
1358
+ "description": "Codex: `turn/interrupt {threadId,turnId}` over the `codex app-server`\nJSON-RPC stdio protocol, which oneharness spawns as the run's own child\nand drives the thread/turn lifecycle on — so the interrupt rides the same\nopen stdin and nothing is shared or pooled.",
1359
+ "type": "string",
1360
+ "const": "codex-app-server"
1361
+ },
1362
+ {
1363
+ "description": "OpenCode: `POST /api/session/{id}/interrupt` against `opencode serve`.",
1364
+ "type": "string",
1365
+ "const": "opencode-http"
1366
+ },
1367
+ {
1368
+ "description": "Goose and Copilot: the ACP `session/cancel` **notification** (no `id`)\nover their JSON-RPC stdio servers.",
1369
+ "type": "string",
1370
+ "const": "acp-cancel"
1371
+ },
1372
+ {
1373
+ "description": "Crush: `POST /v1/workspaces/{id}/agent/sessions/{sid}/cancel` against\n`crush server`.",
1374
+ "type": "string",
1375
+ "const": "crush-http"
1376
+ }
1377
+ ]
1378
+ },
1379
+ "ControlVerb": {
1380
+ "description": "The control verb a supervisor sends. Only `interrupt` exists today; the\nframe's `v` is what leaves room for `steer` later.",
1381
+ "type": "string",
1382
+ "enum": [
1383
+ "interrupt"
1384
+ ]
1385
+ },
1386
+ "ExecutionTelemetry": {
1387
+ "description": "Measured execution telemetry for one harness run: when the invocation ran,\nand — when the harness's transcript supports it — how its wall clock split\nbetween provider latency and tool work.\n\nCarried from the runner/parser to the history writer *and*, since report\nschema `0.5`, serialized on [`RunResult::telemetry`]. Exposing it there is\nwhat lets a consumer read the numbers off the run it just made instead of\nre-opening the history file the same run wrote.\n\nInternally tagged by `source`, so the variant is a value a consumer switches\non rather than a shape it has to sniff. Every variant states only what was\nactually measured — there is no variant meaning \"no telemetry\"; that is a\n`null` field.",
1388
+ "oneOf": [
1389
+ {
1390
+ "description": "The harness's own transcript carried a complete provider trace, so the\ninvocation bounds *and* the model/tool split are measured.",
1391
+ "type": "object",
1392
+ "properties": {
1393
+ "finished_at": {
1394
+ "type": [
1395
+ "string",
1396
+ "null"
1397
+ ],
1398
+ "maxLength": 24,
1399
+ "minLength": 24,
1400
+ "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])T([01]\\d|2[0-3]):[0-5]\\d:([0-5]\\d|60)\\.\\d{3}Z$"
1401
+ },
1402
+ "model_ms": {
1403
+ "type": [
1404
+ "integer",
1405
+ "null"
1406
+ ],
1407
+ "format": "uint128",
1408
+ "minimum": 0
1409
+ },
1410
+ "source": {
1411
+ "type": "string",
1412
+ "const": "provider_measured"
1413
+ },
1414
+ "started_at": {
1415
+ "type": "string",
1416
+ "maxLength": 24,
1417
+ "minLength": 24,
1418
+ "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])T([01]\\d|2[0-3]):[0-5]\\d:([0-5]\\d|60)\\.\\d{3}Z$"
1419
+ },
1420
+ "time_to_first_token_ms": {
1421
+ "type": [
1422
+ "integer",
1423
+ "null"
1424
+ ],
1425
+ "format": "uint128",
1426
+ "minimum": 0
1427
+ },
1428
+ "tool_ms": {
1429
+ "type": [
1430
+ "integer",
1431
+ "null"
1432
+ ],
1433
+ "format": "uint128",
1434
+ "minimum": 0
1435
+ }
1436
+ },
1437
+ "required": [
1438
+ "source",
1439
+ "started_at",
1440
+ "finished_at",
1441
+ "model_ms",
1442
+ "tool_ms",
1443
+ "time_to_first_token_ms"
1444
+ ]
1445
+ },
1446
+ {
1447
+ "description": "The invocation bounds the runner observed for a run whose provider trace\nnever completed, with no model/tool split: a split read out of a\ntranscript that stopped mid-turn is not a measurement, but when the run\nitself started is, and it is what an operator reads off a failure. Typed\nas a [`UtcInstant`] so the one thing this variant claims to know cannot be\nempty or in some other offset by the time a record renders it.",
1448
+ "type": "object",
1449
+ "properties": {
1450
+ "source": {
1451
+ "type": "string",
1452
+ "const": "partial_invocation"
1453
+ },
1454
+ "started_at": {
1455
+ "type": "string"
1456
+ }
1457
+ },
1458
+ "required": [
1459
+ "source",
1460
+ "started_at"
1461
+ ]
1462
+ },
1463
+ {
1464
+ "description": "The union of tool intervals observed at the stdout pipe, for a harness\nwhose transcript carries no provider trace to split. Not provider-measured\nand with no model-latency counterpart, which is what the `source` tag tells\na consumer; per-event provenance lives on `ActionEvent`.",
1465
+ "type": "object",
1466
+ "properties": {
1467
+ "source": {
1468
+ "type": "string",
1469
+ "const": "stdout_observed"
1470
+ },
1471
+ "tool_ms": {
1472
+ "type": "integer",
1473
+ "format": "uint128",
1474
+ "minimum": 0
1475
+ }
1476
+ },
1477
+ "required": [
1478
+ "source",
1479
+ "tool_ms"
1480
+ ]
1481
+ }
1482
+ ]
1483
+ },
974
1484
  "FailureKind": {
975
1485
  "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.",
976
1486
  "oneOf": [
@@ -1124,6 +1634,17 @@
1124
1634
  "type": "string"
1125
1635
  }
1126
1636
  },
1637
+ "control": {
1638
+ "description": "Out-of-band turn control for this run (`--control`), or `null` when none\nwas requested — which is every ordinary run. It records the socket a\nseparate `oneharness interrupt` process could address, the harness\nmechanism behind it, and each request served, so a consumer can tell an\ninterrupted turn from one that simply ended.",
1639
+ "anyOf": [
1640
+ {
1641
+ "$ref": "#/$defs/ControlReport"
1642
+ },
1643
+ {
1644
+ "type": "null"
1645
+ }
1646
+ ]
1647
+ },
1127
1648
  "dry_run": {
1128
1649
  "type": "boolean"
1129
1650
  },
@@ -1247,6 +1768,7 @@
1247
1768
  "spy_file",
1248
1769
  "history_file",
1249
1770
  "config_files",
1771
+ "control",
1250
1772
  "results"
1251
1773
  ]
1252
1774
  },
@@ -1397,6 +1919,18 @@
1397
1919
  "structured": {
1398
1920
  "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."
1399
1921
  },
1922
+ "telemetry": {
1923
+ "description": "Measured execution telemetry for this run: the invocation bounds and, when\nthe harness's transcript carried a trace to read them from, the\nmodel/tool split. `null` when nothing was measured — never estimated.\nAdded in report schema `0.5`; before that a consumer had to re-read the\nhistory file for numbers the run itself already had.",
1924
+ "anyOf": [
1925
+ {
1926
+ "$ref": "#/$defs/ExecutionTelemetry"
1927
+ },
1928
+ {
1929
+ "type": "null"
1930
+ }
1931
+ ],
1932
+ "default": null
1933
+ },
1400
1934
  "text": {
1401
1935
  "description": "Best-effort final assistant text; `null` when extraction is impossible.",
1402
1936
  "type": [
@@ -1441,6 +1975,7 @@
1441
1975
  "model",
1442
1976
  "exit_code",
1443
1977
  "duration_ms",
1978
+ "telemetry",
1444
1979
  "command",
1445
1980
  "output_format",
1446
1981
  "text",
@@ -1528,6 +2063,11 @@
1528
2063
  "type": "string",
1529
2064
  "const": "timeout"
1530
2065
  },
2066
+ {
2067
+ "description": "Terminated before it finished because the run was **cancelled** — a\nlibrary caller flipped its [`crate::io::cancel::CancelToken`], or the host\nreceived SIGINT/SIGTERM while [`crate::io::cancel::install_signal_cancel`]\nwas in force. Distinct from `timeout` (the harness was given its full\ndeadline and exceeded it) and from a consumer-driven streaming stop (which\nstays `ok`, because the consumer already got what it asked for). Any\noutput captured before the cancellation is still normalized, exactly as it\nis for a timeout.",
2068
+ "type": "string",
2069
+ "const": "cancelled"
2070
+ },
1531
2071
  {
1532
2072
  "description": "The binary was resolved but could not be executed.",
1533
2073
  "type": "string",
@@ -1625,6 +2165,10 @@
1625
2165
  "cache_write_tokens",
1626
2166
  "cost_usd"
1627
2167
  ]
2168
+ },
2169
+ "UtcInstant": {
2170
+ "description": "An RFC 3339 timestamp in UTC (`Z`), in oneharness's canonical spelling.",
2171
+ "type": "string"
1628
2172
  }
1629
2173
  }
1630
2174
  },
@@ -1945,7 +2489,8 @@
1945
2489
  "type": "string",
1946
2490
  "enum": [
1947
2491
  "1.2",
1948
- "1.3"
2492
+ "1.3",
2493
+ "1.4"
1949
2494
  ]
1950
2495
  },
1951
2496
  "type": {
@@ -2059,7 +2604,10 @@
2059
2604
  },
2060
2605
  "schema_version": {
2061
2606
  "type": "string",
2062
- "const": "1.3"
2607
+ "enum": [
2608
+ "1.3",
2609
+ "1.4"
2610
+ ]
2063
2611
  }
2064
2612
  },
2065
2613
  "required": [
@@ -2076,6 +2624,7 @@
2076
2624
  "enum": [
2077
2625
  "nonzero",
2078
2626
  "timeout",
2627
+ "cancelled",
2079
2628
  "spawn-error",
2080
2629
  "skipped"
2081
2630
  ]
@@ -2098,6 +2647,37 @@
2098
2647
  ]
2099
2648
  }
2100
2649
  ]
2650
+ },
2651
+ {
2652
+ "anyOf": [
2653
+ {
2654
+ "type": "object",
2655
+ "properties": {
2656
+ "status": {
2657
+ "type": "string",
2658
+ "enum": [
2659
+ "ok",
2660
+ "nonzero",
2661
+ "timeout",
2662
+ "spawn-error",
2663
+ "skipped",
2664
+ "planned"
2665
+ ]
2666
+ }
2667
+ }
2668
+ },
2669
+ {
2670
+ "type": "object",
2671
+ "properties": {
2672
+ "schema_version": {
2673
+ "type": "string",
2674
+ "enum": [
2675
+ "1.4"
2676
+ ]
2677
+ }
2678
+ }
2679
+ }
2680
+ ]
2101
2681
  }
2102
2682
  ],
2103
2683
  "oneOf": [
@@ -2187,7 +2767,8 @@
2187
2767
  "1.0",
2188
2768
  "1.1",
2189
2769
  "1.2",
2190
- "1.3"
2770
+ "1.3",
2771
+ "1.4"
2191
2772
  ]
2192
2773
  },
2193
2774
  "session": {
@@ -2368,7 +2949,8 @@
2368
2949
  "1.0",
2369
2950
  "1.1",
2370
2951
  "1.2",
2371
- "1.3"
2952
+ "1.3",
2953
+ "1.4"
2372
2954
  ]
2373
2955
  },
2374
2956
  "session": {
@@ -2388,6 +2970,7 @@
2388
2970
  "type": "string",
2389
2971
  "enum": [
2390
2972
  "timeout",
2973
+ "cancelled",
2391
2974
  "spawn-error",
2392
2975
  "skipped",
2393
2976
  "planned"
@@ -2546,7 +3129,8 @@
2546
3129
  "type": "string",
2547
3130
  "enum": [
2548
3131
  "1.2",
2549
- "1.3"
3132
+ "1.3",
3133
+ "1.4"
2550
3134
  ]
2551
3135
  },
2552
3136
  "session": {
@@ -2706,7 +3290,8 @@
2706
3290
  "1.0",
2707
3291
  "1.1",
2708
3292
  "1.2",
2709
- "1.3"
3293
+ "1.3",
3294
+ "1.4"
2710
3295
  ]
2711
3296
  },
2712
3297
  "session": {
@@ -2724,6 +3309,7 @@
2724
3309
  "enum": [
2725
3310
  "nonzero",
2726
3311
  "timeout",
3312
+ "cancelled",
2727
3313
  "spawn-error",
2728
3314
  "skipped"
2729
3315
  ]
@@ -2864,7 +3450,8 @@
2864
3450
  "schema_version": {
2865
3451
  "type": "string",
2866
3452
  "enum": [
2867
- "1.3"
3453
+ "1.3",
3454
+ "1.4"
2868
3455
  ]
2869
3456
  },
2870
3457
  "session": {
@@ -2885,6 +3472,7 @@
2885
3472
  "enum": [
2886
3473
  "nonzero",
2887
3474
  "timeout",
3475
+ "cancelled",
2888
3476
  "spawn-error",
2889
3477
  "skipped"
2890
3478
  ]
@@ -3039,7 +3627,8 @@
3039
3627
  "1.0",
3040
3628
  "1.1",
3041
3629
  "1.2",
3042
- "1.3"
3630
+ "1.3",
3631
+ "1.4"
3043
3632
  ]
3044
3633
  },
3045
3634
  "session": {
@@ -3319,6 +3908,11 @@
3319
3908
  "type": "string",
3320
3909
  "const": "timeout"
3321
3910
  },
3911
+ {
3912
+ "description": "Terminated before it finished because the run was **cancelled** — a\nlibrary caller flipped its [`crate::io::cancel::CancelToken`], or the host\nreceived SIGINT/SIGTERM while [`crate::io::cancel::install_signal_cancel`]\nwas in force. Distinct from `timeout` (the harness was given its full\ndeadline and exceeded it) and from a consumer-driven streaming stop (which\nstays `ok`, because the consumer already got what it asked for). Any\noutput captured before the cancellation is still normalized, exactly as it\nis for a timeout.",
3913
+ "type": "string",
3914
+ "const": "cancelled"
3915
+ },
3322
3916
  {
3323
3917
  "description": "The binary was resolved but could not be executed.",
3324
3918
  "type": "string",
@@ -3444,7 +4038,10 @@
3444
4038
  },
3445
4039
  "schema_version": {
3446
4040
  "type": "string",
3447
- "const": "1.3"
4041
+ "enum": [
4042
+ "1.3",
4043
+ "1.4"
4044
+ ]
3448
4045
  }
3449
4046
  },
3450
4047
  "required": [
@@ -3461,6 +4058,7 @@
3461
4058
  "enum": [
3462
4059
  "nonzero",
3463
4060
  "timeout",
4061
+ "cancelled",
3464
4062
  "spawn-error",
3465
4063
  "skipped"
3466
4064
  ]
@@ -3483,6 +4081,37 @@
3483
4081
  ]
3484
4082
  }
3485
4083
  ]
4084
+ },
4085
+ {
4086
+ "anyOf": [
4087
+ {
4088
+ "type": "object",
4089
+ "properties": {
4090
+ "status": {
4091
+ "type": "string",
4092
+ "enum": [
4093
+ "ok",
4094
+ "nonzero",
4095
+ "timeout",
4096
+ "spawn-error",
4097
+ "skipped",
4098
+ "planned"
4099
+ ]
4100
+ }
4101
+ }
4102
+ },
4103
+ {
4104
+ "type": "object",
4105
+ "properties": {
4106
+ "schema_version": {
4107
+ "type": "string",
4108
+ "enum": [
4109
+ "1.4"
4110
+ ]
4111
+ }
4112
+ }
4113
+ }
4114
+ ]
3486
4115
  }
3487
4116
  ],
3488
4117
  "oneOf": [
@@ -3753,7 +4382,8 @@
3753
4382
  "type": "string",
3754
4383
  "enum": [
3755
4384
  "1.2",
3756
- "1.3"
4385
+ "1.3",
4386
+ "1.4"
3757
4387
  ]
3758
4388
  },
3759
4389
  "session": {
@@ -4352,7 +4982,8 @@
4352
4982
  "type": "string",
4353
4983
  "enum": [
4354
4984
  "1.2",
4355
- "1.3"
4985
+ "1.3",
4986
+ "1.4"
4356
4987
  ]
4357
4988
  },
4358
4989
  "session": {
@@ -4526,7 +5157,8 @@
4526
5157
  "type": "string",
4527
5158
  "enum": [
4528
5159
  "1.2",
4529
- "1.3"
5160
+ "1.3",
5161
+ "1.4"
4530
5162
  ]
4531
5163
  },
4532
5164
  "session": {
@@ -4698,7 +5330,8 @@
4698
5330
  "type": "string",
4699
5331
  "enum": [
4700
5332
  "1.2",
4701
- "1.3"
5333
+ "1.3",
5334
+ "1.4"
4702
5335
  ]
4703
5336
  },
4704
5337
  "session": {
@@ -4718,6 +5351,7 @@
4718
5351
  "enum": [
4719
5352
  "nonzero",
4720
5353
  "timeout",
5354
+ "cancelled",
4721
5355
  "spawn-error",
4722
5356
  "skipped"
4723
5357
  ]
@@ -4870,7 +5504,8 @@
4870
5504
  "schema_version": {
4871
5505
  "type": "string",
4872
5506
  "enum": [
4873
- "1.3"
5507
+ "1.3",
5508
+ "1.4"
4874
5509
  ]
4875
5510
  },
4876
5511
  "session": {
@@ -4893,6 +5528,7 @@
4893
5528
  "enum": [
4894
5529
  "nonzero",
4895
5530
  "timeout",
5531
+ "cancelled",
4896
5532
  "spawn-error",
4897
5533
  "skipped"
4898
5534
  ]
@@ -5087,6 +5723,7 @@
5087
5723
  "enum": [
5088
5724
  "nonzero",
5089
5725
  "timeout",
5726
+ "cancelled",
5090
5727
  "spawn-error",
5091
5728
  "skipped"
5092
5729
  ]
@@ -5812,6 +6449,11 @@
5812
6449
  "type": "string",
5813
6450
  "const": "timeout"
5814
6451
  },
6452
+ {
6453
+ "description": "Terminated before it finished because the run was **cancelled** — a\nlibrary caller flipped its [`crate::io::cancel::CancelToken`], or the host\nreceived SIGINT/SIGTERM while [`crate::io::cancel::install_signal_cancel`]\nwas in force. Distinct from `timeout` (the harness was given its full\ndeadline and exceeded it) and from a consumer-driven streaming stop (which\nstays `ok`, because the consumer already got what it asked for). Any\noutput captured before the cancellation is still normalized, exactly as it\nis for a timeout.",
6454
+ "type": "string",
6455
+ "const": "cancelled"
6456
+ },
5815
6457
  {
5816
6458
  "description": "The binary was resolved but could not be executed.",
5817
6459
  "type": "string",
@@ -6109,7 +6751,8 @@
6109
6751
  "type": "string",
6110
6752
  "enum": [
6111
6753
  "1.2",
6112
- "1.3"
6754
+ "1.3",
6755
+ "1.4"
6113
6756
  ]
6114
6757
  },
6115
6758
  "variant": {
@@ -6229,7 +6872,10 @@
6229
6872
  },
6230
6873
  "schema_version": {
6231
6874
  "type": "string",
6232
- "const": "1.3"
6875
+ "enum": [
6876
+ "1.3",
6877
+ "1.4"
6878
+ ]
6233
6879
  }
6234
6880
  },
6235
6881
  "required": [
@@ -6246,6 +6892,7 @@
6246
6892
  "enum": [
6247
6893
  "nonzero",
6248
6894
  "timeout",
6895
+ "cancelled",
6249
6896
  "spawn-error",
6250
6897
  "skipped"
6251
6898
  ]
@@ -6268,6 +6915,37 @@
6268
6915
  ]
6269
6916
  }
6270
6917
  ]
6918
+ },
6919
+ {
6920
+ "anyOf": [
6921
+ {
6922
+ "type": "object",
6923
+ "properties": {
6924
+ "status": {
6925
+ "type": "string",
6926
+ "enum": [
6927
+ "ok",
6928
+ "nonzero",
6929
+ "timeout",
6930
+ "spawn-error",
6931
+ "skipped",
6932
+ "planned"
6933
+ ]
6934
+ }
6935
+ }
6936
+ },
6937
+ {
6938
+ "type": "object",
6939
+ "properties": {
6940
+ "schema_version": {
6941
+ "type": "string",
6942
+ "enum": [
6943
+ "1.4"
6944
+ ]
6945
+ }
6946
+ }
6947
+ }
6948
+ ]
6271
6949
  }
6272
6950
  ],
6273
6951
  "oneOf": [
@@ -6538,7 +7216,8 @@
6538
7216
  "type": "string",
6539
7217
  "enum": [
6540
7218
  "1.2",
6541
- "1.3"
7219
+ "1.3",
7220
+ "1.4"
6542
7221
  ]
6543
7222
  },
6544
7223
  "session": {
@@ -7137,7 +7816,8 @@
7137
7816
  "type": "string",
7138
7817
  "enum": [
7139
7818
  "1.2",
7140
- "1.3"
7819
+ "1.3",
7820
+ "1.4"
7141
7821
  ]
7142
7822
  },
7143
7823
  "session": {
@@ -7311,7 +7991,8 @@
7311
7991
  "type": "string",
7312
7992
  "enum": [
7313
7993
  "1.2",
7314
- "1.3"
7994
+ "1.3",
7995
+ "1.4"
7315
7996
  ]
7316
7997
  },
7317
7998
  "session": {
@@ -7483,7 +8164,8 @@
7483
8164
  "type": "string",
7484
8165
  "enum": [
7485
8166
  "1.2",
7486
- "1.3"
8167
+ "1.3",
8168
+ "1.4"
7487
8169
  ]
7488
8170
  },
7489
8171
  "session": {
@@ -7503,6 +8185,7 @@
7503
8185
  "enum": [
7504
8186
  "nonzero",
7505
8187
  "timeout",
8188
+ "cancelled",
7506
8189
  "spawn-error",
7507
8190
  "skipped"
7508
8191
  ]
@@ -7655,7 +8338,8 @@
7655
8338
  "schema_version": {
7656
8339
  "type": "string",
7657
8340
  "enum": [
7658
- "1.3"
8341
+ "1.3",
8342
+ "1.4"
7659
8343
  ]
7660
8344
  },
7661
8345
  "session": {
@@ -7678,6 +8362,7 @@
7678
8362
  "enum": [
7679
8363
  "nonzero",
7680
8364
  "timeout",
8365
+ "cancelled",
7681
8366
  "spawn-error",
7682
8367
  "skipped"
7683
8368
  ]
@@ -7872,6 +8557,7 @@
7872
8557
  "enum": [
7873
8558
  "nonzero",
7874
8559
  "timeout",
8560
+ "cancelled",
7875
8561
  "spawn-error",
7876
8562
  "skipped"
7877
8563
  ]
@@ -8452,6 +9138,11 @@
8452
9138
  "type": "string",
8453
9139
  "const": "timeout"
8454
9140
  },
9141
+ {
9142
+ "description": "Terminated before it finished because the run was **cancelled** — a\nlibrary caller flipped its [`crate::io::cancel::CancelToken`], or the host\nreceived SIGINT/SIGTERM while [`crate::io::cancel::install_signal_cancel`]\nwas in force. Distinct from `timeout` (the harness was given its full\ndeadline and exceeded it) and from a consumer-driven streaming stop (which\nstays `ok`, because the consumer already got what it asked for). Any\noutput captured before the cancellation is still normalized, exactly as it\nis for a timeout.",
9143
+ "type": "string",
9144
+ "const": "cancelled"
9145
+ },
8455
9146
  {
8456
9147
  "description": "The binary was resolved but could not be executed.",
8457
9148
  "type": "string",
@@ -8728,7 +9419,10 @@
8728
9419
  },
8729
9420
  "schema_version": {
8730
9421
  "type": "string",
8731
- "const": "1.3"
9422
+ "enum": [
9423
+ "1.3",
9424
+ "1.4"
9425
+ ]
8732
9426
  }
8733
9427
  },
8734
9428
  "required": [
@@ -8745,6 +9439,7 @@
8745
9439
  "enum": [
8746
9440
  "nonzero",
8747
9441
  "timeout",
9442
+ "cancelled",
8748
9443
  "spawn-error",
8749
9444
  "skipped"
8750
9445
  ]
@@ -8767,6 +9462,37 @@
8767
9462
  ]
8768
9463
  }
8769
9464
  ]
9465
+ },
9466
+ {
9467
+ "anyOf": [
9468
+ {
9469
+ "type": "object",
9470
+ "properties": {
9471
+ "status": {
9472
+ "type": "string",
9473
+ "enum": [
9474
+ "ok",
9475
+ "nonzero",
9476
+ "timeout",
9477
+ "spawn-error",
9478
+ "skipped",
9479
+ "planned"
9480
+ ]
9481
+ }
9482
+ }
9483
+ },
9484
+ {
9485
+ "type": "object",
9486
+ "properties": {
9487
+ "schema_version": {
9488
+ "type": "string",
9489
+ "enum": [
9490
+ "1.4"
9491
+ ]
9492
+ }
9493
+ }
9494
+ }
9495
+ ]
8770
9496
  }
8771
9497
  ],
8772
9498
  "oneOf": [
@@ -9037,7 +9763,8 @@
9037
9763
  "type": "string",
9038
9764
  "enum": [
9039
9765
  "1.2",
9040
- "1.3"
9766
+ "1.3",
9767
+ "1.4"
9041
9768
  ]
9042
9769
  },
9043
9770
  "session": {
@@ -9636,7 +10363,8 @@
9636
10363
  "type": "string",
9637
10364
  "enum": [
9638
10365
  "1.2",
9639
- "1.3"
10366
+ "1.3",
10367
+ "1.4"
9640
10368
  ]
9641
10369
  },
9642
10370
  "session": {
@@ -9810,7 +10538,8 @@
9810
10538
  "type": "string",
9811
10539
  "enum": [
9812
10540
  "1.2",
9813
- "1.3"
10541
+ "1.3",
10542
+ "1.4"
9814
10543
  ]
9815
10544
  },
9816
10545
  "session": {
@@ -9982,7 +10711,8 @@
9982
10711
  "type": "string",
9983
10712
  "enum": [
9984
10713
  "1.2",
9985
- "1.3"
10714
+ "1.3",
10715
+ "1.4"
9986
10716
  ]
9987
10717
  },
9988
10718
  "session": {
@@ -10002,6 +10732,7 @@
10002
10732
  "enum": [
10003
10733
  "nonzero",
10004
10734
  "timeout",
10735
+ "cancelled",
10005
10736
  "spawn-error",
10006
10737
  "skipped"
10007
10738
  ]
@@ -10154,7 +10885,8 @@
10154
10885
  "schema_version": {
10155
10886
  "type": "string",
10156
10887
  "enum": [
10157
- "1.3"
10888
+ "1.3",
10889
+ "1.4"
10158
10890
  ]
10159
10891
  },
10160
10892
  "session": {
@@ -10177,6 +10909,7 @@
10177
10909
  "enum": [
10178
10910
  "nonzero",
10179
10911
  "timeout",
10912
+ "cancelled",
10180
10913
  "spawn-error",
10181
10914
  "skipped"
10182
10915
  ]
@@ -10371,6 +11104,7 @@
10371
11104
  "enum": [
10372
11105
  "nonzero",
10373
11106
  "timeout",
11107
+ "cancelled",
10374
11108
  "spawn-error",
10375
11109
  "skipped"
10376
11110
  ]
@@ -10951,6 +11685,11 @@
10951
11685
  "type": "string",
10952
11686
  "const": "timeout"
10953
11687
  },
11688
+ {
11689
+ "description": "Terminated before it finished because the run was **cancelled** — a\nlibrary caller flipped its [`crate::io::cancel::CancelToken`], or the host\nreceived SIGINT/SIGTERM while [`crate::io::cancel::install_signal_cancel`]\nwas in force. Distinct from `timeout` (the harness was given its full\ndeadline and exceeded it) and from a consumer-driven streaming stop (which\nstays `ok`, because the consumer already got what it asked for). Any\noutput captured before the cancellation is still normalized, exactly as it\nis for a timeout.",
11690
+ "type": "string",
11691
+ "const": "cancelled"
11692
+ },
10954
11693
  {
10955
11694
  "description": "The binary was resolved but could not be executed.",
10956
11695
  "type": "string",
@@ -11152,9 +11891,50 @@
11152
11891
  "harnesses"
11153
11892
  ],
11154
11893
  "$defs": {
11894
+ "ControlShape": {
11895
+ "description": "How a harness accepts an out-of-band interrupt for an in-flight turn.\n\nRegistry data on [`crate::domain::harness::HarnessSpec::control`], sourced\nfrom a live interrupt against the real CLI — never guessed. `None` there\nmeans `oneharness interrupt` is a loud usage error for the harness, never a\nsilent no-op: a supervisor that is told \"ok\" while the turn keeps running is\nworse off than one told the lever does not exist.",
11896
+ "oneOf": [
11897
+ {
11898
+ "description": "Claude Code: the control frame rides the run process's **own stdin**\n(`-p --input-format stream-json`), so no sidecar server is needed. A\n`{\"type\":\"control_request\",…,\"request\":{\"subtype\":\"interrupt\"}}` line\naborts the turn with a `result` document; the session survives and a\nlater turn continues it with full context. Verified live: a plain user\nmessage written mid-turn is *silently dropped*, so the control frame is\nthe only mechanism that works.",
11899
+ "type": "string",
11900
+ "const": "claude-control-request"
11901
+ },
11902
+ {
11903
+ "description": "Codex: `turn/interrupt {threadId,turnId}` over the `codex app-server`\nJSON-RPC stdio protocol, which oneharness spawns as the run's own child\nand drives the thread/turn lifecycle on — so the interrupt rides the same\nopen stdin and nothing is shared or pooled.",
11904
+ "type": "string",
11905
+ "const": "codex-app-server"
11906
+ },
11907
+ {
11908
+ "description": "OpenCode: `POST /api/session/{id}/interrupt` against `opencode serve`.",
11909
+ "type": "string",
11910
+ "const": "opencode-http"
11911
+ },
11912
+ {
11913
+ "description": "Goose and Copilot: the ACP `session/cancel` **notification** (no `id`)\nover their JSON-RPC stdio servers.",
11914
+ "type": "string",
11915
+ "const": "acp-cancel"
11916
+ },
11917
+ {
11918
+ "description": "Crush: `POST /v1/workspaces/{id}/agent/sessions/{sid}/cancel` against\n`crush server`.",
11919
+ "type": "string",
11920
+ "const": "crush-http"
11921
+ }
11922
+ ]
11923
+ },
11155
11924
  "HarnessInfo": {
11156
11925
  "type": "object",
11157
11926
  "properties": {
11927
+ "control": {
11928
+ "description": "How `run --control` + `oneharness interrupt` abort an in-flight turn on\nthis harness (the mechanism id), or `null` when it has no out-of-band\nturn control at all — in which case `--control` is a loud usage error for\nit, never a socket that reports success while the turn keeps running.",
11929
+ "anyOf": [
11930
+ {
11931
+ "$ref": "#/$defs/ControlShape"
11932
+ },
11933
+ {
11934
+ "type": "null"
11935
+ }
11936
+ ]
11937
+ },
11158
11938
  "default_bin": {
11159
11939
  "type": "string"
11160
11940
  },
@@ -11260,6 +12040,7 @@
11260
12040
  "supports_resume",
11261
12041
  "session_capable",
11262
12042
  "supports_fork",
12043
+ "control",
11263
12044
  "fork_reuses_cache",
11264
12045
  "modes",
11265
12046
  "supports_native_schema",