Skip to content

Hook Event Catalog

Complete reference for CompozyOS hook events, payload schemas, patch schemas, sync eligibility, and example payloads.

For people running agent work3 pages in this section

CompozyOS hooks fire at named runtime points such as session creation, prompt assembly, tool execution, permission decisions, automation runs, and the agent's own context compaction. Each event has a fixed payload type and a fixed patch type. A hook reads the payload from stdin, decides whether it applies, and writes a JSON patch to stdout.

Runtime Placement

Rendering diagram…

Hook events sit on the runtime path where CompozyOS can either control an operation or observe it.

Reading This Catalog

ColumnMeaning
EventThe exact value used in a hook declaration.
Syncyes means a declaration may set mode = "sync". async only means sync declarations are rejected.
PayloadThe JSON object type delivered to the executor.
PatchThe JSON object type the executor may write to stdout. Empty stdout means an empty patch.
Fires whenThe runtime point that dispatches the event.

Most session-scoped payloads include common session fields:

FieldMeaning
eventCurrent event name.
timestampDispatch timestamp.
session_id, session_name, session_typeSession identity and kind when available.
agent_nameAgent attached to the session or runtime operation.
workspace_id, workspaceWorkspace identity and root path when available.
turn_idCurrent turn when the event is turn-scoped.

Control patches use deny and deny_reason. A deny patch stops the sync hook chain. For pre-operation control points, CompozyOS returns the event-specific denial error to the caller.

Session Events

EventSyncPayloadPatchFires when
session.pre_createyesSessionPreCreatePayloadSessionCreatePatchBefore CompozyOS creates a session record.
session.post_createyesSessionPostCreatePayloadSessionPostCreatePatchAfter CompozyOS creates a session record.
session.pre_resumeyesSessionPreResumePayloadSessionPreResumePatchBefore CompozyOS resumes an existing session.
session.post_resumeyesSessionPostResumePayloadSessionPostResumePatchAfter CompozyOS resumes an existing session.
session.pre_stopyesSessionPreStopPayloadSessionPreStopPatchBefore CompozyOS stops a session.
session.post_stopyesSessionPostStopPayloadSessionPostStopPatchAfter CompozyOS stops a session.
session.runtime_recovery.startedasync onlySessionRuntimeRecoveryStartedPayloadAuthoredContextObservationPatchBefore one automatic recovery attempt.
session.runtime_recovery.succeededasync onlySessionRuntimeRecoverySucceededPayloadAuthoredContextObservationPatchAfter the replacement runtime accepts the turn.
session.runtime_recovery.exhaustedasync onlySessionRuntimeRecoveryExhaustedPayloadAuthoredContextObservationPatchAfter the final automatic recovery attempt fails.
session.attention.changedasync onlySessionAttentionChangedPayloadSessionAttentionObservationPatchAfter a canonical session-attention edge is committed.

Session lifecycle patches can set session_name, session_type, agent_name, workspace_id, or workspace. session.pre_create cannot change a session into or out of the daemon-owned coordinator and spawned types. After creation, hooks cannot change the session type or workspace identity. Pre-create, pre-resume, and pre-stop hooks can also deny the operation. session.attention.changed is observation-only and carries from, to, class, and at; hook failure does not change the committed session state. Runtime recovery hooks are also observation-only. Their payload carries the turn, attempt limit, runtime generation, failure kind, and a bounded redacted failure detail. Hook failure never blocks or rolls back recovery.

Input And Prompt Events

EventSyncPayloadPatchFires when
input.pre_submityesInputPreSubmitPayloadInputPreSubmitPatchBefore a user message is submitted to the agent turn.
prompt.post_assembleyesPromptPayloadPromptPatchAfter CompozyOS assembles the final prompt and before it reaches the agent.

input.pre_submit patches can replace message, append or replace context_blocks, or deny the submission. Its attachments field exposes only read-only id, name, mime, bytes, and kind metadata; file contents never enter the hook payload or patch. Its read-only mentions field (InputMentionMetadata: kind, token, target, label, start, end) lists the mentions CompozyOS verified; after a message rewrite only mentions whose exact token is still present survive, and a patch cannot add one. prompt.post_assemble patches can replace prompt, adjust context_blocks, or deny the assembled prompt.

Event Persistence Events

EventSyncPayloadPatchFires when
event.pre_recordasync onlyEventPreRecordPayloadEventPreRecordPatchBefore an ACP/runtime event is persisted.
event.post_recordasync onlyEventPostRecordPayloadEventPostRecordPatchAfter an ACP/runtime event is persisted.

Event-record hooks are observation hooks. Their payload includes record_type, sequence, and raw content. Their patch type accepts labels, but the current dispatch path does not mutate the stored event record from that patch.

Automation Events

EventSyncPayloadPatchFires when
automation.job.pre_fireyesAutomationJobPreFirePayloadAutomationFirePatchBefore a scheduled or manually triggered job dispatches.
automation.job.post_fireasync onlyAutomationJobPostFirePayloadAutomationObservationPatchAfter a job hands work to a session or run.
automation.trigger.pre_fireyesAutomationTriggerPreFirePayloadAutomationFirePatchBefore a trigger dispatches an automation run.
automation.trigger.post_fireasync onlyAutomationTriggerPostFirePayloadAutomationObservationPatchAfter a trigger hands work to a session or run.
automation.run.completedasync onlyAutomationRunCompletedPayloadAutomationObservationPatchAfter an automation run completes successfully.
automation.run.failedasync onlyAutomationRunFailedPayloadAutomationObservationPatchAfter an automation run fails.

Automation pre-fire patches can replace prompt or set cancel = true. Observation patches are empty structs and are used only to record hook execution.

Autonomy Events

Autonomy hooks cover coordinator bootstrap, task-run leases, and safe child-session spawn. They do not replace the authority of task-run leases or spawn policy. A hook can deny or narrow the exposed pre-operation payload where a control patch exists, but it cannot mint raw claim tokens, complete a task run through prompt text, bypass TTL, widen permissions, or bypass child depth and child count limits.

Coordinator

EventSyncPayloadPatchFires when
coordinator.pre_spawnyesCoordinatorPreSpawnPayloadCoordinatorSpawnPatchBefore CompozyOS creates a managed coordinator session.
coordinator.spawnedyesCoordinatorSpawnedPayloadCoordinatorObservationPatchAfter a managed coordinator session is created.
coordinator.decisionyesCoordinatorDecisionPayloadCoordinatorObservationPatchWhen a coordinator records a semantic coordination decision.
coordinator.stoppedyesCoordinatorStoppedPayloadCoordinatorObservationPatchAfter a managed coordinator session stops.
coordinator.failedyesCoordinatorFailedPayloadCoordinatorObservationPatchAfter coordinator bootstrap or lifecycle handling fails.

CoordinatorSpawnPatch may deny coordinator spawn or replace agent_name, provider, and model. Observation patches are empty and record hook execution only.

Task

These are task-level state transitions, deliberately outside the run-level task.run.* namespace — they describe the task's blocking and escalation state, not a run's lifecycle.

EventSyncPayloadPatchFires when
task.blockedyesTaskBlockedPayloadTaskObservationPatchAfter a typed block is created on a task.
task.unblockedyesTaskUnblockedPayloadTaskObservationPatchAfter a block is cleared explicitly or by transient expiry.
task.needs_attentionyesTaskNeedsAttentionPayloadTaskObservationPatchAfter the unblock-loop breaker escalates a task.
task.recoveredyesTaskRecoveredPayloadTaskObservationPatchAfter an operator or agent recovers an escalated task.

TaskBlockedPayload and TaskUnblockedPayload carry block_id, kind, reason, opaque details, and (on unblock) cleared_at / clear_note. TaskNeedsAttentionPayload and TaskRecoveredPayload carry the escalation reason / recovery note plus at. All four share the TaskContext fields (task_id, workspace_id, task_status, actor/origin identity, and — when a run was parked — run_id and claim_token_hash). TaskObservationPatch is empty; these hooks may annotate or deny but cannot mint claim tokens or bypass the breaker. The raw bearer lease token never appears in these payloads. See the block lifecycle for the operator and agent surfaces that drive these transitions.

Task Run

EventSyncPayloadPatchFires when
task.run.enqueuedyesTaskRunEnqueuedPayloadTaskRunObservationPatchAfter a task run is enqueued and its audit event is committed.
task.run.pre_claimyesTaskRunPreClaimPayloadTaskRunPreClaimPatchBefore a task-run claim transaction commits.
task.run.post_claimyesTaskRunPostClaimPayloadTaskRunObservationPatchAfter a task-run claim and audit event commit.
task.run.lease_extendedyesTaskRunLeaseExtendedPayloadTaskRunObservationPatchAfter a token-fenced heartbeat extends a task-run lease.
task.run.lease_expiredyesTaskRunLeaseExpiredPayloadTaskRunObservationPatchAfter a task-run lease reaches expiry.
task.run.lease_recoveredyesTaskRunLeaseRecoveredPayloadTaskRunObservationPatchAfter lease recovery requeues or finalizes expired work.
task.run.releasedyesTaskRunReleasedPayloadTaskRunObservationPatchAfter a token-fenced task-run lease release.
task.run.completedyesTaskRunCompletedPayloadTaskRunObservationPatchAfter a token-fenced task-run completion.
task.run.failedyesTaskRunFailedPayloadTaskRunObservationPatchAfter a token-fenced task-run failure.

TaskRunPreClaimPatch may deny the claim, add required capabilities, or raise priority_min. It cannot remove task-run requirements or change the selected run after the claim transaction commits. The raw bearer lease token never appears in these hook payloads.

Spawn

EventSyncPayloadPatchFires when
spawn.pre_createyesSpawnPreCreatePayloadSpawnCreatePatchBefore CompozyOS creates a child session from an agent-facing request.
spawn.createdyesSpawnCreatedPayloadSpawnObservationPatchAfter a child session is created.
spawn.parent_stoppedyesSpawnParentStoppedPayloadSpawnObservationPatchWhen parent-stop cleanup reaps a child session.
spawn.ttl_expiredyesSpawnTTLExpiredPayloadSpawnObservationPatchWhen TTL expiry reaps a child session.
spawn.reapedyesSpawnReapedPayloadSpawnObservationPatchAfter child-session reap cleanup completes.

SpawnCreatePatch may deny the spawn or narrow the child request by replacing agent_name, spawn_role, ttl_seconds, or child_permissions. The runtime still rejects permission widening, coordinator-role spawn, unauthorized cross-workspace spawn, and invalid TTL/depth/child caps.

spawn.pre_create also runs before every subagent delegation. Those payloads carry spawn_role: "subagent" and a subagent object with title, role, and task_chars (the task length, never the task text), plus read-only isolation and worktree_id. A patch that rebinds isolation or the worktree is rejected with capability_denied. The same deny and narrowing rules apply; a patch that sets a TTL for the subagent role is rejected with capability_denied, because subagents have no TTL.

Subagent

EventSyncPayloadPatchFires when
subagent.settlednoSubagentSettledPayloadSubagentObservationPatchWhen a subagent reaches a terminal status, after its record is committed.

subagent.settled is observe-only: a hook cannot change the status, the result, or the parent's wake. It fires once per subagent for both delegated and provider-native subagents. Its payload additively includes isolation and optional worktree facts (identity, branch, base, Git and PR snapshot); unavailable settlement facts remain absent.

FieldMeaning
event, workspace_idThe event name and the subagent's workspace.
subagent_idThe subagent record id (sub-…).
parent_session_id, child_session_idThe delegating session and the subagent's own session; child_session_id is null for provider-native subagents.
origindelegated or provider_native.
statuscompleted, failed, canceled, or interrupted.
runtimeprovider and model the subagent ran on.
duration_msTime from delegation to settle.
isolationshared or worktree.
worktreeOptional checkout identity, branch, base and available Git/PR settlement facts; unknown fields are omitted.

Agent Process Events

EventSyncPayloadPatchFires when
agent.pre_startyesAgentPreStartPayloadAgentStartPatchBefore CompozyOS starts the ACP agent process.
agent.spawnedyesAgentSpawnedPayloadAgentSpawnedPatchAfter the agent process is spawned.
agent.crashedyesAgentCrashedPayloadAgentCrashedPatchWhen the agent process exits unexpectedly.
agent.stoppedyesAgentStoppedPayloadAgentStoppedPatchAfter the agent process stops.

agent.pre_start patches can replace command, args, or cwd, or deny process startup. The post-start lifecycle patches accept labels for observation.

Turn And Message Events

EventSyncPayloadPatchFires when
turn.startyesTurnStartPayloadTurnStartPatchWhen an agent turn starts.
turn.endyesTurnEndPayloadTurnEndPatchWhen an agent turn ends.
message.startyesMessageStartPayloadMessageStartPatchWhen a streamed message begins.
message.deltaasync onlyMessageDeltaPayloadMessageDeltaPatchFor streaming message deltas.
message.endyesMessageEndPayloadMessageEndPatchWhen a streamed message finishes.

Turn patches can set labels and include the common deny fields. A turn deny stops the hook chain; the current turn events do not return a user-visible denial error. Message patches can replace role, delta_type, or text; only message.start and message.end can run sync hooks.

Tool Events

EventSyncPayloadPatchFires when
tool.pre_callyesToolPreCallPayloadToolCallPatchBefore a tool call executes.
tool.post_callyesToolPostCallPayloadToolResultPatchAfter a tool call succeeds.
tool.post_erroryesToolPostErrorPayloadToolPostErrorPatchAfter a tool call fails.

Tool payloads include tool_call_id, the canonical tool_id, read_only, and tool_input. Pre-call patches expose tool_id, read_only, and tool_input, plus the common deny fields. Post-call and post-error patches can replace title, tool_result, or error; deny fields on post events stop the hook chain but do not undo an already completed tool result.

Permission Events

EventSyncPayloadPatchFires when
permission.requestyesPermissionRequestPayloadPermissionRequestPatchBefore a permission decision resolves.
permission.resolvedasync onlyPermissionResolvedPayloadPermissionResolvedPatchAfter a permission decision resolves.
permission.deniedasync onlyPermissionDeniedPayloadPermissionDeniedPatchAfter a permission denial resolves.

permission.request patches can set decision, decision_class, and reason, or deny the request. CompozyOS rejects escalation patches that try to turn a blocked, denied, or rejected decision into an allow decision; those hook runs are recorded as rejected. Request and resolved payloads include tool_input; denied payloads omit it so rejected input is not exposed to async hooks.

Context Events (experimental)

EventSyncPayloadPatchFires when
context.pre_compactyesContextPreCompactPayloadContextPreCompactPatchOn the first update CompozyOS observes for an agent compaction id.
context.post_compactyesContextPostCompactPayloadContextPostCompactPatchAt the first terminal status (completed, failed, or cancelled) for that id.

Context events are observation-only and experimental: they follow the unstable ACP compaction contract and may change. CompozyOS never compacts a session itself; the agent owns its context window. These hooks fire only when the agent honors the ACP compaction capability, once per compaction id (post_compact at most once), whether a person requested the compaction with Compact now or the agent started it on its own. They never fire for an agent that does not honor the capability. When the first update CompozyOS observes for an id is already terminal, both events fire for it, context.pre_compact first.

Both events run after CompozyOS has recorded the observed compaction snapshot, so the hook sees a compaction that already happened. A failing hook, including a required one, is logged as a session: hook dispatch failed warning and never changes the compaction or the turn.

The two events share one payload shape and one patch shape. In the daemon they are the single types ContextCompactionPayload and ContextCompactionPatch; ContextPreCompactPayload, ContextPostCompactPayload, and the matching ContextPreCompactPatch and ContextPostCompactPatch are aliases of them. Hook introspection (compozy hooks events, GET /api/hooks/events) names the event-specific types, and the Go and TypeScript SDKs generate both the event-specific types and the shared pair with identical fields.

FieldEventsMeaning
event, timestampbothThe event name and the time of the agent update that triggered the dispatch.
Session fieldsbothThe common session fields above, plus profile_id, worktree_id, acp_session_id, state, soul_snapshot_id, soul_digest, created_at, and updated_at when available.
turn_idbothThe turn that carried the compaction.
compaction_idbothThe compaction's identifier. Both events for one compaction carry the same value.
triggerbothrequested when the compaction happened on a turn CompozyOS started to ask for it (Compact now, compozy session compact, the compozy__session_compact tool, or a Goal); agent for every other.
statuspostThe first terminal status: completed, failed, or cancelled. Absent on context.pre_compact.
summarypostThe agent's summary text, when it sent one.
errorpostThe agent's error text, when it sent one.

The patch has one optional field, labels, a map of string keys to string values. It is accepted and applied as a no-op annotation: it does not change the payload, a later hook's input, or the compaction, and the hook run keeps the patch only while the daemon log level is debug. The patch is decoded strictly, so a patch with any other field (deny, deny_reason, summary, or context_blocks, for example) is invalid. The hook run is recorded as failed with the error hooks: decode patch for hook "<name>": json: unknown field "<field>". A sync hook that is not required is skipped and the chain continues, a required sync hook ends the chain, and an async hook logs hook.dispatch.async_failed. In every case the compaction continues, because a hook cannot veto, delay, or rewrite an agent's compaction.

Example Payloads And Patches

Deny a risky permission request

permission.request payload:

{
  "event": "permission.request",
  "timestamp": "2026-04-16T15:04:05Z",
  "session_id": "sess_9f4a",
  "agent_name": "coder",
  "workspace_id": "ws_checkout",
  "turn_id": "turn_42",
  "request_id": "perm_123",
  "action": "write",
  "resource": "/Users/you/src/checkout/.env",
  "decision": "ask",
  "decision_class": "write",
  "tool_input": {
    "path": "/Users/you/src/checkout/.env"
  },
  "tool_call": {
    "id": "toolu_123",
    "kind": "edit_file",
    "title": "Edit .env"
  }
}

PermissionRequestPatch denying the operation:

{
  "deny": true,
  "deny_reason": "Secrets files require manual review.",
  "decision": "deny",
  "decision_class": "write",
  "reason": "Protected file pattern matched."
}

Add workspace context to a prompt

prompt.post_assemble payload:

{
  "event": "prompt.post_assemble",
  "timestamp": "2026-04-16T15:04:05Z",
  "session_id": "sess_9f4a",
  "agent_name": "coder",
  "workspace_id": "ws_checkout",
  "workspace": "/Users/you/src/checkout",
  "turn_id": "turn_42",
  "input_class": "user",
  "prompt": "Review the current diff.",
  "context_blocks": [
    {
      "kind": "workspace",
      "text": "Repository: checkout"
    }
  ]
}

PromptPatch replacing the assembled prompt:

{
  "prompt": "[Workspace: checkout]\n\nReview the current diff."
}

Cancel an automation fire

automation.job.pre_fire payload:

{
  "job_id": "job_daily_review",
  "job_name": "weekday-review",
  "agent_name": "reviewer",
  "workspace_id": "ws_checkout",
  "prompt": "Review the repository.",
  "schedule": {
    "mode": "cron",
    "expr": "0 9 * * 1-5"
  },
  "attempt": 1
}

AutomationFirePatch canceling dispatch:

{
  "cancel": true
}

Observe a finished compaction

A config declaration that runs for compactions the agent started on its own:

[[hooks.declarations]]
name = "audit-agent-compaction"
event = "context.post_compact"
mode = "sync"
timeout = "3s"
command = "./hooks/audit-compaction"

[hooks.declarations.matcher]
compaction_trigger = "agent"

context.post_compact payload (ContextPostCompactPayload):

{
  "event": "context.post_compact",
  "timestamp": "2026-10-08T14:02:19Z",
  "session_id": "sess_9f4a",
  "agent_name": "coder",
  "workspace_id": "ws_checkout",
  "workspace": "/Users/you/src/checkout",
  "state": "active",
  "created_at": "2026-10-08T13:10:02Z",
  "updated_at": "2026-10-08T14:02:19Z",
  "turn_id": "turn_42",
  "compaction_id": "c1f0b8f4-6a52-4d7e-9b0c-2f3a8d1e5c77",
  "trigger": "agent",
  "status": "completed",
  "summary": "Kept the checkout refactor plan and the open review comments."
}

ContextPostCompactPatch attaching a label. It is the only field the patch accepts, and it has no effect on the compaction:

{
  "labels": {
    "audited": "true"
  }
}

Introspection Surfaces

Use the CLI for local inspection:

compozy hooks events --family permission
compozy hooks events --sync-only -o json

Use the API when building tooling:

GET /api/hooks/events?family=permission&sync_only=true
GET /api/hooks/catalog?workspace=checkout&event=permission.request
GET /api/workspaces/ws_checkout/hooks/runs?session=sess_9f4a&event=permission.request&last=20

Related references:

Subagent Events

EventSyncPayloadPatchWhen
subagent.settlednoSubagentSettledPayloadSubagentObservationPatchA subagent reaches its terminal state.

This family is observe-only. The payload includes workspace_id, subagent_id, parent_session_id, nullable child_session_id, origin, status, runtime.provider, runtime.model, and duration_ms, in addition to the common event metadata.

Delegation also uses spawn.pre_create with spawn_role: "subagent" and a typed subagent object containing title, role, task_chars, and read-only isolation and worktree_id. Denying that hook, rebinding isolation/worktree, or rewriting a TTL for this role rejects delegation with capability_denied.

On this page