PAIPAI

Threads And Runs

A thread is one conversation. A run is one send.

A thread is one durable conversation. Messages, tool calls, files, and pending approvals live here.

A run is one turn of work on that thread — what you get back from thread.send().

import { createPaiHttpClient } from "@pai/client-http";
import type { AssistantPai } from "./http-server";

const paiClient = createPaiHttpClient<AssistantPai>({
  url: "http://localhost:3001/api/pai",
  headers: () => ({
    "x-user-id": "dev-user",
    "x-workspace-id": "dev",
  }),
});

const assistant = paiClient.agent("main");
const thread = assistant.thread(assistant.newThreadId());

const run = await thread.send("Draft a report");
const state = await run.waitUntilIdle();

console.log(state.messages);

newThreadId() is just an id. The first accepted send creates the stored thread.

Send

A string is enough:

await thread.send("Summarize this thread");

Or parts, when you attach files:

await thread.send({
  text: "Review this document.",
  attachments: [uploaded],
});

If the thread is already running, a second send throws ThreadBusyError. Wait until the thread is idle, or stop the run, then send again.

Wait, stop, refresh

await run.waitUntilIdle(); // completed, failed, or cancelled
await run.waitUntilBlocked(); // waiting for input, or terminal
await thread.stop(); // cancel the active run
await thread.refresh(); // pull the latest snapshot

thread.getState() is the same shape refresh() and watch() return:

const state = thread.getState();
state.messages; // the transcript
state.runs; // runs and their current lifecycle status
state.activeRunId;

Group messages by message.metadata.pai.runId if you want turn containers in the UI. That grouping is a view, not a second transcript.

Watch

for await (const state of thread.watch()) {
  render(state.messages);
}

React does this for you through ThreadProvider. You only need watch() in Node or a non-React client.

List threads

const page = await assistant.threads.list();
for (const head of page.items) {
  console.log(head.threadId, head.title);
}

In React, useThreads() is the same list, kept live.

Rename and delete

await assistant.threads.rename(thread.threadId, "Q3 report");
await assistant.threads.delete(thread.threadId);

useThreads() exposes the same rename / delete. After delete, route the UI to a new newThreadId().

Next

On this page