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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +42 -0
- package/PRIVACY.md +9 -0
- package/benchmarks/fixtures/system-one-intent-holdout-draft.json +2228 -0
- package/benchmarks/fixtures/system-one-intent-holdout.json +16509 -0
- package/benchmarks/fixtures/system-one-intent-reviews.json +2723 -0
- package/benchmarks/results/system-one/intent-ngram-v1-2026-09-24/README.md +64 -0
- package/benchmarks/results/system-one/intent-ngram-v1-2026-09-24/report.json +15177 -0
- package/benchmarks/results/system-one/labeler-holdout-check-intent-2026-09-24/report.json +2976 -0
- package/benchmarks/results/system-one/labeler-holdout-check-intent-scores-haiku-2026-09-24/report-005-steps.json +6125 -0
- package/benchmarks/results/system-one/labeler-holdout-check-intent-scores-haiku-2026-09-24/report-haiku-sonnet-escalation.json +6344 -0
- package/benchmarks/results/system-one/labeler-holdout-check-intent-scores-haiku-2026-09-24/report-scaled-margin-escalation.json +6206 -0
- package/benchmarks/results/system-one/labeler-holdout-check-intent-scores-haiku-2026-09-24/report.json +6103 -0
- package/benchmarks/results/system-one/laya-ft-2026-09-25/README.md +83 -0
- package/benchmarks/results/system-one/laya-ft-2026-09-25/report.json +71 -0
- package/benchmarks/results/system-one/laya-ft-2026-09-25/shadow-report.json +44 -0
- package/benchmarks/results/system-one/laya-zs-2026-09-25/README.md +68 -0
- package/benchmarks/results/system-one/laya-zs-2026-09-25/report.json +173 -0
- package/ci/fable-mode-test.js +16 -0
- package/ci/mechanism-34-observations.test.js +550 -0
- package/ci/mechanism-system-one-collect.test.js +16 -0
- package/ci/mechanism-system-one-intent.test.js +406 -0
- package/ci/mechanism-system-one-laya.test.js +45 -0
- package/ci/mechanism-system-one-relevance.test.js +112 -0
- package/ci/mechanism-system-one-resident.test.js +16 -0
- package/ci/system-one-laya-calibrate-test.py +65 -0
- package/ci/system-one-laya-export-test.py +155 -0
- package/ci/system-one-laya-label-test.py +201 -0
- package/ci/system-one-laya-perintent-test.py +60 -0
- package/ci/system-one-laya-provider-test.py +115 -0
- package/ci/system-one-laya-train-test.py +93 -0
- package/docs/routing.md +25 -0
- package/docs/system-one-intent-gates.md +102 -0
- package/docs/system-one-intent.md +173 -0
- package/docs/system-one-laya-training.md +38 -0
- package/docs/system-one-observations.md +206 -0
- package/docs/system-one-routing.md +25 -10
- package/docs/system-one-suggestion-gates.md +292 -0
- package/docs/system-one-training.md +56 -6
- package/docs/workflows/fable-mode.md +7 -0
- package/fable-mode/SKILL.md +1 -1
- package/fable-mode/agents/fable-orchestrator.md +4 -4
- package/fable-mode/agents/fable-worker-haiku.md +4 -4
- package/fable-mode/agents/fable-worker-sonnet.md +4 -4
- package/fable-mode/execution-guardrails/SKILL.md +5 -3
- package/fable-mode/execution-guardrails/references/guardrail-rules.md +12 -8
- package/fable-mode/fable-haiku/SKILL.md +1 -1
- package/fable-mode/fable-opus/SKILL.md +1 -1
- package/fable-mode/fable-sonnet/SKILL.md +1 -1
- package/harness-everything/SKILL.md +1 -1
- package/harness-everything/scripts/kernel-router.js +10 -0
- package/harness-everything/scripts/system-one/catalogs.js +35 -0
- package/harness-everything/scripts/system-one/contract.js +45 -1
- package/harness-everything/scripts/system-one/evaluate.js +115 -19
- package/harness-everything/scripts/system-one/intent.js +22 -0
- package/harness-everything/scripts/system-one/laya_adapter.py +141 -0
- package/harness-everything/scripts/system-one/provider.js +10 -3
- package/harness-everything/scripts/system-one/resident.js +8 -3
- package/harness-everything/scripts/system-one/router.js +1 -1
- package/hooks/hooks.json +40 -0
- package/hooks/scripts/codex-user-prompt.js +39 -0
- package/hooks/scripts/lib/observations.js +581 -0
- package/hooks/scripts/observation-hook.js +25 -0
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/hooks/hooks.json +35 -2
- package/plugins/harness-everything/hooks/scripts/codex-user-prompt.js +39 -0
- package/plugins/harness-everything/hooks/scripts/lib/observations.js +581 -0
- package/plugins/harness-everything/hooks/scripts/observation-hook.js +25 -0
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/skills/fable-mode/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/agents/fable-orchestrator.md +4 -4
- package/plugins/harness-everything/skills/fable-mode/agents/fable-worker-haiku.md +4 -4
- package/plugins/harness-everything/skills/fable-mode/agents/fable-worker-sonnet.md +4 -4
- package/plugins/harness-everything/skills/fable-mode/execution-guardrails/SKILL.md +5 -3
- package/plugins/harness-everything/skills/fable-mode/execution-guardrails/references/guardrail-rules.md +12 -8
- package/plugins/harness-everything/skills/fable-mode/fable-haiku/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/fable-opus/SKILL.md +1 -1
- package/plugins/harness-everything/skills/fable-mode/fable-sonnet/SKILL.md +1 -1
- package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
- package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router.js +10 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/system-one/catalogs.js +35 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/system-one/contract.js +45 -1
- package/plugins/harness-everything/skills/harness-everything/scripts/system-one/evaluate.js +115 -19
- package/plugins/harness-everything/skills/harness-everything/scripts/system-one/intent.js +22 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/system-one/laya_adapter.py +141 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/system-one/provider.js +10 -3
- package/plugins/harness-everything/skills/harness-everything/scripts/system-one/resident.js +8 -3
- package/plugins/harness-everything/skills/harness-everything/scripts/system-one/router.js +1 -1
- package/sbom.cdx.json +7 -7
- package/scripts/evaluate-system-one.js +17 -5
- package/scripts/system-one-calibrate-laya.py +165 -0
- package/scripts/system-one-calibrate.js +42 -13
- package/scripts/system-one-collect.js +7 -3
- package/scripts/system-one-compare-laya.py +156 -0
- package/scripts/system-one-corpus.js +22 -15
- package/scripts/system-one-eval-laya.js +112 -0
- package/scripts/system-one-export-laya.py +197 -0
- package/scripts/system-one-label-laya.py +243 -0
- package/scripts/system-one-label.js +163 -37
- package/scripts/system-one-observations-export.js +106 -0
- package/scripts/system-one-observations-review.js +86 -0
- package/scripts/system-one-perintent-laya.py +134 -0
- package/scripts/system-one-train-laya.py +344 -0
- 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.
|