harness-everything 0.27.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +33 -0
  4. package/behavioral-evals/README.md +39 -0
  5. package/behavioral-evals/case-validator.js +34 -0
  6. package/behavioral-evals/cases/baseline-worktree-submodule-reachability.yaml +30 -0
  7. package/behavioral-evals/fixtures/setup/fail.js +3 -0
  8. package/behavioral-evals/fixtures/setup/noop.js +4 -0
  9. package/behavioral-evals/fixtures/setup/verify-worktree-submodule.js +46 -0
  10. package/behavioral-evals/fixtures/setup/worktree-submodule.js +86 -0
  11. package/behavioral-evals/paired-benchmark.js +39 -5
  12. package/behavioral-evals/run-with-plugin.js +53 -39
  13. package/behavioral-evals/run.js +88 -9
  14. package/benchmarks/fixtures/system-one-holdout-reviews.json +18 -18
  15. package/benchmarks/fixtures/system-one-holdout.json +9 -9
  16. package/benchmarks/results/system-one/harness-routing-v1-cpu-2026-09-24/README.md +75 -0
  17. package/benchmarks/results/system-one/harness-routing-v1-cpu-2026-09-24/report.json +10832 -0
  18. package/benchmarks/results/system-one/harness-routing-v1-ngram-2026-09-24/README.md +72 -0
  19. package/benchmarks/results/system-one/harness-routing-v1-ngram-2026-09-24/report.json +10817 -0
  20. package/benchmarks/results/system-one/harness-routing-v3-ngram-2026-09-24/README.md +63 -0
  21. package/benchmarks/results/system-one/harness-routing-v3-ngram-2026-09-24/report.json +10817 -0
  22. package/benchmarks/results/system-one/labeler-holdout-check-2026-09-24/report.json +1104 -0
  23. package/benchmarks/results/system-one/labeler-holdout-check-codex-2026-09-24/report.json +1104 -0
  24. package/benchmarks/results/system-one/labeler-holdout-check-codex-tier3-2026-09-24/report.json +1104 -0
  25. package/ci/mechanism-2j-behavioral-runner.test.js +121 -1
  26. package/ci/mechanism-2j-fixture-entrypoints.test.js +153 -0
  27. package/ci/mechanism-system-one-evaluation.test.js +17 -0
  28. package/ci/mechanism-system-one-label.test.js +27 -0
  29. package/ci/mechanism-system-one-ngram.test.js +161 -0
  30. package/ci/mechanism-system-one-train.test.js +12 -0
  31. package/ci/system-one-ngram-parity.py +19 -0
  32. package/ci/system-one-train-test.py +6 -0
  33. package/ci/test-behavioral-case-validation.js +26 -0
  34. package/docs/system-one-corpus.md +31 -3
  35. package/docs/system-one-routing.md +84 -2
  36. package/docs/system-one-training.md +14 -2
  37. package/harness-everything/SKILL.md +1 -1
  38. package/harness-everything/scripts/system-one/evaluate.js +3 -2
  39. package/harness-everything/scripts/system-one/ngram.js +84 -0
  40. package/harness-everything/scripts/system-one/provider.js +15 -5
  41. package/opencode-plugin/plugin.json +1 -1
  42. package/package.json +1 -1
  43. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  44. package/plugins/harness-everything/plugin.json +1 -1
  45. package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
  46. package/plugins/harness-everything/skills/harness-everything/scripts/system-one/evaluate.js +3 -2
  47. package/plugins/harness-everything/skills/harness-everything/scripts/system-one/ngram.js +84 -0
  48. package/plugins/harness-everything/skills/harness-everything/scripts/system-one/provider.js +15 -5
  49. package/sbom.cdx.json +7 -7
  50. package/scripts/evaluate-system-one.js +16 -9
  51. package/scripts/system-one-calibrate.js +1 -1
  52. package/scripts/system-one-label.js +32 -3
  53. package/scripts/system-one-train-ngram.py +129 -0
  54. package/scripts/system-one-train.py +14 -1
@@ -3,6 +3,7 @@
3
3
  // Labels System One training prompts with an LLM against the owner's rules (docs/system-one-corpus.md).
4
4
  // See docs/system-one-training.md. Labels stay local; only the holdout agreement report may be committed.
5
5
  const fs = require('node:fs');
6
+ const os = require('node:os');
6
7
  const path = require('node:path');
7
8
  const { spawn } = require('node:child_process');
8
9
  const { createHash } = require('node:crypto');
@@ -105,6 +106,32 @@ function commandRunner(model) {
105
106
  child.stdin.end(prompt);
106
107
  });
107
108
  }
109
+ // One stateless `codex exec` per batch: prompt on stdin, schema-checked last message in a temp file,
110
+ // read-only sandbox, no session files and no user config (plugins/hooks), so nothing accumulates across batches.
111
+ function codexRunner(model) {
112
+ const base = process.env.HARNESS_S1_LABEL_COMMAND ? JSON.parse(process.env.HARNESS_S1_LABEL_COMMAND) : ['codex'];
113
+ const work = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-s1-codex-'));
114
+ const schemaFile = path.join(work, 'schema.json');
115
+ fs.writeFileSync(schemaFile, SCHEMA);
116
+ let n = 0;
117
+ return prompt => new Promise((resolve, reject) => {
118
+ const outFile = path.join(work, `reply-${process.pid}-${n++}.json`);
119
+ const args = ['exec', '--model', model, '-c', 'model_reasoning_effort=medium', '--sandbox', 'read-only', '--skip-git-repo-check',
120
+ '--ephemeral', '--ignore-user-config', '--output-schema', schemaFile, '-o', outFile, '-C', work, '-'];
121
+ const child = spawn(base[0], [...base.slice(1), ...args], { stdio: ['pipe', 'ignore', 'ignore'], windowsHide: true,
122
+ shell: process.platform === 'win32' && !process.env.HARNESS_S1_LABEL_COMMAND });
123
+ const timer = setTimeout(() => child.kill(), 600000);
124
+ child.on('error', err => { clearTimeout(timer); reject(err); });
125
+ child.on('close', code => {
126
+ clearTimeout(timer);
127
+ let reply = null;
128
+ try { reply = JSON.parse(fs.readFileSync(outFile, 'utf8')); } catch (_) { reject(new Error(`codex-exit-${code}: no reply`)); return; }
129
+ try { fs.unlinkSync(outFile); } catch (_) { /* temp file */ }
130
+ resolve(reply);
131
+ });
132
+ child.stdin.end(prompt);
133
+ });
134
+ }
108
135
  function outsideRepo(file) {
109
136
  const rel = path.relative(REPO, path.resolve(file));
110
137
  if (!rel.startsWith('..') && !path.isAbsolute(rel)) throw new Error('refusing to write labels inside the repository');
@@ -113,10 +140,12 @@ function outsideRepo(file) {
113
140
  async function main(argv) {
114
141
  const [op, ...rest] = argv;
115
142
  const opt = (name, fallback) => { const i = rest.indexOf(name); return i >= 0 ? rest[i + 1] : fallback; };
116
- const model = opt('--model', 'sonnet');
143
+ const engine = opt('--engine', 'claude');
144
+ if (!['claude', 'codex'].includes(engine)) throw new Error('--engine must be claude or codex');
145
+ const model = opt('--model', engine === 'codex' ? 'gpt-5.6-luna' : 'sonnet');
117
146
  const rules = rulesFromDoc(fs.readFileSync(path.join(REPO, 'docs', 'system-one-corpus.md'), 'utf8'));
118
- const labeler = { model, rulesSha256: sha(rules).slice(0, 16) };
119
- const run = commandRunner(model);
147
+ const labeler = { model, rulesSha256: sha(rules).slice(0, 16), ...(engine === 'codex' ? { engine } : {}) };
148
+ const run = engine === 'codex' ? codexRunner(model) : commandRunner(model);
120
149
  const concurrency = Number(opt('--concurrency', '4')); const batchSize = Number(opt('--batch', '100'));
121
150
  if (op === 'label') {
122
151
  const input = opt('--in'); const out = opt('--out');
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env python3
2
+ """Train the System One ngram tier scorer (docs/system-one-training.md, docs/system-one-routing.md "N-gram provider").
3
+
4
+ Featurization is stdlib-only and must match harness-everything/scripts/system-one/ngram.js exactly
5
+ (ci/mechanism-system-one-ngram.test.js compares them). torch is imported only inside train() for the optimizer.
6
+ """
7
+ import argparse
8
+ import hashlib
9
+ import json
10
+ import math
11
+ from pathlib import Path
12
+ import struct
13
+ import sys
14
+
15
+ sys.dont_write_bytecode = True
16
+ REPO = Path(__file__).resolve().parents[1]
17
+ DEFAULT_HOLDOUT = REPO / 'benchmarks' / 'fixtures' / 'system-one-holdout.json'
18
+
19
+
20
+ def fnv1a32(text):
21
+ h = 0x811c9dc5
22
+ for byte in text.encode('utf-8'):
23
+ h ^= byte
24
+ h = (h * 0x01000193) & 0xFFFFFFFF
25
+ return h
26
+
27
+
28
+ def featurize(text, nmax, dim):
29
+ """bucket -> weight: log(1 + count) per code-point n-gram, one length bucket, L2-normalized."""
30
+ t = ' '.join(text.lower().split())
31
+ counts = {}
32
+ for n in range(1, nmax + 1):
33
+ for i in range(len(t) - n + 1):
34
+ gram = t[i:i + n]
35
+ if not gram.strip():
36
+ continue
37
+ b = fnv1a32(f'{n}:{gram}') % dim
38
+ counts[b] = counts.get(b, 0) + 1
39
+ k = min(8, int(math.floor(math.log2(len(t.encode('utf-8')) + 1))))
40
+ counts[fnv1a32(f'len:{k}') % dim] = 1
41
+ feats = {b: math.log1p(c) for b, c in counts.items()}
42
+ norm = math.sqrt(sum(v * v for v in feats.values()))
43
+ return {b: v / norm for b, v in feats.items()}
44
+
45
+
46
+ def load_jsonl(path):
47
+ return [json.loads(line) for line in Path(path).read_text(encoding='utf-8').splitlines() if line.strip()]
48
+
49
+
50
+ def sha256_file(path):
51
+ return hashlib.sha256(Path(path).read_bytes()).hexdigest()
52
+
53
+
54
+ def train(args):
55
+ import torch
56
+
57
+ torch.manual_seed(args.seed)
58
+ data = Path(args.data_dir)
59
+ corpus = json.loads(Path(args.holdout).read_text(encoding='utf-8'))
60
+ catalog = [o['id'] for o in corpus['cases'][0]['request']['options']]
61
+ gold = {r['id']: r['gold'] for r in load_jsonl(data / 'labels.jsonl')}
62
+ overrides = data / 'owner-overrides.jsonl'
63
+ if overrides.exists():
64
+ gold.update({r['id']: r['gold'] for r in load_jsonl(overrides)})
65
+ excluded = data / 'owner-excluded.jsonl'
66
+ skip = {r['id'] for r in load_jsonl(excluded)} if excluded.exists() else set()
67
+ rows = [p for p in load_jsonl(data / 'prompts.jsonl') if p['id'] in gold and p['id'] not in skip]
68
+ index = lambda g: catalog.index('unclassified' if g is None else g)
69
+ split = {name: [r for r in rows if r['split'] == name] for name in ('train', 'validation')}
70
+
71
+ def matrix(items):
72
+ ii, jj, vv = [], [], []
73
+ for r, item in enumerate(items):
74
+ for b, v in featurize(item['text'], args.nmax, args.dim).items():
75
+ ii.append(r); jj.append(b); vv.append(v)
76
+ return torch.sparse_coo_tensor([ii, jj], vv, (len(items), args.dim), dtype=torch.float32).coalesce()
77
+
78
+ x_train, x_val = matrix(split['train']), matrix(split['validation'])
79
+ y_train = torch.tensor([index(gold[r['id']]) for r in split['train']])
80
+ counts = torch.bincount(y_train, minlength=len(catalog)).float()
81
+ inverse = torch.where(counts > 0, counts.sum() / (len(catalog) * counts.clamp_min(1)), torch.zeros_like(counts))
82
+ weight = {'none': None, 'sqrt': inverse.sqrt(), 'full': inverse}[args.balance]
83
+ W = torch.zeros(args.dim, len(catalog), requires_grad=True)
84
+ bias = torch.zeros(len(catalog), requires_grad=True)
85
+ optimizer = torch.optim.Adam([W, bias], lr=args.lr)
86
+ for _ in range(args.epochs):
87
+ optimizer.zero_grad()
88
+ loss = torch.nn.functional.cross_entropy(torch.sparse.mm(x_train, W) + bias, y_train, weight=weight) + args.weight_decay * (W ** 2).sum()
89
+ loss.backward()
90
+ optimizer.step()
91
+ with torch.no_grad():
92
+ probs = torch.softmax(torch.sparse.mm(x_val, W) + bias, 1)
93
+ out = Path(args.out)
94
+ out.mkdir(parents=True, exist_ok=True)
95
+ weights = W.detach().contiguous().view(-1).tolist()
96
+ (out / 'harness-routing-v1.bin').write_bytes(struct.pack(f'<{len(weights)}f', *weights))
97
+ datasets = {name: sha256_file(data / name) for name in ('prompts.jsonl', 'labels.jsonl')}
98
+ if overrides.exists():
99
+ datasets['owner-overrides.jsonl'] = sha256_file(overrides)
100
+ sidecar = {'format': 'harness-ngram', 'formatVersion': 1, 'dim': args.dim, 'nmax': args.nmax, 'hash': 'fnv1a32', 'catalog': catalog,
101
+ 'bias': [float(v) for v in bias.detach().tolist()],
102
+ 'metadata': {'domain': 'harness-routing-v1', 'balance': args.balance, 'weightDecay': args.weight_decay, 'epochs': args.epochs,
103
+ 'seed': args.seed, 'datasets': datasets, 'counts': {k: len(v) for k, v in split.items()}}}
104
+ (out / 'harness-routing-v1.json').write_text(json.dumps(sidecar), encoding='utf-8')
105
+ with (out / 'val-scores.jsonl').open('w', encoding='utf-8') as fh:
106
+ for r, p in zip(split['validation'], probs.tolist()):
107
+ fh.write(json.dumps({'id': r['id'], 'gold': gold[r['id']], 'probs': p}) + '\n')
108
+ y_val = torch.tensor([index(gold[r['id']]) for r in split['validation']])
109
+ accuracy = (probs.argmax(1) == y_val).float().mean().item() if len(y_val) else 0.0
110
+ print(json.dumps({'counts': sidecar['metadata']['counts'], 'validationAccuracy': accuracy}))
111
+
112
+
113
+ def main(argv=None):
114
+ parser = argparse.ArgumentParser(description=__doc__)
115
+ parser.add_argument('--data-dir', required=True)
116
+ parser.add_argument('--out', required=True)
117
+ parser.add_argument('--holdout', default=str(DEFAULT_HOLDOUT), help='source of the fixed catalog only; holdout rows are never trained on')
118
+ parser.add_argument('--dim', type=int, default=1 << 16)
119
+ parser.add_argument('--nmax', type=int, default=3)
120
+ parser.add_argument('--balance', choices=['none', 'sqrt', 'full'], default='sqrt')
121
+ parser.add_argument('--weight-decay', type=float, default=1e-4)
122
+ parser.add_argument('--epochs', type=int, default=60)
123
+ parser.add_argument('--lr', type=float, default=0.01)
124
+ parser.add_argument('--seed', type=int, default=7)
125
+ train(parser.parse_args(argv))
126
+
127
+
128
+ if __name__ == '__main__':
129
+ main()
@@ -50,6 +50,16 @@ def join(prompts, labels, overrides):
50
50
  return [{'id': p['id'], 'split': p['split'], 'text': p['text'], 'gold': gold[p['id']]} for p in prompts if p['id'] in gold]
51
51
 
52
52
 
53
+ def class_weights(label_indices, num_classes):
54
+ """Inverse-frequency weights that average to 1 over the examples; absent classes get 0."""
55
+ counts = [0] * num_classes
56
+ for i in label_indices:
57
+ counts[i] += 1
58
+ present = sum(1 for c in counts if c)
59
+ total = len(label_indices)
60
+ return [total / (present * c) if c else 0.0 for c in counts]
61
+
62
+
53
63
  def make_batches(lengths, token_budget):
54
64
  """Length-bucketed batches: each batch's size times its longest item stays within the budget."""
55
65
  order = sorted(range(len(lengths)), key=lambda i: (lengths[i], i))
@@ -94,6 +104,8 @@ def train(args):
94
104
  train_batches = make_batches(lengths(split['train']), args.token_budget)
95
105
  val_batches = make_batches(lengths(split['validation']), args.token_budget)
96
106
  optimizer = torch.optim.AdamW(model.parameters(), lr=args.lr, weight_decay=0.01)
107
+ # Class balance counters the tier2-heavy labels; validation NLL stays unweighted so epochs remain comparable.
108
+ weight = torch.tensor(class_weights([e.label for e in train_x], len(catalog)), dtype=torch.float) if args.balance else None
97
109
 
98
110
  def evaluate():
99
111
  model.eval()
@@ -118,7 +130,7 @@ def train(args):
118
130
  total = 0.0
119
131
  for b in order:
120
132
  tensors = collator([train_x[i] for i in train_batches[b]])
121
- loss = torch.nn.functional.cross_entropy(model(tensors), tensors['labels'])
133
+ loss = torch.nn.functional.cross_entropy(model(tensors), tensors['labels'], weight=weight)
122
134
  optimizer.zero_grad()
123
135
  loss.backward()
124
136
  torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
@@ -161,6 +173,7 @@ def main(argv=None):
161
173
  parser.add_argument('--threads', type=int, default=4)
162
174
  parser.add_argument('--context-tokens', type=int, default=1024)
163
175
  parser.add_argument('--token-budget', type=int, default=16384)
176
+ parser.add_argument('--balance', action='store_true', help='inverse-frequency class weights in the training loss')
164
177
  train(parser.parse_args(argv))
165
178
 
166
179