comic-director — the Audited Spiral + Release (Phase 2/3)
The middle + right of Figure 1: take an authored comic.json (produced by the
comic-author skill, Phase 1) and turn it into baked frames + a clickable
viewer, through the audited spiral. Per frame: render a deterministic content-SVG blueprint → bake a
pixel-art frame (agent mcp__codex__codex sidecar) → a 3-reviewer cross-model panel_gate → keep / retry → page assembly →
projection. The movie twin of method-figure's render half. (Image-based today; video-based is next.)
Input contract — comic.json (authored upstream)
The contract boundary is comic.json + the assets it references (content-SVG blueprints, identity refs,
ART_BIBLE.md) — authored by comic-author. This skill is render + verify:
- It never invents a number or a cast — faithfulness is a deterministic token-diff over reviewers who
never see the expected values; identity is judged against the ref the author supplied (cast-aware).
- It writes back only
image_path / active_attempt_id / wiki_node_id (+ the wiki trace) on KEEP.
- The generator family (Codex image_gen) can NOT self-acquit. A KEEP needs the token-diff clean and
the cross-model visual panel (Gemini + Codex) to pass. The calling agent (Claude) gives the final
structural sign-off.
Full IR spec: ../../docs/comic-json.md /
../../schemas/comic.schema.json.
Run it (scripts/run_comic.py — agent-driven bake, CLI gates)
A pure-stdlib faithful port of packages/core/spiral_engine.js's movie branch. The cross-model gates
(Gemini + Codex vision) still shell the gemini / codex CLIs directly. The BAKE is performed by the
skill agent via the mcp__codex__codex MCP tool through --bake-mode=agent (the default): the core
emits a deterministic bake-request sidecar (prompt + absolute ref paths + exact out_path), the agent calls
mcp__codex__codex (sandbox: workspace-write, model: gpt-5.5, config: {model_reasoning_effort: xhigh, include_image_gen_tool: true},
cwd: <project>), and the core verifies the explicit out_path. (codex exec is RETIRED for real bakes — it
forced a hand-drawn SVG/struct+zlib fallback; --bake-mode=exec RAISES if it ever reaches a real bake. See
docs/spiral-runtime.md.)
python3 skills/comic-director/scripts/run_comic.py \
--project examples/comic_m3_audit --page P02_b08 --panels S12,S13,S14,S15 \
[--bake-lang zh] [--finalize] [--dry-run] [--skip-assembly] \
[--gemini-cmd "python3 cli/gemini_agy_shim.py"] [--gemini-family google]
--dry-run validates + prints each frame's concrete bake prompt (no image_gen) — run this first; it's
also how comic-author confirms Phase 1 is correct.
--finalize rebuilds the single-file viewer (packages/viewer/build_comic.py) only if the run is
shippable (nothing escalated/throttled/flagged-for-human; assembly accepted).
--gemini-cmd (default: the legacy gemini CLI) — the argv prefix shelled for the google-family reviewer
slot (the --model auto-gemini-3 -p <prompt> tail is appended). With the legacy CLI deprecated upstream
(IneligibleTierError, 2026-07), pass --gemini-cmd "python3 cli/gemini_agy_shim.py" — the shipped shim
translates the old interface to Antigravity's agy and pins a Gemini model (Antigravity also serves
Claude/GPT-OSS; the slot must stay google-family for cross-model quorum honesty).
--gemini-family (default google) — the provenance family recorded in the wiki for whatever
--gemini-cmd actually routes to. It must stay non-openai (the visual quorum needs a second family);
record honestly, never re-label.
- Needs the
codex CLI + a google-family reviewer (legacy gemini CLI, or Antigravity via
--gemini-cmd + the shim) on PATH + headless Chrome (to rasterize the content-SVG blueprint).
Reuses skills/method-figure/scripts/pickup_image.py.
It prints a JSON run-report (kept, flagged_for_human, needs_human, shippable, throttled,
escalated, assembly, attempts_per_panel). Engine semantics it ports (caps 4/panel + 6/run, the
deterministic verdict, anatomy single-vote veto, throttle → fresh-run): ../../docs/spiral-runtime.md.
Bake seam contract (agent ↔ core) — --bake-mode=agent
The bake is a synchronous sidecar handshake (contract-v2 §1–§4). The core never returns/exits early on a
bake; only the bake primitive moved off codex exec onto mcp__codex__codex:
- (A) core writes
<out>.bakereq.json =
{prompt_text, content_png, identity_ref, out_path, model:"gpt-5.5", config:{model_reasoning_effort:"xhigh", include_image_gen_tool:true}, sandbox:"workspace-write", cwd, created_at, min_bytes:500000, aspect, request_id:"<uuid4 hex>"}
(prompt_text ALREADY contains the absolute ref + out paths — the schema has no -i; config carries
both model_reasoning_effort:"xhigh" AND include_image_gen_tool:true — without the latter codex won't fire
its native image tool; request_id is a per-bake uuid4-hex nonce the core mints — see (D)+(E)), pre-deleting any
stale out_path + status.
- (B) the agent calls
mcp__codex__codex with EXACTLY those params.
- (C) codex writes a native PNG to
out_path.
- (D) the agent writes
<out>.bakestatus.json =
{status:"ok"|"fail", failure_kind:"throttle"|"other", mcp_output:"<bounded raw codex tool output>", mcp_error, request_id:"<verbatim from bakereq>"}.
mcp_output is MANDATORY on BOTH ok and fail — the core feeds this status file to pickup --transcript,
and the HARD-VETO scans it for import struct/zlib.compress/<svg>/matplotlib/… markers. An ok
status with no raw output makes the HARD-VETO inert (a hand-drawn struct+zlib/SVG that still emits a
≥500KB sig-valid PNG would slip through) — so always embed the bounded raw tool result.
request_id is EQUALLY MANDATORY on BOTH ok and fail — copy it VERBATIM from <out>.bakereq.json into
the bakestatus; pickup_image.py --out-existing --request-id fail-closes the bake if the status
request_id is missing or ≠ the bakereq nonce (a stale/foreign status at the same path can't be silently honored).
- (E) the core verifies via
pickup_image.py --out-existing on the EXPLICIT out_path (PNG sig + IHDR dims
- size > 500000 + aspect band +
mtime >= created_at), HARD-VETOES the markers in the transcript
(= the status file's mcp_output), and (via --request-id) fail-closes if the status file's request_id
is absent or ≠ the bakereq nonce — but all ONLY after it has read status:"ok". Empty/timeout/any-non-ok =
fail-closed (no pickup). NO fallback to codex exec/PIL/SVG; effort is ALWAYS xhigh; sandbox ALWAYS
workspace-write.
Who runs --bake-mode=agent (the agent-wrapper SOP) — REQUIRED for the default mode to function
The comic-director skill agent is the bake fulfiller. Concretely:
- Launch the orchestrator in the BACKGROUND:
python3 skills/comic-director/scripts/run_comic.py --project … --page … --panels … --bake-mode agent
(it blocks on each bake by polling <out>.bakestatus.json).
- Loop until the orchestrator prints its final run-report JSON / exits:
- watch the project's
panels/ dir for a new *.bakereq.json;
- read it; call
mcp__codex__codex with exactly its
{prompt: <prompt_text>, model:"gpt-5.5", config:{include_image_gen_tool:true, model_reasoning_effort:"xhigh"}, sandbox:"workspace-write", cwd:<cwd>}
(codex writes the native PNG to the sidecar's out_path). The config MUST carry both
include_image_gen_tool:true AND model_reasoning_effort:"xhigh": without include_image_gen_tool Codex will
not fire its native image tool (it falls back to descriptive text / an SVG renderer), and xhigh is the
required reasoning tier;
- then read
request_id from <out>.bakereq.json and write <out>.bakestatus.json carrying the status, a
bounded raw mcp_output, AND that request_id VERBATIM — mcp_output so the core's HARD-VETO can scan it,
and request_id because pickup_image.py --out-existing --request-id fail-closes the bake if the status
request_id is missing or mismatched (it must be written on BOTH ok and fail). Live-verified pitfall:
if the mcp__codex__codex reply TEXT is EMPTY (an image-only reply), serialize the RAW response object into
mcp_output — never write an empty string (pickup fail-closes a blank mcp_output on the empty-transcript
guard):
{"status":"ok","mcp_output":"<raw>","request_id":"<verbatim from bakereq>"} on success, or
{"status":"fail","failure_kind":"throttle","mcp_output":"<raw>","request_id":"<verbatim from bakereq>"} on a
429 / MODEL_CAPACITY_EXHAUSTED / overloaded error (otherwise
{"status":"fail","failure_kind":"other","mcp_output":"<raw>","mcp_error":"<raw>","request_id":"<verbatim from bakereq>"}).
- Stop when the orchestrator exits. (Without this wrapper, every bake polls to
--bake-timeout and returns
generation_failed with failure_kind="other" — fail-closed, not a hang, and never a false throttle.)
--bake-mode=exec is the legacy/CI non-image path and RAISES if it ever reaches a real bake — never use
it to produce a panel.
Hard do / don't
- Do author
comic.json first with comic-author; copy
examples/comic_m3_audit/comic.json as the shape.
- Do
--dry-run first; confirm every baked figure-panel carries ascii-tokenizable expected_literals
(else the fail-closed gate refuses to run).
- Don't run two bakes at once. The default
--bake-mode=agent writes each native PNG to its explicit
per-panel out_path and has no /tmp/aris_imagegen.lock (that code lock is GONE in agent mode — the
agent wrapper itself serializes the mcp__codex__codex calls). The real race surface: concurrent runs on the
same project/page collide on the per-panel .bakereq.json/.bakestatus.json sidecars + the shared
out_path — so keep ONE runner per project/page. (The global generated-images dir that could
cross-pollinate concurrent bakes is a hazard of the LEGACY --bake-mode=exec path ONLY, retired for real
bakes.)
- Don't resume after an image_gen throttle — launch a FRESH run for the remaining panels.
- Don't treat a
needs_human/flagged panel as shippable.
Protocols (governance contracts this skill honors)
reviewer-independence — reviewers get file paths only; the executor (Codex image_gen) ≠ the visual judges (Gemini + Codex vision).
acceptance-gate — the loop can DRIVE but can't ACQUIT: a KEEP needs the deterministic token-diff clean and Gemini+Codex visual pass; the generator family never self-acquits; Claude gives the final structural sign-off.
artifact-integrity — the model that bakes a frame does not judge its own faithfulness; numbers are verified, never originated.
review-tracing — every reviewer's prompt+response is saved (trace.jsonl) so each verdict is auditable.
reviewer-routing — Codex gpt-5.5 xhigh; Gemini auto-gemini-3; never downgrade the reviewer tier.
1---2name: comic-director3description: comic-director — the Audited Spiral + Release (Phase 2/3)4---56# comic-director — the Audited Spiral + Release (Phase 2/3)78The **middle + right of Figure 1**: take an authored `comic.json` (produced by the9[`comic-author`](../comic-author/SKILL.md) skill, Phase 1) and turn it into baked frames + a clickable10viewer, through the audited spiral. Per frame: render a deterministic content-SVG blueprint → bake a11pixel-art frame (agent `mcp__codex__codex` sidecar) → a 3-reviewer cross-model `panel_gate` → keep / retry → page assembly →12projection. The movie twin of `method-figure`'s render half. (Image-based today; video-based is next.)1314## Input contract — `comic.json` (authored upstream)15The contract boundary is **`comic.json` + the assets it references** (content-SVG blueprints, identity refs,16`ART_BIBLE.md`) — authored by [`comic-author`](../comic-author/SKILL.md). This skill is **render + verify**:17- It **never invents** a number or a cast — faithfulness is a deterministic token-diff over reviewers who18 never see the expected values; identity is judged against the ref the author supplied (cast-aware).19- It writes back **only** `image_path` / `active_attempt_id` / `wiki_node_id` (+ the wiki trace) on KEEP.20- **The generator family (Codex image_gen) can NOT self-acquit.** A KEEP needs the token-diff clean **and**21 the cross-model visual panel (Gemini + Codex) to pass. The calling agent (Claude) gives the final22 structural sign-off.2324Full IR spec: [`../../docs/comic-json.md`](../../docs/comic-json.md) /25[`../../schemas/comic.schema.json`](../../schemas/comic.schema.json).2627## Run it (`scripts/run_comic.py` — agent-driven bake, CLI gates)28A pure-stdlib **faithful port** of `packages/core/spiral_engine.js`'s movie branch. The cross-model **gates**29(Gemini + Codex vision) still shell the `gemini` / `codex` CLIs directly. The **BAKE** is performed by the30skill **agent** via the **`mcp__codex__codex`** MCP tool through `--bake-mode=agent` (the default): the core31emits a deterministic bake-request sidecar (prompt + absolute ref paths + exact out_path), the agent calls32`mcp__codex__codex` (`sandbox: workspace-write`, `model: gpt-5.5`, `config: {model_reasoning_effort: xhigh, include_image_gen_tool: true}`,33`cwd: <project>`), and the core verifies the explicit out_path. (`codex exec` is RETIRED for real bakes — it34forced a hand-drawn SVG/struct+zlib fallback; `--bake-mode=exec` RAISES if it ever reaches a real bake. See35`docs/spiral-runtime.md`.)36```bash37python3 skills/comic-director/scripts/run_comic.py \38 --project examples/comic_m3_audit --page P02_b08 --panels S12,S13,S14,S15 \39 [--bake-lang zh] [--finalize] [--dry-run] [--skip-assembly] \40 [--gemini-cmd "python3 cli/gemini_agy_shim.py"] [--gemini-family google]41```42- `--dry-run` validates + prints each frame's concrete bake prompt (no image_gen) — **run this first**; it's43 also how `comic-author` confirms Phase 1 is correct.44- `--finalize` rebuilds the single-file viewer (`packages/viewer/build_comic.py`) **only if the run is45 shippable** (nothing escalated/throttled/flagged-for-human; assembly accepted).46- `--gemini-cmd` (default: the legacy `gemini` CLI) — the argv prefix shelled for the google-family reviewer47 slot (the `--model auto-gemini-3 -p <prompt>` tail is appended). With the legacy CLI deprecated upstream48 (`IneligibleTierError`, 2026-07), pass `--gemini-cmd "python3 cli/gemini_agy_shim.py"` — the shipped shim49 translates the old interface to Antigravity's `agy` and **pins a Gemini model** (Antigravity also serves50 Claude/GPT-OSS; the slot must stay google-family for cross-model quorum honesty).51- `--gemini-family` (default `google`) — the provenance family recorded in the wiki for whatever52 `--gemini-cmd` actually routes to. It must stay **non-`openai`** (the visual quorum needs a second family);53 record honestly, never re-label.54- Needs the **`codex` CLI** + a **google-family reviewer** (legacy `gemini` CLI, or Antigravity via55 `--gemini-cmd` + the shim) on PATH + headless Chrome (to rasterize the content-SVG blueprint).56 Reuses `skills/method-figure/scripts/pickup_image.py`.5758It prints a JSON run-report (`kept`, `flagged_for_human`, `needs_human`, `shippable`, `throttled`,59`escalated`, `assembly`, `attempts_per_panel`). Engine semantics it ports (caps 4/panel + 6/run, the60deterministic verdict, anatomy single-vote veto, throttle → fresh-run): [`../../docs/spiral-runtime.md`](../../docs/spiral-runtime.md).6162## Bake seam contract (agent ↔ core) — `--bake-mode=agent`63The bake is a **synchronous sidecar handshake** (contract-v2 §1–§4). The core never returns/exits early on a64bake; only the bake **primitive** moved off `codex exec` onto `mcp__codex__codex`:65- **(A) core writes** `<out>.bakereq.json` =66 `{prompt_text, content_png, identity_ref, out_path, model:"gpt-5.5", config:{model_reasoning_effort:"xhigh", include_image_gen_tool:true}, sandbox:"workspace-write", cwd, created_at, min_bytes:500000, aspect, request_id:"<uuid4 hex>"}`67 (`prompt_text` ALREADY contains the absolute ref + out paths — the schema has **no** `-i`; `config` carries68 **both** `model_reasoning_effort:"xhigh"` AND `include_image_gen_tool:true` — without the latter codex won't fire69 its native image tool; `request_id` is a per-bake uuid4-hex nonce the core mints — see (D)+(E)), pre-deleting any70 stale `out_path` + status.71- **(B) the agent calls** `mcp__codex__codex` with EXACTLY those params.72- **(C) codex writes** a native PNG to `out_path`.73- **(D) the agent writes** `<out>.bakestatus.json` =74 `{status:"ok"|"fail", failure_kind:"throttle"|"other", mcp_output:"<bounded raw codex tool output>", mcp_error, request_id:"<verbatim from bakereq>"}`.75 **`mcp_output` is MANDATORY on BOTH ok and fail** — the core feeds this status file to `pickup --transcript`,76 and the HARD-VETO scans it for `import struct`/`zlib.compress`/`<svg>`/`matplotlib`/… markers. **An `ok`77 status with no raw output makes the HARD-VETO inert** (a hand-drawn struct+zlib/SVG that still emits a78 ≥500KB sig-valid PNG would slip through) — so always embed the bounded raw tool result.79 **`request_id` is EQUALLY MANDATORY on BOTH ok and fail** — copy it VERBATIM from `<out>.bakereq.json` into80 the bakestatus; `pickup_image.py --out-existing --request-id` **fail-closes the bake** if the status81 `request_id` is missing or ≠ the bakereq nonce (a stale/foreign status at the same path can't be silently honored).82- **(E) the core verifies** via `pickup_image.py --out-existing` on the EXPLICIT `out_path` (PNG sig + IHDR dims83 + size > 500000 + aspect band + `mtime >= created_at`), **HARD-VETOES** the markers in the transcript84 (= the status file's `mcp_output`), and (via `--request-id`) **fail-closes** if the status file's `request_id`85 is absent or ≠ the bakereq nonce — but all ONLY after it has read `status:"ok"`. Empty/timeout/any-non-ok =86 fail-closed (no pickup). NO fallback to `codex exec`/PIL/SVG; effort is ALWAYS `xhigh`; sandbox ALWAYS87 `workspace-write`.8889### Who runs `--bake-mode=agent` (the agent-wrapper SOP) — REQUIRED for the default mode to function90The comic-director **skill agent** is the bake fulfiller. Concretely:911. Launch the orchestrator in the **BACKGROUND**:92 `python3 skills/comic-director/scripts/run_comic.py --project … --page … --panels … --bake-mode agent`93 (it blocks on each bake by polling `<out>.bakestatus.json`).942. **Loop** until the orchestrator prints its final run-report JSON / exits:95 - watch the project's `panels/` dir for a new `*.bakereq.json`;96 - read it; call `mcp__codex__codex` with **exactly** its97 `{prompt: <prompt_text>, model:"gpt-5.5", config:{include_image_gen_tool:true, model_reasoning_effort:"xhigh"}, sandbox:"workspace-write", cwd:<cwd>}`98 (codex writes the native PNG to the sidecar's `out_path`). The `config` MUST carry **both**99 `include_image_gen_tool:true` AND `model_reasoning_effort:"xhigh"`: without `include_image_gen_tool` Codex will100 **not** fire its native image tool (it falls back to descriptive text / an SVG renderer), and `xhigh` is the101 required reasoning tier;102 - then read `request_id` from `<out>.bakereq.json` and write `<out>.bakestatus.json` carrying **the status, a103 bounded raw `mcp_output`, AND that `request_id` VERBATIM** — `mcp_output` so the core's HARD-VETO can scan it,104 and `request_id` because `pickup_image.py --out-existing --request-id` **fail-closes the bake if the status105 `request_id` is missing or mismatched** (it must be written on BOTH ok and fail). **Live-verified pitfall:**106 if the `mcp__codex__codex` reply TEXT is EMPTY (an image-only reply), serialize the RAW response object into107 `mcp_output` — never write an empty string (pickup fail-closes a blank `mcp_output` on the empty-transcript108 guard):109 `{"status":"ok","mcp_output":"<raw>","request_id":"<verbatim from bakereq>"}` on success, or110 `{"status":"fail","failure_kind":"throttle","mcp_output":"<raw>","request_id":"<verbatim from bakereq>"}` on a111 429 / `MODEL_CAPACITY_EXHAUSTED` / overloaded error (otherwise112 `{"status":"fail","failure_kind":"other","mcp_output":"<raw>","mcp_error":"<raw>","request_id":"<verbatim from bakereq>"}`).1133. Stop when the orchestrator exits. (Without this wrapper, every bake polls to `--bake-timeout` and returns114 `generation_failed` with `failure_kind="other"` — fail-closed, not a hang, and never a false `throttle`.)115> `--bake-mode=exec` is the legacy/CI **non-image** path and RAISES if it ever reaches a real bake — never use116> it to produce a panel.117118## Hard do / don't119- **Do** author `comic.json` first with [`comic-author`](../comic-author/SKILL.md); copy120 `examples/comic_m3_audit/comic.json` as the shape.121- **Do** `--dry-run` first; confirm every baked figure-panel carries ascii-tokenizable `expected_literals`122 (else the fail-closed gate refuses to run).123- **Don't** run two bakes at once. The default `--bake-mode=agent` writes each native PNG to its **explicit124 per-panel `out_path`** and has **no `/tmp/aris_imagegen.lock`** (that code lock is GONE in agent mode — the125 agent wrapper itself serializes the `mcp__codex__codex` calls). The real race surface: concurrent runs on the126 **same project/page** collide on the per-panel `.bakereq.json`/`.bakestatus.json` sidecars + the shared127 `out_path` — so keep **ONE runner per project/page**. (The global generated-images dir that could128 cross-pollinate concurrent bakes is a hazard of the **LEGACY `--bake-mode=exec` path ONLY**, retired for real129 bakes.)130- **Don't** resume after an image_gen throttle — launch a FRESH run for the remaining panels.131- **Don't** treat a `needs_human`/flagged panel as shippable.132133## Protocols (governance contracts this skill honors)134- [`reviewer-independence`](../../protocols/reviewer-independence.md) — reviewers get file paths only; the executor (Codex image_gen) ≠ the visual judges (Gemini + Codex vision).135- [`acceptance-gate`](../../protocols/acceptance-gate.md) — the loop can DRIVE but can't ACQUIT: a KEEP needs the deterministic token-diff clean **and** Gemini+Codex visual pass; the generator family never self-acquits; Claude gives the final structural sign-off.136- [`artifact-integrity`](../../protocols/artifact-integrity.md) — the model that bakes a frame does not judge its own faithfulness; numbers are *verified*, never originated.137- [`review-tracing`](../../protocols/review-tracing.md) — every reviewer's prompt+response is saved (`trace.jsonl`) so each verdict is auditable.138- [`reviewer-routing`](../../protocols/reviewer-routing.md) — Codex `gpt-5.5` `xhigh`; Gemini `auto-gemini-3`; never downgrade the reviewer tier.