@oneharness/sdk 0.3.24 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -131,26 +131,26 @@
131
131
  }
132
132
  },
133
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",
134
+ "schema_version",
144
135
  "oneharness_version",
145
- "permission_mode",
146
136
  "prompt",
147
- "results",
137
+ "model",
138
+ "models",
148
139
  "resume",
140
+ "fork",
141
+ "session",
142
+ "permission_mode",
143
+ "bypass_permissions",
144
+ "dry_run",
149
145
  "schema",
150
146
  "schema_max_retries",
151
- "schema_version",
152
- "session",
153
- "spy_file"
147
+ "batch",
148
+ "fallback",
149
+ "mock_rules",
150
+ "spy_file",
151
+ "history_file",
152
+ "config_files",
153
+ "results"
154
154
  ],
155
155
  "$defs": {
156
156
  "ActionEvent": {
@@ -186,11 +186,11 @@
186
186
  }
187
187
  },
188
188
  "required": [
189
- "index",
190
- "input",
191
189
  "kind",
192
190
  "name",
193
- "output"
191
+ "input",
192
+ "output",
193
+ "index"
194
194
  ]
195
195
  },
196
196
  "BatchReport": {
@@ -213,9 +213,9 @@
213
213
  }
214
214
  },
215
215
  "required": [
216
- "forked",
216
+ "strategy",
217
217
  "prompt_count",
218
- "strategy"
218
+ "forked"
219
219
  ]
220
220
  },
221
221
  "BatchStrategy": {
@@ -301,8 +301,8 @@
301
301
  }
302
302
  },
303
303
  "required": [
304
- "fell_through",
305
- "ran"
304
+ "ran",
305
+ "fell_through"
306
306
  ]
307
307
  },
308
308
  "OutputFormat": {
@@ -530,32 +530,32 @@
530
530
  }
531
531
  },
532
532
  "required": [
533
- "available",
533
+ "harness",
534
534
  "bin",
535
- "command",
535
+ "available",
536
+ "status",
537
+ "prompt",
538
+ "model",
539
+ "exit_code",
536
540
  "duration_ms",
537
- "error",
541
+ "command",
542
+ "output_format",
543
+ "text",
544
+ "text_source",
545
+ "usage",
546
+ "usage_source",
547
+ "session_id",
538
548
  "events",
539
549
  "events_source",
540
- "exit_code",
541
- "failure_kind",
542
- "failure_kind_source",
543
- "harness",
544
- "model",
545
- "output_format",
546
- "prompt",
550
+ "structured",
551
+ "schema_valid",
547
552
  "schema_attempts",
548
553
  "schema_error",
549
- "schema_valid",
550
- "session_id",
551
- "status",
552
- "stderr",
554
+ "failure_kind",
555
+ "failure_kind_source",
553
556
  "stdout",
554
- "structured",
555
- "text",
556
- "text_source",
557
- "usage",
558
- "usage_source"
557
+ "stderr",
558
+ "error"
559
559
  ]
560
560
  },
561
561
  "SessionPhase": {
@@ -603,8 +603,8 @@
603
603
  "required": [
604
604
  "name",
605
605
  "phase",
606
- "store_file",
607
- "token"
606
+ "token",
607
+ "store_file"
608
608
  ]
609
609
  },
610
610
  "Status": {
@@ -692,186 +692,949 @@
692
692
  }
693
693
  },
694
694
  "required": [
695
+ "input_tokens",
696
+ "output_tokens",
695
697
  "cache_read_tokens",
696
698
  "cache_write_tokens",
697
- "cost_usd",
698
- "input_tokens",
699
- "output_tokens"
699
+ "cost_usd"
700
700
  ]
701
701
  }
702
702
  }
703
703
  },
704
- "run_options": {
704
+ "run_stream_envelope": {
705
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": {
706
+ "title": "RunStreamEnvelope",
707
+ "description": "One line of `oneharness run --stream` output.\n\nEvent lines carry normalized actions as they arrive. Exactly one terminal\nresult line carries the complete report unless the consumer closes the\nstream early. This is an output contract, so deserialization deliberately\ntolerates additive fields from newer producers.",
708
+ "oneOf": [
709
+ {
710
+ "description": "A normalized action observed while the harness is still running.",
720
711
  "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"
712
+ "properties": {
713
+ "event": {
714
+ "$ref": "#/$defs/ActionEvent"
810
715
  },
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`, …)",
716
+ "type": {
813
717
  "type": "string",
814
- "const": "bypass"
718
+ "const": "event"
815
719
  }
720
+ },
721
+ "required": [
722
+ "type",
723
+ "event"
816
724
  ]
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
725
  },
829
726
  {
830
- "description": "Select the session named by `session`.",
831
- "$ref": "#/$defs/HistoryLookupBySession"
727
+ "description": "The complete report that terminates a normally consumed stream.",
728
+ "type": "object",
729
+ "properties": {
730
+ "report": {
731
+ "$ref": "#/$defs/RunReport"
732
+ },
733
+ "type": {
734
+ "type": "string",
735
+ "const": "result"
736
+ }
737
+ },
738
+ "required": [
739
+ "type",
740
+ "report"
741
+ ]
832
742
  }
833
743
  ],
834
744
  "$defs": {
835
- "HistoryLookupByLast": {
836
- "description": "A [`HistoryLookup`] that selects the most recent session.",
745
+ "ActionEvent": {
746
+ "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.",
837
747
  "type": "object",
838
748
  "properties": {
839
- "allProjects": {
840
- "type": "boolean"
841
- },
842
- "historyDir": {
843
- "type": "string"
749
+ "index": {
750
+ "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).",
751
+ "type": "integer",
752
+ "format": "uint",
753
+ "minimum": 0
844
754
  },
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
755
+ "input": {
756
+ "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`)."
849
757
  },
850
- "project": {
758
+ "kind": {
759
+ "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.",
851
760
  "type": "string"
852
761
  },
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"
762
+ "name": {
763
+ "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.",
764
+ "type": [
765
+ "string",
766
+ "null"
767
+ ]
768
+ },
769
+ "output": {
770
+ "description": "The result/observation text, when the trace exposes it; `null` otherwise.",
771
+ "type": [
772
+ "string",
773
+ "null"
774
+ ]
856
775
  }
857
776
  },
858
- "additionalProperties": false,
859
777
  "required": [
860
- "last"
778
+ "kind",
779
+ "name",
780
+ "input",
781
+ "output",
782
+ "index"
861
783
  ]
862
784
  },
863
- "HistoryLookupBySession": {
864
- "description": "A [`HistoryLookup`] that names its session.",
785
+ "BatchReport": {
786
+ "description": "Metadata for a same-prefix batch run (one harness, N prompts sharing a\ncacheable prefix). Present on [`RunReport::batch`] only in that mode.",
865
787
  "type": "object",
866
788
  "properties": {
867
- "allProjects": {
789
+ "forked": {
790
+ "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.",
868
791
  "type": "boolean"
869
792
  },
870
- "historyDir": {
871
- "type": "string"
793
+ "prompt_count": {
794
+ "description": "How many prompts were run (equals `results.len()`).",
795
+ "type": "integer",
796
+ "format": "uint",
797
+ "minimum": 0
872
798
  },
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.",
799
+ "strategy": {
800
+ "description": "How the prompts were scheduled across the parallel runner.",
801
+ "$ref": "#/$defs/BatchStrategy"
802
+ }
803
+ },
804
+ "required": [
805
+ "strategy",
806
+ "prompt_count",
807
+ "forked"
808
+ ]
809
+ },
810
+ "BatchStrategy": {
811
+ "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`.",
812
+ "oneOf": [
813
+ {
814
+ "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.",
815
+ "type": "string",
816
+ "const": "speed"
817
+ },
818
+ {
819
+ "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.",
820
+ "type": "string",
821
+ "const": "min-tokens"
822
+ }
823
+ ]
824
+ },
825
+ "FailureKind": {
826
+ "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.",
827
+ "oneOf": [
828
+ {
829
+ "description": "Authentication / authorization rejected the request (401/403, missing or\ninvalid credentials).",
830
+ "type": "string",
831
+ "const": "auth"
832
+ },
833
+ {
834
+ "description": "Rate limited (429, too many requests) — a transient condition of an\notherwise working, authenticated harness.",
835
+ "type": "string",
836
+ "const": "rate_limit"
837
+ },
838
+ {
839
+ "description": "The requested model was not found / is invalid — a configuration mistake.",
840
+ "type": "string",
841
+ "const": "model_not_found"
842
+ },
843
+ {
844
+ "description": "Out of quota / credits, or a billing problem — a provisioning failure.",
845
+ "type": "string",
846
+ "const": "quota"
847
+ },
848
+ {
849
+ "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.",
850
+ "type": "string",
851
+ "const": "tool_deferred"
852
+ }
853
+ ]
854
+ },
855
+ "FallThrough": {
856
+ "description": "One candidate a fallback run fell through, with the reason it could not run.",
857
+ "type": "object",
858
+ "properties": {
859
+ "harness": {
860
+ "description": "Canonical harness id.",
861
+ "type": "string"
862
+ },
863
+ "reason": {
864
+ "description": "Short reason token (`not-installed` / `spawn-error` / `auth` / `quota` /\n`model-not-found` / `rate-limit`).",
865
+ "type": "string"
866
+ }
867
+ },
868
+ "required": [
869
+ "harness",
870
+ "reason"
871
+ ]
872
+ },
873
+ "FallbackReport": {
874
+ "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.",
875
+ "type": "object",
876
+ "properties": {
877
+ "fell_through": {
878
+ "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`]).",
879
+ "type": "array",
880
+ "items": {
881
+ "$ref": "#/$defs/FallThrough"
882
+ }
883
+ },
884
+ "ran": {
885
+ "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.",
886
+ "type": [
887
+ "string",
888
+ "null"
889
+ ]
890
+ }
891
+ },
892
+ "required": [
893
+ "ran",
894
+ "fell_through"
895
+ ]
896
+ },
897
+ "OutputFormat": {
898
+ "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`.",
899
+ "oneOf": [
900
+ {
901
+ "description": "Plain text on stdout; `text` is the trimmed stdout.",
902
+ "type": "string",
903
+ "const": "text"
904
+ },
905
+ {
906
+ "description": "A single JSON document on stdout.",
907
+ "type": "string",
908
+ "const": "json"
909
+ },
910
+ {
911
+ "description": "Line-delimited JSON events on stdout.",
912
+ "type": "string",
913
+ "const": "stream-json"
914
+ }
915
+ ]
916
+ },
917
+ "PermissionMode": {
918
+ "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.",
919
+ "oneOf": [
920
+ {
921
+ "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`].",
922
+ "type": "string",
923
+ "const": "read-only"
924
+ },
925
+ {
926
+ "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.",
927
+ "type": "string",
928
+ "const": "plan"
929
+ },
930
+ {
931
+ "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`].",
932
+ "type": "string",
933
+ "const": "default"
934
+ },
935
+ {
936
+ "description": "Auto-approve file edits, still gate shell/commands. (Claude's\n`acceptEdits`, Qwen's `auto-edit`.)",
937
+ "type": "string",
938
+ "const": "edit"
939
+ },
940
+ {
941
+ "description": "Auto-approve operations the harness deems safe (a classifier), gate the\nrisky ones. (Claude's `auto`, Qwen's `auto`, Goose's `smart_approve`.)",
942
+ "type": "string",
943
+ "const": "auto"
944
+ },
945
+ {
946
+ "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`, …)",
947
+ "type": "string",
948
+ "const": "bypass"
949
+ }
950
+ ]
951
+ },
952
+ "RunReport": {
953
+ "description": "The top-level `run` report written to stdout.",
954
+ "type": "object",
955
+ "properties": {
956
+ "batch": {
957
+ "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`.",
958
+ "anyOf": [
959
+ {
960
+ "$ref": "#/$defs/BatchReport"
961
+ },
962
+ {
963
+ "type": "null"
964
+ }
965
+ ]
966
+ },
967
+ "bypass_permissions": {
968
+ "description": "Back-compat convenience: `true` exactly when `permission_mode` is\n`bypass`. Retained so existing consumers keep working; new consumers\nshould read `permission_mode`.",
969
+ "type": "boolean"
970
+ },
971
+ "config_files": {
972
+ "description": "Config files that shaped this run, in layering order (user first,\nproject last); empty under `--no-config` or when none exist.",
973
+ "type": "array",
974
+ "items": {
975
+ "type": "string"
976
+ }
977
+ },
978
+ "dry_run": {
979
+ "type": "boolean"
980
+ },
981
+ "fallback": {
982
+ "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.",
983
+ "anyOf": [
984
+ {
985
+ "$ref": "#/$defs/FallbackReport"
986
+ },
987
+ {
988
+ "type": "null"
989
+ }
990
+ ]
991
+ },
992
+ "fork": {
993
+ "description": "Whether the resumed session was forked (`--fork`) rather than appended to.\n`false` unless `--resume` was given with `--fork`.",
994
+ "type": "boolean"
995
+ },
996
+ "history_file": {
997
+ "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`.",
998
+ "type": [
999
+ "string",
1000
+ "null"
1001
+ ]
1002
+ },
1003
+ "mock_rules": {
1004
+ "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."
1005
+ },
1006
+ "model": {
1007
+ "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.",
1008
+ "type": [
1009
+ "string",
1010
+ "null"
1011
+ ]
1012
+ },
1013
+ "models": {
1014
+ "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).",
1015
+ "type": [
1016
+ "array",
1017
+ "null"
1018
+ ],
1019
+ "items": {
1020
+ "type": "string"
1021
+ }
1022
+ },
1023
+ "oneharness_version": {
1024
+ "type": "string"
1025
+ },
1026
+ "permission_mode": {
1027
+ "description": "The normalized approval mode requested for this run (see the README\nsupport matrix). Each harness maps it to its own mechanism.",
1028
+ "$ref": "#/$defs/PermissionMode"
1029
+ },
1030
+ "prompt": {
1031
+ "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.",
1032
+ "type": "string"
1033
+ },
1034
+ "results": {
1035
+ "type": "array",
1036
+ "items": {
1037
+ "$ref": "#/$defs/RunResult"
1038
+ }
1039
+ },
1040
+ "resume": {
1041
+ "description": "The session id being continued, when `--resume` was passed; else `null`.",
1042
+ "type": [
1043
+ "string",
1044
+ "null"
1045
+ ]
1046
+ },
1047
+ "schema": {
1048
+ "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."
1049
+ },
1050
+ "schema_max_retries": {
1051
+ "description": "Maximum retries allowed per harness under the validate/retry loop; `null`\nwhen no schema was requested.",
1052
+ "type": [
1053
+ "integer",
1054
+ "null"
1055
+ ],
1056
+ "format": "uint32",
1057
+ "minimum": 0
1058
+ },
1059
+ "schema_version": {
1060
+ "type": "string"
1061
+ },
1062
+ "session": {
1063
+ "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.",
1064
+ "anyOf": [
1065
+ {
1066
+ "$ref": "#/$defs/SessionReport"
1067
+ },
1068
+ {
1069
+ "type": "null"
1070
+ }
1071
+ ]
1072
+ },
1073
+ "spy_file": {
1074
+ "description": "The spy-log path the mock hook appended tool-call records to (absolute);\n`null` when none was requested.",
1075
+ "type": [
1076
+ "string",
1077
+ "null"
1078
+ ]
1079
+ }
1080
+ },
1081
+ "required": [
1082
+ "schema_version",
1083
+ "oneharness_version",
1084
+ "prompt",
1085
+ "model",
1086
+ "models",
1087
+ "resume",
1088
+ "fork",
1089
+ "session",
1090
+ "permission_mode",
1091
+ "bypass_permissions",
1092
+ "dry_run",
1093
+ "schema",
1094
+ "schema_max_retries",
1095
+ "batch",
1096
+ "fallback",
1097
+ "mock_rules",
1098
+ "spy_file",
1099
+ "history_file",
1100
+ "config_files",
1101
+ "results"
1102
+ ]
1103
+ },
1104
+ "RunResult": {
1105
+ "description": "One harness's entry in the report.",
1106
+ "type": "object",
1107
+ "properties": {
1108
+ "available": {
1109
+ "description": "Whether that binary was found.",
1110
+ "type": "boolean"
1111
+ },
1112
+ "bin": {
1113
+ "description": "The binary name or path oneharness resolved and would invoke.",
1114
+ "type": "string"
1115
+ },
1116
+ "command": {
1117
+ "description": "The exact argv oneharness built (argv[0] is the binary).",
1118
+ "type": "array",
1119
+ "items": {
1120
+ "type": "string"
1121
+ }
1122
+ },
1123
+ "duration_ms": {
1124
+ "description": "Wall-clock duration of the run; `null` when not executed.",
1125
+ "type": [
1126
+ "integer",
1127
+ "null"
1128
+ ],
1129
+ "format": "uint128",
1130
+ "minimum": 0
1131
+ },
1132
+ "error": {
1133
+ "description": "Human-readable problem + suggested action; `null` on success.",
1134
+ "type": [
1135
+ "string",
1136
+ "null"
1137
+ ]
1138
+ },
1139
+ "events": {
1140
+ "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`].",
1141
+ "type": [
1142
+ "array",
1143
+ "null"
1144
+ ],
1145
+ "items": {
1146
+ "$ref": "#/$defs/ActionEvent"
1147
+ }
1148
+ },
1149
+ "events_source": {
1150
+ "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.\"",
1151
+ "type": [
1152
+ "string",
1153
+ "null"
1154
+ ]
1155
+ },
1156
+ "exit_code": {
1157
+ "description": "Process exit code; `null` when not run, timed out, or signalled.",
1158
+ "type": [
1159
+ "integer",
1160
+ "null"
1161
+ ],
1162
+ "format": "int32"
1163
+ },
1164
+ "failure_kind": {
1165
+ "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`]).",
1166
+ "anyOf": [
1167
+ {
1168
+ "$ref": "#/$defs/FailureKind"
1169
+ },
1170
+ {
1171
+ "type": "null"
1172
+ }
1173
+ ]
1174
+ },
1175
+ "failure_kind_source": {
1176
+ "description": "Where `failure_kind` was read (`stderr`/`stdout`); `null` when absent.",
1177
+ "type": [
1178
+ "string",
1179
+ "null"
1180
+ ]
1181
+ },
1182
+ "harness": {
1183
+ "description": "Canonical harness id (e.g. `claude-code`).",
1184
+ "type": "string"
1185
+ },
1186
+ "model": {
1187
+ "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.",
1188
+ "type": [
1189
+ "string",
1190
+ "null"
1191
+ ]
1192
+ },
1193
+ "output_format": {
1194
+ "$ref": "#/$defs/OutputFormat"
1195
+ },
1196
+ "prompt": {
1197
+ "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.",
1198
+ "type": [
1199
+ "string",
1200
+ "null"
1201
+ ]
1202
+ },
1203
+ "schema_attempts": {
1204
+ "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.",
1205
+ "type": [
1206
+ "integer",
1207
+ "null"
1208
+ ],
1209
+ "format": "uint32",
1210
+ "minimum": 0
1211
+ },
1212
+ "schema_error": {
1213
+ "description": "Structured-output run only: the validation errors from the final attempt,\njoined for display; `null` when valid or no schema was requested.",
1214
+ "type": [
1215
+ "string",
1216
+ "null"
1217
+ ]
1218
+ },
1219
+ "schema_valid": {
1220
+ "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\").",
1221
+ "type": [
1222
+ "boolean",
1223
+ "null"
1224
+ ]
1225
+ },
1226
+ "session_id": {
1227
+ "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`]).",
1228
+ "type": [
1229
+ "string",
1230
+ "null"
1231
+ ]
1232
+ },
1233
+ "status": {
1234
+ "$ref": "#/$defs/Status"
1235
+ },
1236
+ "stderr": {
1237
+ "description": "Raw captured stderr (empty for skipped/planned).",
1238
+ "type": "string"
1239
+ },
1240
+ "stdout": {
1241
+ "description": "Raw captured stdout (empty for skipped/planned).",
1242
+ "type": "string"
1243
+ },
1244
+ "structured": {
1245
+ "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."
1246
+ },
1247
+ "text": {
1248
+ "description": "Best-effort final assistant text; `null` when extraction is impossible.",
1249
+ "type": [
1250
+ "string",
1251
+ "null"
1252
+ ]
1253
+ },
1254
+ "text_source": {
1255
+ "description": "How `text` was extracted (e.g. `json:result`, `raw`); `null` when absent.",
1256
+ "type": [
1257
+ "string",
1258
+ "null"
1259
+ ]
1260
+ },
1261
+ "usage": {
1262
+ "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.",
1263
+ "$ref": "#/$defs/Usage"
1264
+ },
1265
+ "usage_source": {
1266
+ "description": "How `usage` was read (e.g. `json`); `null` when nothing was found.",
1267
+ "type": [
1268
+ "string",
1269
+ "null"
1270
+ ]
1271
+ }
1272
+ },
1273
+ "required": [
1274
+ "harness",
1275
+ "bin",
1276
+ "available",
1277
+ "status",
1278
+ "prompt",
1279
+ "model",
1280
+ "exit_code",
1281
+ "duration_ms",
1282
+ "command",
1283
+ "output_format",
1284
+ "text",
1285
+ "text_source",
1286
+ "usage",
1287
+ "usage_source",
1288
+ "session_id",
1289
+ "events",
1290
+ "events_source",
1291
+ "structured",
1292
+ "schema_valid",
1293
+ "schema_attempts",
1294
+ "schema_error",
1295
+ "failure_kind",
1296
+ "failure_kind_source",
1297
+ "stdout",
1298
+ "stderr",
1299
+ "error"
1300
+ ]
1301
+ },
1302
+ "SessionPhase": {
1303
+ "description": "Whether a run starts a new named session or continues an existing one.",
1304
+ "oneOf": [
1305
+ {
1306
+ "description": "No stored token yet — the harness runs fresh and its emitted session id is\ncaptured for next time.",
1307
+ "type": "string",
1308
+ "const": "create"
1309
+ },
1310
+ {
1311
+ "description": "A stored token exists — the run resumes it.",
1312
+ "type": "string",
1313
+ "const": "continue"
1314
+ }
1315
+ ]
1316
+ },
1317
+ "SessionReport": {
1318
+ "description": "The uniform session handle for a run (`--session`). Present on\n[`RunReport::session`] only when `--session <name>` was requested.",
1319
+ "type": "object",
1320
+ "properties": {
1321
+ "name": {
1322
+ "description": "The caller's stable handle (`--session <name>`, sanitized for the store).",
1323
+ "type": "string"
1324
+ },
1325
+ "phase": {
1326
+ "description": "Whether this run created the named session (no prior token) or continued\nan existing one.",
1327
+ "$ref": "#/$defs/SessionPhase"
1328
+ },
1329
+ "store_file": {
1330
+ "description": "The session store file backing the handle (absolute); the programmatic\nhandle to the persisted state.",
1331
+ "type": [
1332
+ "string",
1333
+ "null"
1334
+ ]
1335
+ },
1336
+ "token": {
1337
+ "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).",
1338
+ "type": [
1339
+ "string",
1340
+ "null"
1341
+ ]
1342
+ }
1343
+ },
1344
+ "required": [
1345
+ "name",
1346
+ "phase",
1347
+ "token",
1348
+ "store_file"
1349
+ ]
1350
+ },
1351
+ "Status": {
1352
+ "description": "The outcome of attempting to run one harness.",
1353
+ "oneOf": [
1354
+ {
1355
+ "description": "Spawned and exited 0.",
1356
+ "type": "string",
1357
+ "const": "ok"
1358
+ },
1359
+ {
1360
+ "description": "Spawned and exited non-zero.",
1361
+ "type": "string",
1362
+ "const": "nonzero"
1363
+ },
1364
+ {
1365
+ "description": "Killed after exceeding the per-harness timeout.",
1366
+ "type": "string",
1367
+ "const": "timeout"
1368
+ },
1369
+ {
1370
+ "description": "The binary was resolved but could not be executed.",
1371
+ "type": "string",
1372
+ "const": "spawn-error"
1373
+ },
1374
+ {
1375
+ "description": "The binary was not found, so the harness was not run.",
1376
+ "type": "string",
1377
+ "const": "skipped"
1378
+ },
1379
+ {
1380
+ "description": "`--print-command`: the command was built but not executed.",
1381
+ "type": "string",
1382
+ "const": "planned"
1383
+ }
1384
+ ]
1385
+ },
1386
+ "Usage": {
1387
+ "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).",
1388
+ "type": "object",
1389
+ "properties": {
1390
+ "cache_read_tokens": {
1391
+ "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.",
1392
+ "type": [
1393
+ "integer",
1394
+ "null"
1395
+ ],
1396
+ "format": "uint64",
1397
+ "minimum": 0
1398
+ },
1399
+ "cache_write_tokens": {
1400
+ "description": "Prompt tokens written to the provider's prompt cache (a.k.a. cache\ncreation), when the harness reports them. `None` when not surfaced.",
1401
+ "type": [
1402
+ "integer",
1403
+ "null"
1404
+ ],
1405
+ "format": "uint64",
1406
+ "minimum": 0
1407
+ },
1408
+ "cost_usd": {
1409
+ "description": "Total cost in USD, when the harness reports it (often absent on\nsubscription auth, where there is no per-call dollar figure).",
1410
+ "type": [
1411
+ "number",
1412
+ "null"
1413
+ ],
1414
+ "format": "double"
1415
+ },
1416
+ "input_tokens": {
1417
+ "description": "Prompt/input tokens billed, when the harness reports them.",
1418
+ "type": [
1419
+ "integer",
1420
+ "null"
1421
+ ],
1422
+ "format": "uint64",
1423
+ "minimum": 0
1424
+ },
1425
+ "output_tokens": {
1426
+ "description": "Completion/output tokens billed, when the harness reports them.",
1427
+ "type": [
1428
+ "integer",
1429
+ "null"
1430
+ ],
1431
+ "format": "uint64",
1432
+ "minimum": 0
1433
+ }
1434
+ },
1435
+ "required": [
1436
+ "input_tokens",
1437
+ "output_tokens",
1438
+ "cache_read_tokens",
1439
+ "cache_write_tokens",
1440
+ "cost_usd"
1441
+ ]
1442
+ }
1443
+ }
1444
+ },
1445
+ "run_options": {
1446
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
1447
+ "title": "RunOptions",
1448
+ "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.",
1449
+ "type": "object",
1450
+ "properties": {
1451
+ "bins": {
1452
+ "type": "object",
1453
+ "additionalProperties": {
1454
+ "type": "string"
1455
+ }
1456
+ },
1457
+ "cwd": {
1458
+ "type": "string"
1459
+ },
1460
+ "env": {
1461
+ "type": "object",
1462
+ "additionalProperties": {
1463
+ "type": "string"
1464
+ }
1465
+ },
1466
+ "events": {
1467
+ "type": "boolean"
1468
+ },
1469
+ "fork": {
1470
+ "type": "boolean"
1471
+ },
1472
+ "harnesses": {
1473
+ "type": "array",
1474
+ "items": {
1475
+ "type": "string"
1476
+ }
1477
+ },
1478
+ "history": {
1479
+ "type": "boolean"
1480
+ },
1481
+ "historyDir": {
1482
+ "type": "string"
1483
+ },
1484
+ "historyLabels": {
1485
+ "$ref": "#/$defs/HistoryLabels"
1486
+ },
1487
+ "historyName": {
1488
+ "type": "string"
1489
+ },
1490
+ "mode": {
1491
+ "$ref": "#/$defs/PermissionMode"
1492
+ },
1493
+ "models": {
1494
+ "type": "array",
1495
+ "items": {
1496
+ "type": "string"
1497
+ }
1498
+ },
1499
+ "prompt": {
1500
+ "description": "The user message sent to the selected harnesses.",
1501
+ "type": "string",
1502
+ "minLength": 1
1503
+ },
1504
+ "reasoning": {
1505
+ "type": "string"
1506
+ },
1507
+ "resume": {
1508
+ "type": "string"
1509
+ },
1510
+ "session": {
1511
+ "type": "string"
1512
+ },
1513
+ "system": {
1514
+ "type": "string"
1515
+ },
1516
+ "timeoutSeconds": {
1517
+ "type": "integer",
1518
+ "format": "uint64",
1519
+ "minimum": 0
1520
+ }
1521
+ },
1522
+ "additionalProperties": false,
1523
+ "required": [
1524
+ "prompt"
1525
+ ],
1526
+ "$defs": {
1527
+ "HistoryLabels": {
1528
+ "type": "object",
1529
+ "additionalProperties": {
1530
+ "type": "string",
1531
+ "maxLength": 256,
1532
+ "minLength": 1,
1533
+ "not": {
1534
+ "pattern": "[\\u0000-\\u001f\\u007f-\\u009f]"
1535
+ }
1536
+ },
1537
+ "propertyNames": {
1538
+ "type": "string",
1539
+ "maxLength": 64,
1540
+ "not": {
1541
+ "pattern": "[^A-Za-z0-9._-]"
1542
+ },
1543
+ "pattern": "^[A-Za-z0-9]"
1544
+ }
1545
+ },
1546
+ "PermissionMode": {
1547
+ "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.",
1548
+ "oneOf": [
1549
+ {
1550
+ "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`].",
1551
+ "type": "string",
1552
+ "const": "read-only"
1553
+ },
1554
+ {
1555
+ "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.",
1556
+ "type": "string",
1557
+ "const": "plan"
1558
+ },
1559
+ {
1560
+ "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`].",
1561
+ "type": "string",
1562
+ "const": "default"
1563
+ },
1564
+ {
1565
+ "description": "Auto-approve file edits, still gate shell/commands. (Claude's\n`acceptEdits`, Qwen's `auto-edit`.)",
1566
+ "type": "string",
1567
+ "const": "edit"
1568
+ },
1569
+ {
1570
+ "description": "Auto-approve operations the harness deems safe (a classifier), gate the\nrisky ones. (Claude's `auto`, Qwen's `auto`, Goose's `smart_approve`.)",
1571
+ "type": "string",
1572
+ "const": "auto"
1573
+ },
1574
+ {
1575
+ "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`, …)",
1576
+ "type": "string",
1577
+ "const": "bypass"
1578
+ }
1579
+ ]
1580
+ }
1581
+ }
1582
+ },
1583
+ "history_lookup": {
1584
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
1585
+ "title": "HistoryLookup",
1586
+ "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.",
1587
+ "anyOf": [
1588
+ {
1589
+ "description": "Select the most recent session.",
1590
+ "$ref": "#/$defs/HistoryLookupByLast"
1591
+ },
1592
+ {
1593
+ "description": "Select the session named by `session`.",
1594
+ "$ref": "#/$defs/HistoryLookupBySession"
1595
+ }
1596
+ ],
1597
+ "$defs": {
1598
+ "HistoryLookupByLast": {
1599
+ "description": "A [`HistoryLookup`] that selects the most recent session.",
1600
+ "type": "object",
1601
+ "properties": {
1602
+ "allProjects": {
1603
+ "type": "boolean"
1604
+ },
1605
+ "historyDir": {
1606
+ "type": "string"
1607
+ },
1608
+ "last": {
1609
+ "description": "Select the most recent session. Only `true` selects, so this variant\naccepts no other value.",
1610
+ "type": "boolean",
1611
+ "const": true
1612
+ },
1613
+ "project": {
1614
+ "type": "string"
1615
+ },
1616
+ "session": {
1617
+ "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.",
1618
+ "type": "string"
1619
+ }
1620
+ },
1621
+ "additionalProperties": false,
1622
+ "required": [
1623
+ "last"
1624
+ ]
1625
+ },
1626
+ "HistoryLookupBySession": {
1627
+ "description": "A [`HistoryLookup`] that names its session.",
1628
+ "type": "object",
1629
+ "properties": {
1630
+ "allProjects": {
1631
+ "type": "boolean"
1632
+ },
1633
+ "historyDir": {
1634
+ "type": "string"
1635
+ },
1636
+ "last": {
1637
+ "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
1638
  "type": "boolean"
876
1639
  },
877
1640
  "project": {
@@ -890,23 +1653,71 @@
890
1653
  }
891
1654
  }
892
1655
  },
893
- "history_list_options": {
1656
+ "history_list_options": {
1657
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
1658
+ "title": "HistoryListOptions",
1659
+ "description": "Options accepted by `OneHarness.historyList()` in the published Node SDK.",
1660
+ "type": "object",
1661
+ "properties": {
1662
+ "allProjects": {
1663
+ "type": "boolean"
1664
+ },
1665
+ "historyDir": {
1666
+ "type": "string"
1667
+ },
1668
+ "project": {
1669
+ "type": "string"
1670
+ }
1671
+ },
1672
+ "additionalProperties": false
1673
+ },
1674
+ "history_watch_options": {
894
1675
  "$schema": "https://json-schema.org/draft/2020-12/schema",
895
- "title": "HistoryListOptions",
896
- "description": "Options accepted by `OneHarness.historyList()` in the published Node SDK.",
1676
+ "title": "HistoryWatchOptions",
1677
+ "description": "Options accepted by the language SDKs' continuous history iterators.\n\nThe CLI spells `labels` as repeated `--label key=value` arguments, while an\nSDK can expose the validated map directly. Unknown fields remain a boundary\nerror, as they are for every other SDK input contract.",
897
1678
  "type": "object",
898
1679
  "properties": {
1680
+ "after": {
1681
+ "type": "string",
1682
+ "maxLength": 36,
1683
+ "minLength": 36,
1684
+ "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
1685
+ },
899
1686
  "allProjects": {
900
1687
  "type": "boolean"
901
1688
  },
902
1689
  "historyDir": {
903
1690
  "type": "string"
904
1691
  },
1692
+ "labels": {
1693
+ "$ref": "#/$defs/HistoryLabels"
1694
+ },
905
1695
  "project": {
906
1696
  "type": "string"
907
1697
  }
908
1698
  },
909
- "additionalProperties": false
1699
+ "additionalProperties": false,
1700
+ "$defs": {
1701
+ "HistoryLabels": {
1702
+ "type": "object",
1703
+ "additionalProperties": {
1704
+ "type": "string",
1705
+ "maxLength": 256,
1706
+ "minLength": 1,
1707
+ "not": {
1708
+ "pattern": "[\\u0000-\\u001f\\u007f-\\u009f]"
1709
+ }
1710
+ },
1711
+ "propertyNames": {
1712
+ "type": "string",
1713
+ "maxLength": 64,
1714
+ "not": {
1715
+ "pattern": "[^A-Za-z0-9._-]"
1716
+ },
1717
+ "pattern": "^[A-Za-z0-9]"
1718
+ }
1719
+ }
1720
+ }
910
1721
  },
911
1722
  "history_record": {
912
1723
  "$schema": "https://json-schema.org/draft/2020-12/schema",
@@ -954,6 +1765,17 @@
954
1765
  "description": "Canonical harness id (e.g. `claude-code`).",
955
1766
  "type": "string"
956
1767
  },
1768
+ "history_id": {
1769
+ "description": "Globally unique, time-ordered record id. This is also the cursor accepted\nby `history watch --after` and the exact id accepted by history lookup.",
1770
+ "type": "string",
1771
+ "maxLength": 36,
1772
+ "minLength": 36,
1773
+ "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
1774
+ },
1775
+ "labels": {
1776
+ "description": "Caller-supplied metadata used to select related task-graph records.\nOmitted on the wire when empty for additive compatibility.",
1777
+ "$ref": "#/$defs/HistoryLabels"
1778
+ },
957
1779
  "model": {
958
1780
  "description": "The effective top-level model for the run, if any.",
959
1781
  "type": [
@@ -1000,42 +1822,285 @@
1000
1822
  "string",
1001
1823
  "null"
1002
1824
  ]
1003
- },
1004
- "text_source": {
1005
- "description": "How `text` was extracted; `null` when absent.",
1006
- "type": [
1007
- "string",
1008
- "null"
1825
+ },
1826
+ "text_source": {
1827
+ "description": "How `text` was extracted; `null` when absent.",
1828
+ "type": [
1829
+ "string",
1830
+ "null"
1831
+ ]
1832
+ },
1833
+ "timestamp": {
1834
+ "description": "RFC3339 UTC instant the record was written (append time).",
1835
+ "type": "string"
1836
+ },
1837
+ "usage": {
1838
+ "description": "Best-effort token/cost accounting (every field `null` when unreported).",
1839
+ "$ref": "#/$defs/Usage"
1840
+ }
1841
+ },
1842
+ "required": [
1843
+ "schema_version",
1844
+ "history_id",
1845
+ "session",
1846
+ "name",
1847
+ "project",
1848
+ "timestamp",
1849
+ "harness",
1850
+ "model",
1851
+ "prompt",
1852
+ "permission_mode",
1853
+ "status",
1854
+ "exit_code",
1855
+ "duration_ms",
1856
+ "text",
1857
+ "text_source",
1858
+ "usage",
1859
+ "session_id",
1860
+ "events",
1861
+ "failure_kind"
1862
+ ],
1863
+ "$defs": {
1864
+ "ActionEvent": {
1865
+ "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.",
1866
+ "type": "object",
1867
+ "properties": {
1868
+ "index": {
1869
+ "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).",
1870
+ "type": "integer",
1871
+ "format": "uint",
1872
+ "minimum": 0
1873
+ },
1874
+ "input": {
1875
+ "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`)."
1876
+ },
1877
+ "kind": {
1878
+ "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.",
1879
+ "type": "string"
1880
+ },
1881
+ "name": {
1882
+ "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.",
1883
+ "type": [
1884
+ "string",
1885
+ "null"
1886
+ ]
1887
+ },
1888
+ "output": {
1889
+ "description": "The result/observation text, when the trace exposes it; `null` otherwise.",
1890
+ "type": [
1891
+ "string",
1892
+ "null"
1893
+ ]
1894
+ }
1895
+ },
1896
+ "required": [
1897
+ "kind",
1898
+ "name",
1899
+ "input",
1900
+ "output",
1901
+ "index"
1902
+ ]
1903
+ },
1904
+ "FailureKind": {
1905
+ "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.",
1906
+ "oneOf": [
1907
+ {
1908
+ "description": "Authentication / authorization rejected the request (401/403, missing or\ninvalid credentials).",
1909
+ "type": "string",
1910
+ "const": "auth"
1911
+ },
1912
+ {
1913
+ "description": "Rate limited (429, too many requests) — a transient condition of an\notherwise working, authenticated harness.",
1914
+ "type": "string",
1915
+ "const": "rate_limit"
1916
+ },
1917
+ {
1918
+ "description": "The requested model was not found / is invalid — a configuration mistake.",
1919
+ "type": "string",
1920
+ "const": "model_not_found"
1921
+ },
1922
+ {
1923
+ "description": "Out of quota / credits, or a billing problem — a provisioning failure.",
1924
+ "type": "string",
1925
+ "const": "quota"
1926
+ },
1927
+ {
1928
+ "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.",
1929
+ "type": "string",
1930
+ "const": "tool_deferred"
1931
+ }
1932
+ ]
1933
+ },
1934
+ "HistoryLabels": {
1935
+ "type": "object",
1936
+ "additionalProperties": {
1937
+ "type": "string",
1938
+ "maxLength": 256,
1939
+ "minLength": 1,
1940
+ "not": {
1941
+ "pattern": "[\\u0000-\\u001f\\u007f-\\u009f]"
1942
+ }
1943
+ },
1944
+ "propertyNames": {
1945
+ "type": "string",
1946
+ "maxLength": 64,
1947
+ "not": {
1948
+ "pattern": "[^A-Za-z0-9._-]"
1949
+ },
1950
+ "pattern": "^[A-Za-z0-9]"
1951
+ }
1952
+ },
1953
+ "PermissionMode": {
1954
+ "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.",
1955
+ "oneOf": [
1956
+ {
1957
+ "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`].",
1958
+ "type": "string",
1959
+ "const": "read-only"
1960
+ },
1961
+ {
1962
+ "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.",
1963
+ "type": "string",
1964
+ "const": "plan"
1965
+ },
1966
+ {
1967
+ "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`].",
1968
+ "type": "string",
1969
+ "const": "default"
1970
+ },
1971
+ {
1972
+ "description": "Auto-approve file edits, still gate shell/commands. (Claude's\n`acceptEdits`, Qwen's `auto-edit`.)",
1973
+ "type": "string",
1974
+ "const": "edit"
1975
+ },
1976
+ {
1977
+ "description": "Auto-approve operations the harness deems safe (a classifier), gate the\nrisky ones. (Claude's `auto`, Qwen's `auto`, Goose's `smart_approve`.)",
1978
+ "type": "string",
1979
+ "const": "auto"
1980
+ },
1981
+ {
1982
+ "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`, …)",
1983
+ "type": "string",
1984
+ "const": "bypass"
1985
+ }
1986
+ ]
1987
+ },
1988
+ "Status": {
1989
+ "description": "The outcome of attempting to run one harness.",
1990
+ "oneOf": [
1991
+ {
1992
+ "description": "Spawned and exited 0.",
1993
+ "type": "string",
1994
+ "const": "ok"
1995
+ },
1996
+ {
1997
+ "description": "Spawned and exited non-zero.",
1998
+ "type": "string",
1999
+ "const": "nonzero"
2000
+ },
2001
+ {
2002
+ "description": "Killed after exceeding the per-harness timeout.",
2003
+ "type": "string",
2004
+ "const": "timeout"
2005
+ },
2006
+ {
2007
+ "description": "The binary was resolved but could not be executed.",
2008
+ "type": "string",
2009
+ "const": "spawn-error"
2010
+ },
2011
+ {
2012
+ "description": "The binary was not found, so the harness was not run.",
2013
+ "type": "string",
2014
+ "const": "skipped"
2015
+ },
2016
+ {
2017
+ "description": "`--print-command`: the command was built but not executed.",
2018
+ "type": "string",
2019
+ "const": "planned"
2020
+ }
2021
+ ]
2022
+ },
2023
+ "Usage": {
2024
+ "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).",
2025
+ "type": "object",
2026
+ "properties": {
2027
+ "cache_read_tokens": {
2028
+ "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.",
2029
+ "type": [
2030
+ "integer",
2031
+ "null"
2032
+ ],
2033
+ "format": "uint64",
2034
+ "minimum": 0
2035
+ },
2036
+ "cache_write_tokens": {
2037
+ "description": "Prompt tokens written to the provider's prompt cache (a.k.a. cache\ncreation), when the harness reports them. `None` when not surfaced.",
2038
+ "type": [
2039
+ "integer",
2040
+ "null"
2041
+ ],
2042
+ "format": "uint64",
2043
+ "minimum": 0
2044
+ },
2045
+ "cost_usd": {
2046
+ "description": "Total cost in USD, when the harness reports it (often absent on\nsubscription auth, where there is no per-call dollar figure).",
2047
+ "type": [
2048
+ "number",
2049
+ "null"
2050
+ ],
2051
+ "format": "double"
2052
+ },
2053
+ "input_tokens": {
2054
+ "description": "Prompt/input tokens billed, when the harness reports them.",
2055
+ "type": [
2056
+ "integer",
2057
+ "null"
2058
+ ],
2059
+ "format": "uint64",
2060
+ "minimum": 0
2061
+ },
2062
+ "output_tokens": {
2063
+ "description": "Completion/output tokens billed, when the harness reports them.",
2064
+ "type": [
2065
+ "integer",
2066
+ "null"
2067
+ ],
2068
+ "format": "uint64",
2069
+ "minimum": 0
2070
+ }
2071
+ },
2072
+ "required": [
2073
+ "input_tokens",
2074
+ "output_tokens",
2075
+ "cache_read_tokens",
2076
+ "cache_write_tokens",
2077
+ "cost_usd"
2078
+ ]
2079
+ }
2080
+ }
2081
+ },
2082
+ "history_stream_envelope": {
2083
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
2084
+ "title": "HistoryStreamEnvelope",
2085
+ "description": "One line emitted by `history watch --format jsonl`. The tagged envelope lets\nSDKs distinguish the stream from other NDJSON surfaces while the record's\n`history_id` remains the resumable cursor.",
2086
+ "oneOf": [
2087
+ {
2088
+ "description": "A newly observed history record.",
2089
+ "type": "object",
2090
+ "properties": {
2091
+ "record": {
2092
+ "$ref": "#/$defs/HistoryRecord"
2093
+ },
2094
+ "type": {
2095
+ "type": "string",
2096
+ "const": "record"
2097
+ }
2098
+ },
2099
+ "required": [
2100
+ "type",
2101
+ "record"
1009
2102
  ]
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
2103
  }
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
2104
  ],
1040
2105
  "$defs": {
1041
2106
  "ActionEvent": {
@@ -1071,11 +2136,11 @@
1071
2136
  }
1072
2137
  },
1073
2138
  "required": [
1074
- "index",
1075
- "input",
1076
2139
  "kind",
1077
2140
  "name",
1078
- "output"
2141
+ "input",
2142
+ "output",
2143
+ "index"
1079
2144
  ]
1080
2145
  },
1081
2146
  "FailureKind": {
@@ -1108,6 +2173,165 @@
1108
2173
  }
1109
2174
  ]
1110
2175
  },
2176
+ "HistoryLabels": {
2177
+ "type": "object",
2178
+ "additionalProperties": {
2179
+ "type": "string",
2180
+ "maxLength": 256,
2181
+ "minLength": 1,
2182
+ "not": {
2183
+ "pattern": "[\\u0000-\\u001f\\u007f-\\u009f]"
2184
+ }
2185
+ },
2186
+ "propertyNames": {
2187
+ "type": "string",
2188
+ "maxLength": 64,
2189
+ "not": {
2190
+ "pattern": "[^A-Za-z0-9._-]"
2191
+ },
2192
+ "pattern": "^[A-Za-z0-9]"
2193
+ }
2194
+ },
2195
+ "HistoryRecord": {
2196
+ "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.",
2197
+ "type": "object",
2198
+ "properties": {
2199
+ "duration_ms": {
2200
+ "type": [
2201
+ "integer",
2202
+ "null"
2203
+ ],
2204
+ "format": "uint128",
2205
+ "minimum": 0
2206
+ },
2207
+ "events": {
2208
+ "description": "Best-effort normalized tool-call events; `null` when the harness exposes\nno machine-readable trace.",
2209
+ "type": [
2210
+ "array",
2211
+ "null"
2212
+ ],
2213
+ "items": {
2214
+ "$ref": "#/$defs/ActionEvent"
2215
+ }
2216
+ },
2217
+ "exit_code": {
2218
+ "type": [
2219
+ "integer",
2220
+ "null"
2221
+ ],
2222
+ "format": "int32"
2223
+ },
2224
+ "failure_kind": {
2225
+ "description": "Best-effort classified failure reason (see [`FailureKind`]); `null` when\nunclassified.",
2226
+ "anyOf": [
2227
+ {
2228
+ "$ref": "#/$defs/FailureKind"
2229
+ },
2230
+ {
2231
+ "type": "null"
2232
+ }
2233
+ ]
2234
+ },
2235
+ "harness": {
2236
+ "description": "Canonical harness id (e.g. `claude-code`).",
2237
+ "type": "string"
2238
+ },
2239
+ "history_id": {
2240
+ "description": "Globally unique, time-ordered record id. This is also the cursor accepted\nby `history watch --after` and the exact id accepted by history lookup.",
2241
+ "type": "string",
2242
+ "maxLength": 36,
2243
+ "minLength": 36,
2244
+ "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
2245
+ },
2246
+ "labels": {
2247
+ "description": "Caller-supplied metadata used to select related task-graph records.\nOmitted on the wire when empty for additive compatibility.",
2248
+ "$ref": "#/$defs/HistoryLabels"
2249
+ },
2250
+ "model": {
2251
+ "description": "The effective top-level model for the run, if any.",
2252
+ "type": [
2253
+ "string",
2254
+ "null"
2255
+ ]
2256
+ },
2257
+ "name": {
2258
+ "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.",
2259
+ "type": "string"
2260
+ },
2261
+ "permission_mode": {
2262
+ "description": "The normalized approval mode requested for the run.",
2263
+ "$ref": "#/$defs/PermissionMode"
2264
+ },
2265
+ "project": {
2266
+ "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.",
2267
+ "type": "string"
2268
+ },
2269
+ "prompt": {
2270
+ "description": "The prompt this harness run received (its own, on a batch run; else the\nrun's single prompt).",
2271
+ "type": "string"
2272
+ },
2273
+ "schema_version": {
2274
+ "type": "string"
2275
+ },
2276
+ "session": {
2277
+ "description": "The oneharness session id this run belongs to (the history file's stem).",
2278
+ "type": "string"
2279
+ },
2280
+ "session_id": {
2281
+ "description": "The harness's own continuation id, when it exposed one; `null` otherwise.",
2282
+ "type": [
2283
+ "string",
2284
+ "null"
2285
+ ]
2286
+ },
2287
+ "status": {
2288
+ "$ref": "#/$defs/Status"
2289
+ },
2290
+ "text": {
2291
+ "description": "Best-effort final assistant text; `null` when extraction was impossible.",
2292
+ "type": [
2293
+ "string",
2294
+ "null"
2295
+ ]
2296
+ },
2297
+ "text_source": {
2298
+ "description": "How `text` was extracted; `null` when absent.",
2299
+ "type": [
2300
+ "string",
2301
+ "null"
2302
+ ]
2303
+ },
2304
+ "timestamp": {
2305
+ "description": "RFC3339 UTC instant the record was written (append time).",
2306
+ "type": "string"
2307
+ },
2308
+ "usage": {
2309
+ "description": "Best-effort token/cost accounting (every field `null` when unreported).",
2310
+ "$ref": "#/$defs/Usage"
2311
+ }
2312
+ },
2313
+ "required": [
2314
+ "schema_version",
2315
+ "history_id",
2316
+ "session",
2317
+ "name",
2318
+ "project",
2319
+ "timestamp",
2320
+ "harness",
2321
+ "model",
2322
+ "prompt",
2323
+ "permission_mode",
2324
+ "status",
2325
+ "exit_code",
2326
+ "duration_ms",
2327
+ "text",
2328
+ "text_source",
2329
+ "usage",
2330
+ "session_id",
2331
+ "events",
2332
+ "failure_kind"
2333
+ ]
2334
+ },
1111
2335
  "PermissionMode": {
1112
2336
  "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
2337
  "oneOf": [
@@ -1228,11 +2452,11 @@
1228
2452
  }
1229
2453
  },
1230
2454
  "required": [
2455
+ "input_tokens",
2456
+ "output_tokens",
1231
2457
  "cache_read_tokens",
1232
2458
  "cache_write_tokens",
1233
- "cost_usd",
1234
- "input_tokens",
1235
- "output_tokens"
2459
+ "cost_usd"
1236
2460
  ]
1237
2461
  }
1238
2462
  }
@@ -1278,11 +2502,11 @@
1278
2502
  }
1279
2503
  },
1280
2504
  "required": [
1281
- "index",
1282
- "input",
1283
2505
  "kind",
1284
2506
  "name",
1285
- "output"
2507
+ "input",
2508
+ "output",
2509
+ "index"
1286
2510
  ]
1287
2511
  },
1288
2512
  "FailureKind": {
@@ -1315,6 +2539,25 @@
1315
2539
  }
1316
2540
  ]
1317
2541
  },
2542
+ "HistoryLabels": {
2543
+ "type": "object",
2544
+ "additionalProperties": {
2545
+ "type": "string",
2546
+ "maxLength": 256,
2547
+ "minLength": 1,
2548
+ "not": {
2549
+ "pattern": "[\\u0000-\\u001f\\u007f-\\u009f]"
2550
+ }
2551
+ },
2552
+ "propertyNames": {
2553
+ "type": "string",
2554
+ "maxLength": 64,
2555
+ "not": {
2556
+ "pattern": "[^A-Za-z0-9._-]"
2557
+ },
2558
+ "pattern": "^[A-Za-z0-9]"
2559
+ }
2560
+ },
1318
2561
  "HistoryRecord": {
1319
2562
  "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
2563
  "type": "object",
@@ -1359,6 +2602,17 @@
1359
2602
  "description": "Canonical harness id (e.g. `claude-code`).",
1360
2603
  "type": "string"
1361
2604
  },
2605
+ "history_id": {
2606
+ "description": "Globally unique, time-ordered record id. This is also the cursor accepted\nby `history watch --after` and the exact id accepted by history lookup.",
2607
+ "type": "string",
2608
+ "maxLength": 36,
2609
+ "minLength": 36,
2610
+ "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
2611
+ },
2612
+ "labels": {
2613
+ "description": "Caller-supplied metadata used to select related task-graph records.\nOmitted on the wire when empty for additive compatibility.",
2614
+ "$ref": "#/$defs/HistoryLabels"
2615
+ },
1362
2616
  "model": {
1363
2617
  "description": "The effective top-level model for the run, if any.",
1364
2618
  "type": [
@@ -1423,24 +2677,25 @@
1423
2677
  }
1424
2678
  },
1425
2679
  "required": [
1426
- "duration_ms",
1427
- "events",
1428
- "exit_code",
1429
- "failure_kind",
1430
- "harness",
1431
- "model",
2680
+ "schema_version",
2681
+ "history_id",
2682
+ "session",
1432
2683
  "name",
1433
- "permission_mode",
1434
2684
  "project",
2685
+ "timestamp",
2686
+ "harness",
2687
+ "model",
1435
2688
  "prompt",
1436
- "schema_version",
1437
- "session",
1438
- "session_id",
2689
+ "permission_mode",
1439
2690
  "status",
2691
+ "exit_code",
2692
+ "duration_ms",
1440
2693
  "text",
1441
2694
  "text_source",
1442
- "timestamp",
1443
- "usage"
2695
+ "usage",
2696
+ "session_id",
2697
+ "events",
2698
+ "failure_kind"
1444
2699
  ]
1445
2700
  },
1446
2701
  "PermissionMode": {
@@ -1563,11 +2818,11 @@
1563
2818
  }
1564
2819
  },
1565
2820
  "required": [
2821
+ "input_tokens",
2822
+ "output_tokens",
1566
2823
  "cache_read_tokens",
1567
2824
  "cache_write_tokens",
1568
- "cost_usd",
1569
- "input_tokens",
1570
- "output_tokens"
2825
+ "cost_usd"
1571
2826
  ]
1572
2827
  }
1573
2828
  }
@@ -1580,6 +2835,25 @@
1580
2835
  "$ref": "#/$defs/HistorySessionSummary"
1581
2836
  },
1582
2837
  "$defs": {
2838
+ "HistoryLabels": {
2839
+ "type": "object",
2840
+ "additionalProperties": {
2841
+ "type": "string",
2842
+ "maxLength": 256,
2843
+ "minLength": 1,
2844
+ "not": {
2845
+ "pattern": "[\\u0000-\\u001f\\u007f-\\u009f]"
2846
+ }
2847
+ },
2848
+ "propertyNames": {
2849
+ "type": "string",
2850
+ "maxLength": 64,
2851
+ "not": {
2852
+ "pattern": "[^A-Za-z0-9._-]"
2853
+ },
2854
+ "pattern": "^[A-Za-z0-9]"
2855
+ }
2856
+ },
1583
2857
  "HistorySessionSummary": {
1584
2858
  "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
2859
  "type": "object",
@@ -1595,6 +2869,10 @@
1595
2869
  "description": "The session id (the file stem), unique and sortable by start time.",
1596
2870
  "type": "string"
1597
2871
  },
2872
+ "labels": {
2873
+ "description": "Labels shared by every record in the session. Omitted when empty.",
2874
+ "$ref": "#/$defs/HistoryLabels"
2875
+ },
1598
2876
  "name": {
1599
2877
  "description": "The human-meaningful session name (non-unique).",
1600
2878
  "type": "string"
@@ -1619,13 +2897,13 @@
1619
2897
  }
1620
2898
  },
1621
2899
  "required": [
1622
- "harnesses",
1623
2900
  "id",
1624
2901
  "name",
1625
- "path",
1626
2902
  "project",
2903
+ "started",
1627
2904
  "record_count",
1628
- "started"
2905
+ "harnesses",
2906
+ "path"
1629
2907
  ]
1630
2908
  }
1631
2909
  }
@@ -1646,8 +2924,8 @@
1646
2924
  }
1647
2925
  },
1648
2926
  "required": [
1649
- "harnesses",
1650
- "schema_version"
2927
+ "schema_version",
2928
+ "harnesses"
1651
2929
  ],
1652
2930
  "$defs": {
1653
2931
  "HarnessInfo": {
@@ -1744,27 +3022,27 @@
1744
3022
  }
1745
3023
  },
1746
3024
  "required": [
1747
- "default_bin",
1748
- "display",
1749
- "example_command",
1750
- "fork_reuses_cache",
1751
3025
  "id",
3026
+ "display",
3027
+ "default_bin",
1752
3028
  "install_hint",
1753
- "mock_rewrite",
1754
- "modes",
1755
3029
  "output_format",
3030
+ "supports_resume",
1756
3031
  "session_capable",
3032
+ "supports_fork",
3033
+ "fork_reuses_cache",
3034
+ "modes",
3035
+ "supports_native_schema",
3036
+ "supports_reasoning",
3037
+ "sync_file",
1757
3038
  "supports_allowed_tools",
1758
3039
  "supports_denied_tools",
1759
- "supports_fork",
1760
3040
  "supports_hooks",
1761
3041
  "supports_mock_deny",
1762
- "supports_native_schema",
3042
+ "mock_rewrite",
1763
3043
  "supports_prompt_stdin",
1764
- "supports_reasoning",
1765
- "supports_resume",
1766
3044
  "supports_system_file",
1767
- "sync_file"
3045
+ "example_command"
1768
3046
  ]
1769
3047
  },
1770
3048
  "ModeHeadless": {
@@ -1795,8 +3073,8 @@
1795
3073
  }
1796
3074
  },
1797
3075
  "required": [
1798
- "headless",
1799
- "mode"
3076
+ "mode",
3077
+ "headless"
1800
3078
  ]
1801
3079
  },
1802
3080
  "OutputFormat": {
@@ -1872,8 +3150,8 @@
1872
3150
  }
1873
3151
  },
1874
3152
  "required": [
1875
- "detected",
1876
- "schema_version"
3153
+ "schema_version",
3154
+ "detected"
1877
3155
  ],
1878
3156
  "$defs": {
1879
3157
  "DetectInfo": {
@@ -1902,9 +3180,9 @@
1902
3180
  }
1903
3181
  },
1904
3182
  "required": [
1905
- "available",
1906
- "bin",
1907
3183
  "id",
3184
+ "bin",
3185
+ "available",
1908
3186
  "path",
1909
3187
  "version"
1910
3188
  ]