Skip to content

[FEATURE] HITL Intervention Handler #1047

@lizradway

Description

@lizradway

Description

Adds a ready-to-use HumanInTheLoop intervention handler that pauses agent execution before tool calls to request human approval, using the native interrupt system for pause/resume semantics.

This is the first vended intervention — a concrete, configurable InterventionHandler implementation shipped with the SDK, following the same pattern as vended plugins (ContextOffloader, Skills).

Motivation

Human-in-the-loop approval is one of the most common intervention use cases. Rather than requiring every user to implement their own handler, the SDK should provide a battle-tested implementation that covers the standard patterns (approve all, allowlist, denylist).

Dependencies

API

import { Agent, InterruptResponseContent } from '@strands-agents/sdk'
import { HumanInTheLoop } from '@strands-agents/sdk/vended-interventions/hitl'

// Mode 1: Require approval for ALL tool calls (default)
const agent = new Agent({
  tools: [deleteTool, readTool, sendEmail],
  interventions: [new HumanInTheLoop()],
})

// Mode 2: Allow specific safe tools, require approval for everything else
const agent = new Agent({
  interventions: [new HumanInTheLoop({ allowedTools: ['readFile', 'listDir'] })],
})

// Mode 3: Only require approval for specific dangerous tools
const agent = new Agent({
  interventions: [new HumanInTheLoop({ deniedTools: ['deleteFile', 'sendEmail'] })],
})

// Usage: agent pauses with stopReason: 'interrupt'
const result = await agent.invoke('Delete the production database')
// result.stopReason === 'interrupt'
// result.interrupts[0].reason includes tool name + input for display to user

// Resume with approval or denial
const finalResult = await agent.invoke([
  new InterruptResponseContent({
    interruptId: result.interrupts[0].id,
    response: 'yes', // or true, 'approve', 'approved', { approved: true }
  }),
])

Approval response format

The handler accepts any JSONValue response and interprets it as approved if it matches:

  • true
  • 'yes', 'approve', 'approved' (case-insensitive)
  • { approved: true }

Any other value (including false, 'no', null, arbitrary strings) is treated as denial.

Implementation details

  • Lives at strands-ts/src/vended-interventions/hitl/
  • Exported via subpath: @strands-agents/sdk/vended-interventions/hitl
  • Overrides beforeToolCall only — uses event.interrupt() for native pause/resume
  • On denial, returns deny() which sets event.cancel and prevents tool execution
  • Interrupt reason includes the tool name and serialized input so the human has context

Testing

  • 22 unit tests covering: config validation, all three modes, approval response parsing (10 response shapes), interrupt metadata, full agent integration (interrupt → resume → execute/deny)

Metadata

Metadata

Assignees

Labels

No fields configured for Feature.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions