Skip to content

Journal Reference

The journal is the canonical runtime source of truth for an autoloop loop. Every significant event — system lifecycle, agent actions, coordination, review, and chain execution — is appended as a single JSON line to .autoloop/journal.jsonl. Nothing is mutated or deleted; the file is append-only.

Higher-level views (scratchpad, coordination state, chain progress) are projections — derived by reading the journal and filtering/aggregating events.

File format

The journal is JSONL (one JSON object per line). There are two record shapes: system events and agent events.

System events

Emitted by the harness at lifecycle boundaries.

json
{"run": "swift-agent", "iteration": "3", "topic": "iteration.start", "fields": {"recent_event": "tasks.ready", "suggested_roles": "builder", "allowed_events": "review.ready,build.blocked", "backpressure": "", "prompt": "..."}}
FieldTypeDescription
runstringRun identifier. Default format is human-readable: "<word>-<word>" (e.g. "swift-agent"). Set core.run_id_format = "compact" for legacy timestamp-based ids or "counter" for sequential "run-1", "run-2", etc.
iterationstringIteration number. Empty for loop.start.
topicstringEvent type.
fieldsobjectTopic-specific payload (varies by event type).

Agent events

Emitted by the model via autoloop emit.

json
{"run": "swift-agent", "iteration": "3", "topic": "review.ready", "payload": "initial implementation complete", "source": "agent"}
FieldTypeDescription
runstringRun identifier.
iterationstringIteration number.
topicstringThe event name the agent chose to emit.
payloadstringFree-text summary provided by the agent.
sourcestringAlways "agent".

Coordination events

Coordination events use the agent event shape (with payload and source: "agent") but encode structured data inside the payload using key=value; pairs:

json
{"run": "swift-agent", "iteration": "2", "topic": "issue.discovered", "payload": "id=issue-1; summary=fix login bug; disposition=open; owner=builder;", "source": "agent"}

Event lifecycle

A loop run produces events in this order:

loop.start
  iteration.start          ─┐
  backend.start             │  repeated per
  [agent events]            │  iteration
  backend.finish            │
  iteration.finish         ─┘
  [review.start]           ─┐  optional,
  [review.finish]          ─┘  periodic
  [wave.* events]              optional, during structured parallel fan-out/join
  [event.invalid]              optional, on bad emit
loop.complete   or   loop.stop

Lifecycle events

TopicWhenFields
loop.startOnce at the beginning of a run.max_iterations, completion_promise, completion_event, review_every, objective
iteration.startStart of each iteration.recent_event, suggested_roles, allowed_events, backpressure, prompt
backend.startBefore invoking the backend.backend_kind, command, prompt_mode, timeout_ms
backend.finishAfter backend returns.exit_code, timed_out (boolean), output
iteration.finishEnd of each iteration.exit_code, timed_out (boolean), elapsed_s (integer seconds), output
review.startBefore a metareview review pass.kind ("metareview"), backend_kind, command, prompt_mode, prompt, timeout_ms
review.finishAfter review completes.kind ("metareview"), exit_code, timed_out (boolean), output
loop.completeLoop finished successfully.reason ("completion_event" or "completion_promise")
loop.stopLoop halted without completion.reason ("max_iterations", "backend_failed", or "backend_timeout"). The max_iterations variant also includes completed_iterations, stopped_before_iteration, and max_iterations. The backend_failed and backend_timeout variants also include iteration and output_tail.

Agent events (custom)

Agents emit events from the role's emits list (defined in topology.toml). These are the routing events that drive the handoff map:

bash
./.autoloop/autoloops emit review.ready "code is ready for review"
./.autoloop/autoloops emit task.complete "all work done"

Any event the agent emits is recorded with source: "agent" in the journal.

Coordination events

Coordination events are structured bookkeeping events that bypass backpressure validation — they are always accepted regardless of the allowed-events set. They track issue lifecycles, work slices, and context archival.

TopicPayload fieldsDescription
issue.discoveredid, summary, disposition, ownerA new issue was found.
issue.resolvedid, resolutionAn issue was resolved. Updates disposition to "resolved".
slice.startedid, descriptionA unit of work began. Status: "in-progress".
slice.verifiedidA slice passed verification. Status: "verified".
slice.committedid, commit_hashA slice was committed. Status: "committed". Links the slice to a git commit.
context.archivedsource_file, dest_file, reasonThe metareview archived stale context from a working file to a docs file.
chain.spawnchain_id, parent_id, steps, justificationA dynamic sub-chain was spawned from within a running loop.

Coordination payload fields use key=value; encoding inside the payload string.

Wave lifecycle events (structured parallelism)

When parallel.enabled = true, the harness records wave events for parallel branch execution. These use the system event shape.

TopicFieldsDescription
wave.startwave_id, trigger_topic, branch_count, opening_recent_event, opening_roles, opening_events, objectivesA parallel wave was opened.
wave.branch.startwave_id, branch_id, objective, routing_event, branch_roles, branch_eventsA branch within a wave began execution.
wave.branch.finishwave_id, branch_id, stop_reason, outputA branch completed.
wave.join.startwave_id, trigger_topic, branch_count, branch_outcomesAll branches finished; join phase began.
wave.failedwave_id, failed_branchesOne or more branches failed (non-timeout).
wave.timeoutwave_id, timed_out_branchesOne or more branches exceeded parallel.branch_timeout_ms.
wave.invalidtrigger_topic, reason, active_wave_id, opening_recent_eventA .parallel trigger was rejected (e.g. wave already active, parallelism disabled).

Chain events

Chain events are recorded in the journal when running multi-loop compositions via autoloop chain run. They use the system event shape.

TopicFieldsDescription
chain.startname, steps, step_countChain execution began.
chain.step.startstep, preset, preset_dir, work_dirA chain step launched its loop.
chain.step.finishstep, preset, stop_reasonA chain step completed.
chain.completename, steps_completed, outcomeChain execution finished.
chain.spawnchain_id, parent_id, steps, justificationDynamic sub-chain spawned (also a coordination event).

Structured parallel wave events

When structured parallelism is enabled and the parent emits explore.parallel or <allowed-event>.parallel, the harness appends wave lifecycle events to the parent journal.

TopicFieldsDescription
wave.startwave_id, trigger_topic, branch_count, opening_recent_event, opening_roles, opening_events, objectivesA new wave opened from the parent routing context.
wave.branch.startwave_id, branch_id, objective, routing_event, branch_roles, branch_eventsOne branch child run started.
wave.branch.finishwave_id, branch_id, stop_reason, elapsed_ms, outputOne branch child run finished.
wave.join.startwave_id, trigger_topic, branch_count, elapsed_ms, branch_outcomesThe parent reached the join barrier and is aggregating branch outcomes.
wave.join.finishwave_id, trigger_topic, joined_topic, routing_basis, resume_recent_event, resume_roles, resume_events, elapsed_msThe harness resolved the barrier and prepared the parent resume context.
wave.timeoutwave_id, timed_out_branchesOne or more branches exceeded parallel.branch_timeout_ms.
wave.failedwave_id, failed_branchesOne or more branches ended without a success stop reason.
wave.invalidtrigger_topic, reason, active_wave_id, opening_recent_eventA .parallel trigger payload or active-wave rule was invalid.

Notes:

  • only the harness may emit *.parallel.joined; those joined events are recorded as normal agent-style journal entries on the parent run after wave.join.finish
  • the parent launches branch children concurrently, but still waits at the join barrier until every launched branch is terminal
  • branch state stays isolated on disk under core.state_dir/waves/<wave-id>/... (default .autoloop/waves/<wave-id>/...), including spec.md, join.md, per-branch logs, and per-branch result artifacts
  • only one active wave exists at a time in v1

Backpressure and event validation

Autoloop uses soft routing with protocol backpressure. The model receives advisory routing suggestions but is not locked into a state machine. However, the event-emit boundary enforces constraints.

How validation works

  1. The topology's handoff map determines suggested roles from the most recent routing event.
  2. The allowed events are the union of all emits arrays from the suggested roles.
  3. When the agent emits an event (via autoloop emit or detected in backend output), it is checked against the allowed set.
  4. Coordination events bypass validation — they are always accepted.
  5. If the allowed-events list is empty (no topology or unmapped event), all events are accepted.

On invalid emit

When an agent emits a disallowed event, two things happen:

  1. An event.invalid record is appended to the journal with fields: recent_event, emitted (what the agent tried), suggested_roles, allowed_events.
  2. The emit command fails (non-zero exit) and prints a diagnostic to stderr: invalid event 'X'; recent event: 'Y'; suggested roles: ...; allowed next events: ...

The harness also checks for invalid events after each iteration completes (in case the agent emitted via some other path). If detected, the invalid event is logged and the loop re-prompts the agent with backpressure context injected into the next iteration's prompt.

Dual validation points

Invalid events are caught at two points:

  • At emit time — the autoloop emit command validates against AUTOLOOP_ALLOWED_EVENTS environment variable and rejects immediately.
  • After iteration — the harness scans the iteration's journal entries for the latest agent event and validates it against the topology. This catches events emitted through non-standard paths.

Completion detection

The loop checks for completion after each valid iteration, in order:

  1. Completion event — if the completion event (from topology.toml or autoloops.toml) appears in the run's journal topics and all required_events have also been seen, the loop completes with reason: "completion_event".
  2. Completion promise — if the backend output contains the completion_promise string literally, the loop completes with reason: "completion_promise".
  3. Otherwise, the next iteration begins.

Required events are cumulative across the entire run, not per-iteration.

Scratchpad projection

The scratchpad is a markdown view projected from iteration.finish events. It provides a running summary of what happened each iteration.

The rich inspect format is:

markdown
## Iteration 1

exit_code=0

<iteration output>

## Iteration 2

exit_code=0

<iteration output>

Each section is built from the exit_code and output fields of the corresponding iteration.finish journal entry. The scratchpad has two render targets:

  • Iteration prompts and metareview review prompts get a compact view that keeps the most recent iterations detailed and collapses older ones to short summaries.
  • autoloop inspect scratchpad --format md keeps the richer view for debugging.
  • Both views are scoped to the current run (filtered by run ID).

Run scoping

All journal operations filter to the current run ID. The run ID is set at loop.start and carried through as AUTOLOOP_RUN_ID in the environment. Multiple runs can coexist in the same journal file — each run's events are isolated by their run field.

The latest run is found by scanning backward for the most recent loop.start entry.

Inspecting the journal

The journal and its projections can be inspected via CLI:

bash
autoloop inspect journal --format json       # raw JSONL
autoloop inspect scratchpad --format md       # iteration summaries
autoloop inspect coordination --format md     # issues, slices, commits, archives
autoloop inspect metrics --format md          # per-iteration metrics table with summary
autoloop inspect metrics --format csv         # metrics as RFC 4180 CSV
autoloop inspect metrics --format json        # metrics as JSON array
autoloop inspect prompt 3 --format md         # iteration 3 prompt
autoloop inspect output 3 --format text       # iteration 3 output
autoloop inspect chain --format md            # chain execution state

JSON encoding

The journal uses Unicode escape sequences for special characters inside string values:

CharacterEncoding
\\u005c
"\u0022
newline\u000a
carriage return\u000d
tab\u0009

This keeps each journal entry on a single line while preserving the full content of prompts and outputs.