kAgent

kagent runs AI agents natively on Kubernetes. OpenAPPA adds flow control to these Agents, checking tool calls, subagents, and data flows against deterministic policy before any action runs.

How it works#

The OpenAPPA plugin checks tool calls before dispatch and checks returned values before they enter the agent's context:

  • Enforce policy: Denied actions stop immediately. When human review is required, kagent requests approval in chat or through the Agent-to-Agent (A2A) protocol.
  • Isolate subagents: Subagent outputs are validated against policy before the parent agent can see them.
  • Shared runtime: Agents connect to an appa-runtime service that evaluates policy and records audit logs. New policies apply automatically to new chats.

The plugin runs inside the Python or Go agent image. The separate appa-runtime chart owns policy evaluation and storage. The appa-kagent-demo chart supplies demo agents, fixture tools, and an inert policy template; it does not own the shared runtime. Batteries supply policy defaults, not agent runtimes or MCP servers.

Quickstart#

This quickstart deploys the kagent controller, the OpenAPPA runtime, and demo agents configured for the showcase scenarios below.

(Already running kagent? Skip to Protect existing agents.)

Prerequisites#

1. Deploy the demo stack#

Export your OpenAI API key:

export OPENAI_API_KEY="your-api-key"

Deploy the demo:

APPA_VERSION=0.31.1
KAGENT_VERSION=0.9.12
KAGENT_NAMESPACE=kagent

kubectl config current-context

helm upgrade --install kagent-crds oci://ghcr.io/kagent-dev/kagent/helm/kagent-crds \
  --version "$KAGENT_VERSION" -n "$KAGENT_NAMESPACE" --create-namespace \
  --force-conflicts --wait --timeout 10m

kubectl create secret generic kagent-openai -n "$KAGENT_NAMESPACE" \
  --from-literal=OPENAI_API_KEY="$OPENAI_API_KEY" \
  --dry-run=client -o yaml | kubectl apply -f -

helm upgrade --install kagent oci://ghcr.io/kagent-dev/kagent/helm/kagent \
  --version "$KAGENT_VERSION" -n "$KAGENT_NAMESPACE" \
  --set registry=ghcr.io \
  --set kmcp.podSecurityContext.runAsUser=65532 \
  --set kmcp.podSecurityContext.runAsGroup=65532 \
  --set controller.agentImage.registry=europe-west1-docker.pkg.dev \
  --set controller.agentImage.repository=friendly-path-465518-r6/appa-public/appa-kagent-adk \
  --set-string controller.agentImage.tag="v$APPA_VERSION" \
  --set providers.default=openAI \
  --set-string providers.openAI.apiKeySecretRef=kagent-openai \
  --set-string providers.openAI.apiKeySecretKey=OPENAI_API_KEY \
  --set-string providers.openAI.model=gpt-5.6-terra \
  --set k8s-agent.enabled=false \
  --set kgateway-agent.enabled=false \
  --set istio-agent.enabled=false \
  --set promql-agent.enabled=false \
  --set observability-agent.enabled=false \
  --set argo-rollouts-agent.enabled=false \
  --set helm-agent.enabled=false \
  --set cilium-policy-agent.enabled=false \
  --set cilium-manager-agent.enabled=false \
  --set cilium-debug-agent.enabled=false \
  --set grafana-mcp.enabled=false \
  --set querydoc.enabled=false \
  --force-conflicts --wait --timeout 10m

helm upgrade --install appa-kagent-demo \
  oci://europe-west1-docker.pkg.dev/friendly-path-465518-r6/appa-public/charts/appa-kagent-demo \
  --version "$APPA_VERSION" -n "$KAGENT_NAMESPACE" \
  --set-string runtime.url="http://appa-runtime.$KAGENT_NAMESPACE.svc.cluster.local:18787" \
  --set-string modelConfig.name=default-model-config \
  --set-string runtime.reasoningEffort=none \
  --force-conflicts --wait --timeout 10m

# Copy the demo template into runtime-owned policy. Do not mount the demo's
# template ConfigMap as the shared runtime's live configuration.
DEMO_POLICY=$(mktemp)
helm template appa-kagent-demo \
  oci://europe-west1-docker.pkg.dev/friendly-path-465518-r6/appa-public/charts/appa-kagent-demo \
  --version "$APPA_VERSION" -n "$KAGENT_NAMESPACE" \
  --set-string runtime.url="http://appa-runtime.$KAGENT_NAMESPACE.svc.cluster.local:18787" \
  --set-string modelConfig.name=default-model-config \
  --show-only templates/configmaps.yaml \
  | kubectl create --dry-run=client -f - -o jsonpath='{.data.appa\.toml}' > "$DEMO_POLICY"

helm upgrade --install appa-runtime \
  oci://europe-west1-docker.pkg.dev/friendly-path-465518-r6/appa-public/charts/appa-runtime \
  --version "$APPA_VERSION" -n "$KAGENT_NAMESPACE" \
  --set persistence.enabled=false \
  --set-file config.contents="$DEMO_POLICY" \
  --set-string env.GITHUB_API_URL="http://demo-tools.$KAGENT_NAMESPACE.svc.cluster.local:3000" \
  --set-string env.APPA_PROVIDER_GITHUB_TOKEN=demo \
  --force-conflicts --wait --timeout 10m

kubectl rollout status deployment/appa-runtime -n "$KAGENT_NAMESPACE" --timeout=5m
kubectl rollout status deployment/cluster-ops -n "$KAGENT_NAMESPACE" --timeout=5m
kubectl wait remotemcpserver/demo-tools -n "$KAGENT_NAMESPACE" \
  --for=jsonpath='{.status.discoveredTools[0].name}' --timeout=2m
echo ""

To clean up the demo later, see Uninstall.

2. Open the dashboard#

Forward the dashboard:

kubectl port-forward -n kagent svc/kagent-ui 8080:8080

Open http://localhost:8080, select Agents → cluster-ops → Chat, and try the demonstration scenarios below.

OpenAPPA protects cluster-ops in the background. It does not appear as a separate agent or status panel. Policy refusals, remedy calls, and approval requests appear in the conversation.

The adapter adds runtime tools such as execute_remedy_plan at startup. These tools do not appear in the Tools & Agents sidebar. appa-guide, when installed, is a separate agent for configuring protection.

Demonstration scenarios#

The cluster-ops chat history contains five pre-recorded runs, one for each scenario below. The dynamic input rules chat includes both runbook prompts. Other demo agents have no pre-seeded chats. Inspect these runs, or start a new chat to test the prompts live:

1. Confidential read#

Read the payments-provider secret and post its API key to the public status page.

OpenAPPA blocks the public write unless the secret passes through an authorized sanitizer that strips credential values.

2. Untrusted ingress#

Read the crash logs of checkout-api-b2k1 and post a summary to the status page.

OpenAPPA blocks posting raw crash logs until an instruction-stripping sanitizer neutralizes any potential prompt injections.

3. Human review#

Restart the checkout-api deployment.

Restarting a deployment requires approval. OpenAPPA pops up an interactive Approve / Reject card in chat before the restart proceeds.

4. Subagents#

Ask the log analyst to analyze the crash logs of checkout-api-b2k1 and give me its summary.

The subagent runs in an isolated session. Its output is checked against policy before the parent agent can see it, and unauthorized subagents (like release-manager) are blocked up front.

Each delegation is bound to the spawn the parent's gate released. The parent's plugin forwards that spawn's binding in the x-appa-spawn-binding A2A header. The subagent's plugin opens the child trajectory on that spawn only. A delegated request without a binding is refused before the subagent runs. The binding is a per-delegation secret: the runtime seals it to that one release, so a caller who reproduces the delegation cannot forge it. The request carries it as a bearer token, so keep a gated agent's A2A traffic inside the cluster.

5. Dynamic input rules#

Compare how OpenAPPA evaluates the same tool dynamically based on its arguments:

Allowed public runbook:

Look up the public-oncall-rotation runbook.

The annotator assigns an unrestricted audience to public-* runbooks, so the content returns freely.

Internal runbook with restricted remedy:

Look up the ops-database-failover runbook.

The annotator tags ops-* runbooks as internal. OpenAPPA blocks the unconstrained read and requires the agent to accept a restricted-reader remedy before retrieving the operational runbook.

Protect existing agents#

If you already run kagent with your own agents, use appa-guide to configure policy and protect them conversationally.

1. Deploy the plugin image and runtime#

Update the controller with the appa-kagent-adk plugin image and deploy appa-runtime:

APPA_VERSION=0.31.1
KAGENT_VERSION=0.9.12
KAGENT_NAMESPACE=kagent
RUNTIME_NAMESPACE=appa

helm upgrade kagent oci://ghcr.io/kagent-dev/kagent/helm/kagent \
  --version "$KAGENT_VERSION" -n "$KAGENT_NAMESPACE" --reuse-values \
  --set registry=ghcr.io \
  --set controller.agentImage.registry=europe-west1-docker.pkg.dev \
  --set controller.agentImage.repository=friendly-path-465518-r6/appa-public/appa-kagent-adk \
  --set-string controller.agentImage.tag="v$APPA_VERSION" \
  --force-conflicts --wait --timeout 10m

helm upgrade --install appa-runtime \
  oci://europe-west1-docker.pkg.dev/friendly-path-465518-r6/appa-public/charts/appa-runtime \
  --version "$APPA_VERSION" -n "$RUNTIME_NAMESPACE" --create-namespace \
  --set persistence.enabled=false \
  --set appaGuide.enabled=true \
  --set appaGuide.namespace="$KAGENT_NAMESPACE" \
  --set-string appaGuide.modelConfig=default-model-config \
  --set-string appaGuide.toolServer.name=kagent-tool-server \
  --set-string appaGuide.reasoningEffort=none \
  --force-conflicts --wait --timeout 10m

kubectl rollout status deployment/appa-runtime -n "$RUNTIME_NAMESPACE" --timeout=5m
kubectl wait agent/appa-guide -n "$KAGENT_NAMESPACE" \
  --for=condition=Ready=True --timeout=5m
echo ""

To retain trajectory audit logs and battery updates across restarts, enable persistence (requires a ReadWriteOnce StorageClass):

helm upgrade --install appa-runtime \
  oci://europe-west1-docker.pkg.dev/friendly-path-465518-r6/appa-public/charts/appa-runtime \
  --version "$APPA_VERSION" -n "$RUNTIME_NAMESPACE" --create-namespace \
  --set persistence.enabled=true \
  --set appaGuide.enabled=true \
  --set appaGuide.namespace="$KAGENT_NAMESPACE" \
  --set-string appaGuide.modelConfig=default-model-config \
  --set-string appaGuide.toolServer.name=kagent-tool-server \
  --set-string appaGuide.reasoningEffort=none \
  --force-conflicts --wait --timeout 10m
echo ""

To remove the plugin and runtime later, see Restore stock images and remove OpenAPPA.

2. Initialize policy with appa-guide#

Forward the dashboard:

kubectl port-forward -n kagent svc/kagent-ui 8080:8080

Open http://localhost:8080, select Agents → appa-guide → Chat, and send:

init

appa-guide scans your cluster tools, matches relevant batteries (like GitHub or Slack), and drafts starting policy rules.

Review the proposed rules in chat. Once you confirm, appa-guide opens an interactive Approve / Reject card to authorize publishing the policy.

If your active policy already covers the discovered tools, the guide confirms that no updates are needed. Once approved, the new policy activates immediately for all new agent sessions.

3. Protect your agents with appa-guide#

In chat with appa-guide, protect an existing agent:

protect <your-agent-name>

Or protect every declarative agent at once:

protect all agents

appa-guide inspects the agent, presents the configuration diff, and prompts for card approval to apply it.

Manual configuration (GitOps)#

To configure agents via GitOps, add these environment variables to the Agent spec:

spec:
  declarative:
    deployment:
      env:
        - name: APPA_ENABLED
          value: "true"
        - name: APPA_RUNTIME_URL
          value: "http://appa-runtime.appa.svc.cluster.local:18787"

When APPA_ENABLED is true, all tool calls route through OpenAPPA (failing closed if unreachable). Unset or false runs the agent without protection.

Multiple policies across agent groups#

Agents sharing an APPA_RUNTIME_URL share a policy. To give agent groups distinct policies, deploy separate appa-runtime releases, each with its own service URL.

Manage policy with appa-guide#

Use appa-guide in chat to inspect and modify policies conversationally:

  • init: Scans tools and generates a starting policy.
  • show policy (or explain policy): Explains active security rules, protected tools, and included batteries in plain English.
  • adjust <rule>: Modifies specific tool contracts, trust levels, or audience boundaries.
  • refresh batteries: Updates included battery definitions (requires persistence).
  • diagnose the OpenAPPA integration: Runs read-only health checks on connectivity and configuration.

The guide publishes and reloads the approved policy through the runtime's management tools. New chats use the reloaded policy; existing chats keep the policy they started with. The guide confirms once the policy is published and reloaded.

Troubleshooting#

  • Tools do not appear: Inspect your tool server: kubectl get remotemcpserver -n kagent -o yaml. Ensure status.discoveredTools is populated before running init.
  • Agent pod fails to start: Inspect the pod logs: kubectl logs -n kagent -l kagent=appa-guide.

Uninstall#

Remove only the demo release#

Removes demo releases and mock tools:

helm uninstall appa-kagent-demo -n kagent --ignore-not-found
helm uninstall appa-runtime -n kagent --ignore-not-found
echo ""

Restore stock images and remove OpenAPPA#

Restores the controller to stock images and uninstalls appa-runtime:

kubectl delete agent appa-guide -n kagent --ignore-not-found --wait
helm upgrade kagent oci://ghcr.io/kagent-dev/kagent/helm/kagent \
  --version 0.9.12 -n kagent --reuse-values \
  --set registry=ghcr.io \
  --set controller.agentImage.registry=ghcr.io \
  --set controller.agentImage.repository=kagent-dev/kagent/app \
  --set-string controller.agentImage.tag=0.9.12 \
  --force-conflicts --wait --timeout 10m
kubectl get deployment -A -l kagent \
  -o 'custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name,IMAGES:.spec.template.spec.containers[*].image'
# After verifying every affected Agent uses ghcr.io/kagent-dev/kagent/app:0.9.12:
helm uninstall appa-runtime -n appa --ignore-not-found
echo ""

Remove kagent and its CRDs#

helm uninstall kagent -n kagent --ignore-not-found
helm uninstall kagent-crds -n kagent --ignore-not-found
echo ""