Install
$ agentstack add skill-wanshuiyin-aris-movie-director-method-figure ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
method-figure
Turn "draw our method figure" from a one-shot gamble into the same audited spiral the framework uses for comics: a blueprint is the source of truth, the image model bakes the look, a cross-model panel + a deterministic diff keep it honest, and the loop converges to a publication-grade figure that is reproducible (re-run the blueprint) and auditable (a trace of every round).
Two things are simultaneously true: (a) gpt-image-2 CAN render a clean Figure-1 with legible labels when conditioned on a labeled blueprint — do not assume it garbles text; (b) on a free prompt it DRIFTS (renames phases, invents nodes, garbles a token, leaves pasted-looking floating labels). The blueprint + blind-transcribe-then-hard-diff loop turns (a) into a reliable result and catches (b) every round.
system description ─▶ ① BLUEPRINT (JSON content-lock) ── validate_blueprint.py
▼
② CONDITION (white-bg labeled SVG → PNG) + identity sheet (real chibi, optional) ── render_condition.py --png
▼
③ BAKE — agent: mcp__codex__codex(prompt+abs ref paths+out_path, workspace-write, gpt-5.5, config{xhigh}) → gpt-image-2 native PNG ── pickup_image.py --out-existing (sig+size+dims, mtime-bound, HARD-VETO struct/zlib/PIL/SVG, fail-closed)
▼
④ PANEL — Gemini ‖ Codex BLIND-transcribe → content_diff.py (observed ⊖ blueprint) → Claude structural sign-off
▼
⑤ agent reads the diff + the panel blockers → re-bake re-asserting the locked labels
▼
converged? ─ no ─▶ ③ (bounded: max_rounds → escalate to human)
│ yes
▼
⑥ APPROVE → figure.png + blueprint.json + trace.jsonl
Constants
- GENERATOR = Codex
gpt-5.5,config: {model_reasoning_effort: xhigh, include_image_gen_tool: true}→ the nativeimage_generation
tool (gpt-image-2). CRITICAL: imagegen is produced ONLY via mcp__codex__codex (the agent tool), NOT codex exec. codex exec / over-specified / forbid-list prompts make Codex hand-draw a code fallback (struct+zlib PNG or SVG/matplotlib) — visually indistinguishable for trivial shapes, useless for a real method-figure. The working invocation is mcp__codex__codex with a dead-simple prompt + sandbox: "workspace-write" (it must WRITE the outpath) + model: "gpt-5.5" + config: {model_reasoning_effort: "xhigh", include_image_gen_tool: true} (the schema has NO top-level effort param; config{xhigh} shorthand below ALWAYS expands to both these keys — without include_image_gen_tool codex won't fire its native image tool, it falls back to descriptive text / an SVG renderer) + cwd: . Reference images are passed by absolute file path inside the prompt (the schema has NO -i); the output path is a deterministic abs path in the prompt. Pick it up with pickup_image.py --out-existing (verifies the EXPLICIT outpath: PNG sig + size + dims, mtime >= request.created_at) which HARD-VETOES struct/zlib/PIL/`/matplotlib markers in the agent transcript (fail-closed; there is **no** 'native sig wins' override). **Honesty caveat:** as of Jun 2026 native headless persistence is unreliable, so this fail-closed verifier — not any sandbox setting — is the first guard against a non-native bake. But the HARD-VETO is a **BEST-EFFORT denylist** against the *known* codex-exec hand-draw fallback (struct/zlib/PIL/ SVG/matplotlib markers), **NOT a complete security boundary** — a novel fallback that emits a sig-valid PNG without those markers can slip past it. The **load-bearing faithfulness gate is the cross-model blind-transcribe panel + the deterministic contentdiff`** (the pixels are what reviewers transcribe), with this denylist as a cheap upstream filter.
- PANEL (automated blind-transcribe) =
mcp__gemini-cli__ask-gemini(auto-gemini-3) +mcp__codex__codex
(gpt-5.5 xhigh) + the deterministic content_diff. Claude (this agent) is the post-pass STRUCTURAL sign-off, not a blind transcriber — the loop converges on Gemini-approve + Codex-no-veto + empty-diff, then Claude signs off.
- CROSS-MODEL ACQUITTAL — Codex is the generation family, so a Codex
approvecan only diagnose/veto,
never be the sole acquitter. ACCEPT requires Gemini approve + Claude structural approve + the hard-diff empty.
- MAX_ROUNDS = 4, then escalate to human with best-so-far + open blockers.
- LABEL_POLICY =
bakedonly in v0 — the image model renders ALL text; nothing is hand-pasted.
(hybrid/overlay — lock structure + vector-overlay the labels for paper zero-tolerance text — are on the v1 roadmap; do NOT use a vector overlay as an ad-hoc patch on a finished bake, it reads as pasted.)
- OUTPUT_DIR =
figures/method_figure//(figure.png, blueprint.json, condition.svg, trace.jsonl). - NATIVE-IMAGE FAIL-CLOSED — accept a bake ONLY if a real native PNG exists at the explicit out_path,
sha/size/dims check out and mtime >= request.created_at, and the agent transcript shows no struct/zlib/ PIL/`/matplotlib fallback (pickupimage.py --out-existing, HARD-VETO — a clean sig never overrides a fallback marker). This veto is a **BEST-EFFORT denylist** against the *known* codex-exec hand-draw fallback, **NOT a complete security boundary** (a novel marker-free fallback could evade it). The load-bearing faithfulness gate remains the **cross-model blind-transcribe panel + the deterministic contentdiff`**; the denylist is a cheap upstream filter that matters because native headless persistence is currently unreliable.
- SERIALIZE BAKES — never run two image generations at once. The default
--bake-mode=agentwrites each
native PNG to its explicit per-round out_path (no shared dir), so concurrent agent bakes still risk a request/status sidecar race — keep one runner per figure. (The global ~/.codex/generated_images dir + newest-after-marker pickup that could cross-pollinate concurrent bakes is a hazard of the LEGACY --bake-mode=exec path ONLY, which is retired for real bakes.)
Input contract / ARIS hand-off (who decides WHAT, who only renders)
This skill is pure render + verify. Ownership:
- Upstream owns the semantics — what to depict, the labels, the graph, the grouping, the headline
claim/number, the identity refs. method-figure does NOT choose content and must not invent a node, claim, number, or method structure (if one is missing it ESCALATES, it does not make it up).
- Step-0 is now DETERMINISTIC —
run_spiral.pycalls [scripts/compile_brief.py](scripts/compile_brief.py)
to map a method_figure_brief.json → a schema-valid blueprint.json + traceability.json, fail-closed (an object that can't trace to a brief field, or a missing claim/number/trait, is refused — not invented). It is no longer a manual LLM hop. Full field map + the guards: references/blueprint_authoring.md.
- method-figure owns validation → condition render → image bake → cross-model panel → diff → retry, and
has VETO power: it returns FAILED / Logic Drift rather than ship a figure whose pixels contradict the blueprint.
The default single input = a method_figure_brief.json (schemas/method_figure_brief.schema.json) — ONE ARIS-format file; the blueprint, the coordinates, and the identity wiring are all derived. The identity sheet is resolved from the brief's identity_refs[].path (no separate --identity to manage). Where the input comes from, in authority order:
- a
method_figure_brief.json— the canonical ARIS hand-off (whatpaper-planemits); auto-detected + compiled. · - an existing hand-tuned
blueprint.json— power-user override (--from-blueprint, used as-is). · - an
experiment-plan/paper-writemethod section / free-text — no brief yet: the agent first DRAFTS a
method_figure_brief.json from it (claims/numbers verbatim; anything missing → Refuse-and-Escalate), then compiles.
ARIS integration: the canonical producer is paper-plan — after its claims_matrix it emits the method_figure_brief (components, flows, phases, the headline claim/number, identity refs, forbidden_tokens). You feed that one file to run_spiral.py; Step-0 compiles it and the traceability is enforced by the compiler (an un-traceable object is a Refuse-and-Escalate, not a render). The identity sheet is created once upstream and locked; method-figure only reads it.
Fast path — one command (single input: a brief)
Feed ONE method_figure_brief.json; the whole loop is one command:
python3 scripts/run_spiral.py your_method_figure_brief.json --out-dir figures/method_figure/
# auto-detects a brief → Step-0 compile_brief.py → blueprint.json + traceability.json (deterministic, fail-closed)
# → validates → renders condition(+png) → [bake (agent: mcp__codex__codex --bake-mode=agent, workspace-write,
# gpt-5.5 config{xhigh} → gpt-image-2 native PNG via the .bakereq.json sidecar) → pickup_image.py --out-existing
# verify (fail-closed, HARD-VETO over the status file's mcp_output) → Gemini + Codex blind-transcribe → content_diff → blockers] × rounds
# → on PANEL-CLEAN writes figure.png + blueprint.json + traceability.json + trace.jsonl.
# input auto-detect: brief (components+flows) vs blueprint (version) vs ambiguous → fail-closed (--from-brief/--from-blueprint)
# --identity is OPTIONAL (resolved from the brief's identity_refs[0].path); --dry-run prints the round-1 bake
# prompt; --p0-only runs the zero-credit gate (validate+compile+render) then stops; --max-rounds N; --effort high|xhigh.
> Power-user / override: already have a hand-tuned blueprint? run_spiral.py blueprint.json --identity > sheet.png --out-dir … --from-blueprint runs the legacy path unchanged. A worked example brief lives at > [examples/method_figure/method_figure_brief.json](examples/methodfigure/methodfigure_brief.json). Long-running (each bake ~3-8 min) — run it in the background; watch trace.jsonl. It converges to PANEL-CLEAN — BOTH reviewers returned parseable JSON, Gemini approve AND Codex approve, the deterministic content_diff empty, core scores (incl. character_identity when an identity sheet is given) ≥ threshold, and no anomalies/blockers — then STOPS and hands to the calling agent (Claude) for the final structural sign-off (the generator family never self-acquits). The manual steps below are exactly what run_spiral.py automates (run them to debug one stage).
Who runs --bake-mode=agent (the agent-wrapper SOP) — REQUIRED for the default mode to function
The bake is a synchronous sidecar handshake and the skill agent is its fulfiller (without it, every bake polls to --bake-timeout and escalates with failure_kind="other" — fail-closed, not a hang, never a false throttle):
- Launch the orchestrator in the BACKGROUND:
python3 scripts/run_spiral.py your_brief.json --out-dir figures/method_figure/ --bake-mode agent.
- Loop until it prints PANEL-CLEAN / escalates / exits:
- watch
/for a new*.bakereq.json(the orchestrator writesround.png.bakereq.json); - read it; call
mcp__codex__codexwith exactly its
{prompt: , model:"gpt-5.5", config:{include_image_gen_tool:true, model_reasoning_effort:"xhigh"}, sandbox:"workspace-write", 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 gpt-image-2 tool (it falls back to a struct/zlib/SVG hand-draw), and xhigh is the required reasoning tier;
- then read
request_idfrom the*.bakereq.jsonand write.bakestatus.jsoncarrying **the status, a
bounded raw mcp_output, AND that request_id VERBATIM — mcp_output so the HARD-VETO can scan it (the core feeds this file to pickup --transcript; an ok status with no raw output makes the veto INERT), and request_id because pickup_image.py --out-existing --request-id fail-closes the bake if the status request_id is missing or mismatched** (write it on BOTH ok and fail): {"status":"ok","mcp_output":"","request_id":""}, or {"status":"fail","failure_kind":"throttle","mcp_output":"","request_id":""} on a 429 / MODEL_CAPACITY_EXHAUSTED / overloaded error (else {"status":"fail","failure_kind":"other","mcp_output":"","mcp_error":"","request_id":""}).
- The core proceeds to verify ONLY on
status:"ok", viapickup_image.py --out-existing(sig + dims + size >
500000 + mtime >= created_at, HARD-VETO over mcp_output, and --request-id fail-close if the status request_id is absent/mismatched). --bake-mode=exec is the legacy/CI non-image path and RAISES if it reaches a real bake.
Workflow (what run_spiral.py automates — or run by hand)
① Author the BLUEPRINT (content lock)
Write blueprint.json per schemas/blueprint.schema.json. The *_exact fields (label_exact, desc_exact, group/edge/callout *_exact, rail.label_exact) are the LOCKED text re-asserted verbatim every round; expected_tokens[] are what the panel must blind-transcribe and the diff checks. Then:
python3 scripts/validate_blueprint.py blueprint.json # jsonschema (if installed) + unique ids · edges resolve · box/group/callout bounds · no dup labels
② Render the CONDITION
python3 scripts/render_condition.py blueprint.json --out condition.svg --png condition.png # white-bg labeled layout → rasterized
Prepare identity_sheet.png from the project's REAL characters if the figure has any (never invent robots). The condition PNG + the identity sheet are the two image references.
③ BAKE (round N) — agent seam
Call mcp__codex__codex (sandbox workspace-write — it must WRITE the outpath; model: gpt-5.5, config: {model_reasoning_effort: xhigh, include_image_gen_tool: true}, cwd: ) with the prompt from references/prompt_templates.md §A — it RE-ASSERTS every *_exact label + the round-N blockers + the carried positive_invariants, with condition.png + identity_sheet.png referenced by absolute path inside the prompt (the schema has no -i) and the exact outpath to save the native PNG. Write the bake status to round.png.bakestatus.json carrying the raw mcp_output (so the HARD-VETO can scan it) AND the request_id copied VERBATIM from round.png.bakereq.json (pickup --request-id fail-closes if it's missing/mismatched), then verify the explicit out_path (no marker/glob):
python3 scripts/pickup_image.py --out-existing --out figures/method_figure//round.png --min-bytes 500000 --aspect --created-at --request-id .png.bakereq.json> --transcript figures/method_figure//round.png.bakestatus.json
④ PANEL — blind transcribe, then hard diff
Ask each reviewer (references/prompt_templates.md §B) for the STRICT JSON of references/reviewer_protocol.md — they transcribe observed_tokens / observed_edges / identity_audit and an anomalies list (the Negative-Space Audit), NOT shown the expected labels. Save as round.{cc,gemini,codex}.json, then:
python3 scripts/content_diff.py blueprint.json round.cc.json round.gemini.json round.codex.json
# → missing_tokens / unaccounted_tokens / anomalies ; empty == content-accurate
⑤ Decide (stop rule) — the agent consolidates
Read the diff report + the three reviewers' blockers. The executing agent itself merges blockers only (ignore nice_to_have — chasing polish makes it oscillate), carries the unio
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: wanshuiyin
- Source: wanshuiyin/ARIS-Movie-Director
- License: MIT
- Homepage: https://wanshuiyin.github.io/ARIS-Movie-Director/comic/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.