Skip to content

perf(prompt): deliver the contract to fable by path, not inlined - #83

Merged
kevin-agrology merged 4 commits into
mainfrom
fix/fable-prompt-by-path
Aug 12, 2026
Merged

perf(prompt): deliver the contract to fable by path, not inlined#83
kevin-agrology merged 4 commits into
mainfrom
fix/fable-prompt-by-path

Conversation

@kevin-agrology

Copy link
Copy Markdown
Contributor

TL;DR: The reviewer that runs on every round of every review was being handed a 17 KB prompt, 82% of which was a copy of the protocol it can simply open itself. That copy exists to work around a limitation only the Gemini CLI has. Fable now gets a file path instead — 17,008 B → 3,056 B.

Why the contract was inlined at all

Exactly one reason, from #22: gemini-cli refuses any read outside its workspace root, the protocol lives under the plugin cache (always outside the reviewed repo), and the in-repo copy is git-ignored twice over by ensure-skill — which gemini-cli also honors. Inlining removes the filesystem from the path entirely.

That constraint belongs to dispatch-kind shell. It has never applied to fable, which runs in-harness as a subagent and can Read an absolute path anywhere. Applying gemini's sandbox to fable was a generalization nobody checked.

And it lands on the worst possible provider: fable is the guaranteed floor reviewer, so it is the one present in every round of every run.

provider before after why
fable 17,008 B 3,056 B subagent — reads the path
gemini 17,008 B unchanged #22 sandbox is real
codex 2,702 B unchanged byte-frozen golden still matches

Keyed on dispatch-kind, not provider id

prompt_head now has three shapes — skill / inline / path — selected by kind. shell means "an external CLI with its own workspace sandbox", which is precisely the population that cannot be sent to a path. A future shell-kind provider inherits the right behaviour without touching this.

Why this is sound, not merely cheaper

The prompt tail states the operative grammar in full regardless of shape. A fable turn that never opens the file still emits findings merge can read — the file adds marker/scope detail, not the enforceable shape.

The suite asserts that explicitly: each grammar token ([finding:<id>|<sev>], [no-findings], — via, — risk:, — evidence:) is present without the inline. And the prompt now tells the reviewer to stop and say so if it cannot read the file, rather than guess.

So the failure mode of a missed read is a slightly less informed reviewer, not an unparseable turn.

Deliberate test change (§4)

The inline assertions ran over fable gemini and pinned "no filesystem path may stand in for it". That encoded gemini's constraint as a universal — the bug itself, written as a test. The loop is narrowed to gemini, where it remains exactly right and fully asserted; fable gets its own block asserting the path shape plus the grammar-survives-without-the-file property.

Nothing was weakened — gemini keeps every assertion it had, including the two that prove the inline is complete and not truncated.

Also drops the Cost is ~7KB of prompt per dispatch comment, which had drifted to 14 KB as the protocol grew.

Verification

check result
scripts/*.test.sh (15 suites) all pass
reviewer/fable-contract-by-path mutation caught
codex golden byte-identical
multi-review-version-check.sh 1.22.1 → 1.22.2 (ok)

This branch and #82 both edited reviewer.sh and git auto-merged them; both behaviours were re-verified rather than assumed. The table conflict also needed care in a direction the usual keep-both doesn't cover — this branch predates #82's removal of a stale entry, so a blind keep-both would have resurrected it. Confirmed absent.

Not run locally: shellcheck; CI covers it plus the bash 3.2 leg and the full sweep.


🤖 Generated with Claude Code

https://claude.ai/code/session_01Nm3hNXA4WHbgKjFbxX7VUE

kevin-agrology and others added 4 commits August 11, 2026 18:29
The command file documented this gap rather than closing it: "gemini remains
exposed to the same check-cwd-vs-dispatch-cwd drift this fixed for codex".

gemini-cli's workspace is the cwd of the process at LAUNCH, and it refuses to
read or write outside it. Two things followed from never pinning that:

THE CHECK JUDGED BY ITS OWN CWD. The basis was `repo_root()` — the git toplevel
of the CHECK invocation — on the stated reasoning that gemini "runs as a shell
command in the CURRENT cwd" so that was the right basis. The premise holds only
if check and dispatch stand in the same place, and nothing enforced it. The arm
was therefore wrong in BOTH directions, and the suite now asserts both: it
MISSED a copy outside the root dispatch would inherit (the #66 failure), and it
FALSELY hinted on a copy that was inside that root but outside whatever repo the
check happened to be standing in. The second was the surprise — a spurious hint
teaches the reader to ignore the real one.

THE DISPATCH PINNED NOTHING. The shell branch set no directory at all, so the
workspace was whatever cwd that Bash call inherited. Now `cd "<session-root>"`
inside a subshell, scoped to the dispatch, leaving the primary's own cwd alone.

Both halves are needed: pinning only the check would validate a root the
dispatch does not use, which is exactly the shape of #66.

`fable` needs no basis — it runs in-harness with no workspace of its own — so
`--session-root` is now consumed by both arms that have one, and the scope note
saying otherwise is removed. A stale claim like that is how the gap survived:
written down instead of fixed.

Guards: three entries — the check's basis, the dispatch's cwd, and the scope
claim itself, so the documentation cannot quietly revert to describing the bug.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nm3hNXA4WHbgKjFbxX7VUE
Inlining exists for ONE reason (issue #22): gemini-cli refuses any read outside
its workspace root, and the protocol lives under the plugin cache — always
outside the reviewed repo. That constraint belongs to dispatch-kind `shell`.
It has never applied to `fable`, which runs IN-HARNESS as a subagent and can
Read an absolute path anywhere.

Applying gemini's sandbox to fable cost 14 KB on the most-dispatched prompt in
the system: fable is the guaranteed floor reviewer, so it is the one provider
present in every round of every run.

  fable   17,008 B -> 3,056 B   (82% of it was the inlined contract)
  gemini  17,008 B  unchanged   (the #22 constraint is real)
  codex    2,702 B  unchanged   (byte-frozen golden still matches)

prompt_head now has THREE shapes — skill / inline / path — keyed on
dispatch-kind rather than a provider id, so a future shell-kind provider
inherits the right behaviour without touching this.

WHY THIS IS SOUND AND NOT MERELY CHEAPER: the prompt tail states the operative
grammar in full regardless of shape, so a fable turn that never opens the file
still emits findings `merge` can read. The file adds marker/scope detail, not
the enforceable shape. The suite asserts each grammar token — the finding form,
[no-findings], and the via/risk/evidence lines — survives WITHOUT the inline,
and the prompt now tells the reviewer to stop and say so if the read fails
rather than guess.

Deliberate test change (§4): the inline assertions ran over `fable gemini` and
pinned "no filesystem path may stand in for it". That encoded gemini's
constraint as a universal, which is the bug. The loop is narrowed to gemini —
where it is still exactly right and still asserted — and fable gets its own
block asserting the path shape plus the grammar-survives-without-the-file
property. Nothing was weakened: the gemini path keeps every assertion it had.

Also drops the "Cost is ~7KB of prompt per dispatch" comment, which had drifted
to 14 KB as the protocol grew.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nm3hNXA4WHbgKjFbxX7VUE
Both branches edited reviewer.sh; git auto-merged them and BOTH behaviours were
re-verified rather than assumed — G2's session-root basis for the gemini check,
and this branch's three-shape contract delivery.

The table conflict needed care in one direction the usual keep-both does not
cover: this branch predates #82's removal of the now-stale
reviewer/check-doc-gemini-basis entry, so a blind keep-both would have
RESURRECTED it. Confirmed absent (0 occurrences) and both live entries present.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nm3hNXA4WHbgKjFbxX7VUE
Narrowing the inline assertions from `fable gemini` to `gemini` left a
one-element loop, which shellcheck flags as probably-a-mistake. It is not: the
loop is over a POPULATION — every provider whose contract must be inlined —
which has exactly one member today because `shell` is the only dispatch-kind
that cannot be sent to a path. Kept as a loop so a second shell-kind provider
joins by being added to the list and inherits every assertion, with the reason
recorded next to the disable.

Caught by CI, not locally: shellcheck is not installed on this machine, so the
gate's shellcheck step is the only place this could surface.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nm3hNXA4WHbgKjFbxX7VUE
@kevin-agrology
kevin-agrology merged commit 4c7c1ef into main Aug 12, 2026
4 checks passed
@kevin-agrology
kevin-agrology deleted the fix/fable-prompt-by-path branch August 12, 2026 01:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant