PAIPAI

Subagents

Let one agent delegate a job to another agent on a child thread.

A subagent is another agent in the same createPai app. The parent exposes a tool; the model picks a child and a task; PAI runs that child on a hidden thread and writes the result back.

Use this when one specialist should not share the user's full transcript: research, analysis, a read-only explorer.

server/tools/research.ts
import { defineSubagentTool } from "@pai/core";
import { explorerAgent } from "../explorer-agent";

export const research = defineSubagentTool({
  id: "research",
  description: "Delegate research to a specialist in this workspace",
  agents: {
    explorer: {
      agent: explorerAgent,
      description: "Read-only research over workspace documents",
    },
  },
});

The model calls research with { agent: "explorer", task: "..." }. You do not write inputSchema / execute yourself.

Register the parent and the child:

createPai({
  agents: {
    main: defineAgent({
      tools: { research },
      // ...
    }),
    explorer: explorerAgent,
  },
  scopeKey: (identity) => identity.workspaceId,
});

On the HTTP receiver, keep specialists from being started by the browser:

createPaiExpressReceiver({
  pai,
  resolveIdentity,
  access: { explorer: "read" },
});

The parent still spawns them in-process. The client can watch the child thread if you want a nested transcript:

const Explorer = Pai.agent("explorer");

function ExplorerCard({
  tool,
}: {
  tool: ToolRenderProps<AssistantPai["agents"]["main"], "research">;
}) {
  const subagent = Assistant.useSubagent(tool);
  if (!subagent) return null;
  return (
    <Explorer.ThreadProvider threadId={subagent.threadId}>
      <ChildChat />
    </Explorer.ThreadProvider>
  );
}

useSubagent(tool) reads { agent, threadId, status } from the parent card. It does not fetch. Mount the child binding's ThreadProvider with that threadId. See useSubagent().

Next

On this page