App Server Daemon
July 24, 2026 ยท View on GitHub
App Server daemon commands start, attach to, inspect, and stop a background app-server listener for Codex-compatible app and editor integrations.
Starting The Daemon
senpi app-server daemon start
The daemon default listener is ws://127.0.0.1:18800. It uses the same bearer-token websocket authentication as app-server mode; the token file is ${SENPI_CODING_AGENT_DIR:-~/.senpi/agent}/app-server/ws-token.
Use --listen to choose another supported app-server transport:
senpi app-server daemon start --listen ws://127.0.0.1:18800
senpi app-server daemon start --listen unix://
senpi app-server daemon start --listen unix:///tmp/senpi-app-server.sock
Subcommands
Every daemon command prints exactly one JSON object to stdout.
start
start first probes the requested listener. If a compatible app-server already answers initialize, the command attaches to it instead of spawning another process.
Started a managed daemon:
{"status":"started","pid":12345,"listen":"ws://127.0.0.1:18800"}
Attached to a managed daemon that is already running:
{"status":"already-running","pid":12345,"listen":"ws://127.0.0.1:18800","version":"senpi_app_server"}
Attached to a compatible listener without a matching managed pidfile:
{"status":"already-running","listen":"ws://127.0.0.1:18800","version":"senpi_app_server"}
status
Managed daemon is running:
{"status":"running","pid":12345,"listen":"ws://127.0.0.1:18800","version":"senpi_app_server"}
Compatible listener exists, but it is not tracked by the daemon pidfile:
{"status":"running-unmanaged","listen":"ws://127.0.0.1:18800","version":"senpi_app_server"}
No compatible listener is running:
{"status":"not-running"}
stop
Stopped a managed daemon:
{"status":"stopped"}
Nothing was running:
{"status":"not-running"}
restart
restart stops the managed daemon, then starts it again. It preserves the previous daemon flags from settings.json when available, so a daemon started with --listen unix:///tmp/senpi-app-server.sock restarts with the same listener even if the restart command omits --listen.
senpi app-server daemon restart
The final stdout object is the start result, usually {"status":"started",...}.
State Directory
Daemon state lives under ${SENPI_CODING_AGENT_DIR:-~/.senpi/agent}:
app-server-daemon/
app-server.pid
daemon.lock
settings.json
app-server/
ws-token
app-server.sock
app-server.pid stores the daemon pid and process start time to avoid PID reuse. daemon.lock serializes daemon commands. settings.json stores the launch intent used by restart. app-server.sock is the default websocket-over-UDS socket path for unix://; an explicit unix:///absolute/path uses that absolute socket path instead.
Spawn Or Attach
start is idempotent:
- Probe the requested listener with a real
initializerequest. - If the listener answers, print
already-running. - For
ws://listeners that do not answer, test whether the requested TCP address can be bound. A listener already occupying the address fails immediately with anEADDRINUSEdiagnostic instead of spawning a child process. - If a matching pidfile exists, wait briefly for that process to answer.
- Otherwise spawn a detached
senpi app-server --listen <url>process, write the pidfile and settings, then probe until ready.
For websocket listeners, the daemon probe reads the bearer token file and sends Authorization: Bearer <token>.
launchd
For macOS login-session startup, create ~/Library/LaunchAgents/com.senpi.app-server.plist. Replace /absolute/path/to/senpi and /absolute/path/to/ws-token with local absolute paths.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.senpi.app-server</string>
<key>ProgramArguments</key>
<array>
<string>/absolute/path/to/senpi</string>
<string>app-server</string>
<string>--listen</string>
<string>ws://127.0.0.1:18800</string>
<string>--ws-auth</string>
<string>/absolute/path/to/ws-token</string>
</array>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
<key>StandardErrorPath</key>
<string>/tmp/senpi-app-server.err.log</string>
<key>LimitLoadToSessionType</key>
<string>Aqua</string>
</dict>
</plist>
Load and unload it with:
launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/com.senpi.app-server.plist
launchctl bootout "gui/$(id -u)" ~/Library/LaunchAgents/com.senpi.app-server.plist
Check readiness for the websocket form with:
curl -fsS http://127.0.0.1:18800/readyz
Security
Keep ws:// listeners on loopback unless the listener is protected by a bearer token and exposed only over a trusted tailnet or equivalent private network. Do not use --ws-auth off outside loopback development.
unix:// uses websocket framing over a Unix-domain socket. The default socket path is same-user local state and should not be shared across accounts. Use the remote-connection pattern from App Server Mode: prefer authenticated loopback websocket for app/editor clients, and use UDS only for same-user local control planes.