Skip to content
Loops
AGH RuntimeLoops

Loop hooks

The loop.* hook family — seven events, which two can block a Loop, and the payload every observer receives.

Audience
Operators running durable agent work
Focus
Loops guidance shaped for scanability, day-two clarity, and operator context.

Loops dispatch a loop.* hook family at the runtime call site, exactly like every other hook event — typed dispatch, not an event bus. Two of the seven events are sync-eligible and can block; the rest are observe-only. A broken hook is fail-open: the coordinator warns and continues, and only an explicit denial from a sync-eligible event stops the Loop.

The seven events

EventFires whenSync-eligibleA denial…
loop.startedA run is created and startedno
loop.generation.preBefore the coordinator plans a generationyesends the run failed
loop.generation.postAfter a generation plan is producedno
loop.gate.preBefore a gate or terminal decision commitsyesends the run blocked
loop.gate.postAfter a gate decisionno
loop.node.terminalA Loop node's task run reaches a terminal stateno
loop.terminalA run reaches a terminal outcomeno

loop.node.terminal is also the runtime's own progress signal — it advances last_progress_at and wakes the coordinator. loop.terminal is where the daemon promotes a queued sibling and wakes a parent that is awaiting a child Loop.

What a denial does

Only the two *.pre events can block, and each maps to a distinct terminal outcome:

  • loop.generation.pre deny → the run ends failed (reason hook_denied plus the denial's reason). Use this to stop a generation from ever planning — a policy or budget precondition.
  • loop.gate.pre deny → the run ends blocked. Use this to veto a gate or terminal decision — an external dependency the daemon cannot see.

Observe-only events cannot change the outcome; they receive an annotate-only patch.

Payload

Every loop.* payload carries the standard event base plus a loop context:

loop_run_id, parent_loop_run_id, workspace_id, loop_name, generation, task_id, run_id, run_kind, node_id, resolved_network_participation, agent_name, session_id, actor_kind, actor_id, origin_kind, and origin_ref.

resolved_network_participation is the immutable Local/Live snapshot owned by that Loop run. It contains any resolved channel and finite bounds; hooks must not infer participation from availability or from mutable channel metadata.

Events add their own fields on top:

  • loop.started / loop.terminalstatus, cause, reason_code, details.
  • loop.generation.pre / loop.generation.poststatus, reason_code, details, and (on the pre event) denied, deny_reason.
  • loop.gate.pre / loop.gate.postgate_id, decision, status, reason_code, details, and (on the pre event) denied, deny_reason.
  • loop.node.terminaltask_status, run_status, error, details.

Manage loop.* hooks with the agh__hooks_* tools and agh hooks CLI like any other event family. See hooks for the dispatch model and authoring surfaces.

On this page