@sabaiway/agent-workflow-kit 3.15.0 → 4.0.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 +75 -0
- package/README.md +3 -3
- package/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +12 -8
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +735 -55
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +899 -51
- package/bridges/antigravity-cli-bridge/bin/agy.sh +4 -3
- package/bridges/antigravity-cli-bridge/bin/agy.test.mjs +23 -0
- package/bridges/antigravity-cli-bridge/capability.json +14 -4
- package/bridges/antigravity-cli-bridge/references/driving-agy.md +12 -4
- package/bridges/antigravity-cli-bridge/references/models-and-flags.md +4 -3
- package/bridges/antigravity-cli-bridge/references/review-prompt.md +65 -2
- package/bridges/codex-cli-bridge/SKILL.md +1 -1
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +2 -1
- package/bridges/codex-cli-bridge/bin/codex-review.sh +63 -13
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +38 -0
- package/bridges/codex-cli-bridge/capability.json +1 -1
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/agents/review-lens.md +39 -0
- package/references/hooks/gate-approve.mjs +15 -0
- package/references/modes/agents.md +11 -2
- package/references/modes/autonomy-doctor.md +2 -0
- package/references/modes/backends.md +2 -0
- package/references/modes/bootstrap.md +2 -0
- package/references/modes/bridge-settings.md +4 -1
- package/references/modes/commit-guard.md +2 -0
- package/references/modes/core-evidence.md +2 -0
- package/references/modes/coverage-check.md +2 -0
- package/references/modes/doc-parity.md +2 -0
- package/references/modes/gates.md +2 -0
- package/references/modes/grounding.md +2 -0
- package/references/modes/help.md +2 -0
- package/references/modes/hook.md +4 -1
- package/references/modes/migrate-adr-store.md +2 -0
- package/references/modes/procedures.md +2 -0
- package/references/modes/recipes.md +2 -0
- package/references/modes/recommendations.md +4 -3
- package/references/modes/review-state.md +2 -0
- package/references/modes/sandbox-masks.md +2 -0
- package/references/modes/set-autonomy.md +2 -0
- package/references/modes/set-recipe.md +3 -0
- package/references/modes/setup.md +2 -0
- package/references/modes/state-block-guard.md +2 -0
- package/references/modes/status.md +4 -1
- package/references/modes/uninstall.md +2 -0
- package/references/modes/upgrade.md +2 -0
- package/references/modes/velocity.md +7 -0
- package/references/modes/worktrees.md +2 -0
- package/tools/bridge-settings-read.mjs +40 -10
- package/tools/bridge-settings.mjs +22 -7
- package/tools/cheap-agents.mjs +15 -5
- package/tools/commands.mjs +2 -2
- package/tools/core-evidence.mjs +29 -2
- package/tools/detect-backends.mjs +1 -1
- package/tools/manifest/schema.md +7 -0
- package/tools/manifest/validate.mjs +8 -0
- package/tools/presentation.mjs +1 -1
- package/tools/procedures.mjs +9 -2
- package/tools/recipes.mjs +4 -1
- package/tools/recommendations.mjs +110 -59
- package/tools/renderers.mjs +10 -1
- package/tools/review-state.mjs +4 -0
- package/tools/view-model.mjs +3 -1
|
@@ -41,8 +41,9 @@
|
|
|
41
41
|
# AGY_TIMEOUT default = AGY_HARD_TIMEOUT (agy's soft --print-timeout)
|
|
42
42
|
# AGY_MAX_PROMPT_BYTES default 120000 (single-argv byte ceiling; see agy.sh)
|
|
43
43
|
# AGY_PROBE=1 throwaway probe — silences the off-frontier model advisory
|
|
44
|
-
#
|
|
45
|
-
#
|
|
44
|
+
# AGY_REVIEW_MAX_TOTAL_BYTES default 240000 (the chunked feed's total outgoing prompt-byte ceiling)
|
|
45
|
+
# AGY_REVIEW_ALLOW_ADDDIR RETIRED — recognized so an existing settings line never warns, but it
|
|
46
|
+
# arms nothing; an oversized CODE review is delivered as a chunked feed
|
|
46
47
|
# AW_REVIEW_RECEIPTS override the review-receipt file (default: <git dir>/
|
|
47
48
|
# agent-workflow-review-receipts.jsonl — see the --help Receipt block)
|
|
48
49
|
set -euo pipefail
|
|
@@ -108,14 +109,20 @@ Receipt:
|
|
|
108
109
|
gate rejects a receipt with an absent/invalid posture (a pre-D5 wrapper minted it; re-run the
|
|
109
110
|
review), one stderr banner line states the same posture, an ATTESTING review with AGY_MODEL
|
|
110
111
|
explicitly emptied refuses pre-spend, and a model string carrying control bytes refuses
|
|
111
|
-
pre-spend in every mode;
|
|
112
|
+
pre-spend in every mode; delivery = how the change set REACHED the model, currently emitted as
|
|
113
|
+
'inline' (the whole set rode one prompt — proven by construction) or 'fed' (a chunked feed whose
|
|
114
|
+
per-part echo proof verified); REQUIRED on every agy code receipt and its ABSENCE is what stops a
|
|
115
|
+
pre-fed-lane receipt attesting, while the gate accepts any well-formed declaration rather than a
|
|
116
|
+
particular value; absent by construction on plan/diff/continuation receipts, which carry no change
|
|
117
|
+
set; a run whose output carries NO recognized '### Verdict' section — empty
|
|
112
118
|
output included — exits 4 with NO receipt (D4: a FAILED review to RE-RUN, never a fatal session
|
|
113
119
|
error); a write failure warns, never fails the review
|
|
114
120
|
|
|
115
121
|
Settings file (KEY=VALUE, parsed never sourced; env wins over file, file wins over built-in default):
|
|
116
122
|
${XDG_CONFIG_HOME:-~/.config}/agent-workflow/bridge-settings.conf
|
|
117
123
|
AGY_HARD_TIMEOUT — hard wall-clock cap, duration string like 5m/30m/90s (built-in default 30m)
|
|
118
|
-
AGY_REVIEW_ALLOW_ADDDIR —
|
|
124
|
+
AGY_REVIEW_ALLOW_ADDDIR — RETIRED: still recognized (an existing line never warns as unknown) but it arms NOTHING; a set value prints the retirement notice. The oversized code review is a chunked feed now
|
|
125
|
+
AGY_REVIEW_MAX_TOTAL_BYTES — integer bytes 1..100000000 (default 240000): the ceiling on the SUM of all outgoing prompt bytes an over-cap CODE review's chunked feed may send; past it the fed review refuses before spending turn 1
|
|
119
126
|
|
|
120
127
|
Honesty + posture (D4/D5):
|
|
121
128
|
a run whose output carries NO recognized '### Verdict' section — empty output included — exits 4
|
|
@@ -127,7 +134,7 @@ Honesty + posture (D4/D5):
|
|
|
127
134
|
AGY_PROBE=1 is exempt), and a model string carrying control bytes refuses pre-spend in every
|
|
128
135
|
mode.
|
|
129
136
|
|
|
130
|
-
Closed grammar: unknown flags are rejected; no '--' passthrough (the flag escape is --ungrounded; the env
|
|
137
|
+
Closed grammar: unknown flags are rejected; no '--' passthrough (the flag escape is --ungrounded; the env escape is AGY_PROBE=1).
|
|
131
138
|
Requires at run time: the agy CLI on PATH + a Google AI subscription login (--help needs neither).
|
|
132
139
|
HELP
|
|
133
140
|
exit 0
|
|
@@ -135,7 +142,7 @@ HELP
|
|
|
135
142
|
esac
|
|
136
143
|
|
|
137
144
|
# This wrapper's applied settings-file subset (see the shared reader block below).
|
|
138
|
-
AW_SETTINGS_APPLIED="AGY_HARD_TIMEOUT AGY_REVIEW_ALLOW_ADDDIR"
|
|
145
|
+
AW_SETTINGS_APPLIED="AGY_HARD_TIMEOUT AGY_REVIEW_ALLOW_ADDDIR AGY_REVIEW_MAX_TOTAL_BYTES"
|
|
139
146
|
|
|
140
147
|
# --- Bridge settings file (host-level, kit-independent) — byte-identical across the four wrappers ---
|
|
141
148
|
# ${XDG_CONFIG_HOME:-$HOME/.config}/agent-workflow/bridge-settings.conf holds KEY=VALUE lines,
|
|
@@ -156,7 +163,7 @@ aw_settings_file() {
|
|
|
156
163
|
printf '%s/agent-workflow/bridge-settings.conf' "${XDG_CONFIG_HOME:-$HOME/.config}"
|
|
157
164
|
}
|
|
158
165
|
aw_settings_known() {
|
|
159
|
-
case " CODEX_SERVICE_TIER CODEX_HARD_TIMEOUT CODEX_REVIEW_MAX_TOTAL_BYTES AGY_HARD_TIMEOUT AGY_REVIEW_ALLOW_ADDDIR " in
|
|
166
|
+
case " CODEX_SERVICE_TIER CODEX_HARD_TIMEOUT CODEX_REVIEW_MAX_TOTAL_BYTES AGY_HARD_TIMEOUT AGY_REVIEW_ALLOW_ADDDIR AGY_REVIEW_MAX_TOTAL_BYTES " in
|
|
160
167
|
*" $1 "*) return 0 ;;
|
|
161
168
|
*) return 1 ;;
|
|
162
169
|
esac
|
|
@@ -179,6 +186,7 @@ aw_settings_valid() {
|
|
|
179
186
|
CODEX_REVIEW_MAX_TOTAL_BYTES) [[ "$v" =~ $int_re ]] && aw_int_in_range "$v" 1 100000000 ;;
|
|
180
187
|
AGY_HARD_TIMEOUT) [[ "$v" =~ $dur_re && ! "$v" =~ $zero_re ]] ;;
|
|
181
188
|
AGY_REVIEW_ALLOW_ADDDIR) [[ "$v" == "0" || "$v" == "1" ]] ;;
|
|
189
|
+
AGY_REVIEW_MAX_TOTAL_BYTES) [[ "$v" =~ $int_re ]] && aw_int_in_range "$v" 1 100000000 ;;
|
|
182
190
|
*) return 1 ;;
|
|
183
191
|
esac
|
|
184
192
|
}
|
|
@@ -240,6 +248,11 @@ aw_apply_settings() {
|
|
|
240
248
|
done
|
|
241
249
|
return 0
|
|
242
250
|
}
|
|
251
|
+
# Snapshot BEFORE the shared reader runs: it EXPORTS a settings-file value under the same name, so a
|
|
252
|
+
# check made afterwards cannot tell a file value from an env override — and the retirement notice's
|
|
253
|
+
# recovery differs by source (a file `--unset` cannot clear an env override).
|
|
254
|
+
AGY_ALLOW_ADDDIR_FROM_ENV=0
|
|
255
|
+
if [[ -n "${AGY_REVIEW_ALLOW_ADDDIR+x}" ]]; then AGY_ALLOW_ADDDIR_FROM_ENV=1; fi
|
|
243
256
|
aw_apply_settings
|
|
244
257
|
|
|
245
258
|
# --- Effective-timeout resolver (D5 banner honesty; AD-061) --------------------
|
|
@@ -296,7 +309,7 @@ DEFAULT_AGY_REVIEW_MODEL="Gemini 3.1 Pro (High)"
|
|
|
296
309
|
# Review-receipt identity (AD-038). AW_BRIDGE_VERSION mirrors this bridge's SKILL.md/capability.json
|
|
297
310
|
# version (drift-guarded by agy-review.test.mjs against capability.json).
|
|
298
311
|
AW_RECEIPT_BACKEND="agy"
|
|
299
|
-
AW_BRIDGE_VERSION="
|
|
312
|
+
AW_BRIDGE_VERSION="5.0.0"
|
|
300
313
|
# `-` not `:-` so an EXPLICIT empty AGY_MODEL= survives (drop --model, use settings.json — agy.sh:52).
|
|
301
314
|
AGY_MODEL="${AGY_MODEL-$DEFAULT_AGY_REVIEW_MODEL}"
|
|
302
315
|
# D5 control-byte screen — IMMEDIATELY after resolution, BEFORE the off-frontier advisory (or any
|
|
@@ -330,7 +343,33 @@ AGY_PROBE="${AGY_PROBE:-0}"
|
|
|
330
343
|
# review-state gate rejects it — a guards-relaxed review must never attest a tree.
|
|
331
344
|
REVIEW_PROBE=false
|
|
332
345
|
if [[ "$AGY_PROBE" == "1" ]]; then REVIEW_PROBE=true; fi
|
|
346
|
+
# RETIRED (D3), not removed: the key stays RECOGNIZED by the shared settings registry so an existing
|
|
347
|
+
# settings line never starts warning as unknown — but it arms NOTHING. The `--add-dir` offload told
|
|
348
|
+
# agy to read a staging file; headless agy AUTO-DENIES its own read_file tool, so that lane could
|
|
349
|
+
# return a confident fabrication or an empty SHIP with no way to tell. Setting it says so, loudly.
|
|
350
|
+
# The notice keys on the key being CONFIGURED AT ALL, not on its value: a stale `=0` line is just as
|
|
351
|
+
# dead as a `=1` one, and staying silent about it would leave the operator believing a knob they can
|
|
352
|
+
# see still means something. The recovery is source-specific — a file `--unset` cannot clear an env
|
|
353
|
+
# override, so telling an env user to run it would hand them a command that changes nothing.
|
|
354
|
+
AGY_REVIEW_ALLOW_ADDDIR_SOURCE=""
|
|
355
|
+
if (( AGY_ALLOW_ADDDIR_FROM_ENV == 1 )); then AGY_REVIEW_ALLOW_ADDDIR_SOURCE="env"; fi
|
|
333
356
|
AGY_REVIEW_ALLOW_ADDDIR="${AGY_REVIEW_ALLOW_ADDDIR:-0}"
|
|
357
|
+
if [[ -z "$AGY_REVIEW_ALLOW_ADDDIR_SOURCE" ]] && [[ -f "$(aw_settings_file)" ]] && grep -q "^AGY_REVIEW_ALLOW_ADDDIR=" "$(aw_settings_file)"; then
|
|
358
|
+
AGY_REVIEW_ALLOW_ADDDIR_SOURCE="file"
|
|
359
|
+
fi
|
|
360
|
+
if [[ -n "$AGY_REVIEW_ALLOW_ADDDIR_SOURCE" ]]; then
|
|
361
|
+
echo "notice: AGY_REVIEW_ALLOW_ADDDIR is set (${AGY_REVIEW_ALLOW_ADDDIR_SOURCE}) but it is RETIRED and arms nothing. An" >&2
|
|
362
|
+
echo " oversized 'agy-review code' is now DELIVERED as a chunked feed with a per-part delivery" >&2
|
|
363
|
+
echo " proof, so the change set never has to be read from disk — which headless agy cannot do" >&2
|
|
364
|
+
echo " (read_file is auto-denied)." >&2
|
|
365
|
+
if [[ "$AGY_REVIEW_ALLOW_ADDDIR_SOURCE" == "env" ]]; then
|
|
366
|
+
echo " Clear it where you set it: unset AGY_REVIEW_ALLOW_ADDDIR in the environment (a settings-file" >&2
|
|
367
|
+
echo " --unset cannot clear an env override)." >&2
|
|
368
|
+
else
|
|
369
|
+
echo " Clear the line: node <agent-workflow-kit>/tools/bridge-settings.mjs --unset" >&2
|
|
370
|
+
echo " AGY_REVIEW_ALLOW_ADDDIR --apply" >&2
|
|
371
|
+
fi
|
|
372
|
+
fi
|
|
334
373
|
AGY_MAX_PROMPT_BYTES="${AGY_MAX_PROMPT_BYTES:-120000}"
|
|
335
374
|
if [[ ! "$AGY_MAX_PROMPT_BYTES" =~ ^[0-9]+$ ]]; then
|
|
336
375
|
echo "error: AGY_MAX_PROMPT_BYTES='$AGY_MAX_PROMPT_BYTES' is not a non-negative integer." >&2
|
|
@@ -344,6 +383,51 @@ if (( AGY_MAX_PROMPT_BYTES > AGY_ARGV_HARD_MAX )); then
|
|
|
344
383
|
echo " The override may LOWER the ceiling (stricter), never raise it past the OS limit." >&2
|
|
345
384
|
exit 2
|
|
346
385
|
fi
|
|
386
|
+
# The repo file map's share of the prompt (see emit_repo_file_map). A PINNED constant, not a knob:
|
|
387
|
+
# a plain assignment, so an inherited environment value can never widen or narrow it. 8192 = 6.8%
|
|
388
|
+
# of agy's 120000-byte default ceiling — the map is context, the change set is the review.
|
|
389
|
+
AW_REVIEW_MAP_BUDGET_BYTES=8192
|
|
390
|
+
|
|
391
|
+
# --- The chunked-feed ceiling (D1b) -------------------------------------------
|
|
392
|
+
# "The whole conversation" is not knowable before turn 1 (model response sizes are unknown), so the
|
|
393
|
+
# bound is the SUM OF ALL OUTGOING PROMPT BYTES — every envelope, every body, and the final turn's
|
|
394
|
+
# prompt — which IS computable up front. Set deliberately BELOW the 262,967 bytes a live probe
|
|
395
|
+
# proved retained, because that probe measured part BODIES only and the shipped lane adds envelopes
|
|
396
|
+
# and a final prompt on top. This is an ECONOMY guard, not the correctness guard: correctness is the
|
|
397
|
+
# per-part body echo, which fails closed under this ceiling too.
|
|
398
|
+
AGY_REVIEW_MAX_TOTAL_BYTES="${AGY_REVIEW_MAX_TOTAL_BYTES:-240000}"
|
|
399
|
+
# CANONICALIZE BEFORE ANY ARITHMETIC. A hand-rolled `(( value < 1 ))` on `008000` makes bash read an
|
|
400
|
+
# invalid OCTAL constant: the arithmetic errors, both range tests yield false, and the ceiling simply
|
|
401
|
+
# stops existing — the guard silently absent, which is worse than no guard. Leading zeros are
|
|
402
|
+
# stripped exactly as the shared settings reader does, then the value is judged by the wrapper's OWN
|
|
403
|
+
# registry rule (aw_int_in_range is the overflow-safe comparator the manifest bounds are pinned to),
|
|
404
|
+
# never by a second hand-written bound that could drift from the manifest.
|
|
405
|
+
if [[ "$AGY_REVIEW_MAX_TOTAL_BYTES" =~ ^[0-9]+$ ]]; then
|
|
406
|
+
AGY_REVIEW_MAX_TOTAL_BYTES="${AGY_REVIEW_MAX_TOTAL_BYTES#"${AGY_REVIEW_MAX_TOTAL_BYTES%%[!0]*}"}"
|
|
407
|
+
AGY_REVIEW_MAX_TOTAL_BYTES="${AGY_REVIEW_MAX_TOTAL_BYTES:-0}"
|
|
408
|
+
fi
|
|
409
|
+
if ! aw_settings_valid AGY_REVIEW_MAX_TOTAL_BYTES "$AGY_REVIEW_MAX_TOTAL_BYTES"; then
|
|
410
|
+
# Only an EXPLICIT env override can reach here: the shared settings reader already drops an invalid
|
|
411
|
+
# FILE value before the wrapper body runs, leaving the built-in default, which is valid. So this is
|
|
412
|
+
# unconditionally a refusal — a warn-and-default arm would be dead code pretending to be a policy.
|
|
413
|
+
echo "error: AGY_REVIEW_MAX_TOTAL_BYTES='$AGY_REVIEW_MAX_TOTAL_BYTES' is not a valid byte ceiling (integer 1..100000000)." >&2
|
|
414
|
+
echo " Refusing rather than running with a spending ceiling you set and this wrapper ignored." >&2
|
|
415
|
+
exit 2
|
|
416
|
+
fi
|
|
417
|
+
# The echo-candidate window (D1): long enough that a line is distinctive and cannot be guessed,
|
|
418
|
+
# short enough that a model can reproduce it verbatim without truncating.
|
|
419
|
+
# Mirrors agy.sh's `timeout --kill-after=10s`. A turn that ignores TERM survives this long past its
|
|
420
|
+
# own cap, so the fed loop reserves it out of every turn's budget rather than letting the last turn
|
|
421
|
+
# spend it past the shared deadline. Keep in lockstep with agy.sh.
|
|
422
|
+
AGY_TURN_KILL_GRACE_S=10
|
|
423
|
+
FED_ECHO_MIN_BYTES=24
|
|
424
|
+
FED_ECHO_MAX_BYTES=200
|
|
425
|
+
# The largest proof ADDRESS number the parser will convert. It exists because the numbers come from
|
|
426
|
+
# the model: past this, awk's `%d` conversion is no longer faithful, and a silently-wrapped value
|
|
427
|
+
# could land back inside the requested range and impersonate a real address. An entry naming a
|
|
428
|
+
# bigger number is reported as address 0 — a value no request can ever carry — so it reads as an
|
|
429
|
+
# address nobody requested and FAILS the proof, rather than vanishing from the check entirely.
|
|
430
|
+
FED_PROOF_ADDRESS_MAX=1000000000
|
|
347
431
|
|
|
348
432
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
349
433
|
|
|
@@ -391,9 +475,8 @@ here and a known source of false positives. Review ONLY the engineering of the m
|
|
|
391
475
|
AGAINST the grounded facts. If something contradicts your training, trust the facts, not your memory.
|
|
392
476
|
GUARD
|
|
393
477
|
}
|
|
394
|
-
|
|
478
|
+
emit_shape_sections() {
|
|
395
479
|
cat <<'SHAPE'
|
|
396
|
-
## Output — Markdown, this exact shape, nothing else
|
|
397
480
|
### Verdict
|
|
398
481
|
One line: SHIP / SHIP WITH NITS / REWORK, plus a one-sentence reason.
|
|
399
482
|
### Blocking
|
|
@@ -404,6 +487,58 @@ Numbered. Simplifications, reuse, naming, missing tests. Cite file:line. Empty?
|
|
|
404
487
|
Anything ambiguous that would change your verdict if answered.
|
|
405
488
|
SHAPE
|
|
406
489
|
}
|
|
490
|
+
emit_shape() {
|
|
491
|
+
echo "## Output — Markdown, this exact shape, nothing else"
|
|
492
|
+
emit_shape_sections
|
|
493
|
+
}
|
|
494
|
+
# The FED shape (D1): '### Delivery proof' is the FIRST section, so output truncation cannot
|
|
495
|
+
# silently drop the proof. The requested lines are named by ADDRESS only — revealing the text would
|
|
496
|
+
# let a model that never held the body copy it back, which is exactly what the proof exists to catch.
|
|
497
|
+
emit_shape_fed() { # $1 = the requested addresses, "part K line L; part K line L"
|
|
498
|
+
echo "## Output — Markdown, this exact shape, nothing else"
|
|
499
|
+
cat <<'PROOF'
|
|
500
|
+
### Delivery proof
|
|
501
|
+
This section comes FIRST, before ### Verdict. For each requested address below print exactly one
|
|
502
|
+
line, in this form and nothing else:
|
|
503
|
+
part <K> line <L>: <the text of line L of part K, VERBATIM>
|
|
504
|
+
Count lines 1-based from the FIRST line after that part's BEGIN marker. Print the raw line text
|
|
505
|
+
after the colon — no code fences, no backticks, no quotes, no commentary, no ellipsis, no
|
|
506
|
+
re-indenting. If you cannot reproduce a line exactly, say so on that line rather than guessing.
|
|
507
|
+
PROOF
|
|
508
|
+
# ONE ADDRESS PER LINE, and every address line is shorter than FED_ECHO_MIN_BYTES. That is not
|
|
509
|
+
# cosmetic: it makes a collision CONSTRUCTIVELY impossible. A proof candidate is a single line of
|
|
510
|
+
# at least FED_ECHO_MIN_BYTES, and a single line can never match across a newline, so no candidate
|
|
511
|
+
# can occur inside this list — which is what lets the selector pick in ONE pass instead of
|
|
512
|
+
# re-picking against a request that does not exist yet.
|
|
513
|
+
printf 'Requested addresses, one per line:\n%s\n' "$1"
|
|
514
|
+
emit_shape_sections
|
|
515
|
+
}
|
|
516
|
+
# One feed turn's ENVELOPE (D1a). Nothing here ever enters the reviewed artifact: only the BODY
|
|
517
|
+
# between the BEGIN/END markers concatenates, and it concatenates byte-for-byte.
|
|
518
|
+
emit_feed_frame() { # $1 = part index, $2 = part count
|
|
519
|
+
printf '## Chunked delivery — part %s of %s\n' "$1" "$2"
|
|
520
|
+
cat <<'FEED'
|
|
521
|
+
This is one piece of ONE change set being delivered to you in order.
|
|
522
|
+
Reply with exactly OK and NOTHING else: do NOT review yet, do NOT summarise, do NOT comment, do NOT
|
|
523
|
+
ask questions. The review request arrives after the last part. The text between the BEGIN and END
|
|
524
|
+
markers below is the change set VERBATIM — the markers themselves are delivery framing and are not
|
|
525
|
+
part of it.
|
|
526
|
+
Work from THIS CONVERSATION only: do NOT use any tool, do NOT run any command, do NOT read any file.
|
|
527
|
+
Everything you need is already in this conversation, and on this host EVERY tool is auto-denied — a
|
|
528
|
+
denied tool produces no output at all and loses the whole answer.
|
|
529
|
+
FEED
|
|
530
|
+
}
|
|
531
|
+
emit_fed_final_head() {
|
|
532
|
+
cat <<'FINAL'
|
|
533
|
+
## The change set is fully delivered
|
|
534
|
+
Every part above belongs to ONE change set, and it is now complete in this conversation. Review the
|
|
535
|
+
WHOLE change set against the grounded facts from the first message, under the same read-only posture.
|
|
536
|
+
Work from THIS CONVERSATION only: do NOT use any tool, do NOT run any command, do NOT read any file,
|
|
537
|
+
do NOT try to count lines with a tool — count them by reading. Everything you need is already in this
|
|
538
|
+
conversation, and on this host EVERY tool is auto-denied: a denied tool produces no output at all, so
|
|
539
|
+
reaching for one loses your entire review. Observed live on this lane's first real run.
|
|
540
|
+
FINAL
|
|
541
|
+
}
|
|
407
542
|
emit_resume_reminder() {
|
|
408
543
|
cat <<'REMINDER'
|
|
409
544
|
CONTINUE the review you already started in THIS conversation, under the SAME read-only posture: do
|
|
@@ -565,7 +700,7 @@ posture_json() {
|
|
|
565
700
|
# itself instead of inferring it from this wrapper's version (which bumps in a different release
|
|
566
701
|
# phase). Silence is not a declaration — an unmarked receipt is untrustworthy and the gate rejects it.
|
|
567
702
|
write_review_receipt() {
|
|
568
|
-
local artifact="$1" fresh="$2" fingerprint="$3" verdict="$4" grounded="$5" facts_hash="$6" probe="${7:-false}"
|
|
703
|
+
local artifact="$1" fresh="$2" fingerprint="$3" verdict="$4" grounded="$5" facts_hash="$6" probe="${7:-false}" delivery="${8:-}"
|
|
569
704
|
local receipts="${AW_REVIEW_RECEIPTS:-}"
|
|
570
705
|
if [[ -z "$receipts" ]]; then
|
|
571
706
|
local receipt_git_dir
|
|
@@ -575,12 +710,13 @@ write_review_receipt() {
|
|
|
575
710
|
fi
|
|
576
711
|
receipts="$receipt_git_dir/agent-workflow-review-receipts.jsonl"
|
|
577
712
|
fi
|
|
578
|
-
local line probe_field=',"probe":false'
|
|
713
|
+
local line probe_field=',"probe":false' delivery_field=""
|
|
579
714
|
if [[ "$probe" == "true" ]]; then probe_field=',"probe":true'; fi
|
|
580
|
-
|
|
715
|
+
if [[ -n "$delivery" ]]; then delivery_field=",\"delivery\":\"$delivery\""; fi
|
|
716
|
+
line="$(printf '{"schema":1,"artifact":%s,"fresh":%s,"fingerprint":%s,"backend":"%s","verdict":"%s","grounded":%s,"factsHash":%s,"wrapperVersion":"%s","timestamp":"%s"%s,"posture":%s%s}' \
|
|
581
717
|
"$(receipt_json_scalar "$artifact")" "$fresh" "$(receipt_json_scalar "$fingerprint")" \
|
|
582
718
|
"$AW_RECEIPT_BACKEND" "$verdict" "$grounded" "$(receipt_json_scalar "$facts_hash")" \
|
|
583
|
-
"$AW_BRIDGE_VERSION" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$probe_field" "$(posture_json)")"
|
|
719
|
+
"$AW_BRIDGE_VERSION" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$probe_field" "$(posture_json)" "$delivery_field")"
|
|
584
720
|
if ! printf '%s\n' "$line" >>"$receipts" 2>/dev/null; then
|
|
585
721
|
echo "warning: could not append the review receipt to $receipts — the review itself succeeded;" >&2
|
|
586
722
|
echo " the review-state gate will read the current tree as un-receipted." >&2
|
|
@@ -602,15 +738,380 @@ parse_agy_verdict() { # $1 = captured-output file
|
|
|
602
738
|
fi
|
|
603
739
|
}
|
|
604
740
|
|
|
605
|
-
#
|
|
606
|
-
#
|
|
607
|
-
#
|
|
608
|
-
#
|
|
609
|
-
#
|
|
610
|
-
#
|
|
741
|
+
# The repo file map is a FIXED cost that scales with REPO SIZE, not change size (measured 28,735
|
|
742
|
+
# bytes in the home repo — 24% of agy's 120000-byte single-argv ceiling), so an unbounded map taxes
|
|
743
|
+
# the change budget of every review. AW_REVIEW_MAP_BUDGET_BYTES bounds it; the value is set by each
|
|
744
|
+
# wrapper so THIS BODY stays byte-identical across both (review-fingerprint-parity.test.mjs
|
|
745
|
+
# lockstep). Unset/0 = unbounded. Past the budget the map degrades to the CHANGED-path subset — cut
|
|
746
|
+
# at the SAME budget, so a change touching very many long paths cannot re-breach the bound — plus a
|
|
747
|
+
# stated omitted count; a truncation-with-count, never a silent cut. The map was never part of the
|
|
748
|
+
# fingerprint domain (emit_fingerprint_payload does not contain it), so bounding it moves no receipt.
|
|
749
|
+
emit_repo_file_map() {
|
|
750
|
+
local budget="${AW_REVIEW_MAP_BUDGET_BYTES:-0}" total shown omitted subset tracked
|
|
751
|
+
# ONE deduplicated tracked-path SNAPSHOT, captured once and reused for all four uses — the
|
|
752
|
+
# byte-budget predicate, the printed map, `total`, and the index intersection. Re-running the query
|
|
753
|
+
# per use let a concurrent index change make the budget decision, the map and the counts describe
|
|
754
|
+
# DIFFERENT snapshots. Dedupe: an UNMERGED index lists a path once per stage, so a predicate
|
|
755
|
+
# counting duplicates against a map printing unique paths would push a map that fits into the
|
|
756
|
+
# truncated arm. `ls-files` output is sorted by path, so one path's stages are adjacent and `uniq`
|
|
757
|
+
# is exact — no git version floor needed.
|
|
758
|
+
tracked="$(git ls-files | LC_ALL=C uniq)"
|
|
759
|
+
if (( budget <= 0 )) || (( $(printf '%s\n' "$tracked" | wc -c) <= budget )); then
|
|
760
|
+
if [[ -n "$tracked" ]]; then printf '%s\n' "$tracked"; fi
|
|
761
|
+
return 0
|
|
762
|
+
fi
|
|
763
|
+
total=$(( $(printf '%s\n' "$tracked" | wc -l) ))
|
|
764
|
+
# The subset is INTERSECTED with the index before budgeting, so `shown` and `total` live in ONE
|
|
765
|
+
# domain and `shown + omitted == total` holds exactly: a STAGED DELETION is a changed path that is
|
|
766
|
+
# no longer in `git ls-files`, and counting it as shown would make the stated arithmetic a lie.
|
|
767
|
+
# (The NR==FNR reader is safe here: an empty index returns through the in-budget arm above.)
|
|
768
|
+
# awk never `exit`s early: a closed pipe would SIGPIPE `sort` and pipefail would abort the run.
|
|
769
|
+
subset="$(LC_ALL=C awk 'NR == FNR { known[$0] = 1; next } ($0 in known)' <(printf '%s\n' "$tracked") <(git diff --name-only --no-ext-diff; git diff --cached --name-only --no-ext-diff) |
|
|
770
|
+
LC_ALL=C sort -u |
|
|
771
|
+
LC_ALL=C awk -v cap="$budget" '{ n = length($0) + 1; if (!over && used + n <= cap) { used += n; print } else over = 1 }')"
|
|
772
|
+
shown=0
|
|
773
|
+
if [[ -n "$subset" ]]; then
|
|
774
|
+
printf '%s\n' "$subset"
|
|
775
|
+
shown=$(( $(printf '%s\n' "$subset" | wc -l) ))
|
|
776
|
+
fi
|
|
777
|
+
omitted=$(( total - shown ))
|
|
778
|
+
if (( omitted < 0 )); then omitted=0; fi
|
|
779
|
+
printf '=== repo file map TRUNCATED to the changed-path subset: %s of %s tracked paths shown, %s omitted (map budget %s bytes) ===\n' \
|
|
780
|
+
"$shown" "$total" "$omitted" "$budget"
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
# ── the chunked feed: partition, proof selection, turn targeting (Phase 3) ─────────────────────
|
|
784
|
+
# Delivery framing. `printf --` because the format itself begins with a dash.
|
|
785
|
+
FED_BEGIN_FMT='--- BEGIN CHANGE-SET PART %s OF %s ---\n'
|
|
786
|
+
FED_END_FMT='\n--- END CHANGE-SET PART %s OF %s ---\n'
|
|
787
|
+
FED_MODE=0
|
|
788
|
+
FED_PART_COUNT=0
|
|
789
|
+
FED_REQUESTED=""
|
|
790
|
+
declare -a FED_ECHO_LINE=()
|
|
791
|
+
declare -a FED_ECHO_TEXT=()
|
|
792
|
+
|
|
793
|
+
# Byte-exact slice of a file. pipefail is disabled INSIDE the subshell only: `head -c` closes the
|
|
794
|
+
# pipe early, which SIGPIPEs `tail` — under pipefail that would abort the whole run.
|
|
795
|
+
slice_bytes() { # $1 = file, $2 = 0-based byte offset, $3 = length
|
|
796
|
+
( set +o pipefail; tail -c "+$(( $2 + 1 ))" "$1" | head -c "$3" )
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
# Part LENGTHS (bytes, one per line) cutting a file at LINE boundaries: part 1 up to $2, later parts
|
|
800
|
+
# up to $3. Every cut lands on a line boundary, so a split multi-byte code point is impossible — there
|
|
801
|
+
# is NO hard-cut fallback: a line that cannot fit an EMPTY active part makes the whole run refuse
|
|
802
|
+
# (status 1) BEFORE any turn is spent, because a part built from fragments of one giant line carries
|
|
803
|
+
# no line to prove delivery with anyway.
|
|
804
|
+
#
|
|
805
|
+
# Precisely what "does not fit an empty active part" means, because the loose version of this
|
|
806
|
+
# sentence was a review finding: when the current part already holds content, the flush below moves
|
|
807
|
+
# the long line into a later, LARGER part, and only a line longer than that later budget refuses.
|
|
808
|
+
# When the active part is still empty the refusal is immediate against the CURRENT cap — for part 1
|
|
809
|
+
# that is the smaller budget, since turn 1 also carries the grounding and a body-less turn would be
|
|
810
|
+
# a turn spent on nothing. In practice part 1 always accumulates the assembled header first, so the
|
|
811
|
+
# reachable refusal is a line longer than the LATER-part budget.
|
|
812
|
+
#
|
|
813
|
+
# The final record contributes no separator byte when the artifact does not end with a newline —
|
|
814
|
+
# inventing one would split a part that actually fits and cost an entire extra turn.
|
|
815
|
+
plan_fed_parts() { # $1 = file, $2 = first-part budget, $3 = later-part budget, $4 = line count, $5 = 1 if the file ends with a newline
|
|
816
|
+
LC_ALL=C awk -v b1="$2" -v bk="$3" -v last="$4" -v final_nl="$5" '
|
|
817
|
+
function flush() { if (used > 0) { print used; used = 0; cap = bk } }
|
|
818
|
+
BEGIN { cap = b1; used = 0 }
|
|
819
|
+
{
|
|
820
|
+
n = length($0) + ((NR == last && final_nl == 0) ? 0 : 1)
|
|
821
|
+
if (used + n > cap) {
|
|
822
|
+
flush()
|
|
823
|
+
if (n > cap) exit 1
|
|
824
|
+
}
|
|
825
|
+
used += n
|
|
826
|
+
}
|
|
827
|
+
END { flush() }
|
|
828
|
+
' "$1"
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
# ORDERED delivery-proof candidates for ONE part (D1), nearest that part's middle first: an INTERIOR
|
|
832
|
+
# line whose trimmed text sits in the echo window and is unique as a WHOLE line across the change
|
|
833
|
+
# set. This is only the cheap prefilter — the exact fixed-string checks (exactly one occurrence
|
|
834
|
+
# across the bodies, ZERO anywhere the wrapper's own envelope text can reveal it) run in bash over
|
|
835
|
+
# this list, because a candidate that is merely a unique LINE can still occur as a SUBSTRING of
|
|
836
|
+
# another body's line, and a model without that part could then copy it.
|
|
837
|
+
# The order is produced by WALKING OUTWARD from the part's middle, never by sorting: a comparison
|
|
838
|
+
# sort here was quadratic, and the caller may walk the whole list, so the cost of merely ORDERING
|
|
839
|
+
# candidates must not depend on how many there are. There is no cap — capping made the wrapper claim
|
|
840
|
+
# "no usable candidate" while a usable one sat at position 26.
|
|
841
|
+
list_echo_candidates() { # $1 = part file, $2 = unique-trimmed-lines file → "<line number><TAB><text>"
|
|
842
|
+
LC_ALL=C awk -v minlen="$FED_ECHO_MIN_BYTES" -v maxlen="$FED_ECHO_MAX_BYTES" '
|
|
843
|
+
NR == FNR { uniq[$0] = 1; next }
|
|
844
|
+
{ lines[FNR] = $0 }
|
|
845
|
+
function usable(i, t) {
|
|
846
|
+
if (i < 2 || i >= FNR) return 0
|
|
847
|
+
t = lines[i]
|
|
848
|
+
gsub(/^[ \t]+|[ \t]+$/, "", t)
|
|
849
|
+
if (length(t) < minlen || length(lines[i]) > maxlen) return 0
|
|
850
|
+
return (t in uniq)
|
|
851
|
+
}
|
|
852
|
+
END {
|
|
853
|
+
mid = int((FNR + 1) / 2)
|
|
854
|
+
if (usable(mid)) printf "%s\t%s\n", mid, lines[mid]
|
|
855
|
+
for (d = 1; d <= FNR; d++) {
|
|
856
|
+
if (usable(mid - d)) printf "%s\t%s\n", mid - d, lines[mid - d]
|
|
857
|
+
if (usable(mid + d)) printf "%s\t%s\n", mid + d, lines[mid + d]
|
|
858
|
+
}
|
|
859
|
+
}' "$2" "$1"
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
# Exact FIXED-STRING occurrence count of $1 in file $2. `|| true`: grep exits 1 on no match, which
|
|
863
|
+
# pipefail would otherwise turn into an aborted run.
|
|
864
|
+
fixed_occurrences() {
|
|
865
|
+
local n
|
|
866
|
+
n="$( { LC_ALL=C grep -o -F -- "$1" "$2" || true; } | wc -l )"
|
|
867
|
+
printf '%s' "$(( n ))"
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
# The conversation id agy writes into its own run log (D9, Arm A). The format is agy's own to change,
|
|
871
|
+
# which is exactly why an unparseable log DEGRADES LOUDLY to --continue instead of failing: the
|
|
872
|
+
# correctness guarantee is the D1 echo proof, which fails closed if the wrong conversation answers.
|
|
873
|
+
capture_conversation_id() { # $1 = run log → the id, or empty
|
|
874
|
+
local id
|
|
875
|
+
[[ -f "$1" ]] || { printf ''; return 0; }
|
|
876
|
+
id="$(LC_ALL=C awk '/onversation/ && match($0, /[0-9a-fA-F]+-[0-9a-fA-F]+-[0-9a-fA-F]+-[0-9a-fA-F]+-[0-9a-fA-F]+/) { print substr($0, RSTART, RLENGTH); exit }' "$1")"
|
|
877
|
+
if [[ "$id" =~ ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$ ]]; then
|
|
878
|
+
printf '%s' "$id"
|
|
879
|
+
else
|
|
880
|
+
printf ''
|
|
881
|
+
fi
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
# A validated duration string → integer seconds (floor 1). The fed lane needs arithmetic on the cap
|
|
885
|
+
# because ONE wall-clock budget has to cover N+1 turns; the banner keeps printing the duration
|
|
886
|
+
# verbatim, so this conversion never changes what the user is told about the cap itself.
|
|
887
|
+
aw_duration_seconds() { # $1 = duration string matching the settings grammar
|
|
888
|
+
LC_ALL=C awk -v d="$1" 'BEGIN {
|
|
889
|
+
u = substr(d, length(d), 1)
|
|
890
|
+
n = substr(d, 1, length(d) - 1) + 0
|
|
891
|
+
m = (u == "s") ? 1 : (u == "m") ? 60 : (u == "h") ? 3600 : 86400
|
|
892
|
+
s = int(n * m)
|
|
893
|
+
if (s < 1) s = 1
|
|
894
|
+
print s
|
|
895
|
+
}'
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
trim_ws() {
|
|
899
|
+
local s="$1"
|
|
900
|
+
s="${s#"${s%%[![:space:]]*}"}"
|
|
901
|
+
s="${s%"${s##*[![:space:]]}"}"
|
|
902
|
+
printf '%s' "$s"
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
# A final turn that produced NO mandated section at all did not FAIL DELIVERY — it produced no
|
|
906
|
+
# review. Blaming delivery sends the reader hunting the wrong bug. What the wrapper can HONESTLY say
|
|
907
|
+
# is that it SENT every part and every feed turn exited zero; whether the model retained them is
|
|
908
|
+
# exactly what the unanswered proof leaves unknown, so it is never claimed.
|
|
909
|
+
fed_output_is_answerless() { # $1 = captured final-turn output
|
|
910
|
+
! LC_ALL=C grep -qi -e '^###[[:space:]]*Delivery proof' -e '^###[[:space:]]*Verdict' "$1"
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
# agy's OWN refusal line when headless mode auto-denies a tool it chose to call. Recognizing it lets
|
|
914
|
+
# the wrapper name the real cause; anything else is reported as an UNKNOWN cause rather than guessed.
|
|
915
|
+
fed_output_names_tool_denial() { # $1 = captured final-turn output
|
|
916
|
+
LC_ALL=C grep -q 'no output produced.*permission that headless mode cannot prompt for' "$1"
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
# D1/D7: every part's selected line must come back VERBATIM, or the review FAILED — exit 4, no
|
|
920
|
+
# receipt, and a message naming the cause. Never a downgraded verdict, never a warning beside a kept
|
|
921
|
+
# receipt. The comparison tolerates surrounding whitespace only, never content.
|
|
922
|
+
# The proof section is the FIRST '### Delivery proof' block only, ending at the next '###' heading.
|
|
923
|
+
# Anything outside it is prose the model happens to have written and can never satisfy the proof.
|
|
924
|
+
# The FIRST '###' heading of the answer must BE the proof heading — that is the whole point of
|
|
925
|
+
# placing the proof first: output truncation can then never silently drop it. A proof block sitting
|
|
926
|
+
# after '### Verdict' is not the mandated shape and is not searched for.
|
|
927
|
+
fed_proof_section() { # $1 = captured output
|
|
928
|
+
LC_ALL=C awk '
|
|
929
|
+
!seen && /^###/ {
|
|
930
|
+
if (tolower($0) ~ /^###[ \t]*delivery proof[ \t]*$/) { seen = 1; inside = 1; next }
|
|
931
|
+
exit
|
|
932
|
+
}
|
|
933
|
+
inside && /^###/ { exit }
|
|
934
|
+
inside { print }
|
|
935
|
+
' "$1"
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
# One recognized echo per output line: "K<TAB>L<TAB>payload". The anchor is STRICT — optional leading
|
|
939
|
+
# whitespace and at most ONE list marker that must itself be followed by whitespace, then the address
|
|
940
|
+
# at the START of the line. A marker buried mid-sentence is prose, not an echo.
|
|
941
|
+
#
|
|
942
|
+
# Two properties are load-bearing. (1) The addresses are NORMALIZED here, at the parse boundary:
|
|
943
|
+
# `%d` over `k + 0` hands bash a canonical decimal, because a model-written `part 08` would otherwise
|
|
944
|
+
# reach bash arithmetic and an array index as OCTAL and abort the wrapper with `value too great for
|
|
945
|
+
# base` instead of the contracted clean refusal — and a safely padded `01` would mismatch `1`. An
|
|
946
|
+
# out-of-range number is REPORTED as address 0 — never dropped, because a dropped address is an
|
|
947
|
+
# INVISIBLE one, and an answer carrying every correct echo plus one invented giant address would
|
|
948
|
+
# otherwise satisfy a grammar whose whole point is that it is closed. (2) The anchor is
|
|
949
|
+
# matched case-INSENSITIVELY on a lowered copy while the PAYLOAD is sliced from the ORIGINAL line:
|
|
950
|
+
# tolerating `Part 1 Line 5:` removes a false refusal, but the echoed text must keep its own case or
|
|
951
|
+
# the verbatim comparison would be checking something the model never wrote.
|
|
952
|
+
fed_proof_entries() { # reads the section on stdin
|
|
953
|
+
LC_ALL=C awk -v addrmax="$FED_PROOF_ADDRESS_MAX" '
|
|
954
|
+
{
|
|
955
|
+
line = $0
|
|
956
|
+
sub(/^[ \t]+/, "", line)
|
|
957
|
+
sub(/^[-*][ \t]+/, "", line)
|
|
958
|
+
probe = tolower(line)
|
|
959
|
+
if (match(probe, /^part[ \t]+[0-9]+[ \t]+line[ \t]+[0-9]+:/)) {
|
|
960
|
+
head = substr(probe, 1, RLENGTH)
|
|
961
|
+
rest = substr(line, RLENGTH + 1)
|
|
962
|
+
k = head; sub(/^part[ \t]+/, "", k); sub(/[ \t]+line.*$/, "", k)
|
|
963
|
+
l = head; sub(/^.*line[ \t]+/, "", l); sub(/:$/, "", l)
|
|
964
|
+
# An out-of-range address is REPORTED as address 0, never dropped: dropping it made a huge
|
|
965
|
+
# bogus address INVISIBLE, so an answer carrying every correct echo PLUS one invented
|
|
966
|
+
# `part 99999999999999999999 line …` satisfied the closed grammar and minted a receipt.
|
|
967
|
+
if (k + 0 > addrmax || l + 0 > addrmax) { printf "0\t0\t%s\n", rest; next }
|
|
968
|
+
printf "%d\t%d\t%s\n", k + 0, l + 0, rest
|
|
969
|
+
}
|
|
970
|
+
}'
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
verify_delivery_proof() { # $1 = captured final-turn output
|
|
974
|
+
local k l payload want got section entries
|
|
975
|
+
local -a seen_count=() seen_payload=()
|
|
976
|
+
section="$(fed_proof_section "$1")"
|
|
977
|
+
entries="$(printf '%s\n' "$section" | fed_proof_entries)"
|
|
978
|
+
for (( k = 1; k <= FED_PART_COUNT; k++ )); do
|
|
979
|
+
seen_count[k]=0
|
|
980
|
+
seen_payload[k]=""
|
|
981
|
+
done
|
|
982
|
+
if [[ -n "$entries" ]]; then
|
|
983
|
+
while IFS=$'\t' read -r k l payload; do
|
|
984
|
+
if (( k == 0 )); then
|
|
985
|
+
echo "error: the review did NOT prove delivery — its proof section echoes an address whose numbers are" >&2
|
|
986
|
+
echo " out of range (over ${FED_PROOF_ADDRESS_MAX}). An answer that invents its own addresses is not a proof:" >&2
|
|
987
|
+
echo " NO receipt was written. Re-run the review." >&2
|
|
988
|
+
return 1
|
|
989
|
+
fi
|
|
990
|
+
if (( k < 1 || k > FED_PART_COUNT )) || [[ "$l" != "${FED_ECHO_LINE[k]}" ]]; then
|
|
991
|
+
echo "error: the review did NOT prove delivery — its proof section echoes 'part ${k} line ${l}', which" >&2
|
|
992
|
+
echo " was never requested. An answer that invents its own addresses is not a proof: NO receipt" >&2
|
|
993
|
+
echo " was written. Re-run the review." >&2
|
|
994
|
+
return 1
|
|
995
|
+
fi
|
|
996
|
+
seen_count[k]=$(( seen_count[k] + 1 ))
|
|
997
|
+
if (( seen_count[k] == 1 )); then seen_payload[k]="$payload"; fi
|
|
998
|
+
done <<< "$entries"
|
|
999
|
+
fi
|
|
1000
|
+
for (( k = 1; k <= FED_PART_COUNT; k++ )); do
|
|
1001
|
+
if (( seen_count[k] == 0 )); then
|
|
1002
|
+
echo "error: the review did NOT prove delivery — its '### Delivery proof' section carries no echo for" >&2
|
|
1003
|
+
echo " part ${k} of ${FED_PART_COUNT} (requested: part ${k} line ${FED_ECHO_LINE[k]}). A review that cannot" >&2
|
|
1004
|
+
echo " reproduce a part's body never received it: NO receipt was written. Re-run the review." >&2
|
|
1005
|
+
return 1
|
|
1006
|
+
fi
|
|
1007
|
+
if (( seen_count[k] > 1 )); then
|
|
1008
|
+
echo "error: the review did NOT prove delivery — part ${k}'s address is echoed more than once" >&2
|
|
1009
|
+
echo " (${seen_count[k]} times). One address, one line: an ambiguous proof proves nothing, so NO" >&2
|
|
1010
|
+
echo " receipt was written. Re-run the review." >&2
|
|
1011
|
+
return 1
|
|
1012
|
+
fi
|
|
1013
|
+
got="${seen_payload[k]}"
|
|
1014
|
+
want="${FED_ECHO_TEXT[k]}"
|
|
1015
|
+
if [[ "$(trim_ws "$got")" != "$(trim_ws "$want")" ]]; then
|
|
1016
|
+
echo "error: the review did NOT prove delivery — part ${k}'s echoed line does not match the change set" >&2
|
|
1017
|
+
echo " (requested: part ${k} line ${FED_ECHO_LINE[k]}). Delivery is unproven, so NO receipt was written." >&2
|
|
1018
|
+
echo " Re-run the review; a model that DID receive every part but mis-transcribed one line is the" >&2
|
|
1019
|
+
echo " known false-refusal case." >&2
|
|
1020
|
+
return 1
|
|
1021
|
+
fi
|
|
1022
|
+
done
|
|
1023
|
+
return 0
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
# Dispatch the prebuilt turns. Invariant E: a feed turn's stdout is captured PRIVATELY — nothing it
|
|
1027
|
+
# produced (an OK, or a premature verdict) reaches this wrapper's stdout or the parsed capture, and
|
|
1028
|
+
# the FIRST non-zero feed turn stops the run so no later turn is spent.
|
|
1029
|
+
run_fed_review() {
|
|
1030
|
+
local k conv_id="" log_file="$staging/turn1.log" feed_out="$staging/feed-turn-out" turn_rc turn_pass=()
|
|
1031
|
+
# ONE wall-clock budget for the WHOLE fed review, not one per turn. Handing every call the full
|
|
1032
|
+
# AGY_HARD_TIMEOUT multiplied the stated cap by the turn count — a 30m cap silently became up to
|
|
1033
|
+
# 30m × (N+1). Each turn now gets only what is LEFT of the shared deadline, and a review that runs
|
|
1034
|
+
# out of budget stops there instead of quietly outliving its own guarantee.
|
|
1035
|
+
# The grace agy.sh hands timeout(1) as --kill-after: a turn that ignores TERM keeps running that
|
|
1036
|
+
# much longer, so handing a turn the FULL remaining time lets the last one outlive the deadline by
|
|
1037
|
+
# the grace. Each turn is therefore given remaining MINUS the grace, and the whole N+1 sequence
|
|
1038
|
+
# stays inside the stated cap even when every turn has to be SIGKILLed.
|
|
1039
|
+
local deadline remaining turn_hard turn_soft soft_budget budget
|
|
1040
|
+
deadline=$(( $(date +%s) + $(aw_duration_seconds "$AGY_HARD_TIMEOUT") ))
|
|
1041
|
+
soft_budget=$(aw_duration_seconds "$AGY_TIMEOUT")
|
|
1042
|
+
for (( k = 1; k <= FED_PART_COUNT; k++ )); do
|
|
1043
|
+
remaining=$(( deadline - $(date +%s) ))
|
|
1044
|
+
# A turn needs strictly more than the SIGKILL grace to be dispatchable: hand it 1s and a
|
|
1045
|
+
# TERM-ignoring process still runs grace+1 seconds, breaking the very cap this loop enforces.
|
|
1046
|
+
# Too little left is therefore a refusal, never a shrunken turn.
|
|
1047
|
+
if (( remaining <= AGY_TURN_KILL_GRACE_S )); then
|
|
1048
|
+
echo "error: the fed review exhausted its hard wall-clock cap (AGY_HARD_TIMEOUT=${AGY_HARD_TIMEOUT}) before" >&2
|
|
1049
|
+
echo " feed turn ${k} of ${FED_PART_COUNT} — ${remaining}s left, and a turn needs more than the" >&2
|
|
1050
|
+
echo " ${AGY_TURN_KILL_GRACE_S}s SIGKILL grace to run without outliving the cap. The cap covers the WHOLE" >&2
|
|
1051
|
+
echo " review, not each turn — no later turn is spent and NO receipt is written." >&2
|
|
1052
|
+
echo " Re-run with a larger cap, or a smaller change set." >&2
|
|
1053
|
+
return 124
|
|
1054
|
+
fi
|
|
1055
|
+
budget=$(( remaining - AGY_TURN_KILL_GRACE_S ))
|
|
1056
|
+
turn_hard="${budget}s"
|
|
1057
|
+
turn_soft="$(( soft_budget < budget ? soft_budget : budget ))s"
|
|
1058
|
+
if (( k == 1 )); then turn_pass=(--log-file "$log_file")
|
|
1059
|
+
elif [[ -n "$conv_id" ]]; then turn_pass=(--conversation "$conv_id")
|
|
1060
|
+
else turn_pass=(--continue)
|
|
1061
|
+
fi
|
|
1062
|
+
set +e
|
|
1063
|
+
# Dispatched from the STAGING dir, never the work tree: agy surfaces the cwd's context file
|
|
1064
|
+
# (AGENTS.md / GEMINI.md / .antigravity.md) automatically, and that is text the model can see
|
|
1065
|
+
# which is NOT a body. The delivery proof is only sound when the wrapper knows every non-body
|
|
1066
|
+
# byte the model can read, so the fed lane removes the uncontrolled input instead of guessing
|
|
1067
|
+
# at it. Nothing is lost: the grounded facts already carry what the review must know.
|
|
1068
|
+
( cd "$staging" && AGY_MODEL="$AGY_MODEL" AGY_TIMEOUT="$turn_soft" AGY_HARD_TIMEOUT="$turn_hard" \
|
|
1069
|
+
"$AGY_RUN" "@$staging/turn-$k" -- "${turn_pass[@]}" ) > "$feed_out"
|
|
1070
|
+
turn_rc=$?
|
|
1071
|
+
set -e
|
|
1072
|
+
if (( turn_rc != 0 )); then
|
|
1073
|
+
echo "error: feed turn ${k} of ${FED_PART_COUNT} failed (exit ${turn_rc}) — the change set was never fully" >&2
|
|
1074
|
+
echo " delivered, so no later turn is spent and NO receipt is written. Re-run the review." >&2
|
|
1075
|
+
return "$turn_rc"
|
|
1076
|
+
fi
|
|
1077
|
+
if (( k == 1 )); then
|
|
1078
|
+
conv_id="$(capture_conversation_id "$log_file")"
|
|
1079
|
+
if [[ -z "$conv_id" ]]; then
|
|
1080
|
+
echo "notice: could not capture the conversation id from agy's run log — the remaining turns ride" >&2
|
|
1081
|
+
echo " --continue instead. Stated, never silent; the delivery proof still fails closed if" >&2
|
|
1082
|
+
echo " another conversation answers." >&2
|
|
1083
|
+
fi
|
|
1084
|
+
fi
|
|
1085
|
+
done
|
|
1086
|
+
if [[ -n "$conv_id" ]]; then turn_pass=(--conversation "$conv_id"); else turn_pass=(--continue); fi
|
|
1087
|
+
remaining=$(( deadline - $(date +%s) ))
|
|
1088
|
+
if (( remaining <= AGY_TURN_KILL_GRACE_S )); then
|
|
1089
|
+
echo "error: the fed review exhausted its hard wall-clock cap (AGY_HARD_TIMEOUT=${AGY_HARD_TIMEOUT}) before the" >&2
|
|
1090
|
+
echo " final review turn — ${remaining}s left, less than the ${AGY_TURN_KILL_GRACE_S}s SIGKILL grace a turn" >&2
|
|
1091
|
+
echo " needs. Every part was delivered but the review itself was never asked for." >&2
|
|
1092
|
+
echo " NO receipt was written. Re-run with a larger cap, or a smaller change set." >&2
|
|
1093
|
+
return 124
|
|
1094
|
+
fi
|
|
1095
|
+
budget=$(( remaining - AGY_TURN_KILL_GRACE_S ))
|
|
1096
|
+
turn_hard="${budget}s"
|
|
1097
|
+
turn_soft="$(( soft_budget < budget ? soft_budget : budget ))s"
|
|
1098
|
+
set +e
|
|
1099
|
+
( cd "$staging" && AGY_MODEL="$AGY_MODEL" AGY_TIMEOUT="$turn_soft" AGY_HARD_TIMEOUT="$turn_hard" \
|
|
1100
|
+
"$AGY_RUN" "@$staging/turn-final" -- "${turn_pass[@]}" ) | tee "$review_out_file"
|
|
1101
|
+
turn_rc=${PIPESTATUS[0]}
|
|
1102
|
+
set -e
|
|
1103
|
+
return "$turn_rc"
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
# Emit the full review surface to stdout: repo map (bounded, see emit_repo_file_map), status
|
|
1107
|
+
# (never-committable untracked records filtered), staged + unstaged diffs, and the CONTENTS of every
|
|
1108
|
+
# untracked REGULAR file (NUL-safe iteration over the SAME filtered walk as the fingerprint — the
|
|
1109
|
+
# payload is byte-identical with and without a device mask). Symlinks are shown as their target
|
|
1110
|
+
# (never followed — no out-of-repo leak); directories/vanished paths are noted, never read (a `cat`
|
|
1111
|
+
# on a FIFO would hang BEFORE the hard timeout applies — that class never reaches this loop).
|
|
611
1112
|
assemble_code_diff() {
|
|
612
1113
|
echo "=== repo file map (git ls-files) ==="
|
|
613
|
-
|
|
1114
|
+
emit_repo_file_map
|
|
614
1115
|
echo
|
|
615
1116
|
echo "=== git status (porcelain) ==="
|
|
616
1117
|
emit_status_porcelain_filtered
|
|
@@ -671,6 +1172,11 @@ PLAN_CONTENT=""
|
|
|
671
1172
|
DIFF_CONTENT=""
|
|
672
1173
|
REVIEW_ARTIFACT=""
|
|
673
1174
|
REVIEW_FINGERPRINT=""
|
|
1175
|
+
# D8/D8b: an agy `code` receipt SELF-DECLARES how the change set reached the model — `inline` when
|
|
1176
|
+
# the whole set rode ONE prompt (delivery proven BY CONSTRUCTION), `fed` when a chunked feed proved
|
|
1177
|
+
# it by echo. The kit's gate requires the field present and valid, never a particular value, and a
|
|
1178
|
+
# receipt that does not declare one no longer attests (the old --add-dir lane could not).
|
|
1179
|
+
REVIEW_DELIVERY=""
|
|
674
1180
|
if [[ -z "$resume_mode" ]]; then
|
|
675
1181
|
mode="${1:-}"; shift || true
|
|
676
1182
|
case "$mode" in
|
|
@@ -727,7 +1233,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
727
1233
|
need_value "$1" "${2:-}"; FOCUS_PARTS+=("$2"); shift 2 ;;
|
|
728
1234
|
--)
|
|
729
1235
|
echo "error: this wrapper OWNS the review posture — no '--' passthrough. The only escapes are" >&2
|
|
730
|
-
echo " AGY_PROBE=1 (off-frontier model)
|
|
1236
|
+
echo " AGY_PROBE=1 (off-frontier model). An oversized code review is a chunked feed, not a flag." >&2
|
|
731
1237
|
exit 2 ;;
|
|
732
1238
|
--*)
|
|
733
1239
|
echo "error: unknown flag '$1'." >&2; usage; exit 2 ;;
|
|
@@ -864,6 +1370,7 @@ else
|
|
|
864
1370
|
# so the receipt attests exactly the reviewed state.
|
|
865
1371
|
REVIEW_ARTIFACT="code"
|
|
866
1372
|
REVIEW_FINGERPRINT="$(compute_tree_fingerprint || true)"
|
|
1373
|
+
REVIEW_DELIVERY="inline"
|
|
867
1374
|
fi
|
|
868
1375
|
|
|
869
1376
|
emit_artifact() {
|
|
@@ -894,46 +1401,193 @@ else
|
|
|
894
1401
|
fi
|
|
895
1402
|
}
|
|
896
1403
|
|
|
1404
|
+
# Partition the assembled change set into under-cap parts, pick each part's delivery-proof line,
|
|
1405
|
+
# and BUILD every turn's prompt UP FRONT — so the per-turn ceiling and the total outgoing byte
|
|
1406
|
+
# count (D1b) are both checked BEFORE a single subscription turn is spent. A refusal here costs
|
|
1407
|
+
# nothing; a refusal after turn 1 would have burned quota for no review.
|
|
1408
|
+
plan_fed_review() {
|
|
1409
|
+
local artifact_file="$staging/change-set" uniques="$staging/uniques" nonbody="$staging/nonbody"
|
|
1410
|
+
local grounding_bytes frame_bytes begin_bytes end_bytes overhead1 overheadk budget1 budgetk
|
|
1411
|
+
local artifact_bytes concat_bytes offset len k n picked built total_bytes
|
|
1412
|
+
local art_lines final_nl part_lengths
|
|
1413
|
+
|
|
1414
|
+
( umask 077; emit_artifact > "$artifact_file" )
|
|
1415
|
+
( umask 077; emit_grounding > "$staging/grounding" )
|
|
1416
|
+
artifact_bytes=$(( $(wc -c < "$artifact_file") ))
|
|
1417
|
+
grounding_bytes=$(( $(wc -c < "$staging/grounding") ))
|
|
1418
|
+
# Measured at the WIDEST index rendering the loop can produce, so a part count that only becomes
|
|
1419
|
+
# known during planning can never push an already-budgeted turn past the ceiling.
|
|
1420
|
+
frame_bytes=$(( $(emit_feed_frame 9999 9999 | wc -c) ))
|
|
1421
|
+
begin_bytes=$(( $(printf -- "$FED_BEGIN_FMT" 9999 9999 | wc -c) ))
|
|
1422
|
+
end_bytes=$(( $(printf -- "$FED_END_FMT" 9999 9999 | wc -c) ))
|
|
1423
|
+
overhead1=$(( grounding_bytes + frame_bytes + begin_bytes + end_bytes ))
|
|
1424
|
+
overheadk=$(( frame_bytes + begin_bytes + end_bytes ))
|
|
1425
|
+
budget1=$(( AGY_MAX_PROMPT_BYTES - overhead1 ))
|
|
1426
|
+
budgetk=$(( AGY_MAX_PROMPT_BYTES - overheadk ))
|
|
1427
|
+
if (( budget1 < 1 || budgetk < 1 )); then
|
|
1428
|
+
echo "error: the fed lane's fixed overhead (grounding ${grounding_bytes} B + delivery framing ${overheadk} B)" >&2
|
|
1429
|
+
echo " leaves no room for a change-set body under AGY_MAX_PROMPT_BYTES=${AGY_MAX_PROMPT_BYTES}." >&2
|
|
1430
|
+
echo " Trim --facts / --decided / --focus, or raise AGY_MAX_PROMPT_BYTES (never past ~${AGY_ARGV_HARD_MAX})." >&2
|
|
1431
|
+
exit 2
|
|
1432
|
+
fi
|
|
1433
|
+
|
|
1434
|
+
art_lines=$(( $(LC_ALL=C awk 'END { print NR + 0 }' "$artifact_file") ))
|
|
1435
|
+
final_nl=1
|
|
1436
|
+
if [[ "$(tail -c 1 "$artifact_file" | wc -l)" -eq 0 ]]; then final_nl=0; fi
|
|
1437
|
+
# Run the partitioner ONCE into a variable: a refusal must be seen, and a `< <(…)` process
|
|
1438
|
+
# substitution would swallow its status.
|
|
1439
|
+
if ! part_lengths="$(plan_fed_parts "$artifact_file" "$budget1" "$budgetk" "$art_lines" "$final_nl")"; then
|
|
1440
|
+
echo "error: a single change-set line does not fit even an EMPTY fed part (first part budget" >&2
|
|
1441
|
+
echo " ${budget1} B, later parts ${budgetk} B). The lane cuts only at line boundaries, and a part" >&2
|
|
1442
|
+
echo " built from fragments of one giant line carries no line to prove delivery with —" >&2
|
|
1443
|
+
echo " refusing before any turn is spent. Exclude the oversized file, or review it alone." >&2
|
|
1444
|
+
exit 2
|
|
1445
|
+
fi
|
|
1446
|
+
offset=0
|
|
1447
|
+
k=0
|
|
1448
|
+
if [[ -n "$part_lengths" ]]; then
|
|
1449
|
+
while IFS= read -r len; do
|
|
1450
|
+
k=$(( k + 1 ))
|
|
1451
|
+
if (( len < 1 )); then
|
|
1452
|
+
echo "error: the partitioner produced an empty part — refusing rather than spending a turn on nothing." >&2
|
|
1453
|
+
exit 2
|
|
1454
|
+
fi
|
|
1455
|
+
( umask 077; slice_bytes "$artifact_file" "$offset" "$len" > "$staging/part-$k" )
|
|
1456
|
+
offset=$(( offset + len ))
|
|
1457
|
+
done <<< "$part_lengths"
|
|
1458
|
+
fi
|
|
1459
|
+
n=$k
|
|
1460
|
+
concat_bytes=0
|
|
1461
|
+
for (( k = 1; k <= n; k++ )); do concat_bytes=$(( concat_bytes + $(wc -c < "$staging/part-$k") )); done
|
|
1462
|
+
if (( n < 1 || concat_bytes != artifact_bytes )); then
|
|
1463
|
+
echo "error: the change-set partition does not reassemble to the assembled change set" >&2
|
|
1464
|
+
echo " (${concat_bytes} of ${artifact_bytes} bytes across ${n} part(s)) — refusing rather than" >&2
|
|
1465
|
+
echo " reviewing a change set the model would receive incompletely." >&2
|
|
1466
|
+
exit 2
|
|
1467
|
+
fi
|
|
1468
|
+
|
|
1469
|
+
( umask 077; LC_ALL=C awk '{ t = $0; gsub(/^[ \t]+|[ \t]+$/, "", t); c[t]++ } END { for (l in c) if (c[l] == 1) print l }' "$artifact_file" > "$uniques" )
|
|
1470
|
+
if [[ ! -s "$uniques" ]]; then
|
|
1471
|
+
echo "error: the change set carries no line unique enough to serve as a delivery proof — refusing," >&2
|
|
1472
|
+
echo " because an unprovable delivery is exactly what this lane exists to prevent." >&2
|
|
1473
|
+
exit 2
|
|
1474
|
+
fi
|
|
1475
|
+
# Everything the wrapper itself will SEND that is not a BODY. A candidate occurring anywhere in
|
|
1476
|
+
# here would be revealed to a model that never received its part, so the proof would prove
|
|
1477
|
+
# nothing. Rendered with the REAL part indices, because the frames carry them.
|
|
1478
|
+
( umask 077; {
|
|
1479
|
+
cat "$staging/grounding"
|
|
1480
|
+
for (( k = 1; k <= n; k++ )); do
|
|
1481
|
+
emit_feed_frame "$k" "$n"
|
|
1482
|
+
printf -- "$FED_BEGIN_FMT" "$k" "$n"
|
|
1483
|
+
printf -- "$FED_END_FMT" "$k" "$n"
|
|
1484
|
+
done
|
|
1485
|
+
emit_fed_final_head
|
|
1486
|
+
emit_shape_fed "(addresses are appended below)"
|
|
1487
|
+
} > "$nonbody" )
|
|
1488
|
+
|
|
1489
|
+
# ONE pass, no re-picking. The address list cannot reveal a candidate BY CONSTRUCTION (see
|
|
1490
|
+
# emit_shape_fed: one address per line, every such line shorter than a candidate's minimum), so
|
|
1491
|
+
# there is nothing to re-check once the addresses exist — the earlier two-pass blacklist loop was
|
|
1492
|
+
# solving a problem the FORMAT removes, and it could not be made a complete search anyway.
|
|
1493
|
+
local cand_no cand_text cand_trim address
|
|
1494
|
+
FED_REQUESTED=""
|
|
1495
|
+
for (( k = 1; k <= n; k++ )); do
|
|
1496
|
+
picked=""
|
|
1497
|
+
while IFS=$'\t' read -r cand_no cand_text; do
|
|
1498
|
+
cand_trim="$(trim_ws "$cand_text")"
|
|
1499
|
+
[[ -n "$cand_trim" ]] || continue
|
|
1500
|
+
(( $(fixed_occurrences "$cand_trim" "$artifact_file") == 1 )) || continue
|
|
1501
|
+
(( $(fixed_occurrences "$cand_trim" "$nonbody") == 0 )) || continue
|
|
1502
|
+
picked="${cand_no}"$'\t'"${cand_text}"
|
|
1503
|
+
break
|
|
1504
|
+
done < <(list_echo_candidates "$staging/part-$k" "$uniques")
|
|
1505
|
+
if [[ -z "$picked" ]]; then
|
|
1506
|
+
echo "error: part ${k} of ${n} carries no line the wrapper can use as a delivery proof — it needs an" >&2
|
|
1507
|
+
echo " interior line of ${FED_ECHO_MIN_BYTES}..${FED_ECHO_MAX_BYTES} bytes that occurs exactly ONCE in the whole change set and" >&2
|
|
1508
|
+
echo " nowhere in the wrapper's own framing. Refusing: delivery could not be proven." >&2
|
|
1509
|
+
exit 2
|
|
1510
|
+
fi
|
|
1511
|
+
FED_ECHO_LINE[k]="${picked%%$'\t'*}"
|
|
1512
|
+
FED_ECHO_TEXT[k]="${picked#*$'\t'}"
|
|
1513
|
+
address="part ${k} line ${FED_ECHO_LINE[k]}"
|
|
1514
|
+
# The invariant the one-pass selection rests on, ASSERTED rather than assumed: an address line
|
|
1515
|
+
# long enough to contain a candidate would break the constructive guarantee.
|
|
1516
|
+
if (( ${#address} >= FED_ECHO_MIN_BYTES )); then
|
|
1517
|
+
echo "error: the address '${address}' is ${#address} bytes, at or past the ${FED_ECHO_MIN_BYTES}-byte proof-candidate minimum —" >&2
|
|
1518
|
+
echo " the request list could then reveal a candidate. Refusing rather than sending an unsound proof." >&2
|
|
1519
|
+
exit 2
|
|
1520
|
+
fi
|
|
1521
|
+
if [[ -n "$FED_REQUESTED" ]]; then FED_REQUESTED+=$'\n'; fi
|
|
1522
|
+
FED_REQUESTED+="$address"
|
|
1523
|
+
done
|
|
1524
|
+
|
|
1525
|
+
for (( k = 1; k <= n; k++ )); do
|
|
1526
|
+
( umask 077; {
|
|
1527
|
+
if (( k == 1 )); then cat "$staging/grounding"; fi
|
|
1528
|
+
emit_feed_frame "$k" "$n"
|
|
1529
|
+
printf -- "$FED_BEGIN_FMT" "$k" "$n"
|
|
1530
|
+
cat "$staging/part-$k"
|
|
1531
|
+
printf -- "$FED_END_FMT" "$k" "$n"
|
|
1532
|
+
} > "$staging/turn-$k" )
|
|
1533
|
+
done
|
|
1534
|
+
( umask 077; { emit_fed_final_head; echo; emit_shape_fed "$FED_REQUESTED"; } > "$staging/turn-final" )
|
|
1535
|
+
|
|
1536
|
+
total_bytes=0
|
|
1537
|
+
for (( k = 1; k <= n; k++ )); do
|
|
1538
|
+
built=$(( $(wc -c < "$staging/turn-$k") ))
|
|
1539
|
+
if (( built > AGY_MAX_PROMPT_BYTES )); then
|
|
1540
|
+
echo "error: fed turn ${k} would be ${built} bytes, over AGY_MAX_PROMPT_BYTES=${AGY_MAX_PROMPT_BYTES} —" >&2
|
|
1541
|
+
echo " refusing before any turn is spent." >&2
|
|
1542
|
+
exit 2
|
|
1543
|
+
fi
|
|
1544
|
+
total_bytes=$(( total_bytes + built ))
|
|
1545
|
+
done
|
|
1546
|
+
built=$(( $(wc -c < "$staging/turn-final") ))
|
|
1547
|
+
if (( built > AGY_MAX_PROMPT_BYTES )); then
|
|
1548
|
+
echo "error: the final review turn would be ${built} bytes, over AGY_MAX_PROMPT_BYTES=${AGY_MAX_PROMPT_BYTES}." >&2
|
|
1549
|
+
echo " Trim --facts / --decided / --focus." >&2
|
|
1550
|
+
exit 2
|
|
1551
|
+
fi
|
|
1552
|
+
total_bytes=$(( total_bytes + built ))
|
|
1553
|
+
if (( total_bytes > AGY_REVIEW_MAX_TOTAL_BYTES )); then
|
|
1554
|
+
echo "error: the fed review would send ${total_bytes} outgoing prompt bytes across $(( n + 1 )) turns," >&2
|
|
1555
|
+
echo " over AGY_REVIEW_MAX_TOTAL_BYTES=${AGY_REVIEW_MAX_TOTAL_BYTES} — refusing BEFORE the first turn is spent." >&2
|
|
1556
|
+
echo " Split the change set into focused per-area reviews, or raise AGY_REVIEW_MAX_TOTAL_BYTES" >&2
|
|
1557
|
+
echo " knowing that retention past ~263000 bytes of body has never been probed." >&2
|
|
1558
|
+
exit 2
|
|
1559
|
+
fi
|
|
1560
|
+
|
|
1561
|
+
FED_PART_COUNT=$n
|
|
1562
|
+
echo "notice: the assembled prompt is ${prompt_bytes} bytes (> AGY_MAX_PROMPT_BYTES=${AGY_MAX_PROMPT_BYTES}) —" >&2
|
|
1563
|
+
echo " feeding the change set in ${n} part(s) over $(( n + 1 )) subscription turns, then reviewing." >&2
|
|
1564
|
+
echo " Total outgoing prompt bytes: ${total_bytes} (AGY_REVIEW_MAX_TOTAL_BYTES=${AGY_REVIEW_MAX_TOTAL_BYTES})." >&2
|
|
1565
|
+
# 4.3: state the boundary fact at the point of use. agy's own denial names the permission rule it
|
|
1566
|
+
# wants; granting it would widen a boundary for ALL agy use on this machine, so the kit surfaces
|
|
1567
|
+
# it and never writes it. The feed exists precisely so no permission is needed.
|
|
1568
|
+
echo " (The change set is DELIVERED, not read: headless agy auto-denies its own read_file" >&2
|
|
1569
|
+
echo " tool, and this kit never grants that permission — it would widen a boundary for every" >&2
|
|
1570
|
+
echo " agy run on this machine. Delivery is proven per part instead.)" >&2
|
|
1571
|
+
}
|
|
1572
|
+
|
|
897
1573
|
# Full prompt = grounding + artifact (inline) + shape.
|
|
898
1574
|
( umask 077; { emit_grounding; emit_artifact; echo; emit_shape; } > "$prompt_file" )
|
|
899
1575
|
prompt_bytes=$(( $(wc -c < "$prompt_file") ))
|
|
900
1576
|
|
|
901
1577
|
if (( prompt_bytes > AGY_MAX_PROMPT_BYTES )); then
|
|
902
|
-
if [[ "$
|
|
903
|
-
#
|
|
904
|
-
#
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
emit_grounding
|
|
909
|
-
echo "## The change set under review"
|
|
910
|
-
echo "The full change set is too large to inline. It is in the precomputed file at:"
|
|
911
|
-
echo " $artifact_file"
|
|
912
|
-
echo "Read it IN FULL, then review it against the grounded facts above."
|
|
913
|
-
echo
|
|
914
|
-
emit_shape
|
|
915
|
-
} > "$prompt_file" )
|
|
916
|
-
small_bytes=$(( $(wc -c < "$prompt_file") ))
|
|
917
|
-
if (( small_bytes > AGY_MAX_PROMPT_BYTES )); then
|
|
918
|
-
echo "error: even the grounding-only prompt is ${small_bytes} bytes (> AGY_MAX_PROMPT_BYTES=${AGY_MAX_PROMPT_BYTES})." >&2
|
|
919
|
-
echo " Trim --facts / --decided / --focus." >&2
|
|
920
|
-
exit 2
|
|
921
|
-
fi
|
|
922
|
-
echo "warning: the assembled prompt was ${prompt_bytes} bytes (> AGY_MAX_PROMPT_BYTES=${AGY_MAX_PROMPT_BYTES})." >&2
|
|
923
|
-
echo " AGY_REVIEW_ALLOW_ADDDIR=1 — offloading the change set to a private staging dir and" >&2
|
|
924
|
-
echo " passing it via --add-dir. This RE-ENABLES the Issue-001 stall risk (heavy agentic" >&2
|
|
925
|
-
echo " roaming); the inherited hard timeout (AGY_HARD_TIMEOUT=$AGY_HARD_TIMEOUT) bounds it." >&2
|
|
926
|
-
run_passthrough=(--add-dir "$staging")
|
|
1578
|
+
if [[ "$mode" == "code" ]]; then
|
|
1579
|
+
# The change set cannot be FETCHED on this host (agy's native read_file is auto-denied
|
|
1580
|
+
# headlessly), so it is DELIVERED: under-cap parts over continuation turns, proven by echo.
|
|
1581
|
+
plan_fed_review
|
|
1582
|
+
FED_MODE=1
|
|
1583
|
+
REVIEW_DELIVERY="fed"
|
|
927
1584
|
else
|
|
1585
|
+
# D2: chunking is code-mode only — a plan/diff artifact is an operator-supplied file the
|
|
1586
|
+
# operator can split, so those modes keep the refuse-over-cap contract verbatim.
|
|
928
1587
|
echo "error: the assembled prompt is ${prompt_bytes} bytes, over AGY_MAX_PROMPT_BYTES=${AGY_MAX_PROMPT_BYTES}." >&2
|
|
929
1588
|
echo " agy takes the prompt as a single argv; past ~131072 bytes it fails with a cryptic" >&2
|
|
930
1589
|
echo " 'Argument list too long'. Trim to the relevant hunks, or split into focused per-area" >&2
|
|
931
|
-
|
|
932
|
-
echo " reviews. For a large CODE review, AGY_REVIEW_ALLOW_ADDDIR=1 offloads the change set via" >&2
|
|
933
|
-
echo " a private --add-dir staging dir (re-enables the Issue-001 stall risk)." >&2
|
|
934
|
-
else
|
|
935
|
-
echo " reviews (split the $mode into focused parts)." >&2
|
|
936
|
-
fi
|
|
1590
|
+
echo " reviews (split the $mode into focused parts)." >&2
|
|
937
1591
|
exit 2
|
|
938
1592
|
fi
|
|
939
1593
|
fi
|
|
@@ -966,7 +1620,10 @@ echo "review posture: model=${AGY_MODEL:-<agy settings default>} timeout=$aw_tim
|
|
|
966
1620
|
# parsed into the review receipt — the user-facing stream is unchanged.
|
|
967
1621
|
review_out_file="$staging/review-output"
|
|
968
1622
|
set +e
|
|
969
|
-
if ((
|
|
1623
|
+
if (( FED_MODE == 1 )); then
|
|
1624
|
+
run_fed_review
|
|
1625
|
+
rc=$?
|
|
1626
|
+
elif (( ${#run_passthrough[@]} > 0 )); then
|
|
970
1627
|
AGY_MODEL="$AGY_MODEL" AGY_TIMEOUT="$AGY_TIMEOUT" AGY_HARD_TIMEOUT="$AGY_HARD_TIMEOUT" \
|
|
971
1628
|
"$AGY_RUN" "@$prompt_file" -- "${run_passthrough[@]}" | tee "$review_out_file"
|
|
972
1629
|
rc=${PIPESTATUS[0]}
|
|
@@ -982,6 +1639,29 @@ set -e
|
|
|
982
1639
|
# included — is a FAILED review: non-zero exit, NO receipt. This is a failed review to RE-RUN,
|
|
983
1640
|
# never a fatal session error (documented in --help).
|
|
984
1641
|
if [[ $rc -eq 0 ]]; then
|
|
1642
|
+
# D1/D7: on the fed lane, delivery is checked BEFORE the verdict is even parsed — an unproven
|
|
1643
|
+
# delivery is never a downgraded verdict and never a warning beside a kept receipt.
|
|
1644
|
+
if (( FED_MODE == 1 )); then
|
|
1645
|
+
if fed_output_is_answerless "$review_out_file"; then
|
|
1646
|
+
echo "error: the fed review produced no review at all — the final turn returned neither a" >&2
|
|
1647
|
+
echo " '### Delivery proof' nor a '### Verdict' section. The wrapper SENT every one of" >&2
|
|
1648
|
+
echo " the ${FED_PART_COUNT} part(s) and every feed turn exited zero; whether the model retained them is" >&2
|
|
1649
|
+
echo " precisely what the unanswered proof leaves unknown, so nothing is claimed here." >&2
|
|
1650
|
+
if fed_output_names_tool_denial "$review_out_file"; then
|
|
1651
|
+
echo " CAUSE (named by agy itself): it called a tool and headless mode auto-denied it, which" >&2
|
|
1652
|
+
echo " produces NO output at all. Observed on very large fed reviews; the fix is a SMALLER" >&2
|
|
1653
|
+
echo " review — split the change set, or leave AGY_REVIEW_MAX_TOTAL_BYTES at its default so" >&2
|
|
1654
|
+
echo " an oversized feed refuses before spending any turn." >&2
|
|
1655
|
+
else
|
|
1656
|
+
echo " CAUSE: unknown — agy returned no recognizable diagnostic. Inspect the run output." >&2
|
|
1657
|
+
fi
|
|
1658
|
+
echo " NO receipt was written. Re-run the review." >&2
|
|
1659
|
+
exit 4
|
|
1660
|
+
fi
|
|
1661
|
+
if ! verify_delivery_proof "$review_out_file"; then
|
|
1662
|
+
exit 4
|
|
1663
|
+
fi
|
|
1664
|
+
fi
|
|
985
1665
|
verdict="$(parse_agy_verdict "$review_out_file")"
|
|
986
1666
|
if [[ "$verdict" == "unknown" ]]; then
|
|
987
1667
|
echo "error: the review output carries no recognized '### Verdict' section (closed vocabulary:" >&2
|
|
@@ -1003,7 +1683,7 @@ if [[ $rc -eq 0 ]]; then
|
|
|
1003
1683
|
grounded=true
|
|
1004
1684
|
facts_hash="$(printf '%s' "$FACTS_CONTENT" | sha256_stdin || true)"
|
|
1005
1685
|
fi
|
|
1006
|
-
write_review_receipt "$REVIEW_ARTIFACT" true "$REVIEW_FINGERPRINT" "$verdict" "$grounded" "$facts_hash" "$REVIEW_PROBE"
|
|
1686
|
+
write_review_receipt "$REVIEW_ARTIFACT" true "$REVIEW_FINGERPRINT" "$verdict" "$grounded" "$facts_hash" "$REVIEW_PROBE" "$REVIEW_DELIVERY"
|
|
1007
1687
|
fi
|
|
1008
1688
|
fi
|
|
1009
1689
|
exit $rc
|