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.

agent.mjs
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:

SDKDefault locationChange it with
JavaScript~/.pacerelle/agents/<agent-id>.signal.b64PACERELLE_AGENT_SIGNAL_STATE_FILE or PACERELLE_AGENT_SIGNAL_STATE_DIR
Python~/.myagents/<agent-id>/signal.dbstore_root= argument
MCP server~/.pacerelle/mcpPACERELLE_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.

/etc/systemd/system/my-agent.service
[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.target

Store secrets properly

  • Keep PACERELLE_AGENT_TOKEN in an environment file readable only by the service account, or in your platform's secret manager.
  • Add .env to .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

  1. Replace the token in the app — the old one stops working immediately.
  2. Update the secret where the agent runs.
  3. 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.