From ai-research-os
Generates slide decks (Marp), charts (matplotlib), Obsidian Canvas, or social content briefs from wiki pages. Use when asked to render research into visual or written formats.
How this skill is triggered — by the user, by Claude, or both
Slash command
/ai-research-os:research-renderThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Wiki pages are the substrate. This skill turns them into **multi-form answers** — slide decks, charts, canvases, social briefs — and files those outputs back into `wiki/renders/<format>/<slug>.{md,png,canvas}` so they compound just like any other wiki artifact.
Wiki pages are the substrate. This skill turns them into multi-form answers — slide decks, charts, canvases, social briefs — and files those outputs back into wiki/renders/<format>/<slug>.{md,png,canvas} so they compound just like any other wiki artifact.
Four formats are supported:
.py script for reproducibility).canvas JSON)A run can produce several formats at once — the user picks one or many up front (Step 1), and each chosen format is rendered independently.
A fifth notional format ("table") was considered but rejected: tables are best embedded inside comparison wiki pages, which /research already produces via its comparison_writer. If you want a table, ask for a comparison.
You need:
marp | chart | canvas | brief. Resolve as follows:
marp, "chart this" → chart, "build a canvas" → canvas, "extract a post idea / write a brief" → brief), use it without asking.AskUserQuestion with multiSelect: true offering the four formats, so the user can pick one or render several at once. Capture the result as selected_formats (a list).<research_dir>/wiki/. Most renders draw from a single page; canvases, decks, and briefs often draw from multiple (overview + synthesis + a few entity/concept pages).brief, the prompt also carries the body structure the user wants (the sections to cover) — the brief writer follows it for the body (see Format details → Brief).Locate research_dir the same way /research (query mode) and /research-lint do.
Steps 2–4 run once per selected format. When several formats are chosen, run the idempotency checks per format and spawn the format writers in parallel (Step 4).
Slug:
Output paths by format:
| Format | Path |
|---|---|
marp | <research_dir>/wiki/renders/marp/<slug>.md |
chart | <research_dir>/wiki/renders/charts/<slug>.png (+ <slug>.py next to it) |
canvas | <research_dir>/wiki/renders/canvases/<slug>.canvas |
brief | <research_dir>/wiki/renders/briefs/<slug>.md |
Create the format directory if it doesn't exist:
mkdir -p "<research_dir>/wiki/renders/<format>"
Run this check for each selected format (each has its own output path). If the output already exists:
.canvas.meta.yaml sidecar for canvas) or companion .py (for chart).prompt and sources to the new run.AskUserQuestion whether to overwrite, suffix-with-timestamp, or skip.Each format has a dedicated subagent that knows its output shape. Pass:
formatsource_pages — absolute pathsresearch_topic, input_summary (from index.yaml)promptoutput_pathresearch_dirplatform — (brief only) the inferred platform, or generic| Format | Agent file |
|---|---|
marp | agents/marp_writer.md |
chart | agents/chart_writer.md |
canvas | agents/canvas_writer.md |
brief | agents/brief_writer.md |
Each subagent reads its source pages (these are short — entity/concept/comparison/source wiki pages, not raw files), produces the render, and returns a JSON summary on stdout. The orchestrator never reads the source pages itself.
When selected_formats has more than one entry, spawn all the chosen format writers in parallel — one Agent call per format in a single message. They're independent and write to different paths.
For chart only: after the writer subagent saves the .py script, the orchestrator runs it via uv run --script to produce the .png (the script carries a PEP 723 header declaring matplotlib, so it self-bootstraps). The script is the source of truth; the PNG is regenerable. If execution fails, the script stays on disk and the failure is surfaced — the user can fix it and re-run.
For brief only — handle the needs_guidance return. The brief writer is capped at ~1000 words. If covering the prompt's sections faithfully would exceed that, it writes nothing and returns {"action": "needs_guidance", "estimated_words": N, "reason": "...", "options": [...]}. When you get this, do not force the brief — surface it to the user via AskUserQuestion, using the writer's reason as context and its options as the choices (e.g., drop a section, split into two briefs, headline-level only, or raise the cap). Then re-spawn the brief writer with the tightened prompt (or the agreed higher cap). Don't run Steps 5–6 for a brief that returned needs_guidance and wasn't re-rendered.
Renders compound. After all selected formats are written, regenerate index.md once so the new renders appear in the navigation:
uv run --script ${CLAUDE_PLUGIN_ROOT:-.claude}/skills/research/scripts/build_index_md.py --research-dir "<research_dir>"
index.yaml does not need to be rebuilt — renders aren't sources, they don't have entries in the sources: array. The total_wiki_pages count is computed live by build_index_md.py from the wiki tree.
One entry per format rendered (share the date when several ran together):
## [YYYY-MM-DD] render | <format> | <slug>
- format: <marp|chart|canvas|brief>
- output: wiki/renders/<format>/<slug>.<ext>
- sources: <count> wiki page(s) — <comma-separated relpaths>
- prompt: "<verbatim prompt, truncated to 200 chars>"
Tell the user:
marp --watch <file> from the CLI.py is alongside for editing.md in Obsidian; copy the body into a post draft, or use it as the idea seed for whatever content workflow you useprompt (so they can compare against future renders)Marp is a markdown-based slide format. The output file has a YAML frontmatter block configuring the deck, then --- separators between slides:
---
marp: true
theme: default
paginate: true
backgroundColor: white
sources: [<wiki page paths>]
prompt: "<verbatim>"
created: <ISO-8601>
---
# Title slide
Content
---
## Second slide
- bullet
- bullet
---
...
The Marp Obsidian plugin renders this in-vault. Length: 5–15 slides typical; cap at 25 unless explicitly asked. Each slide ≤ 40 words of body text.
Charts are matplotlib outputs. The writer subagent produces a .py script that:
matplotlib.use("Agg") for headless, then import matplotlib.pyplot as plt)sys.argv[1] (so the orchestrator can wire output paths cleanly)Companion files:
wiki/renders/charts/<slug>.py # the script (source of truth, editable)
wiki/renders/charts/<slug>.png # the rendered chart (regenerable)
The script must include a frontmatter-equivalent comment block at the top:
# -- render metadata --
# format: chart
# sources: <comma-separated wiki page paths>
# prompt: "<verbatim>"
# created: <ISO-8601>
# --
Run it via:
uv run --script "<research_dir>/wiki/renders/charts/<slug>.py" "<research_dir>/wiki/renders/charts/<slug>.png"
Obsidian Canvas files are JSON with a fixed schema (see Obsidian docs). The writer subagent produces a .canvas file with:
wiki/renders/canvases/<slug>.canvas.meta.yaml for the same sources / prompt / created infoUse Canvas for visual argument maps, entity-relationship views, and "how these concepts connect" overviews — anything where spatial layout adds meaning.
A brief is a copy-ready social content seed composed from wiki pages — the kind of "executive summary for a post" that a human can paste into a draft. It is prose, never code, and it follows a fixed three-part spine. The body is the only flexible part — the user's prompt dictates its sections.
---
type: brief
format: brief
platform: <linkedin | substack-note | x-thread | reddit | generic>
sources:
- <source_page_relpath_1>
- <source_page_relpath_2>
prompt: "<verbatim prompt>"
created: <ISO-8601 now>
---
# <Working title / hook line>
## Opening — problem → solution
<Problem told as a short story, then the solution and the transformation it brings.
Weaves in the 6 W's: why, what, how, who, where, when.>
## <Body — sections driven by the prompt>
<Whatever the user asked the body to cover.>
## Open questions
1. <highest-signal open question>
2. <second>
3. <third>
---
> Grounding: [[wiki/...]] citations + a one-line `> Synthesis:` note.
The spine, in order:
wiki/open-questions.md if it sharpens them, but never just copy that file wholesale.Conventions:
> Grounding: line wikilinking the source pages, plus a > Synthesis: line (meta-judgment + what new source would extend the idea).platform (LinkedIn ≈ 200–400 words, Substack note shorter, X thread = punchier beats, Reddit = plainer); default generic ≈ the executive-summary shape. The brief is a seed — leave true platform-final shaping to the user.wiki/renders/. Renders are wiki artifacts; they compound. Never write to working-dir or anywhere outside the research dir.wiki/sources/, wiki/entities/, wiki/concepts/, wiki/comparisons/, wiki/synthesis.md, wiki/overview.md. They do NOT read raw/ files. If a render needs a quote that's only in raw, the user should first promote that quote into a source page..py is mandatory. A PNG without script is not allowed — it forecloses future edits.pyproject.toml. Marp viewing is the user's responsibility (Obsidian plugin or CLI). No additional installs.agents/marp_writer.md — produces a .md Marp deckagents/chart_writer.md — produces a .py matplotlib script (the orchestrator runs it to make the PNG)agents/canvas_writer.md — produces a .canvas JSON file plus a .meta.yaml sidecaragents/brief_writer.md — produces a .md social content brief (problem→solution opening with the 6 W's, prompt-driven body, 3 high-signal open questions)npx claudepluginhub iusztinpaul/ai-research-os-workshop --plugin ai-research-osBuild research views for Obsidian literature notes: Canvas maps, Bases tables, Dataview notes, paper maps, and literature dashboards.
Creates scientific documents and diagrams using markdown with embedded Mermaid. Default format for version-controlled documentation, enabling clean diffs and no build steps.
Generates self-contained HTML pages for technical diagrams, architecture reviews, diff reviews, plans, and comparisons. Renders complex tables as styled HTML instead of ASCII.