PAIPAI

Runtime

Run agents in-process for CLIs, jobs, tests, and custom servers.

createAgentRuntime() binds an agent definition to infrastructure without exposing HTTP.

import { createAgentRuntime } from "@pai/core";
import { assistantAgent } from "./assistant-agent";

const runtime = createAgentRuntime({
  agent: assistantAgent,
  storage,
  realtime,
  live: { mode: "auto" },
  files,
  scopeKey: (identity) => identity.workspaceId,
});

live.mode describes the runtime topology:

  • "singleRuntime" uses the runtime's local in-process event bus;
  • "distributed" uses the local bus plus an external realtime provider, with optional fallbackPollIntervalMs;
  • "auto" uses distributed mode when realtime is configured, otherwise single-runtime mode.

Use explicit "distributed" when multiple servers or workers can mutate the same shared storage.

Direct Client

Trusted TypeScript code can use the runtime directly.

const pai = runtime.client({
  identity: { userId, workspaceId },
});

const thread = pai.thread(pai.newThreadId());
const run = await thread.send("Draft a report");

await run.waitUntilIdle();

Direct clients are useful for:

  • CLIs;
  • background jobs;
  • tests;
  • scripts;
  • single-runtime applications.

They are trusted and already authenticated. Browser users should not choose their own scope.

If direct code acts for an end user, run your application policy before creating the client. Use an HTTP receiver such as createPaiHonoReceiver({ pai, resolveIdentity, ... }) for untrusted remote traffic when resolveIdentity() must authenticate requests and choose trusted scope.

Trusted Thread Operations

Server jobs, CLIs, tests, Electron hosts, and background workers sometimes need to mutate a thread without pretending to be a browser user message.

runtime.client() returns the ordinary direct client:

const thread = runtime.client({ identity: { userId, workspaceId } }).thread(threadId);

runtime.admin() returns a trusted admin client whose thread() method returns RuntimeThread, a server-only superset with message mutation and runtime trigger helpers:

const admin = runtime.admin({
  identity: { userId, workspaceId },
});
const thread = admin.thread(threadId);

await thread.trigger({
  notification: "Background job completed.",
  data: { jobId, status: "completed" },
  hidden: true,
});

await thread.messages.append({
  role: "assistant",
  parts: [{ type: "text", text: "The import finished." }],
});

Use thread.trigger() when the model should continue from the notification. Use messages.append() when you only need to persist state.

For a longer-lived trusted helper, create an admin client:

const hidden = await admin.threads.list({ includeHidden: true });

Admin clients stay server-side. Ordinary remote clients keep the smaller Thread surface.

The admin list is also the way to read across agents: an ordinary client's threads.list() returns only threads owned by its own agent, while admin.threads.list() spans all of them and takes an optional agent filter.

Runtime Locks

Use withThreadLock() for custom maintenance work that must not race the active run.

const admin = runtime.admin({ identity });

const result = await admin.withThreadLock(threadId, async (tx) => {
  await tx.messages.append({
    role: "assistant",
    parts: [{ type: "text", text: compactedSummary }],
    visibility: { transcript: false, context: true },
  });

  return { compacted: true };
});

if (result.status === "busy") {
  // Try later or ask the user to stop the active run.
}

Callback-based lock APIs are runtime-only. They cannot be exposed through HTTP because functions cannot cross a transport boundary.

Lifecycle

Close runtimes that own connections or workers.

await runtime.close();

Server Composition

Advanced servers can compose a runtime and pass it to a framework adapter:

import { createPaiHonoReceiver } from "@pai/hono";

const runtime = createAgentRuntime({
  agent,
  storage,
  realtime,
  scopeKey: (identity) => identity.workspaceId,
});

export const routes = createPaiHonoReceiver({
  runtime,
  resolveIdentity,
});

On this page