Skip to content

Sub-agent handoff

When an orchestrator agent spawns a sub-agent, you don’t want to ship the master token. PerSQL handoff tokens (phand_…) are single-use, pinned to one (database, branch, role), and expire — max 24h, default 15 min.

import { PerSQL } from "@persql/sdk";
const persql = new PerSQL({ token: process.env.PERSQL_TOKEN! });
const db = persql.database("acme/orders");
const { token } = await db.branches.pin("pr-42", {
role: "write",
ttlSec: 900, // 15 minutes
});
// → token: "phand_..."

The orchestrator passes token to the sub-agent (in a tool call result, an env var, a queue message — anywhere). The master psql_live_… never leaves the orchestrator.

import { PerSQL } from "@persql/sdk";
const sub = await PerSQL.fromHandoff(process.env.HANDOFF_TOKEN!);
const db = sub.database(sub.handedOff.namespaceSlug, sub.handedOff.databaseSlug);
await db.query("INSERT INTO leads (email) VALUES (?)", ["a@b.co"]);

fromHandoff redeems the phand_… for a regular psql_live_… token scoped exactly to the (database, branch, role) the orchestrator pinned. Single use: once redeemed, the handoff is consumed and another sub-agent can’t reuse it.

  • The sub-agent cannot widen scope: a write handoff can’t issue DDL.
  • It cannot escape the branch: writes go to pr-42, not to main.
  • If the sub-agent process crashes mid-task, the orchestrator just mints another handoff — no shared secret to rotate.

Audit what the sub-agent did with db.queryLog() — every call writes a row to _persql_meta_query_log inside the database itself, so you can SELECT … WHERE token_id = ? from the orchestrator.