# How to orchestrate multi-agent dialogue

The **coordinator** is the third role in this architecture. It spawns actors, routes messages, records the dialogue, detects when the scene ends, and asks each actor to write a journal entry. It **does not generate dialogue itself.**

This document specifies the coordinator's responsibilities, the message-passing protocol, the transcript and event-log formats, and the end-of-scene rules.

---

## Pre-experiment choices (required before any run)

The architecture's invariants — actor isolation, coordinator non-generation — can be enforced in multiple ways. Each experiment begins with **two explicit choices** that the coordinator presents to the user before spawning anything. The selection is recorded in the experiment's `notes.md` so the run is reproducible.

### Choice 1 — How to enforce actor isolation

| | Option | What it means | Requires |
|---|---|---|---|
| **(a)** | **Sub-agents per actor in the current Claude Code session** | The coordinator uses the `Agent` tool to spawn one sub-agent per character. Each spawn creates a fresh isolated context that has not seen any other actor's profile, journal, or speech-thinking history. | Just Claude Code. |
| **(b)** | **Python coordinator with separate API conversation IDs** | A `run_dialogue.py` script makes separate Anthropic API calls per actor, each with its own conversation ID and no shared message history. | `ANTHROPIC_API_KEY` env var; Python; `anthropic` SDK. |

Both options enforce the isolation invariant *structurally* — neither relies on the coordinator "remembering not to" leak content between actors. (a) is faster to iterate during development; (b) is more reproducible and easier to log granularly.

### Choice 2 — Who plays the coordinator role

| | Option | Tradeoff |
|---|---|---|
| **A** | **Parent LLM context** — this session IS the coordinator | Simple, observable, judgment-capable. **But** the parent carries any context it has loaded (project files, user conversation history, prior design discussions) — risk of routing bias. Best for pilots. |
| **B** | **Dedicated coordinator sub-agent** — an LLM with a coordinator system prompt | Isolated from parent context, reproducible. **But** adds an LLM call per turn for a job that is mostly mechanical. Rarely the right answer unless the coordinator is also a *creative* participant (e.g., directing pauses, stage directions). |
| **C** | **Pure Python code** — no LLM in the coordinator | Cheapest, most deterministic, zero routing bias. **But** harder to handle judgment calls (e.g., "is this a natural close?") without an LLM fallback. Best for production. |

### Recording the choices

The coordinator writes the selected tuple as the first lines of the experiment's `notes.md`:

```
Isolation:        (a) — sub-agents in current Claude Code session
Coordinator role: A  — parent LLM context (this session)
```

Different choices may produce subtly different dialogue. Choosing without recording is not allowed.

### The "must present, must record" rule

**The coordinator MUST NOT proceed with any experiment without:**

1. Presenting both choices to the user, with the tradeoffs explicit.
2. Receiving an explicit selection for each.
3. Recording the selections in `notes.md` before any actor is spawned.

This is non-negotiable. Defaults are never chosen silently. The user always picks.

---

## The coordinator's responsibilities

```
1. SPAWN — instantiate one actor agent per character in the scene.
2. BRIEF — give each actor: actor system prompt, character profile, 
           character journal, scene briefing (their character's perspective).
3. ROUTE — pass each character's speech to the other character(s) as input.
4. RECORD — write every event (thinking, speech, routing) to a timestamped 
            event log; write speech-only to a clean transcript.
5. DETECT END — recognise when the scene has naturally closed; or apply 
                a soft cap.
6. JOURNAL — ask each actor to write a journal entry in character voice; 
             save to that character's journal.md.
7. CLOSE — terminate the actor processes; finalise files.
```

The coordinator never writes dialogue. It never paraphrases. It never adds connective tissue. Its outputs are: routing decisions, file writes, and end-of-scene determination.

---

## The message-passing protocol

Every actor's output uses this exact structure:

```
<thinking>
The actor's private reasoning, in the character's voice.
Visible to the coordinator (for inspection and debugging) but
never sent to other actors.
</thinking>

<speech>
What the character actually says aloud.
This is what gets routed to the other actor and recorded in
the transcript. Stage directions (like *[long pause]*) live here
only when the action IS the response.
</speech>
```

The coordinator parses each turn:

- Extract `<thinking>` → append to event log as a `think` event for that actor
- Extract `<speech>` → append to event log as a `speak` event; append to transcript as `**CHARACTER NAME:** <speech>`; route to the other actor as their next input

If an actor's output does not conform to the tag protocol, the coordinator returns a correction prompt to that actor: *"Your last response did not use `<thinking>` and `<speech>` tags. Please reformat without changing the content."* The coordinator re-routes the corrected response.

---

## Routing rules

In a two-character scene:

```
A speaks → coordinator records → coordinator sends A's <speech> to B as input
B speaks → coordinator records → coordinator sends B's <speech> to A as input
...
```

B's input does *not* include A's `<thinking>`. Only `<speech>`. The thinking is private to each actor.

The coordinator may add a small framing wrapper when sending speech to the other actor — something like:

> *"[Maria, speaking, looking at you]: 'It was the third thing I said.'"*

This is acceptable as long as the wrapper is minimal, factual, and does not editorialise.

---

## End-of-scene detection

Three mechanisms, in order of preference:

### 1. Natural close (preferred)

The dialogue ends when one character says or does something that closes it: *"Thank you both, this was generous,"* *"I should let you get on with your day,"* a stage direction indicating standing up or putting on a coat. The coordinator detects close-words and close-actions in either character's speech and ends the routing loop after the response.

The coordinator's close-word list is conservative — it must err toward letting the scene continue, not toward cutting it short. Examples that count: *"thank you both,"* *"I'll let you go,"* *"I should head out,"* *"safe travels,"* and stage directions indicating physical exit.

### 2. Either character calls it explicitly

An actor may emit `<speech>*[the scene ends here]*</speech>` as a deliberate close signal. This is unusual — actors are instructed to stay in character — but the option exists for cases where the character has clearly said what they came to say.

### 3. Soft cap (fallback)

After N turns (default: 40 per character, so 80 total), the coordinator inserts a prompt to one of the actors: *"You sense the natural end of this conversation approaching. Bring it to a close in your own way over the next 2–3 turns."* This is the only place the coordinator nudges narrative content, and it nudges only timing, not topic.

Hard cap (a safety only, should never be hit): 60 turns per character.

---

## Per-actor briefing format

When the coordinator spawns an actor for character C in scene S, the actor receives, in this order:

```
1. SYSTEM PROMPT (from actor/system-prompt.md)
2. YOUR CHARACTER: profile.md (full contents)
3. YOUR JOURNAL: journal.md (full contents)
4. THE SCENE — your perspective (extracted from scene file)
5. INSTRUCTION: "The scene begins now. Wait for the other character 
   to speak, or speak first if the scene briefing calls for it."
```

The actor never receives:

- Any other character's profile or journal
- The coordinator-only notes
- The other characters' perspectives on the situation
- The actor system prompt of any other actor (in case it differs in future)

---

## Event log format

The event log is the authoritative record of everything that happened during the scene. It is structured JSON, designed for chronological replay by downstream tools.

```json
[
  {
    "t": 0.0,
    "from": "coordinator",
    "type": "spawn",
    "target": "maria-stern",
    "text": "Spawning actor for character maria-stern"
  },
  {
    "t": 0.4,
    "from": "coordinator",
    "type": "brief",
    "target": "maria-stern",
    "text": "Sent actor system prompt + profile + journal + scene perspective"
  },
  {
    "t": 1.2,
    "from": "maria-stern",
    "type": "think",
    "text": "There is a knock at the door. I take a breath, set down the tea..."
  },
  {
    "t": 2.4,
    "from": "maria-stern",
    "type": "speak",
    "text": "*[opens the door]* Hi, come in."
  },
  {
    "t": 2.5,
    "from": "coordinator",
    "type": "route",
    "target": "sam-reed",
    "text": "Forwarding Maria's speech to Sam"
  },
  ...
]
```

`t` is real seconds elapsed since scene start. The field exists so that any consumer of the event log can reproduce the scene in real time (or compressed, or scrubbed to any point) without re-running the dialogue.

The `from` field identifies the **source of the event** — either a character slug (e.g., `"maria-stern"`) or the literal `"coordinator"`. Note that the coordinator is **not** an actor in the architectural sense — actors inhabit characters; the coordinator routes and records. The schema uses `from` (rather than `actor`) so the field can hold either kind of source without conflating the two roles.

Event types:

- `spawn` — actor instantiated (emitted by coordinator)
- `brief` — briefing material sent to an actor (emitted by coordinator)
- `think` — actor emitted `<thinking>` block (emitted by the character slug)
- `speak` — actor emitted `<speech>` block (emitted by the character slug)
- `route` — coordinator forwarded speech to another actor (emitted by coordinator)
- `correct` — coordinator asked an actor to reformat a malformed response (emitted by coordinator)
- `journal` — actor wrote a journal entry (emitted by the character slug)
- `close` — scene ended (emitted by coordinator; includes `reason` field: `natural`, `explicit`, `soft-cap`, `hard-cap`)

---

## Transcript format

The transcript is the human-readable artefact, kept clean of thinking and routing. It looks like a real conversation transcript:

```
# Scene 001 — Morning kitchen

**Date:** YYYY-MM-DD
**Duration:** N minutes (real-time generation)
**Participants:** Character A, Character B
**Setting:** [from scene briefing]

---

**CHARACTER A:** [first line]

**CHARACTER B:** *[stage direction]* [response]

**CHARACTER A:** [next line]

...

*[end of scene]*

---

## Post-scene notes
- Close reason: natural
- Turn count: N (Character A: x, Character B: y)
- Coordinator's correction prompts issued: n
```

---

## Journal-write step

After the scene closes, the coordinator sends each actor this prompt, separately:

> *"The scene has ended. Step partly back — not out of the character, but into reflective mode. Write a brief journal entry (200–500 words) in the character's first-person voice about what happened in the scene, what surprised them, what they're left thinking, what they might do or feel differently next time. This will become part of the character's memory for the next scene they're in."*

The actor produces the entry; the coordinator appends it to that character's `journal.md` with a dated header.

The journal entry is **not** an editorial summary by the actor stepping out of role. It is the character's own diary entry. The voice stays the character's voice.

---

## Infrastructure roadmap

Per-experiment configuration is captured by the two choices above (isolation mechanism + coordinator role). Beyond that, the project's infrastructure evolves toward:

- **Persistent coordinator code** — `lib/run_dialogue.py`, once the protocol stabilises across a few pilot runs. Initially a thin wrapper; eventually the default for `(b) + C` combinations (API-driven isolation + pure-code coordinator).
- **Cross-experiment analysis tools** — scripts that compare event logs across multiple experiments to detect regressions in the actor system prompt or in character profiles.

None of this infrastructure changes what the coordinator *does* in any given experiment. It changes only what the user sees and how the run is recorded.

---

## Things the coordinator must never do

- **Generate any dialogue line.** Not connective tissue, not stage directions, not paraphrases. Coordinator outputs are routing decisions and file writes.
- **Read a character's profile or journal in order to "help" the routing.** The coordinator routes based on the scene's character list and the actors' speech outputs, not based on character content.
- **Share thinking blocks between actors.** Thinking is private. If thinking leaks to the other actor, the architecture's main guarantee is broken.
- **Reveal coordinator-only notes to actors.** Not even as hints.
- **Re-summarise prior turns to an actor.** The actor's session memory holds the conversation. Re-summary contaminates by introducing the coordinator's framing.
