initializdocs
DeveloperAgent SDK

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_ENFORCEMENT picks the mode, exactly as for claude-agents: audit_only (default) decides and records but never blocks; enforce cancels 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 BedrockModel against 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 BedrockModel app 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.

main.py
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 thread

One 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 enforce mode 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.

deploy/initializ-governance-python.Dockerfile
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-wheels

The 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:

initializ-deploy.yaml (excerpt)
agent:
  name: advisory-bot
  type: strands

model:
  provider: anthropic

No 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.

On this page