initializdocs
DeveloperAgent SDK

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 constructinitializ 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 dependencyRemoved. 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 → InvokeAgentRuntimeA scheduled or manual invoke of the agent's governed A2A endpoint (see Step 8)
AgentCore session microVMs / observabilityPlatform 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/null

Typical findings and where each one goes:

  • app.py (or equivalent): the BedrockAgentCoreApp shell → rewritten in Step 3.
  • A logging/config module reading AgentCore session/request ids → replaced by ctx.task_id in Step 3.
  • A client module decorated with requires_access_token for 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.

pyproject.toml (before → after)
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:

RemovedReplacement
from bedrock_agentcore import BedrockAgentCoreAppfrom 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_taskNo 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 stateNo 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:

src/myagent/app.py (before)
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:

src/myagent/app.py (after)
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 environment

What 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 dict the entrypoint got. A caller that sends plain text instead delivers a string — keep the defensive isinstance guard.
  • The return value gains shape. Return a dict with text (a human-readable line) and data (your old structured response). A plain string also works. The data.status value 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. SkillContext has an emit field reserved for incremental output, but the served A2A endpoint is fire-and-forget: the Agent Card advertises streaming: 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.provider speaks 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-runtime endpoint allowances, a BEDROCK_MODEL_ID env override (the deploy spec's model.name is the model selector now).
  • Declare the gateway in the deploy spec: model.provider: anthropic (or openai) plus an optional model.name pin — Step 7. The provider must match the wrapper's extra in Step 6.
  • Keep AWS_REGION only 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, and sts.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_agentcore from 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.py now imports initializ_strands at 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):

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 — 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) deploy

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

initializ-deploy.yaml
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 any bedrock-agentcore-control usage): replaced entirely by initializ agent deploy in 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/send with the payload as a data part, authenticated with a platform-issued bearer token, to the agent's A2A URL (expose it via ingress if the scheduler is outside the cluster). This is the direct successor to the InvokeAgentRuntime call — 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

  1. Re-grep. Every pattern from Step 1 must return nothing (comments aside) — one surviving bedrock_agentcore import is a guaranteed ModuleNotFoundError at pod start.
  2. Build and wrap per Step 6; the wrapper's import myagent.app check proves the entrypoint rewrite is importable with the SDK present.
  3. Deploy with initializ agent deploy -f initializ-deploy.yaml --image <wrapped> --wait.
  4. Confirm activation in the logs: startup prints initializ governance active (enforcement=audit_only, gateway=on, ...) followed by the A2A endpoint line listing your skills. A governance inactive: missing INITIALIZ_ORG_ID/AGENT_ID warning means the process is running outside a platform deploy.
  5. Invoke from the console using the typed form (send a small, side-effect-light payload if the agent supports one).
  6. 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_call events carrying the platform model and provider with token usage, tool_exec start/end pairs for each tool, and a closing invocation_complete with aggregate usage. An llm_call naming 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.
  7. Then turn the dials: run in audit_only until the timeline looks right, flip INITIALIZ_ENFORCEMENT to enforce when the policy is dialed in, and enable audit.capture if 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.

On this page