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
6 changes: 4 additions & 2 deletions docs/content/docs/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,15 @@ After a crash or a cut-off upgrade, the agent reads the saved steps on wake and

| Stopped while | On wake |
| --- | --- |
| The model was answering | The model call is sent again from the start. The partial answer is lost. |
| The model was answering | The call counts as a failed attempt, and the partial answer is saved as a stopped message. The model call is sent again after the retry backoff. With no retries left, the run ends unanswered. |
| A tool marked `replay: "safe"` ran | The tool runs again. |
| Any other tool ran | The tool isn't run again. Its result tells the model the call stopped partway. |
| A task phase ran | The phase runs again, and earlier phases are kept. |
| The run was cancelled with `abort` | Nothing resumes. |

Nothing runs while a crashed Actor is down. The run resumes when the Actor starts again. A client that is still connected gets a fresh snapshot on its event stream.
Streamed output is saved in batches, about once a second. A crash can lose about the last second of a partial answer or of a tool's running output. Finished messages and tool results are saved before the run continues.

Nothing runs while a crashed Actor is down. The run resumes when the Actor starts again. A client that is still connected gets a fresh view of the conversation.

## Upgrades

Expand Down
16 changes: 8 additions & 8 deletions docs/content/docs/client-sdk.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: "Client SDK"
description: "Call an agent from your backend, scripts, or any JavaScript runtime, and stream its events."
description: "Call an agent from your backend, scripts, or any JavaScript runtime, and stream its work."
---

The client is the `rivetkit/client` package. It calls actions on the agent and gets the agent's events over a connection. A browser connects with a short-lived token from your backend.
Expand Down Expand Up @@ -49,14 +49,14 @@ The client is the `rivetkit/client` package. It calls actions on the agent and g

## Events

Events go only to a connection that asks for them. Call `conversation.watchEvents(id)` on the connection. It returns a `snapshot` of the conversation, including any answer in progress, and then the connection receives `pi.events` with `{ conversationId, seq, events }` as the agent works.
Events go only to a connection that asks for them. Call `conversation.watch(id)` on the connection. It returns the conversation's view as `{ value }`, including any answer in progress, and then the connection receives `pi.view` frames as the agent works.

- Events from before the call are not sent again. Read them from the snapshot.
- A short answer can arrive whole in `message_end`, with no `text_delta` before it, so print the rest of the text there.
- The `seq` number goes up by one for each batch. If a number is missing, call `watchEvents` again to get a new snapshot.
- To stop listening, call the function that `on` returns, or `conversation.unwatchEvents(id)`.
- Each frame after the first value has Chord operations in `ops`. Apply them with `applyImmutable` from `@earendil-works/chord`, which you install with `npm add @earendil-works/chord`.
- The view holds the newest entries of the transcript. Read older ones with `conversation.entries`.
- The `seq` number goes up by one for each frame. If a number is missing, call `conversation.watch` again to get a new view.
- To stop listening, call the function that `on` returns, or `conversation.unwatch(id)`.

See [Session Lifecycle](/agents/docs/session-lifecycle#events) for the event types.
See [Session Lifecycle](/agents/docs/session-lifecycle#watch-a-conversation) for what the view holds.

## Results and errors

Expand All @@ -65,7 +65,7 @@ See [Session Lifecycle](/agents/docs/session-lifecycle#events) for the event typ
- Pass a `requestId` to make retries safe. A second `prompt` with the same `requestId` returns the first one's result and doesn't run the agent again.
- While a run is in progress, a new prompt waits as a follow-up. Pass `whenBusy: "steer"` to send it into the current run, or `"reject"` to make `prompt` reject with the `ConversationBusy` code.
- The `prompt` action rejects with an `ActorError` when the action itself fails. For example, the run passes the ten-minute action timeout (`action_timed_out`). A timeout only stops the wait. The run keeps going until it finishes or `conversation.abort(id)` stops it.
- To start a run without waiting for it, call `conversation.submit(id, { type: "input", content })`. Follow its progress with events.
- To start a run without waiting for it, call `conversation.submit(id, { type: "input", content })`. Follow its progress with `conversation.watch`.

## Browsers

Expand Down
4 changes: 2 additions & 2 deletions docs/content/docs/custom-tools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ A custom tool is a function the model can call. It runs in the agent Actor on yo
</svg>
</div>

Tool parameters are <a href="https://github.com/sinclairzx81/typebox" target="_blank" rel="noopener noreferrer">TypeBox</a> schemas. Pi validates every call against them, so build them with the `Type` that pi-ai exports:
Tool parameters are JSON Schema. Pi validates every call against them. Build them with pi-schema:

```sh
npm add @earendil-works/pi-ai
npm add @earendil-works/pi-schema
```

<CodeGroup>
Expand Down
2 changes: 1 addition & 1 deletion docs/content/docs/human-in-the-loop.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Pi has no built-in approval flow, so the check goes in the tool. A tool can refu
- The first `deploy` call creates an `approval` Actor with a random id for that version. The tool returns `control: { terminate: true }`, which ends the run instead of letting the model try again. The agent can sleep while it waits.
- The approval id is in the tool result, so the model passes it back on the next call. A made-up id fails, because no `approval` Actor has it.
- The `consume` action returns whether that version was approved and resets it, so one approval allows one deploy.
- The `ApprovalDialog` component watches the root conversation and shows the request from the `tool_execution_end` event. Approve calls `approve`, then prompts the agent, which calls `deploy` again.
- The `ApprovalDialog` component watches the root conversation and shows the request from the `deploy` tool result in its view. Approve calls `approve`, then prompts the agent, which calls `deploy` again.
- The `deploy` tool leaves `replay` unset, so a deploy cut off by a crash isn't sent twice. See [Custom Tools](/agents/docs/custom-tools#after-a-crash).
- Protect the `approval` Actor with [authentication](/docs/authentication) so only approvers can call `approve`.

Expand Down
12 changes: 6 additions & 6 deletions docs/content/docs/pi.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ Each agent is a Rivet Actor. A run is a series of steps, such as model calls, to
- **Idle agents sleep.** A sleeping agent uses no memory. The next action on its key wakes it with its conversations and files.
- **Crashes and upgrades resume.** Work that was cut off carries on from its last saved step. See [Resuming a run](/agents/docs/architecture#resuming-a-run).
- **Resent prompts run once.** A client can safely resend a prompt with the same `requestId`.
- **Clients that join late catch up.** They get a snapshot of the run, then live events.
- **Clients that join late catch up.** They get the current view of the conversation, then live changes.

To get started, see the [Quickstart](/agents/docs/quickstart).

Expand All @@ -99,15 +99,15 @@ An agent needs no sandbox. Without one, Pi's `read`, `write`, and `edit` tools w

## Use pi-ai

The pi-ai package is Pi's model library, and Pi Durable is built on it. Install it next to Pi Durable:
The pi-ai package is Pi's model library, and Pi Durable is built on it. The pi-schema package builds tool parameters. Install them next to Pi Durable:

```sh
npm add @earendil-works/pi-ai
npm add @earendil-works/pi-ai @earendil-works/pi-schema
```

| To | Use from pi-ai |
| To | Use |
| --- | --- |
| Define a tool's parameters | `Type`, which builds the schema Pi checks every call against |
| Define a tool's parameters | `obj`, `str`, and the other builders from pi-schema, which build the JSON Schema Pi checks every call against |
| Read the messages of a conversation | `AssistantMessage`, `UserMessage`, and `ToolResultMessage` |
| Pick a thinking level for `thinkingLevel` | `ModelThinkingLevel`, from `"off"` to `"max"` |

Expand Down Expand Up @@ -165,7 +165,7 @@ See [Personal Agent over SMS](/guides/personal-agent) for a complete example.

<CodeSnippet file="examples/docs/pi/stream.ts" title="stream.ts" />

The `watchEvents` action returns a snapshot, then sends `pi.events` to that connection only. A short answer can arrive whole in `message_end`, with no `text_delta` before it, so print the rest of the text there. A batch with `seq` 0 replaces the client's state, and a gap in `seq` means call `watchEvents` again.
The `conversation.watch` action returns the conversation's view, then sends `pi.view` frames to that connection only. A frame carries Chord operations, and `applyImmutable` from `@earendil-works/chord/delta` applies them to the view. A frame with `seq` 0 replaces the client's view, and a gap in `seq` means call `conversation.watch` again. See [Watch a conversation](/agents/docs/session-lifecycle#watch-a-conversation).

## Your own actions and `c.pi`

Expand Down
8 changes: 4 additions & 4 deletions docs/content/docs/react-sdk.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ The chat UI uses the `@rivetkit/react` package. The agent logic lives in one hoo
<CodeSnippet file="examples/docs/react-sdk/useAgentChat.ts" title="useAgentChat.ts" />
</CodeGroup>

- The `useActor` hook connects to the agent by name and key. Once the connection opens, the hook calls `conversation.watchEvents` on the root conversation. The returned snapshot has the history and any answer in progress, and from then on the connection receives `pi.events`.
- The `message_update` event streams the reply as text deltas, and `message_end` adds each finished message, including your own prompt. See [Session Lifecycle](/agents/docs/session-lifecycle#events) for every event.
- Each batch has a `seq` number. If one is missing, the hook watches again and starts from a new snapshot.
- The `useActor` hook connects to the agent by name and key. Once the connection opens, the hook calls `conversation.watch` on the root conversation. The returned view has the newest messages and any answer in progress, and from then on the connection receives `pi.view` frames.
- Each frame has Chord operations. The hook applies them with `applyImmutable` from `@earendil-works/chord`, and reads the messages, the partial answer, and the running tool from the view. See [Session Lifecycle](/agents/docs/session-lifecycle#watch-a-conversation) for what the view holds.
- Each frame has a `seq` number. If one is missing, the hook watches again and starts from a new view.
- The `send` function calls `conversation.submit`, which starts a run without waiting for it. A message sent during a run waits as a follow-up.
- The `run_start` and `run_end` events mark when the agent works. The `Chat` component switches its button from Send to Stop while it does, and Stop calls `conversation.abort`.
- The view's `pi.live` document has a `run` while the agent works. The `Chat` component switches its button from Send to Stop while it does, and Stop calls `conversation.abort`.

## Next steps

Expand Down
40 changes: 23 additions & 17 deletions docs/content/docs/session-lifecycle.mdx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
---
title: "Session Lifecycle"
description: "What a conversation is, the events one prompt produces, and how to steer or cancel a run."
description: "What a conversation is, how to watch one prompt run, and how to steer or cancel a run."
---

A conversation is one thread of messages inside an agent. Each agent has a root conversation, which `prompt` uses by default, and can hold more conversations. A conversation:

- Keeps the full history of messages and tool calls.
- Handles one run at a time, calling the model and tools until the model is done. Prompts that arrive during a run wait in a queue.
- Streams events to every client that watches it.
- Streams its changes to every client that watches it.
- Summarizes older messages when the context fills up.
- Survives sleep, crashes, and upgrades. See [Architecture](/agents/docs/architecture).

Expand All @@ -31,33 +31,39 @@ A conversation is one thread of messages inside an agent. Each agent has a root
<text x="560" y="45" text-anchor="middle" font-size="14" font-weight="600" fill="#1b1916">Sandbox</text>
<text x="560" y="62" text-anchor="middle" font-size="12" fill="#56524a">runs tools</text>
<text x="194.5" y="29" text-anchor="middle" font-size="12" fill="#56524a">prompt</text>
<text x="195.5" y="76" text-anchor="middle" font-size="12" fill="#56524a">events</text>
<text x="195.5" y="76" text-anchor="middle" font-size="12" fill="#56524a">view</text>
<text x="449.5" y="29" text-anchor="middle" font-size="12" fill="#56524a">tool call</text>
<text x="450.5" y="76" text-anchor="middle" font-size="12" fill="#56524a">result</text>
<text x="325" y="116" text-anchor="middle" font-size="12" fill="#56524a">repeats until the model stops calling tools</text>
</svg>
</div>

This client connects to the [Quickstart](/agents/docs/quickstart) agent, watches the root conversation, sends one prompt, and prints the events it receives:
This client connects to the [Quickstart](/agents/docs/quickstart) agent, watches the root conversation, sends one prompt, and prints what the agent does:

<CodeSnippet file="examples/docs/session-lifecycle/client.ts" title="client.ts" />

## Events
## Watch a conversation

The `conversation.watchEvents(id)` action returns a snapshot of the conversation, including a partial answer. Then the agent sends `pi.events` to that connection only. Each frame has `conversationId`, `seq`, and an `events` array. A frame with `seq` 0 replaces everything the client had, and a gap in `seq` means call `watchEvents` again.
The `conversation.watch(id)` action returns `{ value }`, the conversation's view, including a partial answer. Then the agent sends `pi.view` frames to that connection only. Each frame has `conversationId`, `seq`, and either `value` or `ops`.

| Event type | When it fires |
- A frame with `seq` 0 has a `value`, which replaces everything the client had.
- Other frames have Chord operations in `ops`. Apply them to the view with `applyImmutable` from `@earendil-works/chord/delta`.
- A gap in `seq` means call `conversation.watch` again.
- To stop listening, call `conversation.unwatch(id)`.

| In the view | What it shows |
| --- | --- |
| `run_start`, `run_end` | A run starts or ends. |
| `turn_start`, `turn_end` | A model response starts, or it and its tool calls are done. |
| `message_start`, `message_end` | A message starts or is complete: your prompt, a model reply, or a tool result. |
| `message_update` | The model streams output. Each item in `changes` has a `type`, such as `text_delta`, `thinking_delta`, or `toolcall_delta`. |
| `tool_execution_start`, `tool_execution_end` | A tool starts or finishes. |
| `auto_retry_start`, `auto_retry_end` | A failed model call is retried. |
| `compaction_start`, `compaction_end` | Older messages are summarized to free up context. |
| `usage_changed` | The conversation's token and cost totals change. |

A short answer can arrive whole in `message_end`, with no `text_delta` before it, so print the rest of the text there. Events are not replayed. To read earlier messages, call `conversation.context(id)`, which returns `{ messages }`. To stop listening, call `conversation.unwatchEvents(id)`.
| `entries` | The newest entries of the transcript, starting with the newest 50. |
| `before` | The id of the entry just before `entries`. It is absent when `entries` reaches the start of the transcript. |
| `docs["pi.live"].run` | Present while a run is in progress. |
| `docs["pi.live"].generation` | The partial answer in `message`, and the retry backoff in `retry`. |
| `docs["pi.live"].tools` | The tools of the current round, each with a `name`, a `status`, and its running `output`. |
| `docs["pi.live"].compactions` | Older messages are being summarized to free up context. |
| `docs["pi.live"].runFailure` | Why the newest run ended without an answer. |
| `docs["pi.inbox"]` | Messages waiting for the current run. |
| `docs["pi.usage"]` | The conversation's token and cost totals. |

To read older entries, pass `before` to `conversation.entries(id, { maxEntryId: before }, limit)`. To read the messages the model sees, call `conversation.context(id)`, which returns `{ messages }`.

## Steer, follow up, or cancel

Expand Down
Loading
Loading