Tools
Agent-to-Agent Messages
Let one agent hand work to another agent without waiting for it.
A tool can send another agent a message. Here a coder agent hands a finished branch to the reviewer for that repository and keeps working.
import { Type } from "@earendil-works/pi-ai";
import { createRegistry, defineExtension, defineTool } from "@earendil-works/pi-durable";
import { CodingTools } from "@earendil-works/pi-durable/tools";
import { pi } from "@rivet-dev/pi";
import { e2bProvider } from "@rivet-dev/sandbox-adapter/e2b";
import { createClient } from "rivetkit/client";
import type { registry } from "./server";
const requestReview = defineTool({
name: "request_review",
description: "Send a finished branch to the repository's reviewer. Don't wait for the review.",
parameters: Type.Object({ repo: Type.String(), branch: Type.String(), summary: Type.String() }),
execute: async ({ repo, branch, summary }) => {
await client.reviewer.getOrCreate([repo]).requestReview(`Review the ${branch} branch of https://github.com/${repo}: ${summary}`);
return { content: [{ type: "text", text: `Sent ${branch} for review.` }] };
},
});
const extensions = createRegistry();
extensions.install(CodingTools);
extensions.install(defineExtension({ name: "review", tools: [requestReview] }));
export const coder = pi({
model: "anthropic/claude-opus-5-5",
registry: extensions,
sandbox: e2bProvider(),
});
const client = createClient<typeof registry>();
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
import { createRegistry } from "@earendil-works/pi-durable";
import { CodingTools } from "@earendil-works/pi-durable/tools";
import { pi } from "@rivet-dev/pi";
import { e2bProvider } from "@rivet-dev/sandbox-adapter/e2b";
import { setup } from "rivetkit";
import { coder } from "./coder";
const extensions = createRegistry();
extensions.install(CodingTools);
const reviewer = pi({
model: "openai/gpt-5.5",
registry: extensions,
sandbox: e2bProvider(),
actions: {
// Saves the request and returns without waiting for the review.
requestReview: async (c, request: string) => {
const conversation = await c.pi.root(BACKGROUND_CONTEXT);
await conversation.submit({ type: "input", content: request }, BACKGROUND_CONTEXT);
},
},
});
export const registry = setup({ use: { coder, reviewer } });
registry.start();
import { createClient } from "rivetkit/client";
import type { registry } from "./server";
const client = createClient<typeof registry>();
const coder = client.coder.getOrCreate(["acme/app", "fix-flaky-checkout-test"]);
const result = await coder.prompt(
"Clone https://github.com/acme/app, fix the flaky checkout test on a new fix-flaky-checkout-test branch, push it, and request a review.",
);
console.log(result.status === "done" ? result.text : `Unanswered: ${result.reason}`);
- The reviewer is keyed by repository, so every coder working on the same repository reaches the same reviewer, and its conversation remembers past reviews.
- The
requestReviewaction gets Pi’s harness asc.piand submits the request to the reviewer’s root conversation without waiting. The request is saved before the action returns, so the review runs even if the coder’s request goes away. - When the reviewer is busy with another review, the request waits as a follow-up and runs next.
- The coder’s
request_reviewtool sits next toCodingToolsin its registry. The reviewer runs on a different model family than the coder.
To wait for the other agent’s answer instead, see Subagents. See Pi for more on c.pi.