You take one published agent and run it, unchanged, on three
runtimes: Solo Enterprise for kagent on kind,
AWS Bedrock AgentCore, and Google Cloud (Vertex,
through a GeminiAgentRuntime), with only its runtimeRef
changing between them. The agent pulls approved MCP tool servers
from the AgentRegistry catalog and bakes in a published skill, so it never wires
arbitrary tools. Then you lock down which of those tools it may call with a kagent
AccessPolicy, enforced at an agentgateway waypoint,
and watch the agent's tool list shrink live as printenv drops out.
This is the governance payoff for the AgentRegistry lifecycle from parts 1 and 2,
and it stands on its own kind cluster. Every output below is captured from the live
run.
The use case
An agent is only as useful as the tools it can reach, and only as safe as the
tools you let it reach. A platform team wants developers to pull tools from an
approved catalog rather than wiring arbitrary MCP servers, wants the
same published agent to run wherever the workload belongs (a local cluster, a
managed cloud runtime), and wants to draw a line around exactly which tools any
given agent may call. AgentRegistry plus Solo Enterprise for kagent provides all
three: the catalog and packaging, deployment by reference onto any registered
runtime, and an identity-aware AccessPolicy that an agentgateway
waypoint enforces at the tool level.
What the lab builds
dice agent
- what
- Local tools
roll_die+check_prime - stack
- ADK Python, Anthropic
claude-haiku-4-5 - uses
- The catalog MCP tools + the baked skill
approved tool servers
- what
everything-server(sum, echo, to_uppercase, reverse_text, printenv) +my-mcp(word_count)- stack
- FastMCP, Python, HTTP transport
- ships as
- OCI images published to the catalog by setup
dice-game
- what
- A house format for reporting rolls and prime checks
- stack
- Markdown with YAML frontmatter
- ships as
- Published to the catalog; baked into the agent at build
The agent calls the MCP servers' tools at runtime, and the skill is folded into its instruction at build time. The MCP servers and the skill are published once by the lab setup, so in the demo they are already in the catalog, waiting to be pulled.
The flow
runtimeRef. On kagent the agent runs
the OCI image and uses your Anthropic key. On AgentCore it runs native Bedrock
Claude in your AWS account and clones its source from a git repo at deploy time,
so AgentCore needs a repo URL where the lab setup pushes the scaffolded
agentdemo/. The same published agent also deploys to Google Cloud
through a GeminiAgentRuntime (Vertex), registered with
arctl runtime setup gemini-agent-runtime, by pointing the
runtimeRef at that runtime.
Prerequisites
You bring a few tools and credentials; ./scripts/setup.sh installs the
whole platform into a throwaway kind cluster. Nothing in the “what setup
installs” list below needs to exist beforehand, the scripts stand it all up.
What you provide
- Tooling: Docker,
kind,kubectl,helm,arctl,jq,gh. Addawsand/orgcloudonly for the cloud runtimes you want. - A Solo Enterprise license (kagent + agentgateway/AgentRegistry)
and an Anthropic API key for the agent's model.
./scripts/setup-env.shcaptures these into a gitignored.env.local. - AWS (optional), for the AgentCore runtime: an AWS session or
profile in a region where Bedrock AgentCore is available. Skip it with
CONNECT_AWS=false. - Google Cloud (optional), for the Vertex runtime:
gcloudlogged in to a project where you can runarctl runtime setup gemini-agent-runtime(it creates a deployer service account, so you need rights to create custom IAM roles). Connect it by settingGCP_PROJECT_ID; leave it unset to skip.
The two clouds are independent. Run the lab kagent-only, kagent + AWS, kagent + Google, or all three: the kagent runtime is always registered, and each cloud runtime registers only when you supply its credentials.
What ./scripts/setup.sh installs
- the kind cluster and the host-side local OCI
registry at
localhost:5001(the canonical kind local-registry recipe), whicharctl build --pushpushes to and the cluster pulls from; - Keycloak with the
agentregistryrealm, reached athttp://keycloak.localtest.me; - Solo Enterprise for kagent (the controller and Enterprise UI);
- the in-cluster AgentRegistry in the
agentregistry-systemnamespace (registry server plus a bundled Postgres and ClickHouse); - an ambient mesh and an agentgateway waypoint data plane. This
is required for the AccessPolicy step: the AccessPolicy is enforced at an
agentgateway waypoint, which needs the ambient deployment from
05-waypoint.sh(the Gloo Operator ServiceMeshController in ambient mode plus enterprise-agentgateway); - the agentgateway ingress that routes
*.localtest.meto the registry, the Enterprise UI, and Keycloak (so there are nokubectl port-forwards); - the published catalog (the two MCP servers and the
dice-gameskill) and the runtimes you opted into:kind-kagentalways, plusaws-agentcoreand/orgcp-vertexwhen their credentials are present.
bashsetup
./scripts/setup-env.sh # capture credentials (Anthropic key, Solo licenses; optional AWS profile / GCP project)
./scripts/setup.sh # bring up the platform (~20 min first run)
./scripts/open-consoles.sh # open the AgentRegistry + Enterprise UIs
# then open demo.ipynb (Bash kernel) and run top to bottom
*.localtest.me, no port-forwards: the AgentRegistry UI/API at
http://agentregistry.localtest.me, the Enterprise UI at
http://kagent.localtest.me, and Keycloak at
http://keycloak.localtest.me. The UI login is
admin-user / password (a realm user in the
admins group: registry superuser and kagent Admin). The
arctl CLI logs in as the same user with
arctl user login against the gateway issuer.
Connect
Before any step below, load your credentials and log arctl in to the
in-cluster registry. This reads .env.local, so run
./scripts/setup-env.sh first (it creates .env.local with your
Anthropic key, Solo licenses, and any AWS/GCP details) if you have not already. Then,
once per shell:
bashconnect
source scripts/connect.sh
connect.sh loads .env.local, puts arctl on the
PATH, points ARCTL_API_BASE_URL at
http://agentregistry.localtest.me, and runs arctl user login
as admin-user against the Keycloak issuer. The notebook's first cell does
exactly this; after it, the arctl get commands below work.
Step 1: browse the registry, then create the agent wired to one approved tool
Rather than wiring arbitrary MCP servers, the developer pulls from an
approved catalog. Open the AgentRegistry UI
(http://agentregistry.localtest.me) and go to Tool Servers, or list
the catalog from the CLI. The skills and the runtimes the registry can target are
there too: kind-kagent (Solo Enterprise for kagent) always, plus
aws-agentcore (AWS Bedrock AgentCore) and/or gcp-vertex
(Google Cloud, Vertex AI) for whichever clouds you connected at setup.
bashbrowse the catalog
arctl get mcpservers
arctl get skills
arctl get runtimes
textarctl get mcpservers / runtimes
NAME TAG DESCRIPTION
everything-server latest Approved org tool server: sum, echo, uppercase, reverse.
my-mcp latest Text utilities tool server: word_count.
NAME TYPE
aws-agentcore BedrockAgentCore
kind-kagent Kagent
local Local
Scaffold the agent and reference one approved server from the
catalog with --mcp, my-mcp (the word_count
tool). A second one is added later to show a tool coming online. arctl records the
reference in agent.yaml under spec.mcpServers, and the
scaffolded agent also ships two local tools, roll_die and
check_prime.
bashscaffold
arctl init agent agentdemo --framework adk --language python \
--model-provider anthropic --model-name claude-haiku-4-5 --mcp my-mcp@latest
Step 2: build and run it locally
Build the image, then run it in an interactive chat in a terminal. Ask it to roll
a die: it uses the local roll_die and check_prime tools.
The approved catalog tool word_count (from my-mcp) comes
online when the agent is deployed, where the registry stands the MCP
server up beside it.
bashbuild + local run
arctl build ./agentdemo
arctl run ./agentdemo # interactive A2A chat in your terminal
Step 3: build multi-arch, publish, push the source
Build for both architectures (kagent runs arm64 here, AgentCore runs amd64), publish the agent to the catalog, and push the source to the agent's git repo so AgentCore can clone it.
bashpublish
arctl build ./agentdemo --platform linux/amd64,linux/arm64 --push
arctl apply -f agentdemo/agent.yaml
./scripts/git-push.sh # AgentCore clones this at deploy
Step 4a: kick off the AgentCore deploy (runtime 2)
The same published agent, deployed to a second runtime. This signs in to AWS,
gives the in-cluster registry your AWS credentials, then builds, pushes to ECR,
and deploys in the background while you carry on with the kagent runtime. AgentCore
publishes its own agent record (agentdemo-agentcore, an ECR image), so
it never collides with the kagent agentdemo agent. The agent runs on
Bedrock Claude in your AWS account and clones its source from the git repo.
bashAgentCore (background)
source scripts/agentcore.sh & # foreground AWS sign-in, then build/push/deploy in the background
tail -f /tmp/agentcore-deploy.log # watch it provision (~2-4 min)
Step 4b: kick off the Google Cloud deploy (runtime 3)
Optional, and only if you connected Google at setup (GCP_PROJECT_ID set).
The same published agent, deployed to a third runtime: it lands on
Vertex AI Agent Engine and its MCP tool on Cloud Run,
both built from the same git source. One thing differs from kagent: on Google the
agent's MCP tools are wired explicitly, so the script deploys the MCP to Cloud Run
first, waits for it, then deploys the agent and links it through
deploymentRefs. That whole sequence runs in the background while you carry
on with the kagent runtime.
bashGoogle Cloud (background)
source scripts/gcp.sh & # deploy MCP -> Cloud Run, then agent -> Vertex AI, in the background
tail -f /tmp/gcp-deploy.log # watch it provision (~8-12 min)
Step 5: deploy onto kagent and work with it (runtime 1)
Now the in-cluster runtime. Deploy the approved MCP tool server and the agent onto
kind-kagent. The registry binds the agent to the runtime and derives
its MCP_SERVERS_CONFIG from the MCP servers deployed there, which is
why the tool server is deployed first. The agent then sits behind the kagent
controller's OIDC enforcement.
bashdeploy + pods
arctl apply -f yaml/deploy-mcp-my-mcp.yaml
kubectl --context kind-agentcore-demo -n kagent wait --for=condition=Ready mcpserver/my-mcp --timeout=180s
envsubst < yaml/deploy-kagent.yaml | arctl apply -f -
kubectl --context kind-agentcore-demo -n kagent get pods
Ask what tools it has, over the controller's OIDC-protected A2A endpoint. Right
now it lists word_count (from my-mcp) alongside the local
roll_die and check_prime.
bashask.sh (over OIDC)
./scripts/ask.sh "List the exact names of every tool you can call. Output only a comma-separated list, nothing else."
Add another approved server by declaring it in agent.yaml (in the UI:
Catalog → agentdemo → Edit → add everything-server, or
paste the snippet below under spec.mcpServers) and re-applying. No
rebuild: re-publish the agent, deploy the new MCP server, and re-deploy the agent
so it re-derives its tool list.
yamladd everything-server to agent.yaml
- kind: MCPServer
name: everything-server
tag: latest
bashapply the new tool, redeploy
arctl apply -f agentdemo/agent.yaml
arctl apply -f yaml/deploy-mcp-everything-server.yaml
envsubst < yaml/deploy-kagent.yaml | arctl apply -f -
kubectl --context kind-agentcore-demo -n kagent wait --for=condition=Ready mcpserver/everything-server --timeout=180s
kubectl --context kind-agentcore-demo -n kagent rollout status deploy/agentdemo --timeout=150s
Ask again, and now sum, echo, printenv,
reverse_text, and to_uppercase appear alongside
word_count and the local tools. The tool came online by adding it to
the spec and redeploying, with no rebuild. Then run a real task and watch the
trace: give the agent a multi-step prompt that exercises the whole chain.
- Die roll (13-sided): 4
- 4 + 5: 9
- Is 9 prime?: No, 9 is not a prime number (it's divisible by 3)
The arithmetic is done by the MCP sum tool, not the model, and the
answer follows the dice-game skill's house format. Open the Enterprise
UI (http://kagent.localtest.me) → Tracing and the run shows up as a
span tree, invocation → call_llm → generate_content → execute_tool roll_die
→ sum → check_prime, with the model and token usage on each LLM span.
Step 6: govern the MCP tools with an AccessPolicy
The everything-server exposes a sensitive printenv tool
that returns the server's environment. A platform owner restricts the agent to
only the tools it needs, in the Enterprise UI
(http://kagent.localtest.me) → Access Policies → Create New Access
Policy, doing the steps in order (the agent picker only populates once the cluster
and namespace are set):
- Policy: name it; Cluster
agentcore-demo; Namespacekagent. - Subjects: Subject Kind Agent →
agentdemo. - Action: ALLOW; tools
sum. - Target: Target Type MCP Server →
everything-server.
This is an allowlist, so on the everything-server the agent may now call only
sum, and printenv and the rest are denied. (Prefer the
CLI? ./scripts/accesspolicy-on.sh applies the identical policy.) The
MCP server is already behind an agentgateway waypoint, which the
registry labels at deploy time and which the ambient mesh from the prerequisites
brings up. That waypoint is the enforcement point: the kagent controller compiles
the AccessPolicy into an EnterpriseAgentgatewayPolicy the waypoint
enforces.
bashconfirm the waypoint, show the policy
kubectl --context kind-agentcore-demo -n kagent label mcpserver everything-server kagent.solo.io/waypoint=true --overwrite
kubectl --context kind-agentcore-demo -n kagent get accesspolicy -o yaml
Ask the agent for its tools before and after. The list shrinks, printenv
is gone, and sum and the rest still work:
roll_die, check_prime, echo, printenv, reverse_text, sum, to_uppercase, word_count
roll_die, check_prime, echo, reverse_text, sum, to_uppercase, word_count
The enforcement point is the waypoint, not the application: the agent never sees
printenv in its tool list and a direct call is refused. Revert with
./scripts/accesspolicy-off.sh and the full list returns.
Step 7a: invoke the same agent on AgentCore (runtime 2)
By now the AgentCore runtime kicked off in step 4a has provisioned. Invoke it with
the same kind of task: this runs the identical governed agent in AWS, not your
cluster. ac-invoke.sh waits for the runtime to be READY first, then
prints the answer.
bashac-invoke.sh
./scripts/ac-invoke.sh "Roll a 13-sided die, add 5 to the result, then tell me if it is prime."
Step 7b: invoke the same agent on Google (runtime 3)
The Google deploy from step 4b has provisioned by now. Invoke it with the same task:
this runs the identical governed agent on Vertex AI Agent Engine, calling its MCP tool
on Cloud Run. gcp-invoke.sh waits for the runtime to be READY, then queries
the Vertex Reasoning Engine and prints the answer. (Verified: it rolls a die, adds five,
and tells you whether the total is prime, using its roll_die and
check_prime tools.)
bashgcp-invoke.sh
./scripts/gcp-invoke.sh "Roll a 13-sided die, add 5 to the result, then tell me if it is prime."
Teardown
Everything lives in the throwaway kind cluster except the cloud-runtime resources,
which bill until removed. cleanup.sh tears down each cloud independently,
then the cluster:
bashcleanup
./scripts/cleanup.sh gcp # Vertex reasoning engine + Cloud Run MCP + the deployer SA & custom roles
./scripts/cleanup.sh agentcore # AgentCore runtime + CloudFormation role + ECR repo
./scripts/cleanup.sh all # both clouds, then the kind cluster and local registry
Each cloud path no-ops cleanly when that cloud was never set up, so all is
safe whichever runtimes you deployed. Pass KEEP_GCP_SA=true to keep the
Google deployer service account and custom roles for a later re-deploy.
Versions
Built and verified on:
v2026.6.12026.6.10.4.30.4.30.5.21.29.2-patch0v2026.5.1v1.4.026.3v1.35.0