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.
An AgentCore Strands agent is a Python agent built on
AWS Strands and packaged for the Amazon
Bedrock AgentCore runtime: its app.py wraps the business logic in a
BedrockAgentCoreApp, exposes one @app.entrypoint function that receives
a JSON payload, calls Bedrock directly through BedrockModel/boto3, and is
deployed by AWS-specific scripts (bedrock-agentcore-control, EventBridge
schedules, invoke Lambdas).
Converting it to an initializ agent keeps everything that matters — the
Strands Agent(...) constructions, the tools, the pipeline, usually the
Dockerfile — and swaps only the AgentCore shell around them. The result
runs on the platform as a governed
strands runtime: every
model and tool call audited, model traffic pinned to the platform-managed
gateway, policy checks on managed tool calls, and a typed, authenticated
A2A endpoint instead of InvokeAgentRuntime.
This page is a complete, prescriptive runbook: follow the steps in order and the agent converts end to end. It assumes the concepts in Govern a Strands agent; that page explains why the pieces work, this one tells you exactly what to change.
Conversion at a glance
| AgentCore construct | initializ equivalent |
|---|---|
BedrockAgentCoreApp() + @app.entrypoint def invoke(payload) | initializ_strands.define_agent + skill(...) — the run function keeps the payload dict, gains a ctx |
app.run(host="0.0.0.0") | serve(AGENT) — a dependency-free A2A server (Agent Card + JSON-RPC) |
BedrockAgentCoreContext.get_session_id() / get_request_id() | ctx.task_id — the A2A task id, already the active audit invocation |
app.add_async_task(...) / app.complete_async_task(...) | Delete — serve() answers health probes itself (GET /health); background work is a plain thread + contextvars.copy_context() |
bedrock-agentcore dependency | Removed. initializ-strands is installed by the CI governance wrapper, never by your lockfile |
BedrockModel calling Bedrock directly (boto3, region, IAM) | Unchanged in code — the governance package repoints or swaps it onto the platform model gateway at startup |
AgentCore Identity OAuth (requires_access_token) | A platform MCP connection (mcp block) + the mcp_token / mcp_client helpers |
scripts/deploy_agentcore.py (bedrock-agentcore-control) | initializ agent deploy -f initializ-deploy.yaml |
EventBridge Scheduler → trigger Lambda → InvokeAgentRuntime | A scheduled or manual invoke of the agent's governed A2A endpoint (see Step 8) |
| AgentCore session microVMs / observability | Platform execution records — audit timeline, per-run transcript, token usage in the console |
Before you start
- The agent's repository, with its Dockerfile and dependency lockfile.
- A CI pipeline (or local Docker) that can build, wrap, push, and run
initializ agent deploy— see Deploy a CI-built agent for tokens and registry setup. - The Strands governance package image reference for your environment
(
ghcr.io/initializ/strands-pkg:v<version>) — ask your platform operator.
Step 1 — inventory the AgentCore surface
Find every place AgentCore touches the codebase before changing anything. Run these from the repo root and keep the hit list — Step 9's re-grep must come back empty:
# The SDK itself — every import must be removed or replaced
grep -rn "bedrock_agentcore" src/ tests/
# The runtime shell: app object, entrypoint, server
grep -rn "BedrockAgentCoreApp\|@app.entrypoint\|app\.run(" src/
# Context / session / request ids read from the AgentCore runtime
grep -rn "BedrockAgentCoreContext" src/
# Async-task health plumbing (keeps AgentCore's /ping "busy")
grep -rn "add_async_task\|complete_async_task" src/
# AgentCore Identity (OAuth token vending for external SaaS)
grep -rn "bedrock_agentcore.identity\|requires_access_token" src/
# The dependency and any build-time import checks
grep -n "bedrock-agentcore\|bedrock_agentcore" pyproject.toml requirements*.txt Dockerfile
# Deploy + trigger integrations outside the app
grep -rln "bedrock-agentcore-control\|InvokeAgentRuntime\|agent_runtime\|AGENT_RUNTIME_ARN" \
scripts/ infrastructure/ .buildkite/ .github/ 2>/dev/nullTypical findings and where each one goes:
app.py(or equivalent): theBedrockAgentCoreAppshell → rewritten in Step 3.- A logging/config module reading AgentCore session/request ids → replaced
by
ctx.task_idin Step 3. - A client module decorated with
requires_access_tokenfor a SaaS OAuth provider → replaced by a platform MCP connection in Step 5. pyproject.toml/requirements.txt+ Dockerfile import checks → Step 2 and Step 6.scripts/deploy_agentcore.py, EventBridge/Lambda Terraform, invoke helpers → deleted or repointed in Step 8.
Step 2 — remove the AgentCore SDK dependency
Delete bedrock-agentcore from pyproject.toml (and regenerate your
lockfile / requirements.txt). Keep strands-agents (and
strands-agents-tools if used) — the Strands framework itself is exactly
what the platform governs.
dependencies = [
- "bedrock-agentcore",
"strands-agents",
"strands-agents-tools",
"boto3>=1.36.0", # keep only if the agent genuinely uses AWS APIs (S3, Secrets Manager, ...)
]Do not add initializ-strands to the runtime dependencies. In
production it is installed by the governance wrapper build (Step 6), so
your app's locked, hashed dependency set stays untouched. For local tests
that import your entrypoint module, install the initializ-strands wheel
into your dev environment (a dev extra is a reasonable home for it).
What each removed import maps to — verify every hit from Step 1 against this table and be deliberate about the last two rows:
| Removed | Replacement |
|---|---|
from bedrock_agentcore import BedrockAgentCoreApp | from initializ_strands import define_agent, serve, skill (Step 3) |
BedrockAgentCoreContext.get_session_id() / .get_request_id() | ctx.task_id — one correlation id for the whole task. There are no separate session and request ids; if your logging captured both, pass the task id for one and None for the other |
app.add_async_task / app.complete_async_task | No equivalent — delete. AgentCore used these to keep /ping reporting busy; the platform's probes just need the A2A server's built-in GET /health, which serve() answers. Long work stays a daemon thread |
bedrock_agentcore.identity (requires_access_token) | A platform MCP connection: the platform brokers short-lived per-connection tokens; app code uses mcp_token(name) / mcp_client(name) (Step 5). There is no general-purpose OAuth vending machine — a provider that isn't an MCP server needs its own credential via env/secret |
| AgentCore Memory / long-lived session state | No equivalent. The platform persists a per-task execution record (transcript, audit timeline) automatically, but it is not an app-readable memory API. State the agent reads back across runs must live in a store the agent owns (a database, S3, ...) — most pipeline-style agents already do this |
Also delete AgentCore-specific log plumbing (for example a logger
configuration that special-cases bedrock_agentcore.app's handlers) —
with the SDK gone it configures a logger that no longer exists.
Step 3 — replace the entrypoint
This is the only real code rewrite. The AgentCore shell:
import contextvars
import threading
from bedrock_agentcore import BedrockAgentCoreApp
from bedrock_agentcore.runtime.context import BedrockAgentCoreContext
app = BedrockAgentCoreApp()
@app.entrypoint
def invoke(payload: dict) -> dict:
result = validate(payload) # synchronous precondition check
if result.failed:
return {"status": "error", "error": result.message}
session_id = BedrockAgentCoreContext.get_session_id()
request_id = BedrockAgentCoreContext.get_request_id()
set_log_ids(session_id, request_id)
task_id = app.add_async_task("myagent_pipeline")
ctx = contextvars.copy_context()
threading.Thread(target=ctx.run, args=(run_pipeline, result, task_id), daemon=True).start()
return {"status": "accepted", "run_id": result.run_id}
def run_pipeline(result, task_id):
try:
... # the long-running Strands work
finally:
app.complete_async_task(task_id)
if __name__ == "__main__":
app.run(host="0.0.0.0")becomes an initializ-strands skill served over A2A:
import contextvars
import threading
from initializ_strands import define_agent, serve, skill
# Advertised on the Agent Card so callers (and the console's invoke form)
# know the input shape. Mirror your old entrypoint's documented payload.
_INPUT_SCHEMA = {
"type": "object",
"properties": {
"mode": {"type": "string", "enum": ["discovered", "specified"], "default": "discovered"},
"max_items": {"type": "integer", "default": 1},
},
}
def run(payload: dict, ctx) -> dict:
if not isinstance(payload, dict): # a text-only A2A message arrives as a string
payload = {}
result = validate(payload) # synchronous precondition check, unchanged
if result.failed:
return {"text": f"validation failed: {result.message}", "data": {"status": "error"}}
# ctx.task_id replaces the AgentCore session/request ids: it is the A2A
# task id AND the active audit invocation for everything this run does.
set_log_ids(ctx.task_id, None)
# Snapshot AFTER any per-request context sets — contextvars do not
# auto-propagate into a manually started thread, and the snapshot is
# what carries the audit invocation id into the background pipeline.
snapshot = contextvars.copy_context()
threading.Thread(target=snapshot.run, args=(run_pipeline, result), daemon=True).start()
return {
"text": f"Accepted run {result.run_id}; running in the background.",
"data": {"status": "accepted", "run_id": result.run_id, "task_id": ctx.task_id},
}
def run_pipeline(result):
... # the long-running Strands work — no task bookkeeping needed
AGENT = define_agent(
skills=[
skill(
"run",
run,
name="Run the analysis",
description="Validates the request, then runs the pipeline in the background.",
input_schema=_INPUT_SCHEMA,
)
]
)
if __name__ == "__main__":
serve(AGENT) # host/port/auth come from the platform-injected environmentWhat changed, precisely:
- The run function keeps its payload dict. An A2A caller sends the
payload as the message's structured data part; your skill receives it
as the same
dictthe entrypoint got. A caller that sends plain text instead delivers a string — keep the defensiveisinstanceguard. - The return value gains shape. Return a dict with
text(a human-readable line) anddata(your old structured response). A plain string also works. Thedata.statusvalue is a contract with the audit timeline:"accepted"means a background pipeline will finish the run (its own governed completion event closes the timeline); any other status ("error","rejected", ...) marks the run finished — and failed, for"error"— right there. - Exceptions are handled for you. A raise inside the skill becomes a
failed A2A task and a recorded failed execution — you no longer need to
catch-and-format every error, though returning a structured
"error"ack for expected failures gives callers a nicer artifact. - Every
Agent(...)stays untouched. Your Strands agents, tools, prompts, and pipeline modules do not change — governance hooks and gateway routing are injected into them automatically by the package the wrapper installs. - No streaming today.
SkillContexthas anemitfield reserved for incremental output, but the served A2A endpoint is fire-and-forget: the Agent Card advertisesstreaming: false, and a skill returns one final ack. Keep long work on the background-thread pattern above.
Step 4 — point model access at the managed gateway
An AgentCore agent calls Bedrock directly: BedrockModel(model_id=..., region_name=...) resolves IAM credentials and hits the regional Bedrock
endpoint. On the platform you change no model code — the governance
package routes model construction at interpreter start:
- If the gateway for your
model.providerspeaks the model's native protocol, the model is repointed — only the destination URL changes. - Otherwise it is swapped for the provider model that matches the
gateway (typically
BedrockModel→ the Anthropic model class): the Bedrock-style id is translated to its first-party equivalent (us.anthropic.claude-sonnet-4-6→claude-sonnet-4-6), sampling parameters are re-routed, and structured output is preserved. Bedrock-only knobs — guardrail ids, strict tools — have no equivalent and are dropped with a startup warning; platform policy covers that ground instead.
In both cases the platform-pinned model wins over whatever the app hardcoded. Full semantics in Govern a Strands agent.
What this means for the conversion:
- Keep the
BedrockModel(...)constructions and their model ids — they become inert defaults, useful for running the agent outside the platform. - Delete any deploy-time plumbing whose only job was Bedrock access:
Bedrock IAM policy statements,
bedrock-runtimeendpoint allowances, aBEDROCK_MODEL_IDenv override (the deploy spec'smodel.nameis the model selector now). - Declare the gateway in the deploy spec:
model.provider: anthropic(oropenai) plus an optionalmodel.namepin — Step 7. The provider must match the wrapper's extra in Step 6. - Keep
AWS_REGIONonly if the agent still uses other AWS APIs (Step 5).
One dependency caveat: when the swap targets the Anthropic model class,
Strands drives the anthropic Python SDK that the wrapper's [anthropic]
extra installs. If your lockfile also pins anthropic, keep it inside the
range your Strands version supports — a too-new major can break streaming
calls at runtime (see Troubleshooting).
Step 5 — external calls, AWS access, and secrets
The platform's default posture is deny-all egress: every external host
the agent reaches must be declared in the deploy spec's
egress.additionalDomains. Walk the agent's HTTP clients and list every
hostname — third-party APIs, search endpoints, api.github.com, all of
them.
AWS APIs can stay — honestly declared. If the agent genuinely uses AWS resources (run state in S3, keys in Secrets Manager), that code does not need to change, but two things AgentCore gave it for free must now be provided:
- Credentials: the pod has no AWS role by default. Wire the agent's
Kubernetes ServiceAccount to an IAM role (IRSA or EKS Pod Identity)
scoped to exactly the buckets/secrets it reads — never put static AWS
keys in
env. - Egress: add the regional AWS endpoints it calls, for example
secretsmanager.us-east-1.amazonaws.com,s3.us-east-1.amazonaws.com,<bucket>.s3.us-east-1.amazonaws.com, andsts.us-east-1.amazonaws.com(IRSA's token exchange).
Secrets: non-secret configuration (bucket names, secret names,
feature flags) moves into the deploy spec's env list. Secret values
either stay in AWS Secrets Manager (fetched at runtime under the IAM role
above) or move to platform-managed configuration — pick one home per
secret and delete the other path.
AgentCore Identity OAuth becomes a platform MCP connection. If the
agent used requires_access_token to reach a SaaS API (for example a
hosted MCP server that only accepts OAuth), delete that module's identity
code and declare the server in the spec's mcp block instead. The
platform resolves the declaration against the workspace's connected
servers (fail-closed) and the governed runtime injects it as a tool
provider on every Agent — with short-lived brokered tokens, per-call
policy checks, and audit. For deterministic app-side calls (a pre-fetch
rather than an LLM tool call), use the helpers:
from initializ_strands import mcp_client
with mcp_client("wiki-read") as c: # None if the workspace hasn't connected it
hits = c.call_tool_sync(tool_use_id="lookup-1", name="search", arguments={"query": q})Keep such lookups fail-open (return an empty result on any error) if they are advisory signals — a missing grant then degrades the feature instead of the run. See platform MCP connections for Strands.
Step 6 — containerize with the governance wrapper
Your agent's own Dockerfile mostly stays as-is: same base image, same
locked install, same EXPOSE 8080, and the same
CMD ["python", "-m", "myagent.app"] — which now ends in serve(). Two
edits are usually needed:
- Remove
bedrock_agentcorefrom any build-time import check (python -c "import ..., bedrock_agentcore"fails once Step 2 lands). - If the Dockerfile smoke-imports your entrypoint module
(
python -c "import myagent.app"), move that check into the wrapper stage below —app.pynow importsinitializ_strandsat module top, which exists only after the wrap.
Then add the standard Python governance wrapper (the Strands analog of the Node wrapper; full background in ship the governed image):
ARG STRANDS_PKG_IMAGE=ghcr.io/initializ/strands-pkg:v0
ARG AGENT_IMAGE
ARG INITIALIZ_STRANDS_EXTRA=anthropic # or openai — must 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 platform enforces runAsNonRoot; python base images default to root.
# A fixed numeric UID lets Kubernetes verify non-root without /etc/passwd.
ENV HOME=/home/app
RUN mkdir -p /home/app && chown -R 1000:1000 /app /home/app
USER 1000
# The app-import check the base build had to skip — fail here, not at pod start.
RUN python -c "import myagent.app" && echo "governed image: import OK"The wheel's shipped .pth file auto-activates governance on every
interpreter start — no ENV, no CMD change, no app import. Pin the
strands-pkg tag to a version; a pinned tag is a pinned SDK version.
The pipeline is build → wrap → push → deploy:
docker build -t "myagent:app-${BUILD_ID}" . # 1) unchanged app image
docker build \
--build-arg AGENT_IMAGE="myagent:app-${BUILD_ID}" \
--build-arg STRANDS_PKG_IMAGE="${STRANDS_PKG_IMAGE}" \
-f deploy/initializ-governance-python.Dockerfile \
-t "${IMAGE_REPO}:${TAG}" . # 2) wrap
docker push "${IMAGE_REPO}:${TAG}" # 3) push only the wrapped image
initializ agent deploy -f initializ-deploy.yaml --image "${IMAGE_REPO}:${TAG}" # 4) deployNote the converted image runs governed or not at all: unwrapped, the entrypoint import fails (by design — governance is a build property, not an app option). Local runs use the wrapped image, or a dev env with the wheel installed.
Step 7 — write the deploy spec
Create initializ-deploy.yaml at the repo root. This replaces everything
scripts/deploy_agentcore.py and its Terraform encoded — runtime
creation, endpoint, env, IAM-adjacent wiring:
apiVersion: initializ.ai/v1
kind: AgentDeploy
agent:
name: myagent
type: strands
# owner: you@example.com # → Owner in the console identity view
image: REPLACED_BY_CI # CI passes --image with the wrapped tag
# Selects the platform-managed model gateway. Replaces direct Bedrock access;
# the app's hardcoded Bedrock model id is overridden by `name`.
model:
provider: anthropic # must match the wrapper's extra (Step 6)
name: claude-sonnet-4-6 # optional pin; platform default otherwise
# The A2A endpoint serve() runs. AgentCore images EXPOSE 8080, so pin the
# A2A port to it (the platform default is 9090).
a2a:
enabled: true
port: 8080
auth: bearer # platform-token verification, fail-closed
port: 8080
# Reachable in-cluster only; add `reachability: public` (or private) if an
# external scheduler must call it — see the ingress reference.
ingress:
reachability: cluster
# MCP servers the agent uses (Step 5). Declaration, not a grant — servers the
# workspace hasn't connected are dropped, fail-closed.
mcp:
servers:
- name: wiki-read
url: https://mcp.example.com/v1/sse
auth: platform
tools:
allow: [search, get_page]
env:
- { name: INITIALIZ_ENFORCEMENT, value: audit_only } # observe first, then enforce
# App configuration that used to live in the AgentCore runtime env:
- { name: S3_BUCKET, value: "${S3_BUCKET}" }
- { name: AWS_REGION, value: "${AWS_REGION}" }
# Platform identity, gateway URL/credential, the pinned model, policy and
# audit wiring are ALL injected at deploy time — never set them here.
# Optional: attach bounded raw prompts/completions/tool payloads to audit
# events (privacy-sensitive; hashes-only without it).
audit:
capture: true
# Every external host the agent reaches (deny-all otherwise) — Step 5's list.
egress:
additionalDomains:
- api.example-vuln-feed.com
- api.github.com
- secretsmanager.us-east-1.amazonaws.com
- s3.us-east-1.amazonaws.com
- sts.us-east-1.amazonaws.com
resources:
replicas: 1
requests: { cpu: "500m", memory: 1Gi }
limits: { cpu: "2", memory: 2Gi }Field-by-field reference:
the strands runtime,
A2A exposure,
the mcp block,
audit payload capture,
and network reachability.
An agent that isn't a good A2A fit can declare a plain
http invoke route instead
— but the A2A shape above is the conversion default: it is what serve()
provides, and it gets the typed console invoke form for free.
Step 8 — retire the AgentCore deploy scripts and scheduler
Delete the AWS deploy path — it has no role left:
scripts/deploy_agentcore.py(or anybedrock-agentcore-controlusage): replaced entirely byinitializ agent deployin CI.- The AgentCore runtime/endpoint Terraform, and the CI step that called the deploy script.
The scheduled trigger needs a decision. The AgentCore pattern is
EventBridge Scheduler → a trigger Lambda → InvokeAgentRuntime with the
run payload. Its replacements, in order of preference:
- Invoke on demand from the console. The agent's page renders a typed
invoke form from the skill's
input_schema— often enough while the conversion bakes. - Point your scheduler at the A2A endpoint. Any scheduler that can
POST JSON can replace the Lambda: send a JSON-RPC
message/sendwith the payload as a data part, authenticated with a platform-issued bearer token, to the agent's A2A URL (expose it viaingressif the scheduler is outside the cluster). This is the direct successor to theInvokeAgentRuntimecall — same fire-and-forget ack, now on a governed, audited endpoint:
curl -sS "$AGENT_A2A_URL" \
-H "Authorization: Bearer $PLATFORM_TOKEN" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":"1","method":"message/send","params":{"message":{
"role":"user",
"parts":[{"kind":"data","data":{"mode":"discovered","max_items":10}}],
"metadata":{"skillId":"run"}}}}'- Platform schedules (Schedules) are the platform-owned cron surface — declared on the agent, executed by the platform. Today they apply to platform-created autonomous agents; CI-deployed agents keep repository-owned triggering, so for a converted agent the scheduler-to-A2A pattern above is the supported recurring trigger.
Step 9 — deploy and verify
- Re-grep. Every pattern from Step 1 must return nothing (comments
aside) — one surviving
bedrock_agentcoreimport is a guaranteedModuleNotFoundErrorat pod start. - Build and wrap per Step 6; the wrapper's
import myagent.appcheck proves the entrypoint rewrite is importable with the SDK present. - Deploy with
initializ agent deploy -f initializ-deploy.yaml --image <wrapped> --wait. - Confirm activation in the logs: startup prints
initializ governance active (enforcement=audit_only, gateway=on, ...)followed by the A2A endpoint line listing your skills. Agovernance inactive: missing INITIALIZ_ORG_ID/AGENT_IDwarning means the process is running outside a platform deploy. - Invoke from the console using the typed form (send a small, side-effect-light payload if the agent supports one).
- Check the run's records — the same surface every governed runtime
gets (what the platform records):
- The Activity/Events timeline for the task shows the inbound
verification and session start, then
llm_callevents carrying the platform model and provider with token usage,tool_execstart/end pairs for each tool, and a closinginvocation_completewith aggregate usage. Anllm_callnaming the gateway-pinned model is the proof model traffic did not bypass the gateway. - The execution list shows the run with its transcript — including failed runs.
- The Activity/Events timeline for the task shows the inbound
verification and session start, then
- Then turn the dials: run in
audit_onlyuntil the timeline looks right, flipINITIALIZ_ENFORCEMENTtoenforcewhen the policy is dialed in, and enableaudit.captureif reviewers need raw payloads.
Troubleshooting
Pod crashes at start with ModuleNotFoundError: bedrock_agentcore.
A leftover import survived Step 2 — usually in a lazily-imported client
module, a logging config, or a test helper that ships in the image. Re-run
the Step 1 greps; also check the Dockerfile's python -c "import ..."
lines, which fail the build the same way.
ImportError: initializ_strands at start (or in the base image
build). The process is running unwrapped. Production images must go
through the Step 6 wrapper; base-Dockerfile import checks of the
entrypoint module must live in the wrapper stage.
The skill receives a string, empty dict, or misshapen payload. The
old @app.entrypoint got the raw invoke JSON; the A2A skill gets the
message's data part — a text-only message arrives as a string
instead. Fix the caller to send the payload as a data part (see Step 8's
curl), keep the isinstance(payload, dict) guard, and make sure
input_schema matches what the run actually reads — the console form is
generated from it, so drift there produces well-formed-but-wrong payloads.
Runs never close on the timeline (or close twice). Check the ack's
data.status contract from Step 3: return "accepted" only when a
background pipeline was actually launched; return "error"/"rejected"
for synchronous failures. And snapshot contextvars.copy_context() after
your context sets, or the background pipeline's events land under a
different invocation id than the task.
Model calls bypass the gateway or fail against Bedrock. Symptoms:
egress denials to bedrock-runtime.<region>.amazonaws.com, missing
llm_call events, or the app's own model id showing in Activity. Causes,
in likelihood order: governance never activated (see the Step 9 log
line); the model call is a raw boto3 bedrock-runtime client rather
than a Strands model class — only Strands models are governed, so rewrite
such calls to a BedrockModel; or the wrapper extra doesn't match
model.provider, so the swap's provider SDK is missing.
TypeError inside a model call at stream time. A provider-SDK major
version outran your Strands version (seen with anthropic 1.x removing a
parameter Strands still passes). Pin the provider SDK in your lockfile to
the range your Strands release supports — the wrapper's extra resolves
against your already-installed pin.
A2A returns 401 for every caller. Bearer auth verifies platform
tokens and fails closed. The scheduler/caller must present a valid
platform-issued token for the agent's organization; check the pod log for
the A2A 401 unauthorized line to confirm it is the verifier rejecting
(not a network path issue).
An MCP tool is missing at runtime. The mcp block is a declaration:
a server the workspace admin hasn't connected is dropped fail-closed at
deploy, and the injection log line lists what was actually granted.
Advisory lookups should already be fail-open per Step 5.
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.
Installation
Install Forge via Homebrew, script, or binary download.