harness-everything 0.28.0 → 0.30.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.
Files changed (109) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/AGENTS.md +1 -0
  4. package/CHANGELOG.md +47 -0
  5. package/PRIVACY.md +9 -0
  6. package/benchmarks/fixtures/system-one-holdout-reviews.json +18 -18
  7. package/benchmarks/fixtures/system-one-holdout.json +9 -9
  8. package/benchmarks/fixtures/system-one-intent-holdout-draft.json +2228 -0
  9. package/benchmarks/fixtures/system-one-intent-holdout.json +16509 -0
  10. package/benchmarks/fixtures/system-one-intent-reviews.json +2723 -0
  11. package/benchmarks/results/system-one/harness-routing-v1-cpu-2026-09-24/README.md +75 -0
  12. package/benchmarks/results/system-one/harness-routing-v1-cpu-2026-09-24/report.json +10832 -0
  13. package/benchmarks/results/system-one/harness-routing-v1-ngram-2026-09-24/README.md +72 -0
  14. package/benchmarks/results/system-one/harness-routing-v1-ngram-2026-09-24/report.json +10817 -0
  15. package/benchmarks/results/system-one/harness-routing-v3-ngram-2026-09-24/README.md +63 -0
  16. package/benchmarks/results/system-one/harness-routing-v3-ngram-2026-09-24/report.json +10817 -0
  17. package/benchmarks/results/system-one/intent-ngram-v1-2026-09-24/README.md +64 -0
  18. package/benchmarks/results/system-one/intent-ngram-v1-2026-09-24/report.json +15177 -0
  19. package/benchmarks/results/system-one/labeler-holdout-check-2026-09-24/report.json +1104 -0
  20. package/benchmarks/results/system-one/labeler-holdout-check-codex-2026-09-24/report.json +1104 -0
  21. package/benchmarks/results/system-one/labeler-holdout-check-codex-tier3-2026-09-24/report.json +1104 -0
  22. package/benchmarks/results/system-one/labeler-holdout-check-intent-2026-09-24/report.json +2976 -0
  23. package/benchmarks/results/system-one/labeler-holdout-check-intent-scores-haiku-2026-09-24/report-005-steps.json +6125 -0
  24. package/benchmarks/results/system-one/labeler-holdout-check-intent-scores-haiku-2026-09-24/report-haiku-sonnet-escalation.json +6344 -0
  25. package/benchmarks/results/system-one/labeler-holdout-check-intent-scores-haiku-2026-09-24/report-scaled-margin-escalation.json +6206 -0
  26. package/benchmarks/results/system-one/labeler-holdout-check-intent-scores-haiku-2026-09-24/report.json +6103 -0
  27. package/benchmarks/results/system-one/laya-ft-2026-09-25/README.md +83 -0
  28. package/benchmarks/results/system-one/laya-ft-2026-09-25/report.json +71 -0
  29. package/benchmarks/results/system-one/laya-ft-2026-09-25/shadow-report.json +44 -0
  30. package/benchmarks/results/system-one/laya-zs-2026-09-25/README.md +68 -0
  31. package/benchmarks/results/system-one/laya-zs-2026-09-25/report.json +173 -0
  32. package/ci/mechanism-34-observations.test.js +550 -0
  33. package/ci/mechanism-system-one-collect.test.js +16 -0
  34. package/ci/mechanism-system-one-evaluation.test.js +17 -0
  35. package/ci/mechanism-system-one-intent.test.js +406 -0
  36. package/ci/mechanism-system-one-label.test.js +27 -0
  37. package/ci/mechanism-system-one-laya.test.js +45 -0
  38. package/ci/mechanism-system-one-ngram.test.js +161 -0
  39. package/ci/mechanism-system-one-relevance.test.js +112 -0
  40. package/ci/mechanism-system-one-resident.test.js +16 -0
  41. package/ci/mechanism-system-one-train.test.js +12 -0
  42. package/ci/system-one-laya-calibrate-test.py +65 -0
  43. package/ci/system-one-laya-export-test.py +155 -0
  44. package/ci/system-one-laya-label-test.py +201 -0
  45. package/ci/system-one-laya-perintent-test.py +60 -0
  46. package/ci/system-one-laya-provider-test.py +115 -0
  47. package/ci/system-one-laya-train-test.py +93 -0
  48. package/ci/system-one-ngram-parity.py +19 -0
  49. package/ci/system-one-train-test.py +6 -0
  50. package/docs/routing.md +25 -0
  51. package/docs/system-one-corpus.md +31 -3
  52. package/docs/system-one-intent-gates.md +102 -0
  53. package/docs/system-one-intent.md +173 -0
  54. package/docs/system-one-laya-training.md +38 -0
  55. package/docs/system-one-observations.md +206 -0
  56. package/docs/system-one-routing.md +102 -5
  57. package/docs/system-one-suggestion-gates.md +292 -0
  58. package/docs/system-one-training.md +69 -7
  59. package/harness-everything/SKILL.md +1 -1
  60. package/harness-everything/scripts/kernel-router.js +10 -0
  61. package/harness-everything/scripts/system-one/catalogs.js +35 -0
  62. package/harness-everything/scripts/system-one/contract.js +45 -1
  63. package/harness-everything/scripts/system-one/evaluate.js +118 -21
  64. package/harness-everything/scripts/system-one/intent.js +22 -0
  65. package/harness-everything/scripts/system-one/laya_adapter.py +141 -0
  66. package/harness-everything/scripts/system-one/ngram.js +84 -0
  67. package/harness-everything/scripts/system-one/provider.js +24 -7
  68. package/harness-everything/scripts/system-one/resident.js +8 -3
  69. package/harness-everything/scripts/system-one/router.js +1 -1
  70. package/hooks/hooks.json +40 -0
  71. package/hooks/scripts/codex-user-prompt.js +39 -0
  72. package/hooks/scripts/lib/observations.js +581 -0
  73. package/hooks/scripts/observation-hook.js +25 -0
  74. package/opencode-plugin/plugin.json +1 -1
  75. package/package.json +1 -1
  76. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  77. package/plugins/harness-everything/hooks/hooks.json +35 -2
  78. package/plugins/harness-everything/hooks/scripts/codex-user-prompt.js +39 -0
  79. package/plugins/harness-everything/hooks/scripts/lib/observations.js +581 -0
  80. package/plugins/harness-everything/hooks/scripts/observation-hook.js +25 -0
  81. package/plugins/harness-everything/plugin.json +1 -1
  82. package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
  83. package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router.js +10 -0
  84. package/plugins/harness-everything/skills/harness-everything/scripts/system-one/catalogs.js +35 -0
  85. package/plugins/harness-everything/skills/harness-everything/scripts/system-one/contract.js +45 -1
  86. package/plugins/harness-everything/skills/harness-everything/scripts/system-one/evaluate.js +118 -21
  87. package/plugins/harness-everything/skills/harness-everything/scripts/system-one/intent.js +22 -0
  88. package/plugins/harness-everything/skills/harness-everything/scripts/system-one/laya_adapter.py +141 -0
  89. package/plugins/harness-everything/skills/harness-everything/scripts/system-one/ngram.js +84 -0
  90. package/plugins/harness-everything/skills/harness-everything/scripts/system-one/provider.js +24 -7
  91. package/plugins/harness-everything/skills/harness-everything/scripts/system-one/resident.js +8 -3
  92. package/plugins/harness-everything/skills/harness-everything/scripts/system-one/router.js +1 -1
  93. package/sbom.cdx.json +7 -7
  94. package/scripts/evaluate-system-one.js +32 -13
  95. package/scripts/system-one-calibrate-laya.py +165 -0
  96. package/scripts/system-one-calibrate.js +42 -13
  97. package/scripts/system-one-collect.js +7 -3
  98. package/scripts/system-one-compare-laya.py +156 -0
  99. package/scripts/system-one-corpus.js +22 -15
  100. package/scripts/system-one-eval-laya.js +112 -0
  101. package/scripts/system-one-export-laya.py +197 -0
  102. package/scripts/system-one-label-laya.py +243 -0
  103. package/scripts/system-one-label.js +192 -37
  104. package/scripts/system-one-observations-export.js +106 -0
  105. package/scripts/system-one-observations-review.js +86 -0
  106. package/scripts/system-one-perintent-laya.py +134 -0
  107. package/scripts/system-one-train-laya.py +344 -0
  108. package/scripts/system-one-train-ngram.py +181 -0
  109. package/scripts/system-one-train.py +14 -1
@@ -0,0 +1,292 @@
1
+ # System One suggestion gates
2
+
3
+ Status: **proposed, pending owner review**. Once the owner approves it, this
4
+ document is frozen for the next candidate of each stage. Decided in the
5
+ [#233 design correction](https://github.com/dyphn1/Harness-everything/issues/233#issuecomment-5842359628)
6
+ (one event, matched against every option); research evidence is in
7
+ [#255](https://github.com/dyphn1/Harness-everything/issues/255).
8
+
9
+ This document supersedes the table in
10
+ [system-one-intent-gates.md](system-one-intent-gates.md). That table stays
11
+ as the historical contract for the runs made under it. It also supersedes
12
+ the model-quality part of
13
+ [system-one-routing.md#rollout-gates](system-one-routing.md#rollout-gates).
14
+ `evaluate.relevanceGates` still implements the superseded table. The code
15
+ change follows this document (docs → RED → GREEN) in its own commit.
16
+
17
+ ## Principle
18
+
19
+ 1. **One event, scored against every option.** For a prompt, the scorer
20
+ returns an independent relevance `p_i ∈ [0, 1]` for every option of the
21
+ stage catalog. Scores do not sum to 1 and are never renormalized into a
22
+ simplex. With CUA-S1 this is the per-option logit passed through a
23
+ sigmoid. Upstream `tinyx` already encodes each option separately and
24
+ scores it against the context, so one forward pass scores every option.
25
+ 2. **Suggestions, not verdicts.** Output is advice to the agent. A wrong
26
+ suggestion costs little; a confident wrong suggestion, or a long list the
27
+ agent must read, costs trust and tokens. Gates therefore measure
28
+ *trustworthy confidence* and *suggestion coverage under a noise cap*,
29
+ not exact accuracy.
30
+ 3. **Deterministic controls stay outside learned scoring.** Tier structural
31
+ floor, explicit workflow requests, action/security approval, scope and
32
+ memory ownership and the Rule of 3 are unchanged. Abstention always falls
33
+ back to the lexical path.
34
+
35
+ ## Readout per stage
36
+
37
+ All stages share the scoring and the calibration. Only the readout differs.
38
+
39
+ | Stage | Catalog | Readout | Cap |
40
+ | --- | --- | --- | --- |
41
+ | Validity | `actionable`, `invalid` | `invalid` at or above its threshold → stop: no tier, workflow, intent or skill suggestion; the host agent decides | — |
42
+ | Intent | the 12 intents of [system-one-intent.md](system-one-intent.md) | set: every intent at or above its threshold, highest `p` first, truncated at the cap | 3 |
43
+ | Tier | `tier1`, `tier2`, `tier3` | composed (see [Tier composition](#tier-composition)): tier3 when `feature` or `refactor` reaches its intent threshold; otherwise the tier scorer's pick; final tier = max(that, structural floor); none → abstain | 1 |
44
+ | Workflow | the router's strategies (`direct-single`, `iterative-single`, `fable-staged`, `fable-parallel`, `fable-multi-agent-workspace`) | pick one, same rule; an explicit workflow request always wins | 1 |
45
+ | Skills | canonical skills, scored against their descriptions | set, same rule | 3 |
46
+
47
+ The cap of 3 for set stages comes from AGENTS.md rule 10: a suggested skill
48
+ must be read before it can be dismissed, so each extra suggestion has a
49
+ reading cost. The cap is part of the readout, so noise is bounded by
50
+ construction; the gates then judge what survives the cap.
51
+
52
+ Stages run in table order: validity, intent, tier, workflow, skills. Tier
53
+ reads the intent scores, so intent runs before it.
54
+
55
+ ### Tier composition
56
+
57
+ Tier rules v2 (owner decision, 2026-09-26): tier3 covers new features,
58
+ refactors, changes to definitions, and cross-repository or cross-component
59
+ work, whatever the width of the change; tier1 is a clear bounded action
60
+ (Git, status, lookup, direct answer); tier2 is a bounded modification.
61
+
62
+ Whether a prompt asks for a feature or a refactor is an intent question,
63
+ and the intent stage already scores it. The tier stage therefore does not
64
+ learn that part again:
65
+
66
+ 1. **Intent part.** tier3 when the calibrated score of `feature` or
67
+ `refactor` is at or above that intent's threshold (either one fires; not
68
+ "is the top intent", which a small intent scorer rarely satisfies).
69
+ 2. **Scope part.** The deterministic structural floor (cross-repository,
70
+ cross-component, macro scope) sets tier3 as before.
71
+ 3. **Tier scorer.** Otherwise the tier scorer picks, trained on the
72
+ breadth-based labels, so it separates tier1 from tier2 and keeps the
73
+ breadth-based tier3.
74
+
75
+ Over-reading `feature`/`refactor` moves a prompt one tier up, which the
76
+ gates count as acceptable; missing it moves it down, which they count as
77
+ under-tier. The composition is therefore tuned for intent recall on those
78
+ two intents.
79
+
80
+ Evidence (#265, validation, five seeds): composed with the CUA-S1 intent
81
+ scorer, acceptable precision 0.82 and under-tier 0.13, against 0.77 and
82
+ 0.22 for a tier scorer trained directly on rules-v2 labels.
83
+
84
+ **Holdout.** The owner decided not to re-review the tier holdout under
85
+ rules v2. Its gold follows the older rules, and rules v2 only moves labels
86
+ up (tier1 or tier2 to tier3). So underRate on the old gold stays valid, and
87
+ a tier3 pick for tier2 gold counts as acceptable. A tier3 pick for a
88
+ tier1-gold feature prompt counts as an error, which makes
89
+ acceptablePrecision slightly pessimistic; the count of such cases is
90
+ reported beside it.
91
+
92
+ `unclassified`/`null` is not an option in the new readout. It is the
93
+ abstain outcome: no option reaches its threshold.
94
+
95
+ **The scorer only sees the current prompt.** It never receives earlier
96
+ turns. A prompt whose text alone carries no actionable request is
97
+ `invalid`: System One makes no suggestion, and the host agent, which has
98
+ the conversation, decides. Validity runs first; the later stages run only
99
+ on `actionable` prompts.
100
+
101
+ ## Calibration and thresholds
102
+
103
+ - **Calibration.** A monotone per-option map (isotonic or Platt, chosen and
104
+ recorded before fitting) from raw `p` to calibrated `p`, fitted on
105
+ validation only. Needed because a model trained on soft teacher scores
106
+ outputs a *relevance degree* (0.5 = "secondary relevance"), not a
107
+ probability of being relevant. Gates read calibrated `p`.
108
+ - **Suggest threshold `τ_i`.** One per option, fitted on validation
109
+ (max-F1 against the validation truth), 2-fold CV reported.
110
+ - **Confident band.** Calibrated `p ≥ 0.8`. A confident suggestion is a
111
+ claim that at least 8 in 10 such suggestions are relevant.
112
+ - Calibration maps, thresholds, cap, band and this document are frozen
113
+ before a holdout run. Nothing is refit after it.
114
+
115
+ ## Truth
116
+
117
+ | Split | Relevant set | Primary |
118
+ | --- | --- | --- |
119
+ | Train / validation | teacher score ≥ 0.4, after owner intent overrides and exclusions | teacher primary |
120
+ | Intent holdout | owner `gold ∪ secondary` | owner `gold` |
121
+ | Tier holdout | owner `gold` tier; `null` means abstention is correct | — |
122
+
123
+ ### Invalid prompts
124
+
125
+ Some prompts cannot be judged from their text: a go-ahead (`go`, `好`,
126
+ `完成了`), an option pick, a change verb with no object (`修正一下`),
127
+ feedback on earlier work (`還是一樣的錯誤`), a pointer to earlier content
128
+ (`請依照需求實作`), a paste with no request, or chatter. The small model
129
+ does not try to recover the missing context. It marks them `invalid` and
130
+ hands them to the host agent (the external LLM), which sees the whole
131
+ conversation. Deterministic controls (action gates, scope and memory
132
+ ownership, the Rule of 3) apply unchanged. The noise-gate experiment (#265)
133
+ showed that tier labels given to such prompts from session context were
134
+ what kept a text-only model unsure.
135
+
136
+ - **Definition.** A holdout case is invalid when the frozen rules
137
+ (`rule_v2` in `scripts/system-one-noise-experiment.py`, #265) match its
138
+ text or its owner gold is `null`. The owner reviews that list before the
139
+ holdout run; the list is then frozen with the rest of step 4 below.
140
+ - **Scoring.** On an invalid case, the model is correct when it returns
141
+ `invalid` (or abstains); any tier, intent or skill suggestion is an error
142
+ for that case. Invalid cases are excluded from the later stages' metrics
143
+ and reported as invalid recall. On actionable cases, returning `invalid`
144
+ is reported as invalid false-positive rate.
145
+ - **Size.** The rules mark 9 of 215 tier-holdout cases (6 `null`, 2 `tier1`,
146
+ 1 `tier2`) and 3 of 222 intent-holdout cases; the tier holdout has 20
147
+ `null` cases in all. The holdouts were built with most continuations
148
+ removed, so the validity gate is read on validation until a reviewed
149
+ invalid set exists.
150
+
151
+ ## Metrics
152
+
153
+ Set stages (intent, skills), over actionable prompts:
154
+
155
+ | Metric | Definition |
156
+ | --- | --- |
157
+ | suggestionCoverage | share of prompts with at least one suggestion |
158
+ | confidentCoverage | share of prompts with at least one confident suggestion |
159
+ | primaryHit | share of prompts whose primary is among the suggestions |
160
+ | suggestionPrecision | relevant suggestions / all suggestions |
161
+ | confidentPrecision | relevant confident suggestions / all confident suggestions |
162
+ | ECE | expected calibration error over all (prompt, option) pairs, 10 equal-width bins of calibrated `p` against set membership |
163
+ | Brier | mean squared error of calibrated `p` against set membership |
164
+ | per-option precision / recall / support | support = holdout prompts where the option is relevant |
165
+ | cardinality | mean and p95 suggestions per prompt, share of prompts at the cap |
166
+ | recall | relevant suggestions / relevant options (reported) |
167
+
168
+ ECE alone rewards a timid model that never says anything confident; it is
169
+ gated together with confidentCoverage, never alone.
170
+
171
+ Pick stages (tier, workflow):
172
+
173
+ | Metric | Definition |
174
+ | --- | --- |
175
+ | coverage | share of actionable prompts that get a pick |
176
+ | acceptablePrecision | picks equal to gold or one tier above it, among picks |
177
+ | underRate | picks below gold, among picks |
178
+ | exactPrecision | picks equal to gold, among picks (reported) |
179
+ | ECE / Brier | as for set stages, one-vs-rest per option |
180
+
181
+ One tier above gold is acceptable because over-routing adds process;
182
+ under-routing skips obligations, so it has its own hard gate.
183
+
184
+ Every stage also reports family consistency (same family, same top
185
+ option), repeatability, latency, artifact size and policy evidence.
186
+
187
+ ## Gates
188
+
189
+ ### Intent (holdout)
190
+
191
+ Anchors are validation-proxy measurements, not holdout results: the
192
+ fine-tuned Laya reference, capped at 3 with in-sample thresholds, reaches
193
+ coverage 0.97, primary hit 0.75, precision 0.63, raw-`p ≥ 0.6` precision
194
+ 0.85 and ECE 0.06. Holdout truth is stricter, so bars sit below it.
195
+
196
+ | Gate | Bar |
197
+ | --- | --- |
198
+ | suggestionCoverage | ≥ 0.90 |
199
+ | primaryHit | ≥ 0.65 |
200
+ | suggestionPrecision | ≥ 0.60 |
201
+ | confidentCoverage | ≥ 0.40 |
202
+ | confidentPrecision | ≥ 0.75 (the 0.8 claim minus about two standard errors at about 250 confident suggestions) |
203
+ | ECE | ≤ 0.10 |
204
+ | per-intent precision, support ≥ 20 | ≥ 0.40 |
205
+ | family consistency (top suggestion) | ≥ 0.70 (owner gold 0.80) |
206
+
207
+ ### Tier (holdout)
208
+
209
+ | Gate | Bar |
210
+ | --- | --- |
211
+ | acceptablePrecision | ≥ 0.85 (the owner's advisory bar, now counting one tier above gold) |
212
+ | underRate | ≤ 0.05 |
213
+ | coverage | ≥ 0.60 |
214
+ | ECE | ≤ 0.10 |
215
+ | invalid recall (invalid cases) | reported; ≥ 0.80 on validation invalid rows |
216
+ | invalid false-positive rate (actionable cases) | ≤ 0.05 |
217
+
218
+ Tier bars have no validation anchor from a relevance-native tier model
219
+ yet. The first tier candidate reports its validation numbers first, and
220
+ these bars are confirmed or amended in this document **before** its
221
+ holdout run, never after.
222
+
223
+ ### Workflow and skills
224
+
225
+ No reviewed holdout exists for these stages, so they cannot be promoted.
226
+ Before any gate applies, each needs:
227
+
228
+ - a fixed catalog document (skills: option text written for scoring; the
229
+ current 96-byte option limit truncates skill descriptions, so the
230
+ checkpoint's `option_tokens` must be sized for them);
231
+ - an owner-reviewed, family-disjoint holdout of at least 200 cases, at
232
+ least 50 each in English and Traditional Chinese;
233
+ - the paired lexical baseline on that holdout.
234
+
235
+ Their gates take the shape of tier (workflow) and intent (skills). A
236
+ learned stage must also be no worse than the lexical baseline on
237
+ primaryHit/suggestionPrecision (skills) or acceptablePrecision (workflow)
238
+ in the same paired run.
239
+
240
+ ### Every stage
241
+
242
+ | Gate | Bar |
243
+ | --- | --- |
244
+ | repeatability | identical scores and suggestions across two runs |
245
+ | warm p95 | ≤ 250 ms, with the host record ([latency note](system-one-intent-gates.md#latency-note)) |
246
+ | deployable artifact | CPU only, checkpoint ≤ 10 MB, installed by `npm run system-one:install` with pinned hashes |
247
+ | structural controls | unchanged, rollback path live |
248
+ | policyEvidence / liveHostEvidence | reported; required before the default changes from `shadow` |
249
+
250
+ The artifact gate encodes the #255 decision: Laya is a teacher and a
251
+ ceiling reference, never a production candidate.
252
+
253
+ ## Evaluation order
254
+
255
+ 1. Train on train rows only.
256
+ 2. Fit calibration and thresholds on validation (2-fold CV reported).
257
+ 3. **Qualify on validation.** Report every metric above against the
258
+ validation truth. A candidate may spend a holdout run only if it meets
259
+ the holdout bars on validation *and* passes the deployable-artifact
260
+ gate. Otherwise it stays a validation result.
261
+ 4. Freeze the checkpoint hash, calibration, thresholds, evaluator
262
+ revision and this document.
263
+ 5. Score the holdout once, with two runs for repeatability.
264
+ 6. A fail is final for that candidate. A new candidate restarts at step 1
265
+ with a recorded reason.
266
+
267
+ ## Holdout budget
268
+
269
+ Every holdout run spends the holdout a little: each published result makes
270
+ later choices less independent. Each run is recorded in the evidence
271
+ directory with candidate hash, date and aggregate result.
272
+
273
+ - Intent holdout, spent before this document: n-gram intent v1,
274
+ fine-tuned Laya (single-winner), fine-tuned Laya (relevance shadow).
275
+ - Tier holdout, spent before this document: tinyx v1 and the n-gram
276
+ versions reported in #233, plus the lexical baseline.
277
+
278
+ Under this document, each holdout allows **one** further candidate run.
279
+ Before a second run, the owner adds at least 50 freshly reviewed cases (at
280
+ least 20 per language) that no earlier candidate or threshold saw, and the
281
+ gates are read on the enlarged holdout.
282
+
283
+ Earlier holdout results are historical. They are never re-scored under
284
+ these gates to claim a pass.
285
+
286
+ ## Non-goals
287
+
288
+ - The single-winner path (`contract.decide`, n-gram provider, lexical
289
+ fallback) stays live as rollback until a stage is promoted.
290
+ - The intent taxonomy and the owner-reviewed holdouts do not change here.
291
+ - Private prompts never enter this repository; public evidence holds
292
+ aggregates, hashes and per-option metrics only.
@@ -56,7 +56,7 @@ prompts written by an agent, not by the owner. VS Code terminal notifications
56
56
  - **Deduplication**: exact duplicates after whitespace normalization are dropped. The first
57
57
  occurrence is kept.
58
58
  - **Holdout leakage**: a prompt is dropped if its character-trigram Jaccard similarity to any
59
- holdout prompt is at least 0.5. The same applies if either prompt contains the other after
59
+ prompt of the tier or intent holdout is at least 0.5. The same applies if either prompt contains the other after
60
60
  normalization and the shorter one is more than 12 characters long. This matters because
61
61
  the holdout includes rewrites of local prompt shapes.
62
62
  - **Family**: the source session. Prompts from one conversation never span splits.
@@ -65,10 +65,11 @@ prompts written by an agent, not by the owner. VS Code terminal notifications
65
65
 
66
66
  ## Privacy
67
67
 
68
- - Collected prompts and labels stay on the local machine under
69
- `~/.agents/harness-everything/system-one/training/` and are **never committed**. The
70
- repository holds only the scripts, aggregate statistics and the SHA-256 of each dataset
71
- file.
68
+ - Collected prompts and labels are **never committed to this public repository**. They
69
+ live under `~/.agents/harness-everything/system-one/training/` on the owner's machine.
70
+ A curated copy, for cloud sessions, is in the owner's private repository
71
+ `dyphn1/harness-system-one-data` (`training/` is the `--data-dir`). This repository holds
72
+ only the scripts, aggregate statistics and the SHA-256 of each dataset file.
72
73
  - Email addresses and user home paths are replaced with placeholders before storage. Names
73
74
  and product terms are not reliably detectable and remain.
74
75
  - The released weights come from a 4-way byte-level classifier with about 0.7M parameters. It
@@ -88,6 +89,31 @@ prompts written by an agent, not by the owner. VS Code terminal notifications
88
89
  labels already written. Every returned index must be labeled exactly once with
89
90
  `tier1`, `tier2`, `tier3` or `null`. A malformed batch is retried once and then
90
91
  recorded as failed. It is never partially accepted.
92
+ - `--model` and `--engine codex` choose another labeler; the label rows record it.
93
+ `--batch N` changes the batch size.
94
+ - `--task intent --scores` asks for [relevance scores](system-one-intent.md#relevance-scores).
95
+ Each reply names a primary and scores every intent from 0 to 1 in steps of 0.05. A
96
+ reply is malformed when it misses an intent, leaves the scale, ties two scores above 0,
97
+ has more than eight zeros, names a primary that is not the unique top score of at
98
+ least 0.6, or has a `null` primary with an intent above 0.2. The owner chose Haiku
99
+ for this labeling.
100
+ - `--scale` scales each non-`null` scored label so its top score is 1: every score is
101
+ divided by the top score. Haiku puts most top scores at 0.75 to 0.85, and the owner
102
+ chose to read them as relative (2026-09-24). The bands and the secondary intents are
103
+ derived from the scaled scores; the row keeps the model's scores as `raw`.
104
+ - `--escalate-model M` labels a prompt a second time with model `M` when the first
105
+ label lags:
106
+ - `--escalate-margin R`: the relative margin, (top − second) / top, is below `R`.
107
+ The owner chose this lagging indicator to keep Sonnet to few prompts. `R` = 0.3
108
+ sends about 11% of Haiku's non-`null` training labels to Sonnet; it was read from
109
+ that distribution, not from the holdout. A `null` label is never escalated.
110
+ - Otherwise `--escalate-below` (default 0.8): the top score is below it. On the
111
+ training prompts this sent 69% to Sonnet, and the owner stopped it for its cost.
112
+ - The second label replaces the first; when the second run fails, the first label
113
+ stays. The label rows record the model that produced them, and a resumed run skips
114
+ prompts that were already escalated. The holdout check reports how many prompts were
115
+ escalated. `rescale --in A --out B` rewrites a label file with scaled labels (last
116
+ row per prompt).
91
117
  - **Labeler check**: the labeler labels the 215 holdout prompts once, before any training
92
118
  data is labeled. The report gives agreement with the owner's gold and the per-class recall.
93
119
  - Labeling proceeds only when agreement is at least 80%.
@@ -104,7 +130,38 @@ prompts written by an agent, not by the owner. VS Code terminal notifications
104
130
 
105
131
  ## Training and calibration
106
132
 
107
- - **Model**: a new `cua_s1` `tinyx` scorer built with `make_system`.
133
+ - **Tier engine**: `scripts/system-one-train-ngram.py` trains the `ngram` transport, a
134
+ multinomial logistic regression over hashed character n-gram features. Featurization
135
+ follows [system-one-routing.md](system-one-routing.md#n-gram-provider-phase-4).
136
+ - The trainer uses torch for the optimizer only. The features are stdlib Python and match
137
+ the Node provider exactly.
138
+ - The configuration (`nmax`, class weighting, weight decay) is chosen on validation
139
+ macro-F1. The first exploration picked `nmax` 3, square-root inverse-frequency class
140
+ weights and a weight decay of 1e-4.
141
+ - The output is the `.bin` weights, the `.json` sidecar and validation scores for
142
+ calibration.
143
+ - **Intent engine**: the same trainer with `--task intent` trains the intent stage
144
+ ([system-one-intent.md](system-one-intent.md)).
145
+ - Labels come from `labels-intent.jsonl`, and owner corrections from
146
+ `owner-overrides-intent.jsonl`. `owner-excluded.jsonl` applies to both stages. The
147
+ catalog comes from the intent holdout.
148
+ - Targets are soft. The primary gets 1.0 when there is no secondary intent. Otherwise
149
+ the primary gets 0.6 and the secondary intents share 0.4 equally. `null` maps to
150
+ `unclassified` with 1.0.
151
+ - Validation scores keep each row's secondary intents for calibration.
152
+ - `--target scores` trains on the scored labels in `labels-intent-scores.jsonl`
153
+ instead. Each intent is its own binary target with its score as the soft value, and
154
+ the loss is a per-intent binary cross-entropy. `unclassified` scores 0.6 when the
155
+ primary is `null` and 0 otherwise. An owner override without scores counts as 0.6
156
+ for its primary and 0.4 for its secondary intents. The class weights come from the
157
+ score mass of each intent.
158
+ - The artifact format is unchanged. The provider still returns the softmax over the
159
+ same logits, which ranks the intents in relevance order, so the contract's
160
+ probability vector and the calibration below stay as they are. Handing the absolute
161
+ relevance bands to the skills stage is a separate contract change.
162
+ - The `tinyx` path below is kept for comparison. Its first checkpoint learned little beyond
163
+ class priors.
164
+ - **Model** (`tinyx`): a new `cua_s1` `tinyx` scorer built with `make_system`.
108
165
  - Config: `width` 128, `rank` 128, `layers` 2, `heads` 4, `context_tokens` 1024 and
109
166
  `option_tokens` 96.
110
167
  - Weights are initialized fresh, not from the forms checkpoint, whose domain and
@@ -116,7 +173,12 @@ prompts written by an agent, not by the owner. VS Code terminal notifications
116
173
  - The epoch with the lowest validation negative log-likelihood is kept.
117
174
  - The checkpoint metadata records the dataset hashes, the config and the validation metrics.
118
175
  - **Calibration**: `minConfidence` and `minMargin` are chosen on validation to maximize
119
- coverage, subject to an accepted precision of at least 98%. The router currently uses the
176
+ coverage, subject to an accepted precision of at least 85% (the owner's advisory target).
177
+ For the intent stage (`--task intent`), accepted precision is the mean graded agreement
178
+ of accepted rows. A predicted secondary is any other intent whose probability reaches
179
+ `secondaryThreshold`. That threshold is chosen from 0.05 to 0.50 in steps of 0.05, to
180
+ maximize micro-F1 against the labeled secondary intents, and it is chosen before the
181
+ acceptance thresholds. The router currently uses the
120
182
  Phase 0 defaults (0.9 and 0.2). Carrying calibrated thresholds into the manifest is a
121
183
  separate contract change.
122
184
 
@@ -4,7 +4,7 @@ description: "Route software/project work through Harness: classify tier/topolog
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: Miya Daniel
7
- version: 0.26.0
7
+ version: 0.30.0
8
8
  ---
9
9
 
10
10
  # Harness Everything
@@ -105,6 +105,15 @@ function persistWorkflow(contract, payload, prompt) {
105
105
  return { ...context, hooksRoot, retained: false, memoryCapability };
106
106
  }
107
107
 
108
+ // Printed last so it is the nearest instruction when the turn ends (docs/system-one-observations.md#label-line).
109
+ function printLabelContract() {
110
+ console.log('\n=> TURN LABEL LINE (MUST, EVERY TURN — short answers, lookups and trivial edits included):');
111
+ console.log(' - The last line of your final message MUST be one hidden label line with your own post-work judgement, not a copy of the router tier above:');
112
+ console.log(' <!-- harness-label {"v":1,"validity":"actionable|invalid","contextDependent":true|false,"tier":"tier1|tier2|tier3|null","intents":["up to 3 of explain,discuss,git,fix,edit,feature,refactor,review,test,docs,plan,investigate"],"workflow":"strategy id or null","skills":["skills used"]} -->');
113
+ console.log(' - tier1: answer, lookup, status or git operation with no code change; tier2: bounded change; tier3: new feature, refactor, redefinition or cross-component work. Use null only when validity is invalid.');
114
+ console.log(' - It is invisible in rendered Markdown and records local training data; it never gates routing.');
115
+ }
116
+
108
117
  function run(raw) {
109
118
  const promptArg = process.argv.slice(2).join(' ');
110
119
  let payload = null;
@@ -162,6 +171,7 @@ function run(raw) {
162
171
  } else {
163
172
  console.log(' - Runtime state was not persisted; reminders may be less precise, but execution remains available.');
164
173
  }
174
+ printLabelContract();
165
175
  if (failure) console.error('[Workflow Reminder] routing state was not persisted: ' + failure.message);
166
176
  if (result && result.status !== 0) console.error('[Workflow Reminder] router returned status ' + result.status + '; contract observation is degraded, not blocking.');
167
177
  }
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+ // Fixed System One catalogs, one per stage (docs/system-one-routing.md#staged-classification).
3
+ // Option order is the scorer's output order; changing it invalidates trained weights.
4
+ const { TIER_OPTIONS } = require('./router');
5
+ const INTENT_OPTIONS = Object.freeze([
6
+ { id: 'explain', text: 'Answer a question or explain how something works.' },
7
+ { id: 'discuss', text: 'Weigh options, give an opinion or settle a decision.' },
8
+ { id: 'git', text: 'Git or GitHub housekeeping: commit, push, branch, pull request.' },
9
+ { id: 'fix', text: 'Repair wrong behavior: a bug, crash, failing test or build.' },
10
+ { id: 'edit', text: 'Change an existing value, setting, text or behavior on request.' },
11
+ { id: 'feature', text: 'Add new behavior: a command, option, format or script.' },
12
+ { id: 'refactor', text: 'Restructure or unify existing code without new behavior.' },
13
+ { id: 'review', text: 'Evaluate an existing artifact against a standard.' },
14
+ { id: 'test', text: 'Run tests or builds, or write tests as the main deliverable.' },
15
+ { id: 'docs', text: 'Write or edit documentation as the main deliverable.' },
16
+ { id: 'plan', text: 'Produce a plan, specification or breakdown before implementation.' },
17
+ { id: 'investigate', text: 'Find facts or a cause and report back without changing anything.' },
18
+ { id: 'unclassified', text: 'Unclear intent, insufficient context or outside these categories.' },
19
+ ].map(Object.freeze));
20
+ const CATALOGS = Object.freeze({ tier: TIER_OPTIONS, intent: INTENT_OPTIONS });
21
+ // Gold labels of a stage: its catalog ids, with the abstaining option recorded as null.
22
+ function goldLabels(task) {
23
+ if (!Object.hasOwn(CATALOGS, task)) throw new TypeError('unknown-task');
24
+ return CATALOGS[task].map(o => (o.id === 'unclassified' ? null : o.id));
25
+ }
26
+ // Stages whose labels carry secondary intents beside the primary choice (docs/system-one-intent.md).
27
+ const MULTI = Object.freeze(['intent']);
28
+ // Secondary labels: distinct catalog ids other than the primary; none when the primary abstains.
29
+ function validSecondary(task, primary, secondary) {
30
+ if (!MULTI.includes(task)) return secondary === undefined;
31
+ const ids = goldLabels(task).filter(g => g !== null);
32
+ return Array.isArray(secondary) && new Set(secondary).size === secondary.length
33
+ && secondary.every(s => ids.includes(s) && s !== primary) && (primary !== null || secondary.length === 0);
34
+ }
35
+ module.exports = { INTENT_OPTIONS, CATALOGS, MULTI, goldLabels, validSecondary };
@@ -42,4 +42,48 @@ function decide(req, res, policy = {}) {
42
42
  return { status: reason === 'scored' ? 'accepted' : 'abstain', reason,
43
43
  selectedId: reason === 'scored' ? ranked[0].id : null, confidence, margin, model: { ...res.model } };
44
44
  }
45
- module.exports = { createRequest, validateRequest, decide };
45
+ // Relevance-native decisions (#233): every option except unclassified is an
46
+ // independent Bernoulli against its own threshold. No simplex rule applies:
47
+ // the raw probabilities must each stand in 0..1 but need not sum to one.
48
+ function validThresholds(req, thresholds) {
49
+ const ids = req.options.map(o => o.id).filter(id => id !== 'unclassified');
50
+ if (!thresholds || typeof thresholds !== 'object' || Array.isArray(thresholds)) return false;
51
+ const keys = Object.keys(thresholds);
52
+ return keys.length === ids.length && ids.every(id => probability(thresholds[id]))
53
+ && keys.every(k => ids.includes(k));
54
+ }
55
+ function decideIntents(req, res, policy = {}) {
56
+ validateRequest(req);
57
+ if (!policy || typeof policy !== 'object' || Array.isArray(policy)
58
+ || Object.keys(policy).some(k => !['domain', 'thresholds'].includes(k))
59
+ || !validThresholds(req, policy.thresholds)) fail('invalid-policy');
60
+ const { domain = 'harness-routing-v1', thresholds } = policy;
61
+ if (!text(domain)) fail('invalid-policy');
62
+ const empty = { status: 'invalid-output', reason: 'invalid-response', selectedId: null,
63
+ confidence: null, margin: null, model: null, intents: null };
64
+ if (!exact(res, ['schemaVersion', 'requestHash', 'catalogHash', 'model', 'scores'])
65
+ || res.schemaVersion !== 1 || res.requestHash !== req.requestHash || res.catalogHash !== req.catalogHash
66
+ || !exact(res.model, ['id', 'revision', 'domain']) || !Object.values(res.model).every(v => text(v))
67
+ || !Array.isArray(res.scores) || res.scores.length !== req.options.length
68
+ || res.scores.some((s, i) => !exact(s, ['id', 'probability']) || s.id !== req.options[i].id || !probability(s.probability))) return empty;
69
+ const model = { ...res.model };
70
+ if (model.domain !== domain) {
71
+ return { status: 'abstain', reason: 'domain-mismatch', selectedId: null,
72
+ confidence: null, margin: null, model, intents: null };
73
+ }
74
+ const intents = {};
75
+ for (const s of res.scores) {
76
+ if (s.id === 'unclassified') continue;
77
+ intents[s.id] = { probability: s.probability, fired: s.probability >= thresholds[s.id] };
78
+ }
79
+ const fired = Object.entries(intents).filter(([, v]) => v.fired).sort((a, b) => b[1].probability - a[1].probability);
80
+ if (!fired.length) {
81
+ return { status: 'abstain', reason: 'no-intent-fired', selectedId: null,
82
+ confidence: null, margin: null, model, intents };
83
+ }
84
+ const rest = Object.values(intents).filter(v => v !== fired[0][1]).map(v => v.probability);
85
+ const confidence = fired[0][1].probability;
86
+ return { status: 'accepted', reason: 'scored', selectedId: fired[0][0], confidence,
87
+ margin: confidence - Math.max(...rest, 0), model, intents };
88
+ }
89
+ module.exports = { createRequest, validateRequest, decide, decideIntents };