@sabaiway/agent-workflow-kit 14.20.0 → 14.21.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 (41) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +1 -1
  3. package/SKILL.md +1 -1
  4. package/bridges/antigravity-cli-bridge/SKILL.md +38 -50
  5. package/bridges/antigravity-cli-bridge/bin/agy-model-posture.test.mjs +257 -0
  6. package/bridges/antigravity-cli-bridge/bin/agy-review-harness.test.mjs +10 -3
  7. package/bridges/antigravity-cli-bridge/bin/agy-review-honesty.test.mjs +20 -21
  8. package/bridges/antigravity-cli-bridge/bin/agy-review-model-screen.test.mjs +4 -4
  9. package/bridges/antigravity-cli-bridge/bin/agy-review.sh +120 -48
  10. package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +22 -62
  11. package/bridges/antigravity-cli-bridge/bin/agy.sh +109 -16
  12. package/bridges/antigravity-cli-bridge/bin/agy.test.mjs +5 -5
  13. package/bridges/antigravity-cli-bridge/capability.json +14 -7
  14. package/bridges/antigravity-cli-bridge/references/driving-agy.md +16 -20
  15. package/bridges/antigravity-cli-bridge/references/models-and-flags.md +10 -27
  16. package/bridges/codex-cli-bridge/SKILL.md +51 -49
  17. package/bridges/codex-cli-bridge/bin/codex-exec.sh +145 -46
  18. package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +28 -55
  19. package/bridges/codex-cli-bridge/bin/codex-model-posture.test.mjs +371 -0
  20. package/bridges/codex-cli-bridge/bin/codex-review.sh +134 -36
  21. package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +33 -45
  22. package/bridges/codex-cli-bridge/capability.json +22 -8
  23. package/bridges/codex-cli-bridge/references/driving-codex.md +18 -15
  24. package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +16 -12
  25. package/bridges/codex-cli-bridge/setup/README.md +3 -3
  26. package/capability.json +1 -1
  27. package/package.json +1 -1
  28. package/references/modes/bridge-settings.md +10 -7
  29. package/references/modes/recipes.md +1 -1
  30. package/references/modes/review-state.md +1 -1
  31. package/tools/bridge-catalog.mjs +114 -0
  32. package/tools/bridge-posture.mjs +11 -2
  33. package/tools/bridge-settings-read.mjs +41 -5
  34. package/tools/bridge-settings.mjs +111 -23
  35. package/tools/detect-backends.mjs +6 -6
  36. package/tools/family-registry.mjs +3 -3
  37. package/tools/manifest/schema.md +1 -1
  38. package/tools/manifest/validate.mjs +3 -2
  39. package/tools/procedures.mjs +3 -3
  40. package/tools/recipes.mjs +2 -2
  41. package/tools/review-state.mjs +1 -1
@@ -11,29 +11,97 @@
11
11
  // out-of-range value, and — loudly, naming the key — a file that already carries DUPLICATE keys (it
12
12
  // never emits duplicates and never edits blindly around them). It touches only the KEY= line it owns,
13
13
  // preserving every comment/blank/other line verbatim, and creates the dir + file on first apply via
14
- // the hardened out-of-tree atomic writer (symlink/parent/TOCTOU-safe). Model/effort are NOT in the
15
- // allowlist — the wrappers' quality-first guard is untouched (D4). CODEX_SERVICE_TIER carries the
14
+ // the hardened out-of-tree atomic writer (symlink/parent/TOCTOU-safe). The model keys (kind `posture`,
15
+ // spec bridge-settings) are settable: the read-out shows each bridge's host posture and the models its
16
+ // installed CLI offers, and a set naming a model key is judged against that bridge's catalog before
17
+ // any write. It runs only the catalog commands, never a model. CODEX_SERVICE_TIER carries the
16
18
  // credit-rate caveat wherever it renders (from the manifest `effect`).
17
19
  //
18
20
  // Output is ENGLISH/structured (repo-artifact Hard Constraint); the agent localizes when narrating.
19
- // Exit codes: 0 success (reader, preview, or apply); 2 usage (bad args / unknown key / invalid value);
20
- // 1 precondition STOP (duplicate-carrying / symlinked / unreadable file, a write STOP) or a corrupt
21
- // bundle (cannot determine the allowlist). main(argv, ctx) → { code, stdout, stderr }; getenv / home /
22
- // bundleRoot / fs are injectable for host-independent tests.
21
+ // Exit codes: 0 success (reader, preview, or apply); 2 usage (bad args / unknown key / invalid value /
22
+ // a model or effort the catalog does not offer); 1 precondition STOP (duplicate-carrying / symlinked /
23
+ // unreadable file, a write STOP) or a corrupt bundle (cannot determine the allowlist).
24
+ // main(argv, ctx) → Promise of { code, stdout, stderr }; getenv / home / bundleRoot / fs and the
25
+ // catalog bounds are injectable for host-independent tests.
23
26
  //
24
27
  // Dependency-free, Node >= 22. No side effects on import (the isDirectRun idiom).
25
28
 
26
29
  import { isDirectRun } from './direct-run.mjs';
27
30
  import { settingValueValid } from './manifest/validate.mjs';
28
31
  import { writeHostConfigFileAtomic } from './atomic-write.mjs';
32
+ import { readCatalogs } from './bridge-catalog.mjs';
29
33
  import {
30
34
  SETTINGS_SUBDIR, SETTINGS_FILENAME, fail, loadRegistry, allowedLabel, settingsDir, settingsPath,
31
35
  joinLines, readFileState, parseSettings, duplicateKeys, effectiveOf, displayValue,
32
36
  } from './bridge-settings-read.mjs';
33
37
 
38
+ // ── host posture (the model keys) ──────────────────────────────────────────────────────
39
+
40
+ const slotOf = (key) => (key.endsWith('_EFFORT') ? 'effort' : 'model');
41
+ const postureBridges = (registry) => [...new Set([...registry.values()].filter((e) => e.kind === 'posture').map((e) => e.bridge))];
42
+
43
+ // One bridge's posture as { model: {key, value, source, note}, effort?: … } under the given env.
44
+ const postureOfBridge = (registry, parsed, getenv, bridge) => Object.fromEntries([...registry.values()]
45
+ .filter((e) => e.kind === 'posture' && e.bridge === bridge)
46
+ .map((e) => {
47
+ const eff = effectiveOf(e, parsed, getenv);
48
+ return [slotOf(e.key), { key: e.key, value: eff.value, source: eff.source, note: eff.note ?? null, retired: e.retired ?? null }];
49
+ }));
50
+
51
+ const showModel = (v) => (v == null ? '(none)' : v);
52
+ const offeredLabel = (catalog) => catalog.entries
53
+ .map((e) => (e.efforts === undefined ? e.model : `${e.model} (${e.efforts === null ? 'efforts unknown' : e.efforts.join(', ')})`))
54
+ .join(', ');
55
+ const catalogWarning = (bridge, catalog) => (catalog.state === 'not-installed'
56
+ ? `⚠ ${bridge}: not installed (${catalog.cause}) — no offered list`
57
+ : `⚠ ${bridge}: the model catalog is unreadable (${catalog.cause}) — the offered list is unknown`);
58
+
59
+ const renderPosture = (registry, parsed, getenv, catalogs) => {
60
+ const lines = ['', ' host posture (the model each bridge runs; the offered models are read from the installed CLI, none is run):'];
61
+ for (const bridge of postureBridges(registry)) {
62
+ const p = postureOfBridge(registry, parsed, getenv, bridge);
63
+ const effort = p.effort ? ` · effort ${showModel(p.effort.value)} [${p.effort.source}]` : '';
64
+ const notes = [p.model.note, p.effort?.note].filter(Boolean).map((n) => ` — ${n}`).join('');
65
+ lines.push(` ${bridge}: model ${showModel(p.model.value)} [${p.model.source}]${effort}${notes}`);
66
+ const catalog = catalogs[bridge];
67
+ lines.push(catalog.state === 'offered' ? ` offered: ${offeredLabel(catalog)}` : ` ${catalogWarning(bridge, catalog)}`);
68
+ }
69
+ return lines;
70
+ };
71
+
72
+ const postureJson = (registry, parsed, getenv, catalogs) => postureBridges(registry).map((bridge) => ({
73
+ bridge, ...postureOfBridge(registry, parsed, getenv, bridge), catalog: catalogs[bridge],
74
+ }));
75
+
76
+ // The set judgment: the posture the file would give after the ops (the environment ignored), judged
77
+ // against each touched bridge's catalog. Refuses (exit 2) only a model the catalog does not offer or a
78
+ // codex effort that model does not offer; an unreadable catalog yields an "unchecked" statement.
79
+ const judgePosture = async (ops, registry, newLines, ctx) => {
80
+ const bridges = [...new Set(ops.map((op) => registry.get(op.key)).filter((e) => e.kind === 'posture').map((e) => e.bridge))];
81
+ if (!bridges.length) return [];
82
+ const after = parseSettings(joinLines(newLines));
83
+ const catalogs = await readCatalogs(bridges, ctx);
84
+ const unchecked = [];
85
+ for (const bridge of bridges) {
86
+ const { model, effort } = postureOfBridge(registry, after, {}, bridge);
87
+ const catalog = catalogs[bridge];
88
+ if (catalog.state !== 'offered') {
89
+ unchecked.push(`${bridge}: the value was not checked — ${catalog.state === 'not-installed' ? 'not installed' : 'the model catalog is unreadable'} (${catalog.cause}); the wrapper checks it at run time`);
90
+ continue;
91
+ }
92
+ const entry = catalog.entries.find((e) => e.model === model.value);
93
+ if (!entry) throw fail(2, `${model.key}=${model.value} is not a model the installed ${bridge} catalog offers — refusing before any write. Offered: ${catalog.entries.map((e) => e.model).join(', ')}`);
94
+ if (effort && entry.efforts === null) unchecked.push(`${bridge}: the effort was not checked — the catalog entry for ${model.value} lists no efforts; the wrapper checks it at run time`);
95
+ else if (effort && !entry.efforts.includes(effort.value)) {
96
+ throw fail(2, `${effort.key}=${effort.value} is not an effort the installed ${bridge} catalog offers for ${model.value} — refusing before any write. Offered: ${entry.efforts.join(', ')}`);
97
+ }
98
+ }
99
+ return unchecked;
100
+ };
101
+
34
102
  // ── reader render ──────────────────────────────────────────────────────────────────
35
103
 
36
- const renderReader = (registry, parsed, fileState, ctx, path) => {
104
+ const renderReader = (registry, parsed, fileState, ctx, path, catalogs) => {
37
105
  const getenv = ctx.getenv ?? process.env;
38
106
  const lines = [`bridge settings — ${path}`];
39
107
  if (fileState.state === 'absent') lines.push(' (no settings file yet — every knob is at its built-in default)');
@@ -43,10 +111,12 @@ const renderReader = (registry, parsed, fileState, ctx, path) => {
43
111
  const eff = effectiveOf(entry, parsed, getenv);
44
112
  const note = eff.note ? ` — ${eff.note}` : '';
45
113
  const retired = entry.retired ? ' ⚠ RETIRED' : '';
46
- lines.push(` ${entry.key} = ${displayValue(eff.value)} [${eff.source}]${retired}${note}`);
114
+ const shown = entry.kind === 'posture' ? showModel(eff.value) : displayValue(eff.value);
115
+ lines.push(` ${entry.key} = ${shown} [${eff.source}]${retired}${note}`);
47
116
  lines.push(` ${entry.bridge} · ${allowedLabel(entry)} · ${entry.effect}`);
48
117
  if (entry.retired) lines.push(` RETIRED — ${entry.retired}`);
49
118
  }
119
+ lines.push(...renderPosture(registry, parsed, getenv, catalogs));
50
120
  const dups = duplicateKeys(parsed);
51
121
  const unknown = [...parsed.byKey.keys()].filter((k) => !registry.has(k));
52
122
  if (dups.length) lines.push('', ` ⚠ duplicate keys in the file (last wins at read time; the writer refuses to edit until you fix these): ${dups.join(', ')}`);
@@ -56,7 +126,7 @@ const renderReader = (registry, parsed, fileState, ctx, path) => {
56
126
  return lines.join('\n');
57
127
  };
58
128
 
59
- const buildReaderJson = (registry, parsed, fileState, ctx, path) => {
129
+ const buildReaderJson = (registry, parsed, fileState, ctx, path, catalogs) => {
60
130
  const getenv = ctx.getenv ?? process.env;
61
131
  return {
62
132
  path,
@@ -65,6 +135,7 @@ const buildReaderJson = (registry, parsed, fileState, ctx, path) => {
65
135
  const eff = effectiveOf(entry, parsed, getenv);
66
136
  return { key: entry.key, bridge: entry.bridge, kind: entry.kind, effective: eff.value, source: eff.source, note: eff.note ?? null, default: entry.default, retired: entry.retired ?? null };
67
137
  }),
138
+ bridges: postureJson(registry, parsed, getenv, catalogs),
68
139
  duplicateKeys: duplicateKeys(parsed),
69
140
  unknownKeys: [...parsed.byKey.keys()].filter((k) => !registry.has(k)),
70
141
  malformedLines: parsed.malformed.map((m) => m.index + 1),
@@ -182,12 +253,22 @@ const changeLine = (c) => {
182
253
  return ` ${c.key}: ${b} → ${a}`;
183
254
  };
184
255
 
185
- const renderWriter = ({ changes, wrote, path, willWrite, envShadows, caveats }) => {
256
+ // A model key's env value off the host posture is a one-off the wrappers refuse (spec bridge-model),
257
+ // so its shadow line says that instead of "it wins".
258
+ const SHADOW_EFFECT = {
259
+ 'codex-cli-bridge': 'an environment value off the host posture is a one-off, which codex-exec and codex-review refuse unless CODEX_PROBE=1; unset it.',
260
+ 'antigravity-cli-bridge': 'an environment value off the host posture (an empty one included) is a one-off, which agy-review refuses unless AGY_PROBE=1 (agy-run, the probe role, runs it); unset it.',
261
+ };
262
+ const shadowLine = (entry) => ` ⚠ ${entry.key} is currently set in the environment — ${entry.kind === 'posture'
263
+ ? SHADOW_EFFECT[entry.bridge] : 'the env value overrides the file for this session (unset it, or it wins until you do).'}`;
264
+
265
+ const renderWriter = ({ registry, changes, wrote, path, willWrite, envShadows, caveats, unchecked }) => {
186
266
  const lines = [];
187
267
  lines.push(wrote ? `wrote ${path}` : 'bridge-settings — preview (nothing written; re-run with --apply to write)');
188
268
  for (const c of changes) lines.push(changeLine(c));
189
269
  for (const cav of caveats) lines.push(` ↳ ${cav}`);
190
- for (const key of envShadows) lines.push(` ⚠ ${key} is currently set in the environment — the env value overrides the file for this session (unset it, or it wins until you do).`);
270
+ for (const u of unchecked) lines.push(` ⚠ ${u}`);
271
+ for (const key of envShadows) lines.push(shadowLine(registry.get(key)));
191
272
  if (!wrote && willWrite) lines.push('', `re-run with --apply to write ${path}.`);
192
273
  if (!wrote && !willWrite) lines.push(' no change — the file already reads this way.');
193
274
  return lines.join('\n');
@@ -200,7 +281,9 @@ const HELP = `bridge-settings — read / write the host-level bridge settings fi
200
281
  ${'${XDG_CONFIG_HOME:-~/.config}'}/${SETTINGS_SUBDIR}/${SETTINGS_FILENAME} (KEY=VALUE lines; parsed, never sourced)
201
282
 
202
283
  Usage:
203
- node bridge-settings.mjs show every knob's effective value + source
284
+ node bridge-settings.mjs show every knob's effective value + source, each
285
+ bridge's model (codex: and effort) and the models
286
+ its installed CLI offers
204
287
  node bridge-settings.mjs --set KEY=VALUE preview a change (writes nothing)
205
288
  node bridge-settings.mjs --set KEY=VALUE --apply write the change (atomic; creates the file on first use)
206
289
  node bridge-settings.mjs --unset KEY [--apply] return a knob to its built-in default
@@ -209,14 +292,17 @@ Usage:
209
292
  keys (preserved verbatim), never writes
210
293
 
211
294
  Precedence at run time: explicit env (even empty: KEY= disables) > this file > the wrapper's built-in
212
- default. Allowed keys + value rules come from the bundled bridge manifests; model/effort are NOT
213
- settable here (the wrappers' quality guard is untouched). This host file survives every kit upgrade —
295
+ default. Allowed keys + value rules come from the bundled bridge manifests. CODEX_MODEL, CODEX_EFFORT
296
+ and AGY_MODEL set each bridge's host posture; a set of one is checked against that bridge's installed
297
+ CLI catalog before anything is written (an unreadable catalog leaves it unchecked, and says so). The
298
+ command runs only the catalog commands, never a model. This host file survives every kit upgrade —
214
299
  a kit refresh never writes or clobbers it.
215
300
 
216
- Exit codes: 0 success (read / preview / write); 2 usage (bad args, unknown key, invalid value);
217
- 1 precondition STOP (duplicate-carrying / symlinked / unreadable file, or a corrupt bundle).`;
301
+ Exit codes: 0 success (read / preview / write); 2 usage (bad args, unknown key, invalid value, a model
302
+ or effort the catalog does not offer); 1 precondition STOP (duplicate-carrying / symlinked /
303
+ unreadable file, or a corrupt bundle).`;
218
304
 
219
- export const main = (argv = [], ctx = {}) => {
305
+ export const main = async (argv = [], ctx = {}) => {
220
306
  const getenv = ctx.getenv ?? process.env;
221
307
  try {
222
308
  if (argv.includes('--help') || argv.includes('-h')) return { code: 0, stdout: HELP, stderr: '' };
@@ -236,9 +322,10 @@ export const main = (argv = [], ctx = {}) => {
236
322
  if (ops.length === 0) {
237
323
  const fileState = readFileState(path, ctx);
238
324
  const parsed = parseSettings(fileState.text ?? '');
325
+ const catalogs = await readCatalogs(postureBridges(registry), ctx);
239
326
  const stdout = json
240
- ? JSON.stringify(buildReaderJson(registry, parsed, fileState, ctx, path), null, 2)
241
- : renderReader(registry, parsed, fileState, ctx, path);
327
+ ? JSON.stringify(buildReaderJson(registry, parsed, fileState, ctx, path, catalogs), null, 2)
328
+ : renderReader(registry, parsed, fileState, ctx, path, catalogs);
242
329
  return { code: 0, stdout, stderr: '' };
243
330
  }
244
331
 
@@ -271,15 +358,16 @@ export const main = (argv = [], ctx = {}) => {
271
358
 
272
359
  const newLines = ops.reduce(applyOp, parsed.lines);
273
360
  const body = joinLines(newLines);
361
+ const unchecked = await judgePosture(ops, registry, newLines, ctx);
274
362
 
275
363
  // The machine surface carries the SAME spend/credit-rate caveats the human preview prints (D4) — a
276
364
  // --json consumer must not miss the spend consent warning the human render shows.
277
- const jsonBody = (wrote) => ({ path, wrote, noop, changes: changes.map((c) => ({ key: c.key, kind: c.kind, before: c.before, after: c.after })), envShadows, caveats });
365
+ const jsonBody = (wrote) => ({ path, wrote, noop, changes: changes.map((c) => ({ key: c.key, kind: c.kind, before: c.before, after: c.after })), envShadows, caveats, unchecked });
278
366
 
279
367
  if (!apply || noop) {
280
368
  const stdout = json
281
369
  ? JSON.stringify(jsonBody(false), null, 2)
282
- : renderWriter({ changes, wrote: false, path, willWrite: !noop, envShadows, caveats });
370
+ : renderWriter({ registry, changes, wrote: false, path, willWrite: !noop, envShadows, caveats, unchecked });
283
371
  return { code: 0, stdout, stderr: '' };
284
372
  }
285
373
 
@@ -287,7 +375,7 @@ export const main = (argv = [], ctx = {}) => {
287
375
  writeHostConfigFileAtomic(settingsDir(ctx), SETTINGS_FILENAME, body, ctx, { noun: 'the bridge settings file' });
288
376
  const stdout = json
289
377
  ? JSON.stringify(jsonBody(true), null, 2)
290
- : renderWriter({ changes, wrote: true, path, willWrite: false, envShadows, caveats });
378
+ : renderWriter({ registry, changes, wrote: true, path, willWrite: false, envShadows, caveats, unchecked });
291
379
  return { code: 0, stdout, stderr: '' };
292
380
  } catch (err) {
293
381
  return { code: err.exitCode ?? 1, stdout: '', stderr: `bridge-settings: ${err.message}` };
@@ -295,7 +383,7 @@ export const main = (argv = [], ctx = {}) => {
295
383
  };
296
384
 
297
385
  if (isDirectRun(import.meta.url)) {
298
- const r = main(process.argv.slice(2));
386
+ const r = await main(process.argv.slice(2));
299
387
  if (r.stdout) console.log(r.stdout);
300
388
  if (r.stderr) console.error(r.stderr);
301
389
  process.exit(r.code);
@@ -75,7 +75,7 @@ const RAW_BACKENDS = [
75
75
  'codex-exec --resume-last [--nonce <n>] <plan-file|->',
76
76
  'codex-exec --resume <session-id> [--nonce <n>] <plan-file|->',
77
77
  ],
78
- receipt: "side effect — a NONCED run mints ONE exec receipt beside the delegation store: the dispatch nonce seam is the AW_DISPATCH_NONCE environment value or its plain-argument equivalent --nonce <n>, recognised ONLY before the prompt operand (after the operand or a literal '--' it is passthrough payload, never a flag), under the safe grammar [A-Za-z0-9._-]{1,64} — anything else, a duplicate, or a flag disagreeing with a non-empty env value refuses PRE-SPEND. The store directory resolves exactly as the kit's delegation store does: the dirname of an ABSOLUTE AW_DELEGATION_STORE (a relative one, or one ending in a path separator, refuses), else the git common dir. The artifact is agent-workflow-exec-receipt-<backendLength>-<backend>-<nonce>.json in two states: 'reserved' is written atomically and NO-CLOBBER immediately before the CLI runs — that write IS the nonce reservation, so a second dispatch on the same nonce, or an already-taken report name, refuses BEFORE any spend — and 'terminal' replaces it in place at exit. A nonced run also refuses pre-spend when no timeout/gtimeout binary can cap it (an accounted dispatch that cannot be capped can never honour the terminal-exit rule; a nonce-LESS run still warns and runs uncapped), when node is missing (the mint core), and when the prompt rides on stdin instead of a contract FILE — contractDigest is computed BY THIS WRAPPER from the dispatch file it was actually handed, so the kit can refuse a run that executed a different contract than the one it opened. That digest is taken from the SAME bytes already read as the prompt, never a second open of the path: two reads leave a window in which the file can be swapped, and the run would then execute one contract while its receipt claimed the digest of another. The header's own nonce must EQUAL the dispatch nonce — 'dispatch open' copies the nonce FROM the header, so a disagreeing --nonce could only reserve an identity no return would ever absorb, and it refuses pre-spend. A contract file edited BETWEEN 'dispatch open' and the run is caught at ABSORB by the contractDigest comparison, not pre-spend: the wrapper never reads the ledger, and that boundary is what the whole lane rests on. At exit the wrapper FIRST re-reads its reservation and verifies its own opaque owner token — a foreign owner refuses having published NOTHING, neither report nor receipt — THEN writes the delegate's final message atomically to agent-workflow-exec-report-<backendLength>-<backend>-<nonce>.txt, THEN re-verifies the owner and REPLACES the reservation with the terminal receipt {schema, kind, state, backend, nonce, owner, contractDigest, wrapperVersion, posture {model, effort, tier}, capS, killGraceS, sessionId, exitStatus, outcome, reportDigest, reportLength, timestamp}: the report is complete on disk before any artifact says the run arrived. capS and killGraceS are the cap the run ACTUALLY applied. outcome is the wrapper's own SUBSET of the ledger's vocabulary — exit 0 with a session id -> success, exit 0 without one -> missing-identity, ANY nonzero exit including the timeout codes 124 and 137 -> transport-failure; every orchestrator judgment is recorded at absorb time, never claimed here. The session id is captured BEFORE outcome branching, so a FAILED run records one too; in resume mode it is the validated resume id. FAIL-CLOSED, deliberately NOT the review lane's warn-only receipt: a publication that cannot complete exits nonzero with a DISTINCT status, and the message states only what the run can still prove. 70: the reservation could not be verified BEFORE any publication — NOTHING was published, not the report and not the receipt, and because the artifact found there belongs to another run it is never a '--no-receipt' source. 71: a publication stopped after that point — either the report write failed (nothing beyond the reservation was published; the '--no-receipt' absorb then records reportLength 0, ineligible by the name empty-report) or the report IS on disk and the terminal receipt was not completed (the absorb reads it, report-if-present). The post-report lane never claims the reservation still stands, because after that point its fate is no longer something this run observed. Every lane names the tree as partial/dirtied rather than untouched. A nonce-LESS invocation is byte-unchanged: no reservation, no receipt, no artifact, no node.",
78
+ receipt: "side effect — a NONCED run mints ONE exec receipt beside the delegation store: the dispatch nonce seam is the AW_DISPATCH_NONCE environment value or its plain-argument equivalent --nonce <n>, recognised ONLY before the prompt operand (after the operand or a literal '--' it is passthrough payload, never a flag), under the safe grammar [A-Za-z0-9._-]{1,64} — anything else, a duplicate, or a flag disagreeing with a non-empty env value refuses PRE-SPEND. The store directory resolves exactly as the kit's delegation store does: the dirname of an ABSOLUTE AW_DELEGATION_STORE (a relative one, or one ending in a path separator, refuses), else the git common dir. The artifact is agent-workflow-exec-receipt-<backendLength>-<backend>-<nonce>.json in two states: 'reserved' is written atomically and NO-CLOBBER immediately before the CLI runs — that write IS the nonce reservation, so a second dispatch on the same nonce, or an already-taken report name, refuses BEFORE any spend — and 'terminal' replaces it in place at exit. A nonced run also refuses pre-spend when no timeout/gtimeout binary can cap it (an accounted dispatch that cannot be capped can never honour the terminal-exit rule; a nonce-LESS run still warns and runs uncapped), when node is missing (the mint core), and when the prompt rides on stdin instead of a contract FILE — contractDigest is computed BY THIS WRAPPER from the dispatch file it was actually handed, so the kit can refuse a run that executed a different contract than the one it opened. That digest is taken from the SAME bytes already read as the prompt, never a second open of the path: two reads leave a window in which the file can be swapped, and the run would then execute one contract while its receipt claimed the digest of another. The header's own nonce must EQUAL the dispatch nonce — 'dispatch open' copies the nonce FROM the header, so a disagreeing --nonce could only reserve an identity no return would ever absorb, and it refuses pre-spend. A contract file edited BETWEEN 'dispatch open' and the run is caught at ABSORB by the contractDigest comparison, not pre-spend: the wrapper never reads the ledger, and that boundary is what the whole lane rests on. At exit the wrapper FIRST re-reads its reservation and verifies its own opaque owner token — a foreign owner refuses having published NOTHING, neither report nor receipt — THEN writes the delegate's final message atomically to agent-workflow-exec-report-<backendLength>-<backend>-<nonce>.txt, THEN re-verifies the owner and REPLACES the reservation with the terminal receipt {schema, kind, state, backend, nonce, owner, contractDigest, wrapperVersion, posture {model, effort, tier}, capS, killGraceS, sessionId, exitStatus, outcome, reportDigest, reportLength, timestamp}: the report is complete on disk before any artifact says the run arrived. capS and killGraceS are the cap the run ACTUALLY applied. outcome is the wrapper's own SUBSET of the ledger's vocabulary — exit 0 with a session id -> success, exit 0 without one -> missing-identity, ANY nonzero exit including the timeout codes 124 and 137 -> transport-failure; every orchestrator judgment is recorded at absorb time, never claimed here. The session id is captured BEFORE outcome branching, so a FAILED run records one too; in resume mode it is the validated resume id. FAIL-CLOSED, deliberately NOT the review lane's warn-only receipt: a publication that cannot complete exits nonzero with a DISTINCT status, and the message states only what the run can still prove. 70: the reservation could not be verified BEFORE any publication — NOTHING was published, not the report and not the receipt, and because the artifact found there belongs to another run it is never a '--no-receipt' source. 71: a publication stopped after that point — either the report write failed (nothing beyond the reservation was published; the '--no-receipt' absorb then records reportLength 0, ineligible by the name empty-report) or the report IS on disk and the terminal receipt was not completed (the absorb reads it, report-if-present). The post-report lane never claims the reservation still stands, because after that point its fate is no longer something this run observed. Every lane names the tree as partial/dirtied rather than untouched. A nonce-LESS invocation is byte-unchanged: no reservation, no receipt, no artifact, and it needs no node.",
79
79
  passthrough: {
80
80
  policy: 'guarded',
81
81
  blocked: ['-c*', '--config*', '-s*', '--sandbox*', '--dangerously-bypass-approvals-and-sandbox', '--dangerously-bypass-hook-trust', '--full-auto', '--oss', '--local-provider*', '-p*', '--profile*', '-m*', '--model*', '-o*', '--output-last-message*', '--json*', '--color*', '--output-schema*', '--ephemeral*'],
@@ -83,9 +83,9 @@ const RAW_BACKENDS = [
83
83
  },
84
84
  notes: [
85
85
  'nested-sandbox limit: codex-exec ships its OWN OS sandbox (bwrap workspace-write) and cannot run nested inside a harness sandbox (the FS turns read-only) — route it OUTSIDE the harness sandbox (excludedCommands / a per-run consented bypass) on the OBSERVED bwrap/EPERM failure, never a preemptive blanket',
86
- 'exec posture banner: ONE stderr line before dispatch states the ACTUAL run posture — exec posture: model=… effort=… tier=… sandbox=workspace-write session=fresh|resume:<id> timeout=… — from RESOLVED post-validation values; the resume id is validated pre-spend, and control bytes in any banner field refuse pre-spend',
86
+ 'exec posture banner: ONE stderr line before dispatch states the ACTUAL run posture — exec posture: model=… effort=… tier=… sandbox=workspace-write session=fresh|resume:<id> source=… timeout=… — from RESOLVED post-validation values; the resume id is validated pre-spend, and control bytes in any banner field refuse pre-spend',
87
87
  'threat model: the sidecar byte and grammar screens detect corrupted input under a trusted parent environment. A hostile parent environment — including exported shell functions or PATH substitution of core/backend commands — is outside the threat model and can substitute the backend itself. Targeted shadow-proof resolution protects banner/dispatch honesty from accidental shadowing; it is not an environment security boundary',
88
- 'the exec posture banner appends a banner-only timeout=<duration|uncapped> field — exactly the duration handed to timeout(1), uncapped when no timeout/gtimeout binary caps the run; INFORMATIONAL only: it is never persisted in a receipt or session sidecar',
88
+ 'the exec posture banner appends a banner-only source=model:<s>,effort:<s> field — where each value came from: default, setting or environment — then a banner-only timeout=<duration|uncapped> field — exactly the duration handed to timeout(1), uncapped when no timeout/gtimeout binary caps the run; INFORMATIONAL only: neither is persisted in a receipt or session sidecar',
89
89
  'quote the posture banner verbatim when labeling this dispatch — the banner is the machine-stated posture; a prose re-type drifts',
90
90
  'every-run nested-sandbox scan (DUAL policy, deliberately two different rules): the scan runs on EVERY completed run, not only a failed one, because a run that SURVIVES the nested-sandbox failure exits 0 with an ungrounded answer and nothing said so. Failed run (rc != 0): the existing loose whole-trace combination rule prints the recovery hint. Successful run (rc == 0): a warning fires ONLY on precise per-item evidence — both a sandbox-mechanism token AND a permission/read-only failure token inside the aggregated_output of ONE command_execution item whose failure is PROVEN (a nonzero exit_code, or the serialized status "failed"); a null exit_code is never failure by itself, tokens split across two items never fire, and a successful command\'s output never fires. The answer is printed FIRST on stdout, then the warning on stderr. HONEST RESIDUAL: the exit status does NOT change on that lane (a distinct nonzero exit would give a heuristic scan DENY polarity, refusing real work whenever the scan over-warns), so an orchestrator keying on exit status alone can still bank an ungrounded answer — the stderr warning is the signal',
91
91
  ],
@@ -99,7 +99,7 @@ const RAW_BACKENDS = [
99
99
  continue: [],
100
100
  receipt: 'side effect — a successful review appends one JSON receipt line to <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides): fingerprint = sha256 over the canonical uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the review-payload domain; never-committable untracked paths — character/block devices, FIFOs, sockets — are excluded from the domain entirely, untracked symlinks/directories ride as name-only notes) in code mode, the artifact-file sha256 in plan mode; durationS = integer wall-clock seconds from CLI start through verdict parsing and the wrapper prints review duration: <n>s; blocking = the count of [blocker] and [major] lines (schema mode: findings with either severity; a payload whose findings cannot be counted fails the run, no receipt); artifactPath = the normalized realpath on plan receipts only (repo-relative inside the work tree, absolute otherwise), while a double quote, backslash or control byte refuses pre-spend because the receipt encoder cannot carry it; verdict parsed from the mandated literal verdict line (schema mode: the verdict field); always fresh:true (one-shot) + grounded:true (native AGENTS.md auto-merge, factsHash null); probe = whether the run relaxed the quality guards (CODEX_PROBE=1), written on EVERY receipt so it self-declares — the kit\'s review-state gate rejects a probe-marked receipt (a probe review never attests) and equally rejects an unmarked one (silence is not a declaration); posture = the ACTUAL run posture {model, effort, tier} (tier null on the standard tier), written on EVERY receipt (D5) — the gate rejects a receipt with an absent/invalid posture (a pre-D5 wrapper minted it; re-run the review), one stderr banner line states the same posture, and a posture value carrying control bytes refuses pre-spend in every mode; a run whose final message carries NO recognized \'Verdict: <ship|revise|rethink>\' line — empty or missing output included — exits 4 with NO receipt (D4: a FAILED review to RE-RUN, never a fatal session error); when the dispatch nonce seam is supplied — the AW_REVIEW_NONCE environment value or its plain-argument equivalent --nonce <n> (one seam: the flag assigns the same value; supplying both with different values refuses pre-spend) — under the safe grammar [A-Za-z0-9._-]{1,64} (anything else refuses pre-spend), the wrapper first mints the finding MANIFEST {schema, backend, nonce, fingerprint, findings} beside the receipts file (agent-workflow-finding-manifest-<backend>-<nonce>.json; atomic, no-clobber — a byte-identical rewrite is an idempotent no-op, different bytes refuse loudly) ORDERED before the receipt append — a failed manifest write EXCLUDES the receipt append, so a nonce-supplied dispatch can never land a receipt without its readable manifest; a nonce-less invocation adds NO nonce field and mints NO finding manifest (the existing wrapperVersion field still changes with each bridge release); a write failure warns, never fails the review',
101
101
  notes: [
102
- 'the review posture banner appends a banner-only timeout=<duration> field — exactly the duration handed to timeout(1); the hard-timeout preflight fails CLOSED when no timeout/gtimeout binary exists (the wrapper refuses by name before any CLI run, so an uncapped review run can no longer happen), and the field never enters the receipt posture or the D5 banner↔receipt parity',
102
+ 'the review posture banner appends a banner-only source=model:<s>,effort:<s> field — where each value came from: default, setting or environment — then a banner-only timeout=<duration> field — exactly the duration handed to timeout(1); the hard-timeout preflight fails CLOSED when no timeout/gtimeout binary exists (the wrapper refuses by name before any CLI run, so an uncapped review run can no longer happen), and neither field enters the receipt posture or the D5 banner↔receipt parity',
103
103
  'quote the posture banner verbatim when labeling this dispatch — the banner is the machine-stated posture; a prose re-type drifts',
104
104
  ],
105
105
  },
@@ -139,11 +139,11 @@ const RAW_BACKENDS = [
139
139
  'agy-review --continue [--decided @f] [--focus "…"] [--nonce <n>]',
140
140
  'agy-review --conversation <id> [--decided @f] [--focus "…"] [--nonce <n>]',
141
141
  ],
142
- receipt: "side effect — a successful review appends one JSON receipt line to <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides; plan/diff outside a git tree: warn + skip unless overridden): fingerprint = sha256 over the canonical uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the review-payload domain; never-committable untracked paths — character/block devices, FIFOs, sockets — are excluded from the domain entirely, untracked symlinks/directories ride as name-only notes) in code mode, the artifact-file sha256 in plan/diff mode; durationS = integer wall-clock seconds from CLI start through verdict parsing and the wrapper prints review duration: <n>s; blocking = the count of numbered items in the first ### Blocking section; artifactPath = the normalized realpath on plan/diff receipts only (repo-relative inside the work tree, absolute otherwise), while a double quote, backslash or control byte refuses pre-spend because the receipt encoder cannot carry it; verdict recorded verbatim from the mandated '### Verdict' section (SHIP / SHIP WITH NITS / REWORK); grounded = whether a NON-EMPTY --facts payload was supplied (code mode refuses pre-spend without one — no run, no receipt — unless --ungrounded/AGY_PROBE=1; in plan/diff an empty payload records grounded:false — fail-closed, the state gate rejects it), factsHash = sha256 of the facts payload; a continuation receipt is fresh:false (informational-only — it cannot attest the folded tree); probe = whether the run relaxed the quality guards (AGY_PROBE=1), written on EVERY receipt so it self-declares — the kit's review-state gate rejects a probe-marked receipt (a probe review never attests) and equally rejects an unmarked one (silence is not a declaration); posture = the ACTUAL run posture {model} (agy has no tier), written on EVERY receipt (D5) — the gate rejects a receipt with an absent/invalid posture (a pre-D5 wrapper minted it; re-run the review), one stderr banner line states the same posture, an ATTESTING review with AGY_MODEL explicitly emptied refuses pre-spend, and a model string carrying control bytes refuses pre-spend in every mode; delivery = how the change set REACHED the model, currently emitted as 'inline' (the whole set rode one prompt — proven by construction) or 'fed' (a chunked feed whose per-part echo proof verified); REQUIRED on every agy code receipt and its ABSENCE is what stops a pre-fed-lane receipt attesting, while the gate accepts any well-formed declaration rather than a particular value; absent by construction on plan/diff/continuation receipts, which carry no change set; a run whose output carries NO recognized '### Verdict' section — empty output included — exits 4 with NO receipt (D4: a FAILED review to RE-RUN, never a fatal session error); when the dispatch nonce seam is supplied — the AW_REVIEW_NONCE environment value or its plain-argument equivalent --nonce <n> (one seam: the flag assigns the same value; supplying both with different values refuses pre-spend) — under the safe grammar [A-Za-z0-9._-]{1,64} (anything else refuses pre-spend), the wrapper first mints the finding MANIFEST {schema, backend, nonce, fingerprint, findings} beside the receipts file (agent-workflow-finding-manifest-<backend>-<nonce>.json; atomic, no-clobber — a byte-identical rewrite is an idempotent no-op, different bytes refuse loudly) ORDERED before the receipt append — a failed manifest write EXCLUDES the receipt append, so a nonce-supplied dispatch can never land a receipt without its readable manifest; a nonce-less invocation adds NO nonce field and mints NO finding manifest (the existing wrapperVersion field still changes with each bridge release); a write failure warns, never fails the review",
142
+ receipt: "side effect — a successful review appends one JSON receipt line to <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides; plan/diff outside a git tree: warn + skip unless overridden): fingerprint = sha256 over the canonical uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the review-payload domain; never-committable untracked paths — character/block devices, FIFOs, sockets — are excluded from the domain entirely, untracked symlinks/directories ride as name-only notes) in code mode, the artifact-file sha256 in plan/diff mode; durationS = integer wall-clock seconds from CLI start through verdict parsing and the wrapper prints review duration: <n>s; blocking = the count of numbered items in the first ### Blocking section; artifactPath = the normalized realpath on plan/diff receipts only (repo-relative inside the work tree, absolute otherwise), while a double quote, backslash or control byte refuses pre-spend because the receipt encoder cannot carry it; verdict recorded verbatim from the mandated '### Verdict' section (SHIP / SHIP WITH NITS / REWORK); grounded = whether a NON-EMPTY --facts payload was supplied (code mode refuses pre-spend without one — no run, no receipt — unless --ungrounded/AGY_PROBE=1; in plan/diff an empty payload records grounded:false — fail-closed, the state gate rejects it), factsHash = sha256 of the facts payload; a continuation receipt is fresh:false (informational-only — it cannot attest the folded tree); probe = whether the run relaxed the quality guards (AGY_PROBE=1), written on EVERY receipt so it self-declares — the kit's review-state gate rejects a probe-marked receipt (a probe review never attests) and equally rejects an unmarked one (silence is not a declaration); posture = the ACTUAL run posture {model} (agy has no tier), written on EVERY receipt (D5) — the gate rejects a receipt with an absent/invalid posture (a pre-D5 wrapper minted it; re-run the review), one stderr banner line states the same posture, a one-off model (an explicitly empty AGY_MODEL included) refuses pre-spend unless AGY_PROBE=1, and a model string carrying control bytes refuses pre-spend in every mode; delivery = how the change set REACHED the model, currently emitted as 'inline' (the whole set rode one prompt — proven by construction) or 'fed' (a chunked feed whose per-part echo proof verified); REQUIRED on every agy code receipt and its ABSENCE is what stops a pre-fed-lane receipt attesting, while the gate accepts any well-formed declaration rather than a particular value; absent by construction on plan/diff/continuation receipts, which carry no change set; a run whose output carries NO recognized '### Verdict' section — empty output included — exits 4 with NO receipt (D4: a FAILED review to RE-RUN, never a fatal session error); when the dispatch nonce seam is supplied — the AW_REVIEW_NONCE environment value or its plain-argument equivalent --nonce <n> (one seam: the flag assigns the same value; supplying both with different values refuses pre-spend) — under the safe grammar [A-Za-z0-9._-]{1,64} (anything else refuses pre-spend), the wrapper first mints the finding MANIFEST {schema, backend, nonce, fingerprint, findings} beside the receipts file (agent-workflow-finding-manifest-<backend>-<nonce>.json; atomic, no-clobber — a byte-identical rewrite is an idempotent no-op, different bytes refuse loudly) ORDERED before the receipt append — a failed manifest write EXCLUDES the receipt append, so a nonce-supplied dispatch can never land a receipt without its readable manifest; a nonce-less invocation adds NO nonce field and mints NO finding manifest (the existing wrapperVersion field still changes with each bridge release); a write failure warns, never fails the review",
143
143
  notes: [
144
144
  'transport: every review dispatch drives the CLI in --output-format json (plus --disable-slash-commands) and the returned envelope is parsed in node (bin/agy-envelope.mjs) — the operator-facing invocations and flags above do NOT change, and on a ZERO exit the wrapper still PRINTS the review text, never JSON. A missing or unreadable envelope on a zero exit is a loud failure with NO receipt, never a downgraded verdict and never a fallback to raw-stdout parsing; a non-zero CLI exit keeps its own code and message, and publishes the captured stdout unchanged from the SINGLE dispatch or the FINAL fed turn (which may therefore be a JSON or partial payload — the envelope is parsed only on a zero exit); an INTERMEDIATE feed turn is the exception, its output stays private (Invariant E) and its failure prints only a named error. Enforced by a PRE-SPEND capability probe, not a version floor: agy --help must advertise --output-format and --disable-slash-commands, node must be >= 22, and bin/agy-envelope.mjs must be present — otherwise the review refuses before any run is spent and names the missing capability',
145
145
  'pre-dispatch host-diff: before the FIRST dispatch of this bridge, diff its declared networkHosts against the live sandbox allow-list — a missing host is surfaced to the maintainer BEFORE dispatching, never fired into a known prompt',
146
- 'the review posture banner appends a banner-only timeout=<duration> field — exactly the duration agy-run hands to timeout(1); the hard-timeout preflight fails CLOSED when no timeout/gtimeout binary exists (the wrapper refuses by name before any CLI run, so an uncapped review run can no longer happen), and the field never enters the receipt posture or the D5 banner↔receipt parity',
146
+ 'the review posture banner appends a banner-only source=model:<s> field — where the model came from: default, setting or environment — then a banner-only timeout=<duration> field — exactly the duration agy-run hands to timeout(1); the hard-timeout preflight fails CLOSED when no timeout/gtimeout binary exists (the wrapper refuses by name before any CLI run, so an uncapped review run can no longer happen), and neither field enters the receipt posture or the D5 banner↔receipt parity',
147
147
  'quote the posture banner verbatim when labeling this dispatch — the banner is the machine-stated posture; a prose re-type drifts',
148
148
  ],
149
149
  },
@@ -672,9 +672,9 @@ export const surveyBridges = (deps = {}) => {
672
672
  // Preserve findOnPath's THREE-state result (present | missing | unknown): an `unknown` (EACCES,
673
673
  // "cannot confirm") must stay distinct from a real `missing` — never flattened to a false boolean.
674
674
  const wrappers = m.wrapperCmds.map((cmd) => ({ cmd, state: probe(cmd, deps).state }));
675
- // Fact-only: the settings knobs ACTIVE (env/file, non-default) for THIS bridge. Model/effort are
676
- // structurally absent from the registry, so this can NEVER carry a model claim (the survey's
677
- // no-default-model-claim invariant). Localized-on-error like every other survey.
675
+ // Fact-only: the settings knobs ACTIVE (env/file, non-default) for THIS bridge. The model keys are
676
+ // knobs too, but their rows live in the snapshot's `posture`, never `active`, so the survey still
677
+ // emits no model field (its no-default-model-claim invariant). Localized-on-error like every survey.
678
678
  const settings = snapshot.error
679
679
  ? { error: snapshot.error }
680
680
  : { active: snapshot.active.filter((a) => a.bridge === m.name) };
@@ -68,7 +68,7 @@ unlike `contract`, it is validated by `validate.mjs` itself: a malformed entry *
68
68
  this block). Each entry:
69
69
 
70
70
  - `key` (string, required) — UPPER_SNAKE_CASE env-var name; unique across the array.
71
- - `kind` (string, required) — `enum | integer | duration | boolean`.
71
+ - `kind` (string, required) — `enum | integer | duration | boolean | posture` (`posture`: a non-empty string with no control byte and no leading or trailing whitespace — a bridge model or effort; the catalog, not the kind, decides which values a host offers).
72
72
  - `enum` → `values` (string[], non-empty, unique).
73
73
  - `integer` → `min` / `max` (safe integers, `min <= max`); values are decimal strings.
74
74
  - `duration` → the wrappers' shell duration grammar (`5m`, `30m`, `90s`): a unit suffix
@@ -40,7 +40,7 @@ export const INVALID = 'invalid';
40
40
  // declared `default`), the kit-side bridge-settings writer (for a user-supplied value), and — mirrored
41
41
  // in shell as aw_settings_valid — the wrappers all use, so a value the writer accepts is exactly a
42
42
  // value a wrapper honors. A value is always compared as a STRING (the settings-file wire format).
43
- export const SETTING_KINDS = new Set(['enum', 'integer', 'duration', 'boolean']);
43
+ export const SETTING_KINDS = new Set(['enum', 'integer', 'duration', 'boolean', 'posture']);
44
44
  // The wrappers' shell duration grammar: a unit suffix is REQUIRED (a bare integer is invalid), and
45
45
  // zero durations are rejected — `timeout 0` DISABLES a hard cap, so a persistent settings line could
46
46
  // otherwise silently remove the stall guard.
@@ -56,6 +56,7 @@ export const settingValueValid = (entry, value) => {
56
56
  }
57
57
  case 'duration': return DURATION_RE.test(value) && !ZERO_DURATION_RE.test(value);
58
58
  case 'boolean': return value === '0' || value === '1';
59
+ case 'posture': return value !== '' && !/[\u0000-\u001f\u007f]/u.test(value) && !/^\s|\s$/u.test(value);
59
60
  default: return false;
60
61
  }
61
62
  };
@@ -613,7 +614,7 @@ export const validateManifest = (skillDir) => {
613
614
  }
614
615
  }
615
616
  if (!SETTING_KINDS.has(entry.kind)) {
616
- errors.push(`${at}.kind must be one of enum|integer|duration|boolean`);
617
+ errors.push(`${at}.kind must be one of enum|integer|duration|boolean|posture`);
617
618
  return; // the typed checks below are meaningless without a kind
618
619
  }
619
620
  if (entry.kind === 'enum'
@@ -154,8 +154,8 @@ export const extractSection = (text, activity) => {
154
154
 
155
155
  const resolveAllSlots = ({ activity, config, detection, overrides, surveyLens }) => {
156
156
  // The host-level settings knobs (manifest-as-source), best-effort: a corrupt bundle degrades to none
157
- // and the advisor never crashes. Attached per wrapper cmd via each knob's `appliesTo`. Fact-only —
158
- // model/effort are not knobs, so no model claim can ride here.
157
+ // and the advisor never crashes. Attached per wrapper cmd via each knob's `appliesTo` — the model
158
+ // keys included, as knobs with their allowed-value label, never an effective-model claim.
159
159
  const registry = (() => {
160
160
  try {
161
161
  return loadRegistry({});
@@ -494,7 +494,7 @@ const costLanesAdvice = () => [
494
494
  'Cost lanes (orchestration.md §5) — route every step to the cheapest adequate executor:',
495
495
  ' • L0 deterministic script — run the gate matrix as ONE batch: /agent-workflow-kit gates (the project-declared docs/ai/gates.json); rotation headroom: archive-changelog / archive-issues / archive-decisions --check. An exit code beats a model re-read.',
496
496
  ' • L1 cheap subagent (small model, low effort, read-only tools — /agent-workflow-kit agents places the vehicles) — mechanical sweeps, changelog fact-skeletons, gate-failure triage. Extraction/drafting ONLY; the orchestrator verifies the output and owns every conclusion.',
497
- ' • L2 subscription bridge (codex / agy) — reviews per the resolved recipe above, on frontier bridge models (quality-first).',
497
+ ' • L2 subscription bridge (codex / agy) — reviews per the resolved recipe above, on each bridge\'s host posture (the model the user set, else the bridge default); a weaker model is the user\'s setting, never a per-call choice — a probe is the one route to a one-off and its review receipt never attests.',
498
498
  ' • L3 frontier — judgment: plan/fold/synthesis, ADR/handover/changelog-entry wording, persuasive copy, go/no-go, real code.',
499
499
  ' • A step with no named guardrail does not move down a lane; red lines never move down (council review models · real code · memory/copy wording · the maintainer approval asks).',
500
500
  ' • Sandbox lanes (under an OS sandbox): the L0 surfaces are sandbox-safe — gates/ledger/state/fold checks, git reads, plain no-network tests; the bridge wrappers are genuinely unsandboxed (network); npm-cache-touching commands are COMMAND-SHAPE dependent — first try the sandbox-safe shape (cache under $TMPDIR, offline/notifier off). Move ONLY the failing command out of the sandbox, never its class; BATCH consecutive unsandboxed calls. Pre-dispatch host-diff: before the FIRST dispatch of each bridge, diff its manifest networkHosts against the live sandbox allow-list — a missing host is surfaced to the maintainer BEFORE dispatching, never fired into a known prompt. Nested-sandbox honesty: a backend CLI shipping its OWN OS sandbox cannot run nested inside a harness sandbox — route it outside (excludedCommands / a per-run consented bypass) on the OBSERVED failure, never a preemptive blanket.',
package/tools/recipes.mjs CHANGED
@@ -171,11 +171,11 @@ const dispatchFor = (recipe, detection) => {
171
171
  });
172
172
  };
173
173
 
174
- const QUOTA_NOTE = "Prefer the cheapest model that fits the task; don't reach for a top-tier model by reflex.";
174
+ const QUOTA_NOTE = "Each bridge runs the host posture the user set; a weaker model is the user's bridge setting, never a per-call choice.";
175
175
  const COUNCIL_QUOTA_NOTE = "Council spends two backends' quota for one decision — reserve it for changes that justify the cost.";
176
176
 
177
177
  // The quota reminder rides a subscription-BACKEND dispatch only: the executor vehicle spends no
178
- // bridge quota, and its recipe is a frontier one, so the cheapest-model reminder would contradict it.
178
+ // bridge quota, and its recipe is a frontier one, so a bridge-posture reminder would not apply to it.
179
179
  const notesFor = (recipe, dispatch) => {
180
180
  const notes = [];
181
181
  if (dispatch.some((d) => d.role !== CARRY_ROLE)) notes.push(QUOTA_NOTE);
@@ -48,7 +48,7 @@
48
48
  // (artifact ≠ "code") and continuations (fresh:false — agy --continue/--conversation cannot attest
49
49
  // a folded tree; only a fresh grounded re-run mints a gate-satisfying receipt).
50
50
  // PROBE receipts never satisfy either (BRIDGE-MODES-CATALOG, D3): a CODEX_PROBE=1 / AGY_PROBE=1
51
- // review runs with the frontier-model/max-effort guard OFF, so the wrappers stamp `probe:true` and
51
+ // review is the one route to a one-off model or effort, so the wrappers stamp `probe:true` and
52
52
  // this checker drops those receipts — PER RECEIPT, so a normal receipt at the same fingerprint still
53
53
  // satisfies, and a backend whose ONLY current receipts are probes fails with its own stated reason
54
54
  // (never the stale one). Every receipt from a marker-aware wrapper SELF-DECLARES — `probe` is written