v0.7.8 PREVIEW – Bedrock Guardrails and Strands agent adapters ship with v0.7.8. Available on PyPI and npm at release.

Who this is for: Cloud architects deploying AI on Bedrock, compliance engineers building evidence pipelines for SOC 2 / FedRAMP / EU AI Act, platform teams running Strands agents in production, and security teams responsible for AI governance in regulated environments.

The evidence gap in every Bedrock deployment. CloudTrail logs that InvokeModel was called. It does not log what the model decided, whether guardrails intervened, or how the agent used its tools. When an auditor asks "how do you know this model behaved correctly on March 15th?", CloudTrail gives you an API call record. SWT3 gives you a cryptographic witness anchor with the decision fingerprint.

Contents

1. The Evidence Gap 2. Three Layers of Bedrock Witnessing 3. Guardrails Witnessing (AI-GRD.1) 4. Strands Agent Witnessing (AI-INF.1) 5. Model Inference Witnessing 6. Combining All Three Layers 7. Regulatory Mapping 8. Quick Start 9. References

1. The Evidence Gap

Every enterprise Bedrock deployment has the same compliance architecture: IAM policies control who can call the models, CloudTrail logs that they did, and Guardrails filter what goes in and out. This is necessary. It is not sufficient.

Here is what a CloudTrail entry for a Bedrock Guardrails evaluation actually contains:

{
  "eventName": "ApplyGuardrail",
  "userIdentity": {"arn": "arn:aws:iam::123456:role/ai-service"},
  "requestParameters": {
    "guardrailIdentifier": "abc123",
    "source": "INPUT"
  }
  // No content. No decision. No assessment details.
}

The guardrail ran. Something was evaluated. But the audit record does not capture what was evaluated, whether the guardrail intervened, or which policy triggered the decision. It is metadata about the API call, not evidence of the decision itself.

This matters because regulators do not ask "did you have guardrails?" They ask: "Show me the evidence that your guardrails made the right decision on this specific input at this specific time, and prove this evidence has not been altered."

SWT3 closes this gap. Every guardrail evaluation, every agent invocation, every model inference gets a witness anchor: a cryptographic fingerprint of the decision that can be verified independently, years later, without access to the underlying data.

2. Three Layers of Bedrock Witnessing

A production Bedrock deployment has three distinct decision surfaces. Each produces evidence that auditors need. Each has a dedicated SWT3 adapter:

Layer What Happens Adapter Procedure What Gets Witnessed
Guardrails Content filtering, topic blocking, PII detection wrap_bedrock_guardrails AI-GRD.1 Each guardrail evaluation: what was checked, whether it intervened, which policy triggered
Agents Multi-step reasoning, tool selection, task execution wrap_strands AI-INF.1 Each agent invocation: prompt in, result out, model used, total latency
Inference Direct model calls via InvokeModel/Converse wrap_bedrock AI-INF.1 Each model response: prompt hash, response hash, token usage, latency

These layers are independent. You can witness guardrails without witnessing inference, or witness agents without touching guardrails. Deploy one adapter where you have the biggest evidence gap. Expand when ready. Zero all-or-nothing commitments.

3. Guardrails Witnessing (AI-GRD.1)

Bedrock Guardrails evaluate content against policies you define: topic restrictions, content filters, word blocklists, PII detection, contextual grounding. When a guardrail intervenes, it blocks or modifies the content. When it does not, the content passes through.

Both outcomes are evidence. A guardrail that passes content is attesting "this content met all policy requirements at this time." A guardrail that intervenes is attesting "this content violated policy X and was blocked." Both decisions need to be witnessed because both are compliance-relevant.

Python

import boto3
from swt3_ai import Witness
from swt3_ai.adapters.bedrock_guardrails import wrap_bedrock_guardrails

witness = Witness(
    endpoint="https://your-instance.example.com/api/v1/witness",
    api_key="axm_your_key",
    tenant_id="YOUR_TENANT",
)

bedrock = boto3.client("bedrock-runtime", region_name="us-east-1")
witnessed = wrap_bedrock_guardrails(bedrock, witness)

# Every guardrail evaluation is now witnessed
result = witnessed.apply_guardrail(
    guardrailIdentifier="my-content-policy",
    guardrailVersion="1",
    source="INPUT",
    content=[{"text": {"text": "Evaluate this user message"}}],
)

# result is untouched -- your code works exactly as before
if result["action"] == "GUARDRAIL_INTERVENED":
    print("Content blocked by policy")
else:
    print("Content passed all checks")

witness.flush()

TypeScript

import { Witness } from '@tenova/swt3-ai';
import { wrapBedrockGuardrails } from '@tenova/swt3-ai';
import { BedrockRuntimeClient, ApplyGuardrailCommand } from '@aws-sdk/client-bedrock-runtime';

const witness = new Witness({
  endpoint: 'https://your-instance.example.com/api/v1/witness',
  apiKey: 'axm_your_key',
  tenantId: 'YOUR_TENANT',
});

const bedrock = new BedrockRuntimeClient({ region: 'us-east-1' });
const witnessed = wrapBedrockGuardrails(bedrock, witness);

const result = await witnessed.applyGuardrail({
  guardrailIdentifier: 'my-content-policy',
  guardrailVersion: '1',
  source: 'INPUT',
  content: [{ text: { text: 'Evaluate this user message' } }],
});

await witness.flush();
What the anchor captures

Guardrail Evaluation Evidence

model_id: guardrail:my-content-policy:1 -- identifies which guardrail and version made the decision.

prompt_hash: SHA-256 of the content that was evaluated. You can prove what was checked without storing the content itself.

response_hash: SHA-256 of the guardrail's action + assessments. The full decision is fingerprinted.

has_refusal: true if the guardrail intervened, false if content passed. Instant audit filter.

access_target: INPUT or OUTPUT -- whether this was pre-inference or post-inference filtering.

Assessor insight

This is the evidence auditors cannot get from CloudTrail. The guardrail's actual decision, fingerprinted and anchored, proving that content policy was enforced on this specific input at this specific time.

4. Strands Agent Witnessing (AI-INF.1)

Strands agents are autonomous: they receive a prompt, reason about it, select and execute tools, and return a result. The internal loop -- model calls, tool selections, intermediate reasoning -- is managed by the framework. From the outside, you see the input and the output.

That outer boundary is exactly where compliance evidence lives. An auditor does not need to trace every internal reasoning step (that is model interpretability, a different problem). They need to verify: this agent was invoked with this input, it produced this output, using this model, in this timeframe. SWT3 witnesses that boundary.

Python

from strands import Agent
from swt3_ai import Witness
from swt3_ai.adapters.strands import wrap_strands

witness = Witness(
    endpoint="https://your-instance.example.com/api/v1/witness",
    api_key="axm_your_key",
    tenant_id="YOUR_TENANT",
    agent_id="claims-processor",
)

agent = Agent(model="us.anthropic.claude-sonnet-4-20250514")
witnessed = wrap_strands(agent, witness)

# Identical usage -- agent is still callable
result = witnessed("Review this insurance claim and flag anomalies")

witness.flush()

TypeScript

import { Agent } from '@strands-agents/sdk';
import { Witness, wrapStrands } from '@tenova/swt3-ai';

const witness = new Witness({
  endpoint: 'https://your-instance.example.com/api/v1/witness',
  apiKey: 'axm_your_key',
  tenantId: 'YOUR_TENANT',
  agentId: 'claims-processor',
});

const agent = new Agent({ model: 'us.anthropic.claude-sonnet-4-20250514' });
const witnessed = wrapStrands(agent, witness);

const result = await witnessed.invoke('Review this insurance claim and flag anomalies');

await witness.flush();

Agent witnessing complements tool witnessing. wrap_strands witnesses the outer agent invocation. If you also need evidence of individual tool calls within the agent loop, use wrap_tool on the tools themselves. Two independent attestation layers, each with its own anchor.

AI-INF.1 -- Agent Invocation

What the assessor verifies

Each Strands agent invocation produces a single anchor covering the full agent execution: prompt in, result out, model used, total wall-clock latency.

Assessor tip

Query the ledger for provider="aws-strands" and compare model_hash values across a 30-day window. If the hash changes, the underlying model was swapped (Bedrock model version rotation or configuration change). Cross-reference with the organization's model change management records. Any hash change without a corresponding change ticket is a finding.

5. Model Inference Witnessing

For direct model calls (no agent framework), the existing Bedrock adapter witnesses every InvokeModel or Converse call:

import boto3
from swt3_ai import Witness

witness = Witness(
    endpoint="https://your-instance.example.com/api/v1/witness",
    api_key="axm_your_key",
    tenant_id="YOUR_TENANT",
)

bedrock = boto3.client("bedrock-runtime")
witnessed = witness.wrap(bedrock)  # auto-detects Bedrock

response = witnessed.converse(
    modelId="us.anthropic.claude-sonnet-4-20250514",
    messages=[{"role": "user", "content": [{"text": "Summarize this report"}]}],
)

witness.flush()

Every call gets an anchor: model hash, prompt hash, response hash, latency, token usage. The response is untouched. Your application code does not change.

6. Combining All Three Layers

A real production pipeline often uses all three: guardrails filter input, a model generates a response, guardrails filter output. With SWT3, every decision surface is witnessed independently:

import boto3
from strands import Agent
from swt3_ai import Witness
from swt3_ai.adapters.bedrock_guardrails import wrap_bedrock_guardrails

witness = Witness(
    endpoint="https://your-instance.example.com/api/v1/witness",
    api_key="axm_your_key",
    tenant_id="YOUR_TENANT",
    agent_id="underwriting-agent",
    jurisdiction="US",
)

bedrock = boto3.client("bedrock-runtime")

# Layer 1: Witness guardrail evaluations
guarded = wrap_bedrock_guardrails(bedrock, witness)

# Layer 2: Witness agent invocations (agent uses Bedrock internally)
from swt3_ai.adapters.strands import wrap_strands
agent = Agent(model="us.anthropic.claude-sonnet-4-20250514")
witnessed_agent = wrap_strands(agent, witness)

# Pre-inference guardrail check
input_check = guarded.apply_guardrail(
    guardrailIdentifier="input-policy",
    guardrailVersion="1",
    source="INPUT",
    content=[{"text": {"text": user_message}}],
)

if input_check["action"] == "NONE":
    # Input passed -- run the agent
    result = witnessed_agent(user_message)

    # Post-inference guardrail check
    output_check = guarded.apply_guardrail(
        guardrailIdentifier="output-policy",
        guardrailVersion="1",
        source="OUTPUT",
        content=[{"text": {"text": str(result)}}],
    )

# 3 anchors minted: input guardrail + agent invocation + output guardrail
witness.flush()

Three independent witness anchors from one user interaction. Each anchor is separately verifiable. An auditor can confirm that input filtering, model reasoning, and output filtering all happened -- and in what order -- without accessing the content itself. This is the evidence chain that CloudTrail cannot provide.

Three-layer evidence chain

Assessor verification procedure

For a given user interaction, the ledger should contain three anchors with timestamps in sequence: INPUT guardrail, agent/inference, OUTPUT guardrail.

Assessor tip

Query anchors for a single agent_id within a 5-second window. Verify: (1) an INPUT guardrail anchor exists with guardrail_passed=true before the inference anchor, (2) the inference anchor's model_id matches the approved model list, (3) an OUTPUT guardrail anchor exists after the inference anchor. If any layer is missing, the pipeline has an unwitnessed decision surface. If INPUT guardrail shows has_refusal=true but an inference anchor still exists, the guardrail was bypassed -- this is a critical finding.

Cross-account guardrails

Centralized policy evidence

Organizations using Bedrock cross-account guardrails enforce policy centrally. The SWT3 anchor captures the guardrail ID and version regardless of which account owns the guardrail.

Assessor tip

Verify that model_id on guardrail anchors follows the format guardrail:{id}:{version}. Compare the guardrail identifier against the organization's approved guardrail registry. If a production workload references a guardrail ID not in the registry, it may be using a development or test guardrail in production -- a policy control failure.

7. Regulatory Mapping

Every anchor minted on Bedrock maps to specific regulatory requirements. The evidence is not theoretical -- it is the exact artifact that satisfies the obligation.

Regulation Requirement CloudTrail SWT3
EU AI Act Art. 12 Automatic logging of AI system operation API metadata only Per-decision fingerprint
EU AI Act Art. 13 Transparency: deployers must understand AI output No output evidence Response hash + model ID
EU AI Act Art. 14 Human oversight measures No guardrail decision log Guardrail intervention evidence
NIST AI RMF MAP 1.5 Document AI system interfaces and dependencies Partial (IAM roles) Agent + model + guardrail chain
NIST AI RMF MN 2.2 Mechanisms to track AI system behavior API-level only Decision-level tracking
SOC 2 CC7.2 Monitor system components for anomalies Infrastructure monitoring AI decision monitoring
FedRAMP AU-2 Audit events for security-relevant activities API call audit Decision-level audit
SR 11-7 / SR 26-2 Model risk management: ongoing monitoring No model behavior data Drift detection via hash comparison

The pattern is always the same. CloudTrail tells you the API was called. SWT3 tells you what the AI decided. Regulators care about the decision, not the API call. Both records are necessary; only one provides decision-level accountability.

Audit workpaper guidance

How to use SWT3 anchors in a Bedrock assessment

Assessor tip

Request a ledger export filtered by provider IN ("aws-bedrock-guardrails", "aws-strands", "bedrock") for the assessment period. For each regulatory requirement in scope: (1) confirm at least one anchor type maps to the requirement per the table above, (2) verify anchor density is sufficient (no gaps longer than the organization's stated monitoring interval), (3) spot-check 5 anchors by re-hashing the stated model_id and confirming model_hash matches. If the organization claims continuous monitoring but the ledger shows anchor gaps exceeding 24 hours, the monitoring claim is unsupported.

8. Quick Start

Install

# Python
pip install swt3-ai

# TypeScript
npm install @tenova/swt3-ai

Choose Your Starting Point

If You Have Start With Lines of Code
Bedrock Guardrails in production wrap_bedrock_guardrails(client, witness) 3
Strands agents in production wrap_strands(agent, witness) 3
Direct InvokeModel/Converse calls witness.wrap(bedrock_client) 1

Minimal Example (Guardrails)

import boto3
from swt3_ai import Witness
from swt3_ai.adapters.bedrock_guardrails import wrap_bedrock_guardrails

witness = Witness(
    endpoint="https://your-instance.example.com/api/v1/witness",
    api_key="axm_your_key",
    tenant_id="YOUR_TENANT",
)

bedrock = boto3.client("bedrock-runtime")
witnessed = wrap_bedrock_guardrails(bedrock, witness)

# Your existing code. Unchanged. Every evaluation witnessed.
result = witnessed.apply_guardrail(
    guardrailIdentifier="my-policy",
    guardrailVersion="1",
    source="INPUT",
    content=[{"text": {"text": "user input here"}}],
)

witness.flush()

Three lines added. Zero code changes to your application logic. Every guardrail decision now has a cryptographic witness anchor that survives independently of AWS infrastructure.

9. References