Configure Agent Hooks (API)
You can now create and manage hooks directly in the portal without using the REST API. The portal provides a visual form and code editor — no curl commands needed.
A custom agent with a Stop hook that forces the agent to add a completion marker to every response. You'll configure it via the REST API, then test it in the portal's playground. Time: ~15 minutes.
This tutorial creates hooks on a custom agent (custom-agent-level hooks). These hooks only fire when that specific custom agent runs.
To create agent-level hooks that apply to the entire agent (all threads, all custom agents), use Builder → Hooks in the portal — see Create Hooks via Portal.
| Level | How to create | Scope |
|---|---|---|
| Agent level | Portal: Builder → Hooks | Applies to all threads and custom agents |
| Custom agent level | REST API (this tutorial) or Portal: Agent Canvas → Custom agent → Manage Hooks | Applies only to one custom agent |
Prerequisites
- An SRE Agent in Running state
- curl (or Postman) to call the REST API
- Azure CLI logged in (
az login) to get an access token
Before you start
This tutorial uses the REST API v2 to create hooks on a custom agent. The portal's YAML editor tab shows v1 format and doesn't display hooks configured via the API — but the hooks are still active. You can verify them in the Builder → Hooks page or the Test playground.
- Portal (Builder → Hooks): Best for agent-level hooks — visual form, no code needed
- API (this tutorial): Best for custom-agent-level hooks, CI/CD pipelines, or programmatic management
Step 1: Find your agent's API URL
Your agent's API base URL follows this pattern:
https://{agent-name}--{hash}.{hash}.{region}.azuresre.ai
To find it:
- Open sre.azure.com and click on your agent
- In the left sidebar, click Builder → Agent Canvas
- Open your browser's Developer Tools (F12 or right-click → Inspect)
- Go to the Network tab, filter by "api", and look for requests to a URL ending in
.azuresre.ai - The base URL is everything before
/api/...
Alternatively, check the iframe src attribute in the Elements tab — look for an <iframe> whose src starts with https://{agent-name}--.
Step 2: Get an access token
TOKEN=$(az account get-access-token \
--resource "59f0a04a-b322-4310-adc9-39ac41e9631e" \
--query accessToken -o tsv)
The resource ID 59f0a04a-b322-4310-adc9-39ac41e9631e is the SRE Agent API audience. Using https://management.azure.com will return an auth error.
Step 3: Create a custom agent with a Stop hook
This creates a custom agent called my_hooked_agent with a Stop hook that checks whether the response ends with === RESPONSE COMPLETE ===. If not, it rejects and tells the agent to add the marker.
AGENT_URL="https://your-agent--xxxxxxxx.yyyyyyyy.region.azuresre.ai"
curl -X PUT "${AGENT_URL}/api/v2/extendedAgent/agents/my_hooked_agent" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d @- << 'EOF'
{
"name": "my_hooked_agent",
"properties": {
"instructions": "You are a helpful assistant. Be concise.",
"handoffDescription": "",
"handoffs": [],
"enableVanillaMode": true,
"hooks": {
"Stop": [
{
"type": "prompt",
"prompt": "Check the agent response below.\n\n$ARGUMENTS\n\nDoes it end with === RESPONSE COMPLETE ===?\nIf yes: {\"ok\": true}\nIf no: {\"ok\": false, \"reason\": \"Add === RESPONSE COMPLETE === at the end.\"}",
"timeout": 30
}
]
}
}
}
EOF
You should get HTTP 202 Accepted with the full agent config in the response body.
Here's what the same configuration looks like in v2 YAML format (for reference):
api_version: azuresre.ai/v2
kind: ExtendedAgent
metadata:
name: my_hooked_agent
spec:
instructions: |
You are a helpful assistant. Be concise.
handoffDescription: ""
enableVanillaMode: true
hooks:
Stop:
- type: prompt
prompt: |
Check the agent response below.
$ARGUMENTS
Does it end with === RESPONSE COMPLETE ===?
If yes: {"ok": true}
If no: {"ok": false, "reason": "Add === RESPONSE COMPLETE === at the end."}
timeout: 30
How the Stop hook works:
$ARGUMENTSgets replaced with the hook context JSON (includes the agent's final response)- The LLM evaluates the prompt and returns
{"ok": true}or{"ok": false, "reason": "..."} - If rejected, the reason is injected as a user message and the agent continues working
- After 3 rejections (the default), the agent is forced to stop
Step 4: Test it in the portal
- Go to your agent in the portal → Builder → Agent Canvas
- Select the Test playground radio button
- Click the Subagent/Tool dropdown, find my_hooked_agent, and click Apply
- Type "What is 2+2?" in the chat and press Send
Watch what happens:
- The agent first responds with "4"
- The Stop hook evaluates → rejects (no completion marker)
- You see a "Thought process" step where the agent continues
- The final response appears: "4 === RESPONSE COMPLETE ==="
The hook worked — it forced the agent to add the marker before stopping.
Step 5: Add a PostToolUse hook for auditing
Now add a PostToolUse hook that logs every tool the agent uses. Update the same agent by sending a new PUT with both hooks:
curl -X PUT "${AGENT_URL}/api/v2/extendedAgent/agents/my_hooked_agent" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d @- << 'EOF'
{
"name": "my_hooked_agent",
"properties": {
"instructions": "You are a helpful assistant. Be concise.",
"handoffDescription": "",
"handoffs": [],
"enableVanillaMode": true,
"hooks": {
"Stop": [
{
"type": "prompt",
"prompt": "Check the agent response below.\n\n$ARGUMENTS\n\nDoes it end with === RESPONSE COMPLETE ===?\nIf yes: {\"ok\": true}\nIf no: {\"ok\": false, \"reason\": \"Add === RESPONSE COMPLETE === at the end.\"}",
"timeout": 30
}
],
"PostToolUse": [
{
"type": "command",
"matcher": "*",
"timeout": 30,
"failMode": "allow",
"script": "#!/usr/bin/env python3\nimport sys, json\ncontext = json.load(sys.stdin)\ntool = context.get('tool_name', 'unknown')\nprint(json.dumps({'decision': 'allow', 'hookSpecificOutput': {'additionalContext': f'[AUDIT] {tool} executed.'}}))"
}
]
}
}
}
EOF
matcher: "*" means this hook runs for every tool call. The script logs the tool name and injects an [AUDIT] message into the conversation.
To test: ask the agent a question that triggers a tool (e.g., "Run echo hello").
Step 6: Block dangerous commands
Add a second PostToolUse hook that blocks rm -rf, sudo, and chmod 777:
PostToolUse:
# Audit hook (runs for all tools)
- type: command
matcher: "*"
timeout: 30
failMode: allow
script: |
#!/usr/bin/env python3
import sys, json
context = json.load(sys.stdin)
tool = context.get('tool_name', 'unknown')
print(json.dumps({"decision": "allow",
"hookSpecificOutput": {"additionalContext": f"[AUDIT] {tool} executed."}}))
# Policy hook (only for shell tools)
- type: command
matcher: "Bash|ExecuteShellCommand"
timeout: 30
failMode: block
script: |
#!/usr/bin/env python3
import sys, json, re
context = json.load(sys.stdin)
command = context.get('tool_input', {}).get('command', '')
for pattern in [r'\brm\s+-rf\b', r'\bsudo\b', r'\bchmod\s+777\b']:
if re.search(pattern, command):
print(json.dumps({"decision": "block", "reason": f"Blocked: {pattern}"}))
sys.exit(0)
print(json.dumps({"decision": "allow"}))
Key differences from the audit hook:
matcher: "Bash|ExecuteShellCommand"— only runs for shell tools (pattern is anchored as^(Bash|ExecuteShellCommand)$)failMode: block— if the script itself crashes, the tool result is blocked (strict mode)- Returns
"block"with a reason when a dangerous pattern is found
Hook response formats
Prompt hooks return simple JSON:
{"ok": true}
{"ok": false, "reason": "Please fix X."}
Command hooks return expanded JSON:
{"decision": "allow"}
{"decision": "block", "reason": "Dangerous command."}
{"decision": "allow", "hookSpecificOutput": {"additionalContext": "Audit note."}}
Command hooks can also use exit codes instead of JSON:
| Exit code | Behavior |
|---|---|
0 with no output | Allow |
0 with JSON | Parse the JSON |
2 | Block — stderr becomes the reason |
| Other | Falls back to failMode |
A rejection without a reason is treated as approval. Always include reason when rejecting.
What you learned
- Custom-agent-level hooks are configured via the REST API v2 and apply only to that custom agent
- Agent-level hooks are created in Builder → Hooks and apply across the entire agent (see the portal tutorial)
- Stop hooks reject incomplete responses and force the agent to continue
- PostToolUse hooks audit tool usage and can block dangerous commands
- Prompt hooks use LLM evaluation; command hooks use scripts with exit codes
- Always include a
reasonwhen rejecting — without it, rejection is treated as approval
Troubleshooting
| Issue | Solution |
|---|---|
| Hooks not visible in portal YAML tab | Expected — the YAML tab shows v1 only. Custom-agent-level hooks created via API are active and visible in Builder → Hooks or the playground |
Unsupported kind: ExtendedAgent | Use v2 endpoint: PUT /api/v2/extendedAgent/agents/{name} |
Handoffs cannot be null | Add "handoffs": [] to the JSON payload |
| Auth error (audience validation) | Use resource 59f0a04a-b322-4310-adc9-39ac41e9631e for the token |
| Hook has no effect | Include a reason field when rejecting — without it, rejection = approval |
| Agent loops forever | Lower maxRejections (default: 3, range: 1–25) |
Related
| Resource | What you'll learn |
|---|---|
| Agent Hooks capability overview | Full hook reference, context schema, and limits |
| Create and Manage Hooks (Portal) | Create hooks visually in the portal UI |
| Run Modes | How hooks complement safety controls |
| Python Tools | Build custom tools your hooks can monitor |