Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
3 changes: 1 addition & 2 deletions docs-site/content/kagent/1.x/about/core-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 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.

Expand Down
25 changes: 16 additions & 9 deletions docs-site/content/kagent/1.x/examples/a2a-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -20,19 +22,18 @@ A gRPC caller names the Agent in the standard A2A `tenant` field, as `<namespace
| gRPC | The `tenant` field on each request, as `<namespace>/<name>` |
| 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/<namespace>/<agent-name>/` URL paths that kagent 0.x served over HTTP.

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, optional: as a bare paragraph between the table and the contextId paragraph, this sentence reads as part of the addressing rules. It was easier to scan as the > [!NOTE] it replaced. Feel free to ignore.


> [!NOTE]
> `tenant` and the `/agents/{namespace}/{name}` path replace the `/api/a2a/<namespace>/<agent-name>/` 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. 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 `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 |
| ------ | ------------ |
Expand Down Expand Up @@ -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",
Expand All @@ -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.

Expand Down
4 changes: 2 additions & 2 deletions docs-site/content/kagent/1.x/examples/a2a-byo.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`. 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
Expand Down Expand Up @@ -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
Expand Down
67 changes: 60 additions & 7 deletions docs-site/content/kagent/1.x/examples/agent-substrate.md
Original file line number Diff line number Diff line change
@@ -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
---
Expand Down Expand Up @@ -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 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)"'"}' \
Expand All @@ -119,7 +119,11 @@ 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 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 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
Expand All @@ -135,15 +139,64 @@ Each suspend writes a {{< gloss "Snapshot" >}}snapshot{{< /gloss >}}, and Agent

Underneath, the checkpoint attaches a {{< gloss "Tag" >}}Tag{{< /gloss >}} named `checkpoint-<checkpoint-id>` 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

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: kagent#3165, which moves suspension to DATA snapshots and makes forking possible, merged to main today. The docs pin 1.0.0-alpha13, which does not contain that commit, so ForkSession still returns FAILED_PRECONDITION on the documented version.

Removing this section also breaks the #forking-is-temporarily-unavailable links in about/substrate-runtime/suspend-and-resume.md:88 and observability/launch-ui.md:103. These places still say that forking is unavailable:

  • about/core-concepts.md:154
  • about/substrate-runtime/suspend-and-resume.md:88
  • observability/launch-ui.md:103
  • the fork_session row at examples/agents-via-mcp.md:516
  • the REVIEW comment at operations/operational-considerations.md:253

Can we either keep these notes until the next kagent release, or update all of those places in this PR? If forking is in scope now, a fork step could replace this section. The same applies to the two removals in agents-via-mcp.md.

## Fork the checkpoint into a second Session

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.
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.

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.
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=<your-forked-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"'"}' \
Expand Down
Loading
Loading