From 6b2d14bd86f8b543771a7a06962e783a78d97be8 Mon Sep 17 00:00:00 2001 From: piercypixel Date: Fri, 28 Aug 2026 22:47:02 +0400 Subject: [PATCH 1/9] docs: add kagent integration proposal --- website/app/globals.css | 391 ++++++++++++++++++ website/components/DocContent.tsx | 4 + .../figures/KagentEnforcementFigure.tsx | 168 ++++++++ website/content/docs/kagent.md | 188 ++++++++- 4 files changed, 748 insertions(+), 3 deletions(-) create mode 100644 website/components/figures/KagentEnforcementFigure.tsx diff --git a/website/app/globals.css b/website/app/globals.css index f835dd99..1a610193 100644 --- a/website/app/globals.css +++ b/website/app/globals.css @@ -837,6 +837,397 @@ samp { height: auto; } +/* Static integration diagrams use the documentation palette. */ +.kagent-deployment-figure, +.kagent-profile-figure { + margin: 1.5rem 0 2rem; + padding: 1rem; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg-weak); + font-family: var(--font-mono); +} + +.kagent-deployment-flow { + display: grid; + grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) auto minmax(0, 0.8fr) auto minmax(0, 1fr); + align-items: stretch; + gap: 0.4rem; + padding-top: 1rem; +} + +.kagent-deployment-phase { + display: flex; + min-width: 0; + flex-direction: column; + gap: 0.5rem; + padding: 0.65rem; + border: 1px solid var(--border); + border-radius: 5px; + background: var(--bg); +} + +.kagent-deployment-phase-kagent { + border-color: var(--accent-border); + background: var(--accent-bg); +} + +.kagent-deployment-phase > span { + color: var(--text); + font-size: 11px; +} + +.kagent-deployment-phase > strong { + color: var(--text-strong); + font-size: 13px; + line-height: 1.35; +} + +.kagent-deployment-phase > div { + display: flex; + flex-wrap: wrap; + gap: 0.3rem; + margin-top: auto; +} + +.kagent-deployment-phase small { + padding: 0.15rem 0.3rem; + border: 1px solid var(--border-weak); + border-radius: 3px; + color: var(--text); + font-size: 10px; + line-height: 1.3; +} + +.kagent-deployment-arrow { + align-self: center; + color: var(--icon); +} + +.kagent-profile-actor { + margin-top: 1rem; + padding: 0.8rem; + border: 1px solid var(--border); + border-radius: 5px; + background: var(--bg); +} + +.kagent-profile-actor-title { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 1rem; + padding-bottom: 0.65rem; + border-bottom: 1px solid var(--border-weak); +} + +.kagent-profile-actor-title span { + color: var(--text); + font-size: 12px; +} + +.kagent-profile-actor-title strong { + color: var(--text-strong); + font-size: 12px; +} + +.kagent-profile-process-flow { + display: grid; + grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); + align-items: stretch; + gap: 0.75rem; + padding: 1rem 0; +} + +.kagent-profile-supervisor { + display: flex; + align-items: baseline; + gap: 0.65rem; + margin-top: 0.75rem; + padding: 0.55rem 0.65rem; + border: 1px solid var(--border-weak); + border-radius: 4px; + background: var(--bg-weak); +} + +.kagent-profile-supervisor span, +.kagent-profile-supervisor small { + color: var(--text); + font-size: 11px; +} + +.kagent-profile-supervisor strong { + color: var(--text-strong); + font-size: 12px; +} + +.kagent-profile-supervisor small { + margin-left: auto; +} + +.kagent-profile-process { + display: flex; + min-height: 7rem; + flex-direction: column; + justify-content: center; + gap: 0.4rem; + padding: 0.8rem; + border: 1px solid var(--border); + border-radius: 5px; + background: var(--bg-weak); +} + +.kagent-profile-process-appa { + border-color: var(--accent-border); + background: var(--accent-bg); +} + +.kagent-profile-process span, +.kagent-profile-process small { + color: var(--text); + font-size: 12px; + line-height: 1.4; +} + +.kagent-profile-process strong { + color: var(--text-strong); + font-size: 14px; +} + +.kagent-profile-exchange { + display: grid; + align-content: center; + gap: 0.5rem; + color: var(--accent); + font-size: 11px; + white-space: nowrap; +} + +.kagent-profile-request::after { + content: " →"; +} + +.kagent-profile-response::before { + content: "← "; +} + +.kagent-profile-runtime-meta { + display: flex; + justify-content: space-between; + gap: 1rem; + padding-top: 0.65rem; + border-top: 1px solid var(--border-weak); + color: var(--text); + font-size: 11px; +} + +.kagent-figure { + margin: 1.5rem 0 2rem; + padding: 1rem; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg-weak); + font-family: var(--font-mono); +} + +.kagent-figure-title { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding-bottom: 0.75rem; + border-bottom: 1px solid var(--border-weak); + color: var(--text-strong); + font-size: 12px; +} + +.kagent-figure-title span:last-child { + color: var(--text); +} + +.kagent-figure-title span:first-child { + flex-shrink: 0; +} + +.kagent-gates { + display: grid; + grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); + align-items: stretch; + gap: 0.8rem; + padding: 1rem 0; +} + +.kagent-gate { + display: flex; + min-height: 8rem; + flex-direction: column; + justify-content: center; + gap: 0.45rem; + padding: 0.8rem; + border: 1px solid var(--accent-border); + border-radius: 5px; + background: var(--accent-bg); +} + +.kagent-gate > span, +.kagent-gate > small { + color: var(--text); + font-size: 11px; + line-height: 1.4; +} + +.kagent-gate > strong { + color: var(--text-strong); + font-size: 13px; + line-height: 1.35; +} + +.kagent-execution { + display: flex; + align-items: center; + align-self: center; + gap: 0.35rem; + color: var(--text); + font-size: 11px; + white-space: nowrap; +} + +.kagent-execution strong { + color: var(--text-strong); + font-size: 13px; +} + +.kagent-execution span:first-child::after { + content: " →"; +} + +.kagent-execution span:last-child::before { + content: "→ "; +} + +.kagent-runtime-band { + display: flex; + align-items: center; + justify-content: center; + gap: 0.75rem; + padding: 0.8rem 1rem; + border: 1px solid var(--accent-border); + border-radius: 5px; + background: var(--bg); + color: var(--text); + font-size: 12px; +} + +.kagent-runtime-band strong { + color: var(--accent); +} + +.kagent-decision-strip { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 0.75rem; + margin-top: 0.75rem; + padding-top: 0.75rem; + border-top: 1px solid var(--border-weak); + color: var(--text); + font-size: 11px; + text-align: center; +} + +.kagent-decision-strip strong { + color: var(--text-strong); +} + +@media (max-width: 767px) { + .kagent-figure-title { + align-items: flex-start; + flex-direction: column; + gap: 0.2rem; + } + + .kagent-gates { + grid-template-columns: 1fr; + gap: 0.6rem; + } + + .kagent-gate { + min-height: 0; + } + + .kagent-execution { + flex-direction: column; + gap: 0.2rem; + } + + .kagent-execution span:first-child::after { + content: " ↓"; + } + + .kagent-execution span:last-child::before { + content: "↓ "; + } + + .kagent-decision-strip { + grid-template-columns: 1fr; + gap: 0.35rem; + text-align: left; + } + + .kagent-runtime-band { + align-items: stretch; + flex-direction: column; + text-align: center; + } + + .kagent-runtime-band > span[aria-hidden="true"] { + transform: rotate(90deg); + } + + .kagent-profile-actor-title, + .kagent-profile-runtime-meta, + .kagent-profile-supervisor { + align-items: flex-start; + flex-direction: column; + gap: 0.25rem; + } + + .kagent-profile-supervisor small { + margin-left: 0; + } + + .kagent-deployment-flow { + grid-template-columns: 1fr; + gap: 0.6rem; + } + + .kagent-deployment-arrow { + justify-self: center; + transform: rotate(90deg); + } + + .kagent-profile-process-flow { + grid-template-columns: 1fr; + gap: 0.75rem; + } + + .kagent-profile-process { + min-height: 0; + } + + .kagent-profile-exchange { + justify-items: center; + } + + .kagent-profile-request::after { + content: " ↓"; + } + + .kagent-profile-response::before { + content: "↑ "; + } + +} + .prose blockquote { border-left: 2px solid var(--border); padding-left: 1rem; diff --git a/website/components/DocContent.tsx b/website/components/DocContent.tsx index 7e33d84b..7aeb97f8 100644 --- a/website/components/DocContent.tsx +++ b/website/components/DocContent.tsx @@ -20,6 +20,7 @@ import { ConnectedAgentFigure } from "@/components/figures/ConnectedAgentFigure" import { ExfiltrationFigure } from "@/components/figures/ExfiltrationFigure"; import { GuardrailFigure } from "@/components/figures/GuardrailFigure"; import { LabelFoldFigure } from "@/components/figures/LabelFoldFigure"; +import { KagentDeploymentFigure, KagentEnforcementFigure, KagentProfileFigure } from "@/components/figures/KagentEnforcementFigure"; import { NegotiationFigure } from "@/components/figures/NegotiationFigure"; import { PolicyStackFigure } from "@/components/figures/PolicyStackFigure"; import { RemedyPlanFigure } from "@/components/figures/RemedyPlanFigure"; @@ -44,6 +45,9 @@ const DIRECTIVES: Record ReactNode> = { "fig-exfiltration": () => , "fig-guardrail": () => , "fig-label-fold": () => , + "kagent-enforcement": () => , + "kagent-deployment": () => , + "kagent-profile": () => , "fig-negotiation": () => , "fig-policy-stack": () => , "fig-remedy-plan": () => , diff --git a/website/components/figures/KagentEnforcementFigure.tsx b/website/components/figures/KagentEnforcementFigure.tsx new file mode 100644 index 00000000..6dce9531 --- /dev/null +++ b/website/components/figures/KagentEnforcementFigure.tsx @@ -0,0 +1,168 @@ +export function KagentDeploymentFigure() { + return ( +
+
+ The operator installs resources. kagent prepares a revision and ActorTemplate. The user creates an AgentInstance. Substrate creates the Actor. +
+ +
+ deployment flow +
+ +
+
+ 01 · cluster operator + install resources +
+ CRD bundle + patched control plane + Harness + policy ConfigMap + AgentTemplate +
+
+ + + +
+ 02 · kagent controller + prepare immutable runtime +
+ prepared revision + ActorTemplate + image digest + policy digest +
+
+ + + +
+ 03 · application team + create AgentInstance +
+ select AgentTemplate + select Harness +
+
+ + + +
+ 04 · Substrate + create the Actor +
+ pull Actor image + start supervisor + mount durable /data + expose A2A endpoint +
+
+
+
+ ); +} + +export function KagentProfileFigure() { + return ( +
+
+ The kagent Go runtime sends tool calls and results to appa-runtime. appa-runtime returns policy decisions over local HTTP. +
+ +
+ component architecture + inside one Substrate Actor +
+ +
+
+ one container + digest-pinned kagent-openappa Actor image +
+ +
+ PID 1 + kagent-openappa-supervisor + starts, monitors, signals, and stops both child processes +
+ +
+
+ kagent-go-adk process + Google ADK + OpenAPPA extension implements callbacks and plugin +
+ + + +
+ appa-runtime process + appa-adapter-kagent + Engine + apply policy and append facts to /data/openappa/appa.db +
+
+ +
+ local HTTP · 127.0.0.1:8787/hook + durable state · /data/openappa/appa.db +
+
+
+ ); +} + +export function KagentEnforcementFigure() { + return ( +
+
+ Google ADK calls OpenAPPA before tool dispatch and after the terminal tool outcome. OpenAPPA can block the call or replace its result. +
+ +
+ Google ADK extension points + kagent bridge ↔ OpenAPPA +
+ +
+
+ 01 · before dispatch + BeforeToolCallbacks + kagent sends HookEvent::ToolCall + OpenAPPA returns AllowCall or DenyCall +
+ +
+ AllowCall + tool.Run + outcome +
+ +
+ 02 · after execution + AfterToolCallbacks + OnToolErrorCallbacks capture failure first + kagent sends HookEvent::ToolResult +
+
+ +
+ kagent OpenAPPA extension + + HTTP /hook + + appa-adapter-kagent + Engine +
+ +
+ DenyCall blocks dispatch + Ack keeps the result + ReplaceOutput substitutes before model delivery + Block withholds the result +
+
+ ); +} diff --git a/website/content/docs/kagent.md b/website/content/docs/kagent.md index 2ac782c1..d44b829c 100644 --- a/website/content/docs/kagent.md +++ b/website/content/docs/kagent.md @@ -1,6 +1,188 @@ --- -title: kAgent +title: kagent integration category: Integration -order: 6 -description: Putting OpenAPPA between kAgent-managed agents and their tools. +order: 8 +description: Proposal for enforcing OpenAPPA policy in the kagent Go runtime. --- + +:::proposal +name: kagent integration +date: 2026-08-28 +author: Mark Novikov + +This proposal adds an OpenAPPA execution profile to the kagent Go runtime. + +The profile authorizes the exact arguments that kagent passes to a tool. It also controls the result before any consumer receives it. + +The profile refuses a path when kagent cannot observe both boundaries. + +This proposal uses [kagent commit `9e246fd37`](https://github.com/kagent-dev/kagent/commit/9e246fd3797457b18fc277680be1629a0f57fce0) as its source baseline. + +OpenAPPA policy semantics remain in [How it works](/how-it-works) and [Policy contracts](/contracts). This proposal covers only the kagent integration. + +## Gate ordinary tool calls twice + +kagent uses Google ADK to run model, tool, and child-agent steps. An ordinary ADK function tool uses two existing boundaries: + +1. `BeforeToolCallbacks` send `HookEvent::ToolCall` before tool dispatch. +2. `AfterToolCallbacks` send one terminal `HookEvent::ToolResult` after execution or error handling. + +:::kagent-enforcement::: + +The Go extension sends both events to the local `appa-runtime` process. It applies the returned decision before kagent continues. + +OpenAPPA can block dispatch, keep the returned result, replace it, or withhold it. + +## Install the integration + +The OpenAPPA integration maintainers build and publish these artifacts from pinned source commits: + +- A generated kagent resource-definition bundle. +- A patched kagent control-plane image. +- A digest-pinned Actor image that contains kagent and OpenAPPA. +- An OpenAPPA `Harness` manifest. + +The cluster operator installs those artifacts and supplies the policy `ConfigMap`. + +## Select the OpenAPPA Harness + +A `Harness` is a kagent Kubernetes custom resource. It selects the runtime image, eligible workers, snapshot rules, and allowed `AgentTemplate` resources. + +An `AgentTemplate` defines one agent. An `AgentInstance` runs one prepared `AgentTemplate` and `Harness` pair as a Substrate `Actor`. + +Substrate is the kagent workload backend. kagent prepares an immutable revision, then Substrate uses its `ActorTemplate` to create the durable `Actor`. + +```yaml +# Proposed fields only. This excerpt omits existing required Harness fields. +apiVersion: kagent.dev/v1alpha3 +kind: Harness +metadata: + name: kagent-openappa +spec: + kagent: + openappa: + policyRef: + name: customer-support-policy + workload: + image: ghcr.io/archestra-ai/kagent-openappa@sha256: +``` + +`policyRef` names a `ConfigMap` in the same Kubernetes namespace. A policy change creates a new prepared revision. + +After kagent prepares the pair, an application team creates an `AgentInstance` with the OpenAPPA `Harness`. + +:::kagent-deployment::: + +## Run OpenAPPA inside the Actor + +The Actor image contains three binaries: + +```text +/usr/local/bin/kagent-openappa-supervisor + |-- /usr/local/bin/appa-runtime --adapter kagent + `-- /usr/local/bin/kagent-go-adk +``` + +The supervisor runs as PID 1. It starts `appa-runtime`, waits for `/health`, then starts `kagent-go-adk`. + +The supervisor forwards termination signals and stops the other child when either child exits. + +It then exits with failure so Substrate applies the configured `Actor` lifecycle policy. + +:::kagent-profile::: + +The Go extension implements existing ADK callback and plugin interfaces inside `kagent-go-adk`. + +Each callback sends kagent `JSON` to `http://127.0.0.1:8787/hook`. `appa-adapter-kagent` translates the request into `HookEvent` and `HookDecision` values. + +kagent declares `/data` as durable storage in the `ActorTemplate`. Substrate mounts it when it creates the `Actor`. + +OpenAPPA stores its database at `/data/openappa/appa.db`. + +## Map every execution path + +Most paths use existing Google ADK callbacks. The table marks proposed kagent callbacks explicitly. + +| Execution path | Dispatch boundary | Result boundary | +|---|---|---| +| Normal function, MCP, skill, or model-called memory tool | `BeforeToolCallbacks` → `HookEvent::ToolCall` | `OnToolErrorCallbacks`, then `AfterToolCallbacks` → `HookEvent::ToolResult` | +| Local ADK `chat` transfer | `BeforeToolCallbacks` on `transfer_to_agent` → `HookEvent::ToolCall` | No bounded child result. `BeforeAgentCallbacks` keep the same trajectory | +| Local ADK `single_turn` | `BeforeToolCallbacks` → `ToolCall`. `BeforeAgentCallbacks` → `ChildStart` | `AfterToolCallbacks` → `SpawnResult` | +| Local ADK `task` | `AfterModelCallbacks` → `ToolCall`. `BeforeAgentCallbacks` → `ChildStart` | `plugin.OnEventCallback` → `SpawnResult` | +| Remote A2A agent | `BeforeToolCallbacks` → `ToolCall` and `ChildStart` | `AfterToolCallbacks` → `SpawnResult` | +| Automatic `preload_memory` | Context-bound `memory.Service` decorator before `Search` | The same decorator before model delivery | +| MCP App internal tool or resource | Proposed owning-Actor gate → `ToolCall` | The same proposed gate → `ToolResult` | +| Registered long-running work | Proposed `BeforeBackgroundStart` → `ToolCall` and `ChildStart` | Proposed `OnBackgroundResult` → `SpawnResult` | + +`BeforeModelCallbacks` confirm that each model-bound result already passed admission. `plugin.OnUserMessageCallback` rejects forged task responses. + +Trusted deployment configuration must bind the authenticated remote endpoint to an isolated OpenAPPA `Harness`. + +A direct message or `completed` task can return content. `input_required` and `auth_required` pause the remote call. + +`failed`, `canceled`, and `rejected` produce a failed `SpawnResult` without content. `submitted` and `working` continue waiting or fail without a parent result. + +## Check first-release limits + +The first implementation refuses these paths: + +- Streaming tool chunks and provider-native tools. +- Unregistered background work, asynchronous MCP jobs, and notification-only results. +- Remote or BYO agents without a compatible OpenAPPA adapter. +- Python ADK, OpenAI Agents, LangGraph, and CrewAI without framework adapters. + +The implementation plan contains the complete supported and refused path catalog. + +## Fail closed + +kagent prepares no revision until coverage validation passes. The `Actor` accepts no work until `appa-runtime` reports ready. + +A runtime failure before dispatch blocks the call. A runtime failure after execution withholds the result. + +The profile rejects kagent `RequireApproval` configuration. + +The runtime currently registers `MakeApprovalCallback` first in `BeforeToolCallbacks`, so it can return before the OpenAPPA callback. + +The profile also rejects any added callback or plugin that can bypass an OpenAPPA boundary. + +Cancellation reports `ToolOutcome::Indeterminate` before the profile releases the trajectory permit. + +After Actor recovery, the profile sends `TurnEnd` before a new call to close an unknown outstanding dispatch. + +One trajectory runs one tool call at a time. Separate child trajectories can run concurrently. + +## Adopt side by side + +An existing `AgentInstance` cannot switch to another `Harness` or prepared revision. Existing clusters adopt OpenAPPA through a side-by-side replacement. + +| Phase | Operator action | Routing state | +|---|---|---| +| Upgrade | Install the resource definitions and patched control plane. Wait for controllers and webhooks | Existing instances continue on current revisions | +| Prepare | Apply the policy `ConfigMap` and OpenAPPA `Harness`. Wait for a ready revision | Existing instances continue serving | +| Canary | Create a protected `AgentInstance` and run internal checks | Production traffic remains on the old instance | +| Cut over | Route new root A2A tasks to protected instance | Pin old task and context IDs to old instance | +| Drain | Wait for terminal work or apply the timeout and cancellation policy | No new work reaches the old instance | +| Retain | Suspend old instance, export records, and verify `/data` snapshot | Keep both for at least 30 days | + +The protected instance starts a new OpenAPPA database and new root trajectories. The first release does not import an old unprotected transcript. + +When the application uses an external ADK session store, migration starts a new protected session ID. + +Migration excludes the old model context. + +External records re-enter through protected tools. Their tool contracts assign the required policy Labels. + +Rollback routes new root tasks to the unchanged old instance. Existing protected task and context IDs stay protected until terminal or canceled. + +After rollback drain, the operator suspends the protected instance and exports its task and event records. + +The operator verifies that the Substrate snapshot contains `/data`, then retains the export and snapshot for at least 30 days. + +Deletion requires a separate operator decision after the retention period. Rollback never merges or rewrites OpenAPPA trajectory logs. + +## Implementation plan + +The [kagent implementation plan](https://github.com/archestra-ai/OpenAPPA/blob/main/integrations/kagent/IMPLEMENTATION.md) contains exact APIs, source ownership, and callback ordering. + +It also contains coverage tests and the upstream contribution sequence. +::: From 6972f6f9e3e13f6d49de8ced305c5e67a8d76527 Mon Sep 17 00:00:00 2001 From: piercypixel Date: Fri, 28 Aug 2026 22:47:14 +0400 Subject: [PATCH 2/9] docs: add kagent implementation plan --- integrations/kagent/IMPLEMENTATION.md | 565 ++++++++++++++++++++++++++ 1 file changed, 565 insertions(+) create mode 100644 integrations/kagent/IMPLEMENTATION.md diff --git a/integrations/kagent/IMPLEMENTATION.md b/integrations/kagent/IMPLEMENTATION.md new file mode 100644 index 00000000..f8340948 --- /dev/null +++ b/integrations/kagent/IMPLEMENTATION.md @@ -0,0 +1,565 @@ +# kagent integration implementation plan + +Status: proposed + +Source baselines: + +- kagent commit `9e246fd3797457b18fc277680be1629a0f57fce0` +- Google ADK Go tag `v2.2.0` +- OpenAPPA runtime API in `appa-runtime-api/src/lib.rs` + +The reader-facing proposal is available at [openappa.com/kagent](https://www.openappa.com/kagent). + +## Invariants + +The implementation MUST preserve these properties: + +1. OpenAPPA authorizes the final JSON-semantic argument value that kagent passes to `tool.Run`, not provider wire bytes. +2. kagent publishes no returned content before OpenAPPA admission. +3. Each root, child, call, and pending outcome has a stable identity. +4. Uncovered execution paths fail before they handle protected content. +5. Existing Harnesses keep current behavior when OpenAPPA is absent. + +## Change map + +| Owner | Change | +|---|---| +| `go/api/v1alpha3` | Add the optional OpenAPPA Harness profile and local collaboration mode | +| `go/core/v2/translator/kagent` | Resolve policy, compile profile data, and validate supported paths | +| `go/core/v2/substrate` | Include compiled OpenAPPA data in the immutable revision | +| `go/adk/pkg/config` | Materialize the policy bundle and expose process configuration | +| `go/adk/pkg/agent` | Inject callbacks and snapshot final ADK arguments | +| `go/adk/pkg/runner` | Add the OpenAPPA plugin and memory-service decorator | +| `go/adk/pkg/tools` | Preserve remote A2A task state and child identity | +| OpenAPPA | Add `appa-adapter-kagent` and the `kagent` runtime adapter option | + +## Release artifacts + +The integration release publishes these artifacts: + +| Artifact | Producer | Consumer | +|---|---|---| +| Generated CRD and Helm bundle | Integration release CI from the kagent fork | Cluster operator upgrade | +| Patched kagent control-plane image | Integration release CI from the kagent fork | Cluster operator deployment | +| OpenAPPA Actor runtime image | Integration release CI | OpenAPPA `Harness` workload | +| OpenAPPA `Harness` manifest | Integration release | Kubernetes API | +| Example policy `ConfigMap` | OpenAPPA repository | Policy author and cluster operator | + +The Actor image contains the patched kagent Go runtime and the `appa-runtime` binary from the same reviewed release. + +The `Harness` manifest MUST pin the Actor image by SHA-256 digest. + +Release notes MUST record the CRD bundle version, both image digests, and source commits. + +The cluster operator installs the patched control plane before applying the `Harness`. Existing unpatched control planes reject the new `openappa` field. + +## Harness profile + +Extend the existing empty `KagentHarness` type: + +```go +type KagentHarness struct { + OpenAPPA *OpenAPPAHarnessProfile `json:"openappa,omitempty"` +} + +type OpenAPPAHarnessProfile struct { + PolicyRef corev1.LocalObjectReference `json:"policyRef"` +} +``` + +The Harness compiler performs these operations: + +1. Resolve the same-namespace policy ConfigMap. +2. Require one `appa.toml` key and reject unsafe relative file names. +3. Validate the OpenAPPA configuration during revision preparation. +4. Add ConfigMap identity and content hash to revision provenance. +5. Add the policy bundle and digest to the compiled `adk.AgentConfig`. + +The revision digest MUST cover the complete policy bundle. A policy change creates a new prepared revision. + +Parse every configured external authority, sanitizer, cast, resolver, membership, and LLM endpoint after policy validation. + +Add each HTTPS destination to `Revision.EgressDestinations`. The destination set and policy bytes MUST affect the revision digest. + +The profile requires an explicit `[externals.llm].url` when policy can invoke an external LLM. It does not use an implicit provider endpoint. + +## Local process model + +kagent generates a one-container `ActorTemplate` with `DurableDirs: [{Path: "/data"}]`. + +Substrate provisions and mounts that durable directory when it creates the `Actor`. The integration does not create a Kubernetes PVC directly. + +The `Harness` worker pool and snapshot policy control storage placement and Actor checkpoint behavior. + +The OpenAPPA profile uses two runtime processes in that container. + +```text +/usr/local/bin/kagent-openappa-supervisor + |-- /usr/local/bin/appa-runtime --adapter kagent + `-- /usr/local/bin/kagent-go-adk + +/data/openappa/ + |-- policy//appa.toml + `-- appa.db +``` + +Extend compiled `adk.AgentConfig` with an internal OpenAPPA bundle: + +```go +type OpenAPPACompiledConfig struct { + Files map[string]string `json:"files"` + Digest string `json:"digest"` +} +``` + +Add `go/adk/cmd/openappa-supervisor/main.go`. Build it as the Actor image entrypoint and PID 1. + +The Actor image build copies three reviewed binaries into `/usr/local/bin`: the supervisor, patched kagent Go runtime, and `appa-runtime`. + +The supervisor uses `os/exec` to start both children in one process group. It forwards `SIGTERM` and `SIGINT`, reaps children, and enforces shutdown timeouts. + +The runtime materializer writes each file atomically under `/data/openappa/policy/`. It verifies the digest before startup. + +The supervisor starts `appa-runtime` first and waits for `/health`. It starts `kagent-go-adk` only after runtime health succeeds. + +The Go `/readyz` endpoint MUST fail when the local runtime is unavailable. + +The supervisor exits with failure when either child process exits. Substrate then applies its configured `Actor` lifecycle policy. + +Use these process settings: + +```text +APPA_RUNTIME_URL=http://127.0.0.1:8787 +APPA_CONFIG=/data/openappa/policy//appa.toml +APPA_DB=/data/openappa/appa.db +``` + +## OpenAPPA adapter + +Add a pure `appa-adapter-kagent` crate beside `appa-adapter-claude-code`. + +The crate depends only on `appa-runtime-api`. It parses kagent wire JSON into `HookEvent` and renders `HookDecision` into kagent wire JSON. + +Add `Kagent` to the runtime `Adapter` enum and select the new codec for `--adapter kagent`. + +The adapter MUST hold no state, call no policy API, and perform no external I/O. + +## Go ADK extension + +The Go extension implements existing ADK callbacks and one runner plugin. It owns transport and correlation but contains no policy logic. + +```go +type OpenAPPAExtensions struct { + Client *HookClient + AgentCallbacks AgentCallbackSet + Plugin *plugin.Plugin + MemoryService memory.Service + SupportedToolsets ToolsetCatalog +} +``` + +Final names follow kagent conventions. These responsibilities do not change. + +### Builder compatibility + +Keep current exported builder signatures: + +```text +CreateRunnerConfig(...) + -> createRunnerConfig(..., extensions = nil) + +CreateGoogleADKAgent(...) + -> createGoogleADKAgent(..., extensions = nil) +``` + +Add extension-aware entry points for the OpenAPPA Harness: + +```text +CreateRunnerConfigWithExtensions(..., extensions) + -> createRunnerConfig(..., extensions) + -> CreateGoogleADKAgentWithExtensions(..., extensions) + -> createGoogleADKAgent(..., extensions) + -> recurse with the same extensions +``` + +### Callback order + +```text +BeforeTool OpenAPPA argument snapshot and decision -> tool +OnToolError capture failure -> safe error -> mark outcome pending +AfterTool if released, report one success or failure -> publication +BeforeModel OpenAPPA admission assertion -> model +AfterModel OpenAPPA task dispatch gate -> native task dispatch +OnEvent OpenAPPA task return gate -> persistence -> parent model +``` + +The profile rejects kagent `RequireApproval` and any non-OpenAPPA callback or plugin that can short-circuit `BeforeTool`. + +Use an OpenAPPA authority and remedy plan when a policy requires human approval. + +The OpenAPPA callback MUST finish before kagent constructs or publishes tool-result content. + +`OnToolError` MUST NOT send `ToolResult`. It stores failure state in the invocation context. + +Track one gate state by function-call ID: + +| State | Meaning | `AfterTool` behavior | +|---|---|---| +| `Released` | OpenAPPA returned `AllowCall` | Report exactly one terminal `ToolResult` | +| `Denied` | OpenAPPA denied the call | Send no `ToolResult`. Preserve the rendered denial | +| `SnapshotFailed` | Final arguments could not serialize | Send no `ToolResult`. Preserve the fail-closed response | +| `DeferredTask` | ADK emitted the task-mode no-op callbacks | Send no `ToolResult`. The task plugin owns both gates | +| Missing | Another callback bypassed OpenAPPA | Replace all output with a fail-closed refusal | + +For `Released`, `AfterTool` reads failure state and sends exactly one outcome. Tests MUST assert one outcome for each release. + +## Authoritative argument snapshot + +ADK passes one `map[string]any` value through `BeforeToolCallbacks` and then into `tool.Run`. + +Register the OpenAPPA callback last in the `BeforeTool` chain. No later callback can change the arguments after authorization. + +Callbacks MUST NOT retain or mutate `args` after they return. The pending-call store MUST synchronize access across concurrent ADK tool workers. + +Coverage validation MUST reject every other runner or agent `BeforeToolCallback` under this profile. + +Serialize that final ADK map once: + +```go +type PendingToolCall struct { + ID string + Tool string + Arguments json.RawMessage +} +``` + +Send those bytes to OpenAPPA as `RawValue`. Here, raw means unparsed at the adapter boundary, not original provider wire bytes. + +The tool receives the same unchanged ADK map after `AllowCall`. A serialization error refuses the call. + +If kagent changed that map after `AllowCall`, the tool would execute arguments that OpenAPPA never authorized. + +Duplicate object keys no longer exist at this boundary, and the tool cannot receive them. OpenAPPA validates the JSON value that the tool receives. + +Keep the serialized snapshot in memory until `AfterTool` reports the terminal outcome. kagent does not write this record to disk. + +`HookEvent::ToolResult` has no independent call ID. It carries the proposed call, so kagent needs the snapshot to identify which released call produced the outcome. + +Send the stored bytes with `ToolResult`. `appa-runtime` compares their canonical JSON arguments with the released call. + +Test final callback ordering, later mutation attempts, unsupported JSON values, numeric values, serialization failure, and outcome correlation. + +`appa-runtime` persists the released call. On Actor recovery, send `TurnEnd` before any new call to close an unknown outcome. + +## Remedy control tool + +Register the local `appa-runtime` MCP endpoint with the root agent and every protected local child. + +The reserved `execute_remedy_plan` tool follows this flow: + +```text +model proposes execute_remedy_plan(offer_id) + -> BeforeTool identifies the exact reserved tool + -> appa-runtime vouches the acting trajectory + -> PassControl permits the MCP call + -> MCP endpoint consumes the vouch and judges the live offer +``` + +An offer from another trajectory MUST fail. An offer ID alone grants no authority. + +`Authorized` and `Substituted` outcomes return the exact tool name and arguments to propose next. + +The control tool MUST NOT execute that call itself. The next model-proposed call crosses normal enforcement with the returned JSON-semantic arguments. + +`Returned`, `Declined`, and `NoAnswer` remain normal control-tool results. + +## Dispatch serialization + +The current OpenAPPA host contract permits one open dispatch per trajectory. + +Wait for a trajectory permit in the pre-dispatch callback. Do not send a second `ToolCall` while the first dispatch remains open. + +Release the permit after `AfterTool` or terminal child return admission. + +Cancellation MUST report an indeterminate outcome before permit release. Separate child trajectories can run in parallel. + +## Local collaboration modes + +Add an optional mode to `AgentToolBinding`: + +```go +type AgentToolBinding struct { + Name string `json:"name"` + Description string `json:"description"` + TemplateRef AgentTemplateLocalReference `json:"templateRef"` + Isolation AgentToolIsolation `json:"isolation,omitempty"` + Mode AgentCollaborationMode `json:"mode,omitempty"` +} +``` + +Supported values are `chat`, `single_turn`, and `task`. An omitted value preserves the current `chat` behavior. + +The compiler carries the mode into the Go ADK child configuration and validates the required OpenAPPA capability. + +`BeforeAgentCallbacks` select the trajectory context for the activated local agent. `AfterAgentCallbacks` clear that activation context. + +These callbacks do not report a child return. `AfterTool` or the task `OnEvent` gate owns the parent-facing result. + +The active trajectory context also selects the correct memory-service decorator. + +### Chat + +The transfer tool runs through normal tool callbacks. The selected local agent continues on the parent trajectory and Label. + +There is no separate child-return boundary. Every later tool call remains protected on the same actor. + +### Single turn + +The generated single-turn tool runs through normal `BeforeTool`, `AfterTool`, and error callbacks. + +Classify the call as `spawn: true`, bind the child with `ChildStart`, and report the returned function response through `SpawnResult`. + +Derive a valid stable run ID from `toolCtx.FunctionCallID()`: + +```go +sum := sha256.Sum256([]byte(toolCtx.FunctionCallID())) +runID := "appa-" + hex.EncodeToString(sum[:12]) +``` + +Pass `runID` to `workflow.WithRunID`. Derive the OpenAPPA child ID from the same value. + +Test empty, numeric-only, slash-bearing, at-sign-bearing, and repeated provider call IDs. + +### Task + +ADK task mode emits a no-op function tool callback before native deferred dispatch. OpenAPPA tool callbacks MUST skip configured task targets. + +Use `AfterModelCallbacks` to inspect the model function call before `dispatchTaskFC` runs. Send `ToolCall` with `spawn: true` there. + +Use the child `BeforeAgentCallbacks` to send `ChildStart` before the task child begins execution. + +Use the runner plugin `OnEventCallback` before event persistence. Intercept the synthesized task function response and report `SpawnResult`. + +Use `OnUserMessageCallback` to reject forged task function responses. Suppress task-scope child content until the gated parent return. + +Test pause, resume, cancellation, duplicate callbacks, and process restart. + +## Remote A2A children + +The parent-side remote A2A call is a normal function tool. Existing tool callbacks gate its dispatch and return. + +Require `isolateSessions: true` for a child trajectory. + +Allocate the remote context ID during `BeforeTool`. Store it by function-call ID, then send `ChildStart` before the A2A request. + +`remoteA2AState.handleFirstCall` MUST reuse that prepared context ID instead of creating one inside `tool.Run`. + +Trusted deployment configuration MUST bind the authenticated remote endpoint to an OpenAPPA-capable Harness. + +Change `remote_a2a_tool.processResult` to preserve `TaskState` in the tool result. + +| A2A result | Parent behavior | +|---|---| +| Direct message | Report successful `SpawnResult` with content | +| `completed` | Report successful `SpawnResult` with content | +| `failed`, `canceled`, or `rejected` | Report failed `SpawnResult` without content | +| `input_required` or `auth_required` | Pause without a parent-facing result | +| `submitted` or `working` | Continue waiting or refuse. Do not return content | +| Unknown state | Refuse. Do not return content | + +## Memory + +Model-called `load_memory` uses normal tool callbacks. + +Automatic `preload_memory` runs as a request processor and bypasses tool callbacks. Wrap `memory.Service` with an OpenAPPA decorator. + +Before `Runner.Run`, put the acting trajectory ID in the invocation context. The decorator reads that ID before `Search`. + +The decorator gates search before execution and admits returned memory before `PreloadMemoryTool` adds it to the model request. + +Register background memory save, summarization, and embedding work as child work. Refuse it until the terminal return is observable. + +## MCP and MCP Apps + +HTTP, SSE, and stdio MCP tools become normal ADK function tools after discovery. Validate each dynamically resolved tool at dispatch. + +Under the profile, MCP discovery and MCP App classification failures MUST fail closed. Do not use lazy fallback for unknown App metadata. + +Model-called MCP App tools use normal tool callbacks. Their full result passes post-execution admission before UI publication or model compaction. + +App-internal tools and resources bypass ADK. Route them back through the owning Actor instead of calling MCP directly from the controller. + +Extend the tool and resource request protos with `agent_instance_id` and `trajectory_capability`. + +The Actor mints an opaque random capability after the model-called App result passes admission. It stores the bound trajectory, actor, tool scope, and expiration. + +The App UI returns that capability with each internal request. The controller resolves the AgentInstance and forwards the request to a new Actor gate RPC. + +The Actor validates the capability, runs both OpenAPPA gates, executes the MCP call, and returns only admitted content. + +Update these owners: + +| Owner | Change | +|---|---| +| tools and resources protobufs | Add AgentInstance and capability fields | +| App UI client | Preserve and return the capability | +| `go/core/internal/grpcserver` | Stop direct execution for protected App requests | +| A2A or Actor control service | Add the private App gate RPC | +| Go ADK extension | Mint, store, validate, and expire capabilities | + +Reject missing, expired, mismatched, or unknown capabilities before MCP dispatch. + +## Long-running work + +The profile supports long-running work only after registration as child work. + +Google ADK exposes no callback for work that outlives the initiating callback. Add two optional callbacks to each kagent-owned background launcher and resume path: + +| Proposed callback | Invocation point | +|---|---| +| `BeforeBackgroundStart` | Before kagent starts or resumes work and before any external effect | +| `OnBackgroundResult` | After terminal completion but before result publication or storage | + +These are kagent function parameters, not user configuration and not a second middleware system. + +When the OpenAPPA profile is active, a launcher MUST refuse work if either callback is absent. + +Registration creates a stable child ID before launch. Completion, failure, cancellation, and resume use that same ID. + +The terminal result MUST pass `SpawnResult` before model, parent, memory, UI, or event delivery. + +Refuse streaming chunks, detached work, asynchronous MCP jobs, notifications, provider-native tools, and background memory until they use this lifecycle. + +## Coverage catalog + +### Supported after implementation + +| Path | Required mechanism | +|---|---| +| Go ADK function tool | Final ADK argument snapshot and existing callbacks | +| HTTP, SSE, and stdio MCP tool | Dynamic validation and existing callbacks | +| Skill tool | Existing callbacks | +| Model-called memory tool | Existing callbacks | +| Automatic memory preload | Context-bound memory-service decorator | +| Local `chat` | Transfer gate on the parent trajectory | +| Local `single_turn` | Child binding and normal tool callbacks | +| Local `task` | `AfterModel` dispatch and `OnEvent` return gates | +| Remote A2A parent boundary | Normal callbacks, isolation, and preserved task state | +| Instrumented remote child | Compatible adapter and authenticated endpoint | +| Model-called MCP App tool | Normal callbacks before UI publication | +| `execute_remedy_plan` | Actor-bound vouch and local runtime MCP endpoint | +| MCP App internal call | Host gate and trajectory capability | +| Registered background work | Child identity and terminal return gate | +| Instrumented BYO agent | Compatible adapter and capability declaration | + +### Refused in the first implementation + +| Path | Missing mechanism | +|---|---| +| Streaming tool chunk | Chunk admission protocol | +| Detached or unregistered work | Stable child identity and terminal return | +| Unregistered asynchronous MCP job | Protected completion path | +| Notification-only result | Parent-facing return gate | +| Unregistered background memory | Protected completion path | +| Provider-native tool | Go ADK callbacks | +| Native `RequireApproval` | OpenAPPA authority and remedy integration | +| Additional short-circuit `BeforeTool` callback | Guaranteed OpenAPPA ordering and gate state | +| Uninstrumented remote child | Protected internal execution boundary | +| Uninstrumented Python ADK | Python adapter | +| Uninstrumented OpenAI Agents | Framework tool and handoff adapter | +| Uninstrumented LangGraph | Protected tool nodes and graph validation | +| Uninstrumented CrewAI | Protected tool and delegation layer | +| Uninstrumented BYO agent | Protected internal execution boundary | + +## Upstream sequence + +| Pull request | Generic kagent change | Proof | +|---|---|---| +| 1 | Snapshot final ADK arguments and call identity | Tool receives the value that policy authorized | +| 2 | Add extension-aware Go ADK builders | Existing call sites compile and keep behavior | +| 3 | Add local collaboration mode to AgentTemplate tools | All three modes compile and validate | +| 4 | Add task dispatch and return plugin seams | Deferred task cannot bypass either gate | +| 5 | Preserve remote A2A task state | Only valid terminal outcomes return content | +| 6 | Add result publication barriers | Unadmitted result reaches no consumer before callbacks finish | +| 7 | Add Harness callback and dynamic tool validation | Unknown or short-circuiting paths fail before execution | +| 8 | Add background-work lifecycle | Every registered result has one terminal gate | + +The OpenAPPA Harness profile, policy compiler, Go ADK extension, and Rust adapter can remain integration-specific. + +## Existing-cluster migration + +`AgentInstance` pins one `Harness` and prepared revision. The implementation MUST NOT mutate a running instance into the OpenAPPA profile. + +### Control-plane upgrade + +1. Apply the generated CRD or Helm bundle with optional `KagentHarness.OpenAPPA` and collaboration-mode fields. +2. Deploy the patched kagent control-plane image. +3. Wait for every controller and conversion webhook to report ready. +4. Apply no OpenAPPA `Harness` until the patched control plane is ready. + +Existing Harnesses omit the new field. Their compilation, builders, callbacks, and Actor images remain unchanged. + +### Compatibility preparation + +For each existing `AgentTemplate` selected for adoption: + +1. Apply the policy `ConfigMap` and OpenAPPA `Harness`. +2. Let the compiler inventory tools, providers, child modes, MCP Apps, memory, and background paths. +3. Refuse preparation when any enabled path lacks required coverage. +4. Wait for a ready revision and Substrate `ActorTemplate` before creating an instance. + +The same `AgentTemplate` can serve both the old and OpenAPPA Harnesses during rollout. + +### State boundary + +Create a new `AgentInstance`, Actor, `/data/openappa/appa.db`, and root trajectory family. + +Do not copy an unprotected transcript into the OpenAPPA event log. The first release has no trustworthy facts for activity that happened before enforcement. + +Persistent external records re-enter through policy-covered tools. Their tool contracts assign the resulting Labels. + +If the application uses an external ADK session store, start a new protected session ID. Do not attach an old model context to the new root trajectory. + +### Canary and cutover + +The cluster operator owns routing, drain, retention, rollback, and destructive retirement. + +1. Send synthetic and internal canary requests to the protected `AgentInstance`. +2. Verify runtime health, policy hash, callback coverage, denial, replacement, child return, and database persistence. +3. Route new root A2A tasks to the protected instance. +4. Keep continuations for old task and context IDs pinned to the old instance. +5. Drain until old work reaches a terminal state, or apply the documented timeout and cancellation policy. +6. Suspend the old instance, export its task and event records, and verify a Substrate snapshot containing `/data`. +7. Retain the export and snapshot for at least 30 days before any deletion. + +Destructive deletion requires separate cluster-operator approval after the retention period. + +### Rollback + +Keep the old `AgentInstance` unchanged until cutover verification ends. + +Rollback routes new root tasks to the old instance. Existing protected task and context IDs remain pinned to the protected instance until terminal or canceled. + +After drain, suspend the protected instance, export its task and event records, and verify a Substrate snapshot containing `/data`. + +Retain that export and snapshot for at least 30 days. Rollback does not merge, rewrite, or discard the OpenAPPA event log. + +## Validation matrix + +| Area | Required tests | +|---|---| +| Compatibility | Existing Harnesses, builders, CRDs, callback order, approval, and concurrency stay unchanged without the profile | +| Tool calls | Allow, deny, final argument snapshot, mutation attempt, serialization error, outcome, timeout, cancellation, and replacement | +| Callback bypass | Native approval, runner short-circuit, agent short-circuit, missing gate state, and safe refusal | +| Remedies | Actor binding, stale offer, authorized call, substituted bytes, returned value, decline, and no answer | +| Concurrency | Same-actor serialization, child parallelism, callback panic, and permit cleanup | +| Local agents | Chat transfer, single turn, task, pause, resume, forged response, cancellation, and restart | +| Remote A2A | Isolation, direct message, every task state, capability mismatch, and terminal replacement | +| Memory | Load, preload, save, context loss, background work, and replacement before model delivery | +| MCP Apps | Discovery failure, model tool, app-internal call, resource read, expired capability, and UI publication | +| Runtime | Startup health, process exit, database persistence, revision change, and fail-closed recovery | +| Egress | Every policy endpoint and explicit LLM URL enter the allowlist and revision digest | +| Migration | CRD ordering, dual revisions, task affinity, canary, cutover, drain, fresh session, suspend, export, snapshot, rollback, and retention | +| Browser | Proposal diagrams at desktop, mobile, light, and dark modes | + +The implementation is complete only when each enabled execution path reaches its required pre-dispatch and result boundary. From 29e553c83a259d341374cc0ea9d80beb53319889 Mon Sep 17 00:00:00 2001 From: piercypixel Date: Fri, 28 Aug 2026 22:47:33 +0400 Subject: [PATCH 3/9] docs: clarify tool argument provenance --- appa-adapter-claude-code/src/lib.rs | 2 ++ appa-engine/src/transition.rs | 3 ++- appa-runtime-api/src/lib.rs | 5 +++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/appa-adapter-claude-code/src/lib.rs b/appa-adapter-claude-code/src/lib.rs index c10b6c71..f8aee01d 100644 --- a/appa-adapter-claude-code/src/lib.rs +++ b/appa-adapter-claude-code/src/lib.rs @@ -169,6 +169,8 @@ struct WireEvent { prompt: Option, #[serde(default)] tool_name: Option, + /// The JSON spelling in Claude Code's `tool_input` hook field. This is + /// Claude Code's execution-boundary value, not Anthropic provider wire data. #[serde(default)] tool_input: Option>, #[serde(default)] diff --git a/appa-engine/src/transition.rs b/appa-engine/src/transition.rs index 21349a18..41f1e814 100644 --- a/appa-engine/src/transition.rs +++ b/appa-engine/src/transition.rs @@ -67,7 +67,8 @@ pub struct ProposalBatch { pub expansions: Vec, } -/// One call the model proposed, as it wrote it: a tool name and untrusted argument bytes. +/// One model-directed call as the harness presents it for dispatch: a tool name and +/// untrusted argument bytes representing the value the harness would execute. /// Only the engine turns this into a [`ResolvedCall`], so no caller can present a /// payload under a schema the registry does not hold. #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/appa-runtime-api/src/lib.rs b/appa-runtime-api/src/lib.rs index ce8921ec..bf71b0d0 100644 --- a/appa-runtime-api/src/lib.rs +++ b/appa-runtime-api/src/lib.rs @@ -7,8 +7,9 @@ #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct TrajectoryId(pub String); -/// A tool call as the model proposed it. The engine canonicalizes; -/// the runtime and the adapter pass it through unchanged. +/// A model-directed tool call at the harness's execution boundary. The +/// arguments are the JSON spelling the harness would execute. The engine +/// canonicalizes them; the runtime and adapter do not parse or rewrite them. #[derive(Debug, Clone, serde::Serialize)] pub struct ProposedCall { pub tool: String, From d5c26cb8233dc376819a5fd77a9127d53fd7b462 Mon Sep 17 00:00:00 2001 From: piercypixel Date: Sat, 29 Aug 2026 01:24:11 +0400 Subject: [PATCH 4/9] docs: tighten kagent Harness explanation --- website/content/docs/kagent.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/website/content/docs/kagent.md b/website/content/docs/kagent.md index d44b829c..b7b5c00d 100644 --- a/website/content/docs/kagent.md +++ b/website/content/docs/kagent.md @@ -46,11 +46,7 @@ The cluster operator installs those artifacts and supplies the policy `ConfigMap ## Select the OpenAPPA Harness -A `Harness` is a kagent Kubernetes custom resource. It selects the runtime image, eligible workers, snapshot rules, and allowed `AgentTemplate` resources. - -An `AgentTemplate` defines one agent. An `AgentInstance` runs one prepared `AgentTemplate` and `Harness` pair as a Substrate `Actor`. - -Substrate is the kagent workload backend. kagent prepares an immutable revision, then Substrate uses its `ActorTemplate` to create the durable `Actor`. +A kagent `Harness` is a Kubernetes resource that tells Substrate which workload image, workers, and snapshot rules to use when running an `AgentTemplate`. ```yaml # Proposed fields only. This excerpt omits existing required Harness fields. @@ -67,7 +63,7 @@ spec: image: ghcr.io/archestra-ai/kagent-openappa@sha256: ``` -`policyRef` names a `ConfigMap` in the same Kubernetes namespace. A policy change creates a new prepared revision. +`policyRef` names a `ConfigMap` in the same Kubernetes namespace. After kagent prepares the pair, an application team creates an `AgentInstance` with the OpenAPPA `Harness`. From f609200f93e3d31d9dcda794562ee290521762a1 Mon Sep 17 00:00:00 2001 From: piercypixel Date: Sat, 29 Aug 2026 02:07:29 +0400 Subject: [PATCH 5/9] docs: make kagent installation concrete --- integrations/kagent/IMPLEMENTATION.md | 71 ++++++-- website/app/globals.css | 171 +----------------- website/components/DocContent.tsx | 3 +- .../figures/KagentEnforcementFigure.tsx | 121 ++----------- website/content/docs/kagent.md | 101 +++++++---- 5 files changed, 142 insertions(+), 325 deletions(-) diff --git a/integrations/kagent/IMPLEMENTATION.md b/integrations/kagent/IMPLEMENTATION.md index f8340948..21188aa5 100644 --- a/integrations/kagent/IMPLEMENTATION.md +++ b/integrations/kagent/IMPLEMENTATION.md @@ -39,19 +39,67 @@ The integration release publishes these artifacts: | Artifact | Producer | Consumer | |---|---|---| -| Generated CRD and Helm bundle | Integration release CI from the kagent fork | Cluster operator upgrade | +| OCI Helm chart with generated CRDs and Harness template | Integration release CI from the kagent fork | Cluster operator install or upgrade | | Patched kagent control-plane image | Integration release CI from the kagent fork | Cluster operator deployment | | OpenAPPA Actor runtime image | Integration release CI | OpenAPPA `Harness` workload | -| OpenAPPA `Harness` manifest | Integration release | Kubernetes API | +| Patched `kagentctl` binary | Integration release CI from the kagent fork | Application-team AgentInstance creation | | Example policy `ConfigMap` | OpenAPPA repository | Policy author and cluster operator | The Actor image contains the patched kagent Go runtime and the `appa-runtime` binary from the same reviewed release. -The `Harness` manifest MUST pin the Actor image by SHA-256 digest. +The Helm chart MUST pin the control-plane and Actor images by SHA-256 digest. -Release notes MUST record the CRD bundle version, both image digests, and source commits. +Release notes MUST record the chart version, both image digests, and source commits. -The cluster operator installs the patched control plane before applying the `Harness`. Existing unpatched control planes reject the new `openappa` field. +The chart contains generated CRDs, the patched control plane, and the OpenAPPA `Harness` template. + +`openappa.harness.enabled` defaults to false so an unpatched controller never observes the new `openappa` field. + +## Helm installation contract + +Publish the chart at `oci://ghcr.io/archestra-ai/charts/kagent-openappa`. + +The chart exposes these required values: + +```yaml +openappa: + policyRef: customer-support-policy + harness: + enabled: false + allowedAgentTemplates: + matchLabels: + openappa: enabled +substrate: + workerPoolRef: default + snapshotLocation: s3://kagent-snapshots/openappa +``` + +When enabled, the chart maps these values to `spec.kagent.openappa.policyRef`, `spec.allowedAgentTemplates.selector`, `spec.substrate.workerPoolRef`, and `spec.substrate.snapshotPolicy.location`. + +The chart pins `spec.workload.image` to the reviewed Actor image digest. Operators do not supply a mutable tag. + +`helm show crds` MUST produce the complete generated CRD bundle for existing-cluster upgrades. + +The first `helm upgrade --install --atomic --wait` MUST keep `openappa.harness.enabled=false` and leave chart-managed Deployments and Services ready. + +Operators MUST verify the controller Deployment and at least one ready `kagent-controller` service endpoint before labeling templates or creating instances. That endpoint serves the CRD conversion path. + +Only then can a second atomic Helm upgrade set `openappa.harness.enabled=true` and create the Harness. + +Harness preparation is a separate controller operation. Operators label each selected `AgentTemplate` and wait for its `status.harnesses[harness=kagent-openappa].conditions[Ready]` value. + +A failed pair preparation leaves the chart installed for diagnosis. The operator fixes the policy or template, or runs `helm rollback` explicitly. + +Add this CLI mapping: + +```text +kagentctl agent-instance create \ + --namespace \ + --template \ + --harness kagent-openappa +``` + +The command calls `AgentInstanceService.CreateAgentInstance` with namespace, Harness, AgentTemplate, idempotency request ID, and display name. ## Harness profile @@ -492,10 +540,11 @@ The OpenAPPA Harness profile, policy compiler, Go ADK extension, and Rust adapte ### Control-plane upgrade -1. Apply the generated CRD or Helm bundle with optional `KagentHarness.OpenAPPA` and collaboration-mode fields. -2. Deploy the patched kagent control-plane image. -3. Wait for every controller and conversion webhook to report ready. -4. Apply no OpenAPPA `Harness` until the patched control plane is ready. +1. Apply the policy `ConfigMap` and values with `openappa.harness.enabled=false`. +2. Apply the generated CRD bundle with optional `KagentHarness.OpenAPPA` and collaboration-mode fields. +3. Run the first atomic Helm upgrade with the Harness disabled. +4. Wait for every controller and the `kagent-controller` service endpoint to report ready. +5. Run the second atomic Helm upgrade with `openappa.harness.enabled=true`. Existing Harnesses omit the new field. Their compilation, builders, callbacks, and Actor images remain unchanged. @@ -503,10 +552,10 @@ Existing Harnesses omit the new field. Their compilation, builders, callbacks, a For each existing `AgentTemplate` selected for adoption: -1. Apply the policy `ConfigMap` and OpenAPPA `Harness`. +1. Label the `AgentTemplate` so the chart-managed Harness admits it. 2. Let the compiler inventory tools, providers, child modes, MCP Apps, memory, and background paths. 3. Refuse preparation when any enabled path lacks required coverage. -4. Wait for a ready revision and Substrate `ActorTemplate` before creating an instance. +4. Wait for the Harness-specific ready condition and Substrate `ActorTemplate` before creating an instance. The same `AgentTemplate` can serve both the old and OpenAPPA Harnesses during rollout. diff --git a/website/app/globals.css b/website/app/globals.css index 1a610193..949412a4 100644 --- a/website/app/globals.css +++ b/website/app/globals.css @@ -838,7 +838,6 @@ samp { } /* Static integration diagrams use the documentation palette. */ -.kagent-deployment-figure, .kagent-profile-figure { margin: 1.5rem 0 2rem; padding: 1rem; @@ -848,62 +847,6 @@ samp { font-family: var(--font-mono); } -.kagent-deployment-flow { - display: grid; - grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) auto minmax(0, 0.8fr) auto minmax(0, 1fr); - align-items: stretch; - gap: 0.4rem; - padding-top: 1rem; -} - -.kagent-deployment-phase { - display: flex; - min-width: 0; - flex-direction: column; - gap: 0.5rem; - padding: 0.65rem; - border: 1px solid var(--border); - border-radius: 5px; - background: var(--bg); -} - -.kagent-deployment-phase-kagent { - border-color: var(--accent-border); - background: var(--accent-bg); -} - -.kagent-deployment-phase > span { - color: var(--text); - font-size: 11px; -} - -.kagent-deployment-phase > strong { - color: var(--text-strong); - font-size: 13px; - line-height: 1.35; -} - -.kagent-deployment-phase > div { - display: flex; - flex-wrap: wrap; - gap: 0.3rem; - margin-top: auto; -} - -.kagent-deployment-phase small { - padding: 0.15rem 0.3rem; - border: 1px solid var(--border-weak); - border-radius: 3px; - color: var(--text); - font-size: 10px; - line-height: 1.3; -} - -.kagent-deployment-arrow { - align-self: center; - color: var(--icon); -} - .kagent-profile-actor { margin-top: 1rem; padding: 0.8rem; @@ -912,25 +855,6 @@ samp { background: var(--bg); } -.kagent-profile-actor-title { - display: flex; - align-items: baseline; - justify-content: space-between; - gap: 1rem; - padding-bottom: 0.65rem; - border-bottom: 1px solid var(--border-weak); -} - -.kagent-profile-actor-title span { - color: var(--text); - font-size: 12px; -} - -.kagent-profile-actor-title strong { - color: var(--text-strong); - font-size: 12px; -} - .kagent-profile-process-flow { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); @@ -939,32 +863,6 @@ samp { padding: 1rem 0; } -.kagent-profile-supervisor { - display: flex; - align-items: baseline; - gap: 0.65rem; - margin-top: 0.75rem; - padding: 0.55rem 0.65rem; - border: 1px solid var(--border-weak); - border-radius: 4px; - background: var(--bg-weak); -} - -.kagent-profile-supervisor span, -.kagent-profile-supervisor small { - color: var(--text); - font-size: 11px; -} - -.kagent-profile-supervisor strong { - color: var(--text-strong); - font-size: 12px; -} - -.kagent-profile-supervisor small { - margin-left: auto; -} - .kagent-profile-process { display: flex; min-height: 7rem; @@ -1013,7 +911,7 @@ samp { .kagent-profile-runtime-meta { display: flex; - justify-content: space-between; + justify-content: center; gap: 1rem; padding-top: 0.65rem; border-top: 1px solid var(--border-weak); @@ -1105,39 +1003,6 @@ samp { content: "→ "; } -.kagent-runtime-band { - display: flex; - align-items: center; - justify-content: center; - gap: 0.75rem; - padding: 0.8rem 1rem; - border: 1px solid var(--accent-border); - border-radius: 5px; - background: var(--bg); - color: var(--text); - font-size: 12px; -} - -.kagent-runtime-band strong { - color: var(--accent); -} - -.kagent-decision-strip { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 0.75rem; - margin-top: 0.75rem; - padding-top: 0.75rem; - border-top: 1px solid var(--border-weak); - color: var(--text); - font-size: 11px; - text-align: center; -} - -.kagent-decision-strip strong { - color: var(--text-strong); -} - @media (max-width: 767px) { .kagent-figure-title { align-items: flex-start; @@ -1167,44 +1032,12 @@ samp { content: "↓ "; } - .kagent-decision-strip { - grid-template-columns: 1fr; - gap: 0.35rem; - text-align: left; - } - - .kagent-runtime-band { - align-items: stretch; - flex-direction: column; - text-align: center; - } - - .kagent-runtime-band > span[aria-hidden="true"] { - transform: rotate(90deg); - } - - .kagent-profile-actor-title, - .kagent-profile-runtime-meta, - .kagent-profile-supervisor { + .kagent-profile-runtime-meta { align-items: flex-start; flex-direction: column; gap: 0.25rem; } - .kagent-profile-supervisor small { - margin-left: 0; - } - - .kagent-deployment-flow { - grid-template-columns: 1fr; - gap: 0.6rem; - } - - .kagent-deployment-arrow { - justify-self: center; - transform: rotate(90deg); - } - .kagent-profile-process-flow { grid-template-columns: 1fr; gap: 0.75rem; diff --git a/website/components/DocContent.tsx b/website/components/DocContent.tsx index 7aeb97f8..1491b782 100644 --- a/website/components/DocContent.tsx +++ b/website/components/DocContent.tsx @@ -20,7 +20,7 @@ import { ConnectedAgentFigure } from "@/components/figures/ConnectedAgentFigure" import { ExfiltrationFigure } from "@/components/figures/ExfiltrationFigure"; import { GuardrailFigure } from "@/components/figures/GuardrailFigure"; import { LabelFoldFigure } from "@/components/figures/LabelFoldFigure"; -import { KagentDeploymentFigure, KagentEnforcementFigure, KagentProfileFigure } from "@/components/figures/KagentEnforcementFigure"; +import { KagentEnforcementFigure, KagentProfileFigure } from "@/components/figures/KagentEnforcementFigure"; import { NegotiationFigure } from "@/components/figures/NegotiationFigure"; import { PolicyStackFigure } from "@/components/figures/PolicyStackFigure"; import { RemedyPlanFigure } from "@/components/figures/RemedyPlanFigure"; @@ -46,7 +46,6 @@ const DIRECTIVES: Record ReactNode> = { "fig-guardrail": () => , "fig-label-fold": () => , "kagent-enforcement": () => , - "kagent-deployment": () => , "kagent-profile": () => , "fig-negotiation": () => , "fig-policy-stack": () => , diff --git a/website/components/figures/KagentEnforcementFigure.tsx b/website/components/figures/KagentEnforcementFigure.tsx index 6dce9531..1bab247e 100644 --- a/website/components/figures/KagentEnforcementFigure.tsx +++ b/website/components/figures/KagentEnforcementFigure.tsx @@ -1,68 +1,3 @@ -export function KagentDeploymentFigure() { - return ( -
-
- The operator installs resources. kagent prepares a revision and ActorTemplate. The user creates an AgentInstance. Substrate creates the Actor. -
- -
- deployment flow -
- -
-
- 01 · cluster operator - install resources -
- CRD bundle - patched control plane - Harness - policy ConfigMap - AgentTemplate -
-
- - - -
- 02 · kagent controller - prepare immutable runtime -
- prepared revision - ActorTemplate - image digest - policy digest -
-
- - - -
- 03 · application team - create AgentInstance -
- select AgentTemplate - select Harness -
-
- - - -
- 04 · Substrate - create the Actor -
- pull Actor image - start supervisor - mount durable /data - expose A2A endpoint -
-
-
-
- ); -} - export function KagentProfileFigure() { return (
@@ -71,44 +6,31 @@ export function KagentProfileFigure() {
- component architecture - inside one Substrate Actor + runtime request and decision flow
-
- one container - digest-pinned kagent-openappa Actor image -
- -
- PID 1 - kagent-openappa-supervisor - starts, monitors, signals, and stops both child processes -
-
- kagent-go-adk process - Google ADK - OpenAPPA extension implements callbacks and plugin + kagent-go-adk + Google ADK callbacks + OpenAPPA Go extension
- appa-runtime process - appa-adapter-kagent + Engine - apply policy and append facts to /data/openappa/appa.db + appa-runtime + appa-adapter-kagent → Engine + append-only trajectory log
local HTTP · 127.0.0.1:8787/hook - durable state · /data/openappa/appa.db
@@ -123,16 +45,15 @@ export function KagentEnforcementFigure() {
- Google ADK extension points - kagent bridge ↔ OpenAPPA + two Google ADK enforcement points
01 · before dispatch BeforeToolCallbacks - kagent sends HookEvent::ToolCall - OpenAPPA returns AllowCall or DenyCall + HookEvent::ToolCall → /hook + AllowCall or DenyCall
@@ -144,25 +65,11 @@ export function KagentEnforcementFigure() {
02 · after execution AfterToolCallbacks - OnToolErrorCallbacks capture failure first - kagent sends HookEvent::ToolResult + HookEvent::ToolResult → /hook + Ack, ReplaceOutput, or Block + OnToolErrorCallbacks record failure
- -
- kagent OpenAPPA extension - - HTTP /hook - - appa-adapter-kagent + Engine -
- -
- DenyCall blocks dispatch - Ack keeps the result - ReplaceOutput substitutes before model delivery - Block withholds the result -
); } diff --git a/website/content/docs/kagent.md b/website/content/docs/kagent.md index b7b5c00d..e39d5b0a 100644 --- a/website/content/docs/kagent.md +++ b/website/content/docs/kagent.md @@ -33,57 +33,85 @@ The Go extension sends both events to the local `appa-runtime` process. It appli OpenAPPA can block dispatch, keep the returned result, replace it, or withhold it. -## Install the integration +## Install with Helm -The OpenAPPA integration maintainers build and publish these artifacts from pinned source commits: +The integration release publishes one OCI Helm chart. It pins both images and includes an OpenAPPA `Harness` template that is disabled by default. -- A generated kagent resource-definition bundle. -- A patched kagent control-plane image. -- A digest-pinned Actor image that contains kagent and OpenAPPA. -- An OpenAPPA `Harness` manifest. +Create the policy `ConfigMap`: -The cluster operator installs those artifacts and supplies the policy `ConfigMap`. - -## Select the OpenAPPA Harness +```sh +kubectl create namespace kagent --dry-run=client -o yaml | kubectl apply -f - +kubectl -n kagent create configmap customer-support-policy \ + --from-file=appa.toml=./appa.toml \ + --dry-run=client -o yaml | kubectl apply -f - +``` -A kagent `Harness` is a Kubernetes resource that tells Substrate which workload image, workers, and snapshot rules to use when running an `AgentTemplate`. +Apply the generated CRDs, then install or upgrade kagent: ```yaml -# Proposed fields only. This excerpt omits existing required Harness fields. -apiVersion: kagent.dev/v1alpha3 -kind: Harness -metadata: - name: kagent-openappa -spec: - kagent: - openappa: - policyRef: - name: customer-support-policy - workload: - image: ghcr.io/archestra-ai/kagent-openappa@sha256: +# openappa-values.yaml +openappa: + policyRef: customer-support-policy + harness: + enabled: false + allowedAgentTemplates: + matchLabels: + openappa: enabled +substrate: + workerPoolRef: default + snapshotLocation: s3://kagent-snapshots/openappa ``` -`policyRef` names a `ConfigMap` in the same Kubernetes namespace. +```sh +helm show crds oci://ghcr.io/archestra-ai/charts/kagent-openappa \ + --version | kubectl apply -f - -After kagent prepares the pair, an application team creates an `AgentInstance` with the OpenAPPA `Harness`. +helm upgrade --install kagent \ + oci://ghcr.io/archestra-ai/charts/kagent-openappa \ + --version \ + --namespace kagent \ + --values openappa-values.yaml \ + --atomic --wait --timeout 5m +``` -:::kagent-deployment::: +Wait for the patched controller and its service endpoint: -## Run OpenAPPA inside the Actor +```sh +kubectl -n kagent rollout status deployment/kagent-controller --timeout=5m +kubectl -n kagent wait endpoints/kagent-controller \ + --for='jsonpath={.subsets[0].addresses[0].ip}' --timeout=5m +``` -The Actor image contains three binaries: +Enable the chart-managed `Harness`, label the selected `AgentTemplate`, and wait for preparation: -```text -/usr/local/bin/kagent-openappa-supervisor - |-- /usr/local/bin/appa-runtime --adapter kagent - `-- /usr/local/bin/kagent-go-adk +```sh +helm upgrade kagent oci://ghcr.io/archestra-ai/charts/kagent-openappa \ + --version \ + --namespace kagent \ + --values openappa-values.yaml \ + --set openappa.harness.enabled=true \ + --atomic --wait --timeout 5m + +kubectl -n kagent label agenttemplate customer-support-agent openappa=enabled +kubectl -n kagent wait agenttemplate/customer-support-agent \ + --for='jsonpath={.status.harnesses[?(@.harness=="kagent-openappa")].conditions[?(@.type=="Ready")].status}=True' \ + --timeout=5m ``` -The supervisor runs as PID 1. It starts `appa-runtime`, waits for `/health`, then starts `kagent-go-adk`. +The integration adds a `kagentctl` command for creating the protected instance: + +```sh +kagentctl agent-instance create customer-support \ + --namespace kagent \ + --template customer-support-agent \ + --harness kagent-openappa +``` + +## Run OpenAPPA inside the Actor -The supervisor forwards termination signals and stops the other child when either child exits. +The Actor image runs `kagent-go-adk` and `appa-runtime` as separate processes in one container. -It then exits with failure so Substrate applies the configured `Actor` lifecycle policy. +The Actor becomes ready only after `appa-runtime` passes its health check. :::kagent-profile::: @@ -153,8 +181,9 @@ An existing `AgentInstance` cannot switch to another `Harness` or prepared revis | Phase | Operator action | Routing state | |---|---|---| -| Upgrade | Install the resource definitions and patched control plane. Wait for controllers and webhooks | Existing instances continue on current revisions | -| Prepare | Apply the policy `ConfigMap` and OpenAPPA `Harness`. Wait for a ready revision | Existing instances continue serving | +| Prepare | Apply the policy `ConfigMap` and values with the `Harness` disabled | Existing instances continue serving | +| Upgrade | Apply chart CRDs, upgrade the control plane, and wait for its service endpoint | Existing instances continue on current revisions | +| Enable | Enable the chart-managed `Harness`, label the template, and wait for its ready revision | Existing instances continue serving | | Canary | Create a protected `AgentInstance` and run internal checks | Production traffic remains on the old instance | | Cut over | Route new root A2A tasks to protected instance | Pin old task and context IDs to old instance | | Drain | Wait for terminal work or apply the timeout and cancellation policy | No new work reaches the old instance | From f3cce01c52a4ede255cc73d8831146df6e38a5e5 Mon Sep 17 00:00:00 2001 From: piercypixel Date: Sat, 29 Aug 2026 02:18:13 +0400 Subject: [PATCH 6/9] docs: simplify kagent adoption steps --- website/content/docs/kagent.md | 41 ++++++++++++++++------------------ 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/website/content/docs/kagent.md b/website/content/docs/kagent.md index e39d5b0a..7993d419 100644 --- a/website/content/docs/kagent.md +++ b/website/content/docs/kagent.md @@ -175,35 +175,32 @@ After Actor recovery, the profile sends `TurnEnd` before a new call to close an One trajectory runs one tool call at a time. Separate child trajectories can run concurrently. -## Adopt side by side +## Move an existing agent -An existing `AgentInstance` cannot switch to another `Harness` or prepared revision. Existing clusters adopt OpenAPPA through a side-by-side replacement. +An existing `AgentInstance` cannot change its `Harness`. Replace it with a new protected instance: -| Phase | Operator action | Routing state | -|---|---|---| -| Prepare | Apply the policy `ConfigMap` and values with the `Harness` disabled | Existing instances continue serving | -| Upgrade | Apply chart CRDs, upgrade the control plane, and wait for its service endpoint | Existing instances continue on current revisions | -| Enable | Enable the chart-managed `Harness`, label the template, and wait for its ready revision | Existing instances continue serving | -| Canary | Create a protected `AgentInstance` and run internal checks | Production traffic remains on the old instance | -| Cut over | Route new root A2A tasks to protected instance | Pin old task and context IDs to old instance | -| Drain | Wait for terminal work or apply the timeout and cancellation policy | No new work reaches the old instance | -| Retain | Suspend old instance, export records, and verify `/data` snapshot | Keep both for at least 30 days | - -The protected instance starts a new OpenAPPA database and new root trajectories. The first release does not import an old unprotected transcript. - -When the application uses an external ADK session store, migration starts a new protected session ID. +1. Run the Helm upgrade and readiness commands above. +2. Create a protected instance from the existing `AgentTemplate`. +3. Update the application to use the new `AgentInstance` ID. +4. Let requests already assigned to the old instance finish there. +5. Suspend the old instance. -Migration excludes the old model context. - -External records re-enter through protected tools. Their tool contracts assign the required policy Labels. +```sh +kagentctl agent-instance create customer-support-openappa \ + --namespace kagent \ + --template customer-support-agent \ + --harness kagent-openappa -Rollback routes new root tasks to the unchanged old instance. Existing protected task and context IDs stay protected until terminal or canceled. +kagentctl agent-instance suspend --namespace kagent +``` -After rollback drain, the operator suspends the protected instance and exports its task and event records. +The protected instance starts a new OpenAPPA database and a new model session. It does not import the old transcript. -The operator verifies that the Substrate snapshot contains `/data`, then retains the export and snapshot for at least 30 days. +To revert, resume the old instance and point the application back to its ID: -Deletion requires a separate operator decision after the retention period. Rollback never merges or rewrites OpenAPPA trajectory logs. +```sh +kagentctl agent-instance resume --namespace kagent +``` ## Implementation plan From e672ee389353d5fbf9fe66eea425117c0becd9d7 Mon Sep 17 00:00:00 2001 From: piercypixel Date: Sat, 29 Aug 2026 02:27:38 +0400 Subject: [PATCH 7/9] docs: define kagent Harness selection --- website/content/docs/kagent.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/website/content/docs/kagent.md b/website/content/docs/kagent.md index 7993d419..67ae7048 100644 --- a/website/content/docs/kagent.md +++ b/website/content/docs/kagent.md @@ -82,7 +82,7 @@ kubectl -n kagent wait endpoints/kagent-controller \ --for='jsonpath={.subsets[0].addresses[0].ip}' --timeout=5m ``` -Enable the chart-managed `Harness`, label the selected `AgentTemplate`, and wait for preparation: +Create the chart-managed `Harness`, label the `AgentTemplate` that may use it, and wait for preparation: ```sh helm upgrade kagent oci://ghcr.io/archestra-ai/charts/kagent-openappa \ @@ -107,6 +107,10 @@ kagentctl agent-instance create customer-support \ --harness kagent-openappa ``` +`--harness kagent-openappa` sets the `harness` field in `CreateAgentInstance` to that Kubernetes resource name. + +kagent resolves the same-namespace `Harness` and `AgentTemplate`, then starts their latest ready revision. + ## Run OpenAPPA inside the Actor The Actor image runs `kagent-go-adk` and `appa-runtime` as separate processes in one container. From d7d0a15442e7bd9cb8f784ecba36742b1517acf7 Mon Sep 17 00:00:00 2001 From: piercypixel Date: Sat, 29 Aug 2026 02:51:49 +0400 Subject: [PATCH 8/9] docs: replace kagent diagrams with architecture specs --- website/app/globals.css | 224 ------------------ website/components/DocContent.tsx | 3 - .../figures/KagentEnforcementFigure.tsx | 75 ------ website/content/docs/kagent.md | 76 ++++-- 4 files changed, 59 insertions(+), 319 deletions(-) delete mode 100644 website/components/figures/KagentEnforcementFigure.tsx diff --git a/website/app/globals.css b/website/app/globals.css index 949412a4..f835dd99 100644 --- a/website/app/globals.css +++ b/website/app/globals.css @@ -837,230 +837,6 @@ samp { height: auto; } -/* Static integration diagrams use the documentation palette. */ -.kagent-profile-figure { - margin: 1.5rem 0 2rem; - padding: 1rem; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg-weak); - font-family: var(--font-mono); -} - -.kagent-profile-actor { - margin-top: 1rem; - padding: 0.8rem; - border: 1px solid var(--border); - border-radius: 5px; - background: var(--bg); -} - -.kagent-profile-process-flow { - display: grid; - grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); - align-items: stretch; - gap: 0.75rem; - padding: 1rem 0; -} - -.kagent-profile-process { - display: flex; - min-height: 7rem; - flex-direction: column; - justify-content: center; - gap: 0.4rem; - padding: 0.8rem; - border: 1px solid var(--border); - border-radius: 5px; - background: var(--bg-weak); -} - -.kagent-profile-process-appa { - border-color: var(--accent-border); - background: var(--accent-bg); -} - -.kagent-profile-process span, -.kagent-profile-process small { - color: var(--text); - font-size: 12px; - line-height: 1.4; -} - -.kagent-profile-process strong { - color: var(--text-strong); - font-size: 14px; -} - -.kagent-profile-exchange { - display: grid; - align-content: center; - gap: 0.5rem; - color: var(--accent); - font-size: 11px; - white-space: nowrap; -} - -.kagent-profile-request::after { - content: " →"; -} - -.kagent-profile-response::before { - content: "← "; -} - -.kagent-profile-runtime-meta { - display: flex; - justify-content: center; - gap: 1rem; - padding-top: 0.65rem; - border-top: 1px solid var(--border-weak); - color: var(--text); - font-size: 11px; -} - -.kagent-figure { - margin: 1.5rem 0 2rem; - padding: 1rem; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg-weak); - font-family: var(--font-mono); -} - -.kagent-figure-title { - display: flex; - align-items: center; - justify-content: space-between; - gap: 1rem; - padding-bottom: 0.75rem; - border-bottom: 1px solid var(--border-weak); - color: var(--text-strong); - font-size: 12px; -} - -.kagent-figure-title span:last-child { - color: var(--text); -} - -.kagent-figure-title span:first-child { - flex-shrink: 0; -} - -.kagent-gates { - display: grid; - grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); - align-items: stretch; - gap: 0.8rem; - padding: 1rem 0; -} - -.kagent-gate { - display: flex; - min-height: 8rem; - flex-direction: column; - justify-content: center; - gap: 0.45rem; - padding: 0.8rem; - border: 1px solid var(--accent-border); - border-radius: 5px; - background: var(--accent-bg); -} - -.kagent-gate > span, -.kagent-gate > small { - color: var(--text); - font-size: 11px; - line-height: 1.4; -} - -.kagent-gate > strong { - color: var(--text-strong); - font-size: 13px; - line-height: 1.35; -} - -.kagent-execution { - display: flex; - align-items: center; - align-self: center; - gap: 0.35rem; - color: var(--text); - font-size: 11px; - white-space: nowrap; -} - -.kagent-execution strong { - color: var(--text-strong); - font-size: 13px; -} - -.kagent-execution span:first-child::after { - content: " →"; -} - -.kagent-execution span:last-child::before { - content: "→ "; -} - -@media (max-width: 767px) { - .kagent-figure-title { - align-items: flex-start; - flex-direction: column; - gap: 0.2rem; - } - - .kagent-gates { - grid-template-columns: 1fr; - gap: 0.6rem; - } - - .kagent-gate { - min-height: 0; - } - - .kagent-execution { - flex-direction: column; - gap: 0.2rem; - } - - .kagent-execution span:first-child::after { - content: " ↓"; - } - - .kagent-execution span:last-child::before { - content: "↓ "; - } - - .kagent-profile-runtime-meta { - align-items: flex-start; - flex-direction: column; - gap: 0.25rem; - } - - .kagent-profile-process-flow { - grid-template-columns: 1fr; - gap: 0.75rem; - } - - .kagent-profile-process { - min-height: 0; - } - - .kagent-profile-exchange { - justify-items: center; - } - - .kagent-profile-request::after { - content: " ↓"; - } - - .kagent-profile-response::before { - content: "↑ "; - } - -} - .prose blockquote { border-left: 2px solid var(--border); padding-left: 1rem; diff --git a/website/components/DocContent.tsx b/website/components/DocContent.tsx index 1491b782..7e33d84b 100644 --- a/website/components/DocContent.tsx +++ b/website/components/DocContent.tsx @@ -20,7 +20,6 @@ import { ConnectedAgentFigure } from "@/components/figures/ConnectedAgentFigure" import { ExfiltrationFigure } from "@/components/figures/ExfiltrationFigure"; import { GuardrailFigure } from "@/components/figures/GuardrailFigure"; import { LabelFoldFigure } from "@/components/figures/LabelFoldFigure"; -import { KagentEnforcementFigure, KagentProfileFigure } from "@/components/figures/KagentEnforcementFigure"; import { NegotiationFigure } from "@/components/figures/NegotiationFigure"; import { PolicyStackFigure } from "@/components/figures/PolicyStackFigure"; import { RemedyPlanFigure } from "@/components/figures/RemedyPlanFigure"; @@ -45,8 +44,6 @@ const DIRECTIVES: Record ReactNode> = { "fig-exfiltration": () => , "fig-guardrail": () => , "fig-label-fold": () => , - "kagent-enforcement": () => , - "kagent-profile": () => , "fig-negotiation": () => , "fig-policy-stack": () => , "fig-remedy-plan": () => , diff --git a/website/components/figures/KagentEnforcementFigure.tsx b/website/components/figures/KagentEnforcementFigure.tsx deleted file mode 100644 index 1bab247e..00000000 --- a/website/components/figures/KagentEnforcementFigure.tsx +++ /dev/null @@ -1,75 +0,0 @@ -export function KagentProfileFigure() { - return ( -
-
- The kagent Go runtime sends tool calls and results to appa-runtime. appa-runtime returns policy decisions over local HTTP. -
- -
- runtime request and decision flow -
- -
-
-
- kagent-go-adk - Google ADK callbacks - OpenAPPA Go extension -
- - - -
- appa-runtime - appa-adapter-kagent → Engine - append-only trajectory log -
-
- -
- local HTTP · 127.0.0.1:8787/hook -
-
-
- ); -} - -export function KagentEnforcementFigure() { - return ( -
-
- Google ADK calls OpenAPPA before tool dispatch and after the terminal tool outcome. OpenAPPA can block the call or replace its result. -
- -
- two Google ADK enforcement points -
- -
-
- 01 · before dispatch - BeforeToolCallbacks - HookEvent::ToolCall → /hook - AllowCall or DenyCall -
- -
- AllowCall - tool.Run - outcome -
- -
- 02 · after execution - AfterToolCallbacks - HookEvent::ToolResult → /hook - Ack, ReplaceOutput, or Block - OnToolErrorCallbacks record failure -
-
-
- ); -} diff --git a/website/content/docs/kagent.md b/website/content/docs/kagent.md index 67ae7048..e6c98eb8 100644 --- a/website/content/docs/kagent.md +++ b/website/content/docs/kagent.md @@ -27,15 +27,29 @@ kagent uses Google ADK to run model, tool, and child-agent steps. An ordinary AD 1. `BeforeToolCallbacks` send `HookEvent::ToolCall` before tool dispatch. 2. `AfterToolCallbacks` send one terminal `HookEvent::ToolResult` after execution or error handling. -:::kagent-enforcement::: - -The Go extension sends both events to the local `appa-runtime` process. It applies the returned decision before kagent continues. +```text +Model Google ADK Go extension appa-runtime Tool + | FunctionCall | | | | + |------------------->| BeforeTool | | | + | |------------------>| ToolCall | | + | | |------------------->| | + | | |<-------------------| Allow / deny | + | |-----------------------------------------------> Run | + | |<----------------------------------------------- result | + | | AfterTool | | | + | |------------------>| ToolResult | | + | | |------------------->| | + | | |<-------------------| result rule | + |<-------------------| admitted result | | | +``` -OpenAPPA can block dispatch, keep the returned result, replace it, or withhold it. +OpenAPPA can block dispatch, keep the result, replace it, or withhold it before model delivery. ## Install with Helm -The integration release publishes one OCI Helm chart. It pins both images and includes an OpenAPPA `Harness` template that is disabled by default. +The integration release publishes one OCI Helm chart. It pins both images and includes an OpenAPPA `Harness` template. + +The chart disables that template by default. Create the policy `ConfigMap`: @@ -111,21 +125,36 @@ kagentctl agent-instance create customer-support \ kagent resolves the same-namespace `Harness` and `AgentTemplate`, then starts their latest ready revision. -## Run OpenAPPA inside the Actor - -The Actor image runs `kagent-go-adk` and `appa-runtime` as separate processes in one container. - -The Actor becomes ready only after `appa-runtime` passes its health check. - -:::kagent-profile::: - -The Go extension implements existing ADK callback and plugin interfaces inside `kagent-go-adk`. +## Architecture + +```text ++---------------------------- Kubernetes cluster -----------------------------+ +| | +| kubectl / Helm -> Kubernetes API -> patched kagent controller | +| Harness + AgentTemplate + policy ConfigMap | +| | | +| | prepared Revision + ActorTemplate | +| v | +| Application -- A2A --> +------------- Substrate Actor -------------------+ | +| | | | +| Model provider <------> | kagent-go-adk | | +| Tools / MCP / A2A <--> | Google ADK + OpenAPPA Go extension | | +| | | ToolCall / ToolResult | | +| | v /hook ^ HookDecision | | +| | appa-runtime -> appa-adapter-kagent -> Engine | | +| | | | | +| | v | | +| | /data/openappa/appa.db | | +| +--------------------------------------------------+ | +| | ++------------------------------------------------------------------------------+ +``` -Each callback sends kagent `JSON` to `http://127.0.0.1:8787/hook`. `appa-adapter-kagent` translates the request into `HookEvent` and `HookDecision` values. +The Actor image runs `kagent-go-adk` and `appa-runtime` as separate processes. The Actor becomes ready after `appa-runtime` passes its health check. -kagent declares `/data` as durable storage in the `ActorTemplate`. Substrate mounts it when it creates the `Actor`. +The Go extension implements existing ADK callbacks inside `kagent-go-adk`. It sends kagent `JSON` to the local `/hook` endpoint. -OpenAPPA stores its database at `/data/openappa/appa.db`. +Substrate mounts the `ActorTemplate` `/data` directory. OpenAPPA stores its database at `/data/openappa/appa.db`. ## Map every execution path @@ -142,6 +171,19 @@ Most paths use existing Google ADK callbacks. The table marks proposed kagent ca | MCP App internal tool or resource | Proposed owning-Actor gate → `ToolCall` | The same proposed gate → `ToolResult` | | Registered long-running work | Proposed `BeforeBackgroundStart` → `ToolCall` and `ChildStart` | Proposed `OnBackgroundResult` → `SpawnResult` | +For `single_turn`, `task`, remote A2A, and registered long-running work, the child return follows this sequence: + +```text +Parent ADK -> Go extension: path-specific dispatch callback +Go extension -> appa-runtime: ToolCall(spawn = true) +appa-runtime -> Go extension: AllowCall + prepared child binding +Go extension -> appa-runtime: ChildStart(actual child ID) +Child, remote agent, or worker -> Go extension: terminal result +Go extension -> appa-runtime: SpawnResult +appa-runtime -> Go extension: Ack | ChildReturn | Block +Go extension -> Parent ADK: admitted child result +``` + `BeforeModelCallbacks` confirm that each model-bound result already passed admission. `plugin.OnUserMessageCallback` rejects forged task responses. Trusted deployment configuration must bind the authenticated remote endpoint to an isolated OpenAPPA `Harness`. From f79a2fbf078c8f1c1d4e105e7f9c2c0857c69548 Mon Sep 17 00:00:00 2001 From: piercypixel Date: Sat, 29 Aug 2026 02:52:01 +0400 Subject: [PATCH 9/9] docs: streamline kagent implementation plan --- integrations/kagent/IMPLEMENTATION.md | 660 ++++++-------------------- 1 file changed, 142 insertions(+), 518 deletions(-) diff --git a/integrations/kagent/IMPLEMENTATION.md b/integrations/kagent/IMPLEMENTATION.md index 21188aa5..d68077b8 100644 --- a/integrations/kagent/IMPLEMENTATION.md +++ b/integrations/kagent/IMPLEMENTATION.md @@ -6,60 +6,60 @@ Source baselines: - kagent commit `9e246fd3797457b18fc277680be1629a0f57fce0` - Google ADK Go tag `v2.2.0` -- OpenAPPA runtime API in `appa-runtime-api/src/lib.rs` +- OpenAPPA runtime contract in `appa-runtime-api/src/lib.rs` -The reader-facing proposal is available at [openappa.com/kagent](https://www.openappa.com/kagent). +The reader-facing proposal is at [openappa.com/kagent](https://www.openappa.com/kagent). -## Invariants +## Goal -The implementation MUST preserve these properties: +Add an optional OpenAPPA profile to the kagent Go runtime without changing behavior for existing Harnesses. -1. OpenAPPA authorizes the final JSON-semantic argument value that kagent passes to `tool.Run`, not provider wire bytes. -2. kagent publishes no returned content before OpenAPPA admission. -3. Each root, child, call, and pending outcome has a stable identity. -4. Uncovered execution paths fail before they handle protected content. -5. Existing Harnesses keep current behavior when OpenAPPA is absent. +The code MUST preserve four invariants: -## Change map +1. OpenAPPA authorizes the final JSON argument value passed to `tool.Run`. +2. No consumer receives a result before OpenAPPA admission. +3. Every root, child, call, and terminal outcome has stable identity. +4. kagent refuses any enabled path that lacks a dispatch or result boundary. + +## Deliverables | Owner | Change | |---|---| -| `go/api/v1alpha3` | Add the optional OpenAPPA Harness profile and local collaboration mode | -| `go/core/v2/translator/kagent` | Resolve policy, compile profile data, and validate supported paths | -| `go/core/v2/substrate` | Include compiled OpenAPPA data in the immutable revision | -| `go/adk/pkg/config` | Materialize the policy bundle and expose process configuration | -| `go/adk/pkg/agent` | Inject callbacks and snapshot final ADK arguments | -| `go/adk/pkg/runner` | Add the OpenAPPA plugin and memory-service decorator | -| `go/adk/pkg/tools` | Preserve remote A2A task state and child identity | -| OpenAPPA | Add `appa-adapter-kagent` and the `kagent` runtime adapter option | - -## Release artifacts +| `go/api/v1alpha3` | Add optional OpenAPPA Harness configuration and local child collaboration mode | +| `go/core/v2/translator/kagent` | Resolve policy, compile profile data, and validate path coverage | +| `go/core/v2/substrate` | Include policy and egress digests in the prepared revision and `ActorTemplate` | +| `go/adk/pkg/agent` | Register OpenAPPA callbacks on the root and every local child | +| `go/adk/pkg/config` | Materialize the policy bundle from compiled Actor configuration | +| `go/adk/pkg/runner` | Register the task plugin and memory decorator | +| `go/adk/pkg/tools` | Wrap final tool execution and preserve remote A2A context and `TaskState` | +| `go/adk/cmd/openappa-supervisor` | Start and monitor `appa-runtime` and `kagent-go-adk` | +| `go/core/internal/grpcserver` | Route protected MCP App internal calls to the owning Actor | +| `appa-adapter-kagent` | Translate kagent JSON to `HookEvent` and `HookDecision` | +| Helm chart and `kagentctl` | Install the profile and create protected `AgentInstance` resources | -The integration release publishes these artifacts: +## Package the profile -| Artifact | Producer | Consumer | -|---|---|---| -| OCI Helm chart with generated CRDs and Harness template | Integration release CI from the kagent fork | Cluster operator install or upgrade | -| Patched kagent control-plane image | Integration release CI from the kagent fork | Cluster operator deployment | -| OpenAPPA Actor runtime image | Integration release CI | OpenAPPA `Harness` workload | -| Patched `kagentctl` binary | Integration release CI from the kagent fork | Application-team AgentInstance creation | -| Example policy `ConfigMap` | OpenAPPA repository | Policy author and cluster operator | +Extend `KagentHarness` with an optional `OpenAPPA` block containing a same-namespace policy `ConfigMap` reference. -The Actor image contains the patched kagent Go runtime and the `appa-runtime` binary from the same reviewed release. +The compiler MUST: -The Helm chart MUST pin the control-plane and Actor images by SHA-256 digest. +1. Resolve and validate the policy bundle. +2. Include all policy bytes and external endpoint destinations in the revision digest. +3. Require an explicit external LLM URL when policy can invoke one. +4. Add the validated files and digest to `AgentConfig.OpenAPPA` in `KAGENT_CONFIG_JSON`. +5. Set `APPA_CONFIG`, `APPA_DB`, and `APPA_RUNTIME_URL` for the Actor image. -Release notes MUST record the chart version, both image digests, and source commits. +`go/adk/pkg/config` decodes `AgentConfig.OpenAPPA`, verifies the digest, and writes files atomically under `/data/openappa/policy/`. -The chart contains generated CRDs, the patched control plane, and the OpenAPPA `Harness` template. +The Actor image contains `kagent-openappa-supervisor`, `kagent-go-adk`, and `appa-runtime`. -`openappa.harness.enabled` defaults to false so an unpatched controller never observes the new `openappa` field. +The supervisor starts `appa-runtime`, waits for `/health`, then starts `kagent-go-adk`. It forwards signals and exits when either child exits. -## Helm installation contract +`/readyz` fails whenever `appa-runtime` is unavailable. Runtime state remains at `/data/openappa/appa.db`. -Publish the chart at `oci://ghcr.io/archestra-ai/charts/kagent-openappa`. +The OCI chart at `ghcr.io/archestra-ai/charts/kagent-openappa` pins the control-plane and Actor image digests. -The chart exposes these required values: +Chart values map to these Harness fields: ```yaml openappa: @@ -74,541 +74,165 @@ substrate: snapshotLocation: s3://kagent-snapshots/openappa ``` -When enabled, the chart maps these values to `spec.kagent.openappa.policyRef`, `spec.allowedAgentTemplates.selector`, `spec.substrate.workerPoolRef`, and `spec.substrate.snapshotPolicy.location`. - -The chart pins `spec.workload.image` to the reviewed Actor image digest. Operators do not supply a mutable tag. - -`helm show crds` MUST produce the complete generated CRD bundle for existing-cluster upgrades. - -The first `helm upgrade --install --atomic --wait` MUST keep `openappa.harness.enabled=false` and leave chart-managed Deployments and Services ready. - -Operators MUST verify the controller Deployment and at least one ready `kagent-controller` service endpoint before labeling templates or creating instances. That endpoint serves the CRD conversion path. - -Only then can a second atomic Helm upgrade set `openappa.harness.enabled=true` and create the Harness. - -Harness preparation is a separate controller operation. Operators label each selected `AgentTemplate` and wait for its `status.harnesses[harness=kagent-openappa].conditions[Ready]` value. - -A failed pair preparation leaves the chart installed for diagnosis. The operator fixes the policy or template, or runs `helm rollback` explicitly. - -Add this CLI mapping: - -```text -kagentctl agent-instance create \ - --namespace \ - --template \ - --harness kagent-openappa -``` - -The command calls `AgentInstanceService.CreateAgentInstance` with namespace, Harness, AgentTemplate, idempotency request ID, and display name. - -## Harness profile - -Extend the existing empty `KagentHarness` type: - -```go -type KagentHarness struct { - OpenAPPA *OpenAPPAHarnessProfile `json:"openappa,omitempty"` -} - -type OpenAPPAHarnessProfile struct { - PolicyRef corev1.LocalObjectReference `json:"policyRef"` -} -``` - -The Harness compiler performs these operations: +Installation is two-phase: -1. Resolve the same-namespace policy ConfigMap. -2. Require one `appa.toml` key and reject unsafe relative file names. -3. Validate the OpenAPPA configuration during revision preparation. -4. Add ConfigMap identity and content hash to revision provenance. -5. Add the policy bundle and digest to the compiled `adk.AgentConfig`. +1. Apply CRDs and upgrade the control plane with `openappa.harness.enabled=false`. +2. Wait for the controller Deployment and service endpoint. +3. Upgrade again with `openappa.harness.enabled=true`. +4. Label each admitted `AgentTemplate` and wait for its Harness-specific `Ready=True` condition. -The revision digest MUST cover the complete policy bundle. A policy change creates a new prepared revision. +`kagentctl agent-instance create --template --harness kagent-openappa` maps directly to `AgentInstanceService.CreateAgentInstance`. -Parse every configured external authority, sanitizer, cast, resolver, membership, and LLM endpoint after policy validation. +## Add the Go ADK extension -Add each HTTPS destination to `Revision.EgressDestinations`. The destination set and policy bytes MUST affect the revision digest. +The Go extension owns transport and correlation. It contains no policy logic. -The profile requires an explicit `[externals.llm].url` when policy can invoke an external LLM. It does not use an implicit provider endpoint. +Keep current exported builder signatures. Add extension-aware variants that accept one optional `OpenAPPAExtensions` value and pass it recursively to local children. -## Local process model +Register the OpenAPPA plugin first in `PluginConfig.Plugins`. Reject kagent `RequireApproval` and any other callback or plugin that can short-circuit `BeforeTool`. -kagent generates a one-container `ActorTemplate` with `DurableDirs: [{Path: "/data"}]`. - -Substrate provisions and mounts that durable directory when it creates the `Actor`. The integration does not create a Kubernetes PVC directly. - -The `Harness` worker pool and snapshot policy control storage placement and Actor checkpoint behavior. - -The OpenAPPA profile uses two runtime processes in that container. - -```text -/usr/local/bin/kagent-openappa-supervisor - |-- /usr/local/bin/appa-runtime --adapter kagent - `-- /usr/local/bin/kagent-go-adk - -/data/openappa/ - |-- policy//appa.toml - `-- appa.db -``` - -Extend compiled `adk.AgentConfig` with an internal OpenAPPA bundle: - -```go -type OpenAPPACompiledConfig struct { - Files map[string]string `json:"files"` - Digest string `json:"digest"` -} -``` - -Add `go/adk/cmd/openappa-supervisor/main.go`. Build it as the Actor image entrypoint and PID 1. - -The Actor image build copies three reviewed binaries into `/usr/local/bin`: the supervisor, patched kagent Go runtime, and `appa-runtime`. - -The supervisor uses `os/exec` to start both children in one process group. It forwards `SIGTERM` and `SIGINT`, reaps children, and enforces shutdown timeouts. - -The runtime materializer writes each file atomically under `/data/openappa/policy/`. It verifies the digest before startup. - -The supervisor starts `appa-runtime` first and waits for `/health`. It starts `kagent-go-adk` only after runtime health succeeds. - -The Go `/readyz` endpoint MUST fail when the local runtime is unavailable. - -The supervisor exits with failure when either child process exits. Substrate then applies its configured `Actor` lifecycle policy. - -Use these process settings: - -```text -APPA_RUNTIME_URL=http://127.0.0.1:8787 -APPA_CONFIG=/data/openappa/policy//appa.toml -APPA_DB=/data/openappa/appa.db -``` +Wrap every executable tool with `OpenAPPATool`. Its `Run` method compares the final argument bytes with the authorized snapshot immediately before delegating to the underlying tool. -## OpenAPPA adapter +A mismatch closes the open dispatch as indeterminate and refuses underlying execution. -Add a pure `appa-adapter-kagent` crate beside `appa-adapter-claude-code`. - -The crate depends only on `appa-runtime-api`. It parses kagent wire JSON into `HookEvent` and renders `HookDecision` into kagent wire JSON. - -Add `Kagent` to the runtime `Adapter` enum and select the new codec for `--adapter kagent`. - -The adapter MUST hold no state, call no policy API, and perform no external I/O. - -## Go ADK extension - -The Go extension implements existing ADK callbacks and one runner plugin. It owns transport and correlation but contains no policy logic. - -```go -type OpenAPPAExtensions struct { - Client *HookClient - AgentCallbacks AgentCallbackSet - Plugin *plugin.Plugin - MemoryService memory.Service - SupportedToolsets ToolsetCatalog -} -``` - -Final names follow kagent conventions. These responsibilities do not change. - -### Builder compatibility - -Keep current exported builder signatures: - -```text -CreateRunnerConfig(...) - -> createRunnerConfig(..., extensions = nil) - -CreateGoogleADKAgent(...) - -> createGoogleADKAgent(..., extensions = nil) -``` - -Add extension-aware entry points for the OpenAPPA Harness: - -```text -CreateRunnerConfigWithExtensions(..., extensions) - -> createRunnerConfig(..., extensions) - -> CreateGoogleADKAgentWithExtensions(..., extensions) - -> createGoogleADKAgent(..., extensions) - -> recurse with the same extensions -``` - -### Callback order - -```text -BeforeTool OpenAPPA argument snapshot and decision -> tool -OnToolError capture failure -> safe error -> mark outcome pending -AfterTool if released, report one success or failure -> publication -BeforeModel OpenAPPA admission assertion -> model -AfterModel OpenAPPA task dispatch gate -> native task dispatch -OnEvent OpenAPPA task return gate -> persistence -> parent model -``` - -The profile rejects kagent `RequireApproval` and any non-OpenAPPA callback or plugin that can short-circuit `BeforeTool`. - -Use an OpenAPPA authority and remedy plan when a policy requires human approval. +| ADK point | OpenAPPA action | +|---|---| +| `BeforeToolCallbacks` | Snapshot final arguments and send `ToolCall` | +| `OnToolErrorCallbacks` | Record failure state and replace unsafe error text | +| `AfterToolCallbacks` | Send one terminal `ToolResult` for each released call | +| `BeforeModelCallbacks` | Confirm model-bound results already passed admission | +| `BeforeAgentCallbacks` | Select or bind the local child trajectory | +| `AfterModelCallbacks` | Gate deferred ADK task dispatch | +| `plugin.OnEventCallback` | Gate the synthesized task return before persistence | +| `plugin.OnUserMessageCallback` | Reject forged task responses | -The OpenAPPA callback MUST finish before kagent constructs or publishes tool-result content. +The OpenAPPA `OnToolError` plugin callback runs before kagent error logging. It stores failure state and returns `{"error":"tool execution failed"}`. -`OnToolError` MUST NOT send `ToolResult`. It stores failure state in the invocation context. +The first `AfterTool` callback sends the terminal outcome. `ReplaceOutput` or `Block` returns admitted replacement content and stops later callback observation. -Track one gate state by function-call ID: +The pending-call store uses the function-call ID as its key and synchronizes access. -| State | Meaning | `AfterTool` behavior | +| State | Meaning | `AfterTool` action | |---|---|---| -| `Released` | OpenAPPA returned `AllowCall` | Report exactly one terminal `ToolResult` | -| `Denied` | OpenAPPA denied the call | Send no `ToolResult`. Preserve the rendered denial | -| `SnapshotFailed` | Final arguments could not serialize | Send no `ToolResult`. Preserve the fail-closed response | -| `DeferredTask` | ADK emitted the task-mode no-op callbacks | Send no `ToolResult`. The task plugin owns both gates | -| Missing | Another callback bypassed OpenAPPA | Replace all output with a fail-closed refusal | - -For `Released`, `AfterTool` reads failure state and sends exactly one outcome. Tests MUST assert one outcome for each release. - -## Authoritative argument snapshot - -ADK passes one `map[string]any` value through `BeforeToolCallbacks` and then into `tool.Run`. - -Register the OpenAPPA callback last in the `BeforeTool` chain. No later callback can change the arguments after authorization. - -Callbacks MUST NOT retain or mutate `args` after they return. The pending-call store MUST synchronize access across concurrent ADK tool workers. - -Coverage validation MUST reject every other runner or agent `BeforeToolCallback` under this profile. - -Serialize that final ADK map once: - -```go -type PendingToolCall struct { - ID string - Tool string - Arguments json.RawMessage -} -``` - -Send those bytes to OpenAPPA as `RawValue`. Here, raw means unparsed at the adapter boundary, not original provider wire bytes. - -The tool receives the same unchanged ADK map after `AllowCall`. A serialization error refuses the call. - -If kagent changed that map after `AllowCall`, the tool would execute arguments that OpenAPPA never authorized. - -Duplicate object keys no longer exist at this boundary, and the tool cannot receive them. OpenAPPA validates the JSON value that the tool receives. - -Keep the serialized snapshot in memory until `AfterTool` reports the terminal outcome. kagent does not write this record to disk. - -`HookEvent::ToolResult` has no independent call ID. It carries the proposed call, so kagent needs the snapshot to identify which released call produced the outcome. - -Send the stored bytes with `ToolResult`. `appa-runtime` compares their canonical JSON arguments with the released call. +| `Released` | OpenAPPA returned `AllowCall` | Send one terminal outcome | +| `Denied` | OpenAPPA denied dispatch | Preserve the denial and send no outcome | +| `SnapshotFailed` | Arguments could not serialize | Refuse and send no outcome | +| `DeferredTask` | ADK emitted task-mode placeholder callbacks | Let the task plugin own both boundaries | +| Missing | Another callback bypassed OpenAPPA | Replace all output with a refusal | -Test final callback ordering, later mutation attempts, unsupported JSON values, numeric values, serialization failure, and outcome correlation. +## Bind the executed call -`appa-runtime` persists the released call. On Actor recovery, send `TurnEnd` before any new call to close an unknown outcome. +The OpenAPPA `BeforeTool` callback serializes the ADK argument map and stores the authorized bytes. -## Remedy control tool +Send those bytes as `RawValue`. Do not require model-provider transport bytes. -Register the local `appa-runtime` MCP endpoint with the root agent and every protected local child. +Immediately before execution, `OpenAPPATool.Run` serializes the final map again and requires byte equality with the authorized snapshot. -The reserved `execute_remedy_plan` tool follows this flow: +On equality, the wrapper recursively copies the JSON map and passes only that unshared copy to the underlying tool. -```text -model proposes execute_remedy_plan(offer_id) - -> BeforeTool identifies the exact reserved tool - -> appa-runtime vouches the acting trajectory - -> PassControl permits the MCP call - -> MCP endpoint consumes the vouch and judges the live offer -``` - -An offer from another trajectory MUST fail. An offer ID alone grants no authority. - -`Authorized` and `Substituted` outcomes return the exact tool name and arguments to propose next. - -The control tool MUST NOT execute that call itself. The next model-proposed call crosses normal enforcement with the returned JSON-semantic arguments. - -`Returned`, `Declined`, and `NoAnswer` remain normal control-tool results. - -## Dispatch serialization - -The current OpenAPPA host contract permits one open dispatch per trajectory. - -Wait for a trajectory permit in the pre-dispatch callback. Do not send a second `ToolCall` while the first dispatch remains open. - -Release the permit after `AfterTool` or terminal child return admission. - -Cancellation MUST report an indeterminate outcome before permit release. Separate child trajectories can run in parallel. - -## Local collaboration modes - -Add an optional mode to `AgentToolBinding`: - -```go -type AgentToolBinding struct { - Name string `json:"name"` - Description string `json:"description"` - TemplateRef AgentTemplateLocalReference `json:"templateRef"` - Isolation AgentToolIsolation `json:"isolation,omitempty"` - Mode AgentCollaborationMode `json:"mode,omitempty"` -} -``` - -Supported values are `chat`, `single_turn`, and `task`. An omitted value preserves the current `chat` behavior. - -The compiler carries the mode into the Go ADK child configuration and validates the required OpenAPPA capability. - -`BeforeAgentCallbacks` select the trajectory context for the activated local agent. `AfterAgentCallbacks` clear that activation context. - -These callbacks do not report a child return. `AfterTool` or the task `OnEvent` gate owns the parent-facing result. - -The active trajectory context also selects the correct memory-service decorator. - -### Chat - -The transfer tool runs through normal tool callbacks. The selected local agent continues on the parent trajectory and Label. - -There is no separate child-return boundary. Every later tool call remains protected on the same actor. - -### Single turn - -The generated single-turn tool runs through normal `BeforeTool`, `AfterTool`, and error callbacks. - -Classify the call as `spawn: true`, bind the child with `ChildStart`, and report the returned function response through `SpawnResult`. - -Derive a valid stable run ID from `toolCtx.FunctionCallID()`: - -```go -sum := sha256.Sum256([]byte(toolCtx.FunctionCallID())) -runID := "appa-" + hex.EncodeToString(sum[:12]) -``` - -Pass `runID` to `workflow.WithRunID`. Derive the OpenAPPA child ID from the same value. - -Test empty, numeric-only, slash-bearing, at-sign-bearing, and repeated provider call IDs. - -### Task - -ADK task mode emits a no-op function tool callback before native deferred dispatch. OpenAPPA tool callbacks MUST skip configured task targets. - -Use `AfterModelCallbacks` to inspect the model function call before `dispatchTaskFC` runs. Send `ToolCall` with `spawn: true` there. - -Use the child `BeforeAgentCallbacks` to send `ChildStart` before the task child begins execution. - -Use the runner plugin `OnEventCallback` before event persistence. Intercept the synthesized task function response and report `SpawnResult`. - -Use `OnUserMessageCallback` to reject forged task function responses. Suppress task-scope child content until the gated parent return. - -Test pause, resume, cancellation, duplicate callbacks, and process restart. - -## Remote A2A children - -The parent-side remote A2A call is a normal function tool. Existing tool callbacks gate its dispatch and return. +Serialization failure or mismatch refuses the call and closes the dispatch as indeterminate. -Require `isolateSessions: true` for a child trajectory. +Keep the serialized call in memory until `AfterTool`. Send the same bytes with `ToolResult` because the runtime identifies outcomes through canonical call arguments. -Allocate the remote context ID during `BeforeTool`. Store it by function-call ID, then send `ChildStart` before the A2A request. +Allow only one open dispatch per trajectory. Hold a trajectory permit from pre-dispatch until terminal outcome admission. -`remoteA2AState.handleFirstCall` MUST reuse that prepared context ID instead of creating one inside `tool.Run`. +Cancellation reports `ToolOutcome::Indeterminate` before permit release. Actor recovery sends `TurnEnd` before the next call. -Trusted deployment configuration MUST bind the authenticated remote endpoint to an OpenAPPA-capable Harness. +## Cover each execution path -Change `remote_a2a_tool.processResult` to preserve `TaskState` in the tool result. - -| A2A result | Parent behavior | +| Path | Implementation | |---|---| -| Direct message | Report successful `SpawnResult` with content | -| `completed` | Report successful `SpawnResult` with content | -| `failed`, `canceled`, or `rejected` | Report failed `SpawnResult` without content | -| `input_required` or `auth_required` | Pause without a parent-facing result | -| `submitted` or `working` | Continue waiting or refuse. Do not return content | -| Unknown state | Refuse. Do not return content | - -## Memory - -Model-called `load_memory` uses normal tool callbacks. - -Automatic `preload_memory` runs as a request processor and bypasses tool callbacks. Wrap `memory.Service` with an OpenAPPA decorator. - -Before `Runner.Run`, put the acting trajectory ID in the invocation context. The decorator reads that ID before `Search`. +| Normal function, MCP, skill, or memory tool | Existing tool callbacks | +| Local `chat` | Gate `transfer_to_agent` and keep the parent trajectory | +| Local `single_turn` | `BeforeTool` prepares spawn, `BeforeAgent` sends `ChildStart`, `AfterTool` sends `SpawnResult` | +| Local `task` | `AfterModel` prepares spawn, `BeforeAgent` sends `ChildStart`, `OnEvent` sends `SpawnResult` | +| Remote A2A | Allocate isolated context before dispatch, send `ChildStart`, preserve terminal `TaskState`, send `SpawnResult` | +| Automatic `preload_memory` | Wrap `memory.Service` and bind the acting trajectory through context | +| MCP App internal call | Route the request to the owning Actor with a short-lived trajectory capability | +| Registered long-running work | Add optional `BeforeBackgroundStart` and `OnBackgroundResult` parameters to kagent launch and resume functions | -The decorator gates search before execution and admits returned memory before `PreloadMemoryTool` adds it to the model request. +ADK task placeholder callbacks MUST emit no OpenAPPA event. The task plugin is the only owner of task dispatch and return. -Register background memory save, summarization, and embedding work as child work. Refuse it until the terminal return is observable. +Remote A2A rules: -## MCP and MCP Apps +- Require `isolateSessions=true` and bind the authenticated endpoint to an OpenAPPA-capable Harness. +- Allow content from a direct message or `completed` task only. +- Pause on `input_required` or `auth_required`. +- Report `failed`, `canceled`, and `rejected` without content. +- Continue waiting or fail on `submitted`, `working`, or unknown state. -HTTP, SSE, and stdio MCP tools become normal ADK function tools after discovery. Validate each dynamically resolved tool at dispatch. +The first implementation refuses streaming chunks, provider-native tools, unregistered background work, notification-only results, and uninstrumented non-Go frameworks. -Under the profile, MCP discovery and MCP App classification failures MUST fail closed. Do not use lazy fallback for unknown App metadata. +## Keep result publication ordered -Model-called MCP App tools use normal tool callbacks. Their full result passes post-execution admission before UI publication or model compaction. +OpenAPPA admission MUST finish before kagent constructs or publishes result content. -App-internal tools and resources bypass ADK. Route them back through the owning Actor instead of calling MCP directly from the controller. +Apply this barrier before model input, parent return, A2A streams, memory, MCP App UI, content logs, and background storage. -Extend the tool and resource request protos with `agent_instance_id` and `trajectory_capability`. +For MCP App internal calls, extend tool and resource requests with `agent_instance_id`, `request_id`, and an opaque capability. -The Actor mints an opaque random capability after the model-called App result passes admission. It stores the bound trajectory, actor, tool scope, and expiration. +The Actor mints a random 256-bit capability only after admission of the model-called App result. -The App UI returns that capability with each internal request. The controller resolves the AgentInstance and forwards the request to a new Actor gate RPC. +Store only its hash. Bind it to root trajectory, actor, caller identity, MCP server, allowed tool or resource scope, and expiry. -The Actor validates the capability, runs both OpenAPPA gates, executes the MCP call, and returns only admitted content. +The controller forwards the request to the owning Actor and performs no direct MCP execution. -Update these owners: +The Actor rejects missing, expired, replayed, used-request, caller, scope, server, actor, or trajectory mismatches before `ToolCall`. -| Owner | Change | -|---|---| -| tools and resources protobufs | Add AgentInstance and capability fields | -| App UI client | Preserve and return the capability | -| `go/core/internal/grpcserver` | Stop direct execution for protected App requests | -| A2A or Actor control service | Add the private App gate RPC | -| Go ADK extension | Mint, store, validate, and expire capabilities | - -Reject missing, expired, mismatched, or unknown capabilities before MCP dispatch. +After the Actor validates the capability, it runs both OpenAPPA gates and records the `request_id` against replay. -## Long-running work +## Preserve remedy control -The profile supports long-running work only after registration as child work. +Register the local `appa-runtime` MCP endpoint on the root and every protected local child. -Google ADK exposes no callback for work that outlives the initiating callback. Add two optional callbacks to each kagent-owned background launcher and resume path: - -| Proposed callback | Invocation point | -|---|---| -| `BeforeBackgroundStart` | Before kagent starts or resumes work and before any external effect | -| `OnBackgroundResult` | After terminal completion but before result publication or storage | +`execute_remedy_plan` MUST bind its offer to the acting trajectory. The control tool never executes a substituted call itself. -These are kagent function parameters, not user configuration and not a second middleware system. +The next model-proposed call crosses normal dispatch enforcement with the returned tool name and JSON arguments. -When the OpenAPPA profile is active, a launcher MUST refuse work if either callback is absent. +## Existing-cluster adoption -Registration creates a stable child ID before launch. Completion, failure, cancellation, and resume use that same ID. +Do not mutate an existing `AgentInstance` into the OpenAPPA profile. -The terminal result MUST pass `SpawnResult` before model, parent, memory, UI, or event delivery. +1. Run the two-phase Helm upgrade. +2. Create a new protected instance from the existing `AgentTemplate`. +3. Route only new root tasks and contexts to the new instance ID. +4. Keep existing task and context IDs pinned to the old instance until terminal or canceled. +5. Suspend the old instance. -Refuse streaming chunks, detached work, asynchronous MCP jobs, notifications, provider-native tools, and background memory until they use this lifecycle. +The protected instance starts a new ADK session, OpenAPPA database, and root trajectory family. Do not import the old model transcript. -## Coverage catalog +Rollback resumes the old instance and routes new roots back to it. -### Supported after implementation - -| Path | Required mechanism | -|---|---| -| Go ADK function tool | Final ADK argument snapshot and existing callbacks | -| HTTP, SSE, and stdio MCP tool | Dynamic validation and existing callbacks | -| Skill tool | Existing callbacks | -| Model-called memory tool | Existing callbacks | -| Automatic memory preload | Context-bound memory-service decorator | -| Local `chat` | Transfer gate on the parent trajectory | -| Local `single_turn` | Child binding and normal tool callbacks | -| Local `task` | `AfterModel` dispatch and `OnEvent` return gates | -| Remote A2A parent boundary | Normal callbacks, isolation, and preserved task state | -| Instrumented remote child | Compatible adapter and authenticated endpoint | -| Model-called MCP App tool | Normal callbacks before UI publication | -| `execute_remedy_plan` | Actor-bound vouch and local runtime MCP endpoint | -| MCP App internal call | Host gate and trajectory capability | -| Registered background work | Child identity and terminal return gate | -| Instrumented BYO agent | Compatible adapter and capability declaration | - -### Refused in the first implementation - -| Path | Missing mechanism | -|---|---| -| Streaming tool chunk | Chunk admission protocol | -| Detached or unregistered work | Stable child identity and terminal return | -| Unregistered asynchronous MCP job | Protected completion path | -| Notification-only result | Parent-facing return gate | -| Unregistered background memory | Protected completion path | -| Provider-native tool | Go ADK callbacks | -| Native `RequireApproval` | OpenAPPA authority and remedy integration | -| Additional short-circuit `BeforeTool` callback | Guaranteed OpenAPPA ordering and gate state | -| Uninstrumented remote child | Protected internal execution boundary | -| Uninstrumented Python ADK | Python adapter | -| Uninstrumented OpenAI Agents | Framework tool and handoff adapter | -| Uninstrumented LangGraph | Protected tool nodes and graph validation | -| Uninstrumented CrewAI | Protected tool and delegation layer | -| Uninstrumented BYO agent | Protected internal execution boundary | +Protected task and context IDs remain on the protected instance until terminal or canceled. Rollback never merges OpenAPPA logs. ## Upstream sequence -| Pull request | Generic kagent change | Proof | -|---|---|---| -| 1 | Snapshot final ADK arguments and call identity | Tool receives the value that policy authorized | -| 2 | Add extension-aware Go ADK builders | Existing call sites compile and keep behavior | -| 3 | Add local collaboration mode to AgentTemplate tools | All three modes compile and validate | -| 4 | Add task dispatch and return plugin seams | Deferred task cannot bypass either gate | -| 5 | Preserve remote A2A task state | Only valid terminal outcomes return content | -| 6 | Add result publication barriers | Unadmitted result reaches no consumer before callbacks finish | -| 7 | Add Harness callback and dynamic tool validation | Unknown or short-circuiting paths fail before execution | -| 8 | Add background-work lifecycle | Every registered result has one terminal gate | - -The OpenAPPA Harness profile, policy compiler, Go ADK extension, and Rust adapter can remain integration-specific. - -## Existing-cluster migration - -`AgentInstance` pins one `Harness` and prepared revision. The implementation MUST NOT mutate a running instance into the OpenAPPA profile. - -### Control-plane upgrade - -1. Apply the policy `ConfigMap` and values with `openappa.harness.enabled=false`. -2. Apply the generated CRD bundle with optional `KagentHarness.OpenAPPA` and collaboration-mode fields. -3. Run the first atomic Helm upgrade with the Harness disabled. -4. Wait for every controller and the `kagent-controller` service endpoint to report ready. -5. Run the second atomic Helm upgrade with `openappa.harness.enabled=true`. - -Existing Harnesses omit the new field. Their compilation, builders, callbacks, and Actor images remain unchanged. - -### Compatibility preparation - -For each existing `AgentTemplate` selected for adoption: - -1. Label the `AgentTemplate` so the chart-managed Harness admits it. -2. Let the compiler inventory tools, providers, child modes, MCP Apps, memory, and background paths. -3. Refuse preparation when any enabled path lacks required coverage. -4. Wait for the Harness-specific ready condition and Substrate `ActorTemplate` before creating an instance. - -The same `AgentTemplate` can serve both the old and OpenAPPA Harnesses during rollout. - -### State boundary - -Create a new `AgentInstance`, Actor, `/data/openappa/appa.db`, and root trajectory family. - -Do not copy an unprotected transcript into the OpenAPPA event log. The first release has no trustworthy facts for activity that happened before enforcement. - -Persistent external records re-enter through policy-covered tools. Their tool contracts assign the resulting Labels. - -If the application uses an external ADK session store, start a new protected session ID. Do not attach an old model context to the new root trajectory. - -### Canary and cutover - -The cluster operator owns routing, drain, retention, rollback, and destructive retirement. - -1. Send synthetic and internal canary requests to the protected `AgentInstance`. -2. Verify runtime health, policy hash, callback coverage, denial, replacement, child return, and database persistence. -3. Route new root A2A tasks to the protected instance. -4. Keep continuations for old task and context IDs pinned to the old instance. -5. Drain until old work reaches a terminal state, or apply the documented timeout and cancellation policy. -6. Suspend the old instance, export its task and event records, and verify a Substrate snapshot containing `/data`. -7. Retain the export and snapshot for at least 30 days before any deletion. - -Destructive deletion requires separate cluster-operator approval after the retention period. - -### Rollback - -Keep the old `AgentInstance` unchanged until cutover verification ends. - -Rollback routes new root tasks to the old instance. Existing protected task and context IDs remain pinned to the protected instance until terminal or canceled. - -After drain, suspend the protected instance, export its task and event records, and verify a Substrate snapshot containing `/data`. +| PR | Generic kagent change | +|---|---| +| 1 | Add extension-aware Go ADK builders | +| 2 | Add local collaboration mode to `AgentToolBinding` | +| 3 | Add task dispatch and return plugin seams | +| 4 | Preserve remote A2A `TaskState` and preallocate context IDs | +| 5 | Add result-publication barriers and dynamic path validation | +| 6 | Add background-work lifecycle callbacks | -Retain that export and snapshot for at least 30 days. Rollback does not merge, rewrite, or discard the OpenAPPA event log. +The OpenAPPA Harness profile, policy compiler, Go extension, and Rust adapter remain integration-specific. -## Validation matrix +## Verification -| Area | Required tests | +| Area | Required coverage | |---|---| -| Compatibility | Existing Harnesses, builders, CRDs, callback order, approval, and concurrency stay unchanged without the profile | -| Tool calls | Allow, deny, final argument snapshot, mutation attempt, serialization error, outcome, timeout, cancellation, and replacement | -| Callback bypass | Native approval, runner short-circuit, agent short-circuit, missing gate state, and safe refusal | -| Remedies | Actor binding, stale offer, authorized call, substituted bytes, returned value, decline, and no answer | -| Concurrency | Same-actor serialization, child parallelism, callback panic, and permit cleanup | -| Local agents | Chat transfer, single turn, task, pause, resume, forged response, cancellation, and restart | -| Remote A2A | Isolation, direct message, every task state, capability mismatch, and terminal replacement | -| Memory | Load, preload, save, context loss, background work, and replacement before model delivery | -| MCP Apps | Discovery failure, model tool, app-internal call, resource read, expired capability, and UI publication | -| Runtime | Startup health, process exit, database persistence, revision change, and fail-closed recovery | -| Egress | Every policy endpoint and explicit LLM URL enter the allowlist and revision digest | -| Migration | CRD ordering, dual revisions, task affinity, canary, cutover, drain, fresh session, suspend, export, snapshot, rollback, and retention | -| Browser | Proposal diagrams at desktop, mobile, light, and dark modes | - -The implementation is complete only when each enabled execution path reaches its required pre-dispatch and result boundary. +| Compatibility | Existing Harnesses, builders, CRDs, callbacks, approval, and concurrency remain unchanged without the profile | +| Tool calls | Allow, deny, argument snapshot, mutation attempt, serialization error, failure, cancellation, and replacement | +| Callback safety | Short-circuit rejection, native approval, error logger order, missing state, and post-authorization mutation | +| Concurrency | Same-trajectory serialization, child parallelism, cancellation, panic, and permit cleanup | +| Local children | `chat`, `single_turn`, `task`, forged response, pause, resume, cancellation, and restart | +| Remote A2A | Isolation, endpoint binding, direct message, every task state, replacement, and mismatch | +| Memory and MCP Apps | Preload context, result barrier, capability expiry, resource read, and UI publication | +| MCP App capability | Caller, actor, trajectory, server, scope, expiry, request replay, and capability mismatch | +| Policy and discovery | Policy digest, egress allowlist, explicit LLM URL, MCP discovery failure, and dynamic tool validation | +| Remedies | Offer trajectory binding, stale offer, authorization, substitution, returned value, decline, and no answer | +| Migration | CRD order, two-phase Helm upgrade, ready revision, task affinity, fresh session, replacement, and rollback | +| Runtime | Health, process exit, database persistence, policy revision, and recovery | + +The implementation is complete when every enabled path reaches its required dispatch and result boundary.