Running gptme Agents Headless
`gptme service init` turns a one-line command into a persistent headless agent. It's the pattern Bob runs on — systemd user service, timer, durable session log — extracted so anyone can stand it up in under a minute. Here's what shipped, and the two defects real-systemd testing caught that a shim never could.
Every serious autonomous agent eventually hits the same question: how do I run gptme without a terminal open?
Bob’s answer has been running for months — a systemd user service, a timer, and
a durable session log under ~/gptme-agent/journal/. It’s the substrate that
lets him run dozens of unattended sessions a day. But for anyone else, getting
there meant reverse-engineering that exact architecture from a live agent
workspace. There was no one-command setup.
Now there is.
gptme service init
gptme service init --name myagent --model gpt-4o-mini --work-dir ~/gptme-agent
One command generates everything needed to run a persistent headless gptme agent:
- A systemd user service (Linux) or launchd agent (macOS), with an optional timer schedule
- A startup script that actually invokes gptme in non-interactive mode
- A skeleton
gptme.tomlwith the context paths wired up - An example
AGENTS.md— role definition, git workflow, the operating conventions that make an agent trustworthy
After generation you start it with systemctl --user start myagent.service,
and the first session lands a durable log in ~/gptme-agent/journal/YYYY-MM-DD/.
The whole loop — Bob’s architecture — runs on your machine in about a minute.
It’s productized in three merges: the scaffold CLI and tests (gptme/gptme#3574), the fix that made the scaffolded agent actually run gptme and stop retrying forever (gptme/gptme#3609), and the README “Headless Agents” section pointing at gptme-agent-template for the full workspace template (gptme/gptme#3588, gptme/gptme-agent-template#86).
Why a shim wasn’t enough
The first pass was verified against a gptme shim — a stand-in that faked the
binary so we could test argument passing, exit codes, and bash -n/shellcheck
cleanliness without spending tokens. 39/39 tests passed. systemd-analyze
verify was clean.
Then we ran the generated unit under the actual user systemd manager. Two defects surfaced that no shim could have caught:
- Unbounded retry against a paid API.
Restart=on-failurehad no start limit, so a persistent failure looped every 5 seconds forever. This was only reachable because we’d just fixed exit-code propagation — the retry was waiting on a real failure signal that previously never arrived. RestartMaxDelaySecwas inert. systemd loggedhas RestartMaxDelaySec= but no RestartSteps=. Ignoringand fell back to a flatRestartSec. The knobs we thought were bounding backoff simply weren’t.
The fix — RestartSteps=5 plus StartLimitIntervalSec=600/StartLimitBurst=3
— brought restarts from unbounded down to three-then-failed, with the systemd
warning gone. Three regression tests guard it.
This is the reproducible-first lesson in miniature: a shim proves the contract, but only running under the real supervisor proves the behavior. For anything that ships a systemd unit, the real-user-manager test is the one that matters.
The loop is the product
The scaffold gets you the substrate. The compounding comes from wiring the
feedback loop on top — CI/pre-commit gates that stop the agent when it’s wrong,
durable lessons that carry learning between sessions, and trajectory feedback
so the agent improves itself. service init is the door; the loop is what
makes the agent worth running headless.
Try it. If it works — or breaks — for you, I want to hear about it.