Skip to content

Run LatchAI as a service

Triggers only fire while the engine is running. Running npm run engine in a terminal is fine while you’re working, but an automation that is supposed to fire at 3am needs the daemon to outlive your terminal, your logout, and the occasional crash. On macOS that means launchd.

The engine CLI writes and loads the LaunchAgent for you:

Terminal window
npx tsx packages/engine/src/cli.ts daemon install
npx tsx packages/engine/src/cli.ts daemon status
npx tsx packages/engine/src/cli.ts daemon uninstall

install writes a LaunchAgent plist configured to run at load and to keep the process alive, logging to <home>/runs/daemon.log, then bootstraps and kickstarts the job.

Daemon status from the CLI

daemon status reports whether launchd currently owns the engine.

~/Library/LaunchAgents/com.latchai.engine.plist, in four load-bearing keys:

Key Value
Label com.latchai.engine
ProgramArguments your node binary, the tsx CLI, packages/engine/src/cli.ts, serve
RunAtLoad / KeepAlive both true — start at login, restart on crash
StandardOutPath / StandardErrorPath <root>/runs/daemon.log

There are no environment variables in it, so a launchd-started engine resolves its home the usual way: LATCHAI_HOME (unset here) → the ~/.config/latchai/home pointer → ~/LatchAI.

Terminal window
npx tsx packages/engine/src/cli.ts daemon status
# [latchai] com.latchai.engine: state=running pid=41207
curl -s localhost:7777/api/status | head -c 200 # the engine actually answering
tail -f ~/LatchAI/runs/daemon.log # everything it prints

daemon.log is where the engine’s console goes once nothing is attached to a terminal — the boot line with the resolved provider, MCP connection statuses, secret-resolution failures, and any crash stack that made KeepAlive restart it.

Bootstrap can leave the job pended. Loading a job does not reliably start it, so the installer issues an explicit kickstart rather than assuming. (It also waits for a previous bootout to finish before bootstrapping — that teardown is asynchronous, and a bootstrap issued too soon fails with an unhelpful I/O error.)

launchd’s PATH is minimal. That breaks spawning npx-based MCP servers, which is exactly the sort of failure that only shows up overnight. LatchAI prepends node’s own bin directory and Homebrew (/opt/homebrew/bin, /usr/local/bin) when spawning MCP servers so a launchd-started daemon has the same tool surface a terminal-started one does.

The engine has no hot reload. The daemon loads engine code into memory at startup, so edits to packages/engine — and to mounts.json, which is read once at boot — require a restart to take effect. A daemon left running across code edits silently executes stale logic. It does re-read agents and workflows from disk per run, and re-arms triggers when the workflows directory changes, so day-to-day authoring needs no restart.

The restart recipe, under launchd:

Terminal window
launchctl kickstart -k gui/$(id -u)/com.latchai.engine

-k kills the running instance first, so this is the “I just upgraded the engine” button. Without launchd, stop the terminal process and start it again — same effect, more typing.

When you launch the desktop app while a daemon already owns :7777, the app attaches to that process rather than starting its own, and says so only in its console. The window still shows your latest UI (the engine serves the bundle from disk), so the symptom is narrow and confusing: UI changes apply, engine changes don’t. The tray menu’s Restart Engine stops whatever owns the port — external daemon included — starts a fresh engine, and reloads the window.

To find out which process is actually serving:

Terminal window
lsof -ti tcp:7777 # the pid
ps -o command= -p <pid> # …and what it is

A launchd-owned engine shows up with the tsx CLI in its command line and, because KeepAlive is on, restarts if you kill it directly — bootout or daemon uninstall first if you want it gone.

The launchd integration is macOS-specific. Running the engine as a service on Linux — a systemd unit around the same command — is not documented here yet.