# AWS Strands

`HighflameStrandsHooks` integrates Highflame Shield into AWS Strands Agents by implementing the Strands `HookProvider` interface. It intercepts agent invocations, model responses, and tool calls without modifying agent or tool definitions.

Available for **Python** and **TypeScript**.

### Installation

{% tabs %}
{% tab title="Python" %}

```bash
pip install 'highflame[strands]'
```

{% endtab %}

{% tab title="TypeScript" %}

```bash
npm install @highflame/sdk @aws/bedrock-ai-agents-strands
```

{% endtab %}
{% endtabs %}

### Basic Usage

{% tabs %}
{% tab title="Python" %}

```python
from highflame import Highflame
from highflame.integrations.strands import HighflameStrandsHooks
from strands import Agent

client = Highflame(api_key="hf_sk_...")
hooks = HighflameStrandsHooks(client, mode="enforce")

agent = Agent(
    model=model,
    tools=[my_tool],
    hooks=[hooks],
)

result = await agent.invoke_async("Your prompt here")
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
import { Highflame } from "@highflame/sdk";
import { HighflameStrandsHooks } from "@highflame/sdk/integrations/strands";

const client = new Highflame({ apiKey: "hf_sk_..." });
const hooks = new HighflameStrandsHooks(client, { mode: "enforce" });

// registerHooks is async — it lazy-loads the Strands peer dependency
await hooks.registerHooks(registry);
```

{% endtab %}
{% endtabs %}

On a policy violation, `HighflameStrandsHooks` raises `BlockedError`. For tool call denials, it also sets `event.cancel_tool` to the deny reason before raising, following the idiomatic Strands cancellation pattern.

### Constructor

{% tabs %}
{% tab title="Python" %}

```python
HighflameStrandsHooks(
    client: Highflame,
    *,
    mode: str = "enforce",
    session_id: str | None = None,
    session_id_key: str = "session_id",
)
```

| Parameter        | Type          | Default        | Description                                                       |
| ---------------- | ------------- | -------------- | ----------------------------------------------------------------- |
| `client`         | `Highflame`   | required       | Initialized Highflame client                                      |
| `mode`           | `str`         | `"enforce"`    | Enforcement mode: `"enforce"`, `"monitor"`, or `"alert"`          |
| `session_id`     | `str \| None` | `None`         | Static session ID. If set, takes priority over invocation state.  |
| `session_id_key` | `str`         | `"session_id"` | Key to read from `event.invocation_state` for dynamic session ID. |
| {% endtab %}     |               |                |                                                                   |

{% tab title="TypeScript" %}

```typescript
new HighflameStrandsHooks(client: Highflame, options?: {
  mode?: "enforce" | "monitor" | "alert";
  sessionId?: string;
  sessionIdKey?: string;
})
```

| Parameter      | Type                  | Default        | Description                                                       |
| -------------- | --------------------- | -------------- | ----------------------------------------------------------------- |
| `client`       | `Highflame`           | required       | Initialized Highflame client                                      |
| `mode`         | `string`              | `"enforce"`    | Enforcement mode: `"enforce"`, `"monitor"`, or `"alert"`          |
| `sessionId`    | `string \| undefined` | `undefined`    | Static session ID. If set, takes priority over invocation state.  |
| `sessionIdKey` | `string`              | `"session_id"` | Key to read from `event.invocation_state` for dynamic session ID. |
| {% endtab %}   |                       |                |                                                                   |
| {% endtabs %}  |                       |                |                                                                   |

### Session ID Resolution

The hooks resolve the session ID in this order:

1. Static `session_id` / `sessionId` — if explicitly provided at construction time
2. `event.invocation_state[session_id_key]` / `event.invocation_state[sessionIdKey]` — from the Strands invocation state
3. `None` / `undefined` — no session tracking

To pass a session ID dynamically:

{% tabs %}
{% tab title="Python" %}

```python
result = await agent.invoke_async(
    "Analyze this document",
    invocation_state={"session_id": f"user-{user_id}"},
)
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
const result = await agent.invokeAsync("Analyze this document", {
  invocationState: { session_id: `user-${userId}` },
});
```

{% endtab %}
{% endtabs %}

### Hooks

`HighflameStrandsHooks` registers four async hooks with the Strands hook registry:

#### `BeforeInvocationEvent`

Fires before the agent loop starts for a new invocation. Extracts the last user message from `event.messages` and evaluates it as a prompt.

* Content type: `"prompt"`, action: `"process_prompt"`
* On deny: raises `BlockedError` before the agent processes the request

#### `AfterModelCallEvent`

Fires after the LLM produces a response. Extracts text from `event.stop_response.message.content` and evaluates it as a model response.

* Content type: `"response"`, action: `"process_prompt"`
* On deny: raises `BlockedError` before the response flows back into the agent loop

#### `BeforeToolCallEvent`

Fires before a tool executes. Evaluates the tool call using `event.tool_use.name` and `event.tool_use.input`.

* Content type: `"tool_call"`, action: `"call_tool"`
* On deny: sets `event.cancel_tool` to the deny reason, then raises `BlockedError`

Setting `event.cancel_tool` is the idiomatic Strands way to signal tool cancellation. It is set before raising to ensure Strands runtime state is consistent even if the exception is caught upstream.

#### `AfterToolCallEvent`

Fires after a tool returns. Extracts text from `event.result` and evaluates it as a tool response.

* Content type: `"response"`, action: `"call_tool"`
* On deny: raises `BlockedError`, result is not returned to the agent

### Registration

{% tabs %}
{% tab title="Python" %}
Strands `HookProvider` objects register themselves through `register_hooks(registry)`, which Strands calls automatically when you pass the provider to the `Agent` constructor:

```python
# Strands calls register_hooks automatically
agent = Agent(model=model, tools=[...], hooks=[hooks])
```

{% endtab %}

{% tab title="TypeScript" %}
Call `registerHooks` explicitly and `await` it. The method is async because it lazy-loads the `@aws/bedrock-ai-agents-strands` package on first call — this avoids a hard import-time dependency:

```typescript
await hooks.registerHooks(registry);
```

If `@aws/bedrock-ai-agents-strands` is not installed, `registerHooks` throws with an actionable install message.
{% endtab %}
{% endtabs %}

### Enforcement Modes

{% tabs %}
{% tab title="Python" %}

```python
hooks = HighflameStrandsHooks(client, mode="enforce")  # block violations (default)
hooks = HighflameStrandsHooks(client, mode="monitor")  # observe without blocking
hooks = HighflameStrandsHooks(client, mode="alert")    # allow but trigger alerting
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
new HighflameStrandsHooks(client, { mode: "enforce" })  // block violations (default)
new HighflameStrandsHooks(client, { mode: "monitor" })  // observe without blocking
new HighflameStrandsHooks(client, { mode: "alert" })    // allow but trigger alerting
```

{% endtab %}
{% endtabs %}

### Complete Example

{% tabs %}
{% tab title="Python" %}

```python
import asyncio
from strands import Agent
from strands.models.bedrock import BedrockModel

from highflame import Highflame, BlockedError
from highflame.integrations.strands import HighflameStrandsHooks

client = Highflame(api_key="hf_sk_...")
hooks = HighflameStrandsHooks(client, mode="enforce", session_id_key="session_id")

model = BedrockModel(model_id="us.anthropic.claude-sonnet-4-5-20251101-v2:0")

agent = Agent(model=model, tools=[file_tool, web_tool], hooks=[hooks])

async def handle_request(user_input: str, session_id: str):
    try:
        return await agent.invoke_async(
            user_input,
            invocation_state={"session_id": session_id},
        )
    except BlockedError as e:
        return {"error": f"Request blocked: {e.response.policy_reason}"}

asyncio.run(handle_request("Analyze the quarterly report", "user-42-session-7"))
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
import { Highflame, BlockedError } from "@highflame/sdk";
import { HighflameStrandsHooks } from "@highflame/sdk/integrations/strands";

const client = new Highflame({ apiKey: "hf_sk_..." });
const hooks = new HighflameStrandsHooks(client, {
  mode: "enforce",
  sessionIdKey: "session_id",
});

// Register hooks before starting the agent
await hooks.registerHooks(registry);

async function handleRequest(userInput: string, sessionId: string) {
  try {
    return await agent.invokeAsync(userInput, {
      invocationState: { session_id: sessionId },
    });
  } catch (e) {
    if (e instanceof BlockedError) {
      return { error: `Request blocked: ${e.response.policy_reason}` };
    }
    throw e;
  }
}
```

{% endtab %}
{% endtabs %}

### Content Extraction

The integration handles both Bedrock-native content formats:

* Plain strings
* `[{"type": "text", "text": "..."}]` — standard content block format
* `[{"text": "..."}]` — Bedrock native format (no `"type"` key)
* Objects with `type`/`text` attributes (Python SDK objects)

### Error Handling

{% tabs %}
{% tab title="Python" %}

```python
from highflame import BlockedError

try:
    result = await agent.invoke_async(user_input)
except BlockedError as e:
    print(f"Blocked: {e.response.policy_reason}")
    print(f"Signals: {[s.name for s in e.response.signals]}")
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
import { BlockedError } from "@highflame/sdk";

try {
  const result = await agent.invokeAsync(userInput);
} catch (e) {
  if (e instanceof BlockedError) {
    console.log(`Blocked: ${e.response.policy_reason}`);
    console.log(`Signals: ${e.response.signals}`);
  }
}
```

{% endtab %}
{% endtabs %}

### Requirements

{% tabs %}
{% tab title="Python" %}

| Package          | Minimum Version |
| ---------------- | --------------- |
| `strands-agents` | 1.0+            |
| `highflame`      | latest          |
| {% endtab %}     |                 |

{% tab title="TypeScript" %}

| Package                          | Minimum Version |
| -------------------------------- | --------------- |
| `@aws/bedrock-ai-agents-strands` | 1.0+            |
| `highflame`                      | latest          |
| {% endtab %}                     |                 |
| {% endtabs %}                    |                 |


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.highflame.ai/api-reference/sdk/shield/integrations/strands.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
