The Shot Lifecycle¶
This is the single most important idea in agentwood. Understand it and the whole DSL makes sense.
Declare, then compile
Everything you write inside a with … as shot: block only records intent. Nothing
renders until the block exits — then agentwood runs a fixed compilation pipeline that
turns your intent into Blender keyframes and a video.
Why deferral matters¶
Camera and lighting depend on where the actors end up and how long the shot is — facts that
aren't known until you've added all the actors and actions. So calls like shot.camera(...) and
shot.lighting(...) are stored as specs and resolved last, against the final scene.
with canal_bridge.Tree1.A1 as shot:
shot.block(face_to_face(), bob, anne) # records placement
shot.camera("static", mode="medium") # records a camera spec
shot.lighting("rembrandt") # records a lighting spec
bob.say("You came.") # records an action (+ TTS)
anne.idle()
shot.clip(4.0) # records a duration ceiling
# ← everything above compiles HERE, on exit
What happens on __enter__¶
Opening the block does very little:
- Load the set (if any) and apply the render engine + FPS.
- Clear leftover lights and audio strips from the previous shot.
- Create a fresh Blender collection
DSL_shotN. - Mark this shot as the active one so actor calls route to it.
What happens on __exit__ (the pipeline)¶
When the block closes without error, this fixed order runs:
_validate() → actor/blocking counts, one-speaker rule
_apply_default_idle_if_needed() → any actor with no action gets a tiny idle
_compute_timeline() → frame_end = min(natural_end, clip)
_extend_short_actors_to_frame_end()
_apply_camera() → resolve the camera spec vs final positions
_apply_lighting() → resolve the lighting spec
_render() → fast proxy or production
# then: destroy the shot collection + clear sequencer audio
Shots are self-contained
The collection, lights, and audio strips are torn down on exit, so nothing leaks from one
shot into the next. Don't expect state to carry between with blocks.
Consequences to remember¶
- The order of your calls inside the block mostly doesn't matter — they're all just recorded.
- A shot always renders on exit; there's no separate "render" call.
- After the block,
shot.render.source_pathis the output file, andshot.frame_endis the computed length.