Daemon — Jazz without a terminal attached
jazz chat and jazz run are one process talking to one terminal. That’s fine until you
need something to happen when nobody’s typing: a scheduled workflow firing at 6 AM, a
webhook from GitHub landing at 2 PM, a run parked on an approval that the person who can
answer it is on a different machine entirely.
jazz daemon is that “something.” It’s the same agent runtime, but reachable over HTTP
instead of a REPL, and able to sit there running with nobody attached.
The short version
# Start it
jazz daemon
# From another terminal (or another machine): start a run
curl -X POST http://localhost:4747/runs \
-d '{"agent":"default","prompt":"summarize today'\''s deploys"}'
# Poll it
curl http://localhost:4747/runs/<runId>
# If it parked on an approval, answer it
curl -X POST http://localhost:4747/runs/<runId>/answer -d '{"approved":true}'
jazz runs (list/show/approve/reject) is the same thing from the CLI, and works whether or
not a daemon is involved — a daemon just means the run can be answered from somewhere other
than the process that started it.
What it actually does
One process, four jobs, each opt-in:
- Serves runs over HTTP.
POST /runsstarts one,GET /runs/:idpolls it,POST /runs/:id/answerapproves or rejects what it’s parked on,GET /runslists what’s in flight. This is the only way to answer a parked run from a different process than the one that started it. - Owns the schedule ticker, if
scheduler.modeis set toin-process. Workflow schedules normally ride your OS scheduler (launchd/cron), which only fires while the machine is awake; the daemon’s own ticker is the alternative for a host you mean to leave running. See Scheduling. - Answers peers, if started with
--serve-peers <agentId>.POST /peer/askandPOST /a2aboth need a running daemon to have anyone to ask — without it, your agent can still ask other peers, but nobody can ask yours. See Peers. - Serves webhooks.
POST /webhooks/<name>wakes the agent that webhook is configured for. See Webhooks.
It’s also the fallback ticker for wake triggers: a
trigger normally fires via a one-shot launchd/at job the host schedules directly, with
no daemon required. The daemon’s in-process ticker only matters on a host with neither
(most containers).
None of this needs all of it. A daemon started plain, with no flags, only serves runs and —
if scheduler.mode is in-process — ticks workflows. Peers and webhooks activate on top of
that, not instead of it.
Authentication
GET /health is unauthenticated on purpose — a process supervisor should be able to check
that the daemon is alive without holding a credential that can drive an agent.
Everything else needs a bearer token, but only when it matters: binding to 127.0.0.1 (the
default) needs no token at all, since reaching it already means being on the machine.
Binding anywhere else does. The first time a daemon binds a non-loopback host with no token
already set, Jazz generates one, stores it (OS keyring, or a chmod 600
$JAZZ_HOME/secrets.json where there’s no keyring), and prints it once so you can copy it to
a client.
jazz daemon set-token # generate (or store $JAZZ_DAEMON_TOKEN) ahead of the daemon's first run
jazz daemon forget-token # remove it
Set $JAZZ_DAEMON_TOKEN yourself instead of letting Jazz generate one when the value needs
to be known in advance — a client config written before the daemon has ever run, or an
ephemeral container whose $JAZZ_HOME won’t survive to the next deploy.
Peers and webhooks don’t use this token — each has its own, checked separately, because a credential that can start and approve runs is a much bigger grant than one that can only ask a question or fire one fixed prompt.
Reaching it from another machine
Bind an interface other than loopback and pass the token on every request:
# On the host
jazz daemon --host 0.0.0.0
# → Generated a daemon token and stored it in <keyring>: <token>
# From another machine
curl http://<host>:4747/runs \
-H "Authorization: Bearer <token>" \
-d '{"agent":"default","prompt":"summarize today'\''s deploys"}'
0.0.0.0 binds every interface on the host, so reachability beyond that is whatever your
firewall or router already allows — bind a specific interface’s address instead if you mean
one network and not “anywhere this host has a route.” Either way, the token is the only thing
standing between that interface and an agent with filesystem access, so treat exposing it like
exposing any other unauthenticated-by-default service: scope who can reach the port, not just
who holds the token.
Running it persistently
jazz daemon runs in the foreground. Restarting it on crash and starting it on boot is your
host’s job, not the daemon’s — that’s what a process supervisor is for. jazz daemon install wires it into your OS’s supervisor (systemd on Linux, launchd on macOS) instead
of leaving that hand-written:
sudo jazz daemon install --serve-peers my-agent
sudo jazz daemon uninstall
Both need root, and install doesn’t report success until /health actually answers.
Related
- Scheduling — the ticker the daemon owns in
in-processmode - Peers — what
--serve-peersturns on, and its own credential model - Webhooks — the other thing a daemon serves
- CLI reference →
jazz daemon— every flag and command - Lexicon — run, park, approval