Agent-to-Agent Communication
Connect agents through agentOS host functions so one coding agent can call another while sessions, permissions, and results remain isolated.
Agents communicate through host functions. You define a host-function collection that lets one agent send work to another, and the agent calls it like any other CLI command.
Example: code writer + reviewer
This example gives the writer agent a review host function. The writer sends the file’s full contents (the VMs share no filesystem), and the host function writes them into a separate reviewer VM and sends a review prompt back through the reviewer.
import { agentOS, setup } from "@rivet-dev/agentos";
import { createClient } from "@rivet-dev/agentos/client";
import { z } from "zod";
// The reviewer is its own isolated agent VM.
const reviewer = agentOS({});
// Bridge the writer to the reviewer. The VMs share no filesystem, so the writer
// sends the full file contents; the bridge writes them into the reviewer's VM
// and asks the reviewer to review. Runs on the host.
async function reviewCode(code: string): Promise<string> {
const client = createClient<typeof registry>({
endpoint: "http://localhost:6420",
});
const reviewerHandle = client.reviewer.getOrCreate("my-project");
// Write the submitted contents into the reviewer's VM.
await reviewerHandle.filesystem.writeFile("/home/agentos/review.ts", code);
// Ask the reviewer to review.
await reviewerHandle.sessions.open({
agent: "claude",
env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});
const result = await reviewerHandle.sessions.prompt({
content: [
{
type: "text",
text: "Review the code at /home/agentos/review.ts and list any issues.",
},
],
});
await reviewerHandle.sessions.delete();
return (
result.message?.content
.filter((block) => block.type === "text")
.map((block) => block.text)
.join("") ?? ""
);
}
// The writer agent gets a `review` host-function collection. When the writer runs
// `agentos-review submit`, the bridge above executes on the host.
const writer = agentOS({
hostFunctions: {
review: {
submit: {
inputSchema: z
.object({
code: z.string().describe("The full source code to review."),
})
.describe(
"Submit the full contents of a file to the reviewer agent for review. Returns the reviewer's feedback as text.",
),
execute: async (input: { code: string }) => ({
review: await reviewCode(input.code),
}),
},
},
},
});
export const registry = setup({ use: { writer, reviewer } });
registry.start();
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";
const client = createClient<typeof registry>({
endpoint: "http://localhost:6420",
});
const writerAgent = client.writer.getOrCreate("my-project");
await writerAgent.sessions.open({
agent: "claude",
env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});
// The writer calls the `review` host-function collection, which bridges to the reviewer VM.
await writerAgent.sessions.prompt({
content: [
{
type: "text",
text: "Write a small REST API, then send it to the review agent for review.",
},
],
});
The writer agent sees the review host function as a CLI command. Because the VMs share no filesystem, it sends the full file contents, not a path:
agentos-review submit --code "$(cat api.ts)"
The host function writes the contents into the reviewer’s VM, prompts the reviewer, and returns the review to the writer as JSON.
Why host functions?
Host functions are the natural communication layer between agents because:
- The agent doesn’t need to know about other agents. It just calls a host function. You can swap the implementation without changing the agent’s behavior.
- No credentials in the VM. The host function executes on the server, so it can access other agents directly without exposing connection details.
- Composable. Chain any number of agents by adding more host functions. Each host function is a self-contained bridge to another agent.
Recommendations
- Each agent has its own isolated VM and filesystem (they share no filesystem). Pass file contents through the host-function input, then use
writeFilein the host function to place them in the other VM. - Use Workflows to make multi-agent pipelines durable across restarts.
Embedded API
Create one AgentOs handle per agent and call the reviewer from a host function.
Your application owns both handles and their lifecycle.
import pi from "@agentos-software/pi";
import { AgentOs } from "@rivet-dev/agentos-core";
import { z } from "zod";
const apiKey = process.env.ANTHROPIC_API_KEY;
if (!apiKey) {
throw new Error("Set ANTHROPIC_API_KEY before running this example.");
}
const reviewer = await AgentOs.create({ software: [pi] });
await reviewer.sessions.open({
agent: "pi",
env: { ANTHROPIC_API_KEY: apiKey },
});
const review = {
draft: {
inputSchema: z.object({ draft: z.string() }).describe("Review a draft"),
execute: async ({ draft }: { draft: string }) => {
const response = await reviewer.sessions.prompt({
content: [{ type: "text", text: `Review this draft:\n\n${draft}` }],
});
const feedback =
response.message?.content
.filter((block) => block.type === "text")
.map((block) => block.text)
.join("") ?? "";
return { feedback };
},
},
};
const writer = await AgentOs.create({
software: [pi],
hostFunctions: { review: review },
});
try {
await writer.sessions.open({
agent: "pi",
env: { ANTHROPIC_API_KEY: apiKey },
});
await writer.sessions.prompt({
content: [
{
type: "text",
text: "Draft a release note, then ask the review host function for feedback.",
},
],
});
} finally {
await writer.dispose();
await reviewer.dispose();
}
Read more in the embedded API quickstart.