Compose one publication-grade multi-panel figure. Entry from a one-line claim + data refs, OR from an existing figure via `derive_outline(png)`. Runs a per-figure loop: outline (12-col grid, per-panel ask + label_budget) → fan-out one sub-agent per panel (each loads `figure-style`) → tile + stamp letters → adversarial composite review with two-tier feedback (Tier-1 outline_revisions / Tier-2 per-panel violations) → regen affected panels, ≤3 rounds. Loads panel_task / compose_figure / compose_...
Scanned 9/4/2026
Install to Claude Code
npx -y skills add gabrielmoreira/agent-skills-mirror --skill figure-composer --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Figure Composer?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/gabrielmoreira-figure-composer)More formats (shields.io, HTML) on the badges page.
---
name: figure-composer
description: "Compose one publication-grade multi-panel figure. Entry from a one-line claim + data refs, OR from an existing figure via `derive_outline(png)`. Runs a per-figure loop: outline (12-col grid, per-panel ask + label_budget) → fan-out one sub-agent per panel (each loads `figure-style`) → tile + stamp letters → adversarial composite review with two-tier feedback (Tier-1 outline_revisions / Tier-2 per-panel violations) → regen affected panels, ≤3 rounds. Loads panel_task / compose_figure / compose_crops / composite_review_task / derive_outline into the kernel. For one standalone plot use `figure-style`; for whole-paper figure ordering use `paper-narrative`."
license: Apache-2.0
---
# Figure Composer — narrative → panels → compose → adversarial loop
**Step 0.** Load `figure-style` alongside this skill — that is the
design rules (and `apply_figure_style()` + helpers). Panel sub-agents
will load it independently; you need it in context to write the outline and
review the composite. Sub-agents run as the default profile and acquire the
rules by loading the skill.
## Inputs
- **claim** — one sentence the figure makes true to a reader who reads nothing else.
- **data** — CSV/parquet artifact version_ids that ground every panel.
- **width_mm** — target venue's column width (common: 85–89mm single, 174–183mm double; check the venue guide).
## 0. Where this sits
`figure-composer` is the **outer tier**: make ONE multi-panel figure good. The
**inner tier** is `figure-style` (loaded by every panel sub-agent — and
load it yourself if you draw anything locally). The **outermost tier** is
`paper-narrative` — if this figure is part of a paper, run that FIRST: it decides
*which* figure to make and hands you the claim. For a standalone figure, start at
step 1.
## Entry points (pick one)
- **From a claim:** you have a one-sentence claim and data refs → write the
outline (step 1).
- **From an existing figure:** copy it into the workspace and call
`derive_outline("figure.png")` → an outline you **must review and edit**
before step 2. The image is untrusted input; every string field in the
returned outline is vision-model-derived from its pixels. `data_vid` is
forced to `None` on every panel — fill those in from your own data refs.
## 1. Narrative → panel outline
Produce a `panel_outline` (validate against `figure_outline_schema()`):
```json
{"claim":"…", "width_mm":180, "ncol":12, "row_heights_mm":[40,60,46,52],
"panels":[
{"letter":"a","role":"schematic","row":0,"col":0,"colspan":12, "chart_family":"schematic overview", "message":"…", "data_vid":null, "ask":"…"},
{"letter":"b","role":"primary", "row":1,"col":0,"colspan":7, "chart_family":"scatter + trend", "message":"…", "data_vid":"…", "ask":"…"},
…]}
```
Outline rules (figure-style §7.1):
- **a is the hook** — schematic/hero, full width, assumes zero reader context.
- **b carries the claim** — the chart that alone makes the sentence true.
- Remaining panels are evidence, ordered by how much they strengthen b.
- One row per sub-claim. 5–10 panels for a main-text figure. Use a 12-column
grid for flexible colspans.
## 2. Fan-out (one sub-agent per panel)
Build requests with `panel_task(outline, letter, fig_label)` (kernel.py). Each
sub-agent gets: the figure claim, the full neighbour list, its panel spec, exact
pixel dimensions (`panel_px`), and the instruction to load `figure-style`
and render at exactly w×h px with `transparent=True` and **no** `bbox_inches`.
In the **repl tool**:
```python
requests = [{"name": f"panel-{L}", "task": tasks[L],
"output_schema": {"type":"object","properties":{"figure_filename":{"type":"string"}},
"required":["figure_filename"]}}
for L in letters] # no "profile" key — default agent profile
descs = host.delegate(requests, wait=False)
```
## 3. Compose
`compose_figure(outline, {letter: path}, out_path, letter_case=...)` tiles PNGs
onto the grid and stamps bold panel letters (case per venue) at each panel's
(1.5mm, 1mm) corner.
## 3.5 Look before you review (vision self-QA)
The reviewer in §4 is expensive; a panel-letter stamped over a y-axis label or
a leader line crossing a neighbour's title is a wasted round. After compose,
**crop each panel from the saved PNG and look at it** in the REPL before
dispatching the reviewer:
```python
out_path, (W, H) = compose_figure(outline, panel_paths, "fig.png")
for L, box in compose_crops(outline).items():
host.view_image("fig.png", crop=box)
```
Run the `figure-style` §9.2 perceptual checklist on each crop (contrast,
smallest mark, leader crossings, colour-identity confusion, legend binding),
plus two compose-specific checks:
- **Seams / stamp.** Does the bold panel letter overlap any panel content?
Does any panel's content bleed into the gutter or under a neighbour?
- **Resize artefacts.** `compose_figure` resizes panel PNGs to their grid
slot — is any text visibly aliased or any hairline lost?
Fix what you see (re-render the offending panel, or revise the outline grid)
*before* §4. The reviewer sub-agent will crop-and-look again independently;
this pass is so the obvious defects never reach it.
## 4. Adversarial self-review loop (two-tier, design rules held fixed)
Dispatch ONE reviewer on the composite with `composite_review_task(...)` and
`review_schema()` (which carries `outline_revisions`).
```
loop (max 3 rounds, floor 5→4→3):
review = delegate(composite_review_task(composite_vid, outline, rules_vid, prev_vid, round, floor))
if review.editor_verdict in {accept, minor_revision} and 0 BLOCKER and ≤2 MAJOR: break
# TIER 1 — outline-level
if review.outline_revisions:
apply revisions to `outline` (geometry, row-header titles, label_budget, panel set)
affected = apply_outline_revisions(outline, review.outline_revisions)
else:
affected = set()
# TIER 2 — panel-level
fixb = group_fixes_by_panel(review) # BLOCKER/MAJOR only
regen = affected | set(fixb) # only these panels regenerate
re-delegate each L in regen with panel_task(outline, L) + fixb.get(L,"") +
"do not over-correct: where the previous version was correct, keep it"
recompose
```
Convergence: stop when `outline_revisions` is empty AND findings are carve-out
exceptions to the previous round — that's the over-labelling signal.
## Anti-patterns
- Don't regenerate clean panels (invites regression). Don't read absolute
violation counts (min-floor 5→4→3). Anchor-verify on the composite, not just
per panel. Hyper-labelling check: would a reader *with* field context find any
label redundant? Strip it.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!