anthropics / anthropics/claude-agent-sdk-python

[FEATURE] Native Bedrock Guardrail body wrapping for InvokeModel / InvokeModelWithResponseStream

Open
#999 0 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
Python
Stars
8.1k
Forks
1.3k
Avg merge
2d 31m
Merged PRs (30d)
1

Description

## Summary

When using `claude-agent-sdk` with Amazon Bedrock and a Bedrock Guardrail
attached via the `X-Amzn-Bedrock-GuardrailIdentifier` /
`X-Amzn-Bedrock-GuardrailVersion` headers (e.g. injected through
`ANTHROPIC_CUSTOM_HEADERS`), every `InvokeModel` /
`InvokeModelWithResponseStream` call fails closed with:

HTTP 400 Guardrail was enabled but input is in incorrect format.

The model is never invoked, the guardrail never evaluates, no usage is
billed beyond the round-trip, and no `guardrailTrace` event is produced —
so guardrails can be neither **enforced** nor **observed** while routing
through this SDK against Bedrock.

Reproduced on `claude-agent-sdk==0.2.87` + `us.anthropic.claude-opus-4-7`
on Bedrock `us-east-1`, both single-agent and multi-agent (sub-agent /
A2A) configurations.

## Why it fails

Bedrock Guardrail enforcement on the InvokeModel path requires **two
coordinated changes** per request:

1. **Headers** — `X-Amzn-Bedrock-GuardrailIdentifier`,
`X-Amzn-Bedrock-GuardrailVersion`, optional
`X-Amzn-Bedrock-Trace`. The SDK forwards these correctly.

2. **Body** — when the headers are present, Bedrock requires:
- a top-level `amazon-bedrock-guardrailConfig` object
(e.g. `{\"streamProcessingMode\": \"sync\"}`), and
- natural-language text segments in `messages[]` and the system
prompt wrapped in
`>...`
markers so the engine knows which spans to evaluate.
Tool-use / tool-result blocks must NOT be wrapped.

The SDK currently emits Anthropic Messages-shaped bodies on the
Bedrock path with neither of these, so Bedrock fails closed.

Reference: AWS Bedrock Guardrails — *Use a guardrail with the
InvokeModel API* (the `guardContent` and `guardrailConfig` sections).

## Requested behavior

When \`ClaudeAgentOptions\` is configured for Bedrock and the runtime
detects guardrail headers (or via an explicit option, e.g.
\`bedrock_guardrail={\"id\": \"...\", \"version\": \"DRAFT\"}\`), the SDK should:

- Inject \`amazon-bedrock-guardrailConfig\` into the outgoing body.
- Wrap text content blocks in \`messages[]\` and the system prompt with
\`guardContent\` markers; leave \`tool_use\` / \`tool_result\` blocks
untouched.
- Pass through \`guardrailTrace\` / intervention metadata on the
response side so callers can attribute guardrail actions to a turn.

Equivalent support is already needed on
\`InvokeModelWithResponseStream\` for the streaming code path.

## Why this matters

Without native support, integrators have two choices, both bad:

1. **Local botocore hook** that monkey-patches the outgoing body —
fragile, will silently double-wrap once the SDK adds real support
unless a feature flag is plumbed.
2. **\`ApplyGuardrail\` sidecar calls** — possible for *observability*
but does not enforce; adds 1–2 extra round-trips per turn.

Native support lets every \`claude-agent-sdk\` user pick up Bedrock
Guardrail enforcement and the corresponding \`guardrailTrace\`
observability with a single config option, the same way Bedrock
Guardrail headers already work today.

## Related

- Bedrock Converse API handles this natively; the InvokeModel path
does not. Migrating the SDK's Bedrock transport to Converse would
also resolve this (and prompt-caching #626) but is a larger change.

## Environment

- \`claude-agent-sdk==0.2.87\`
- AWS Bedrock \`us-east-1\`
- \`us.anthropic.claude-opus-4-7\`
- Reproduced on AWS Bedrock AgentCore Runtime; same body-shape issue
applies to any caller that adds guardrail headers.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.