Skip to content

alpha17 doc changes - #644

Merged
Rachael-Graham merged 2 commits into
mainfrom
alpha17
Oct 23, 2026
Merged

Rachael-Graham merged 2 commits into
mainfrom
alpha17

Conversation

@Rachael-Graham

Copy link
Copy Markdown
Contributor

Document agent runtime authentication and memory ownership, built-in tools and their security boundaries, and the expanded kagent uninstall behavior. Also clarify token passthrough requirements and use fully qualified Service URLs in MCP examples.

Generated ref docs and version bumps will come in the automated workflow PR.

Document agent runtime authentication and memory ownership, built-in tools and their security boundaries, and the expanded `kagent uninstall` behavior. Also clarify token passthrough requirements and use fully qualified Service URLs in MCP examples.

Signed-off-by: Rachael Graham <rachael.graham@solo.io>
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Docs preview

Link Points at
Branch preview The newest push to this branch. Updates in place.
Commit preview 94b319a only. Frozen.

Both are uploaded Worker versions and serve no production traffic.


### Agent runtime calls

An agent's runtime calls the controller to store task state and memories, and to read its own Agent and Session. These calls arrive on the same port as user calls, `8083`. However, the controller authenticates agent runtime calls separately from the user authentication mode. Even in `insecure` mode, a runtime call needs a valid token.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking (security accuracy): I think "Even in insecure mode, a runtime call needs a valid token" overstates the boundary. The alpha17 architecture doc (docs/architecture/a2a-gateway.md, "Runtime authority") says:

Substrate replaces Authorization only when the actor sends the header; a request without it follows the configured user authentication policy. TaskStore and actor-only Memory methods always require an Actor JWT.

So a call from the sandbox that omits Authorization is treated as a user call, and in insecure mode it is admitted. Only TaskStore and the actor-only Memory methods always demand the JWT. A reader could take this sentence as a reason to relax the port 8083 warning above. Suggested wording:

Suggested change
An agent's runtime calls the controller to store task state and memories, and to read its own Agent and Session. These calls arrive on the same port as user calls, `8083`. However, the controller authenticates agent runtime calls separately from the user authentication mode. Even in `insecure` mode, a runtime call needs a valid token.
An agent's runtime calls the controller to store task state and memories, and to read its own Agent and Session. These calls arrive on the same port as user calls, `8083`. However, the controller authenticates agent runtime calls separately from the user authentication mode. The task state and memory calls that only an agent makes always need a valid token, even in `insecure` mode. A call that does not send an `Authorization` header is treated as a user call, and follows the user authentication mode.

2. The Agent Substrate egress gateway replaces the placeholder with a JSON Web Token (JWT) that identifies the {{< gloss "Actor" >}}Actor{{< /gloss >}}. The token is valid for five minutes, and its audience is `kagent-runtime`. The gateway adds the token only to calls to the controller. Actor snapshots do not store the token.
3. The controller verifies the token's signature, issuer, audience, and lifetime, and allows 30 seconds of clock skew. The controller then binds the token to the Actor's current Session. The controller rejects a token that fails verification, and does not fall back to user authentication.

An authenticated Actor can read its own Agent and use its own task state and memories. The Actor can also read its own Session and send messages to that Session. The Actor cannot create or delete a Session, use a share, reach any other Session, or act with its owner's permissions. That restriction includes other Sessions of the same agent and the same owner.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: The default actor policy in the alpha17 architecture doc also allows the Actor to update its existing Session ("reading, sending to, or updating its existing Session"). Consider "read, update, and send messages to its own Session" so the list matches.

Should fix (maybe, your call): The same doc says controller and runtime images must be upgraded together, because snapshots from older runtime images still send the removed identity header and must be recreated. If that is not on the upgrade page yet, it could go here or in the release notes.

| ------ | ------------- |
| `content` | The text that the agent saved. Retrieval returns it to a later conversation. |
| `agent_name` | The agent that owns the memory, written as `<namespace>__NS__<agent>` with every hyphen replaced by an underscore. The name identifies one {{< gloss "Agent" >}}Agent{{< /gloss >}}, so the same AgentTemplate paired into two Agents owns two separate sets of memories. |
| `agent_name` | The agent that owns the memory, written as `<namespace>/<agent>`, such as `kagent/my-first-agent`. The name identifies one {{< gloss "Agent" >}}Agent{{< /gloss >}}, so the same AgentTemplate paired into two Agents owns two separate sets of memories. A memory that an earlier release saved keeps its original key. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should fix: "A memory that an earlier release saved keeps its original key" is true, but it leaves out the consequence a reader cares about: the agent no longer recalls those memories.

In alpha16 the runtime wrote kagent__NS__my_first_agent... (go/adk/cmd/main.go). In alpha17, SearchAgentMemory matches only kagent/my-first-agent and the hyphens-to-underscores spelling kagent/my_first_agent (go/core/internal/database/memory.go), so neither matches the __NS__ rows. The architecture doc says it directly: "existing memories keyed by runtime display names are not migrated."

Suggested:

Suggested change
| `agent_name` | The agent that owns the memory, written as `<namespace>/<agent>`, such as `kagent/my-first-agent`. The name identifies one {{< gloss "Agent" >}}Agent{{< /gloss >}}, so the same AgentTemplate paired into two Agents owns two separate sets of memories. A memory that an earlier release saved keeps its original key. |
| `agent_name` | The agent that owns the memory, written as `<namespace>/<agent>`, such as `kagent/my-first-agent`. The name identifies one {{< gloss "Agent" >}}Agent{{< /gloss >}}, so the same AgentTemplate paired into two Agents owns two separate sets of memories. A memory that an earlier release saved keeps its original key, such as `kagent__NS__my_first_agent`, and kagent does not migrate it. The agent no longer retrieves that memory. To list or delete it, pass the original key. |

I checked that List and Delete pass a user-supplied key straight to the query, so the old key should work there. I did not run it.

The SQL query only reads the table. To list or clear memories, call the `MemoryService` that the kagent controller serves over gRPC.

No CLI command manages memories, so these examples call the service with [grpcurl](https://github.com/fullstorydev/grpcurl). Both calls take the `agent_name` exactly as the memory table stores it. Be sure to copy the value out of the table rather than assembling it by hand, because an incorrect name fails silently.
No CLI command manages memories, so these examples call the service with [grpcurl](https://github.com/fullstorydev/grpcurl). Each call acts on the memories of the user that the call authenticates as, and does not take a user ID. For how the controller resolves that user, see [Controller authentication modes]({{< link path="about/substrate-runtime/identity#controller-authentication-modes" >}}).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should fix: This change is correct (managementScope uses principal.User.ID and ignores the request's user ID). But the steps below send no identity, so in the default insecure mode every call runs as admin@kagent.dev. A reader whose memories belong to another user_id (the SQL query just above shows that column) gets an empty list. An empty Delete also succeeds silently.

Consider saying how to act as another user in insecure mode, for example by adding -H 'X-User-Id: <user_id>' to the grpcurl calls, with the value copied from the user_id column. Otherwise, warn that the calls only reach the authenticated user's memories.

> [!NOTE]
> The built-in server is installed only when the `kagent-tools.enabled` Helm value is `true`, which is the default. If the command does not return any resources, either re-install with that value enabled, or use your own server as described in [Bind your own MCP server](#bind-your-own-mcp-server).

<!-- REVIEW (docs): at v1.0.0-alpha17 this guide fails at the first tool call with "lookup kagent-tools.kagent ... no such host". The chart registers kagent-tool-server with a short Service name that Substrate 0.6 sandboxes cannot resolve (kagent-dev/kagent#3315, fix in #3323). Re-run the guide once the fix ships, and update the example output in step 1 to the fully qualified URL. -->

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking (or decide on purpose): This comment says the get-started guide fails at alpha17. The comment is hidden, so a reader following the guide hits no such host with no explanation. kagent-dev/kagent#3315 is still open, and #3323 has not merged.

I would rather either ship a visible workaround now, or hold this guide's alpha17 publish until #3323 ships, than publish a known-broken get-started page with a hidden note. Untested workaround idea: an alert in step 1 that patches the built-in server to the fully qualified name.

kubectl patch remotemcpserver kagent-tool-server -n kagent --type=merge \
  -p '{"spec":{"url":"http://kagent-tools.kagent.svc.cluster.local:8084/mcp"}}'

The chart owns that resource, so a later helm upgrade reverts the patch. That needs saying too. Also, this repo is public, so I would remove the REVIEW (docs) note before merge either way.

```bash
helm uninstall substrate-podcert -n podcertificate-controller-system
```
Uninstall kagent with the kagent CLI, and then remove the namespace that the command keeps. The command waits for each Helm release to finish its removal, so the uninstall takes about a minute.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should fix (contradiction): This intro says to uninstall "and then remove the namespace that the command keeps," but step 2 is marked Optional. One of the two is wrong. Since runUninstall deliberately keeps the namespace because --namespace can name a namespace that other workloads share, I think Optional is right. Also, "about a minute" is a claim I could not verify. Each release runs helm uninstall --wait, and deleteServed has a 5m timeout, so it can take longer.

Suggested change
Uninstall kagent with the kagent CLI, and then remove the namespace that the command keeps. The command waits for each Helm release to finish its removal, so the uninstall takes about a minute.
Uninstall kagent with the kagent CLI. The command waits for each Helm release to finish its removal before it continues.

Remove the kagent release before the CRDs release, because deleting the definitions first strands the controller.

1. Uninstall the kagent chart.
2. Confirm that your kubeconfig points at the cluster that you want to uninstall from. The command prints the name of the current context.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit (ambiguous): On this page "The command" has meant kagent uninstall up to this point, so "The command prints the name of the current context" reads as if kagent uninstall prints it. It does not, because runUninstall never prints the context. Since kagent uninstall has no confirmation prompt, this check matters.

Suggested change
2. Confirm that your kubeconfig points at the cluster that you want to uninstall from. The command prints the name of the current context.
2. Confirm that your kubeconfig points at the cluster that you want to uninstall from. The `kagent uninstall` command does not ask for confirmation, and it acts on the current context.

podidentity.podcert.ate.dev:identity:primary-bundle \
postgres.podcert.ate.dev:identity:primary-bundle \
servicedns.podcert.ate.dev:identity:primary-bundle
kubectl delete namespace kagent

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Step 1 explains --namespace for a non-default namespace, but this step and the example output hardcode kagent. Consider "Delete the namespace that kagent ran in, such as kagent," so the custom-namespace reader is not left guessing.

| `load_skill_resource` | With a skill | Reads one file inside a skill directory, such as a reference document. |
| `read_file` | Built-in | Reads a file, with line numbers. |
| `write_file` | Built-in | Writes a file, and creates its parent directories. |
| `edit_file` | Built-in | Replaces an exact string in a file that the agent has already read. The edit fails when the string occurs more than once, unless the agent asks to replace every occurrence. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit (carried over from the old text): "in a file that the agent has already read" suggests the tool enforces a read first. It does not. editFileContent (go/adk/pkg/tools/files.go) only reads the file and replaces the string. Read-first is an instruction in the tool description to the model.

Suggested change
| `edit_file` | Built-in | Replaces an exact string in a file that the agent has already read. The edit fails when the string occurs more than once, unless the agent asks to replace every occurrence. |
| `edit_file` | Built-in | Replaces an exact string in a file. The edit fails when the string occurs more than once, unless the agent asks to replace every occurrence. |

Otherwise I checked the rest of this table, the builtinTools values, the 60s/600s/64 KiB bash limits, and /data/workspace against go/adk/pkg/tools/builtin.go at alpha17. They all match.

Signed-off-by: Rachael Graham <rachael.graham@solo.io>
@Rachael-Graham
Rachael-Graham merged commit abe3777 into main Oct 23, 2026
4 checks passed
@Rachael-Graham
Rachael-Graham deleted the alpha17 branch October 23, 2026 16:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants