Skip to content
This repository was archived by the owner on Jun 3, 2026. It is now read-only.

Commit c806606

Browse files
committed
feat: add interventions primitive
1 parent 9f780d6 commit c806606

3 files changed

Lines changed: 494 additions & 0 deletions

File tree

site/src/config/navigation.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ sidebar:
8686
- docs/user-guide/concepts/plugins/skills
8787
- docs/user-guide/concepts/plugins/steering
8888
- docs/user-guide/concepts/plugins/context-offloader
89+
- docs/user-guide/concepts/agents/interventions
8990
- label: Streaming
9091
items:
9192
- docs/user-guide/concepts/streaming/async-iterators
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
---
2+
title: Interventions
3+
description: "A composable control layer for authorization, guardrails, and steering with typed actions, ordered evaluation, and short-circuiting."
4+
languages: [typescript]
5+
---
6+
7+
Interventions are a composable control layer for agents. They provide a typed action model for common control concerns — authorization, guardrails, steering, and content transformation — with ordered evaluation and short-circuiting. Unlike raw [hooks](./hooks.mdx) and [plugins](../plugins/index.mdx) which mutate event objects directly, intervention handlers return typed decisions (`proceed`, `deny`, `guide`, `confirm`, `transform`) that the framework applies with well-defined semantics — enabling automatic short-circuiting, feedback accumulation, and conflict resolution.
8+
9+
## Basic Usage
10+
11+
Create an intervention handler by extending `InterventionHandler` and overriding the lifecycle methods you need. Register handlers via the `interventions` option in agent configuration:
12+
13+
```typescript
14+
--8<-- "user-guide/concepts/agents/interventions.ts:basic_usage"
15+
```
16+
17+
Handlers only need to override the lifecycle methods relevant to their concern — all methods default to `proceed()`.
18+
19+
## Action Types
20+
21+
Each lifecycle method returns one of five typed actions:
22+
23+
| Action | Factory | Description |
24+
|--------|---------|-------------|
25+
| Proceed | `InterventionActions.proceed()` | Allow the operation to continue unchanged |
26+
| Deny | `InterventionActions.deny(reason)` | Block the operation. Short-circuits remaining handlers |
27+
| Guide | `InterventionActions.guide(feedback)` | Cancel and provide feedback for the model to retry with |
28+
| Confirm | `InterventionActions.confirm(prompt)` | Pause for human approval (beforeToolCall only) |
29+
| Transform | `InterventionActions.transform(apply)` | Modify event content in-place before execution continues |
30+
31+
```typescript
32+
--8<-- "user-guide/concepts/agents/interventions.ts:action_types"
33+
```
34+
35+
## Lifecycle Methods
36+
37+
Intervention handlers can override five lifecycle methods. Each method supports a specific subset of actions:
38+
39+
| Method | Valid Actions | When it Runs |
40+
|--------|-------------|--------------|
41+
| `beforeInvocation` | Proceed, Deny, Guide, Transform | Before the agent loop starts |
42+
| `beforeToolCall` | Proceed, Deny, Guide, Confirm, Transform | Before each tool execution |
43+
| `afterToolCall` | Proceed, Transform | After each tool execution |
44+
| `beforeModelCall` | Proceed, Deny, Guide, Transform | Before each model API call |
45+
| `afterModelCall` | Proceed, Guide, Transform | After each model response |
46+
47+
How actions behave depends on the lifecycle method:
48+
49+
| Action | Before events | After events |
50+
|--------|--------------|--------------|
51+
| **Deny** | Sets `event.cancel`, short-circuits remaining handlers | No effect (warns at runtime) |
52+
| **Guide** | On `beforeToolCall`/`beforeInvocation`: cancels with accumulated feedback. On `beforeModelCall`: injects feedback as user message | On `afterModelCall`: injects feedback and retries |
53+
| **Confirm** | Pauses agent via interrupt/resume for human approval; denied responses set `event.cancel` | Not supported |
54+
| **Transform** | Calls `action.apply(event)` — later handlers see modified content | Calls `action.apply(event)` |
55+
56+
## Evaluation Order and Short-Circuiting
57+
58+
Handlers evaluate in **registration order**. If any handler returns `deny()`, remaining handlers are skipped — the operation is blocked immediately. This enables efficient pipelines where fast checks (like authorization) run first and prevent expensive evaluations (like LLM-based steering) from running unnecessarily.
59+
60+
```typescript
61+
--8<-- "user-guide/concepts/agents/interventions.ts:short_circuiting"
62+
```
63+
64+
For `guide()` actions, all handlers continue to run and their feedback is accumulated — the model receives combined guidance from all guiding handlers.
65+
66+
## Error Handling
67+
68+
The `onError` property controls what happens when a handler throws an exception:
69+
70+
| Value | Behavior |
71+
|-------|----------|
72+
| `'throw'` | Rethrow the error (default). The invocation fails. |
73+
| `'proceed'` | Log the error and continue as if `proceed()` was returned. |
74+
| `'deny'` | Log the error and treat it as a `deny()` (fail-closed). |
75+
76+
```typescript
77+
--8<-- "user-guide/concepts/agents/interventions.ts:error_handling"
78+
```
79+
80+
Use `'deny'` for security-critical handlers where a failure should block execution. Use `'proceed'` for non-critical handlers like logging where availability is more important than enforcement.
81+
82+
## Vended Interventions
83+
84+
The SDK ships ready-to-use intervention handlers for common patterns:
85+
86+
### Human in the Loop
87+
88+
`HumanInTheLoop` gates tool execution behind human approval. By default it uses the interrupt/resume pattern — the agent pauses and returns control to the caller, who presents the approval request to the user and resumes with their response.
89+
90+
```typescript
91+
--8<-- "user-guide/concepts/agents/interventions.ts:hitl"
92+
```
93+
94+
For CLI applications, use `ask: 'stdio'` to prompt directly in the terminal:
95+
96+
```typescript
97+
--8<-- "user-guide/concepts/agents/interventions.ts:hitl_stdio"
98+
```
99+
100+
Configuration options:
101+
- **`allowedTools`** — tools that run without approval. Supports wildcards (`['*']`) and negation (`['*', '!delete_file']`)
102+
- **`ask`** — how to collect the response: omit for interrupt/resume, `'stdio'` for terminal prompting, or a custom `async (prompt) => response` function
103+
104+
### Steering
105+
106+
`LLMSteeringHandler` provides just-in-time contextual guidance using an LLM judge. Instead of front-loading all instructions in the system prompt, it evaluates tool calls and model output against steering rules and returns `proceed`, `guide`, or `confirm` actions.
107+
108+
```typescript
109+
--8<-- "user-guide/concepts/agents/interventions.ts:steering"
110+
```
111+
112+
For custom steering logic without an LLM, extend `SteeringHandler` directly:
113+
114+
```typescript
115+
import { SteeringHandler, proceed, guide } from '@strands-agents/sdk/vended-interventions/steering'
116+
117+
class RedirectEmailHandler extends SteeringHandler {
118+
override readonly name = 'redirect-email'
119+
120+
override async evaluateToolCall(event) {
121+
if (event.toolUse.name === 'send_email') {
122+
return guide('Use send_notification instead.')
123+
}
124+
return proceed()
125+
}
126+
}
127+
```
128+
129+
## Composing Interventions
130+
131+
Interventions are designed to compose. A typical production agent might combine authorization (fast, deterministic), content transformation (PII redaction), and steering (LLM-guided) — each as an independent handler with a single responsibility:
132+
133+
```typescript
134+
--8<-- "user-guide/concepts/agents/interventions.ts:composed_example"
135+
```
136+
137+
## How Interventions Relate to Hooks and Plugins
138+
139+
Interventions are built on top of the [hooks](./hooks.mdx) system — under the hood, each lifecycle method registers a hook callback. The difference is in how they communicate with the framework.
140+
141+
[Hooks](./hooks.mdx) and [plugins](../plugins/index.mdx) mutate event properties directly (e.g., setting `event.cancel = "reason"`). The framework doesn't know _why_ something was cancelled — was it a hard authorization denial or soft guidance to retry differently? Multiple plugins modifying the same event can conflict silently with last-write-wins semantics.
142+
143+
Interventions return typed actions that the framework interprets. This enables:
144+
145+
- **Short-circuiting** — a `deny()` from an authorization handler skips all remaining handlers automatically. With hooks, each plugin must independently check `event.cancel` before doing work.
146+
- **Feedback accumulation** — multiple handlers can return `guide()` and their feedback is combined into a single message to the model, rather than overwriting each other.
147+
- **Human-in-the-loop**`confirm()` integrates with the SDK's interrupt/resume system to pause for approval without the handler needing to manage interrupt lifecycle.
148+
- **Ordered evaluation** — handlers always run in registration order with well-defined precedence (deny > confirm > guide > transform > proceed).
149+
- **Error policies** — each handler declares its own failure mode via `onError`. A logging handler can use `'proceed'` (skip on failure), while an auth handler can use `'deny'` (fail closed). Hooks have no equivalent — a thrown error always propagates.

0 commit comments

Comments
 (0)