Topology Reference
Topology defines the role structure and routing within an autoloop loop. It is declared in topology.toml and controls which roles exist, what events they can emit, and how events route to the next role.
Topology is advisory — it is not a hard workflow engine. The model receives routing suggestions and allowed events as context, and backpressure enforces the protocol at the event-emit boundary.
File format
topology.toml lives at the root of a loop's project directory.
name = "autocode"
completion = "task.complete"
[[role]]
id = "planner"
emits = ["tasks.ready", "task.complete"]
prompt_file = "roles/planner.md"
[[role]]
id = "builder"
emits = ["review.ready", "build.blocked"]
prompt_file = "roles/build.md"
[[role]]
id = "critic"
emits = ["review.passed", "review.rejected"]
prompt_file = "roles/critic.md"
[[role]]
id = "finalizer"
emits = ["queue.advance", "finalization.failed", "task.complete"]
prompt_file = "roles/finalizer.md"
[handoff]
"loop.start" = ["planner"]
"queue.advance" = ["planner"]
"build.blocked" = ["planner"]
"tasks.ready" = ["builder"]
"review.ready" = ["critic"]
"review.rejected" = ["builder"]
"review.passed" = ["finalizer"]
"finalization.failed" = ["builder"]Top-level keys
| Key | Type | Required | Description |
|---|---|---|---|
name | string | No | Human-readable name for the topology. |
completion | string | No | The event that signals loop completion. Falls back to event_loop.completion_event in autoloops.toml, then to the completion_promise text fallback. |
[[role]] — role definitions
Each [[role]] table defines one role in the loop. Roles are processed in declaration order.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique identifier for the role. Used in handoff maps and prompt rendering. |
emits | array of strings | Yes | Events this role is allowed to emit. Determines the allowed-event set for backpressure. |
prompt | string | No | Inline prompt text for this role. |
prompt_file | string | No | Path to a markdown file containing the role's prompt, relative to the project directory. |
If both prompt and prompt_file are set, prompt takes precedence. If neither is set, the role has no prompt text.
[handoff] — event routing map
The handoff section maps events to suggested next roles. Each key is an event name and each value is an array of role IDs.
[handoff]
"loop.start" = ["planner"]
"tasks.ready" = ["builder"]
"review.passed" = ["finalizer"]When an event is emitted, the handoff map is consulted to determine which roles should run next. If the event is not in the handoff map, all roles are suggested — the model picks from the full deck.
The special event "loop.start" is the initial routing event at the beginning of the loop.
How routing works
The routing model has three layers:
- Suggested roles — looked up from the handoff map using the most recent event. If the event has no entry, all roles are suggested.
- Allowed events — the union of
emitsarrays from all suggested roles. This is what the model may emit next. - Backpressure — if the model emits an event not in the allowed set,
autoloop emitfails immediately and the event is logged asevent.invalidin the journal. The model is re-prompted with routing context.
This is soft routing: the model sees suggestions and constraints but is not forced into a fixed state machine. The backpressure layer prevents protocol violations without requiring hard-coded transitions.
Structured parallel routing
When parallel.enabled = true, topology still owns the normal routing model, but the harness recognizes two bounded fan-out forms:
explore.parallel— globally available exploratory fan-out<allowed-event>.parallel— dispatch fan-out for a normal event that is already in the current allowed set
Examples:
- if
tasks.readyis allowed, the parent may emittasks.ready.parallel - if
review.readyis not allowed,review.ready.parallelis rejected - completion events and coordination events do not gain
.parallelvariants in v1
Joined events are harness-owned:
- the model must not emit
*.parallel.joined explore.parallel.joinedresumes the same routing context that opened the wave<base-event>.parallel.joinedcan be routed explicitly inhandoff
Example explicit join routing:
[handoff]
"tasks.ready" = ["builder"]
"tasks.ready.parallel.joined" = ["builder"]Parallelism is still structured, not free-form:
- only normal parent turns get the global
Structured parallelismprompt block - branch child prompts do not get that global metaprompt
- only one active wave may exist at a time
- the parent launches all branches in that wave before joining them
- branch state is isolated under
.autoloop/waves/<wave-id>/...
Prompt injection
Each iteration, the topology is rendered into the prompt as advisory context:
Topology (advisory):
Recent routing event: tasks.ready
Suggested next roles: builder
Allowed next events: review.ready, build.blocked
Role deck:
- role `planner`
emits: tasks.ready, task.complete
prompt: You are the planner.
- role `builder`
emits: review.ready, build.blocked
prompt: You are the builder.
...The prompt summary for each role shows the first non-empty line of its prompt text.
Default topology
If no topology.toml exists, the loop runs with an empty topology: no roles, no handoff map, no completion event from topology. The loop still functions — it relies on autoloops.toml for the completion event and the model operates without role routing.
Completion
The loop completes when the completion event is emitted. The completion event is resolved in this order:
completionfield intopology.tomlevent_loop.completion_eventinautoloops.toml- The
completion_promisetext fallback (a string the model can output directly)
Additionally, autoloops.toml can declare event_loop.required_events — events that must appear in the journal before the completion event is accepted.
Design patterns
Linear pipeline
Roles hand off in sequence. Each role emits one "success" event that routes to the next role.
planner → builder → critic → finalizerRejection loops
A reviewing role can reject and route back to the producing role, creating iterative refinement cycles.
"review.rejected" = ["builder"] # builder tries again
"fix.failed" = ["fixer"] # fixer tries againFan-back to start
After a cycle completes a unit of work, route back to the first role to pick up the next unit.
"queue.advance" = ["planner"] # planner picks next task
"report.updated" = ["scanner"] # scanner looks for moreBlocked escalation
A role that cannot proceed emits a .blocked event, routing to a role that can re-plan or provide context.
"build.blocked" = ["planner"]
"fix.blocked" = ["diagnoser"]Examples
Every auto* preset in presets/ includes a topology.toml. See:
presets/autocode/topology.toml— 4-role build loop with rejectionpresets/autospec/topology.toml— clarify → research → design → task → critique spec looppresets/autodoc/topology.toml— audit → write → check → publish cyclepresets/autoresearch/topology.toml— hypothesis → implement → measure → evaluatepresets/autosec/topology.toml— scan → analyze → harden → reportpresets/autofix/topology.toml— diagnose → fix → verify → close with re-open support