---
title: Events
description: Every event the loop writes to its log and emits on its bus, with the fields it carries.
---

{/* Generated by `pnpm docs:generate`. Do not edit by hand: edit `src/loop/event-vocabulary.ts` and regenerate. */}

Every loop event is appended to `<stateDir>/events.ndjson` and emitted on the plugin bus, with `at` (ISO
timestamp) and `type` on top of the fields below. A plugin subscribes with
`bus.on('<type>' | '*', listener)`; a notification channel subscribes by listing types in
`notifications.events`.

The vocabulary is enforced by the compiler: `appendLoopEvent` accepts only these names, so an event that is not
here cannot be emitted — and a name here that nothing emits fails the test suite.

63 loop events.

## `contract.*`

| Event | Carries | What it means |
|---|---|---|
| `contract.escalated` | `issue`, `reasons`, `digest` | A contract was frozen but is not dispatchable; a human was asked to settle it. |
| `contract.failed` | `issue`, `error` | Contract generation failed on every candidate provider. |

## `plan.*`

| Event | Carries | What it means |
|---|---|---|
| `plan.voted` | `issue`, `cycle`, `approvals`, `votes` | One planning cycle finished: how many agents approved the plan, out of how many voted. |
| `plan.failed` | `issue`, `error` | Planning failed on every candidate provider. |
| `plan.escalated` | `issue`, `cycles`, `unresolved` | The cycles ran out without consensus; the unresolved objections are a human's to settle. |

## `worker.*`

| Event | Carries | What it means |
|---|---|---|
| `worker.dispatched` | `issue`, `worktree`, `worktreeId`, `branch`, `terminal`, `provider`, `model`, `contractDigest`, `briefDigest`, `command`, `briefAccepted`, `tuiIdle` | A worker was launched in its own worktree. Carries the whole dispatch record plus the command that ran. |
| `worker.dispatch-failed` | `issue`, `error` | The dispatch itself failed — worktree, terminal or brief — before any work started. |
| `worker.setup` | `issue`, `worktreeId`, `ok` | `project.setup.command` ran in the fresh worktree. |
| `worker.nudged` | `issue`, `kind`, `reason`, `worktreeId` | An idle worker was nudged in its own terminal — and also the terminal outcome `nudged`, which ends a delivery pass on the same issue. Two emissions, one name: `kind` is present on the first, `reason` on the second. |
| `worker.reactivated` | `issue`, `terminal`, `previousTerminal` | A stale terminal was relaunched for a worker that was still supposed to be working. |
| `worker.reopened` | `issue`, `pr`, `previousHead`, `head`, `previousOutcome` | A finished issue came back: a new head on a PR the loop had already closed out. |
| `worker.handed-off` | `issue`, `from`, `to`, `worktreeId`, `branch`, `reason`, `briefAccepted` | The task was handed to another provider in the same worktree, on the same branch — and also the terminal outcome `handed-off` that ends the delivery pass which did it. |
| `worker.ci-round` | `issue`, `pr`, `head`, `round` | CI is red; the failing checks went back to the worker as a fix round. |
| `worker.review-round` | `issue`, `pr`, `head`, `round` | The review found blocking issues; the findings went back to the worker as a fix round. |
| `worker.conflict-round` | `issue`, `pr`, `head`, `round` | The PR conflicts with the base branch; the rebase instruction went back to the worker. It costs no fix round. |
| `worker.merged` | `issue`, `reason`, `worktreeId` | Delivery finished for this issue: the pull request was merged and the issue closed out. |
| `worker.held` | `issue`, `reason`, `worktreeId` | Held for a human: protected paths, a secret-shaped file, a crossed layer boundary, or a gate the config demands. |
| `worker.blocked` | `issue`, `reason`, `worktreeId` | Blocked: the fix rounds ran out, or a circuit breaker stopped the dispatch. |
| `worker.stuck` | `issue`, `reason`, `worktreeId` | The worker stopped producing output for longer than the idle timeout and could not be revived. |
| `worker.abandoned` | `issue`, `reason`, `worktreeId` | The pull request was closed without merging, or the branch disappeared. |
| `worker.failed` | `issue`, `reason`, `worktreeId` | The delivery pass itself failed - a tool, a credential, an unexpected state. |
| `worker.waiting` | `issue`, `reason`, `worktreeId` | Nothing to do at this head: the pass ended waiting for CI, a push, or a human. |
| `worker.reviewed` | `issue`, `reason`, `worktreeId` | The review ran and the pass ended there, without merging. |
| `worker.fix-round` | `issue`, `reason`, `worktreeId` | The pass ended by sending the worker back to work. |

## `pr.*`

| Event | Carries | What it means |
|---|---|---|
| `pr.reviewed` | `issue`, `pr`, `head`, `status`, `blocking`, `provider`, `model`, `source` | A review ran against a PR, with the verdict and who gave it. `source` marks a PR that came from GitHub intake. |
| `pr.merged` | `issue`, `pr`, `head`, `sha` | The PR was squash-merged by the loop. |
| `pr.merge-refused` | `issue`, `pr`, `head`, `message` | GitHub refused the merge — branch protection, a required check, a race with another merge. |
| `pr.smoke-failed` | `issue`, `pr`, `head`, `detail` | The optional post-merge smoke failed. |

## `github-intake.*`

| Event | Carries | What it means |
|---|---|---|
| `github-intake.merged` | `pr`, `reason` | Merged by the loop. |
| `github-intake.held` | `pr`, `reason` | Held for a human. |
| `github-intake.blocked` | `pr`, `reason` | Out of fix rounds. |
| `github-intake.failed` | `pr`, `reason` | The pass over it failed. |
| `github-intake.stuck` | `pr`, `reason` | It stopped moving. |
| `github-intake.abandoned` | `pr`, `reason` | Closed without merging. |
| `github-intake.waiting` | `pr`, `reason` | Nothing to do at this head. |
| `github-intake.reviewed` | `pr`, `reason` | Reviewed without merging. |
| `github-intake.fix-round` | `pr`, `reason` | Sent back to its author. |
| `github-intake.nudged` | `pr`, `reason` | Its author was nudged. |
| `github-intake.handed-off` | `pr`, `reason` | It changed hands. |

## `release.*`

| Event | Carries | What it means |
|---|---|---|
| `release.waiting` | `head`, `branch`, `commits`, `issues`, `detail` | A batch is on the integration branch with nobody's approval behind it. Emitted once per head. |
| `release.promoted` | `head`, `branch`, `commits`, `issues`, `approvedBy` | The approved batch reached the release branch. |
| `release.deployed` | `head`, `branch` | The deploy command succeeded. |
| `release.failed` | `head`, `phase`, `detail` | Promotion or deploy failed; `phase` says which. |
| `release.smoke-failed` | `head`, `detail` | The post-deploy smoke failed. |
| `release.rolled-back` | `head`, `ok`, `detail` | The rollback ran after a failed smoke; `ok` says whether it worked. |

## `intake.*`

| Event | Carries | What it means |
|---|---|---|
| `intake.filed` | `issue`, `source`, `fingerprint`, `severity` | An external alert became a tracked issue. |

## `maintain.*`

| Event | Carries | What it means |
|---|---|---|
| `maintain.filed` | `issue`, `check`, `fingerprint` | A maintenance check became a tracked issue. |

## `memory.*`

| Event | Carries | What it means |
|---|---|---|
| `memory.recalled` | `issue`, `hits`, `docBridgeBefore`, `docBridgeAfter`, `approxCharsSaved`, `memoryDigest` | Approved memory was recalled into a prompt, with what it cost and what it saved. |
| `memory.auto-promoted` | `ids`, `remembered`, `digest` | Learnings the retro promoted into memory without a human in the middle (`memory.autoPromote`). |
| `memory.auto-promote-failed` | `error` | The automatic promotion itself failed; the learnings stay in the ledger. |

## `provider.*`

| Event | Carries | What it means |
|---|---|---|
| `provider.cooldown` | `provider`, `kind`, `until`, `source` | A provider was put on cooldown after a quota/auth/timeout failure. `source` names the caller when not a dispatch. |

## `queue.*`

| Event | Carries | What it means |
|---|---|---|
| `queue.claim-failed` | `issue`, `assignee`, `error` | The tracker refused the claim that reserves an issue for this loop. |

## `security.*`

| Event | Carries | What it means |
|---|---|---|
| `security.pii-detected` | `issue`, `source`, `kinds`, `count` | Text that looks like PII was found before it reached a model. |

## `issue.*`

| Event | Carries | What it means |
|---|---|---|
| `issue.paused` | `issue`, `kind`, `consecutive`, `reason` | An issue was paused after consecutive failures; it needs a human before it is tried again. |

## `stage.*`

| Event | Carries | What it means |
|---|---|---|
| `stage.paused` | `stage`, `reason`, `consecutiveFailures` | A whole stage was paused after consecutive failures. |

## `agent.*`

| Event | Carries | What it means |
|---|---|---|
| `agent.adopted` | `role`, `agent`, `detail` | The retro's agent-improvement pass adopted a dated note into the agent's instructions; the eval passed. |
| `agent.rejected` | `role`, `agent`, `detail` | Nothing was adopted: no installed agent for the role, or nothing to measure the change with. |
| `agent.needs-human` | `role`, `agent`, `detail` | A proposal was recorded for a human: a critical role, too many lines, or an agent that is code. |
| `agent.reverted` | `role`, `agent`, `detail` | The change was applied, the eval did not pass, and the file was put back exactly as it was. |

## `tuning.*`

| Event | Carries | What it means |
|---|---|---|
| `tuning.applied` | `path`, `from`, `to`, `metric`, `reason` | A tuning knob moved by itself after a retro, inside its declared range and justified by its declared metric. |
| `tuning.reverted` | `path`, `from`, `to`, `metric`, `reason` | The same knob moved back, because the metric that justified the change got worse. |

## `cost-guard.*`

| Event | Carries | What it means |
|---|---|---|
| `cost-guard.tripped` | `issue`, `reason` | The cost circuit breaker stopped a dispatch that burned more usage than `resilience.maxUsageDeltaPercent` allows. |

## `max-duration.*`

| Event | Carries | What it means |
|---|---|---|
| `max-duration.tripped` | `issue`, `reason` | The time circuit breaker stopped a dispatch older than `delivery.maxDispatchMinutes`. |

## Harness run events

A separate, older vocabulary: the run-level event log the harness keeps for a single verification run
(`HARNESS_EVENT_TYPES`), independent of the loop.

- `run.created`
- `state.transitioned`
- `context.attached`
- `verification.completed`
- `artifact.recorded`
- `approval.recorded`
- `authorization.recorded`
- `session.started`
- `session.resumed`
- `agent.turn.started`
- `policy.evaluated`
- `tool.approval.requested`
- `tool.approval.recorded`
- `tool.requested`
- `tool.execution.started`
- `tool.recovery.recorded`
- `tool.blocked`
- `tool.completed`
- `tool.failed`
- `session.ended`
