harness-everything 0.29.0 → 0.30.1

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 (108) 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 +42 -0
  5. package/PRIVACY.md +9 -0
  6. package/benchmarks/fixtures/system-one-intent-holdout-draft.json +2228 -0
  7. package/benchmarks/fixtures/system-one-intent-holdout.json +16509 -0
  8. package/benchmarks/fixtures/system-one-intent-reviews.json +2723 -0
  9. package/benchmarks/results/system-one/intent-ngram-v1-2026-09-24/README.md +64 -0
  10. package/benchmarks/results/system-one/intent-ngram-v1-2026-09-24/report.json +15177 -0
  11. package/benchmarks/results/system-one/labeler-holdout-check-intent-2026-09-24/report.json +2976 -0
  12. package/benchmarks/results/system-one/labeler-holdout-check-intent-scores-haiku-2026-09-24/report-005-steps.json +6125 -0
  13. package/benchmarks/results/system-one/labeler-holdout-check-intent-scores-haiku-2026-09-24/report-haiku-sonnet-escalation.json +6344 -0
  14. package/benchmarks/results/system-one/labeler-holdout-check-intent-scores-haiku-2026-09-24/report-scaled-margin-escalation.json +6206 -0
  15. package/benchmarks/results/system-one/labeler-holdout-check-intent-scores-haiku-2026-09-24/report.json +6103 -0
  16. package/benchmarks/results/system-one/laya-ft-2026-09-25/README.md +83 -0
  17. package/benchmarks/results/system-one/laya-ft-2026-09-25/report.json +71 -0
  18. package/benchmarks/results/system-one/laya-ft-2026-09-25/shadow-report.json +44 -0
  19. package/benchmarks/results/system-one/laya-zs-2026-09-25/README.md +68 -0
  20. package/benchmarks/results/system-one/laya-zs-2026-09-25/report.json +173 -0
  21. package/ci/fable-mode-test.js +16 -0
  22. package/ci/mechanism-34-observations.test.js +550 -0
  23. package/ci/mechanism-system-one-collect.test.js +16 -0
  24. package/ci/mechanism-system-one-intent.test.js +406 -0
  25. package/ci/mechanism-system-one-laya.test.js +45 -0
  26. package/ci/mechanism-system-one-relevance.test.js +112 -0
  27. package/ci/mechanism-system-one-resident.test.js +16 -0
  28. package/ci/system-one-laya-calibrate-test.py +65 -0
  29. package/ci/system-one-laya-export-test.py +155 -0
  30. package/ci/system-one-laya-label-test.py +201 -0
  31. package/ci/system-one-laya-perintent-test.py +60 -0
  32. package/ci/system-one-laya-provider-test.py +115 -0
  33. package/ci/system-one-laya-train-test.py +93 -0
  34. package/docs/routing.md +25 -0
  35. package/docs/system-one-intent-gates.md +102 -0
  36. package/docs/system-one-intent.md +173 -0
  37. package/docs/system-one-laya-training.md +38 -0
  38. package/docs/system-one-observations.md +206 -0
  39. package/docs/system-one-routing.md +25 -10
  40. package/docs/system-one-suggestion-gates.md +292 -0
  41. package/docs/system-one-training.md +56 -6
  42. package/docs/workflows/fable-mode.md +7 -0
  43. package/fable-mode/SKILL.md +1 -1
  44. package/fable-mode/agents/fable-orchestrator.md +4 -4
  45. package/fable-mode/agents/fable-worker-haiku.md +4 -4
  46. package/fable-mode/agents/fable-worker-sonnet.md +4 -4
  47. package/fable-mode/execution-guardrails/SKILL.md +5 -3
  48. package/fable-mode/execution-guardrails/references/guardrail-rules.md +12 -8
  49. package/fable-mode/fable-haiku/SKILL.md +1 -1
  50. package/fable-mode/fable-opus/SKILL.md +1 -1
  51. package/fable-mode/fable-sonnet/SKILL.md +1 -1
  52. package/harness-everything/SKILL.md +1 -1
  53. package/harness-everything/scripts/kernel-router.js +10 -0
  54. package/harness-everything/scripts/system-one/catalogs.js +35 -0
  55. package/harness-everything/scripts/system-one/contract.js +45 -1
  56. package/harness-everything/scripts/system-one/evaluate.js +115 -19
  57. package/harness-everything/scripts/system-one/intent.js +22 -0
  58. package/harness-everything/scripts/system-one/laya_adapter.py +141 -0
  59. package/harness-everything/scripts/system-one/provider.js +10 -3
  60. package/harness-everything/scripts/system-one/resident.js +8 -3
  61. package/harness-everything/scripts/system-one/router.js +1 -1
  62. package/hooks/hooks.json +40 -0
  63. package/hooks/scripts/codex-user-prompt.js +39 -0
  64. package/hooks/scripts/lib/observations.js +581 -0
  65. package/hooks/scripts/observation-hook.js +25 -0
  66. package/opencode-plugin/plugin.json +1 -1
  67. package/package.json +1 -1
  68. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  69. package/plugins/harness-everything/hooks/hooks.json +35 -2
  70. package/plugins/harness-everything/hooks/scripts/codex-user-prompt.js +39 -0
  71. package/plugins/harness-everything/hooks/scripts/lib/observations.js +581 -0
  72. package/plugins/harness-everything/hooks/scripts/observation-hook.js +25 -0
  73. package/plugins/harness-everything/plugin.json +1 -1
  74. package/plugins/harness-everything/skills/fable-mode/SKILL.md +1 -1
  75. package/plugins/harness-everything/skills/fable-mode/agents/fable-orchestrator.md +4 -4
  76. package/plugins/harness-everything/skills/fable-mode/agents/fable-worker-haiku.md +4 -4
  77. package/plugins/harness-everything/skills/fable-mode/agents/fable-worker-sonnet.md +4 -4
  78. package/plugins/harness-everything/skills/fable-mode/execution-guardrails/SKILL.md +5 -3
  79. package/plugins/harness-everything/skills/fable-mode/execution-guardrails/references/guardrail-rules.md +12 -8
  80. package/plugins/harness-everything/skills/fable-mode/fable-haiku/SKILL.md +1 -1
  81. package/plugins/harness-everything/skills/fable-mode/fable-opus/SKILL.md +1 -1
  82. package/plugins/harness-everything/skills/fable-mode/fable-sonnet/SKILL.md +1 -1
  83. package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
  84. package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router.js +10 -0
  85. package/plugins/harness-everything/skills/harness-everything/scripts/system-one/catalogs.js +35 -0
  86. package/plugins/harness-everything/skills/harness-everything/scripts/system-one/contract.js +45 -1
  87. package/plugins/harness-everything/skills/harness-everything/scripts/system-one/evaluate.js +115 -19
  88. package/plugins/harness-everything/skills/harness-everything/scripts/system-one/intent.js +22 -0
  89. package/plugins/harness-everything/skills/harness-everything/scripts/system-one/laya_adapter.py +141 -0
  90. package/plugins/harness-everything/skills/harness-everything/scripts/system-one/provider.js +10 -3
  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 +17 -5
  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 +163 -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 +66 -14
@@ -0,0 +1,93 @@
1
+ """Contract tests for the single-device Laya trainer (#255 Phase 3).
2
+
3
+ Pure parts run on stdlib; torch parts skip when torch is missing
4
+ (same convention as S1-I08/S1-I13). Run with:
5
+ python3 ci/system-one-laya-train-test.py
6
+ """
7
+ import importlib.util
8
+ import sys
9
+ import unittest
10
+ from pathlib import Path
11
+
12
+ sys.dont_write_bytecode = True # never leave __pycache__ in the source tree that packaging copies
13
+ path = Path(__file__).resolve().parents[1] / 'scripts/system-one-train-laya.py'
14
+ spec = importlib.util.spec_from_file_location('laya_trainer', path)
15
+ laya_trainer = importlib.util.module_from_spec(spec)
16
+ spec.loader.exec_module(laya_trainer)
17
+
18
+ try:
19
+ import torch # noqa: F401
20
+ HAS_TORCH = True
21
+ except ImportError:
22
+ HAS_TORCH = False
23
+
24
+
25
+ class CalibSplitTests(unittest.TestCase):
26
+ def test_holdout_is_stable_disjoint_and_ten_percent(self):
27
+ items = [{'id': i} for i in range(1000)]
28
+ a_train, a_calib = laya_trainer.calib_split(items, seed=20260922, frac=0.1)
29
+ b_train, b_calib = laya_trainer.calib_split(items, seed=20260922, frac=0.1)
30
+ self.assertEqual(a_calib, b_calib, 'same seed replays the holdout')
31
+ self.assertEqual(len(a_calib), 100)
32
+ self.assertEqual(len(set(map(str, a_train)) & set(map(str, a_calib))), 0, 'disjoint')
33
+ c_train, c_calib = laya_trainer.calib_split(items, seed=1, frac=0.1)
34
+ self.assertNotEqual(a_calib, c_calib, 'seed matters')
35
+
36
+ def test_small_inputs_keep_at_least_one_calib(self):
37
+ _, calib = laya_trainer.calib_split([{'id': i} for i in range(5)], seed=7, frac=0.1)
38
+ self.assertEqual(len(calib), 1)
39
+
40
+ def test_prompt_groups_do_not_cross_split_and_calib_deduplicates_oversamples(self):
41
+ items = []
42
+ for prompt_id in range(20):
43
+ items.append({'promptId': str(prompt_id), 'intent': 'fix'})
44
+ items.append({'promptId': str(prompt_id), 'intent': 'refactor'})
45
+ items.append({'promptId': str(prompt_id), 'intent': 'refactor'}) # oversampled duplicate
46
+ train, calib = laya_trainer.calib_split(items, seed=20260922, frac=0.1)
47
+ train_prompts = {it['promptId'] for it in train}
48
+ calib_prompts = {it['promptId'] for it in calib}
49
+ self.assertFalse(train_prompts & calib_prompts, 'a prompt must stay wholly on one side')
50
+ calib_keys = [(it['promptId'], it['intent']) for it in calib]
51
+ self.assertEqual(len(calib_keys), len(set(calib_keys)),
52
+ 'oversampled duplicates must not overweight temperature fitting')
53
+ self.assertEqual(len(calib), 4, '10% of 40 de-duplicated prompt-intent identities')
54
+ self.assertEqual(sum(1 for it in train if it['intent'] == 'refactor'), 2 * len(train_prompts),
55
+ 'training keeps oversampled copies for non-calibration prompt groups')
56
+
57
+
58
+ class IntentWeightTests(unittest.TestCase):
59
+ def test_default_weights_are_one(self):
60
+ self.assertEqual(laya_trainer.intent_weights(['fix', 'test'], {}), [1.0, 1.0])
61
+
62
+ def test_table_applies_per_item_intent(self):
63
+ self.assertEqual(laya_trainer.intent_weights(['refactor', 'fix', 'refactor'], {'refactor': 2.0}),
64
+ [2.0, 1.0, 2.0])
65
+
66
+ def test_bad_table_fails(self):
67
+ with self.assertRaisesRegex(ValueError, 'intent-weights'):
68
+ laya_trainer.intent_weights(['fix'], {'nope': 2.0})
69
+ with self.assertRaisesRegex(ValueError, 'intent-weights'):
70
+ laya_trainer.intent_weights(['fix'], {'fix': 0})
71
+
72
+
73
+ @unittest.skipUnless(HAS_TORCH, 'torch not installed')
74
+ class TorchTests(unittest.TestCase):
75
+ def test_collate_shapes(self):
76
+ items = [{'ids': [1, 2, 3], 'markers': [0, 2], 'qtype': 2,
77
+ 'target': [0.3, 0.7], 'label': 1} for _ in range(4)]
78
+ batch = laya_trainer.collate(items, pad_id=0)
79
+ self.assertEqual(tuple(batch['input_ids'].shape), (4, 3))
80
+ self.assertEqual(tuple(batch['marker_pos'].shape), (4, 2))
81
+ self.assertTrue(bool((batch['target'].sum(-1) - 1.0 < 1e-6).all()))
82
+
83
+ def test_temperature_fit_is_finite_and_bounded(self):
84
+ import random
85
+ rng = random.Random(0)
86
+ sel = [([rng.uniform(-1, 1), rng.uniform(-1, 1)],
87
+ [0.4, 0.6]) for _ in range(30)]
88
+ t = laya_trainer.fit_one_temp(sel)
89
+ self.assertTrue(0.1 <= t <= 10.0)
90
+
91
+
92
+ if __name__ == '__main__':
93
+ unittest.main()
package/docs/routing.md CHANGED
@@ -64,6 +64,31 @@ Keep one short evidence item inline when that reads better; use nested bullets f
64
64
 
65
65
  This MUST is semantic rather than a hard runtime lock: hosts may observe/remind with different strength, but lack of a blocking hook does not downgrade the contract to optional advice.
66
66
 
67
+ ### Turn label line
68
+
69
+ End the final message of **every** turn with one line that rendered Markdown hides. This includes short answers, lookups, and turns that end with a question back to the user; unlike the Harness Status, it is not limited to non-trivial work:
70
+
71
+ ```
72
+ <!-- harness-label {"v":1,"validity":"actionable","contextDependent":false,"tier":"tier2","intents":["fix","test"],"workflow":"iterative-single","skills":["tdd"]} -->
73
+ ```
74
+
75
+ It states the agent's own judgement after the work, not a copy of the router's suggestion: `validity` (`actionable` or `invalid`), `contextDependent` (the prompt text alone would not have been enough), `tier` under tier rules v2, up to three `intents`, the `workflow` strategy and the `skills` actually used. The observation collector stores it as training data ([system-one-observations.md](system-one-observations.md)). A missing or malformed line is recorded with a reason code; it never blocks a turn or changes routing.
76
+
77
+ Tier values:
78
+
79
+ | Tier | Meaning |
80
+ | --- | --- |
81
+ | `tier1` | answer, lookup, status or Git operation; no file change |
82
+ | `tier2` | a bounded file change: code, config or docs |
83
+ | `tier3` | a new feature, a refactor, a redefinition, or cross-component work |
84
+ | `null` | only when `validity` is `invalid` |
85
+
86
+ The router prints this contract as the **last** section of its output, so it
87
+ is the nearest instruction when the turn ends. In live Claude Code runs the
88
+ label appeared on 1 of 4 turns while it sat inside the Harness Status contract
89
+ (which is scoped to non-trivial work), and on 5 of 6 turns as its own final
90
+ section.
91
+
67
92
  ## Suggested skills vs. selected workflow
68
93
 
69
94
  These are intentionally different concepts.
@@ -0,0 +1,102 @@
1
+ # System One intent promotion gates (relevance-native)
2
+
3
+ Status: **superseded** by [system-one-suggestion-gates.md](system-one-suggestion-gates.md).
4
+ This table remains the contract for the runs made under it (for example
5
+ `benchmarks/results/system-one/laya-ft-2026-09-25/`); those results are
6
+ never re-scored under the new gates.
7
+
8
+ Originally decided in
9
+ [#233](https://github.com/dyphn1/Harness-everything/issues/233) (accept
10
+ relevance-native direction, keep production migration gated); evidence in
11
+ [#255](https://github.com/dyphn1/Harness-everything/issues/255). Thresholds
12
+ below are proposals: they take effect only after owner review, and the final
13
+ holdout runs **once** against the reviewed numbers. No threshold, gate, or
14
+ wording change may use the frozen holdout as feedback.
15
+
16
+ This replaces the single-winner intent gates (acceptedPrecision ≥ 0.85 at
17
+ coverage ≥ 0.8), which a 57%-precision / 66%-recall multi-label system
18
+ cannot clear by construction: owner prompts carry about 2.5 secondaries on
19
+ average, so a single-winner contract caps coverage near 6% no matter how
20
+ good the ranking is.
21
+
22
+ ## Ground truth
23
+
24
+ - **Training/validation truth.** Teacher relevance scores binarized at the
25
+ owner secondary band: intent is positive iff score ≥ 0.4
26
+ ([system-one-intent.md](system-one-intent.md#relevance-scores)).
27
+ - **Holdout truth.** Owner-reviewed `{gold, secondary}` per case. A predicted
28
+ set is compared as sets; graded agreement still applies where the report
29
+ cites it, but promotion gates use the binary metrics below.
30
+
31
+ ## Threshold flow (frozen)
32
+
33
+ 1. Train on train rows only.
34
+ 2. Fit one threshold per intent (max-F1) on validation, 2-fold CV reported.
35
+ 3. Freeze thresholds, code, and this document.
36
+ 4. Score the frozen holdout exactly once (two runs for repeatability).
37
+ 5. Gate on the table below. A fail is a fail; re-tuning restarts at step 2
38
+ with a documented reason, never at step 4.
39
+
40
+ ## Metrics
41
+
42
+ - **micro precision / recall / F1** over all (case, intent) pairs.
43
+ - **macro F1**: mean of per-intent F1.
44
+ - **per-intent precision / recall / support** (support = holdout positives).
45
+ - **predicted cardinality**: mean fires per prompt vs gold mean.
46
+ - **abstain rate**: prompts with no intent fired.
47
+ - **exact-set-match**: all 12 decisions correct (reported, not gated).
48
+ - **weak-intent precision**: minimum precision over intents with support.
49
+ - **family consistency**: model vs gold (same definition as the old gates).
50
+ - **repeatability**: byte-identical decisions across two runs.
51
+ - **latency**: warm p50/p95 per decision batch.
52
+ - **policy evidence**: structural controls untouched (floor, fallback,
53
+ abstain path, rollback availability).
54
+
55
+ ## Proposed gates
56
+
57
+ Anchors: per-intent train-majority predicts nothing (micro-F1 0), so gates
58
+ are absolute. Reference measurement is the fine-tuned multilingual model on
59
+ validation teacher proxy (micro-F1 CV 0.60, fires 2.0 vs truth 1.8); holdout
60
+ gold is stricter, so thresholds sit below it.
61
+
62
+ | Metric | Gate | Rationale |
63
+ | --- | --- | --- |
64
+ | micro-F1 | ≥ 0.55 | CV 0.60 minus holdout strictness margin |
65
+ | macro-F1 | ≥ 0.50 | reference ≈ 0.61 |
66
+ | per-intent F1 (support ≥ 10) | ≥ 0.30 | keeps refactor-class weak spots visible, not fatal |
67
+ | weak-intent precision | ≥ 0.30 | tripwire for silent degradation |
68
+ | mean fires | within gold mean ± 1.0 | blocks spray (4+) and collapse (<1) |
69
+ | abstain rate | ≤ 0.20 | reference 0.09 |
70
+ | family consistency (model) | ≥ gold | same bar as the old gates |
71
+ | repeatability | == 1 | deterministic inference required |
72
+ | warm p95 | ≤ 250 ms | environment-tolerant bar (see latency note) |
73
+ | structural controls | unchanged + rollback path live | migration stays shadow-compatible |
74
+
75
+ `rolloutReady` is true only when every gate passes. `policyEvidence` and
76
+ `liveHostEvidence` remain false until independently produced; they are
77
+ reported, not bypassed.
78
+
79
+ ## Latency note
80
+
81
+ The old single-winner gate used warm p95 ≤ 100 ms. That bar flakes on
82
+ developer hardware for model-backed transports: the fine-tuned multilingual
83
+ model measured p50 135 ms / p95 179 ms on a Mac mini M4 over 444 warm
84
+ samples, and thermal throttling, GPU contention, or memory pressure can push
85
+ any single run past 100 ms without a code change. The 250 ms bar still rules
86
+ out pathological transports (per-request model reloads at 7–10 s, cold CPU
87
+ fallbacks in seconds) while tolerating host variance. For reference, the
88
+ MLX port of the same model class measures 7–13 ms; transport choice, not
89
+ the gate, is where that gap belongs. Latency evidence always ships with the
90
+ host record (cpu/os/node/python); a gate pass on one host is not a claim
91
+ about another.
92
+
93
+ ## Non-goals and invariants
94
+
95
+ - The single-winner path (contract.decide as shipped, n-gram provider,
96
+ lexical fallback) stays live as rollback until promotion completes.
97
+ - Tier structural floor, explicit workflow constraints, action/security
98
+ approval, and scope/memory ownership stay outside learned scoring.
99
+ - Taxonomy is frozen: 12 intents in catalog order plus abstain. Wording
100
+ versions are recorded per artifact but never change categories.
101
+ - Private prompts never enter the public repository; public evidence holds
102
+ aggregates, hashes, and per-intent metrics only.
@@ -0,0 +1,173 @@
1
+ # System One intent stage: catalog and labeling
2
+
3
+ This document defines the second stage of [System One](system-one-routing.md#staged-classification):
4
+ the **intent** of a prompt, meaning the kind of work it asks for (issue
5
+ [#233](https://github.com/dyphn1/Harness-everything/issues/233)). The tier stage is
6
+ defined in [system-one-corpus.md](system-one-corpus.md) and is unchanged. The intent
7
+ stage reuses the tier pipeline in [system-one-training.md](system-one-training.md) with
8
+ its own catalog, labels, holdout and gates.
9
+
10
+ ## Intent labels
11
+
12
+ An intent label answers one question: **what kinds of work does this prompt ask the
13
+ assistant to do, judged from the prompt and its session context?** The catalog is the
14
+ repository owner's (confirmed 2026-09-24, with `edit` added the same day) and is fixed.
15
+ Its order is the scorer's option order.
16
+
17
+ A label has two parts:
18
+
19
+ - **Primary.** Exactly one intent, or `null`. This is the work that produces the main
20
+ deliverable.
21
+ - **Secondary.** Zero or more other intents that the request also needs. Changing a
22
+ default port, for example, is primary `edit` with secondary `test` and `docs` when
23
+ tests and docs mention the port. Secondary never contains the primary intent or
24
+ `null`, and it is empty when the primary is `null`.
25
+
26
+ | Gold | Use when the prompt asks the assistant to | Examples of shape |
27
+ | --- | --- | --- |
28
+ | `explain` | Answer a question or explain how something works, without deciding anything or changing files | "what does this flag do?", "why is this test slow?" (explanation only), "今天這段 code 在做什麼" |
29
+ | `discuss` | Weigh options, give an opinion, or settle a decision for work in progress | "should we keep the old name?", "1. agree 2. use hash", "你覺得哪個方案好" |
30
+ | `git` | Perform Git or GitHub housekeeping: commit, push, branch, merge, rebase, cherry-pick, tag, open or merge a PR, post a comment | "commit and push", "開 PR", "commit the sub-repos, then the main repo" |
31
+ | `fix` | Repair behavior that is wrong: a bug, a crash, a failing or flaky test, a build or CI failure, a dependency conflict | "the CLI panics here, fix it", "CI times out, find the cause and fix it" |
32
+ | `edit` | Change an existing value, setting, text or behavior on request, when it is neither a bug nor new behavior | "change the default port from 8080 to 3000", "把 timeout 改成 30 秒", "rename the button label to Save" |
33
+ | `feature` | Add new behavior: a command, an option, a format, an endpoint, a script, a migration | "add a --verbose flag", "support YAML input", "寫一個資料遷移腳本" |
34
+ | `refactor` | Restructure or unify existing code without new behavior: rename, move, extract, replace a pattern everywhere, upgrade a dependency | "rename component to module everywhere", "replace console.log with the logger", "把常數搬出去" |
35
+ | `review` | Evaluate an existing artifact against a standard: a PR, a diff, a design, a document, test coverage, a benchmark result | "review this PR", "檢查測試是不是只有 happy path", "audit the skill descriptions" |
36
+ | `test` | Run tests or builds, or write tests as the main deliverable | "run npm test", "add tests for the parser" |
37
+ | `docs` | Write or edit documentation as the main deliverable: README, guides, comments, changelogs, a memory note | "update the install guide", "改成全英文文件" |
38
+ | `plan` | Produce a plan, specification, roadmap, ADR or breakdown before implementation | "plan phase 4", "拆分實作階段", "write an ADR for the new cache" |
39
+ | `investigate` | Find facts or a cause and report back, without being asked to change anything | "find every repo still using the old logger", "查一下為什麼變慢,先不要改" |
40
+ | `null` | Nothing actionable: a bare continuation, a vague reaction, pasted status with no request, or a request outside software work | "go", "嗯", "今天股價多少" |
41
+
42
+ Rules:
43
+
44
+ 1. **One primary intent, then the rest as secondary.** The primary is the work that
45
+ produces the main deliverable. "Fix the bug and add a regression test" is primary
46
+ `fix`, secondary `test`. "Investigate and fix" is primary `fix`, secondary
47
+ `investigate`. "Implement the feature, then commit" is primary `feature`, secondary
48
+ `git`. Add a secondary intent only when the request asks for it or clearly needs it,
49
+ not for every step an assistant might take.
50
+ 2. **Change beats inquiry.** When the prompt asks for a change, label the change, not
51
+ the reading that precedes it. `investigate` and `explain` are for prompts that only
52
+ ask for information.
53
+ 3. **`investigate` against `explain`.** `investigate` needs searching or running
54
+ something to find facts; `explain` can be answered from what is already known or
55
+ shown.
56
+ 4. **`review` against `investigate`.** `review` judges a given artifact against a
57
+ standard; `investigate` looks for a cause or for facts that are not yet known.
58
+ 5. **`fix`, `edit`, `feature` and `refactor`.** Wrong behavior made right is `fix`.
59
+ A requested change to a value, setting, text or existing behavior that was not wrong
60
+ is `edit`. New behavior is `feature`. The same behavior in a new structure is
61
+ `refactor`.
62
+ 6. **Short replies follow the work in progress**, as in the tier rules. A reply that
63
+ asks to change an output takes the intent of that change ("not this format, use the
64
+ table" in a docs session is `docs`). A reply that settles decisions is `discuss`.
65
+ 7. **Intent is independent of tier.** A repository-wide `refactor` is `tier3` and a
66
+ one-line `refactor` is `tier2`; both are `refactor`. Never infer intent from a tier
67
+ label or tier from an intent label.
68
+ 8. **Never derive gold from the lexical router or any model output.** A reviewer may
69
+ read a proposed intent, but the recorded gold is the reviewer's decision.
70
+
71
+ ### Relevance scores
72
+
73
+ A scored label rates every intent of the catalog, except `null`, from 0 to 1 in steps of
74
+ 0.05. The owner set this scale on 2026-09-24 (it replaces a four-level draft of the same
75
+ day). System One only suggests skills, so a scored label is meant to rank every candidate,
76
+ not to pick one.
77
+
78
+ | Score | Band |
79
+ | --- | --- |
80
+ | 0.6 or more | Primary relevance: the prompt asks for this work as a main deliverable |
81
+ | 0.4 to 0.55 | Secondary relevance: the request also needs this work |
82
+ | 0.2 to 0.35 | Related: useful context or a likely follow-up that the prompt does not ask for |
83
+ | 0.05 to 0.15 | Faint: a remote link to the prompt |
84
+ | 0 | Unrelated |
85
+
86
+ Rules:
87
+
88
+ 1. **Score every intent on its own.** This replaces "add a secondary intent only when the
89
+ request asks for it" above for scored labels: every intent gets a considered score.
90
+ The scores are not a budget shared by the intents.
91
+ 2. **No ties.** Two intents never share a score other than 0, so the scores rank the
92
+ whole catalog.
93
+ 3. **At most eight zeros.** At least four intents score above 0.
94
+ 4. **Name one primary.** It is the intent with the highest score, and it scores 0.6 or
95
+ more. Rule 1 above decides which work produces the main deliverable.
96
+ 5. **`null` means nothing actionable.** With a `null` primary, no intent scores more
97
+ than 0.2.
98
+ 6. **Bands give the label.** The secondary intents are every other intent that scores
99
+ 0.4 or more, highest first. Related intents are kept only as scores.
100
+
101
+ ## Holdout
102
+
103
+ The intent holdout reuses the prompts of the tier holdout
104
+ (`benchmarks/fixtures/system-one-holdout.json`). Their families are already
105
+ holdout-only, so no training prompt can leak into them. Each case gets an intent gold
106
+ through the same owner review as the tier holdout. An intent review decision also
107
+ records `secondary`, the list of secondary intents. `accept` and `relabel` refer to the
108
+ primary intent only. The review records live in
109
+ `benchmarks/fixtures/system-one-intent-reviews.json`, and the evaluation corpus is
110
+ `benchmarks/fixtures/system-one-intent-holdout.json`. The `reviewedHoldout` gate is the
111
+ same: at least 200 reviewed cases and at least 50 per language. The report also gives
112
+ the count per intent, because a class with only a few holdout cases cannot be measured.
113
+
114
+ **Scope.** The intent stage runs only on actionable prompts. When the tier stage
115
+ abstains (`unclassified`), no intent is asked. In the first review the owner rejected
116
+ all 25 non-actionable prompts, which left 190 cases, so the holdout gains
117
+ **supplementary intent-only families** (`synthetic:authored`). Those families favor
118
+ the thinnest intents (`test`, `plan`, `discuss`, `review`, `edit`). They are appended
119
+ to the intent draft, reviewed the same way, and added to the training-leakage filter
120
+ like every holdout prompt.
121
+
122
+ ## Scoring
123
+
124
+ System One gives a probability for every intent. The highest is the predicted
125
+ primary, and every other intent at or above a calibrated threshold is a predicted
126
+ secondary. The skills stage consumes the primary intent together with the accepted
127
+ secondary intents.
128
+
129
+ Two intent labels are compared by **graded agreement**. Each side is a primary `p`
130
+ plus a secondary set `S`, and its full set is `{p} ∪ S`. The owner set these grades:
131
+
132
+ | Case | Score |
133
+ | --- | --- |
134
+ | Same primary | 1.0 |
135
+ | Different primaries, and each primary is in the other side's full set (only the order differs) | 0.6 |
136
+ | Different primaries, and only one primary is in the other side's full set; or the order differs but the secondary counts differ by 3 or more | 0.3 |
137
+ | Neither primary is in the other side's full set | 0 |
138
+
139
+ Both sides agreeing on intent but differing in order or in the number of steps means
140
+ the same goal with a different breakdown, not a different goal. Graded agreement is
141
+ reported next to exact primary agreement, never instead of it. The same grades apply
142
+ to the labeler check and to accepted predictions of a model.
143
+
144
+ **Family consistency.** A family groups variants and translations of one scenario.
145
+ The report gives the share of families whose members share one primary intent, for the
146
+ owner's gold and for the predictions. This makes a language-dependent intent visible
147
+ without treating every variant as a translation.
148
+
149
+ ## Training data
150
+
151
+ The collected prompts are labeled a second time, for intent. The labeler returns a
152
+ primary and a secondary list for each prompt. Intent labels are stored beside the tier
153
+ labels, never in place of them. Owner spot-checks for intent show session context, like
154
+ the tier spot-checks. The labeler check against the intent holdout must reach a graded
155
+ agreement of 80% before training data is labeled.
156
+
157
+ **Scored labels.** The second intent labeling, requested by the owner on 2026-09-24,
158
+ asks for [relevance scores](#relevance-scores) instead of a primary and a short list.
159
+ The labeler returns a primary and a score for every intent. Scored labels are stored in
160
+ `labels-intent-scores.jsonl`, beside the earlier intent labels. Each row keeps the
161
+ primary as `gold`, the secondary intents derived from the bands and all the scores. The
162
+ labeler check grades the derived primary and secondary intents against the holdout, as
163
+ above. Its report also gives the mean number of secondary and related intents per
164
+ prompt, next to the owner's mean number of secondary intents.
165
+
166
+ ## Gates
167
+
168
+ The intent stage uses the rollout gates of
169
+ [system-one-routing.md](system-one-routing.md#rollout-gates): accepted precision at least
170
+ 85% (graded agreement over accepted predictions, with exact primary precision
171
+ reported beside it), coverage at least 80%, repeatability, warm p95 at most 100 ms and
172
+ verified provenance. There is no lexical intent router, so the macro-F1 baseline is the
173
+ majority-class predictor on the holdout.
@@ -0,0 +1,38 @@
1
+ # System One Laya weak-intent training contract
2
+
3
+ This document defines the training-data semantics used by the #255 weak-intent experiment.
4
+
5
+ ## Intent-level oversampling
6
+
7
+ `system-one-export-laya.py --oversample intent:factor` is an **intent-item** intervention.
8
+
9
+ For a prompt whose teacher score for the selected intent is at least `0.4`:
10
+
11
+ - only that `promptId + intent` training item is repeated to the requested factor;
12
+ - sibling intents from the same prompt are emitted once;
13
+ - prompts below the positive threshold are not repeated;
14
+ - the manifest records the requested oversample map.
15
+
16
+ Example: `--oversample refactor:3` emits three copies of the positive `refactor` item, but still emits one copy each of `review`, `explain`, and every other sibling intent.
17
+
18
+ This distinction is required so a weak-intent intervention does not silently reweight neighboring intents.
19
+
20
+ ## Calibration isolation
21
+
22
+ The trainer calibration split is **prompt-group disjoint**.
23
+
24
+ - All items sharing one `promptId` must stay on the same side of the train/calibration boundary.
25
+ - Calibration uses one copy of each `promptId + intent` identity.
26
+ - Oversampled duplicates are retained only for prompt groups assigned to training.
27
+ - Oversampling therefore cannot create an exact duplicate in both training and calibration, and it cannot overweight temperature fitting.
28
+ - Items without `promptId` retain the legacy per-item deterministic split behavior for unit-level callers.
29
+
30
+ The calibration selection remains deterministic for a fixed seed and continues to respect the configured fraction and `CALIB_MAX` target over the de-duplicated base identities.
31
+
32
+ ## Loss weights
33
+
34
+ `system-one-train-laya.py --intent-weights '{"refactor": 2.0}'` applies a per-sample multiplier to both RL and CE losses for the named intent. Unlisted intents remain at weight `1.0`. Weights must be finite and greater than zero.
35
+
36
+ ## Evidence rule
37
+
38
+ Results produced before these isolation rules were enforced must not be used as causal evidence for neighbor-intent regressions without re-running the experiment. In particular, prompt-level duplication and train/calibration duplicate leakage confound interpretation of an intent-specific intervention.
@@ -0,0 +1,206 @@
1
+ # System One observations: collecting routing data from real sessions
2
+
3
+ Status: **accepted**, 2026-09-27. Owner decisions are recorded below.
4
+ Implementation: `hooks/scripts/observation-hook.js`,
5
+ `hooks/scripts/lib/observations.js`, `scripts/system-one-observations-export.js`
6
+ and `scripts/system-one-observations-review.js`; tests in
7
+ `ci/mechanism-34-observations.test.js`. Issue
8
+ [#233](https://github.com/dyphn1/Harness-everything/issues/233).
9
+
10
+ ## Why
11
+
12
+ Two data problems limit the System One scorers
13
+ ([system-one-suggestion-gates.md](system-one-suggestion-gates.md)):
14
+
15
+ 1. There are too few valid, text-decidable examples.
16
+ 2. Teacher labels from an offline LLM are thin, and were often assigned with
17
+ session context the small model never sees.
18
+
19
+ Every Harness turn already produces better material. The host LLM sees the
20
+ whole conversation, states its view in the Harness Status, and then acts.
21
+ Recording the prompt, that view, and what the agent actually did, turn by
22
+ turn, gives labeled data that grows with daily use. The data covers
23
+ validity, intent, tier, workflow and skills.
24
+
25
+ ## Owner decisions (2026-09-27)
26
+
27
+ - The host LLM adds one machine-readable label line to its final message
28
+ each turn. The token cost is accepted.
29
+ - Collection is **on by default**, with no environment variable needed to
30
+ enable it. `HARNESS_OBSERVATIONS=off` turns it off; this opt-out exists
31
+ because the plugin ships to other users.
32
+ - Hosts: **Claude Code** and **Codex** first.
33
+ - This document is the specification; implementation follows
34
+ docs → RED → GREEN.
35
+
36
+ ## One observation per turn
37
+
38
+ A turn runs from `UserPromptSubmit` to `Stop`. Its record:
39
+
40
+ | Field | Source | Notes |
41
+ | --- | --- | --- |
42
+ | `id`, `sessionId`, `turn`, `host`, `observedAt` | hooks | `id` = hash of session id + turn |
43
+ | `prompt` | `UserPromptSubmit` | stored in the private text store only (see Storage) |
44
+ | `previous` | the last assistant message before the prompt | truncated to 2 KB; text store only |
45
+ | `router` | kernel-router output | lexical tier, strategy, suggested skills; System One shadow scores when present |
46
+ | `behavior` | `PostToolUse`, `PostToolUseFailure` | counts and categories, never arguments: files written, distinct repositories written, files created, commands by class (git, gh, test, build, package, shell), skills loaded or read, subagents started. Failed attempts are counted separately (`toolFailures`, `failedWrites`, `failedSkillLoads`) and never count as a write or a load |
47
+ | `workflow` | `workflow-run.json` / `workflow-disposition` | selected and confirmed strategy |
48
+ | `selfReport` | label line in the final message | see below; `null` if missing or invalid |
49
+ | `derived` | exporter | labels computed from the fields above |
50
+
51
+ ### Label line
52
+
53
+ At the end of the final message of each turn the agent writes one line,
54
+ which is invisible in rendered Markdown:
55
+
56
+ ```
57
+ <!-- harness-label {"v":1,"validity":"actionable","contextDependent":false,"tier":"tier2","intents":["fix","test"],"workflow":"iterative-single","skills":["tdd"]} -->
58
+ ```
59
+
60
+ | Key | Values |
61
+ | --- | --- |
62
+ | `validity` | `actionable` or `invalid` (continuation, feedback on earlier work, pointer to earlier content, paste or chatter) |
63
+ | `contextDependent` | `true` when the prompt text alone would not have been enough to know the task |
64
+ | `tier` | `tier1` (no file change), `tier2` (bounded file change: code, config or docs), `tier3` (feature, refactor, redefinition, cross-component) or `null` (only for `invalid`), under tier rules v2 |
65
+ | `intents` | up to 3 of the 12 intent ids, strongest first |
66
+ | `workflow` | a router strategy id or `null` |
67
+ | `skills` | skill ids actually used this turn |
68
+
69
+ The line states the agent's own judgement **after** the work, not a copy of
70
+ the router's suggestion. A line that does not parse, or has an invalid
71
+ validity, tier or workflow, is recorded as `selfReport: null` with a reason
72
+ code; it never blocks the turn. Two slips seen in live runs are tolerated
73
+ rather than losing the whole label: unknown intent ids are dropped and kept
74
+ in `unknownIntents`, and a final label line missing its closing `-->` is
75
+ accepted when it ends the message.
76
+
77
+ ## Labels and how much to trust them
78
+
79
+ The label is the host LLM's own judgement. It saw the whole conversation and
80
+ did the work, so no fixed rule replaces it (owner decision, 2026-09-27: no
81
+ file-count threshold for tier).
82
+
83
+ | Label | Source | Override |
84
+ | --- | --- | --- |
85
+ | validity, contextDependent | self-report | owner review |
86
+ | intents | self-report | — |
87
+ | tier | self-report (tier rules v2) | owner review |
88
+ | workflow | self-report, else the confirmed strategy | — |
89
+ | skills | skills actually loaded or read, else the self-report | — |
90
+
91
+ Behavior counters (files, repositories, command classes, skills,
92
+ subagents) are kept as **evidence**, never as a label rule. They serve two
93
+ purposes:
94
+
95
+ - **Contradictions.** Some records contradict themselves. A `tier1` label
96
+ after files were written contradicts the tier1 definition (no code
97
+ change). These records go to the owner's review page.
98
+ - **Future features.** The counters can train or check later models
99
+ without re-reading transcripts.
100
+
101
+ **Owner review.** Missing labels, contradictions, and turns where the
102
+ router's tier and the self-reported tier differ go to a blind review page.
103
+ The owner decides validity, context dependence and tier; each decision
104
+ overrides the self-report, an explicit `false` included, and also labels a
105
+ turn that has no self-report.
106
+
107
+ **Bias controls.** The agent sees the router's suggestion before it
108
+ answers, so it may copy it. Router output and self-report are both stored.
109
+ Each export reports how often they agree, over turns where the router chose
110
+ a tier (`unclassified` is not a tier); a rate close to 1 over many turns is a
111
+ warning sign. The review page samples the turns where they differ.
112
+
113
+ ## Storage, index and privacy
114
+
115
+ - **Location**: `~/.agents/harness-everything/system-one/observations/`,
116
+ outside every workspace. It is never written under `memories/repo`, which
117
+ can be committed.
118
+ - **Text store**: `text/<sha256>.json` holds `prompt` and `previous`, with
119
+ the collector's redaction applied (email addresses and home paths).
120
+ - **Index**: `observations-index.json` uses the `index_memory.js` record
121
+ shape: `schemaVersion: 1` and `records[]`. Each record has `id`,
122
+ `status`, `validUntil`, `contentSha256`, `scope`, `writer.sessionId` and
123
+ `source: "observation"`, plus the non-text fields above. `scope.taskTerms`
124
+ hold normalized terms, so `retrieveMemoryRecords`-style lookups can find
125
+ similar past turns.
126
+ - **Retention**: `validUntil` is 180 days after `observedAt`. An expired
127
+ record keeps its labels and counts, but its text is deleted. Expiry is
128
+ enforced on read as well: the exporter and the review page skip the text
129
+ of any record past `validUntil`, whether or not a later turn has swept it.
130
+ A text blob is content-addressed, so it is deleted only when no active
131
+ record references it. Session state that holds text (the previous-message
132
+ tail and an unfinished turn's prompt) follows the same 180 days, counted
133
+ from when the text was captured. Finishing or recovering an old turn never
134
+ renews that deadline: its record's `validUntil` comes from the capture
135
+ time, and a turn whose text has already expired is recorded with its
136
+ labels and counts but no text.
137
+ - **Concurrency**: tool hooks may run in parallel. Each tool event is written
138
+ as its own immutable file, and `Stop` adds them up, so no event is lost to
139
+ a concurrent read-modify-write.
140
+ - **Telemetry stays content-free.** The telemetry channel keeps its
141
+ `FORBIDDEN_KEYS` rule. Observations are a separate, local-only channel and
142
+ are never uploaded by Harness.
143
+ - `PRIVACY.md` is updated to state that prompts and the previous assistant
144
+ message are stored locally by default, where they are stored, how long
145
+ they are kept, and how to turn this off.
146
+
147
+ ## Hosts
148
+
149
+ - **Claude Code**: plugin hooks `UserPromptSubmit`, `PostToolUse` and
150
+ `Stop`. The final message is read from the Stop payload or from the
151
+ transcript the payload points to.
152
+ - **Codex**: the same hooks. Verified live on codex-cli 0.157.1
153
+ (2026-09-28):
154
+ - Codex adds `UserPromptSubmit` context to the model only from JSON
155
+ `hookSpecificOutput.additionalContext`. With the router's plain stdout
156
+ the model never saw the routing contract or the label instruction, so
157
+ it wrote no label; the same text as JSON reached the model and all five
158
+ test turns carried a label. The Codex manifest therefore runs the router
159
+ through `hooks/scripts/codex-user-prompt.js`, which wraps its output.
160
+ - Plugin-bundled hooks do not run on this version (`plugin_hooks` is
161
+ `removed`); the explicit fallback `harness codex-hooks install` is
162
+ needed, and Codex runs the hooks only after they are trusted in
163
+ `/hooks`.
164
+ - The final message comes from the rollout's `task_complete` event.
165
+ - The final message must belong to the current turn. A transcript scan
166
+ stops at the start of the current turn (Codex `task_started` or a user
167
+ message; a Claude user prompt) and a Codex `task_complete` must carry the
168
+ current `turn_id` when both are known. With no final message for the
169
+ current turn, `selfReport` is `null` with `final-message-unavailable`; the
170
+ previous turn's label is never reused.
171
+ - Failure follows each host's convention: the `PostToolUseFailure` event
172
+ (Claude Code), or an error flag or non-zero exit code in the tool response
173
+ (Codex).
174
+ - A missing field records `null` with a reason code. Collection must never
175
+ block, slow down, or fail a turn; each hook has a short time budget.
176
+
177
+ ## Export
178
+
179
+ `scripts/system-one-observations-export.js` writes to the private data
180
+ repository:
181
+
182
+ - `prompts-observed.jsonl`: `{id, family, source: "observation", split, text}`
183
+ - `labels-observed.jsonl`: `{id, validity, contextDependent, tier, tierSource, intents, workflow, skills}`
184
+
185
+ `family` groups near-duplicate prompts. The split is by time: the newest 15%
186
+ is validation. The existing holdouts stay frozen and separate. Observed data
187
+ may later seed a new holdout, but only after owner review.
188
+
189
+ ## Phases
190
+
191
+ 1. **Contract.** Define the label-line format in `AGENTS.md` rule 11 and in
192
+ the Harness Status docs. The parser and validator come with tests.
193
+ 2. **Collector.** Hook-side recording for Claude Code: the turn record,
194
+ behavior counters, the text store, the index and the opt-out. Tests cover
195
+ redaction, the time budget, fail-open behavior and index shape.
196
+ 3. **Codex.** Verify the payload fields, then add the adapter and its tests.
197
+ 4. **Exporter.** Derived labels, the family grouping, the time split, and
198
+ bias reports.
199
+ 5. **Review page.** A blind review of disagreements; decisions feed back as
200
+ owner overrides.
201
+
202
+ ## Non-goals
203
+
204
+ - Nothing is uploaded and there is no remote collection.
205
+ - The label line never gates or changes routing.
206
+ - Observed data does not replace the owner-reviewed holdouts.