Lifecycle Hooks

Observe and control agent execution at well-defined points in the conversation loop.


Overview

Lifecycle hooks let you register handlers on an agent version that fire during a chat — for example, to log tool calls, inject context, or block a tool before it runs. Hooks are configured on the agent version alongside tools and skills.

Each hook listens for one event and dispatches to a webhook URL, task agent, or gate (human-in-the-loop interrupt). Webhook handlers can return a decision or context injection synchronously; gate hooks pause the run until an external resolver calls the Interrupt gates API.

Approval settings — built-in human-in-the-loop for individual tools


Configuration

Add a hooks array to the agent version when creating or updating an agent via the Agents API or SDK:

1from inferencesh import inference, lifecycle_hook, HookEvent23client = inference(api_key="inf_your_key")45client.agents.create({6    "name": "guarded-agent",7    "core_app": {"ref": "infsh/claude-sonnet-4@latest"},8    "hooks": [9        lifecycle_hook(HookEvent.TOOL_CALL)10            .webhook("https://example.com/hooks/tool-call")11            .timeout(10)12            .build(),13        lifecycle_hook(HookEvent.TURN_START)14            .task("my-team/auditor@latest")15            .async_(True)16            .build(),17    ],18})

Hook fields

FieldTypeDescription
eventstringLifecycle event to listen for (see Events)
type"webhook" | "task" | "gate"How the handler is invoked
handlerstringWebhook URL (webhook) or agent ref (task). Omitted for gate.
asyncbooleanRun without blocking the agent loop (default false; not used for gate)
timeoutintegerHandler timeout in seconds. Webhook default 30 (max 300). Gate default 300 (max 86400).
default_resolution"allow" | "deny"Gate only — resolution applied when timeout elapses (default "allow"). See Gate expiry.

Webhook handlers receive a POST with Content-Type: application/json and may return a JSON response body. Task handlers receive the payload as the input text of a new message to the referenced agent; task hooks do not return decisions. Gate handlers create an interrupt with reason: hook_gate, store the hook payload in meta, and suspend the run until you resolve it via POST /interrupts/:id/resolve.

Set async: true to fire-and-forget — async hooks never block execution or apply injections.

GET /agents/hook-events returns the canonical event list with can_gate flags. Requires the agents:read scope.


Events

EventWhen it firesEvent dataWebhook gateGate hook
agent.startFirst turn of a chat (turn count 0)Blocks turn on deny or stopYes
agent.turn_startBefore each LLM turnBlocks turn on deny or stopYes
agent.tool_callBefore a tool executesTool name and argumentsBlocks that tool on denyYes
agent.tool_resultAfter an async app tool completesTool name, status, resultObserve onlyNo
agent.turn_completeAfter the assistant message, before tool dispatchObserve onlyNo
agent.post_compactAfter context auto-compactionObserve onlyNo
agent.errorWhen the agent run failsError messageObserve onlyNo
agent.completeWhen the active run finishesObserve onlyNo

The following events are defined in the API but not emitted yet: agent.pre_compact (supports gate hooks when wired), agent.idle.


Request payload

Every hook handler receives the same envelope:

json
1{2  "event": "agent.tool_call",3  "timestamp": "2026-08-11T14:00:00Z",4  "agent_id": "ag_abc123",5  "chat_id": "ch_def456",6  "run_id": "ar_ghi789",7  "turn_count": 3,8  "data": {9    "tool": "delete_file",10    "arguments": { "path": "/tmp/old.txt" }11  }12}

data shape depends on the event:

Eventdata fields
agent.tool_calltool (string), arguments (object)
agent.tool_resulttool (string), status (string), result (string)
agent.errorerror (string)

Response

Synchronous webhook handlers may return JSON. An empty 200 response is treated as { "decision": "allow" }.

json
1{2  "decision": "allow",3  "reason": "optional explanation",4  "inject": {5    "content": "Reminder: Only delete files in /tmp.",6    "role": "system",7    "ttl_turns": 5,8    "dedup_key": "policy-reminder"9  },10  "system": "Permanent system note from this handler."11}

Decisions

DecisionEffect
allowContinue (default)
denyBlock the gated action
stopSame as deny for turn-level gates (agent.start, agent.turn_start)
suspendPause the run until the interrupt is resolved (gate hooks only)

Turn gates (agent.start, agent.turn_start): returning deny or stop aborts the turn. The agent receives an error such as turn blocked: <reason>. A gate hook on these events creates an interrupt and suspends the run instead.

Tool gate (agent.tool_call): returning deny skips execution for that invocation. The tool is completed with status failed and your reason (defaults to "blocked by hook" if omitted). The agent sees the failure and can try an alternative. stop does not block tool execution — use deny. A gate hook sets the tool invocation to awaiting_approval and suspends the run until resolved.

Only the first matching hook that returns deny, stop, or suspend takes effect; remaining hooks for that event are skipped.

Context injection

inject adds ephemeral content to the chat context on the next turn. Injections are stored as chat messages and respect ttl_turns (0 = permanent until replaced). Use dedup_key to replace a prior injection from the same key.

system is shorthand for a permanent injection keyed by the handler URL.


Blocking tool calls

Use agent.tool_call with a synchronous webhook to implement programmatic guardrails — for example, blocking destructive tools unless your policy service approves:

json
1// POST body from platform  your webhook2{3  "event": "agent.tool_call",4  "data": { "tool": "delete_file", "arguments": { "path": "/etc/passwd" } }5}67// Your response to block8{ "decision": "deny", "reason": "path not allowed" }

The platform marks the invocation failed with your reason and does not call the tool. This is separate from per-tool approval, which pauses for a human in the UI or CLI.


Observing tool results

agent.tool_result fires after an async app tool completes (when the backing task finishes and the platform records the result). The payload includes the tool name, status (completed), and the result string.

Today this event is emitted for async app-tool completions only. Synchronous tools (client tools, call tools, MCP, sub-agents that return inline) do not emit agent.tool_result.


Handler types

Webhook

json
1{2  "event": "agent.tool_call",3  "type": "webhook",4  "handler": "https://example.com/hooks/tool-call",5  "timeout": 156}

Your endpoint must return HTTP 2xx. Return a JSON body to apply decisions or injections (sync only).

Task

json
1{2  "event": "agent.turn_start",3  "type": "task",4  "handler": "my-team/auditor@latest",5  "async": true6}

The platform sends the full hook payload as the task agent's input text. Task hooks are useful for audit logging or routing events to a dedicated reviewer agent. They cannot return decision or inject — use a webhook or gate for gating.

Gate

Gate hooks pause the agent run and create an interrupt with reason: hook_gate. Use them when a human or external system must approve before execution continues — for example, reviewing a destructive tool call in your own UI.

json
1{2  "event": "agent.tool_call",3  "type": "gate",4  "timeout": 600,5  "default_resolution": "deny"6}

When the hook fires:

  1. The platform creates an interrupt linked to the active run (source is hook:<event>). For agent.tool_call, resource_type is tool_invocation and resource_id is the tool invocation ID; for turn-level gates, resource_type is hook_event.
  2. The full hook payload is stored on the interrupt's meta field.
  3. The run enters input_required with interrupt_reason: hook_gate.
  4. For agent.tool_call, the related tool invocation moves to awaiting_approval.

Resolve the gate with POST /interrupts/:id/resolve (allow or deny). Optional data is stored as resolved_data. Resolving with allow invokes the tool; deny rejects it. The run resumes only after all pending interrupts for that run are resolved — multiple gates on the same run are resolved independently.

Gate expiry

When a gate interrupt is created, the platform schedules a one-shot scheduled trigger (action: resolve_interrupt, scheduled_at = now + timeout) whose input includes the interrupt ID and default_resolution (default allow). At expiry, this trigger resolves the interrupt the same way as a manual resolve — invoking or rejecting the tool for tool_invocation gates. Each gate on the same run gets its own expiry trigger. A background expiry sweep remains as a safety net for orphaned interrupts.

See Triggers API — resolve_interrupt action.

See Interrupt gates for list/resolve endpoints, SDK helpers, and belt interrupt list / belt interrupt resolve CLI commands.

bash
1# List pending gates for a run2belt interrupt list ar_abc12334# Approve and resume (optional data stored as resolved_data)5belt interrupt resolve int_abc123 allow

Gate hooks require an active run and chat — they cannot fire outside a running conversation.

Configure gate hooks in Python by setting type on the hook dict (the lifecycle_hook() builder does not yet expose a .gate() helper):

python
1from inferencesh.types import HookEvent, HookHandlerType, InterruptResolution23{4    "event": HookEvent.TOOL_CALL,5    "type": HookHandlerType.HOOK_HANDLER_GATE,6    "timeout": 600,7    "default_resolution": InterruptResolution.ALLOW,8}

Example: policy webhook

python
1# Flask handler (illustrative)2from flask import request, jsonify34@app.post("/hooks/tool-call")5def on_tool_call():6    payload = request.json7    tool = payload["data"]["tool"]8    args = payload["data"].get("arguments", {})910    if tool == "delete_file" and not str(args.get("path", "")).startswith("/tmp/"):11        return jsonify({"decision": "deny", "reason": "only /tmp deletions allowed"})1213    return jsonify({"decision": "allow"})

Register it on the agent:

python
1lifecycle_hook(HookEvent.TOOL_CALL)2    .webhook("https://api.example.com/hooks/tool-call")3    .build()

  • Approval settings — per-tool require_approval() for human review
  • Interrupt gates — list and resolve hook_gate interrupts
  • App tools — async app tools that complete via task callbacks
  • Agents API — create and update agent versions with hooks
  • Streaming — real-time tool_call / tool_result SSE events in the chat stream

we use cookies

we use cookies to ensure you get the best experience on our website. for more information on how we use cookies, please see our cookie policy.

by clicking "accept", you agree to our use of cookies.
learn more.