mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
Submissions with request-ID deduplication and handles, the pi.live run control and presentation document, the built-in pi.generation task (system prompt preparation, streamed durable partials, retries, deferred polling, abort), typed entry tokens, stream options and retry policy, TaskRuntime reads, Tx.settleSubmission, registry conversation setups, Storage.scanSubmissions, Harness.inspect, and byTaskId attribution. Waiting calls enable scheduling. The scratch tour is split into test/examples.
65 lines
2.8 KiB
TypeScript
65 lines
2.8 KiB
TypeScript
// A background task that owns a child conversation.
|
|
// Run from packages/durable:
|
|
// node --conditions=source --experimental-strip-types test/examples/03-owned-conversations.ts
|
|
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
|
import { type ConversationId, createSession, defineDoc, defineTask, MemoryStorage } from "../../src/index.ts";
|
|
|
|
const context = BACKGROUND_CONTEXT;
|
|
const session = createSession(new MemoryStorage());
|
|
|
|
// A typical agent setup: a background task supervises a helper conversation,
|
|
// and the main conversation keeps a registry that maps agent names to their
|
|
// conversations. All three are created in one commit, so after a crash either
|
|
// all of them exist or none do.
|
|
|
|
// A task definition needs a name, a version, the task's starting state, a
|
|
// handler for every phase, and an abort handler (12-tasks.ts runs a task).
|
|
// This example only creates the task record; a plain Session never runs it.
|
|
const Supervisor = defineTask<null, { phase: "ready" }, null>({
|
|
name: "example.supervisor",
|
|
version: 1,
|
|
initial: () => ({ phase: "ready" }),
|
|
phases: { ready: async () => {} },
|
|
abort: async () => {},
|
|
});
|
|
|
|
// "latest" keeps only the current value. "initial" means forks of this
|
|
// conversation start without a registry, so a child doesn't inherit its
|
|
// parent's list of agents.
|
|
const AgentRegistry = defineDoc<{
|
|
agents: Record<string, { conversationId: ConversationId; requestId: string }>;
|
|
}>({
|
|
kind: "example.agent-registry",
|
|
version: 1,
|
|
scope: "conversation",
|
|
history: "latest",
|
|
fork: "initial",
|
|
initial: () => ({ agents: {} }),
|
|
});
|
|
|
|
const main = await session.commit((tx) => tx.createConversation({ ownership: { kind: "ownerless" } }), context);
|
|
|
|
const setup = await session.commit(async (tx) => {
|
|
// `background: true` means the task is side work: waiting for the main
|
|
// conversation to finish does not wait for it.
|
|
const supervisorId = await tx.createTask(Supervisor, null, { conversationId: main.id, background: true });
|
|
|
|
// The child records that it belongs to the supervisor task. The task was
|
|
// created a few lines above in this same commit, which is allowed.
|
|
const child = await tx.createConversation({ ownership: { kind: "task", taskId: supervisorId } });
|
|
|
|
// requestId is a fixed name for the child's first message. Later code sends
|
|
// that message using this requestId, so a retry after a crash cannot
|
|
// deliver it twice.
|
|
(await tx.doc(AgentRegistry, main.id)).agents.researcher = {
|
|
conversationId: child.id,
|
|
requestId: `researcher:first-message:${supervisorId}`,
|
|
};
|
|
return { supervisorId, child };
|
|
}, context);
|
|
|
|
console.log("supervisor task:", setup.supervisorId);
|
|
console.log("child conversation:", setup.child);
|
|
console.log("registry:", await session.snapshot(AgentRegistry, main.id, context));
|
|
|
|
await session.close(context);
|