Run it on your own server
The agent inside your Node app or as a process of its own, with a server token.
Production's agent is an ordinary process: the same pinecall start you run on a laptop, with
--prod, on a server token of the org. Run it inside the Node app you already have, or as a
process of its own beside it.
How it works#
Your process opens a socket to the gateway and holds the agent. Calls reach it over that socket, so it listens on no port and needs no public address.
mount() the class in your server's startup.
A process of its ownpinecall start --prod under your process manager.
DrainingA restart hands live calls to the next process. No call is cut.
If you would rather not run a server at all, Deploy to Pinecall runs the same process for you.
The server token#
Make it#
Open production's console, Settings ▸ Tokens, name the token after what runs it
(clinica web) under New production server token, and press Create. Production's token
is made by somebody with production access.
Copy it#
It is shown once, as the line to paste:
PINECALL_KEY=pc_live_…
Keep it in the server's secrets#
Your platform's config vars, a systemd EnvironmentFile, the container's secret: wherever the
app's database URL is kept. A lost token is revoked from its row and a new one made.
A token opens only the sandbox or only production, whichever it was made in, and its prefix says which: pc_live_ for production,
pc_test_ for the sandbox (made on the sandbox's console, for CI). The verb must agree: the CLI
refuses a pc_test_ token with --prod, and the gateway refuses a production token used without
it:
this key is a production server's token, and this request is for sandbox: a server's token opens the world it was made in
PINECALL_URL goes beside the key only when your gateway is not https://cloud.pinecall.io. It is
one address for the sandbox and production.
Inside your Node app#
import { readFileSync } from "node:fs";
import { mount } from "@pinecall/agents";
import { Pinecall } from "@pinecall/agents/client";
import FrontDesk from "./agents/front-desk/agent.js";
const pc = new Pinecall({ url: "https://cloud.pinecall.io", apiKey: process.env.PINECALL_KEY });
mount(FrontDesk, { pc, source: readFileSync(new URL("./agents/front-desk/agent.tsx", import.meta.url), "utf8") });
await pc.connect();
process.on("SIGTERM", () => void pc.drain().finally(() => pc.close()));
new Pinecall reads nothing from the environment: you hand it the URL and the key. source is the
agent file's own text. Compiling removes the class docstring and the tools' parameter types, and
this is how they come back, so ship the .tsx beside your build. Call drain() before close().
A class mounted this way draws no side panel: the panel is answered by the process
pinecall startruns.
A process of its own#
Run pinecall start --prod under whatever keeps your processes up. In a Procfile:
web: node dist/server.js
agent: pinecall start --prod
With pm2 it is pm2 start "pinecall start --prod" --name agent; with systemd, the same line as
ExecStart. It prints one line per log entry on stdout, or JSON lines with --events. A project of
several agents runs them all in one process, or one per process with --agent <name>.
pinecall start is a Node program whatever language the agent is written in, so the server needs
Node.
If the gateway restarts, the process prints a line ending in reconnecting, dials again on its own,
and prints gateway back. Calls in progress go on.
Deploys and restarts#
On SIGTERM the process drains: it tells the gateway it is leaving, the gateway hands its live
calls to another process holding the agent, or keeps them for the next one, and running tools are
allowed to finish. pinecall start gives the agent's process 40 seconds before it kills it, so give
pinecall start 45:
| manager | setting |
|---|---|
| pm2 | --kill-timeout 45000 (its default is 1.6 s, which cuts every tool in flight) |
| systemd | TimeoutStopSec=45, KillSignal=SIGTERM |
| a platform with a fixed grace | calls still move; a tool slower than the grace may be cut |
Start the new process as soon as the old one has the signal: the calls it kept wait for the new one to register.
What is running#
pinecall agent list --prod prints every process holding the org's agents in production: the
agents, the machine and address it connected from, the SDK, and since when. pinecall agent stop <app> closes one, and pinecall start exits instead of dialling back. A manager that restarts
whatever exits starts it again, so stop it where it is managed.