comonteur — start here
A human edits this .blend live, at the same time as you. Everything below exists to keep
that safe. The scene is the source of truth. You mutate it. You never regenerate it.
Reach Blender through the official Blender MCP server's execute_python only. There is no
comonteur MCP tool — the library is the typed surface.
The one code shape
Every piece of Python you send starts like this:
try:
from bl_ext.user_default import comonteur as cmt
except ImportError: # dev checkouts that put addon/ on sys.path
import comonteur as cmt
with cmt.journal.batch("shot-03: title fade-up"):
scn = cmt.scene.new_scene("shot-03", fps=30, frame_range=(1, 90))
title = cmt.text.create(scn, "Ship faster", size=0.8)
cmt.anim.tween(title, "location", 1, frm=-0.4, to=0.0, start=1, dur=18, ease="power2.out")
The batch is not optional. journal.set() and anim.tween() raise outside one (journal.py:86,
anim.py:82). The batch writes the journal entries, bumps cmt_rev, gates the human-edit
detector so your own writes aren't misread as theirs, and produces one labelled undo step.
Label batches for a human reading an undo menu: "shot-03: title fade-up", not "batch 1".
Hard rules
Break one of these and you corrupt the project silently.
- Batches save themselves.
journal.batch() saves the .blend on exit once it's been
saved once already (Blender's own save_version backup is bumped first). Don't call
bpy.ops.wm.save_mainfile() yourself — that's a second save path outside the batch.
- Never regenerate. Mutate existing data, never delete-and-recreate it.
scene.new_scene() returns the existing scene when cmt_id already exists (scene.py:20)
— that's deliberate, don't route around it. Delete-and-recreate throws away every human
edit and breaks the journal's history.
- Untagged data is human data. No
cmt_origin means a human made it. Don't touch it
unless the current instruction names it. Check cmt.provenance.origin(id_block) before
touching anything you didn't create this session.
shared means the human touched it. cmt.provenance.claimed_paths(id_block) returns
every path a human has claimed since — anything whose live value no longer matches what you
last wrote there. Those paths are lost to you: mutate anything else, and never delete a
shared datablock. (Mechanism and limits: references/api.md.)
- Prefer
cmt.* over raw bpy. Raw bpy isn't forbidden, but it's untracked — it never
hits the journal, revert can't undo it, the human's ownership view won't show it. If you
must, keep it to reads, or route the write through cmt.journal.set(obj, "path", value).
- Prefer
mise run comonteur:<task> over raw shell, in a project that has them (check
mise tasks). A task is what the human can re-run and what pins the tools — a command in a
chat log is neither. Add a task instead of improvising a repeat. Ask permission, then run it
yourself — don't tell the human to open a terminal.
batch() does not nest (journal.py:61). One batch per logical unit of work. Not one
per write, not one for the whole session.
- Ask before sourcing a missing resource. Comonteur's own default-creatable primitives (a
new scene, a VSE strip, etc.) need no asking. For anything else missing — an asset, font,
footage, or another project to reuse from — don't silently search disk or the web for it.
Ask the human: search on disk (outside this project), search online asset providers, have
them provide it, or generate it if that's possible. Only search outside the project
directory once they've said which.
The loop
This is the loop for one shot. For work around it — starting a project, ingest, assembly,
delivery — read references/workflows.md first.
- Read the intent. The shot's
timeline.toml entry — title, notes, and where it
sits in the assembly. If notes names a rules:/blueprint: list, that list is a
checklist you'll close out in step 5, not background color — write it down.
- Read the scene before writing to it.
cmt.introspect.outline(scn),
describe(obj, "data"), animated_paths(scn). These are capped and truncated on purpose
(introspect.py:1). Never dump bpy.data or walk a whole tree — that burns the context
you need for the actual work.
- Build, in one batch. See
references/api.md. A named rules:/blueprint: value is
almost always a known combination of cmt.* calls — check references/vfx-recipes.md
before improvising one from scratch.
- Look at it.
cmt.preview.frames(scn, [1, 45, 90]) returns PNG paths under
.comonteur/review/. Read those images.
- Adjust, re-preview, report, stop. For a structural fix (wrong object, wrong path,
missing keyframe), re-check with
introspect.animated_paths/describe first — free, and
catches the same mistakes a render would. Only call preview.frames() again once you've
actually changed the scene, not to re-confirm what you already saw.
Gate: never call a shot done without reading a rendered frame of it. Blender fails
silently and visually — text off-canvas, a missing material, an F-curve on the wrong array
index. Code that ran without error proves nothing here.
Second gate: never call a shot done without closing out step 1's checklist. A render with
no errors proves the scene doesn't crash, not that it matches what the notes asked for — that
needs its own check, the same way code needs a test beside it, not just a clean run. For each
rules:/blueprint: token, confirm it's actually present: cross-check animated_paths(scn)/
describe() for the structural evidence that rule implies (split-tilt-cards → a
rotation_euler keyframe on the card root; scale-swap-transition → two overlapping
scale+alpha tweens; see references/vfx-recipes.md for what each named rule should have left
behind) and look for it in the rendered frame from step 4. A rule with no evidence in either
place was skipped, not implemented — go back to step 3, don't report the shot done. If a
snapshots/*.png HyperFrames reference still happens to exist from the import, treat it as a
bonus fidelity check, not the mechanism — most projects only ever had HyperFrames prepare the
storyboard and assets, never rendered, so no such reference exists.
Hit a real gap or bug in comonteur itself — a missing helper, a wrong doc, a broken mise
task — tell the human and offer to draft a GitHub issue at
https://github.com/davidB/comonteur/issues. Propose it, don't file it yourself.
Routing
| Read |
When |
references/workflows.md |
Starting a project, or deciding which workflow a directory is: order of work, ingest, rendering, delivery. Also covers delegating shots to a subagent when building more than a couple in one session. |
references/api.md |
Writing any scene content: scenes, text, animation, eases, components, preview, provenance. |
references/vfx-recipes.md |
A named look (spring-pop, underline draw, card tilt, scale-swap, typewriter, shatter, discrete text-state swaps, sine-wave idle loop, center-outward assembly, flash/marker word accents, directional entrances) or a blueprint: value from a storyboard — which cmt.* calls to combine, and how to install/reuse the GN VFX asset library. |
references/timeline.md |
Assembling or changing the edit: timeline.toml, anchors, reconcile, VSE strips, audio, transitions. |
references/hyperframes-import.md |
Converting an existing HyperFrames project into a comonteur project. |
references/troubleshooting.md |
A previously-working project shows broken/missing data after reopening a .blend — fonts, links, or other external references. |
Read the matching reference before the first call in that area — these are command
contracts, not background reading. Don't read them all speculatively.
Ownership, in eight lines
Every datablock you create carries cmt_id (stable id), cmt_origin, cmt_rev (bumped per
batch). A text object bound with scene.bind_param() also carries cmt_param.
cmt_origin |
You may |
agent |
mutate and delete freely |
shared |
mutate paths not in claimed_paths(); never delete |
human |
mutate only when the current instruction names it; never delete |
| (absent) |
treat as human |
The flip from agent to shared is automatic — a depsgraph_update_post handler catches
human edits to your data (provenance.py:36). The human can override either way from the
comonteur sidebar panel (Take ownership / Return to agent), acting on the active object —
scenes and actions only ever flip automatically.
When a human has claimed a property you wanted to change, route around it and say so in your
report. Don't overwrite it back, don't ask them to undo their edit.
1---2name: comonteur3description: Build and edit video scenes, shots, animations and timelines inside Blender through the comonteur helper library, in a project that has `timeline.blend`, `timeline.toml` or `.comonteur/`, or starting one from scratch. Use for: scaffolding a new video project, creating or changing a shot (`shots/*.blend`), title cards and kinetic text, keyframes and GSAP-named eases, staggered reveals, linking components from `lib/*.blend`, rendering review frames, assembling the VSE timeline from `timeline.toml`, and importing an existing HyperFrames project. Read this before writing any `bpy` code against such a project — raw `bpy` bypasses the ownership and journal system that lets a human edit the same `.blend` at the same time.4---56# comonteur — start here78A human edits this `.blend` live, at the same time as you. Everything below exists to keep9that safe. The scene is the source of truth. You mutate it. You never regenerate it.1011Reach Blender through the official Blender MCP server's `execute_python` only. There is no12comonteur MCP tool — the library is the typed surface.1314## The one code shape1516Every piece of Python you send starts like this:1718```python19try:20 from bl_ext.user_default import comonteur as cmt21except ImportError: # dev checkouts that put addon/ on sys.path22 import comonteur as cmt2324with cmt.journal.batch("shot-03: title fade-up"):25 scn = cmt.scene.new_scene("shot-03", fps=30, frame_range=(1, 90))26 title = cmt.text.create(scn, "Ship faster", size=0.8)27 cmt.anim.tween(title, "location", 1, frm=-0.4, to=0.0, start=1, dur=18, ease="power2.out")28```2930The batch is not optional. `journal.set()` and `anim.tween()` raise outside one (`journal.py:86`,31`anim.py:82`). The batch writes the journal entries, bumps `cmt_rev`, gates the human-edit32detector so your own writes aren't misread as theirs, and produces one labelled undo step.3334Label batches for a human reading an undo menu: `"shot-03: title fade-up"`, not `"batch 1"`.3536## Hard rules3738Break one of these and you corrupt the project silently.3940- **Batches save themselves.** `journal.batch()` saves the `.blend` on exit once it's been41 saved once already (Blender's own `save_version` backup is bumped first). Don't call42 `bpy.ops.wm.save_mainfile()` yourself — that's a second save path outside the batch.43- **Never regenerate.** Mutate existing data, never delete-and-recreate it.44 `scene.new_scene()` returns the existing scene when `cmt_id` already exists (`scene.py:20`)45 — that's deliberate, don't route around it. Delete-and-recreate throws away every human46 edit and breaks the journal's history.47- **Untagged data is human data.** No `cmt_origin` means a human made it. Don't touch it48 unless the current instruction names it. Check `cmt.provenance.origin(id_block)` before49 touching anything you didn't create this session.50- **`shared` means the human touched it.** `cmt.provenance.claimed_paths(id_block)` returns51 every path a human has claimed since — anything whose live value no longer matches what you52 last wrote there. Those paths are lost to you: mutate anything else, and never delete a53 `shared` datablock. (Mechanism and limits: `references/api.md`.)54- **Prefer `cmt.*` over raw `bpy`.** Raw `bpy` isn't forbidden, but it's untracked — it never55 hits the journal, `revert` can't undo it, the human's ownership view won't show it. If you56 must, keep it to reads, or route the write through `cmt.journal.set(obj, "path", value)`.57- **Prefer `mise run comonteur:<task>` over raw shell**, in a project that has them (check58 `mise tasks`). A task is what the human can re-run and what pins the tools — a command in a59 chat log is neither. Add a task instead of improvising a repeat. Ask permission, then run it60 yourself — don't tell the human to open a terminal.61- **`batch()` does not nest** (`journal.py:61`). One batch per logical unit of work. Not one62 per write, not one for the whole session.63- **Ask before sourcing a missing resource.** Comonteur's own default-creatable primitives (a64 new scene, a VSE strip, etc.) need no asking. For anything else missing — an asset, font,65 footage, or another project to reuse from — don't silently search disk or the web for it.66 Ask the human: search on disk (outside this project), search online asset providers, have67 them provide it, or generate it if that's possible. Only search outside the project68 directory once they've said which.6970## The loop7172This is the loop for one shot. For work around it — starting a project, ingest, assembly,73delivery — read `references/workflows.md` first.74751. **Read the intent.** The shot's `timeline.toml` entry — `title`, `notes`, and where it76 sits in the assembly. If `notes` names a `rules:`/`blueprint:` list, that list is a77 checklist you'll close out in step 5, not background color — write it down.782. **Read the scene before writing to it.** `cmt.introspect.outline(scn)`,79 `describe(obj, "data")`, `animated_paths(scn)`. These are capped and truncated on purpose80 (`introspect.py:1`). Never dump `bpy.data` or walk a whole tree — that burns the context81 you need for the actual work.823. **Build, in one batch.** See `references/api.md`. A named `rules:`/`blueprint:` value is83 almost always a known combination of `cmt.*` calls — check `references/vfx-recipes.md`84 before improvising one from scratch.854. **Look at it.** `cmt.preview.frames(scn, [1, 45, 90])` returns PNG paths under86 `.comonteur/review/`. Read those images.875. **Adjust, re-preview, report, stop.** For a structural fix (wrong object, wrong path,88 missing keyframe), re-check with `introspect.animated_paths`/`describe` first — free, and89 catches the same mistakes a render would. Only call `preview.frames()` again once you've90 actually changed the scene, not to re-confirm what you already saw.9192**Gate: never call a shot done without reading a rendered frame of it.** Blender fails93silently and visually — text off-canvas, a missing material, an F-curve on the wrong array94index. Code that ran without error proves nothing here.9596**Second gate: never call a shot done without closing out step 1's checklist.** A render with97no errors proves the scene doesn't crash, not that it matches what the notes asked for — that98needs its own check, the same way code needs a test beside it, not just a clean run. For each99`rules:`/`blueprint:` token, confirm it's actually present: cross-check `animated_paths(scn)`/100`describe()` for the structural evidence that rule implies (`split-tilt-cards` → a101`rotation_euler` keyframe on the card root; `scale-swap-transition` → two overlapping102scale+alpha tweens; see `references/vfx-recipes.md` for what each named rule should have left103behind) and look for it in the rendered frame from step 4. A rule with no evidence in either104place was skipped, not implemented — go back to step 3, don't report the shot done. If a105`snapshots/*.png` HyperFrames reference still happens to exist from the import, treat it as a106bonus fidelity check, not the mechanism — most projects only ever had HyperFrames prepare the107storyboard and assets, never rendered, so no such reference exists.108109**Hit a real gap or bug in comonteur itself** — a missing helper, a wrong doc, a broken mise110task — tell the human and offer to draft a GitHub issue at111`https://github.com/davidB/comonteur/issues`. Propose it, don't file it yourself.112113## Routing114115| Read | When |116|---|---|117| `references/workflows.md` | Starting a project, or deciding which workflow a directory is: order of work, ingest, rendering, delivery. Also covers delegating shots to a subagent when building more than a couple in one session. |118| `references/api.md` | Writing any scene content: scenes, text, animation, eases, components, preview, provenance. |119| `references/vfx-recipes.md` | A named look (spring-pop, underline draw, card tilt, scale-swap, typewriter, shatter, discrete text-state swaps, sine-wave idle loop, center-outward assembly, flash/marker word accents, directional entrances) or a `blueprint:` value from a storyboard — which `cmt.*` calls to combine, and how to install/reuse the GN VFX asset library. |120| `references/timeline.md` | Assembling or changing the edit: `timeline.toml`, anchors, `reconcile`, VSE strips, audio, transitions. |121| `references/hyperframes-import.md` | Converting an existing HyperFrames project into a comonteur project. |122| `references/troubleshooting.md` | A previously-working project shows broken/missing data after reopening a `.blend` — fonts, links, or other external references. |123124Read the matching reference before the first call in that area — these are command125contracts, not background reading. Don't read them all speculatively.126127## Ownership, in eight lines128129Every datablock you create carries `cmt_id` (stable id), `cmt_origin`, `cmt_rev` (bumped per130batch). A text object bound with `scene.bind_param()` also carries `cmt_param`.131132| `cmt_origin` | You may |133|---|---|134| `agent` | mutate and delete freely |135| `shared` | mutate paths not in `claimed_paths()`; never delete |136| `human` | mutate only when the current instruction names it; never delete |137| *(absent)* | treat as `human` |138139The flip from `agent` to `shared` is automatic — a `depsgraph_update_post` handler catches140human edits to your data (`provenance.py:36`). The human can override either way from the141comonteur sidebar panel (*Take ownership* / *Return to agent*), acting on the active object —142scenes and actions only ever flip automatically.143144When a human has claimed a property you wanted to change, route around it and say so in your145report. Don't overwrite it back, don't ask them to undo their edit.