The design document
Spiral, the council, part 2: runtime, transparency and magic. The September 2026 proposal the council prototype was built from, as written, with nine departments and the backend the prototype has not built yet.
This is the council design as it was written before the prototype: the second version of Spiral, drafted after the first design’s single agent hit its wall. Reproduced with the working film’s name removed. Where the build departed from it, Against the plan says how. Nine departments here; seven in the build.
1. The one idea
The event log is the film’s production diary. Everything else (the artifacts, the frontend, the audit trail, the evaluation harness) is a view of it.
Transparency is not a feature you add to an agent system; it is a property of how state is stored. If the only way any state changes is by appending a typed, attributed, causally-linked event to an immutable log, then “why did this happen?” is a query, “what did the system see when it decided?” is a query, and “what would have happened if I’d answered differently?” is a replay. Nothing needs to be reconstructed after the fact because nothing was ever discarded.
Four mechanisms together give you a completely auditable system. Each is cheap on its own; the combination is what matters.
Event sourcing: every change is an appended event with an actor, a cause, and a cost. Single writer: only the owning department can emit a change to its artifact, so authorship is never ambiguous. Tool gateway: permissions are enforced where the tool is called, not in the prompt, so an agent that “decides” to overstep simply can’t. Provenance manifests, every generated asset carries the prompt, model, seed, and lock versions it was made under, signed at export.
2. The four questions
Short answers first. The rest of the document is the reasoning.
Does each department own its agent with its own skills? Yes, with one distinction: the department is durable, the agent is ephemeral. A department is a persistent identity (its artifact, its ledger, its subscriptions, its history) plus a versioned skill bundle plus a tool allowlist. An “agent” is one invocation of the shared five-role harness loaded with that department’s skill and a task. It runs, emits events, and is gone. Nine departments; one harness; nine SKILL.md folders. The skill format is Anthropic’s open Agent Skills standard, so the bundles run in Claude Code, Codex, Gemini CLI and Cursor without a rewrite.
How does the loop run? Is it async? Async between departments, sync inside one. Departments are durable workflows that sleep until an event they subscribe to arrives, wake, run one harness invocation (assemble → generate → critique → resolve → record) as a chain of retryable activities, emit their own events, and sleep again. A human answer is a signal a workflow can wait days for without holding a process. Order is not scripted; it falls out of subscriptions and the spine. Convergence is computed, not felt: a gate can close only when no FLAG is open and every department has evaluated the current version of every lock that binds it.
Can all agents see every action and trace, or are they called when relevant? Both, at different layers. The log is readable by every department without exception. That is the transparency guarantee for agents as much as for you. But departments are woken by subscription, not broadcast, and when a department wakes its assembler pulls: the diff since it last ran, the locks that bind it, and the trace excerpts relevant to this task. Read access to everything; attention to what changed. Two departments are subscribed to everything by design (the Director to every FLAG, the Producer to every ASK and every cost) and a recorded digest at each gate (“dailies”) lets the others catch up cheaply.
How does it feel like magic? Surprise is engineered as a fourth message type, OFFER: an unrequested proposal from a department that noticed something while reading another department’s artifact, delivered with its reasoning and a cheap preview. Surprises live in proposals, never in locks. They are grounded (they cite what they read), traceable (their provenance is visible), and controllable (a budget per gate, one tap to try or dismiss). The magic is the council noticing what you didn’t ask about, and being able to show you exactly why.
3. The backend, in seven layers
Bottom to top. The first two are the substrate that survives any model change; the rest are views, runtime, and enforcement over them.
L1 · The event log. Append-only, one table per project to start (Postgres is enough for years). Every event has an id, timestamp, actor (department, role, model, skill version), the id of the event that caused it, the gate and round it belongs to, a type, a payload, a cost, and an idempotency key. Why: causation ids are what make “why?” a query. A ruling points at the FLAG that prompted it, which points at the lock change that triggered it, which points at the ASK you answered on Tuesday.
L2 · The asset store. Object storage, content-addressed. Every generated asset carries a provenance manifest: prompt, model, seed, parameters, reference inputs, and the exact lock versions it was generated under. The manifest is itself an event. Why: a shot is only auditable if you can say which version of the Look Bible and which version of the character sheet it was made against.
L3 · Projections. Read models rebuilt from the log: current artifact state, the lock table with versions, open FLAGs, open ASKs, the budget ledger, each department’s “last seen” lock versions, gate status. If a projection is ever wrong, delete it and replay. Why: the frontend never reads the log directly; it reads projections, which are fast and rebuildable.
L4 · The runtime. Durable execution: one workflow per department per project, one for the Producer’s gates. LLM calls, tool calls and render jobs are activities with retries and heartbeats (a video render is minutes; heartbeats are how you tell “slow” from “dead”). Human answers arrive as signals. Temporal is the mature choice; Restate or Inngest are lighter. Why: agents crash, workers restart, renders take twenty minutes, and you might answer a question next Thursday. State has to live in the history, not in a process.
L5 · The tool gateway. Every tool call goes through one gate that enforces allowlists: only the Unit calls video models; only Sound calls the voice API; only the owner of an artifact can emit a change to it; nothing renders without a budget reservation. Denials are events too. Why: permission enforced in the prompt is a request; enforced in the gateway it’s a guarantee. This is the layer that makes “single writer” true rather than aspirational.
L6 · The skills registry. Nine SKILL.md folders, versioned. Every event records the skill version and model that produced it. Why: when you edit Camera’s rubric, you can replay last week’s project against the new version and diff the behaviour. Taste becomes regression-testable.
L7 · Telemetry. OpenTelemetry spans using the GenAI conventions (invoke_agent, execute_tool, chat), with prompt and completion content stored as span events rather than attributes so it can be filtered at the collector. Each event stores its trace id; each span carries its event id. Why: the log tells you what was decided; the trace tells you what it cost and how long it took. Instrument to the standard, let the vendor be a detail.
One layer that is deliberately missing: a message bus between departments. Departments do not message each other. They append to the log and subscribe to it. Point-to-point channels are where context gets lost and where the “telephone game” starts.
4. The event
Everything in the system is one of these. If it can’t be expressed as an event, it didn’t happen.
event:
id: evt_01J9Q...
at: 2026-09-07T10:42:18Z
project: the-film
gate: preproduction round: 3
actor: {dept: camera, role: critic,
model: claude-opus-5, skill: camera@v7}
caused_by: evt_01J9P... # the story change it reacted to
type: flag.raised
target: script/scene-04 # an artifact, never a department
against: lock:shot_spec/22@v3
payload: {severity: block, reason: "...", suggested: "..."}
cost: {input_tokens: 18240, output_tokens: 611, usd: 0.09}
trace_id: 4bf92f3577b34da6...
idempotency: camera:critic:scene-04@v5
Event families. lock.changed · artifact.proposed · artifact.committed · flag.raised · flag.resolved · ruling.issued · offer.made · offer.tried · ask.opened · ask.answered · guess.taken · asset.rendered · asset.rejected · budget.reserved · budget.spent · gate.opened · gate.closed · tool.denied
The four message types. PROPOSE: a change to the artifact you own. Becomes artifact.committed when the resolver accepts it. FLAG: a concern about someone else’s artifact, measured against a lock. Read-only; it cannot change anything by itself. Carries a severity: note, revise, block. ASK: a question only the human can answer. Routed to the Producer, batched, and delivered with the four fields. OFFER: new in this draft. An unrequested proposal with reasoning and a cheap preview. The mechanism of surprise. Costs nothing until accepted.
Notice that FLAG, OFFER and PROPOSE all target an artifact, never a department. That is what lets anyone read them later without knowing who they were “for”.
5. The loop, precisely
One worked example: Story rewrites scene 4 during pre-production. Nine events, no orchestrator, and the order was never written down anywhere.
- artifact.committed · Story. Story’s resolver accepts a rewrite of scene 4. The lock
scene/04moves to v5. Story’s workflow sleeps. - wake · Camera, Characters, Sound. All three subscribe to
scene/*. Each workflow wakes independently, in parallel, at its own pace. Nothing else in the project stops. - assemble · Camera. Camera’s assembler pulls the diff v4 → v5, the shot specs that reference scene 4, the Look Bible entries those shots cite, and the last ruling on this scene. Not the whole script. Not the whole log.
- flag.raised · Camera. Camera’s critic finds that the rewrite moved the confrontation outdoors, and shot 22’s lens and light no longer make sense. Severity: revise. Target:
script/scene-04, againstshot_spec/22@v3. - offer.made · Sound. Sound’s critic has no objection, but its generator notices the new version ends on a silence and offers a temp cue that drops the score two bars early. Preview: a 12-second sketch. Cost so far: text only.
- ruling.issued · Director. The Director is subscribed to every FLAG. It rules: keep the rewrite, Camera re-specs shot 22. It does not touch either artifact.
- artifact.committed · Camera. Camera’s resolver commits a new shot 22.
shot_spec/22moves to v4. Camera records its “seen” versions:scene/04@v5. - wake · no-op · Unit. The Unit subscribes to shot specs, wakes, sees the gate is still pre-production and nothing is locked for render, records that it saw v4, sleeps. A wake that does nothing still leaves a trace.
- gate check · Producer. The Producer evaluates the convergence rule: no open FLAGs; every subscriber’s seen-versions match the lock table. Sound’s OFFER is still pending, so it goes into the next call sheet. Round 3 can close.
The convergence rule. A gate closes when two things are true: there is no FLAG of severity “block” or “revise” left open, and for every department, the version of each lock it last evaluated equals the current version in the lock table. That second condition is a projection you can read at any moment. It replaces the question “are we done?” with a table that is either all green or isn’t.
Bounds. Story changes, Camera flags, Story changes again, Camera flags again. Rounds per gate are capped (three is a good default). At the cap the Producer opens an ASK with both positions and the Director’s last ruling, and the human decides. Loops end in a question, never in silence.
What is sync and what isn’t. Inside one invocation the five roles run in order, sharing context: the generator sees what the assembler bundled; the critic sees the generator’s output; the resolver sees both. Between departments there is no shared call stack at all. A department that takes four minutes to reason does not block a department that takes four seconds. Renders run as long-lived activities with heartbeats, so a twenty-minute render job is just an event that hasn’t arrived yet.
6. Who sees what
Read-all is a permission. Attention is a budget. The design separates them.
Readable. The whole log, every artifact, every trace. No department is ever denied a read. If Editorial wants to know why the Director ruled against a shot in round 2, it can.
Woken. Only by subscription. Camera doesn’t wake when Sound commits a cue, because it doesn’t subscribe to the audio timeline. Broadcast would burn the budget on no-op wakes.
Bundled. What the assembler actually puts in front of the model: the diff since last run, the binding locks, relevant trace excerpts, and the department’s own recent rationale. The bundle is recorded, so you can see exactly what the model saw.
The bundle is the single most important thing to make visible in the frontend. When an output is wrong, the question is almost always “what did it have in front of it?”, and the answer is a click away.
Two departments see everything. The Director subscribes to every FLAG and OFFER, because resolving conflicts is its artifact. The Producer subscribes to every ASK and every budget event, because the Brief and the ledger are its artifacts. Neither reads every PROPOSE; they don’t need to, and if they did, their contexts would collapse first.
Dailies. At every gate close, the recorder writes a digest event: what changed, what was argued, what was ruled, what was guessed on the human’s behalf, what was offered and not yet tried. Departments that didn’t wake during the round read the digest instead of the log. So does the human. This is CineBench’s hierarchical memory (shot → scene → story → character) applied to time instead of space.
7. What a department owns
Identity, skill, allowlist, and subscriptions. The harness is shared; everything that makes Camera “Camera” is data in a folder.
departments/camera/
SKILL.md # mandate, when to wake, how to critique
rubric.md # the critic's questions, with examples
references/
shot-grammar.md # coverage patterns, axis, motivation
lens-and-light.md
video-model-prompting.md # model-specific; expected to churn
scripts/
previs_keyframe.py # calls the image model through the gateway
allowlist.yaml # tools: [image_gen]; writes: [shot_list]
subscriptions.yaml # scene/*, look_bible/*, character/*, spine
The skill loads progressively. The Producer’s context holds only each department’s name and one-line description; the full SKILL.md loads when the department wakes; the reference files load only when the assembler decides they matter for this task. A department library of nine costs the Producer a few hundred tokens at rest.
Roles inside the department can use different models. The assembler and recorder are clerical: a fast, cheap model. The generator is craft: the strongest model you can afford. The critic should, where possible, be a different model family from the generator. Two models rarely share the same blind spot, and a critic that agrees with everything is worse than none. Which model ran each role is on every event, so you can measure this instead of believing it.
Skill versions are part of the audit trail. When you change Camera’s rubric, that is camera@v8. Every event from then on says so. This is what makes CineForge’s stage-local patches possible in your system: a recurring defect traced to Camera’s critic gets a rubric patch, and the next project’s events show whether the patch worked.
8. The frontend: the Production Board
Design thinking first. Whoever opens this screen has one of three questions: what is happening, why did that happen, and what can I change. Every surface answers one of them and links to the other two.
The board is organised around the film, not the agents. Departments never get their own screens; they appear as annotations in the margins of the artifacts they argued about, the way a script supervisor’s notes appear in the margin of a page. This keeps the human’s attention on the work and makes the machinery legible only where it touched something.
The four depths of the same moment: Board answers “what is happening”. Margins answers “why”. Call sheet answers “what can I change”. Inspector is for when you’re debugging the council, not the film.
The seven surfaces.
- Board: the four gates, the scene strip with lock and flag state, the nine departments with a single status word each, and a live feed that streams tool output as it happens. Streaming the work, not just the result, is the single change teams report as most trust-building.
- Margins: any artifact, with every FLAG, ruling and OFFER anchored to the passage or shot it concerns. Version history with diffs; every version links to the event that caused it.
- Why: click anything and walk its causation chain back to the answer you gave or the guess the Producer took. A time-travel scrubber shows the project’s state at any event.
- Call sheet: the Producer’s inbox. Batched ASKs with the four fields, OFFERs with previews, and guesses taken on your behalf that you haven’t reviewed. One tap to accept a guess.
- Ledger: cost by department, by shot, by gate, by round. Regeneration counts. The single most useful number: cost of re-renders caused by a lock changing after render.
- Dailies: the recorder’s digest at each gate: what changed, what was argued, what was guessed, what the council noticed. This is where surprises are presented, in a form you can read in two minutes.
- Inspector: the span tree, the prompts, the bundle, the critic’s scores, the skill version. Developer view. Never the default.
9. Three depths and the controls
The tension in every agent interface is transparency against overload. Explain everything and nobody can read it; explain nothing and nobody trusts it. The answer is depth on demand.
- Glance: a status word per department, a colour per scene, a number on the call sheet. Readable in five seconds. This is the default.
- Read: margins and dailies. The arguments and the rulings in plain language, anchored to the work. Readable in five minutes.
- Audit: the inspector, the bundle, the trace, the replay. Everything the system had and did. For when you are tuning the council, or proving what happened.
The controls that matter. Autonomy per department per gate. A slider from “ask me for everything” to “take the guess and tell me later”. Departments earn autonomy: the Producer can propose raising it when a department’s proposals have been accepted unchanged for two gates. Undo everywhere. The log is forward-only, so undo is a compensating event: reverting a lock re-emits the prior version with a cause of “reverted by you”. Nothing is deleted, so undo is itself auditable. Approvals with accumulated context. An ASK never arrives bare. It arrives with what has been done, what this decision changes, and what happens next. People decide faster and better when the context travels with the question. Interrupt without restart. Because every activity is durable, stopping Camera mid-critique loses one activity, not the round.
10. Engineering surprise
Magic is competence you didn’t ask for, made visible at the right moment. The research on co-creative tools is consistent: surprise is productive when it is grounded, traceable and controllable, and corrosive when it is random or silent.
- Surprise lives in proposals, never in locks. A department may OFFER anything. It may not change anything it doesn’t own, and it may not change its own artifact in ways nobody asked for without saying so. The film never changes under you; it is offered to you.
- Surprises come from cross-reading. The emergent behaviour you wanted is exactly this: Sound reading the script and noticing a silence; Camera reading the beat sheet and proposing one unbroken take for the climax; Characters noticing that a minor figure’s costume could carry the theme. Each department’s generator is asked one extra question on every wake: “reading this, is there something we should offer?”
- Every offer is grounded and previewed. An OFFER must cite what it read and attach a cheap preview: a keyframe, a 12-second sketch, a rewritten paragraph. Text and stills are free; renders are not. The preview is the offer’s cost ceiling until you tap “try it”.
- A surprise budget per gate. One OFFER per department per gate by default. Enough that dailies always contain something you didn’t ask for; few enough that the call sheet stays readable and the token cost stays flat.
- Show the roads not taken. Dailies include the three alternatives the Director considered and rejected, with one line on why. Nothing builds trust faster than seeing what a system chose not to do.
- Explore at the top. In ideation the Director runs three visibly different treatments, Co-Director style, and you pick. Divergence early is cheap; divergence late is a re-render. The bandit lives in the Director; the local refinement lives in each department.
- The right question is a surprise too. The best moment in a working session is the Producer asking something you hadn’t thought of and instantly recognise as the real question. It comes from departments declaring their needs honestly, not from a clever prompt.
Anti-magic, for the avoidance of doubt: a silent rewrite, an untraceable change, a surprise that spends render budget without permission, or a delightful thing you cannot find again because nobody recorded where it came from.
11. Provenance of the pixels
The decision log audits the council. The manifests audit the media. As of this summer, the second is also a legal surface.
Every rendered asset gets a manifest event: model and version, prompt as sent, seed and parameters, reference images by content hash, and the versions of every lock it was generated under (look bible, character sheet, shot spec, spine). Repairs and upscales chain to the asset they modified. At export, the final cut is signed with C2PA Content Credentials, which the EU AI Act’s Article 50 obligations (in force since 2 August 2026 for synthetic video reaching EU audiences) name as the reference mechanism for machine-readable disclosure. California’s transparency law aligned to the same date.
Two things this buys you beyond compliance. When a shot drifts, you can ask “which lock version was it made against?” and find the render that predates the change. And for festivals and distributors, “here is the signed provenance of every frame” is a sentence very few AI films can say.
12. Replay, forks, and evaluation
Event sourcing’s quiet superpower: the past is executable.
Fork. Pick any event, branch the project there, answer differently, let the council run. “What if the spine had been music-led?” becomes a second timeline with its own ledger, not a thought experiment. Divergence as a mode is a fork, and a fork is a copy of the log up to an event.
Replay a skill. Take last month’s project, substitute camera@v8 for camera@v7, replay the wakes. Diff the FLAGs. You now have regression tests for taste, and a way to answer “did the rubric change help?” with numbers.
Trajectory as training data. Every project’s log, with the human’s accepts and rejects on ASKs, OFFERs and guesses, is a labelled trajectory. CineForge’s case-to-pattern-to-policy loop runs on exactly this: recurring defects, traced to a stage, patched in that stage’s skill. You don’t need their machinery to start; you need the log and a monthly hour reading dailies.
13. Build order
The log first, because everything else is a view of it. Durable execution second, because the first render that dies at minute nineteen will teach you why.
- v0: the log and one worker. Postgres events table with the schema above; projections as views; a single async worker that wakes departments by subscription. The harness you already have, wrapped so every role emits events. No Temporal yet.
- v1: durable execution. Move wakes and activities onto Temporal (or Restate if you want lighter). Human ASKs become signals. Heartbeats on renders. Nothing about the events changes; only who runs them.
- v2: the Board. Board, margins, call sheet, ledger, fed by projections over server-sent events. Inspector last; it is mostly a JSON viewer with a span tree.
- v3: telemetry and provenance. OpenTelemetry GenAI spans exported to whichever backend you like; manifests on every asset; C2PA signing at export.
- v4: OFFER and dailies. Turn on the extra question in every generator, the surprise budget, and the recorder’s digest. This is the last thing to build and the first thing people will talk about.
Graph Cut’s A/B fits at v0: the solo control agent is the same harness with one department that owns everything. Same log, same board. The difference you’re testing is subscriptions and single-writer, nothing else.
14. Open questions worth a fight
Where the design is thinnest.
- How big can a bundle get before the assembler is doing the generator’s job? The recorded bundles will tell you, but only once you’ve decided what “too big” costs.
- Should OFFERs be generated on every wake, or only on gate close? Every wake surfaces more; gate close is cheaper and lands them in dailies where they read well.
- Does the Director’s “roads not taken” require the Director to actually generate the alternatives, or just to name them? Naming is nearly free; generating is where ideation cost goes.
- Where do you draw the trace-content privacy line for a product? The log is yours; a customer’s script in OpenTelemetry span events is a different question.
- How much of the Board should exist before the first outside user sees it? My bet: Board and call sheet only. Margins are for the second week.
The council, part 2 · a design proposal for Spiral · September 2026.