Subscribe to events, block a decision, and keep a broken plugin from taking down the loop.
View raw Markdown · llms.txt · For agents
plugins:
modules: [plugin.mjs, ./tools/loop-metrics.mjs]Each module is loaded once per stage run and gets the bus.
export default function register(bus) {
// Every event, or one type.
bus.on('*', (event) => metrics.count(event.type))
bus.on('worker.dispatched', (event) => {
console.log(`${event.issue} → ${event.provider}/${event.model} on ${event.branch}`)
})
// A gate of your own. Returning { block: true, reason } stops the action.
bus.hook('beforeMerge', ({ issue, pr, head }) => {
if (isChangeFreeze()) return { block: true, reason: `change freeze: ${issue} waits` }
return undefined
})
}The eight hooks
beforeDispatch · afterDispatch · beforeReview · afterReview · beforeMerge · afterMerge · onPause ·
onEscalate
They wrap loop-orchestration decisions, not what happens inside a worker's own CLI session — that loop is opaque, and a hook that claimed to see it would be lying.
Multiple listeners on one hook all run, and the first { block: true } wins. A listener that throws is treated
as a non-blocking no-op and its error is collected into the stage report: a broken plugin must not take down
the loop.
Events are typed
The names and fields come from LOOP_EVENT_TYPES — see the events reference. An
event not in that vocabulary cannot be emitted, so bus.on('some.typo') is a subscription that will never
fire, and the reference is the list of what will.
Two things a plugin should not do
- Write to the loop's state directory. The stages own those files and rewrite them atomically; a second writer is a race with no error message.
- Block on something slow. A hook runs inside the stage, and a stage has a scheduler budget (600 seconds for a precheck-run stage). Queue the slow work elsewhere and return.
Notifications are not plugins
A channel — a webhook or a local command — is configuration, not code: see events and plugins. Reach for a plugin when you need a decision (a gate) or a destination the two generic shapes cannot express.