@azure-id/orc 1.1.0 → 1.2.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.
- package/CHANGELOG.md +2518 -2437
- package/README.md +681 -694
- package/bin/cli.js +472 -1
- package/bin/verify-contracts.js +38 -1
- package/package.json +1 -1
- package/templates/hooks/orc-statusline.js +188 -1
- package/templates/skills/_shared/phases/execution.md +2 -0
- package/templates/skills/_shared/return-validation.md +222 -145
- package/templates/skills/orc/SKILL.md +241 -237
- package/templates/skills/orc-doc/SKILL.md +484 -480
- package/templates/skills/orc-fast/SKILL.md +216 -214
- package/templates/skills/orc-mini/SKILL.md +246 -243
- package/templates/skills/orc-quick/SKILL.md +347 -345
- package/templates/skills/orc-wiki/SKILL.md +174 -170
|
@@ -126,7 +126,46 @@ process.stdin.on("end", () => {
|
|
|
126
126
|
written_at: Date.now(),
|
|
127
127
|
}) + "\n"
|
|
128
128
|
);
|
|
129
|
+
// -- Session consumption (v1.2.0) -------------------------------------
|
|
130
|
+
// `usage.json` is a SNAPSHOT of the window. It cannot answer "how much
|
|
131
|
+
// has THIS session eaten", which is the question a user actually asks
|
|
132
|
+
// mid-run -- and the one they could otherwise only answer by remembering
|
|
133
|
+
// what the number was an hour ago.
|
|
134
|
+
//
|
|
135
|
+
// So keep a per-session ledger beside it: the reading when this session
|
|
136
|
+
// first rendered, and the reading now. Same rules as every other bridge
|
|
137
|
+
// here -- RAW numbers only, never a computed word, fail-silent, and the
|
|
138
|
+
// reader decides what it means.
|
|
139
|
+
//
|
|
140
|
+
// A window RESET mid-session (used_percentage drops) is not a refund:
|
|
141
|
+
// bank what was consumed before the reset into `accumulated` and
|
|
142
|
+
// re-baseline, so the running total keeps counting across the boundary.
|
|
143
|
+
const sid = String(d.session_id || d.sessionId || "");
|
|
144
|
+
const sfile = path.join(orcDir, "usage-session.json");
|
|
145
|
+
let led = null;
|
|
146
|
+
try { led = JSON.parse(fs.readFileSync(sfile, "utf8")); } catch (_) {}
|
|
147
|
+
const pctOf = (o) => (o && typeof o.used_percentage === "number" ? o.used_percentage : null);
|
|
148
|
+
const track = (prev, cur) => {
|
|
149
|
+
if (cur == null) return prev || null;
|
|
150
|
+
if (!prev) return { baseline: cur, last: cur, accumulated: 0, resets: 0 };
|
|
151
|
+
if (cur < prev.baseline)
|
|
152
|
+
return {
|
|
153
|
+
baseline: cur,
|
|
154
|
+
last: cur,
|
|
155
|
+
accumulated: prev.accumulated + Math.max(0, prev.last - prev.baseline),
|
|
156
|
+
resets: prev.resets + 1,
|
|
157
|
+
};
|
|
158
|
+
return { baseline: prev.baseline, last: cur, accumulated: prev.accumulated, resets: prev.resets };
|
|
159
|
+
};
|
|
160
|
+
if (!led || led.session_id !== sid) led = { session_id: sid, started_at: Date.now() };
|
|
161
|
+
led.five_hour = track(led.five_hour, pctOf(rl0 && rl0.five_hour));
|
|
162
|
+
led.seven_day = track(led.seven_day, pctOf(rl0 && rl0.seven_day));
|
|
163
|
+
led.context_used_percentage =
|
|
164
|
+
cw0 && typeof cw0.used_percentage === "number" ? cw0.used_percentage : null;
|
|
165
|
+
led.updated_at = Date.now();
|
|
166
|
+
fs.writeFileSync(sfile, JSON.stringify(led) + "\n");
|
|
129
167
|
}
|
|
168
|
+
|
|
130
169
|
} catch (_) {}
|
|
131
170
|
|
|
132
171
|
// ── Subscription usage (Claude Code v2.1.80+) ──────────────────────────────
|
|
@@ -236,6 +275,24 @@ process.stdin.on("end", () => {
|
|
|
236
275
|
// older Claude Code that doesn't surface `rate_limits`.
|
|
237
276
|
if (rlSeg) line += " · " + rlSeg;
|
|
238
277
|
|
|
278
|
+
// How far the window moved while THIS session ran (v1.2.0). The ledger below
|
|
279
|
+
// keeps the raw numbers; this renders the delta. Never shown as "this session
|
|
280
|
+
// used X%" — the window is per ACCOUNT, and a second terminal moves it too.
|
|
281
|
+
try {
|
|
282
|
+
const fs = require("fs");
|
|
283
|
+
const path = require("path");
|
|
284
|
+
const projectDir =
|
|
285
|
+
(d.workspace && d.workspace.project_dir) || d.cwd || process.cwd();
|
|
286
|
+
const led = JSON.parse(
|
|
287
|
+
fs.readFileSync(path.join(projectDir, ".claude", "orc", "usage-session.json"), "utf8")
|
|
288
|
+
);
|
|
289
|
+
const w = led && led.five_hour;
|
|
290
|
+
if (w && typeof w.last === "number" && typeof w.baseline === "number") {
|
|
291
|
+
const used = Math.max(0, (w.accumulated || 0) + Math.max(0, w.last - w.baseline));
|
|
292
|
+
if (used > 0) line += " · sess +" + used + "%";
|
|
293
|
+
}
|
|
294
|
+
} catch (_) {}
|
|
295
|
+
|
|
239
296
|
// Wiki freshness tier (computed on read from wiki-meta.json — zero model
|
|
240
297
|
// tokens; the manifest is written only by `orc wiki sync`). Fail-silent: no
|
|
241
298
|
// wiki / no git / any error → no segment. Thresholds mirror the config
|
|
@@ -340,5 +397,135 @@ process.stdin.on("end", () => {
|
|
|
340
397
|
} catch (_) {}
|
|
341
398
|
}
|
|
342
399
|
|
|
343
|
-
|
|
400
|
+
// ── Session line (v1.2.0) ──────────────────────────────────────────────────
|
|
401
|
+
// Line 1 answers "what tier am I on and how full is the window". This second
|
|
402
|
+
// line answers "what has this session actually been DOING" — how many agents
|
|
403
|
+
// it spawned, which lanes ran, whether work can leave Claude, and how long it
|
|
404
|
+
// has been going. All of it is read from disk; none of it costs a model call.
|
|
405
|
+
//
|
|
406
|
+
// The dispatch count is the one that earns its place. v1.2.0 exists because a
|
|
407
|
+
// retry cloned a live agent three times over and nothing surfaced it. A count
|
|
408
|
+
// that says `7 (2 running)` makes that visible from the status bar.
|
|
409
|
+
//
|
|
410
|
+
// Fail-silent and THROTTLED: the statusline re-renders on every keystroke, so
|
|
411
|
+
// the trace scan runs at most every 5s and its answer is cached in the same
|
|
412
|
+
// per-session ledger. Any error → no second line, never a broken one.
|
|
413
|
+
let line2 = "";
|
|
414
|
+
try {
|
|
415
|
+
const fs = require("fs");
|
|
416
|
+
const path = require("path");
|
|
417
|
+
const projectDir =
|
|
418
|
+
(d.workspace && d.workspace.project_dir) || d.cwd || process.cwd();
|
|
419
|
+
const orcDir = path.join(projectDir, ".claude", "orc");
|
|
420
|
+
const sfile = path.join(orcDir, "usage-session.json");
|
|
421
|
+
const sid = String(d.session_id || d.sessionId || "");
|
|
422
|
+
|
|
423
|
+
let led = null;
|
|
424
|
+
try { led = JSON.parse(fs.readFileSync(sfile, "utf8")); } catch (_) {}
|
|
425
|
+
if (!led || led.session_id !== sid) led = { session_id: sid, started_at: Date.now() };
|
|
426
|
+
|
|
427
|
+
// The hook cannot read the RESOLVED config — that is the lane resolver's
|
|
428
|
+
// job, and a hook has no lane — so this reads the two raw keys it needs
|
|
429
|
+
// straight from the file and takes the documented default
|
|
430
|
+
// otherwise — the same caveat the wiki segment above already carries. A
|
|
431
|
+
// user override shifts skill behaviour; this label follows the file.
|
|
432
|
+
let logRel = ".claude/orc/logs";
|
|
433
|
+
let extraOn = false;
|
|
434
|
+
try {
|
|
435
|
+
const raw = fs.readFileSync(path.join(projectDir, ".claude", "orc.config.yaml"), "utf8");
|
|
436
|
+
const ld = /^[ \t]*log_dir:[ \t]*["']?([^"'#\r\n]+)/m.exec(raw);
|
|
437
|
+
if (ld) logRel = ld[1].trim();
|
|
438
|
+
extraOn = /^[ \t]*extra_enabled:[ \t]*true[ \t]*$/m.test(raw);
|
|
439
|
+
} catch (_) {}
|
|
440
|
+
|
|
441
|
+
const now = Date.now();
|
|
442
|
+
// The scan interval is the ONE seam over this budget, on the
|
|
443
|
+
// ORC_TEST_PROBE_MS precedent: a test that proves the throttle by SLEEPING
|
|
444
|
+
// past it is a test that fails on a loaded machine, and a flake is recorded
|
|
445
|
+
// and removed, never retried away. Unset, this is byte-identical to a
|
|
446
|
+
// hardcoded 5000, and nothing in ORC ever sets it.
|
|
447
|
+
const scanEvery = (() => {
|
|
448
|
+
const n = Number(process.env.ORC_STATUSLINE_SCAN_MS);
|
|
449
|
+
return Number.isFinite(n) && n >= 0 ? n : 5000;
|
|
450
|
+
})();
|
|
451
|
+
const stale = !led.dispatch || typeof led.dispatch.scanned_at !== "number" ||
|
|
452
|
+
now - led.dispatch.scanned_at >= scanEvery;
|
|
453
|
+
if (stale) {
|
|
454
|
+
const logDir = path.isAbsolute(logRel) ? logRel : path.join(projectDir, logRel);
|
|
455
|
+
const sessionFloor = Math.floor((led.started_at || 0) / 1000) * 1000;
|
|
456
|
+
let spawns = 0;
|
|
457
|
+
let running = 0;
|
|
458
|
+
const lanes = [];
|
|
459
|
+
try {
|
|
460
|
+
for (const f of fs.readdirSync(logDir)) {
|
|
461
|
+
if (!f.startsWith("run-") || !f.endsWith(".txt")) continue;
|
|
462
|
+
const full = path.join(logDir, f);
|
|
463
|
+
// Only traces touched since this session began. A trace from last
|
|
464
|
+
// week is not this session's spend.
|
|
465
|
+
let st;
|
|
466
|
+
try { st = fs.statSync(full); } catch (_) { continue; }
|
|
467
|
+
if (st.mtimeMs < (led.started_at || 0)) continue;
|
|
468
|
+
const text = fs.readFileSync(full, "utf8");
|
|
469
|
+
// Count by the trace's OWN line timestamps, not the file's mtime. A
|
|
470
|
+
// run that was already going when this session started shares its
|
|
471
|
+
// file with the session before it, and mtime cannot tell the two
|
|
472
|
+
// apart — it would attribute the whole file to whoever looked last.
|
|
473
|
+
let mine = 0;
|
|
474
|
+
for (const raw of text.split("\n")) {
|
|
475
|
+
const t = /^\[(\d{2})(\d{2})(\d{2}) (\d{2}):(\d{2}):(\d{2})/.exec(raw);
|
|
476
|
+
if (!t) continue;
|
|
477
|
+
if (raw.indexOf("] hook") === -1 || raw.indexOf(" SPAWN ") === -1) continue;
|
|
478
|
+
const at = new Date(
|
|
479
|
+
2000 + Number(t[3]), Number(t[2]) - 1, Number(t[1]),
|
|
480
|
+
Number(t[4]), Number(t[5]), Number(t[6])
|
|
481
|
+
).getTime();
|
|
482
|
+
// Trace stamps have SECOND resolution and started_at has
|
|
483
|
+
// milliseconds, so a dispatch in the same second as the
|
|
484
|
+
// session start compares as earlier than it. Floor the
|
|
485
|
+
// boundary to the second the trace could actually express.
|
|
486
|
+
if (at >= sessionFloor) mine += 1;
|
|
487
|
+
}
|
|
488
|
+
spawns += mine;
|
|
489
|
+
let openHere = 0;
|
|
490
|
+
try {
|
|
491
|
+
const pend = JSON.parse(fs.readFileSync(full + ".pending.json", "utf8"));
|
|
492
|
+
if (Array.isArray(pend)) openHere = pend.length;
|
|
493
|
+
} catch (_) {}
|
|
494
|
+
running += openHere;
|
|
495
|
+
// A lane earns its name by having actually dispatched in this
|
|
496
|
+
// session — listing a lane that contributed nothing is noise.
|
|
497
|
+
if (mine > 0 || openHere > 0) {
|
|
498
|
+
const m = /^run-([a-z0-9-]+?)-.+-\d{6}-\d{6}\.txt$/.exec(f);
|
|
499
|
+
if (m && lanes.indexOf(m[1]) === -1) lanes.push(m[1]);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
} catch (_) {}
|
|
503
|
+
led.dispatch = { spawns, running, lanes, scanned_at: now };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
led.updated_at = now;
|
|
507
|
+
try {
|
|
508
|
+
fs.mkdirSync(orcDir, { recursive: true });
|
|
509
|
+
fs.writeFileSync(sfile, JSON.stringify(led) + "\n");
|
|
510
|
+
} catch (_) {}
|
|
511
|
+
|
|
512
|
+
const dsp = led.dispatch || { spawns: 0, running: 0, lanes: [] };
|
|
513
|
+
const parts = [];
|
|
514
|
+
// `running` is never hidden, because an agent still in flight is the thing
|
|
515
|
+
// a user most needs to see (v1.2.0). Zero is simply not printed.
|
|
516
|
+
parts.push(
|
|
517
|
+
"agents " + dsp.spawns + (dsp.running ? " (" + dsp.running + " running)" : "")
|
|
518
|
+
);
|
|
519
|
+
parts.push("orc-extra: " + (extraOn ? "on" : "off"));
|
|
520
|
+
// An empty lane list means no ORC lane has dispatched yet this session —
|
|
521
|
+
// an ANSWER, not a gap, so it keeps its slot and says so.
|
|
522
|
+
parts.push("lanes: " + (dsp.lanes.length ? dsp.lanes.join(", ") : "none yet"));
|
|
523
|
+
if (led.started_at)
|
|
524
|
+
parts.push(Math.max(0, Math.round((now - led.started_at) / 60000)) + "m");
|
|
525
|
+
line2 = " " + parts.join(" · ");
|
|
526
|
+
} catch (_) {
|
|
527
|
+
line2 = "";
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
process.stdout.write(line2 ? line + "\n" + line2 : line);
|
|
344
531
|
});
|
|
@@ -91,6 +91,8 @@ the codifier); hold resolved patterns in run state.
|
|
|
91
91
|
invalidates a DONE task → re-run once, then set every reverse-`depends_on`
|
|
92
92
|
consumer to `stale_review`. **Worker failure/garbage/timeout:** flag +
|
|
93
93
|
continue the wave; audit and re-dispatch at the next batch checkpoint
|
|
94
|
+
|
|
95
|
+
**Before any re-dispatch, run `orc run inflight`** (0 clear · 1 in-flight · 2 unknown). A Task error does not kill the agent behind it, and exit 2 REFUSES by default — `a lane that re-dispatches over a live attempt` has broken the contract. Canonical: `../return-validation.md`.
|
|
94
96
|
(`requeued`, retry_count++). Hard retry cap 2 → STOP and surface.
|
|
95
97
|
|
|
96
98
|
<!-- /orc:layer -->
|
|
@@ -1,145 +1,222 @@
|
|
|
1
|
-
# Return validation (every lane, every subagent return)
|
|
2
|
-
|
|
3
|
-
Canonical procedure for validating a spawned agent's return. Every ORC lane
|
|
4
|
-
(full, mini, fast, wiki, diy) runs this on EVERY return; a malformed return is
|
|
5
|
-
a failure (requeue/re-dispatch with reason — lane sets the retry cap).
|
|
6
|
-
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
`
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
(`
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
1
|
+
# Return validation (every lane, every subagent return)
|
|
2
|
+
|
|
3
|
+
Canonical procedure for validating a spawned agent's return. Every ORC lane
|
|
4
|
+
(full, mini, fast, wiki, diy) runs this on EVERY return; a malformed return is
|
|
5
|
+
a failure (requeue/re-dispatch with reason — lane sets the retry cap).
|
|
6
|
+
|
|
7
|
+
## 0. Is the previous attempt still ALIVE? (v1.2.0) — BEFORE anything else
|
|
8
|
+
|
|
9
|
+
> **`a lane that re-dispatches over a live attempt` has broken this contract.**
|
|
10
|
+
|
|
11
|
+
**A Task error does not kill the agent behind it.** Claude Code's tool call can
|
|
12
|
+
fail, time out, or be cut off mid-turn while the subagent it started keeps
|
|
13
|
+
running — and keeps writing files. Every rule below this line ends in
|
|
14
|
+
"re-dispatch", and every one of them silently assumed a failed call meant a dead
|
|
15
|
+
agent. It does not.
|
|
16
|
+
|
|
17
|
+
What that costs, measured: one graded `/orc-quick` entry put THREE
|
|
18
|
+
`orc-executor-opus-5-low` agents on the SAME task — 50m19s, 115m22s and
|
|
19
|
+
100m53s, **266 minutes of Opus 5 for one authorised dispatch**, all editing the
|
|
20
|
+
same files, inside a 2h04m window. The second was dispatched 4m19s after the
|
|
21
|
+
first, while the first was still working. The hook had recorded all three; no
|
|
22
|
+
lane had ever read that record.
|
|
23
|
+
|
|
24
|
+
### The rule
|
|
25
|
+
|
|
26
|
+
Before ANY re-dispatch, requeue or repair round — and before the first dispatch
|
|
27
|
+
of a resumed run — run:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
orc run inflight --json # 0 clear · 1 in-flight · 2 unknown
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
| exit | meaning | what the lane does |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| 0 | provably nothing in flight | dispatch |
|
|
36
|
+
| 1 | ≥1 dispatch has not returned | **REFUSE. Name the agent, the task and its age.** Ask the user. |
|
|
37
|
+
| 2 | cannot prove either way | **REFUSE by default.** Say why, and let the USER decide. |
|
|
38
|
+
|
|
39
|
+
**Exit 2 refuses, and that is deliberate.** Everywhere else in ORC an absent
|
|
40
|
+
reading is treated as absent and never blocks — `orc usage check` exit 2 never
|
|
41
|
+
stops a run, an UNCHECKABLE pact never raises the exit code. This is the one
|
|
42
|
+
place the default inverts, because the two outcomes are not symmetrical: a
|
|
43
|
+
wrongly-refused dispatch costs one question, and a wrongly-issued one costs a
|
|
44
|
+
second Opus agent for an hour. Refusing on `unknown` is the cheap error.
|
|
45
|
+
|
|
46
|
+
### An interrupted turn is UNKNOWN, never FAILED
|
|
47
|
+
|
|
48
|
+
A usage limit, an API error, a dropped connection or a `Ctrl+C` between a
|
|
49
|
+
dispatch and its return says **nothing** about the agent. Treat it as §0 exit 2
|
|
50
|
+
and ask. A lane that classifies an interruption as a failure re-dispatches into
|
|
51
|
+
a live agent, gets interrupted again sooner because it is now paying twice, and
|
|
52
|
+
the loop tightens on itself — which is exactly how the 266-minute entry
|
|
53
|
+
happened.
|
|
54
|
+
|
|
55
|
+
### What refusing looks like
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
⛔ 1 dispatch is still in flight — not re-dispatching.
|
|
59
|
+
|
|
60
|
+
orc-executor-opus-5-low started 4m ago
|
|
61
|
+
"Fix approval flow defects"
|
|
62
|
+
|
|
63
|
+
A Task error does not kill the agent behind it. It may still be writing.
|
|
64
|
+
1. wait for it 2. dispatch anyway (2 agents on one task) 3. stop
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Option 2 must always be offered and never be the default: the user is allowed
|
|
68
|
+
to overrule this, and an unreadable sidecar must never trap a run.
|
|
69
|
+
|
|
70
|
+
### The evidence, and its one honest limit
|
|
71
|
+
|
|
72
|
+
`orc run inflight` reads the pending sidecar that `orc-trace.js` writes on every
|
|
73
|
+
`SPAWN`, cross-checked against the trace's own SPAWN/RETURN balance. It reports
|
|
74
|
+
`unknown` — never `clear` — when the sidecar is missing, unreadable, or holds
|
|
75
|
+
only records older than six hours, and when the sidecar and the trace disagree.
|
|
76
|
+
**Unknown is not zero.**
|
|
77
|
+
|
|
78
|
+
It cannot see an **ad-hoc** dispatch (`/orc-quick` recon, model+effort rather
|
|
79
|
+
than a pinned `orc-*` agent): the hook writes no `SPAWN` for one, so no record
|
|
80
|
+
exists. Those are read-only and short, so the exposure is small — but the limit
|
|
81
|
+
is stated rather than papered over, and a lane must not report `clear` as proof
|
|
82
|
+
that an ad-hoc read is finished.
|
|
83
|
+
|
|
84
|
+
## 1. Contract shape
|
|
85
|
+
|
|
86
|
+
The return must carry every field its agent contract names. Missing or extra
|
|
87
|
+
shape = malformed. Never repair a return yourself; re-dispatch.
|
|
88
|
+
|
|
89
|
+
## 2. Claimed-vs-actual model (tier-downgrade check)
|
|
90
|
+
|
|
91
|
+
Every return carries:
|
|
92
|
+
|
|
93
|
+
- `actual_model` — quoted VERBATIM from the agent's system-prompt model-id
|
|
94
|
+
line, never inferred (`unknown` when no such line exists)
|
|
95
|
+
- `actual_effort` — the agent's `$CLAUDE_EFFORT` value
|
|
96
|
+
|
|
97
|
+
Compare both against what the dispatch expected (the agent NAME encodes it).
|
|
98
|
+
Append the `VERIFY` trace line with the comparison; any mismatch is surfaced
|
|
99
|
+
to the user as a ⛔ DOWNGRADE — never silently accepted. (A subagent can't
|
|
100
|
+
exceed the MAIN session's tier, so a downgrade usually means the main session
|
|
101
|
+
is on the wrong model.)
|
|
102
|
+
|
|
103
|
+
## 2b. A FOREIGN return — the SUBSTITUTION check (v0.50.0)
|
|
104
|
+
|
|
105
|
+
A foreign worker (`orc extra dispatch`, `_shared/extra-dispatch.md`) is not a
|
|
106
|
+
Claude subagent. It has no injected system-prompt model-id line, so **it cannot
|
|
107
|
+
carry `actual_model`** — and §2 must not be faked for it. A foreign return that
|
|
108
|
+
claimed an `actual_model` would be claiming evidence that does not exist.
|
|
109
|
+
|
|
110
|
+
It carries instead, and every one of these is quoted from the wire rather than
|
|
111
|
+
assumed:
|
|
112
|
+
|
|
113
|
+
- `engine` (`api` | `claude-shim` | `cli`), `provider`, `profile`
|
|
114
|
+
- `model_requested` — what the route row asked for
|
|
115
|
+
- **`model_reported`** — the `model` field the endpoint echoed back
|
|
116
|
+
- `usage` — the four token kinds, never blended, **or `null`**
|
|
117
|
+
|
|
118
|
+
**`model_reported != model_requested` is ⛔ SUBSTITUTION**, surfaced to the user
|
|
119
|
+
exactly as ⛔ DOWNGRADE is today and never silently accepted. It is the only
|
|
120
|
+
defence against an aggregator quietly serving something else. `unknown` is a
|
|
121
|
+
valid, honest value and is reported as `unknown` — **never as a match**.
|
|
122
|
+
|
|
123
|
+
**A clean model check is not a clean answer.** An aggregator's *provider-level*
|
|
124
|
+
fallback is on by default and it PRESERVES the model id, so the substitution
|
|
125
|
+
check reads clean while the code went to a different company. Engine `api`
|
|
126
|
+
records the response's `provider` echo and reports **⚠ REROUTE**; the other two
|
|
127
|
+
engines cannot see it at all, and their `served_by_note` says so. An absent
|
|
128
|
+
measurement is never a pass.
|
|
129
|
+
|
|
130
|
+
**`usage: null` is not four zeros.** A worker that reported no token counts
|
|
131
|
+
(engine `cli` frequently) returns `null` plus a note. `{0,0,0,0}` would tell
|
|
132
|
+
`/orc-budget` the run was free. Engine `api`'s `cache_write: 0` is the opposite
|
|
133
|
+
case — a real measurement — so the two must never be normalised together.
|
|
134
|
+
|
|
135
|
+
**The fence is per-engine, and the return says which one it had.** Engine `api`
|
|
136
|
+
ENFORCES `declared_files`; engines `claude-shim` and `cli` ASK. A return
|
|
137
|
+
carrying `fence: {declared_files: false}` means the list was an instruction, not
|
|
138
|
+
a rule — treat §6 below as the only real check, and say so to the user rather
|
|
139
|
+
than reporting a constraint that was never applied.
|
|
140
|
+
|
|
141
|
+
**A RESUMED foreign dispatch owes three more fields** (v0.54.0). The dispatch
|
|
142
|
+
return sets `resume_expected: true`, so the obligation is never inferred:
|
|
143
|
+
|
|
144
|
+
- **`resume_state`** — `continued` · `restarted` · `no-op`. Absent on a slice
|
|
145
|
+
with no `resumed_from` is correct; **absent on a resume slice is MALFORMED.**
|
|
146
|
+
A return claiming `restarted` while `preexisting[]` was non-empty is a
|
|
147
|
+
FINDING, not a failure — it is how `/orc-retro` learns which providers ignore
|
|
148
|
+
a resume preamble, so surface it rather than treating it as a bad return.
|
|
149
|
+
- **`preexisting_read[]`** — which pre-existing files the worker actually
|
|
150
|
+
opened. Quoted like `wiki_used`: **what it did, never what the dispatcher
|
|
151
|
+
assumed.** An EMPTY list on a resume whose `preexisting[]` was not empty is an
|
|
152
|
+
honest and informative return — it says the worker ignored the preamble — and
|
|
153
|
+
it must be surfaced, never dropped.
|
|
154
|
+
- **`journal_fidelity`** — relayed from the dispatch return (`per-turn` |
|
|
155
|
+
`streamed-opaque`), so a validator never reports `streamed-opaque` evidence as
|
|
156
|
+
if it had per-turn tool attribution.
|
|
157
|
+
|
|
158
|
+
Everything else in this file applies to a foreign return unchanged: the
|
|
159
|
+
honest-status rules, the pattern attestation, the TDD attestation, the wiki
|
|
160
|
+
attestation, and above all **§6, the worktree delta** — which is engine-blind
|
|
161
|
+
because it reads the worktree rather than the return, and is therefore what
|
|
162
|
+
makes a foreign executor safe at all.
|
|
163
|
+
|
|
164
|
+
## 3. Honest-status rules (executor returns)
|
|
165
|
+
|
|
166
|
+
- `status=done` on a stack with a runnable build/test REQUIRES `evidence`
|
|
167
|
+
{command, exit_code, tail} quoted VERBATIM; a missing block or a false
|
|
168
|
+
`no_runner_detected` is malformed.
|
|
169
|
+
- `done` with a non-empty `unmet[]` is `partial` — treat it as such.
|
|
170
|
+
|
|
171
|
+
## 4. Pattern attestation (when a `pattern` was injected)
|
|
172
|
+
|
|
173
|
+
A task that received a `pattern` slice must return `invariants_checked: true`
|
|
174
|
+
plus the matching `pattern_version`; false/absent on a pattern task is
|
|
175
|
+
malformed.
|
|
176
|
+
|
|
177
|
+
## 5. TDD attestation (when a `tdd_spec` was injected — v0.33.0)
|
|
178
|
+
|
|
179
|
+
A task whose slice carried a `tdd_spec` must return `tdd_state: green|red` —
|
|
180
|
+
`green` only with the passing run quoted in `evidence`; `status=done` with
|
|
181
|
+
`tdd_state: red` (or an absent field) is malformed. `red` is an HONEST return:
|
|
182
|
+
the lane runs its repair loop up to `tdd_loop_max`, then STOPS with the red
|
|
183
|
+
report — never re-dispatch past the cap.
|
|
184
|
+
|
|
185
|
+
## 5b. Wiki attestation (when wiki content or page pointers were injected — v0.41.0)
|
|
186
|
+
|
|
187
|
+
A task whose slice carried wiki material must return **`wiki_used`** — the doc
|
|
188
|
+
paths it ACTUALLY read, or `none`. Quoted like `actual_model`: what the agent
|
|
189
|
+
did, never what the dispatcher assumed.
|
|
190
|
+
|
|
191
|
+
`none` is a valid and INFORMATIVE return, not a failure: it says the pages were
|
|
192
|
+
not useful or were ignored. Record it and surface it — a wiki whose pages are
|
|
193
|
+
shipped into every slice and read by nobody is the failure mode this field
|
|
194
|
+
exists to make visible, and it is invisible if `none` is quietly dropped. Absent
|
|
195
|
+
on a slice that carried wiki material is malformed. Not required otherwise.
|
|
196
|
+
|
|
197
|
+
## 6. Worktree delta (post-wave, every lane that dispatches executors)
|
|
198
|
+
|
|
199
|
+
Compare `git status --short` before and after each dispatch. A path that
|
|
200
|
+
appears, disappears, or **reverts** and is absent from that task's
|
|
201
|
+
`declared_files` is a slice violation regardless of what the return said —
|
|
202
|
+
including a file that became LESS modified, which is how a destructive `git`
|
|
203
|
+
command inside a slice disguises itself as a clean tree. `actual_files` is a
|
|
204
|
+
CLAIM; the worktree is the EVIDENCE. An unexplained delta gates the wave: name
|
|
205
|
+
it, attribute it, and get a decision before closing.
|
|
206
|
+
|
|
207
|
+
**On a RESUMED task the "before" side of the delta is the JOURNAL BASELINE**
|
|
208
|
+
(`orc extra reconcile`, `_shared/extra-dispatch.md`), not the state at the top of
|
|
209
|
+
this wave. A file the previous attempt created is already in the tree and is
|
|
210
|
+
**not** an unexplained delta — it is explained, by the journal, by name. Without
|
|
211
|
+
that the first resumed wave trips its own gate on the work it just recovered.
|
|
212
|
+
|
|
213
|
+
## 7. Gotcha capture (repair loops only — v0.40.0)
|
|
214
|
+
|
|
215
|
+
A return that closes a repair loop (`tdd_state` went red → green, a drift
|
|
216
|
+
round resolved, a reviewer P0/P1 was fixed in-run) carries
|
|
217
|
+
`gotcha_recorded` — either the entry body (`trigger`, `symptom`, `cause`,
|
|
218
|
+
`fix`, `scope`) or `none` with a one-line reason. Absent on a repair-closing
|
|
219
|
+
return is malformed. It is NOT required on a return that never repaired
|
|
220
|
+
anything, and a loop that hit its cap and STOPPED must return `none` — an
|
|
221
|
+
unsolved failure is not a gotcha. The agent RETURNS the body; the
|
|
222
|
+
orchestrator writes the file. See `gotchas.md`.
|