Govern a Strands agent
Run an AWS Strands (Python) agent as a governed strands runtime — the initializ-strands package, zero-code activation via the shipped .pth, gateway egress and audit, the Python A2A authoring shape, platform MCP connections, and the wheel-image wrapper build.
Agents built on AWS Strands run on the platform as the governed
strands runtime — the
Python sibling of the claude-agent runtime, with the same governance
envelope: audit of every model and tool call, runtime policy checks, the
platform-managed model gateway, and optional A2A exposure. What differs is
how governance reaches the image: instead of the Node loader, your CI
installs the initializ-strands Python package into the agent's
image, and it activates itself on every interpreter start.
The same split as the Node SDK applies: the governance package is always present, never imported by application code, while the A2A authoring construct is optional — only needed when you expose typed skills to other agents.
The initializ-strands package
initializ-strands (Python ≥ 3.10) has zero hard runtime
dependencies — Strands itself is your app's dependency, and audit and
policy calls use only the standard library. Two extras exist because a
gateway model swap needs a provider SDK in the image: [anthropic] (the
default) or [openai], matching the deploy spec's model.provider.
Activation is zero-code: the wheel ships an initializ_strands.pth file
that Python executes on interpreter start — the Python analog of the Node
loader's --import flag — so governance activates in whatever process
your image's CMD/ENTRYPOINT runs (python -m app, gunicorn,
uvicorn, …) with no source change. An app can also activate
explicitly:
import initializ_strands
initializ_strands.activate()When Strands isn't importable or the platform identity isn't configured in the environment, activation is a safe no-op — the package never breaks an ungovernable process.
What governance does to a Strands agent
Once active, every strands.Agent — subclasses and ad-hoc inline
instances included — is governed without any app edit:
- Audit of the full run lifecycle. The Strands hook lifecycle is mapped onto the platform's audit contract: session start, each model call (with model and provider, and per-call token usage including cache read/creation tokens, so the console's Usage view sums correctly), each tool call's start and end, and run completion with aggregate usage. Failures close their timeline too.
- Policy checks on managed tool calls. Tool calls on platform-managed
connections are checked online against the platform's policy decision
point.
INITIALIZ_ENFORCEMENTpicks the mode, exactly as for claude-agents:audit_only(default) decides and records but never blocks;enforcecancels a denied tool call. - Payload capture on the standard evidence fields. Audit events
always carry content hashes and lengths; when the deploy spec enables
audit.capture, bounded raw prompts, completions, and tool arguments/results are attached on the same fields the console renders for every runtime. - Model traffic pinned to the managed gateway. See below.
Gateway egress — repoint or swap
Model calls are routed onto the platform-managed gateway selected by the
deploy spec's model.provider, driven by the API protocol the gateway
advertises versus the protocol the app's model speaks — never hardcoded:
- When they match — a
BedrockModelagainst a Converse-protocol gateway, or an Anthropic/OpenAI model against its own protocol — the model is repointed: only the destination changes, so Bedrock guardrails and strict-tools settings survive. - When they differ — typically a
BedrockModelapp against an Anthropic or OpenAI gateway — the model is swapped for the matching provider model: the Bedrock-style model id is translated to its first-party equivalent, sampling parameters are re-routed, and structured output is preserved. Bedrock-only knobs (guardrail id, strict tools) have no equivalent on the target and are dropped — platform policy covers that ground instead. The swap is why the image needs the[anthropic]or[openai]extra.
In both cases the gateway URL and the platform-pinned model win over whatever the app hardcoded — the app chooses neither, which is exactly the model-id portability governed claude-agents get.
A2A skills in Python
The package includes the Python mirror of @initializ/a2a-kit: author a
skill as a plain function, assemble an executor, and serve it —
dependency-free, under the same governance the package injects.
from initializ_strands import define_agent, serve, skill
def review(inp: dict, ctx) -> dict:
# ctx.task_id is the A2A task id — already the active audit
# invocation, so everything this run does correlates under it.
result = run_review_pipeline(inp["repo"], inp["pr"], trace_id=ctx.task_id)
return {"status": "accepted", "trace": ctx.task_id}
agent = define_agent(
skills=[
skill(
"review-pr",
review,
name="Review a pull request",
description="Run the code review pipeline on a PR.",
input_schema={
"type": "object",
"properties": {"repo": {"type": "string"}, "pr": {"type": "integer"}},
"required": ["pr"],
},
)
]
)
serve(agent) # blocks; pass block=False to run in a background threadOne authoring difference from the Node kit: there is no package.json
pointer in Python — your app calls serve() itself as its main
server. Everything else matches the platform's A2A contract:
- The Agent Card (
/.well-known/agent-card.json) is built from your skills — id, name, description, tags, examples, input schema — and advertises the bearer auth scheme. - Inbound calls are bearer-authenticated by default and fail closed: the caller's platform token is verified against the platform, and workspace-scoped tokens must match the agent's workspace. The audit actor is always the verified caller identity — a client-supplied "acting user" header is recorded separately and can never spoof attribution.
- The inbound task id becomes the audit invocation for the whole run, so the A2A lifecycle and every governed model/tool call the task drives appear as one timeline.
- Each task — successes and failures — is pushed to the platform's session store, so the agent's execution list and per-run transcripts fill in the console like any platform-built agent's. See what the platform records for every run.
Enable it at deploy time with the spec's a2a block
(A2A exposure), like any governed agent.
Platform MCP connections for Strands
Declare the MCP servers the agent uses in the deploy spec's
mcp block;
the platform resolves them against the workspace's connected servers
(fail-closed) and injects the granted list — the INITIALIZ_MCP_SERVERS
environment the package reads. At startup, each granted server is
attached to every governed Agent as a Strands MCP tool provider, its
tools namespaced per server, with:
- Short-lived per-connection access tokens minted from the platform and refreshed before expiry — the durable credential never reaches the agent.
- Policy and audit on every MCP tool call, identical to any other
managed tool call — a deny in
enforcemode cancels the call. - Best-effort injection: a server that fails to set up is skipped and
logged rather than crashing the agent, unless the spec marked it
required.
Currently auth: platform (the agent's own workspace identity) is the
supported mode for Strands; delegated per-user connections are not yet
wired for this runtime.
For app code that needs to reach a granted MCP server directly — a
deterministic pre-fetch rather than an LLM-driven tool call — the package
exposes mcp_connection(name), mcp_token(name), and mcp_client(name)
helpers that use the same grant scoping and token cache.
Ship the governed image
Like the Node governance wrapper,
governance is added as a wrapper build step in CI — your app's
Dockerfile and dependency lockfile stay untouched. The governance wheel
ships as an OCI image (ghcr.io/initializ/strands-pkg, the Python analog
of the claude-agent package image): the wrapper copies the wheel out of it
and pip-installs it into the same interpreter your app uses, so the
shipped .pth lands on the app's startup path. Pin a v<version> tag —
a pinned tag is a pinned SDK version; v0 tracks latest.
ARG STRANDS_PKG_IMAGE=ghcr.io/initializ/strands-pkg:v0
ARG AGENT_IMAGE
ARG INITIALIZ_STRANDS_EXTRA=anthropic # or openai — match model.provider
FROM ${STRANDS_PKG_IMAGE} AS strandspkg
FROM ${AGENT_IMAGE}
ARG INITIALIZ_STRANDS_EXTRA
COPY --from=strandspkg /wheels /tmp/initializ-wheels
RUN pip install --no-cache-dir "$(ls /tmp/initializ-wheels/initializ_strands-*.whl)[${INITIALIZ_STRANDS_EXTRA}]" \
&& rm -rf /tmp/initializ-wheelsThe pipeline is the same build → wrap → push → deploy shape as the Node
wrapper — build your unchanged image, wrap it with this Dockerfile
(--build-arg AGENT_IMAGE=…), push only the wrapped image, and deploy it
with agent.type: strands:
agent:
name: advisory-bot
type: strands
model:
provider: anthropicNo platform configuration is baked into the image: identity, the gateway endpoint and credential, enforcement mode, and audit wiring are all injected at deploy time, so one governed image promotes across environments. See the strands runtime reference for the full spec surface and Deploy a CI-built agent for the surrounding pipeline.
Build a governed image in CI
Wrap your agent's unchanged image with the initializ governance layer in CI — the wrapper Dockerfile, the build-wrap-push-deploy pipeline, and what the governed loader records and enforces at runtime.
Convert an AgentCore Strands agent
A step-by-step runbook for taking a Strands agent written for AWS Bedrock AgentCore — entrypoint decorator, AgentCore runtime, AWS deploy scripts — and making it a governed initializ platform agent, with every code change shown.