Run in production
Production checklist
Keep your agent online around the clock — reconnect automatically, supervise the process, protect secrets and make work safe to retry.
An agent that works in your terminal is one step from an agent you can rely on from your phone. This page covers that step.
Reconnect automatically
The SDK opens one connection. Networks drop, laptops sleep and servers restart, so wrap the client in a small loop that creates a fresh client from the saved state and retries with a growing delay.
import {
createNodeAgentGatewayClient,
createNodeSignalSnapshotStore,
} from "@pacerelle/sdk/node";
const agentId = process.env.PACERELLE_AGENT_ID;
const token = process.env.PACERELLE_AGENT_TOKEN;
const delays = [1, 2, 5, 10, 30]; // seconds
let attempt = 0;
let generation = 0;
async function start() {
const current = ++generation;
const state = createNodeSignalSnapshotStore(agentId); // reloads the latest saved state
const client = createNodeAgentGatewayClient({
agentId,
token,
e2ee: true,
signalSnapshot: state.signalSnapshot,
onSignalSnapshot: state.onSignalSnapshot,
onOpen: () => {
attempt = 0;
console.log("Connected");
},
onClose: () => {
if (current === generation) retry(client);
},
onError: (error) => console.error("Pacerelle error:", error),
});
client.onMessage(handleMessage);
try {
await client.connect();
} catch {
if (current === generation) retry(client);
}
}
function retry(client) {
generation++; // ignore any late event from the old client
client.close();
const delay = delays[Math.min(attempt++, delays.length - 1)];
console.log(`Disconnected. Reconnecting in ${delay}s…`);
setTimeout(start, delay * 1000);
}
await start();Messages sent while the agent was offline are delivered after it reconnects.
With Agent Connect, runtime tokens last 15 minutes: request a new one
inside start() before creating each client.
Keep the state file
The SDK stores the agent's private keys and delivery journal on disk:
| SDK | Default location | Change it with |
|---|---|---|
| JavaScript | ~/.pacerelle/agents/<agent-id>.signal.b64 | PACERELLE_AGENT_SIGNAL_STATE_FILE or PACERELLE_AGENT_SIGNAL_STATE_DIR |
| Python | ~/.myagents/<agent-id>/signal.db | store_root= argument |
| MCP server | ~/.pacerelle/mcp | PACERELLE_STORE_ROOT |
- Put it on persistent storage — a Docker volume, not the container's filesystem.
- Restrict access to the account running the agent, and back it up securely.
- Run one process per state file. Two processes sharing it will corrupt the agent's sessions.
Supervise the process
Run the agent as a service that starts at boot and restarts on failure.
[Unit]
Description=My Pacerelle agent
After=network-online.target
Wants=network-online.target
[Service]
User=agent
WorkingDirectory=/opt/my-agent
EnvironmentFile=/opt/my-agent/.env
ExecStart=/usr/bin/node agent.mjs
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.targetStore secrets properly
- Keep
PACERELLE_AGENT_TOKENin an environment file readable only by the service account, or in your platform's secret manager. - Add
.envto.gitignore. Never bake tokens into images. - Use separate agents for development, staging and production.
Make work idempotent
A message can be delivered again if your handler fails or the process crashes before it finishes.
Use message.id as an idempotency key for anything with side effects:
client.onMessage(async (message, agent) => {
if (await jobs.exists(message.id)) return; // already handled
await jobs.create(message.id, message.text); // persist before returning
await agent.reply(message, { text: "Started — I'll report back here." });
});Sending a message confirms it was handed to the connection, not that it was displayed. If a send fails, check the conversation before repeating anything consequential.
Log safely
Log connection events, errors and timings. Don't log message text, file contents, widget answers or tokens in production — they're exactly what end-to-end encryption protects.
Rotate a token
- Replace the token in the app — the old one stops working immediately.
- Update the secret where the agent runs.
- Restart the service and check the agent is back online.
Try it once before you need it in an emergency.
Checklist
- The agent reconnects on its own after a network loss.
- It restarts after a crash and after a reboot.
- The state file is on persistent storage, private and backed up.
- The token lives in a secret store, not in code or images.
- Side effects are idempotent on
message.id. - Destructive or external actions ask first with widgets or permissions.
- Logs contain no message content or secrets.
- Development and production use different agents.