Put the agent in its own folder with a Python virtualenv, run it through a systemd unit with a Restart= policy, and treat the LLM as an optional plug-in: a coding-agent CLI you already logged into, called as a subprocess, so no API key sits on the server. Run an offline test suite before you enable the service, and give the agent a persistent queue so a failed task is retried instead of lost.
The problem
An agent that works in your terminal usually breaks in predictable ways once it has to run on its own:
- It stops when the SSH session closes, and nothing brings it back after a crash or a reboot.
- API keys end up in shell history,
.envfiles or git. - A task fails at 3 a.m. and is simply gone: no retry, no record.
- You cannot test it without spending tokens or calling the network.
- Under systemd the agent cannot find the LLM CLI, because the service does not see your
~/.local/binin itsPATH.
A practical approach you can apply today
1. One folder, one virtualenv
sudo apt-get install -y python3 python3-venv python3-pip
python3 -m venv /opt/myagent/venv
/opt/myagent/venv/bin/pip install -r /opt/myagent/requirements.txt
Keep code, venv and the agent's data (SQLite file, logs) in that folder, owned by the account that runs the service.
2. A unit file
[Unit]
Description=My agent
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=alice
WorkingDirectory=/opt/myagent
Environment=PATH=/home/alice/.local/bin:/usr/local/bin:/usr/bin:/bin
EnvironmentFile=-/etc/myagent.env
ExecStart=/opt/myagent/venv/bin/python /opt/myagent/agent.py run
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Save it as /etc/systemd/system/myagent.service, then:
sudo systemctl daemon-reload
sudo systemctl enable --now myagent
journalctl -u myagent -f
Points that matter:
User=should be the account where your LLM CLI is logged in, since the login is stored in that user's home.Environment=PATH=must include the folder where the CLI lives, or the agent will not find it.EnvironmentFile=-: the leading dash makes the file optional. Keep any settings there withchmod 600, not in the unit.Type=simplefits a long-running loop. For a job that runs once and exits, useType=oneshotand trigger it with a timer:
# /etc/systemd/system/myagent.timer
[Timer]
OnCalendar=hourly
Persistent=true
[Install]
WantedBy=timers.target
Enable it with sudo systemctl enable --now myagent.timer. See the systemd.service and systemd.timer man pages.
3. Plug in your own LLM CLI, no API key
Coding-agent CLIs have a non-interactive mode: claude -p "prompt" for Claude Code, codex exec "prompt" for Codex. Your agent can call one with subprocess.run(..., timeout=...), read stdout, extract the JSON it asked for and validate it. Three rules:
- Detect the CLI with
shutil.which()and treat "not found" as a normal state, not a crash. - Keep a deterministic code path for everything that must always work. The LLM adds reasoning; it should not be the only way a task completes.
- Always set a timeout. CLIs can be slow or wait for input.
4. A queue with retries
A SQLite table is enough for one machine: task id, type, JSON payload, status, attempt count, next run time and a unique idempotency key. On failure, increase the attempt count and reschedule with a growing delay. After the last attempt, move the task to a dead-letter state you can inspect and replay. The idempotency key stops a producer that retries from creating the same work twice.
5. Offline tests
Write tests that run with no network and no LLM: stub the CLI call and check the deterministic path. Run them before enabling the service and after every update.
How the packs do it
The packs below share one install pattern. Paid packs cost $3 one-time; the sample is free. Their install.sh (run as sudo bash install.sh):
- installs
python3,python3-venvandpython3-pipwith apt, - creates
venv/in the pack folder and installsrequirements.txt, - runs
llm_adapter.pyto detect a logged-in Claude Code, Kimi Code or Codex CLI, or a local Ollama server, - writes
/etc/systemd/system/<pack-id>.servicewithUser=set to the account that ransudoand aPATHthat includes that account's~/.local/bin.
The installer is idempotent, so it is safe to re-run. bash install.sh --no-service sets up only the venv. PACK_LLM=claude|kimi|codex forces a backend. If no CLI is found, the pack runs its deterministic core. bash smoke_test.sh runs the offline test suite with no keys. The quickstart walks through these steps.
Free Sample Agent (free): try the pattern first
Free Sample Agent is small on purpose: a JSON-file task queue feeding four deterministic transforms (upper, lower, reverse, wordcount), with atomic queue writes (temp file plus os.replace). Its unit is Type=oneshot: the installer enables it but does not start it. It is a structural sample, not a production product, and it is MIT licensed.
sudo bash install.sh
bash smoke_test.sh
python3 agent.py enqueue --text "hello world" --transform upper
python3 agent.py run --once
python3 agent.py status
It lets you read the installer, the unit, the LLM adapter and the tests before paying for anything.
Agent Worker Pack v2.0: the durable queue
Agent Worker Pack v2.0 is the long-running version. Its core is a SQLite queue where each task has a type, a priority (HIGH, NORMAL, LOW), a JSON payload, a maximum number of attempts, dependencies and an optional idempotency key. Failed attempts are rescheduled after 10, 60 and 600 seconds; a task that runs out of attempts goes to the dead-letter queue. Task states are pending, running, completed, blocked and dead. It does not run arbitrary commands found in a payload. The generated unit uses Restart=always and RestartSec=5.
python3 agent.py enqueue --type send_report --payload '{"report_id":"weekly"}'
python3 agent.py run --once --deterministic
python3 agent.py status
python3 agent.py replay --task t-example
Autonomous Stack Starter v2.0: a runnable foundation
Autonomous Stack Starter v2.0 is for building your own agent. It ships 5 named prompts (Bootstrap Architect, Config Reviewer, Running Guard, Scaling Advisor, Deployment Reporter), a 35-step checklist with commands, config templates (starter_config.yaml, agent_rules.yaml), a prompt tester that works with Ollama or a stub, and a minimal OODA orchestrator.
cp templates/starter_config.yaml ./starter.yaml
python3 scripts/test_prompt.py --prompt bootstrap --input "My agent must monitor Reddit and create alerts"
python3 scripts/run_agent.py --config starter.yaml --duration 60
Autonomous Agent Starter: design before code
Autonomous Agent Starter is the design companion: SOUL.md (identity, values, decision loop, anti-patterns), AUTONOMOUS.md (OODA architecture, components, MCP and A2A protocols), PROMPTS.md (5 system prompts) and a 48-step "Zero to Autonomous" checklist. Every file starts with YAML frontmatter, so an LLM can parse it too.
Honest limits
- Debian/Ubuntu first. The installers use apt and systemd. Elsewhere, use
--no-serviceand write your own unit. - Which LLMs. The shared
llm_adapter.pyprobes for Claude Code, Kimi Code and Codex, then for a local Ollama server athttp://127.0.0.1:11434(orPACK_OLLAMA_HOST). SetPACK_LLM=ollamato prefer your local model over any installed CLI. - Root step. The installer runs apt and writes a unit file as root. Read
install.shbefore running it; it is short. - Scope. The Worker pack is a single-machine queue, not a distributed scheduler. The free sample only demonstrates structure.
Where to start
- Download the free Free Sample Agent, read
install.sh, runbash smoke_test.sh, then install it with the quickstart. - If you need a real task queue with retries, move to Agent Worker Pack v2.0.
- If you are designing a new agent, read Autonomous Agent Starter, then build on Autonomous Stack Starter v2.0.
- Browse all packs for domain-specific agents.