Skip to content
Agentmart
Submit a tool

decision-canvas

Present batched user decisions as an interactive atrium canvas with inline grounding — code snippets with real line numbers, click-to-open file links, and…

Skill by thunter009

IndexedMIT
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/thunter009-decision-canvas

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Present batched user decisions as an interactive atrium canvas with inline grounding — code snippets with real line numbers, click-to-open file links, and (for data questions) charts built from live prod data. Use when ATRIUM=1 AND a decision set exceeds what AskUserQuestion handles well: (1) More than 4 questions, or options that overflow 4-choice limits (2) Decisions the user can't judge without inspecting evidence (code, diffs, migrations, configs) (3) Data questions — "which threshold", "is this distribution acceptable", "which cohort" — where the user needs to SEE the data to decide (4) Audit/review/plan follow-ups with per-item verdicts (open questions, finding triage, bead grooming verdicts) Keep AskUserQuestion for quick forks: ≤4 questions, ≤4 options each, no evidence inspection needed. Outside atrium (no ATRIUM=1), always use AskUserQuestion.

Use cases: design, data, security

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found
Runs as
Python package

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/decision-canvas

What's inside

Skill name
decision-canvas
Description
Present batched user decisions as an interactive atrium canvas with inline grounding — code snippets with real line numbers, click-to-open file links, and (for data questions) charts built from live prod data. Use when ATRIUM=1 AND a decision set exceeds what AskUserQuestion handles well: (1) More than 4 questions, or options that overflow 4-choice limits (2) Decisions the user can't judge without inspecting evidence (code, diffs, migrations, configs) (3) Data questions — "which threshold", "is this distribution acceptable", "which cohort" — where the user needs to SEE the data to decide (4) Audit/review/plan follow-ups with per-item verdicts (open questions, finding triage, bead grooming verdicts) Keep AskUserQuestion for quick forks: ≤4 questions, ≤4 options each, no evidence inspection needed. Outside atrium (no ATRIUM=1), always use AskUserQuestion.
License
MIT
Instructions
240 lines in SKILL.md · view on GitHub

Before installing

  • Ships executable scripts

Files (3)

  • SKILL.md
  • build_question_patches.py · scripts/
  • decision_index.py · scripts/

SKILL.md (first lines)

# Decision Canvas

Turn a batch of decisions into an atrium canvas the user fills in and sends back: one card per question, recommendation pre-selected, evidence embedded in-line so the user never has to leave the canvas (or trust your summary) to decide.

## The contract

Every canvas MUST have:

1. **One card per question** — title, 1-line description, short context `Text`, and a `Radio` bound to a unique state key. Pre-select your recommendation in `state` and mark it `(recommended)` in the label.
2. **A per-question notes `Textarea` on every card** — always, bound to `/{qid}_notes`, so the user can qualify *that one* decision inline ("yes but only for prod", "recommended is wrong because…") without dumping everything into the global box. The generator adds this automatically for each question; `{qid}_notes` joins the reply schema. Override the placeholder per question with `notes_placeholder`.
3. **Grounding per card** — a `Collapsible` ("Grounding — evidence", `defaultOpen: false`) holding the evidence: `Code` snippets (with `filePath` + `startLine` so gutters are honest), `atrium://commands/file.open` link buttons, muted `Text` for facts, and charts for data questions (see below).
4. **A global notes `Textarea`** — bound to `/notes`, for cross-cutting overrides that aren't tied to a single question.
5. **A submit `Button`** — `send_to_agent` with `{"payload": {"$state": ""}}`. Without it the user cannot reply. Set `--send-framing` at note-create time so the payload arrives labeled.
6. **A Resolution card — always, once the reply comes back.** The canvas is not complete until the session that receives the reply patches a `Resolution` card in (chosen answer + outcome per question). This is mandatory, not a nicety, and it holds **even when the canvas was passed back to a calling agent session** — whichever session parses the reply owns the resolution patch (see "After the reply"). A canvas with answers but no Resolution card is an unfinished decision record. The card goes **at the top**, directly under the heading and intro. On a `--tabs` canvas it fills the Resolution tab instead, and the tabs open on it. The heading gains a `✅`, and the submit button stops asking for a send. `--resolution --canvas` does all three.

State keys ARE the reply schema: name them `q1_<topic>`, `q2_<topic>`, … so the returned JSON parses on sight.

**Use `--tabs`.** A stack of cards with evidence blocks is a long scroll in a half-width pane. `--tabs` puts each question card in its own tab, plus a `Resolution` tab that waits for the reply. Global notes and the Send button stay below the tabs, so Send is always visible. A tab label defaults to the qid (`q3_ci_cache` → `3 ci cache`); set `tab` per question for a better one or two words. The Tabs element uses `defaultValue`, not a bound `value`: a bound tab position would land in the reply payload.

## Naming & provenance (so canvases are findable later)

Decision canvases accumulate; an ad-hoc title ("Audit: open questions") is unfindable in a month. Every canvas MUST be created with:

- **Title**: `Decisions: <topic> (<YYYY-MM-DD>)` — e.g. `Decisions: repo-audit open questions (2026-07-02)`. The `Decisions:` prefix makes the whole family one search away; the date disambiguates recurring topics (grooming, sprint reviews).
- **Folder**: `--folder decisions` — groups all decision canvases under one node in the notes tree instead of scattering them through the workspace root.
- **Tags**: `--tags decision,<topic-slug>,<tracker-ids>` — include the bead/PR/task IDs the decisions concern (e.g. `decision,repo-audit,bd-cor34`). Tags are searchable; tracker IDs make the canvas discoverable from the ticket side.
- **Provenance footer**: set `provenance` in the manifest (rendered as a muted footer line) with the calling pane (id + name), the session id, the driving artifact, and the date — e.g. `pane 29724c0c "Canvas subtab link" · session e93271… · source: repo-audit report · 2026-07-02`. `meta.json` records which pane authored the note, but panes die with sessions; the footer is the durable tie. Name the pane, not just the id — ids are unreadable in a room full of canvases.

## Where the canvas opens — a sub-tab of the calling pane

A canvas with no visible link to the session that asked for it is a puzzle. With several agents running, the user cannot tell which pane wants which answer. Open the canvas as a sub-tab of your own pane. The tab group is the link.

**Verified atrium 0.347.0, 2026-10-20.** `note open "$NOTE_ID" --split "$ATRIUM_PANE_ID" --direction subtab --json` loads the note into a new notepad grouped under the caller. `workspace.json` shows `subTabGroups.<caller>.order` = `[caller, notepad]` and the notepad's `currentNoteId` = the note. The canvas renders, and the tab label shows the note title. The 0.330.0 limits are gone: `note open` used to ignore target panes, and an empty notepad subtab could not load a note.

The sub-tab opens behind your chat, because the group's visible tab stays on the caller. Tell the user in chat that the canvas is in the sub-tab next to this chat. Do not pass `--focus`: it switches the user's active room and takes keyboard focus from whatever they type.

Fallback: when the split fails (no app UI, caller pane not in a room), run plain `note open "$NOTE_ID" --json`. It opens the canvas as a sibling in your room. When that fails too, the note is still durable on disk: give the user its id.

**Rename the notepad to the exact note title.** `pane list` reports an unrenamed notepad as `Notepad`. `atrium-workspace-sweep` keeps a session-less notepad out of its close list only when the pane name contains `Decisions` (`hold_empty_reason` in `skills/atrium-workspace-sweep/scripts/classify.py`). Use the title itself, never a variant. A renamed notepad can later copy its pane name into the note title and set `titleManual: true` (bd-k5tm). With the exact title, that copy changes nothing.

Do not put status or the caller in any pane name. The old `● Decisions ← <caller>` names replaced the topic and date in 72 note titles (all 72 carry `titleManual: true`, measured 2026-10-13). Status lives in the canvas: `--resolution` puts the Resolution card at the top and prefixes the heading with `✅`. `decision_index.py` reads status from the canvas, not from a name.

**Your own pane.** If it still carries a launcher default, rename it to a 10–20 char work description before you open the canvas. The provenance footer names it.

**Close the loop after acting — always**: once decisions come back and you've executed them, you MUST patch a `Resolution` card into the canvas (chosen answer per question + what you did: tracker IDs created/updated, commits, verification results). The canvas then reads as a complete decision record. This step is non-optional. It fires even when another session created the canvas: the session holding the reply patches the Resolution before its turn ends.

## Workflow

```bash
# 1. Build a manifest (see schema below) describing questions + grounding.
#    Snippets/queries are resolved AT BUILD TIME from real files/data — never paste code by hand.
# 2. Generate skeleton + patches:
python3 <skill-dir>/scripts/build_question_patches.py manifest.json --tabs \
  --skeleton-out /tmp/spec.json --patches-out /tmp/patches.jsonl

# 3. Name YOUR pane before the canvas opens — the provenance footer names it.
#    Substitute a real 10-20 char description for the placeholder; no script
#    can invent one for you.
CALLER=$("$ATRIUM_CLI_PATH" pane list --json | jq -r --arg me "$ATRIUM_PANE_ID" \
  '.[] | select(.id == $me) | .name')
case "$CALLER" in
  ""|claude-code|codex|gemini|cursor-agent)
    "$ATRIUM_CLI_PATH" pane rename "$ATRIUM_PANE_ID" --name "<10-20 char work desc>" ;;
esac

# 4. Create the note, then open it (separately — `note open --json` returns the
#    pane id, `note new --open` does not). Title/folder/tags per
#    "Naming & provenance" below — non-optional.
NOTE_TITLE="Decisions: <topic> ($(date +%F))"
NOTE_ID=$("$ATRIUM_CLI_PATH" note new --type canvas \
  --title "$NOTE_TITLE" \
  --folder decisions --tags "decision,<topic-slug>,<tracker-ids>" \
  --source agent \
  --send-framing "Decisions (canvas '{noteTitle}', note {noteId}):\n\n{payload}" \
  --spec /tmp/spec.json --json | jq -r '.meta.id // .id')
# Sub-tab of this pane; a sibling in this room when the split fails.
NOTE_PANE=$("$ATRIUM_CLI_PATH" note open "$NOTE_ID" --split "$ATRIUM_PANE_ID" \
  --direction subtab --json | jq -r '.paneId // empty')
[ -n "$NOTE_PANE" ] || NOTE_PANE=$("$ATRIUM_CLI_PATH" note open "$NOTE_ID" --json | jq -r '.paneId // empty')
# The exact title, never a variant (see "Where the canvas opens").
[ -n "$NOTE_PANE" ] && "$ATRIUM_CLI_PATH" pane rename "$NOTE_PANE" --name "$NOTE_TITLE"
"$ATRIUM_CLI_PATH" note canvas-patch "$NOTE_ID" < /tmp/patches.jsonl

# 5. End your turn, and say the canvas is in the sub-tab next to this chat. The
#    reply arrives as a framed user turn when they hit Send. Parse the state
#    JSON, act on each decision, confirm what you did.

# 6. ALWAYS close the loop: after acting, patch a Resolution card into the SAME note.
#    Write a resolution manifest {title?, summary?, date, items:[{qid, decision,
#    outcome}]} (see "After the reply"). Read the live canvas first: the script
#    needs it to put the card under the heading, mark the heading ✅, relabel the
#    submit button, and turn option values into the labels the user clicked.
"$ATRIUM_CLI_PATH" note read "$NOTE_ID" > /tmp/canvas.json
python3 <skill-dir>/scripts/build_question_patches.py resolution.json \
  --resolution --canvas /tmp/canvas.json --patches-out /tmp/resolution.jsonl
"$ATRIUM_CLI_PATH" note canvas-patch "$NOTE_ID" < /tmp/resolution.jsonl
# Read it back. The command is `note read`; there is no `note show`. Expect 1.
# 0 = the patch missed. 2+ = duplicate references: re-run --resolution against
# a fresh read (see "Amend a Resolution"). Other `resolution*` keys are addenda.
"$ATRIUM_CLI_PATH" note read "$NOTE_ID" \
  | jq '.elements[.root].children | map(select(. == "resolutionCard")) | length'
# No pane rename: the ✅ heading and the top card carry the status.
#    $NOTE_ID is the note the canvas already lives in. Cross-session, the reply's
#    framing carries {noteId} — read it from there (fallback: note list --folder).
```

### Adding questions to an open canvas

A new question can join a canvas the user is already filling in. Do not open a second canvas. Do not run the fresh build again: its counter keys restart at `txt_1`, and its `notesCard`/`submitRow` ops land on the live ones. `add` replaces silently, so the first question loses its evidence.

```bash
# more.json uses the manifest schema below; only `questions` is read.
"$ATRIUM_CLI_PATH" note read "$NOTE_ID" > /tmp/canvas.json
python3 <skill-dir>/scripts/build_question_patches.py more.json \
  --append --canvas /tmp/canvas.json --patches-out /tmp/more.jsonl \
  && "$ATRIUM_CLI_PATH" note canvas-patch "$NOTE_ID" < /tmp/more.jsonl
```

`--append` reads the live canvas and emits ops for the new cards only. It inserts them before `notesCard` (else `submitRow`). On a `--tabs` canvas each card becomes a new tab, ahead of the Resolution tab. It seeds their state keys, and numbers counter keys past the canvas's highest. It refuses a question id already on the canvas and any op that would overwrite a live element or state key, so the user's answers so far stay. Give the new question the next free id (`q5_<topic>`). Daemon down: pass the note's `note.canvas.json` as `--canvas`. Tell the user in chat that a card was added.

From thunter009/skills, MIT.

More from thunter009/skills

Signals

Stars
0
Forks
0
Open issues
0
Last commit (this folder)
Oct 9, 2026 (20 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from thunter009

Cite this page

decision-canvas skill. Agent Mart. https://agentmart.dev/skills/thunter009-decision-canvas (last updated 2026-10-24)