From 1ea84110a9454d53df2d6dbff65e82dc4ee9996a Mon Sep 17 00:00:00 2001 From: Rachael Graham Date: Thu, 15 Oct 2026 11:50:43 -0500 Subject: [PATCH 1/4] init Signed-off-by: Rachael Graham --- .../content/kagent/1.x/examples/a2a-agents.md | 25 ++++++++++------ .../content/kagent/1.x/examples/a2a-byo.md | 4 +-- .../kagent/1.x/examples/agent-substrate.md | 13 ++++---- .../kagent/1.x/examples/agents-via-mcp.md | 30 +++++++------------ .../content/kagent/1.x/examples/skills.md | 8 ++--- .../content/kagent/1.x/operations/debug.md | 4 +-- docs-site/data/glossary.yaml | 2 +- 7 files changed, 41 insertions(+), 45 deletions(-) diff --git a/docs-site/content/kagent/1.x/examples/a2a-agents.md b/docs-site/content/kagent/1.x/examples/a2a-agents.md index 4d77db2f..b033e1ce 100644 --- a/docs-site/content/kagent/1.x/examples/a2a-agents.md +++ b/docs-site/content/kagent/1.x/examples/a2a-agents.md @@ -5,7 +5,9 @@ weight: 30 author: kagent.dev --- -Every {{< gloss "Agent" >}}Agent{{< /gloss >}} is reachable over the {{< gloss "A2A" >}}A2A{{< /gloss >}} (Agent-to-Agent) protocol through the kagent controller. {{< reuse "kagent-docs/snippets/name-product.md" >}} uses the A2A protocol for its own agent traffic, rather than an extra interface beside it. The CLI, the [MCP server]({{< link path="examples/agents-via-mcp" >}}), and any client you write all take the same path. +Every {{< gloss "Agent" >}}Agent{{< /gloss >}} is reachable over the {{< gloss "A2A" >}}A2A{{< /gloss >}} (Agent-to-Agent) protocol through the kagent controller. This example calls that service directly to read an Agent's card, send it a message, stream a reply, and answer the agent when it pauses for a person. + +In {{< reuse "kagent-docs/snippets/name-product.md" >}}, agent traffic runs over A2A. The CLI and the [MCP server]({{< link path="examples/agents-via-mcp" >}}) reach an Agent through the same service. This example uses [grpcurl](https://github.com/fullstorydev/grpcurl) to show the requests and replies directly. Real callers use an A2A client library rather than assembling requests by hand. @@ -20,19 +22,18 @@ A gRPC caller names the Agent in the standard A2A `tenant` field, as `/` | | HTTP JSON-RPC | `POST /agents/{namespace}/{name}`, with the card at `GET /agents/{namespace}/{name}/.well-known/agent-card.json` | -An HTTP request that also sets a tenant must set one that matches its URL. There is no deployment-wide agent card, because the gateway serves many agents. +An HTTP request that also sets the `tenant` field must set a value that matches its URL. There is no deployment-wide agent card, because the controller serves many Agents. -A caller addresses the Agent rather than one conversation. The message's `contextId` selects the conversation. Omitting both a `contextId` and a task ID starts a new {{< gloss "Session" >}}Session{{< /gloss >}}, and repeating a `contextId` continues that Session. +Both forms replace the `/api/a2a///` URL paths that kagent 0.x served over HTTP. -> [!NOTE] -> `tenant` and the `/agents/{namespace}/{name}` path replace the `/api/a2a///` URL paths that kagent 0.x served over HTTP. +A caller addresses the Agent rather than one conversation. The message's `contextId` selects the conversation. Omitting both a `contextId` and a task ID starts a new {{< gloss "Session" >}}Session{{< /gloss >}}, and repeating a `contextId` continues that Session. > [!WARNING] -> The open source build does not authenticate this port. Any caller that can reach it can invoke any Agent, so do not expose port `8083` outside the cluster. For what the open source build does guarantee, see [Identity]({{< link path="about/substrate-runtime/identity" >}}). +> A default installation sets `config.server.auth.mode` to `insecure`. In this mode any caller that can reach the port can invoke any Agent. Do not expose port `8083` outside the cluster while the controller runs this way. To take the caller's identity from a claim that a fronting proxy sets instead, set `config.server.auth.mode` to `trusted-proxy` and name the claim in `config.server.auth.userIdClaim`. For the wider identity model, see [Identity]({{< link path="about/substrate-runtime/identity" >}}). ### A2A methods -This example calls each method in the following table except the three `TaskPushNotificationConfig` calls, which manage a task's callbacks and which the kagent CLI wraps. The service defines more, including `ListTasks`. `GetTaskPushNotificationConfig` reads a single callback by ID. +The following table covers the methods that this example uses. The kagent CLI wraps the three `TaskPushNotificationConfig` calls, which manage a task's callbacks. `lf.a2a.v1.A2AService` defines further methods. For the ones that this example leaves out, see the [A2A protocol](https://a2a-protocol.org) specification. | Method | What it does | | ------ | ------------ | @@ -66,10 +67,16 @@ An A2A client typically starts by reading the agent card, which tells it what th "name": "my_first_agent", "description": "My first kagent agent", "supportedInterfaces": [ + { + "url": "http://kagent-controller.kagent.svc:8083/agents/kagent/my-first-agent", + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0" + }, { "url": "http://kagent-controller.kagent.svc:8083", "protocolBinding": "GRPC", - "protocolVersion": "1.0" + "protocolVersion": "1.0", + "tenant": "kagent/my-first-agent" } ], "version": "v1", @@ -91,7 +98,7 @@ An A2A client typically starts by reading the agent card, which tells it what th 2. Read the card for what a caller acts on. * Both `name` and `description` come from the {{< gloss "AgentTemplate" >}}AgentTemplate{{< /gloss >}}. `name` replaces hyphens with underscores, and `description` is `spec.description` verbatim, so a caller sees the description you wrote. - * The `supportedInterfaces` URL is the controller's in-cluster address rather than the Actor's, because a caller reaches the agent through the controller. The card advertises JSON-RPC first, then gRPC. The gRPC interface carries the `tenant` value to send. + * Each `supportedInterfaces` URL is the controller's in-cluster address rather than the Actor's, because a caller reaches the agent through the controller. The card advertises JSON-RPC first, then gRPC. The JSON-RPC URL identifies the Agent in its path. The gRPC entry sets the `tenant` value instead. * The `capabilities.pushNotifications` value advertises task callbacks. Each callback belongs to one task, not to the whole Session. * The `capabilities.extensions` list advertises human-in-the-loop support, which a client opts into per call. diff --git a/docs-site/content/kagent/1.x/examples/a2a-byo.md b/docs-site/content/kagent/1.x/examples/a2a-byo.md index 544f5e93..3d76899a 100644 --- a/docs-site/content/kagent/1.x/examples/a2a-byo.md +++ b/docs-site/content/kagent/1.x/examples/a2a-byo.md @@ -90,7 +90,7 @@ A `byo` Harness carries two fields that the other runtimes do not need: an empty ``` > [!NOTE] - > This Harness does not set a port, because `byoa2a/main.go` reads `runtime.port` from the compiled configuration and falls back to `80`. An image that neither reads `runtime.port` nor defaults to `80` listens on the wrong port and still reports `READY`. For that trap, see [Bring your own agent]({{< link path="agents/bring-your-own-agent#the-a2a-contract" >}}). + > This Harness does not set a port, because `byoa2a/main.go` reads `runtime.port` from the compiled configuration and falls back to `80`. When an image ignores `runtime.port` and does not default to `80`, it listens on the wrong port and still reports `READY`. For this failure, see [Bring your own agent]({{< link path="agents/bring-your-own-agent#the-a2a-contract" >}}). 2. Create the AgentTemplate and an Agent that pairs it with the Harness. `description` is the only field that this template needs. ```yaml @@ -164,7 +164,7 @@ At this point, the BYO agent behaves in the same way as any other. The {{< gloss BYO agent response ``` - That string is hardcoded, so the reply itself proves nothing. Its path proves the contract: kagent compiled a revision, Agent Substrate started a sandboxed {{< gloss "Actor" >}}Actor{{< /gloss >}} from your image, the controller's A2A gateway routed the message to it, and your executor answered. + That string is hardcoded, so the reply itself proves nothing. Its path proves the contract: kagent compiled a revision, Agent Substrate started a sandboxed {{< gloss "Actor" >}}Actor{{< /gloss >}} from your image, the controller routed the message to it, and your executor answered. 4. Send another message to the same Session. The reply does not change, but the message reaches the same Actor. Agent Substrate suspended that Actor after the first turn and resumed it for this message. For more information about the Actor lifecycle, see [Suspend and resume]({{< link path="about/substrate-runtime/suspend-and-resume#suspension-between-turns" >}}). ```bash diff --git a/docs-site/content/kagent/1.x/examples/agent-substrate.md b/docs-site/content/kagent/1.x/examples/agent-substrate.md index e10b0415..4e1fd90a 100644 --- a/docs-site/content/kagent/1.x/examples/agent-substrate.md +++ b/docs-site/content/kagent/1.x/examples/agent-substrate.md @@ -96,7 +96,7 @@ The Session stays `READY` throughout all steps. A suspended agent remains listed Each suspend writes a {{< gloss "Snapshot" >}}snapshot{{< /gloss >}}, and Agent Substrate is free to collect that snapshot once a newer one supersedes it. A {{< gloss "Checkpoint" >}}checkpoint{{< /gloss >}} pins a snapshot so that you can come back to it. -1. Create a checkpoint. The checkpoint records the snapshot that it pinned and how far the {{< gloss "Transcript" >}}transcript{{< /gloss >}} had advanced. `expectedHeadTaskId` names the terminal task to pin, so the call fails rather than pinning the wrong turn when the conversation has advanced. The `requestId` field is a required idempotency key of 1 to 128 characters, so reusing it returns the same checkpoint rather than creating a second one. +1. Create a checkpoint. A checkpoint keeps the conversation through the turn that you name. `expectedHeadTaskId` names the terminal task to pin. When the conversation has advanced past that task, the call fails rather than pinning the wrong turn. `requestId` is a required idempotency key of 1 to 128 characters. Repeating a `requestId` returns the checkpoint that the earlier call created rather than pinning a second one. ```bash grpcurl -plaintext \ -d '{"sessionId":"'"$SESSION_ID"'","expectedHeadTaskId":"'"$TASK_ID"'","requestId":"'"$(uuidgen)"'"}' \ @@ -119,7 +119,10 @@ Each suspend writes a {{< gloss "Snapshot" >}}snapshot{{< /gloss >}}, and Agent ``` > [!NOTE] - > A checkpoint captures a turn boundary, so the named task must be terminal and its snapshot must be written. A newer turn that has already started fails the call with `conversation advanced beyond the expected task`, and a snapshot that is still being written fails it with `snapshot for the expected task is not ready`. The second is safe to retry with the same values; the first needs a newer task ID. + > A checkpoint captures a turn boundary. The task that you name must be terminal, and its snapshot must be written. + > + > * When a newer turn has already started, the call fails with `conversation advanced beyond the expected task`. Name the current task ID and send the request again. + > * When the snapshot for the named task is still being written, the call fails with `snapshot for the expected task is not ready`. Send the same request again. 2. Save the checkpoint's `id` so that you can delete it during cleanup. ```bash @@ -135,12 +138,6 @@ Each suspend writes a {{< gloss "Snapshot" >}}snapshot{{< /gloss >}}, and Agent Underneath, the checkpoint attaches a {{< gloss "Tag" >}}Tag{{< /gloss >}} named `checkpoint-` to the snapshot, and Agent Substrate does not collect a snapshot while a tag names it. You can see the tag by running `kubectl ate get tags --atespace kagent`. -## Forking is temporarily unavailable - -Forking creates a second Session from a checkpoint only when the pinned snapshot contains durable data without process memory. In the current kagent release, suspended Actors use `Full` snapshots so that their process memory survives suspend and resume. A `ForkSession` request for these checkpoints is rejected with `FAILED_PRECONDITION`, because a restored Actor would resume processes that still hold the source Session's identifiers. - -Checkpoint creation is still useful without forking. A checkpoint pins the snapshot for retention, gives the turn boundary a stable record, and keeps the checkpoint visible in the API while the original Session continues. - ## Clean up 1. Delete the checkpoint. Deleting removes the Tag and releases the pin, and Agent Substrate can collect the snapshot whenever no tag names it. diff --git a/docs-site/content/kagent/1.x/examples/agents-via-mcp.md b/docs-site/content/kagent/1.x/examples/agents-via-mcp.md index 629da85d..5eab3ddc 100644 --- a/docs-site/content/kagent/1.x/examples/agents-via-mcp.md +++ b/docs-site/content/kagent/1.x/examples/agents-via-mcp.md @@ -15,15 +15,12 @@ You can do this work from an MCP client such as Claude Code or Cursor, which bui The MCP server is part of the controller's HTTP port rather than a separate deployment, so a default installation already serves it at `/mcp` on port `8083`. -- **Transport**: Streamable HTTP only. Server-Sent Events (SSE) as a standalone transport and stdio are both unsupported. A client that offers a transport choice must use Streamable HTTP. +- **Transport**: Clients connect over Streamable HTTP. Standalone Server-Sent Events (SSE) and stdio are unsupported. - **Sessions**: The handler is stateless, so each request stands alone and a client does not need to establish a session first. - **Extensions**: The server advertises the `io.modelcontextprotocol/tasks` extension, which changes how invocations behave. For more information, see [Invoke without waiting](#invoke-without-waiting). -> [!NOTE] -> A Session **is** one conversation, so sending a second message to the same `session_id` continues where the first left off. The reply's `context_id` is the Session's own ID. To hold two independent conversations with one {{< gloss "Agent" >}}Agent{{< /gloss >}}, create two Sessions. - > [!WARNING] -> The open source build does not authenticate this endpoint. Every request is accepted, and the caller's identity is read from an `X-User-Id` header that the caller sets itself, defaulting to `admin@kagent.dev`. Because the endpoint can invoke agents, create checkpoints, and create Sessions, do not expose port `8083` outside the cluster. For the wider identity model and what the open source build does guarantee, see [Identity]({{< link path="about/substrate-runtime/identity" >}}). +> A default installation sets `config.server.auth.mode` to `insecure`. In this mode the endpoint accepts every request and reads the caller's identity from an `X-User-Id` header that the caller sets itself, defaulting to `admin@kagent.dev`. The endpoint can invoke agents, create checkpoints, and create Sessions. Do not expose port `8083` outside the cluster while the controller runs this way. To take the caller's identity from a claim that a fronting proxy sets instead, set `config.server.auth.mode` to `trusted-proxy` and name the claim in `config.server.auth.userIdClaim`. For the wider identity model, see [Identity]({{< link path="about/substrate-runtime/identity" >}}). ## Before you begin @@ -93,7 +90,7 @@ Claude Code and Cursor read the tool schemas and build each call, so you work in > List my kagent sessions. ``` - An unexpectedly empty list is typically due to creator scoping rather than a missing agent. The tool returns only the Sessions that the calling identity created, and has no option to widen that scope. The kagent command line interface and this endpoint both default to `admin@kagent.dev`, so they see each other's Sessions. If you created the Session with `kagent --user-id `, send a matching `X-User-Id` header from the client. + The tool returns only the Sessions that the calling identity created, and cannot widen that scope. An empty list therefore means that this identity has not created a Session. The kagent command line interface and this endpoint both default to `admin@kagent.dev`, so they see each other's Sessions. When you created the Session as another user with `kagent --user-id `, send a matching `X-User-Id` header from the client. 2. Ask the agent a question by naming the Session you want to use. The client calls `invoke_session` and fills in the arguments from the tool schema. ```console @@ -101,7 +98,9 @@ Claude Code and Cursor read the tool schemas and build each call, so you work in Answer with just the number. ``` -3. Ask a follow-up that depends on the previous answer, such as `Multiply that by 10.`, against the same Session. The word "that" resolves only when the earlier turns are in context, because the transcript belongs to the Session rather than to the client. +3. Ask a follow-up that depends on the previous answer, such as `Multiply that by 10.`, against the same Session. The word "that" points back to the earlier answer. The model can follow the reference because the transcript belongs to the Session rather than to the client. + + A Session **is** one conversation, so a second message to the same `session_id` continues where the first left off. The reply's `context_id` is the Session's own ID. To hold two independent conversations with one {{< gloss "Agent" >}}Agent{{< /gloss >}}, create two Sessions. > [!NOTE] > Nothing here configures whether an invocation blocks or returns a task to poll, because a client declares its own capabilities on each request. A reply means your client did not declare the `io.modelcontextprotocol/tasks` extension and the call waited for the agent to finish. A task ID means it did, and the client polls in the background so that a long agent run never holds a request open. @@ -128,8 +127,6 @@ You can checkpoint a Session before letting an agent try something risky. The cl 2. Keep the checkpoint ID if you need to inspect or delete the checkpoint later. -Forking a checkpoint is temporarily unavailable. The current release stores process memory in suspended Actor snapshots, so `fork_session` returns `FAILED_PRECONDITION` instead of creating a second Session from a checkpoint. - You can now safely [clean up these resources](#clean-up). ## Use agents from curl @@ -197,7 +194,7 @@ With `curl` you build each request yourself, so every field is visible: the tool } ``` -4. Send the same request again with `Multiply that by 10.` as the `message`. The word "that" resolves only when the earlier turns are in context. Note that `context_id` is unchanged while `task_id` is new, so the second turn joined the first conversation instead of starting its own. +4. Send the same request again with `Multiply that by 10.` as the `message`. The word "that" points back to the earlier answer. The model can follow the reference because the transcript belongs to the Session. Note that `context_id` is unchanged while `task_id` is new, so the second turn joined the first conversation instead of starting its own. ```bash curl -s -X POST http://localhost:8083/mcp \ -H 'Content-Type: application/json' \ @@ -489,27 +486,22 @@ The checkpoint tools pin a Session's state. These tools do not need the tasks ex } ``` -Forking a checkpoint is temporarily unavailable. The current release stores process memory in suspended Actor snapshots, so `fork_session` returns `FAILED_PRECONDITION` instead of creating a second Session from a checkpoint. For what a checkpoint captures and why forking is rejected, see [Suspend and resume]({{< link path="about/substrate-runtime/suspend-and-resume" >}}) and the [Agent Substrate example]({{< link path="examples/agent-substrate" >}}). +For what a checkpoint captures, see [Suspend and resume]({{< link path="about/substrate-runtime/suspend-and-resume" >}}) and the [Agent Substrate example]({{< link path="examples/agent-substrate" >}}). You can now safely [clean up these resources](#clean-up). ## Clean up -1. Port-forward the controller's gRPC port, and leave the command running. `CheckpointService` listens there rather than on the HTTP port that serves MCP. - ```bash - kubectl port-forward -n kagent svc/kagent-controller 8083:8083 - ``` - -2. Delete the checkpoint that you created. +1. Delete the checkpoint that you created. `CheckpointService` listens on the same port that serves MCP, so the port-forward from [Connect a client](#connect-a-client) already reaches the service. ```bash grpcurl -plaintext \ -d '{"checkpointId":""}' \ localhost:8083 kagent.api.v1alpha1.CheckpointService/DeleteCheckpoint ``` -3. Remove the server entry from your client. In Claude Code, run `claude mcp remove kagent`. In Cursor, delete the `kagent` entry from your MCP settings. +2. Remove the server entry from your client. In Claude Code, run `claude mcp remove kagent`. In Cursor, delete the `kagent` entry from your MCP settings. -4. Stop both port-forwards with `Ctrl+C`. +3. Stop the port-forward with `Ctrl+C`. ## MCP tool reference diff --git a/docs-site/content/kagent/1.x/examples/skills.md b/docs-site/content/kagent/1.x/examples/skills.md index 609ad628..c19ace54 100644 --- a/docs-site/content/kagent/1.x/examples/skills.md +++ b/docs-site/content/kagent/1.x/examples/skills.md @@ -32,7 +32,7 @@ On a `kagent` Harness, attaching a skill adds seven tools to the agent, whether | `edit_file` | Replaces an exact string in a file that the agent has already read. | | `bash` | Runs a shell command in the session directory `/tmp/kagent//`. Commands time out after 30 seconds. | -Two further tools are available but off by default. Set the `config.runtimeDefaults.fileSearchTools` Helm value to `true` to add `list_files`, which lists the contents of a directory, and `grep_file`, which searches file contents for a pattern and can recurse. The setting applies to every agent in the installation, because a Harness has no per-agent equivalent. Both are confined to the same skills and session roots as `read_file`. They stay opt-in because they give an agent broad visibility of the filesystem, and they are how an agent keeps that visibility when a deployment disables `bash`. +Two further tools are available but off by default. Set the `config.runtimeDefaults.fileSearchTools` Helm value to `true` to add `list_files`, which lists the contents of a directory, and `grep_file`, which searches file contents for a pattern and can recurse. The setting applies to every agent in the installation, because a Harness has no per-agent equivalent. `list_files` and `grep_file` reach only the skills and session roots that `read_file` reads. They stay opt-in because they give an agent broad visibility of the filesystem, and they are how an agent keeps that visibility when a deployment disables `bash`. Attaching a skill also changes what the agent is told. The runtime appends the name and description of every attached skill to the model request, along with an instruction to call `load_skill` before acting on one, so a skill reaches the model even before any tool is called. @@ -90,7 +90,7 @@ A skill is a directory whose root holds a `SKILL.md` file. Everything else in th EOF ``` - The `description` decides whether the skill is ever used. The agent sees every attached skill's name and description, and chooses among them the same way it chooses any other tool, so state plainly when the skill applies. The instructions in the body are only read after the agent calls `load_skill`. + The model reads the `description` and decides whether to load the skill. The model sees the name and description of every attached skill, and chooses among them in the same way that it chooses any other tool. State plainly when the skill applies. The instructions in the body are only read after the agent calls `load_skill`. 3. Add the script that the instructions call. The script runs in the agent's runtime image, so it uses `bash` rather than Python. ```bash @@ -267,7 +267,7 @@ A source is immutable, so changing a skill is a two-step change: publish new con ## Bundle the skill in a plugin -While a standalone source carries one skill, a {{< gloss "Plugin" >}}plugin{{< /gloss >}} contains multiple skills. An AgentTemplate attaches the plugin once and lists the skills that it wants. Use a plugin when you ship a set of skills together, or when you want the same artifact to contribute [MCP servers]({{< link path="skills-and-mcp/plugins#mcp-servers" >}}) as well. +While a standalone source contains one skill, a {{< gloss "Plugin" >}}plugin{{< /gloss >}} contains multiple skills. An AgentTemplate attaches the plugin once and lists the skills that it wants. Use a plugin when you ship a set of skills together, or when you want the same artifact to contribute [MCP servers]({{< link path="skills-and-mcp/plugins#mcp-servers" >}}) as well. 1. Restructure the directory so that each skill sits under `skills/`, and add the manifest that makes it a plugin. ```bash @@ -374,7 +374,7 @@ A skill that kagent cannot fetch stops the agent from starting at all, rather th ## Clean up -1. Delete the Sessions that you created. Deleting the Agent does not remove them. Skip the second command if you did not complete the plugin section. +1. Delete the Sessions that you created. Deleting the Agent does not remove them. If you did not complete the plugin section, skip the `$PLUGIN_SESSION_ID` command. ```bash kagent agent session delete $SESSION_ID kagent agent session delete $PLUGIN_SESSION_ID diff --git a/docs-site/content/kagent/1.x/operations/debug.md b/docs-site/content/kagent/1.x/operations/debug.md index 17355d9d..ec70bc8d 100644 --- a/docs-site/content/kagent/1.x/operations/debug.md +++ b/docs-site/content/kagent/1.x/operations/debug.md @@ -161,14 +161,14 @@ Work through the causes in this order. An edit that appears to do nothing has two possible causes, and the Agent's conditions distinguish them. -The first cause is a revision that never prepared. Compare the two revision fields on the Agent that references the template. When `desiredRevision` and `latestSuccessfulRevision` differ, the edit produces a new revision that is not ready, and the conditions say why. +One cause is a revision that never prepared. Compare the two revision fields on the Agent that references the template. When `desiredRevision` and `latestSuccessfulRevision` differ, the edit produces a new revision that is not ready, and the conditions say why. ```bash kubectl get agent -n kagent \ -o jsonpath='{"desired="}{.status.desiredRevision}{" latestSuccessful="}{.status.latestSuccessfulRevision}{"\n"}' ``` -The second cause is deliberate. A {{< gloss "Session" >}}Session{{< /gloss >}} runs the revision that it was created from for its whole life, so editing an AgentTemplate never changes a conversation that already exists. Create a new Session to pick up the edit. +The other cause is deliberate. A {{< gloss "Session" >}}Session{{< /gloss >}} runs the revision that it was created from for its whole life, so editing an AgentTemplate never changes a conversation that already exists. Create a new Session to pick up the edit. ## Collect logs diff --git a/docs-site/data/glossary.yaml b/docs-site/data/glossary.yaml index f9b22b25..14c96bd1 100644 --- a/docs-site/data/glossary.yaml +++ b/docs-site/data/glossary.yaml @@ -115,7 +115,7 @@ Skill: link: "/docs/kagent/1.x/skills-and-mcp/skills/" Plugin: - short: "A bundle that an AgentTemplate attaches with spec.plugins. A plugin carries skills and can also declare Model Context Protocol servers. Plugins follow the Agent Plugins specification." + short: "A bundle that an AgentTemplate attaches with spec.plugins. A plugin contains skills and can also declare Model Context Protocol servers. Plugins follow the Agent Plugins specification." link: "/docs/kagent/1.x/skills-and-mcp/plugins/" Tool binding: From d4495d885dc5d95559c8f0513887fa2713235ec5 Mon Sep 17 00:00:00 2001 From: Rachael Graham Date: Thu, 15 Oct 2026 14:08:24 -0500 Subject: [PATCH 2/4] wording tweaks Signed-off-by: Rachael Graham --- docs-site/content/kagent/1.x/examples/a2a-agents.md | 2 +- docs-site/content/kagent/1.x/examples/a2a-byo.md | 2 +- docs-site/content/kagent/1.x/examples/agent-substrate.md | 7 ++++--- docs-site/content/kagent/1.x/examples/agents-via-mcp.md | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs-site/content/kagent/1.x/examples/a2a-agents.md b/docs-site/content/kagent/1.x/examples/a2a-agents.md index b033e1ce..7fba6391 100644 --- a/docs-site/content/kagent/1.x/examples/a2a-agents.md +++ b/docs-site/content/kagent/1.x/examples/a2a-agents.md @@ -33,7 +33,7 @@ A caller addresses the Agent rather than one conversation. The message's `contex ### A2A methods -The following table covers the methods that this example uses. The kagent CLI wraps the three `TaskPushNotificationConfig` calls, which manage a task's callbacks. `lf.a2a.v1.A2AService` defines further methods. For the ones that this example leaves out, see the [A2A protocol](https://a2a-protocol.org) specification. +The following table covers the methods that this example uses. The kagent CLI wraps the three `TaskPushNotificationConfig` calls, which manage a task's callbacks. For the further methods that `lf.a2a.v1.A2AService` defines, see the [A2A protocol](https://a2a-protocol.org) specification. | Method | What it does | | ------ | ------------ | diff --git a/docs-site/content/kagent/1.x/examples/a2a-byo.md b/docs-site/content/kagent/1.x/examples/a2a-byo.md index 3d76899a..62be4b12 100644 --- a/docs-site/content/kagent/1.x/examples/a2a-byo.md +++ b/docs-site/content/kagent/1.x/examples/a2a-byo.md @@ -90,7 +90,7 @@ A `byo` Harness carries two fields that the other runtimes do not need: an empty ``` > [!NOTE] - > This Harness does not set a port, because `byoa2a/main.go` reads `runtime.port` from the compiled configuration and falls back to `80`. When an image ignores `runtime.port` and does not default to `80`, it listens on the wrong port and still reports `READY`. For this failure, see [Bring your own agent]({{< link path="agents/bring-your-own-agent#the-a2a-contract" >}}). + > This Harness does not set a port, because `byoa2a/main.go` reads `runtime.port` from the compiled configuration and falls back to `80`. An image that binds another port still reports `READY`. The mismatch surfaces only on the first invoke. For the requirements that a BYO image must meet, and the error that the invoke returns, see [Bring your own agent]({{< link path="agents/bring-your-own-agent#the-a2a-contract" >}}). 2. Create the AgentTemplate and an Agent that pairs it with the Harness. `description` is the only field that this template needs. ```yaml diff --git a/docs-site/content/kagent/1.x/examples/agent-substrate.md b/docs-site/content/kagent/1.x/examples/agent-substrate.md index 4e1fd90a..d486465a 100644 --- a/docs-site/content/kagent/1.x/examples/agent-substrate.md +++ b/docs-site/content/kagent/1.x/examples/agent-substrate.md @@ -96,7 +96,7 @@ The Session stays `READY` throughout all steps. A suspended agent remains listed Each suspend writes a {{< gloss "Snapshot" >}}snapshot{{< /gloss >}}, and Agent Substrate is free to collect that snapshot once a newer one supersedes it. A {{< gloss "Checkpoint" >}}checkpoint{{< /gloss >}} pins a snapshot so that you can come back to it. -1. Create a checkpoint. A checkpoint keeps the conversation through the turn that you name. `expectedHeadTaskId` names the terminal task to pin. When the conversation has advanced past that task, the call fails rather than pinning the wrong turn. `requestId` is a required idempotency key of 1 to 128 characters. Repeating a `requestId` returns the checkpoint that the earlier call created rather than pinning a second one. +1. Create a checkpoint. A checkpoint keeps the conversation through the turn that you name. `expectedHeadTaskId` names the finished turn to pin. When the conversation has advanced past that task, the call fails rather than pinning the wrong turn. `requestId` is a required idempotency key of 1 to 128 characters. Repeating a `requestId` returns the checkpoint that the earlier call created rather than pinning a second one. ```bash grpcurl -plaintext \ -d '{"sessionId":"'"$SESSION_ID"'","expectedHeadTaskId":"'"$TASK_ID"'","requestId":"'"$(uuidgen)"'"}' \ @@ -119,10 +119,11 @@ Each suspend writes a {{< gloss "Snapshot" >}}snapshot{{< /gloss >}}, and Agent ``` > [!NOTE] - > A checkpoint captures a turn boundary. The task that you name must be terminal, and its snapshot must be written. + > A checkpoint captures a turn boundary. The call succeeds only once every turn on the Session has reached a final state, meaning `TASK_STATE_COMPLETED`, `TASK_STATE_FAILED`, `TASK_STATE_CANCELED`, or `TASK_STATE_REJECTED`. > + > * When a turn on the Session is still running, the call fails with `checkpoint requires all tasks to be terminal`. Wait for the turn to finish, then send the request again. > * When a newer turn has already started, the call fails with `conversation advanced beyond the expected task`. Name the current task ID and send the request again. - > * When the snapshot for the named task is still being written, the call fails with `snapshot for the expected task is not ready`. Send the same request again. + > * When Agent Substrate is still writing the snapshot, the call fails with `snapshot for the expected task is not ready`. Send the same request again. 2. Save the checkpoint's `id` so that you can delete it during cleanup. ```bash diff --git a/docs-site/content/kagent/1.x/examples/agents-via-mcp.md b/docs-site/content/kagent/1.x/examples/agents-via-mcp.md index 5eab3ddc..dabf1567 100644 --- a/docs-site/content/kagent/1.x/examples/agents-via-mcp.md +++ b/docs-site/content/kagent/1.x/examples/agents-via-mcp.md @@ -100,7 +100,7 @@ Claude Code and Cursor read the tool schemas and build each call, so you work in 3. Ask a follow-up that depends on the previous answer, such as `Multiply that by 10.`, against the same Session. The word "that" points back to the earlier answer. The model can follow the reference because the transcript belongs to the Session rather than to the client. - A Session **is** one conversation, so a second message to the same `session_id` continues where the first left off. The reply's `context_id` is the Session's own ID. To hold two independent conversations with one {{< gloss "Agent" >}}Agent{{< /gloss >}}, create two Sessions. + Because a Session _is_ one conversation, a second message to the same `session_id` continues where the first left off. The reply's `context_id` is the Session's own ID. To hold two independent conversations with one {{< gloss "Agent" >}}Agent{{< /gloss >}}, create two Sessions. > [!NOTE] > Nothing here configures whether an invocation blocks or returns a task to poll, because a client declares its own capabilities on each request. A reply means your client did not declare the `io.modelcontextprotocol/tasks` extension and the call waited for the agent to finish. A task ID means it did, and the client polls in the background so that a long agent run never holds a request open. @@ -405,7 +405,7 @@ For the pause types, the approval model, and what the agent receives, see [Human The checkpoint tools pin a Session's state. These tools do not need the tasks extension, so they do not carry `_meta`. -1. Create a checkpoint. `expected_head_task_id` names the terminal task to pin. The call fails rather than pinning the wrong turn when the conversation has advanced past it. Use the `$TASK_ID` that an earlier invocation returned. The `request_id` is an idempotency key, so repeating the call with the same value returns the checkpoint that the first call created rather than pinning a second one. +1. Create a checkpoint. `expected_head_task_id` names the finished turn to pin. When the conversation has advanced past that turn, the call fails rather than pinning the wrong one. Use the `$TASK_ID` that an earlier invocation returned. `request_id` is an idempotency key. Repeating the call with the same value returns the checkpoint that the earlier call created rather than pinning a second one. ```bash curl -s -X POST http://localhost:8083/mcp \ -H 'Content-Type: application/json' \ From aeb03ab53fb1f184ad5d3ea018007e06e7fdb3c3 Mon Sep 17 00:00:00 2001 From: Rachael Graham Date: Thu, 15 Oct 2026 14:30:35 -0500 Subject: [PATCH 3/4] forking Signed-off-by: Rachael Graham --- docs-site/content/kagent/1.x/about/core-concepts.md | 3 +-- .../1.x/about/substrate-runtime/suspend-and-resume.md | 2 +- docs-site/content/kagent/1.x/examples/a2a-agents.md | 4 ++-- docs-site/content/kagent/1.x/examples/agents-via-mcp.md | 6 +++--- docs-site/content/kagent/1.x/observability/launch-ui.md | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/docs-site/content/kagent/1.x/about/core-concepts.md b/docs-site/content/kagent/1.x/about/core-concepts.md index 9bc86f4d..dbefb339 100644 --- a/docs-site/content/kagent/1.x/about/core-concepts.md +++ b/docs-site/content/kagent/1.x/about/core-concepts.md @@ -150,8 +150,7 @@ The Harness, AgentTemplate, and Agent resources describe what an agent is. The A An Actor occupies a slot on a Worker, which is a pre-started pod in a WorkerPool. A Worker hosts many Actors at once, and an Actor that goes idle is suspended to a snapshot so that its slot returns to the pool. A pool therefore serves far more Sessions than it has Workers. Agent Substrate also pauses an Actor to a node-local snapshot, and resumes an Actor onto whichever Worker has room. For these operations and what each snapshot keeps, see [Suspend and resume]({{< link path="about/substrate-runtime/suspend-and-resume" >}}). -> [!NOTE] -> Forking a {{< gloss "Checkpoint" >}}checkpoint{{< /gloss >}} into a second Session is unavailable in this release. kagent captures process memory in every snapshot, and a restored copy would hold the source Session's identifiers, so `ForkSession` rejects the request. Forking returns once Agent Substrate separates the pause and suspend snapshot scopes again. +Starting a second Session from a {{< gloss "Checkpoint" >}}checkpoint{{< /gloss >}} is currently unavailable. For why `ForkSession` rejects the request, see [Checkpoints]({{< link path="about/substrate-runtime/suspend-and-resume#checkpoints" >}}). ### Identity diff --git a/docs-site/content/kagent/1.x/about/substrate-runtime/suspend-and-resume.md b/docs-site/content/kagent/1.x/about/substrate-runtime/suspend-and-resume.md index ada2c8e5..dddc8ae9 100644 --- a/docs-site/content/kagent/1.x/about/substrate-runtime/suspend-and-resume.md +++ b/docs-site/content/kagent/1.x/about/substrate-runtime/suspend-and-resume.md @@ -85,7 +85,7 @@ Because kagent suspends at every turn boundary, every message to a Session pays ## Checkpoints -A **checkpoint** marks one point in a conversation and keeps it. You can create a checkpoint when a Session reaches a state worth returning to, such as a long investigation that is only partway through. Starting a second Session from that point, which is known as a {{< gloss "Fork" >}}fork{{< /gloss >}}, is unavailable in this release. kagent captures process memory in every snapshot, so `ForkSession` rejects a checkpoint with `FAILED_PRECONDITION` rather than restore an Actor that still holds the source Session's identifiers. To create and inspect a checkpoint, follow the [Agent Substrate example]({{< link path="examples/agent-substrate#forking-is-temporarily-unavailable" >}}). +A **checkpoint** marks one point in a conversation and keeps it. You can create a checkpoint when a Session reaches a state worth returning to, such as a long investigation that is only partway through. Starting a second Session from that point, which is known as a {{< gloss "Fork" >}}fork{{< /gloss >}}, is unavailable in this release. kagent captures process memory in every snapshot, so `ForkSession` rejects a checkpoint with `FAILED_PRECONDITION` rather than restore an Actor that still holds the source Session's identifiers. To create and inspect a checkpoint, follow the [Agent Substrate example]({{< link path="examples/agent-substrate" >}}). Create a checkpoint only after a turn finishes. The request names the task that the turn ended on, and it fails when a newer turn has already started or while that turn's snapshot is still being written. diff --git a/docs-site/content/kagent/1.x/examples/a2a-agents.md b/docs-site/content/kagent/1.x/examples/a2a-agents.md index 7fba6391..e4d14b48 100644 --- a/docs-site/content/kagent/1.x/examples/a2a-agents.md +++ b/docs-site/content/kagent/1.x/examples/a2a-agents.md @@ -29,11 +29,11 @@ Both forms replace the `/api/a2a///` URL paths that kagen A caller addresses the Agent rather than one conversation. The message's `contextId` selects the conversation. Omitting both a `contextId` and a task ID starts a new {{< gloss "Session" >}}Session{{< /gloss >}}, and repeating a `contextId` continues that Session. > [!WARNING] -> A default installation sets `config.server.auth.mode` to `insecure`. In this mode any caller that can reach the port can invoke any Agent. Do not expose port `8083` outside the cluster while the controller runs this way. To take the caller's identity from a claim that a fronting proxy sets instead, set `config.server.auth.mode` to `trusted-proxy` and name the claim in `config.server.auth.userIdClaim`. For the wider identity model, see [Identity]({{< link path="about/substrate-runtime/identity" >}}). +> A default installation sets `config.server.auth.mode` to `insecure`. In this mode any caller that can reach the port can invoke any Agent. Do not expose port `8083` outside the cluster. For the wider identity model, see [Identity]({{< link path="about/substrate-runtime/identity" >}}). ### A2A methods -The following table covers the methods that this example uses. The kagent CLI wraps the three `TaskPushNotificationConfig` calls, which manage a task's callbacks. For the further methods that `lf.a2a.v1.A2AService` defines, see the [A2A protocol](https://a2a-protocol.org) specification. +The following table covers the methods that this example uses. The `TaskPushNotificationConfig` calls manage a task's callbacks, and this example reaches them through the kagent CLI. For the further methods that `lf.a2a.v1.A2AService` defines, see the [A2A protocol specification](https://a2a-protocol.org/latest/specification/#31-core-operations). | Method | What it does | | ------ | ------------ | diff --git a/docs-site/content/kagent/1.x/examples/agents-via-mcp.md b/docs-site/content/kagent/1.x/examples/agents-via-mcp.md index dabf1567..e37fd650 100644 --- a/docs-site/content/kagent/1.x/examples/agents-via-mcp.md +++ b/docs-site/content/kagent/1.x/examples/agents-via-mcp.md @@ -20,7 +20,7 @@ The MCP server is part of the controller's HTTP port rather than a separate depl - **Extensions**: The server advertises the `io.modelcontextprotocol/tasks` extension, which changes how invocations behave. For more information, see [Invoke without waiting](#invoke-without-waiting). > [!WARNING] -> A default installation sets `config.server.auth.mode` to `insecure`. In this mode the endpoint accepts every request and reads the caller's identity from an `X-User-Id` header that the caller sets itself, defaulting to `admin@kagent.dev`. The endpoint can invoke agents, create checkpoints, and create Sessions. Do not expose port `8083` outside the cluster while the controller runs this way. To take the caller's identity from a claim that a fronting proxy sets instead, set `config.server.auth.mode` to `trusted-proxy` and name the claim in `config.server.auth.userIdClaim`. For the wider identity model, see [Identity]({{< link path="about/substrate-runtime/identity" >}}). +> A default installation sets `config.server.auth.mode` to `insecure`. In this mode the endpoint accepts every request and reads the caller's identity from an `X-User-Id` header that the caller sets itself, defaulting to `admin@kagent.dev`. The endpoint can invoke agents, create checkpoints, and create Sessions. Do not expose port `8083` outside the cluster. For the wider identity model, see [Identity]({{< link path="about/substrate-runtime/identity" >}}). ## Before you begin @@ -486,7 +486,7 @@ The checkpoint tools pin a Session's state. These tools do not need the tasks ex } ``` -For what a checkpoint captures, see [Suspend and resume]({{< link path="about/substrate-runtime/suspend-and-resume" >}}) and the [Agent Substrate example]({{< link path="examples/agent-substrate" >}}). +For what a checkpoint captures, see [Suspend and resume]({{< link path="about/substrate-runtime/suspend-and-resume#checkpoints" >}}) and the [Agent Substrate example]({{< link path="examples/agent-substrate" >}}). You can now safely [clean up these resources](#clean-up). @@ -513,7 +513,7 @@ The server exposes five Session tools. Two cover discovery and conversation. Thr | `invoke_session` | `session_id`, `message` | Sends a message and returns the agent's reply. Takes `message_id` for idempotency. | | `create_session_checkpoint` | `session_id`, `expected_head_task_id` | Pins the conversation at a turn boundary. The call fails if the conversation has advanced past that task. Takes `request_id` for idempotency. | | `list_session_checkpoints` | `session_id` | Lists that Session's checkpoints. Takes `page_size` and `page_token`. | -| `fork_session` | `checkpoint_id` | Temporarily rejects checkpoints with `FAILED_PRECONDITION` while suspended snapshots include process memory. Takes `request_id` for idempotency. | +| `fork_session` | `checkpoint_id` | Starts a second Session from a checkpoint. Currently rejects every checkpoint with `FAILED_PRECONDITION`, for the reason given in [Checkpoints]({{< link path="about/substrate-runtime/suspend-and-resume#checkpoints" >}}). Takes `request_id` for idempotency. | ## Next steps diff --git a/docs-site/content/kagent/1.x/observability/launch-ui.md b/docs-site/content/kagent/1.x/observability/launch-ui.md index a1656170..37c32e9c 100644 --- a/docs-site/content/kagent/1.x/observability/launch-ui.md +++ b/docs-site/content/kagent/1.x/observability/launch-ui.md @@ -100,7 +100,7 @@ To take a checkpoint, select the save icon beside the message box. kagent pins t A checkpoint appears in the transcript as a mark carrying the snapshot name and three controls. Select the mark to open the record behind it. The record holds the checkpoint's ID, turn, state, and age. -- **Fork** is temporarily unavailable in the current release. The API rejects checkpoint forks with `FAILED_PRECONDITION` while suspended Actor snapshots include process memory. For more information, see [Forking is temporarily unavailable]({{< link path="examples/agent-substrate#forking-is-temporarily-unavailable" >}}). +- **Fork** starts a second Session from the checkpoint. Currently, the API rejects every fork with `FAILED_PRECONDITION`, for the reason given in [Checkpoints]({{< link path="about/substrate-runtime/suspend-and-resume#checkpoints" >}}). - **Rename** replaces the snapshot name that kagent generates on creation from the Session ID and the turn the checkpoint pins. Clearing the name restores that generated default. - **Delete** removes the checkpoint and releases the snapshot it pinned. Agent Substrate is then free to collect that snapshot. From 4413c3237bdde93009d627c9ba2235fd4a13485f Mon Sep 17 00:00:00 2001 From: Rachael Graham Date: Thu, 15 Oct 2026 14:40:06 -0500 Subject: [PATCH 4/4] re-add forks Signed-off-by: Rachael Graham --- .../1.x/about/architecture/agent-substrate.md | 2 +- .../content/kagent/1.x/about/core-concepts.md | 2 +- .../substrate-runtime/suspend-and-resume.md | 2 +- .../kagent/1.x/examples/agent-substrate.md | 59 ++++++++++++++++++- .../kagent/1.x/examples/agents-via-mcp.md | 4 +- .../kagent/1.x/observability/launch-ui.md | 2 +- .../operations/operational-considerations.md | 2 +- docs-site/data/glossary.yaml | 2 +- 8 files changed, 65 insertions(+), 10 deletions(-) diff --git a/docs-site/content/kagent/1.x/about/architecture/agent-substrate.md b/docs-site/content/kagent/1.x/about/architecture/agent-substrate.md index cdd2976e..010c7a3d 100644 --- a/docs-site/content/kagent/1.x/about/architecture/agent-substrate.md +++ b/docs-site/content/kagent/1.x/about/architecture/agent-substrate.md @@ -78,7 +78,7 @@ A **WorkerPool** keeps **Workers** running and ready, and a Worker hosts the **A The diagram forks at this snapshot, because a snapshot serves two purposes. - **Resume** restores the same Actor onto **any Worker in the pool with room**, which is not necessarily the Worker that it ran on before. Because the snapshot captures the Actor's full state, the conversation continues from where it left off. Every idle agent takes this path. -- A **{{< gloss "Tag" >}}Tag{{< /gloss >}}** pins this snapshot so that Agent Substrate does not collect it. Seeding a **New Actor** from a tag is unavailable in this release. Resuming an existing Actor never goes through a tag. +- A **{{< gloss "Tag" >}}Tag{{< /gloss >}}** pins this snapshot so that Agent Substrate does not collect it. To fork a Session from a checkpoint, kagent seeds a **New Actor** from that tag. Resuming an existing Actor never goes through a tag. A tag gives a snapshot a stable name, so callers do not track Agent Substrate's internal snapshot identity. The tag names one snapshot permanently, and only its visibility scope can change afterward. A tag is also a retention pin, so Agent Substrate does not delete a snapshot while a tag still names it. diff --git a/docs-site/content/kagent/1.x/about/core-concepts.md b/docs-site/content/kagent/1.x/about/core-concepts.md index dbefb339..6f62ac60 100644 --- a/docs-site/content/kagent/1.x/about/core-concepts.md +++ b/docs-site/content/kagent/1.x/about/core-concepts.md @@ -150,7 +150,7 @@ The Harness, AgentTemplate, and Agent resources describe what an agent is. The A An Actor occupies a slot on a Worker, which is a pre-started pod in a WorkerPool. A Worker hosts many Actors at once, and an Actor that goes idle is suspended to a snapshot so that its slot returns to the pool. A pool therefore serves far more Sessions than it has Workers. Agent Substrate also pauses an Actor to a node-local snapshot, and resumes an Actor onto whichever Worker has room. For these operations and what each snapshot keeps, see [Suspend and resume]({{< link path="about/substrate-runtime/suspend-and-resume" >}}). -Starting a second Session from a {{< gloss "Checkpoint" >}}checkpoint{{< /gloss >}} is currently unavailable. For why `ForkSession` rejects the request, see [Checkpoints]({{< link path="about/substrate-runtime/suspend-and-resume#checkpoints" >}}). +Starting a second Session from a {{< gloss "Checkpoint" >}}checkpoint{{< /gloss >}} is known as a fork. For what a fork replays and where it diverges, see [Checkpoints]({{< link path="about/substrate-runtime/suspend-and-resume#checkpoints" >}}). ### Identity diff --git a/docs-site/content/kagent/1.x/about/substrate-runtime/suspend-and-resume.md b/docs-site/content/kagent/1.x/about/substrate-runtime/suspend-and-resume.md index dddc8ae9..6e6116b9 100644 --- a/docs-site/content/kagent/1.x/about/substrate-runtime/suspend-and-resume.md +++ b/docs-site/content/kagent/1.x/about/substrate-runtime/suspend-and-resume.md @@ -85,7 +85,7 @@ Because kagent suspends at every turn boundary, every message to a Session pays ## Checkpoints -A **checkpoint** marks one point in a conversation and keeps it. You can create a checkpoint when a Session reaches a state worth returning to, such as a long investigation that is only partway through. Starting a second Session from that point, which is known as a {{< gloss "Fork" >}}fork{{< /gloss >}}, is unavailable in this release. kagent captures process memory in every snapshot, so `ForkSession` rejects a checkpoint with `FAILED_PRECONDITION` rather than restore an Actor that still holds the source Session's identifiers. To create and inspect a checkpoint, follow the [Agent Substrate example]({{< link path="examples/agent-substrate" >}}). +A **checkpoint** marks one point in a conversation and keeps it. You can create a checkpoint when a Session reaches a state worth returning to, such as a long investigation that is only partway through. Starting a second Session from that point is known as a {{< gloss "Fork" >}}fork{{< /gloss >}}. The fork replays the conversation through the pinned turn and then continues on its own, with its own conversation ID and its own task IDs. A fork needs the pinned snapshot to keep durable data without process memory. Agent Substrate writes that kind of snapshot on every suspend, so any checkpoint that reaches `CHECKPOINT_STATE_READY` can be forked. To create a checkpoint and fork it, follow the [Agent Substrate example]({{< link path="examples/agent-substrate" >}}). Create a checkpoint only after a turn finishes. The request names the task that the turn ended on, and it fails when a newer turn has already started or while that turn's snapshot is still being written. diff --git a/docs-site/content/kagent/1.x/examples/agent-substrate.md b/docs-site/content/kagent/1.x/examples/agent-substrate.md index d486465a..58cb8280 100644 --- a/docs-site/content/kagent/1.x/examples/agent-substrate.md +++ b/docs-site/content/kagent/1.x/examples/agent-substrate.md @@ -1,6 +1,6 @@ --- title: Agent Substrate -description: Watch an agent's Actor suspend between turns and pin its state with a checkpoint. +description: Watch an agent's Actor suspend between turns, pin its state with a checkpoint, and fork that checkpoint into a second Session. weight: 10 author: kagent.dev --- @@ -139,9 +139,64 @@ Each suspend writes a {{< gloss "Snapshot" >}}snapshot{{< /gloss >}}, and Agent Underneath, the checkpoint attaches a {{< gloss "Tag" >}}Tag{{< /gloss >}} named `checkpoint-` to the snapshot, and Agent Substrate does not collect a snapshot while a tag names it. You can see the tag by running `kubectl ate get tags --atespace kagent`. +## Fork the checkpoint into a second Session + +A fork starts a new Session from a checkpoint. The new Session replays the conversation through the pinned turn and then continues on its own. You can therefore take a conversation in a second direction without disturbing the first. + +1. Fork the checkpoint. `requestId` is an idempotency key of 1 to 128 characters. Repeating a `requestId` returns the Session that the earlier call created rather than forking a second one. + ```bash + grpcurl -plaintext \ + -d '{"checkpointId":"'"$CHECKPOINT_ID"'","requestId":"'"$(uuidgen)"'"}' \ + localhost:8083 kagent.api.v1alpha1.CheckpointService/ForkSession + ``` + + The fork takes the checkpoint's name, and runs the revision that the source Session was created on. Example output: + ```json + { + "session": { + "id": "0198c3f1-2d7a-7c48-9e03-4b1f6a8d2c95", + "creator": "admin@kagent.dev", + "agent": { + "namespace": "kagent", + "name": "my-first-agent" + }, + "state": "RUNTIME_STATE_READY", + "createdAt": "2026-08-31T15:19:02Z", + "name": "my-first-agent turn 4", + "contextId": "0198c3f1-2d7a-7c48-9e03-4b1f6a8d2c95" + } + } + ``` + + > [!NOTE] + > The fork receives its own conversation ID and its own task IDs, so `contextId` is the fork's own ID rather than the source Session's. The replayed turns keep the timestamps they had on the source Session. Agent Substrate measures the fork's idle lifetime from its creation time instead, so a fork of an old conversation is not collected as idle. + +2. Save the forked Session's ID. + ```bash + export FORK_SESSION_ID= + ``` + +3. Ask the fork about the replayed conversation. The agent answers from the turns that the checkpoint pinned, which is what makes the fork a continuation rather than a new conversation. + ```bash + kagent agent invoke --session $FORK_SESSION_ID --task "What did I ask you first?" + ``` + +4. Confirm that the two Sessions are now independent. Send the source Session a message, and note that the fork's transcript does not change. + ```bash + kagent agent invoke --session $SESSION_ID --task "Remember the number 7." + kagent agent invoke --session $FORK_SESSION_ID --task "What number did I ask you to remember?" + ``` + + The fork does not know the number, because the two conversations diverged at the checkpoint. + ## Clean up -1. Delete the checkpoint. Deleting removes the Tag and releases the pin, and Agent Substrate can collect the snapshot whenever no tag names it. +1. Delete the forked Session. + ```bash + kagent agent session delete $FORK_SESSION_ID + ``` + +2. Delete the checkpoint. Deleting removes the Tag and releases the pin, and Agent Substrate can collect the snapshot whenever no tag names it. ```bash grpcurl -plaintext \ -d '{"checkpointId":"'"$CHECKPOINT_ID"'"}' \ diff --git a/docs-site/content/kagent/1.x/examples/agents-via-mcp.md b/docs-site/content/kagent/1.x/examples/agents-via-mcp.md index e37fd650..07345288 100644 --- a/docs-site/content/kagent/1.x/examples/agents-via-mcp.md +++ b/docs-site/content/kagent/1.x/examples/agents-via-mcp.md @@ -486,7 +486,7 @@ The checkpoint tools pin a Session's state. These tools do not need the tasks ex } ``` -For what a checkpoint captures, see [Suspend and resume]({{< link path="about/substrate-runtime/suspend-and-resume#checkpoints" >}}) and the [Agent Substrate example]({{< link path="examples/agent-substrate" >}}). +To start a second Session from a checkpoint, call `fork_session` with the checkpoint ID. For what a fork replays and where it diverges, see [Suspend and resume]({{< link path="about/substrate-runtime/suspend-and-resume#checkpoints" >}}) and the [Agent Substrate example]({{< link path="examples/agent-substrate" >}}). You can now safely [clean up these resources](#clean-up). @@ -513,7 +513,7 @@ The server exposes five Session tools. Two cover discovery and conversation. Thr | `invoke_session` | `session_id`, `message` | Sends a message and returns the agent's reply. Takes `message_id` for idempotency. | | `create_session_checkpoint` | `session_id`, `expected_head_task_id` | Pins the conversation at a turn boundary. The call fails if the conversation has advanced past that task. Takes `request_id` for idempotency. | | `list_session_checkpoints` | `session_id` | Lists that Session's checkpoints. Takes `page_size` and `page_token`. | -| `fork_session` | `checkpoint_id` | Starts a second Session from a checkpoint. Currently rejects every checkpoint with `FAILED_PRECONDITION`, for the reason given in [Checkpoints]({{< link path="about/substrate-runtime/suspend-and-resume#checkpoints" >}}). Takes `request_id` for idempotency. | +| `fork_session` | `checkpoint_id` | Starts a second Session from a checkpoint, replaying the conversation through the pinned turn. Takes `request_id` for idempotency. | ## Next steps diff --git a/docs-site/content/kagent/1.x/observability/launch-ui.md b/docs-site/content/kagent/1.x/observability/launch-ui.md index 37c32e9c..ac59d061 100644 --- a/docs-site/content/kagent/1.x/observability/launch-ui.md +++ b/docs-site/content/kagent/1.x/observability/launch-ui.md @@ -100,7 +100,7 @@ To take a checkpoint, select the save icon beside the message box. kagent pins t A checkpoint appears in the transcript as a mark carrying the snapshot name and three controls. Select the mark to open the record behind it. The record holds the checkpoint's ID, turn, state, and age. -- **Fork** starts a second Session from the checkpoint. Currently, the API rejects every fork with `FAILED_PRECONDITION`, for the reason given in [Checkpoints]({{< link path="about/substrate-runtime/suspend-and-resume#checkpoints" >}}). +- **Fork** starts a second Session from the checkpoint. The new Session replays the conversation through the pinned turn and then continues on its own. For what a fork keeps from its source, see [Checkpoints]({{< link path="about/substrate-runtime/suspend-and-resume#checkpoints" >}}). - **Rename** replaces the snapshot name that kagent generates on creation from the Session ID and the turn the checkpoint pins. Clearing the name restores that generated default. - **Delete** removes the checkpoint and releases the snapshot it pinned. Agent Substrate is then free to collect that snapshot. diff --git a/docs-site/content/kagent/1.x/operations/operational-considerations.md b/docs-site/content/kagent/1.x/operations/operational-considerations.md index 92f60cfc..53fe2601 100644 --- a/docs-site/content/kagent/1.x/operations/operational-considerations.md +++ b/docs-site/content/kagent/1.x/operations/operational-considerations.md @@ -250,7 +250,7 @@ This value governs every Session in the installation. kagent does not provide a Idle time runs from whichever is later: the Session's creation, or its most recently stored A2A event. Reads, renames, lifecycle calls, and retried writes do not reset it. A Session that is only ever listed still expires. - +A fork is the case where the creation time decides. A fork replays its source Session's events and keeps their original timestamps, so its most recently stored event is older than the fork itself. Idle time therefore runs from the fork's own creation, which gives it a full interval rather than an almost expired one. Work in progress is never deleted. A Session with a running task survives past the interval, and so does one with a turn waiting at `INPUT_REQUIRED` or `AUTH_REQUIRED`. A pending lifecycle operation or a checkpoint being captured also holds a Session open. diff --git a/docs-site/data/glossary.yaml b/docs-site/data/glossary.yaml index 81f8ab8a..5432ba0d 100644 --- a/docs-site/data/glossary.yaml +++ b/docs-site/data/glossary.yaml @@ -105,7 +105,7 @@ Tag: Fork: short: "A second Session created from a checkpoint, continuing the conversation from the point that the checkpoint pinned. A fork inherits the checkpoint's revision, so later edits to the Agent do not change what it runs, and new turns append only to the fork, leaving the original's history untouched." - link: "/docs/kagent/1.x/examples/agent-substrate/#fork-the-conversation-into-a-second-agent" + link: "/docs/kagent/1.x/examples/agent-substrate/#fork-the-checkpoint-into-a-second-session" Transcript: short: "The record of a Session's conversation, held server-side and append-only. It survives the Actor suspending between turns, and a resumed runtime cannot shrink it."