Integrations

Strands

Add ArmorIQ to an AWS Strands agent

Strands Integration

ArmorIQStrands wires ArmorIQ into AWS Strands so every tool call an agent makes goes through ArmorIQ's plan, token, policy, and audit pipeline. You keep your existing Agent, model, and tools. You just attach the ArmorIQ hooks for the duration of each request.

Live in Python and TypeScript. Python ships in armoriq-sdk 0.6.0+, TypeScript in @armoriq/sdk 0.6.0+. See Integrations for the full status matrix.

Install

pip install "armoriq-sdk[strands]"
npm install @armoriq/sdk
# plus your Strands agent package

Minimal example

import os
from strands import Agent
from armoriq_sdk import ArmorIQClient
from armoriq_sdk.integrations.strands import ArmorIQStrands

# Once per process
armoriq = ArmorIQStrands(
    armoriq_client=ArmorIQClient(api_key=os.environ["ARMORIQ_API_KEY"]),
    mode="sdk",
)

# Per request: for_user returns a hook provider you pass to the Agent
hooks = armoriq.for_user("alice@example.com", goal=user_message)
agent = Agent(model=model, tools=tools, hooks=[hooks])

async for event in agent.stream_async(user_message):
    handle(event)
import { ArmorIQClient } from '@armoriq/sdk';
import { ArmorIQStrands } from '@armoriq/sdk/dist/integrations/strands';

// Once per process
const armoriq = new ArmorIQStrands({
  client: new ArmorIQClient({ apiKey: process.env.ARMORIQ_API_KEY! }),
  mode: 'sdk',
});

// Per request: forUser returns a plugin you attach to the Agent
const plugin = await armoriq.forUser('alice@example.com', { goal: userMessage });
const agent = new Agent({ model, tools, plugins: [plugin] });

That's it. The SDK attaches three Strands lifecycle hooks:

HookWhat ArmorIQ does
AfterModelCallEventBuilds a plan from the tool calls the model chose and mints an intent token for exactly those tools
BeforeToolCallEventEnforces the user's policy before the tool runs: allow, hold (wait for approval), or block
AfterToolCallEventReports the executed tool so the plan lifecycle closes (see below)

Enforcement is fail-closed: a tool runs only on an explicit allow. A block, a hold that is not approved in time, or any enforcement error stops the tool.

Live enforcement events

Pass an on_event callback (onEvent in TypeScript) to stream the enforcement lifecycle into your own UI. The kinds are hold, approved, rejected, timeout, block, and error.

def on_event(kind, payload):
    # kind in {"hold","approved","rejected","timeout","block","error"}
    stream_to_ui(kind, payload)

hooks = armoriq.for_user("alice@example.com", goal=user_message, on_event=on_event)
const plugin = await armoriq.forUser('alice@example.com', {
  goal: userMessage,
  onEvent: (kind, payload) => streamToUi(kind, payload),
});

On a hold, the hook emits hold, then waits asynchronously for the delegation decision. Approved lets the tool run; rejected or timeout stops it. The wait is non-blocking, so your app can stream held to approved to success live.

Plan lifecycle: the execution report

After each tool actually runs, the AfterToolCallEvent hook reports that step back to ArmorIQ. This advances the plan to completed once every declared step has run, instead of leaving it active forever. Tools that were cancelled by enforcement never ran, so they are not reported.

ArmorIQStrands parameters

ParameterTypeDefaultDescription
armoriq_clientArmorIQClient-The client (holds your API key and endpoints)
modestr"sdk"Session mode
validity_secondsint3600Intent-token validity window
default_mcp_namestrNoneMCP name to attribute tools that can't be auto-mapped
tool_name_parsercallablebuilt-inCustom tool_name -> (mcp, action) mapper
approval_wait_secondsfloat300How long to wait for a held tool's approval before failing closed
approval_poll_intervalfloat5.0How often to poll for the approval decision

for_user(email, *, goal=None, on_event=None)

Returns a per-request hook provider. Pass it to Agent(hooks=[...]) in Python or Agent({ plugins: [...] }) in TypeScript. The provider caches plan state for the request, so tool calls that aren't in the plan get blocked before they hit the MCP.

Reference

For a complete example, wire ArmorIQStrands into any Strands Agent that already has tools attached: the accounts-payable and finance-workflow demos built internally follow exactly the pattern shown above: one ArmorIQStrands instance per process, one for_user(...) scope per request.

On this page