Skip to content
Docs

Run the worker as a service

pidgen setup starts the worker in the background, and it keeps running until you stop it, log out of a machine that ends your processes, or restart the machine. After a restart you start it again with the Pidgen CLI (see Workers).

A worker that should come back by itself after a restart or a crash belongs under the machine's service manager: systemd on Linux, launchd on macOS. This page shows how to set that up for the worker the Pidgen CLI installed, and how to run a worker in a container instead.

Stop the worker the CLI started. The worker holds a lease on the machine, and a second copy started by the service manager would only wait for that lease while the first copy keeps running outside the service manager's control. Stop it with the Pidgen CLI (Workers), then check that nothing is left:

Terminal window
pgrep -af pidgen-worker

It should print nothing.

Find the worker's program and the tools it needs. A service manager starts programs with a short PATH, so the service file names the worker by its absolute path and sets a PATH that reaches Git, Node.js and your coding agents. In your usual shell:

Terminal window
command -v pidgen-worker
command -v git node claude codex pi

Note each directory these print. Leave out any coding agent you do not use.

Check the machine. pidgen-worker doctor should pass before the service manager starts the worker; see Logs and diagnostics.

The worker is started with no arguments: pidgen-worker on its own is the long-running process, and it reads its credential and settings from its home (~/.pidgen unless your setup chose another directory). It takes no other arguments, so the service file passes none.

A user service runs as you, with your home, your coding-agent logins and the worker's ~/.pidgen, exactly as the worker the CLI started did. You need no root access.

Create ~/.config/systemd/user/pidgen-worker.service, replacing the ExecStart= path with what command -v pidgen-worker printed and adding the directories you noted to PATH (%h is your home directory):

[Unit]
Description=Pidgen worker
Documentation=https://docs.pidgen.ai/worker/run-as-a-service/
[Service]
Type=simple
Environment=PIDGEN_HOME=%h/.pidgen
Environment=PATH=%h/.local/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=%h/.local/bin/pidgen-worker
Restart=on-failure
RestartSec=5s
KillSignal=SIGTERM
KillMode=mixed
TimeoutStopSec=120s
[Install]
WantedBy=default.target

What the settings are for:

Setting Why
Environment=PIDGEN_HOME= The worker's home. Keep it the same as the one your setup used.
Restart=on-failure A crash, or a start that fails because the network is not up yet, is retried every 5 seconds. A worker that stopped cleanly stays stopped: one you stopped on purpose, and one whose credential was revoked (see Remove a worker).
KillMode=mixed, KillSignal=SIGTERM A stop sends SIGTERM to the manager alone, which runs the worker's planned stop: it stops taking work, interrupts running steps and hands them back to Pidgen so another worker can pick them up, then exits.
TimeoutStopSec=120s The planned stop takes up to two minutes. Only after that does systemd kill what is left.

Load the service, start it, and have it start at boot:

Terminal window
systemctl --user daemon-reload
systemctl --user enable --now pidgen-worker

A user service runs only while you are logged in, unless lingering is on. Turn it on so the worker starts at boot and keeps running after you log out:

Terminal window
loginctl enable-linger "$USER"

Check it and read its log:

Terminal window
systemctl --user status pidgen-worker
journalctl --user -u pidgen-worker

The prompt, script and workspace workers it starts still write their own logs under ~/.pidgen/logs/; see Logs and diagnostics.

While systemd runs the worker, stop and start it with systemd, so the service always knows whether the worker is meant to be running:

Terminal window
systemctl --user stop pidgen-worker
systemctl --user restart pidgen-worker

Both run the planned stop and can take up to two minutes. To stop the worker and keep it from starting at the next boot:

Terminal window
systemctl --user disable --now pidgen-worker

After you change the service file, run systemctl --user daemon-reload and restart the service. After an update of the worker (see Workers), restart the service so the new version runs.

On a Mac, launchd runs the worker as a launch agent: a property list in ~/Library/LaunchAgents that starts the worker as you when you log in, and starts it again if it crashes.

Create ~/Library/LaunchAgents/ai.pidgen.worker.plist. Replace you with your user name, the first ProgramArguments entry with what command -v pidgen-worker printed, and add the directories you noted to PATH (/opt/homebrew/bin is Homebrew's on Apple Silicon, /usr/local/bin on Intel):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>ai.pidgen.worker</string>
<key>ProgramArguments</key>
<array>
<string>/Users/you/.local/bin/pidgen-worker</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PIDGEN_HOME</key>
<string>/Users/you/.pidgen</string>
<key>PATH</key>
<string>/Users/you/.local/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>ExitTimeOut</key>
<integer>120</integer>
<key>StandardOutPath</key>
<string>/Users/you/.pidgen/logs/launchd.log</string>
<key>StandardErrorPath</key>
<string>/Users/you/.pidgen/logs/launchd.log</string>
</dict>
</plist>

What the keys are for:

Key Why
ProgramArguments The worker's absolute path and nothing else: the worker takes no arguments.
EnvironmentVariables The worker's home, and a PATH that reaches Git, Node.js and your coding agents. launchd does not read your shell's profile.
RunAtLoad Start the worker when the agent is loaded, which is at every login.
KeepAlive with SuccessfulExit false Start the worker again when it crashes, but not when it stopped cleanly, for example because its credential was revoked.
ExitTimeOut launchd stops the worker with SIGTERM, which runs its planned stop; this gives that stop its two minutes before launchd kills what is left.
StandardOutPath, StandardErrorPath Where the manager's own output goes. The other workers keep their logs under ~/.pidgen/logs/.

Keep the home path short: ~/.pidgen is fine, a deeply nested directory is not, because macOS limits the length of the socket path the worker creates in it.

Check the file, load the agent, and see that it runs:

Terminal window
plutil -lint ~/Library/LaunchAgents/ai.pidgen.worker.plist
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ai.pidgen.worker.plist
launchctl print gui/$(id -u)/ai.pidgen.worker
tail -f ~/.pidgen/logs/launchd.log

launchctl print shows state = running and the worker's process id.

A launch agent runs while you are logged in. For a Mac that should take work after a restart without anyone logging in, turn on automatic login for your user in System Settings.

Restart the worker, running the planned stop first:

Terminal window
launchctl kickstart -k gui/$(id -u)/ai.pidgen.worker

Stop it and unload the agent:

Terminal window
launchctl bootout gui/$(id -u)/ai.pidgen.worker

bootout runs the planned stop and can take up to two minutes. The agent is loaded again at your next login while the file stays in ~/Library/LaunchAgents; to stop the worker for good, bootout it and then move the file out of that folder. After you change the file, bootout the agent and bootstrap it again.

The worker can also run in a container. Pidgen does not publish a worker image yet (planned). If you build your own on the published pidgen-worker package, these are what matter:

  • Run the worker itself, in the foreground. The container's command is pidgen-worker with no arguments. Run it under a small init (docker run --init, or tini as the entrypoint) so that signals reach it and finished processes are reaped. Run it as an unprivileged user, not as root.

  • Put the home on a volume. Set PIDGEN_HOME to a directory on a volume, so the credential, the clones and the logs survive an upgrade of the image. Put worker.env, config/node.yaml and config/worker-policy.yaml from a worker you set up into it before the first start, owned by the container's user and readable only by it, and stop the worker you copied them from: one credential is one worker, and two copies would contend for the same lease.

  • Give the stop two minutes. The container's stop sends SIGTERM, which runs the planned stop. Docker's default of 10 seconds would kill the worker halfway through and leave its steps stranded until their leases run out. Start the container with docker run --stop-timeout 120, or under Compose:

    services:
    pidgen-worker:
    stop_signal: SIGTERM
    stop_grace_period: 120s
    restart: on-failure
  • Let the container restart on failure. docker run --restart on-failure (Compose: restart: on-failure) brings back a worker that crashed, and leaves one whose credential was revoked stopped.

  • Put the tools and the logins in the image. The image needs Git, Node.js and the coding agents on its PATH, and the agents need their logins inside the container: log them in there, or mount their login directories into it. A login on the host machine is not seen from inside. Run pidgen-worker doctor in the container to check.

kill -9, systemctl --user kill, docker kill, a power cut or a sudden sleep skip the planned stop. Nothing hands the running steps back, so they stay assigned to this worker until their leases run out, and only then does Pidgen give them to another worker. A run tied to this machine waits until the worker is back. Stop the worker with its service manager instead, and give it its two minutes. If a run is still stuck afterwards, see A run is stranded on a worker that is gone.