Every ticket as the root of its provenance graph — goal, patch, approval, runs and (as the ticket-class build lands) mandate, manifestations, guarantees, journey, artifacts and children. Rendered through the <<<TicketGraphList>>> tag.
THE COUPLER: senior emits signatures (lower_functions = the mock senior), junior fills ONE body at a time, critic gates AST-equivalence. Reuses code_wrapper.py's lower/critic/judge/render. Python only (89% of unique forge code). Includes the blank-and-refill benchmark.
goal
Build the COUPLER: the loop that sits between codebean and the coding agent. The senior hands the
junior ONE function signature; the junior returns ONLY that function's body; codebean holds the
skeleton and the machine applies the decorators. The LLM never sees a path, a tool, or a repo.
WHY THIS SHAPE (operator's design, do not re-litigate it):
* A worker told "rewrite this module" returns 1791 lines for a 65-line decoy. A worker told
"here is `def call_tool(name, args):` — return the body" CANNOT. The skeleton is not the LLM's
to invent, so size, paths, tool schemas and cross-module wandering all become UNREPRESENTABLE
rather than merely discouraged.
* MEASURED failure modes this removes (from the GLM canaries #414/#416): wandered into an
unrelated module for ~18 steps; wrote to a plausible-but-wrong nested path; emitted Bash
`command` as a list; needed ~33 steps vs Claude's ~12.
WHAT ALREADY EXISTS — REUSE IT, BUILD NOTHING NEW:
codebean_mcp/prototype/code_wrapper.py already provides the whole spine:
lower_functions(src_path) -> list[Function] # source -> model (names, args, docstrings)
lower_node(node) -> Function
_ast_norm(src) # the SAME identity the ticketer uses
make_candidates(fn) -> list[Candidate] # per-function candidates (best-of-N)
critic(cand, original_src) -> bool # _ast_norm(rendered) == _ast_norm(original)
judge(cand) -> (bool, [reasons]) # decorator present, imports, ...
render(instrument=True|False) # born-instrumented -> frozen/mature
The codebean MCP surface is the contract: new_module / add_class / add_function / set_body /
render / save. add_function IS the senior's output; set_body IS the junior's.
DELIVERABLE: checkouts/current/congruency/tools/coupler.py — standalone; imports code_wrapper.
1. MOCK SENIOR (no LLM). `lower_functions(original)` IS a deterministic senior for the refactor
case: it yields every signature + docstring mechanically. The ticket's embedded
===ORIGINAL SOURCE=== is the input. No senior agent is built here.
2. STRUCTURE FIRST. Lay down EVERY signature via add_function BEFORE any body is filled, so a body
may call any sibling regardless of fill order. No topological sort needed.
3. THE JUNIOR LOOP — one function at a time. Hand the junior EXACTLY:
its own signature + docstring,
the sibling signatures (names and args ONLY, no bodies),
the module's imports and constants.
Nothing else. No filesystem, no tools, no repo. It returns ONLY the body text.
(Signature alone is not enough: a body calls siblings and imports; without them it hallucinates
helpers. This is the minimum viable context and it is still text-in/text-out.)
4. THE GATE, per function, cheapest first: parse -> then `critic` (AST-normalized equivalence to the
original body) for PRESERVE functions. critic is a MECHANICAL PROOF of behaviour preservation —
not an auditor's prose opinion.
5. RETRY + ESCALATE PER FUNCTION, not per module. Gate fails -> re-prompt that ONE function with the
exact error (SyntaxError line N / the _ast_norm mismatch). Fails N times -> escalate THAT ONE
BLANK to a stronger model. A bad body costs one re-prompt, never a module.
6. THE BLANK-AND-REFILL BENCHMARK (`--benchmark`) — this is how the tool proves itself. Take a real
module, BLANK each body, have the junior refill it from signature+docstring, and compare against
the original you kept. Report per function: `_ast_norm`-equal (reproduced it) AND parses/passes
(works). Ground truth is free; 262 forge repos of real code are the corpus. This answers #173
("benchmark on ACTUAL agent tasks, not generic evals") with numbers instead of vibes.
7. --engine passthrough so the junior can be claude | cerebras (GLM 4.7) | ollama via the existing
ollama_agent.py engines. Default claude.
8. --selftest on a THROWAWAY synthetic module: structure-first (all signatures land before bodies),
a body that fails parse is re-prompted, a PRESERVE function that differs semantically FAILS
critic, one that differs only in whitespace/comments PASSES, and escalation fires after N.
No network, no live db, no writes outside the temp dir.
SCOPE: PYTHON ONLY. Measured: by unique blob the forge is 89% Python / 6% PHP (5,119 of 5,736 unique
code files). codebean is ast-based and cannot lower PHP. A PHP bean is a separate ticket (357 unique
files, 0 parse failures, 0 generators, 0 by-ref params — 99.8% wrapper-safe; the rename+wrapper keeps
critic intact and was PROVEN against the shipped php binary).
ACCEPTANCE: --selftest green; `--benchmark` on a real forge module prints per-function reproduced/
works rates; a PRESERVE run on a module yields code that passes critic against the original; the
junior is never handed a path or a tool (assert it — the prompt contains only signature/docstring/
sibling-signatures/imports). RUN ON CLAUDE.
patch
none
approval
unapproved
runs
no runs recorded
mandate (clauses)
not yet recorded — lands with the ticket-class build
manifestations
not yet recorded — lands with the ticket-class build
guarantees
not yet recorded — lands with the ticket-class build
journey (blunders & successes)
not yet recorded — lands with the ticket-class build