The fundamental split#
Before topology, understand the two communication patterns:
1. Backend channels — phone, SIP, WhatsApp. These talk to your Node.js process via the SDK's WebSocket. Your code receives events through an in-process EventEmitter.

2. Browser channels — WebRTC and chat. The browser connects directly to voice.pinecall.io. Your backend's only job is minting short-lived tokens.

This split is why some topologies support SSE event streaming and others don't — SSE requires the agent to be in the same process as your web server.
Topology 1: Embedded#
Agent runs inside your existing web app (Express, Next.js, Hono, Remix). The web server and the agent share a Node.js process.

Pros:
- SSE streaming works (you can build live dashboards)
- One deployment unit — easy ops
- Token endpoint is one route away from the agent
Cons:
- The agent process restarts every time you deploy the web app
- Web traffic and voice traffic share resources
When to use: small apps, dashboards that need live call event streaming, single-team projects.
Topology 2: Standalone#
Agent runs as a separate process from your web app. The web app handles HTTP, the agent process handles voice.

Pros:
- Independent deploys — restart the agent without touching the web app
- Independent scaling — give the agent its own resources
- Crash isolation — a web bug doesn't kill calls in flight
Cons:
- No SSE — the web app can't stream events from the agent process directly. If you need live dashboards, the agent has to expose its own SSE endpoint or push to a shared bus (Redis, NATS).
- Two deployments to manage
When to use: higher-traffic apps, when ops cares about independent scaling, when you want to avoid the "web deploy kills in-flight calls" problem.
Topology 3: Headless#
No web server at all. Just the agent. Use this when you only need phone/SIP/WhatsApp — no browser channels, no dashboards, no tokens to mint.
// agent/index.js — a complete production agent, no web server needed
import { Pinecall } from "@pinecall/sdk";
const pc = new Pinecall();
export const agent = pc.agent("support", {
prompt: "You are a support agent for an online store...",
llm: "openai/gpt-5-chat-latest",
voice: "elevenlabs/sarah",
stt: "deepgram/flux",
language: "en",
phoneNumber: "+13186330963",
greeting: "Hi! How can I help?",
tools: [lookupOrder, processReturn],
});Run it with pinecall run agent/index.js for a polished boot banner and live transcript.
Pros:
- Lowest possible complexity
- No HTTP surface to attack or maintain
- Easy to ship as a container, a systemd unit, or a serverless function
Cons:
- No browser channels (no WebRTC, no chat) unless someone else mints tokens
- No SSE
- No dashboards from this process
When to use: IoT devices, intercoms, single-purpose phone bots, WhatsApp-only bots, scheduled outbound campaigns.
Comparison#
| Feature | Embedded | Standalone | Headless |
|---|---|---|---|
SSE (agent.stream()) | ✅ | ❌ | ❌ |
| WebRTC / Chat | ✅ | ✅ (token from web app) | ❌ (or you build it) |
| Phone / SIP | ✅ | ✅ | ✅ |
| ✅ | ✅ | ✅ | |
| Outbound calls | ✅ | ✅ | ✅ |
| Operational complexity | Medium | Medium | Lowest |
| Independent scaling | ❌ | ✅ | ✅ |
| Crash isolation | ❌ | ✅ | n/a |
Which one should you pick?#
- Just starting out — embedded. Get something running, split later if you need to.
- You need browser channels and a dashboard — embedded.
- You're scaling and ops cares — standalone.
- You're shipping a fixed-purpose device or WhatsApp-only bot — headless.
Migration between topologies is cheap. The agent code is the same in all three. You're just choosing where to run it.
What's next#
- Multi-tenant dashboards — embed multiple agents, scope events per user
- Dev mode — run prod and dev agents on the same phone number
- SSE streaming reference — for embedded dashboards

