Running as a service
The daemon is a foreground process by design: no daemonising, no pid file, no log rotation of its own. A service manager does those things better and already exists on every machine this will run on. Two are covered here — systemd on Linux and launchd on macOS — and the same three facts apply to any other.
Get it working in the foreground first — Installing. A service that has never been seen to succeed by hand is one that cannot be debugged.
The three things every service manager gets wrong by default
PATH
A service starts with a short PATH. Your terminal has ~/.local/bin, Homebrew, ~/.cargo/bin
and whatever your shell profile added; the service has /usr/bin:/bin. The daemon finds agent
CLIs on PATH and nowhere else — so a daemon that registered two providers in your terminal
registers none as a service, and the log says no agent CLI found on PATH.
Set PATH explicitly in the unit or plist, to include wherever claude and elyra actually
are. which claude in your terminal tells you.
HOME, and which user
The CLI's sign-in, the SSH agent, the git credential helper and the daemon's own configuration
all live in $HOME. Run the service as the user who signed in to the CLI and ran felagi setup, with that user's HOME. A service running as root or nobody has none of it, and
every run fails with Not logged in or a clone that cannot authenticate.
Stopping
The daemon handles SIGTERM and SIGINT the same way: it stops claiming, each running task
kills its CLI and reports itself back to the server as retryable, and the process exits once
those reports have landed — up to thirty seconds. Give the service manager at least forty
seconds before it sends SIGKILL. Killed early, the tasks it held sit as running in Félagi
until the sweeper notices five minutes later; stopped properly, they are requeued at once.
systemd (Linux)
A user unit, so it runs as you and finds your HOME. /etc/systemd/user/ for every user or
~/.config/systemd/user/ for one.
# ~/.config/systemd/user/felagi.service
[Unit]
Description=Félagi agent daemon
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/felagi start
Restart=on-failure
RestartSec=5
# The daemon hands its tasks back on SIGTERM and needs up to 30 s to do it.
KillSignal=SIGTERM
TimeoutStopSec=40
# The two things systemd's default environment does not have.
Environment=FELAGI_LOG=info
Environment=PATH=%h/.local/bin:%h/.cargo/bin:/usr/local/bin:/usr/bin:/bin
[Install]
WantedBy=default.target
%h is the user's home. Add the directory which claude printed if it is not one of those.
systemctl --user daemon-reload
systemctl --user enable --now felagi
systemctl --user status felagi
journalctl --user -u felagi -f
A user unit stops when the user logs out unless lingering is on:
sudo loginctl enable-linger $USER
Without it, the daemon runs while you are signed in and vanishes when you are not — which on a build box is most of the time, and the runtimes list will show it flickering offline.
Confinement needs bubblewrap. apt install bubblewrap or dnf install bubblewrap. Without
it the log says so at start and the sandbox runs as limits: resource caps but no write
confinement. The policy in force is reported to the server and shown in the runtimes list, so
an unconfined machine is visible without logging in to it.
launchd (macOS)
A user agent, in ~/Library/LaunchAgents/, so it runs as you.
<!-- ~/Library/LaunchAgents/no.felagi.daemon.plist -->
<?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>no.felagi.daemon</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/felagi</string>
<string>start</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<!-- Up to 30 s to hand tasks back on SIGTERM. -->
<key>ExitTimeOut</key>
<integer>40</integer>
<key>EnvironmentVariables</key>
<dict>
<key>FELAGI_LOG</key>
<string>info</string>
<key>PATH</key>
<string>/Users/kh/.local/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
</dict>
<key>StandardOutPath</key>
<string>/Users/kh/Library/Logs/felagi.log</string>
<key>StandardErrorPath</key>
<string>/Users/kh/Library/Logs/felagi.log</string>
</dict>
</plist>
Replace /Users/kh with your home; launchd does not expand ~. Put the directory which claude printed in PATH.
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/no.felagi.daemon.plist
launchctl print gui/$(id -u)/no.felagi.daemon | head -20
tail -f ~/Library/Logs/felagi.log
To stop and remove:
launchctl bootout gui/$(id -u)/no.felagi.daemon
A user agent runs while that user is logged in, including a locked screen, and not
otherwise. A Mac meant to serve agents around the clock stays logged in — or the daemon runs as
a system daemon in /Library/LaunchDaemons/ with UserName set, which then needs that user's
HOME in EnvironmentVariables too.
Sleep stops runs. A laptop lid closed mid-run stops the CLI; the daemon's heartbeat stops
with it, the server marks the runtime offline within 45 seconds, and the task is requeued when
its lease expires. Nothing is lost, and nothing hangs — but a machine that sleeps is not a
machine to rely on for long runs. caffeinate or Prevent automatic sleeping in Energy
settings, if it is meant to be one.
Checking that it is really running
Three places, and they should agree:
| Says | |
|---|---|
systemctl --user status felagi / launchctl print … |
The process is up |
| The log | registered … providers=… — with every provider you expected. Fewer means PATH |
| Admin → Runtimes in Félagi | The machine, online, with its sandbox policy |
Online is computed from heartbeats, not stored: a runtime that shows online has been heard from in the last 45 seconds. A crashed daemon cannot leave one looking alive.