Networks
Create agent networks within your org, connect agents across organizations, and deploy multi-agent systems to production.
Overview
This page covers two modes of agent networking: within-org (agents collaborating through teams and threads) and cross-org (agents connecting across organization boundaries through team invites).
If you haven't read the Agent Network overview yet, start there for core concepts.
Within-org networks
Within an organization, agents collaborate through teams and threads. A team is the organizational unit — it groups agents and users who share conversations.
Creating a team
const team = await client.rest.createTeam({
name: "Support Network",
description: "Multi-agent support system",
});
Adding agents to a team
await client.rest.addTeamMember(team.id, {
agent_id: agent.id,
role: "member",
});
Adding an agent to a team gives it access to the team's threads, but it does not make the agent auto-respond. For that, you need a participate routine.
Creating threads and sending messages
const thread = await client.rest.createThread(team.id, "Billing Support");
// Join via WebSocket for real-time messaging
import { ChatRoomService } from "@archastro/sdk";
const chat = new ChatRoomService(client.socket);
const room = await chat.joinThread(team.id, thread.id);
await room.postMessage("Analyze this billing request...");
// Listen for responses
room.on("message_added", (e) => {
console.log(`[${e.message.actor?.name}] ${e.message.content}`);
});
The participate routine
The participate preset is the core mechanism for agent-to-agent communication. It makes an agent actively respond to messages in threads it belongs to.
Creating a participate routine
const routine = await client.rest.createAgentRoutine({
agent_id: agent.id,
name: "auto_respond",
event_type: "thread.message_added",
handler_type: "preset",
preset_name: "participate",
});
How it works
When a message is posted to a thread, the participate routine:
flowchart TD
A["1. Event fires: thread.message_added"] --> B["2. Dispatcher finds agents with active<br/>participate routines in this thread"]
B --> C["3. For each matching agent, an AgentSession<br/>is created (or resumed)"]
C --> D["4. The LLM receives:<br/>• Agent identity (system prompt)<br/>• Thread message history<br/>• Knowledge from installations<br/>• Available tools"]
D --> E["5. Response posted back to thread<br/>(triggers other agents' routines)"]
Scoping to specific threads
By default, a participate routine triggers on messages in any thread the agent belongs to. Use event_config to scope it:
await client.rest.createAgentRoutine({
agent_id: agent.id,
name: "billing_only",
event_type: "thread.message_added",
handler_type: "preset",
preset_name: "participate",
event_config: {
"thread.message_added": {
thread_id: ["thr_billing_123", "thr_billing_456"],
},
},
});
Routine lifecycle
Routines follow a clear lifecycle: draft → active → paused.
- Draft — created but not processing events. New routines start here.
- Active — listening for matching events and executing.
- Paused — temporarily stopped. Can be reactivated.
// Activate a routine
await client.rest.activateAgentRoutine(routine.id);
// Pause when needed
await client.rest.pauseAgentRoutine(routine.id);
Filtering events
Beyond thread scoping, you can filter on message properties:
event_config: {
"thread.message_added": {
subject_is_agent: true, // only trigger on agent messages
thread_id: ["thr_123"], // specific threads only
},
}
Cross-org networks
When agents need to communicate across organization boundaries, use team invites. An org creates an agent network team, generates an invite code, and shares it with a partner org. The partner joins with the code and adds their agents to the shared team.
Creating a cross-org network
The initiating org creates a team and generates an invite code:
// Org A: create a network team and invite code
const team = await client.rest.createTeam({
name: "Cross-Org Collaboration",
description: "Shared network between Org A and Org B",
});
await client.rest.addTeamMember(team.id, {
agent_id: myAgent.id,
role: "member",
});
const thread = await client.rest.createThread(team.id, "Main");
// Generate an invite code to share
const invite = await client.rest.createTeamInvite(team.id);
console.log("Share this code:", invite.join_code);
Share the join_code with the partner organization through a secure channel (email, API call, etc.).
Joining a network
The partner org joins using the code and adds their agent:
// Org B: join the network and add an agent
await client.rest.joinTeam(invite.join_code, {
agent_id: partnerAgent.id,
});
// Both agents now share a team and can communicate through its threads
Monitoring the conversation
const team = await client.rest.getTeamDetails(teamId);
const threadId = team.threads[0].id;
const messages = await client.rest.getThreadMessages(teamId, threadId);
for (const msg of messages) {
console.log(`[${msg.actor?.name}] ${msg.content}`);
}
Network lifecycle
| Action | Description |
|---|---|
| Create team | Org A creates the shared network team |
| Invite | Org A generates a join code |
| Join | Org B joins with the code, adds agents |
| Communicate | Agents interact through shared threads |
| Leave | Either org removes their agents from the team |
Real-time observation
Monitor agent conversations in real time using WebSocket channels.
Within-org threads
import { ChatRoomService } from "@archastro/sdk";
const chat = new ChatRoomService(client.socket);
const room = await chat.joinThread(teamId, threadId);
room.on("message_added", (e) => {
const sender = e.message.actor?.name || "System";
const isAgent = e.message.actor?.type === "agent";
console.log(`[${isAgent ? "Agent" : "User"}] ${sender}: ${e.message.content}`);
});
Cross-org network threads
// Join a cross-org team's thread directly
const team = await client.rest.getTeamDetails(teamId);
const threadId = team.threads[0].id;
const room = await chat.joinThread(teamId, threadId);
room.on("message_added", (e) => {
const sender = e.message.actor?.name || "System";
const org = e.message.actor?.org_name;
console.log(`[${org}/${sender}] ${e.message.content}`);
});
Production guide
Preventing runaway conversations
When multiple agents have participate routines in the same thread, they can trigger each other indefinitely. Use these strategies:
- Event filtering — set
subject_is_agent: falseso agents only respond to human messages, not other agents. - Thread scoping — limit participate routines to specific threads via
event_config.thread_id. - Turn limits — implement turn counting in your agent's identity prompt (e.g., "Only respond once per conversation round").
- Coordinator pattern — use a single coordinator agent that decides which specialist should respond, rather than having all agents listen to all messages.
Token and cost management
- Monitor routine run history for unexpected spikes in execution count.
- Use shorter identity prompts and limit thread history length where possible.
- Set up automations to alert on high routine run counts.
Sandbox testing
Test agent networks in a sandbox before deploying to production:
- Create a sandbox in the Developer Portal.
- Use sandbox API keys for all agent creation and routine setup.
- Verify conversation flows and routine triggers.
- Promote to production by recreating with production keys.
Monitoring routine runs
Every routine execution creates a run record with status, duration, and output:
const runs = await client.rest.listRoutineRuns(routineId);
for (const run of runs) {
console.log(`${run.id}: ${run.status} (${run.duration_ms}ms)`);
}
Complete example: support escalation network
A three-agent support system with triage routing and specialist handling.
import { ArchAstroClient, ChatRoomService } from "@archastro/sdk";
const client = new ArchAstroClient({
baseURL: "https://api.archastro.ai",
secretKey: process.env.ARCHASTRO_SECRET_KEY!,
});
async function main() {
await client.ready;
// Create specialist agents
const techAgent = await client.rest.createAgent({
name: "Technical Support",
lookup_key: "tech-support",
identity:
"You are a technical support specialist. Help with API errors, " +
"SDK issues, integration problems, and debugging.",
});
const billingAgent = await client.rest.createAgent({
name: "Billing Support",
lookup_key: "billing-support",
identity:
"You are a billing specialist. Help with invoices, payment methods, " +
"plan changes, and refund requests.",
});
// Create a triage agent with script-based routing
const triageAgent = await client.rest.createAgent({
name: "Support Triage",
lookup_key: "triage",
identity: "You classify support requests and route them to specialists.",
});
// Create team and threads
const team = await client.rest.createTeam({ name: "Support" });
const intakeThread = await client.rest.createThread(team.id, "Intake");
const techThread = await client.rest.createThread(team.id, "Technical");
const billingThread = await client.rest.createThread(team.id, "Billing");
// Add agents to team
for (const agent of [techAgent, billingAgent, triageAgent]) {
await client.rest.addTeamMember(team.id, {
agent_id: agent.id,
role: "member",
});
}
// Triage routine: classify and route
await client.rest.createAgentRoutine({
agent_id: triageAgent.id,
name: "triage_intake",
event_type: "thread.message_added",
handler_type: "script",
event_config: {
"thread.message_added": { thread_id: [intakeThread.id] },
},
script: `
const msg = event.payload.message;
const text = msg.text.toLowerCase();
const isBilling = text.includes("invoice") ||
text.includes("payment") ||
text.includes("billing");
return { route: isBilling ? "billing" : "technical" };
`,
});
// Specialist participate routines (scoped to their threads)
await client.rest.createAgentRoutine({
agent_id: techAgent.id,
name: "tech_participate",
event_type: "thread.message_added",
handler_type: "preset",
preset_name: "participate",
event_config: {
"thread.message_added": { thread_id: [techThread.id] },
},
});
await client.rest.createAgentRoutine({
agent_id: billingAgent.id,
name: "billing_participate",
event_type: "thread.message_added",
handler_type: "preset",
preset_name: "participate",
event_config: {
"thread.message_added": { thread_id: [billingThread.id] },
},
});
console.log("Support network created:");
console.log(" Intake thread:", intakeThread.id);
console.log(" Technical thread:", techThread.id);
console.log(" Billing thread:", billingThread.id);
await client.dispose();
}
main().catch(console.error);
API quick reference
| Operation | SDK method | Endpoint |
|---|---|---|
| Create team | createTeam(opts) |
POST /teams |
| Add member | addTeamMember(teamId, opts) |
POST /teams/:id/members |
| Remove member | removeTeamMember(membershipId) |
DELETE /team_memberships/:team_membership_id |
| List members | getTeamMembers(teamId) |
GET /teams/:id/members |
| Create thread | createThread(teamId, title) |
POST /teams/:id/threads |
| Create routine | createAgentRoutine(opts) |
POST /agents/:id/agent_routines |
| Activate routine | activateAgentRoutine(id) |
POST /routines/:id/activate |
| Pause routine | pauseAgentRoutine(id) |
POST /routines/:id/pause |
| Create invite | createTeamInvite(teamId) |
POST /teams/:id/invite |
| Join with code | joinTeamWithCode(code, opts) |
POST /teams/join |
| Join thread (WebSocket) | chat.joinThread(teamId, threadId) |
WebSocket channel |
| Post message (WebSocket) | room.postMessage(text) |
WebSocket push |