looop
A tiny, portable control plane for agent-driven work.
looop watches the things you care about (GitHub, Linear, Grafana, …) and runs
your worker fleet. It is autonomous: each beat it senses the world and, when
something changed, decides the single most important move and executes it —
spawning worker agents for hands-on work. The judgment lives inside looop (a
small, gated LLM call per beat); looop is the brain.
You are a peer, not the driver: you steer by editing goals / the PLAYBOOK, and a worker that hits a decision only a human can make asks you and waits. To watch and steer in plain language you can run a concierge — a pi/claude session pointed at looop that reads its state and relays — but looop runs fine without one. One self-contained binary, no database, no server.

How it works
looop is the brain; workers are the hands; you (optionally via a concierge) are the peer who shapes goals and answers what only a human can.
pulse (looop — autonomous) workers concierge (optional)
────────────────────────── ─────── ────────────────────
each beat: sense the world, real agents a pi/claude YOU run:
if it changed → decide ONE ──▶ doing multi- reads `looop _ state`,
move → execute it (gated) step work relays asks to you,
(skips the LLM if unchanged) `ask` + wait ──▶ helps you edit goals
- SENSE — the pulse runs every
sensors/*.sheach beat, keepingsnapshots/fresh. If the world is unchanged since last beat it stops here — no LLM call, nearly free. - DECIDE — when the world changed, looop hands the PLAYBOOK + goals +
readings + pending asks to the
tickrunner, which emits ONE typed move. - ACT — looop executes that move (and gates risky ones): write a goal/sensor/PLAYBOOK, run one reversible shell command, or start a worker. One move per beat; a daily budget breaker caps spend.
- HUMAN — you steer by editing goals/PLAYBOOK (observed next beat); a worker
that needs a human decision
asks and waits. Irreversible things never happen without your explicit yes.
State lives entirely in files (goals, snapshots, journal, mailbox), so it is level-triggered: the pulse re-senses from scratch every beat and a crashed pulse just re-reads the unanswered asks on restart.
The human-in-the-loop path is a durable mailbox, not a tmux prompt: a worker
that needs a decision runs one blocking looop _ ask … and waits; you answer with
looop _ answer (directly or through the concierge). No attach, no stdin
wrangling — it works for headless workers.
Concepts
Everything lives as plain files in the data dir (the loop's memory):
| File / dir | Role (Kubernetes analogy) |
|---|---|
PLAYBOOK.md |
the controller logic — your judgment, priorities, guardrails |
goals/*.md |
desired state — one declarative spec per thing you're pushing |
sensors/*.sh |
observers — each prints one JSON object describing the world |
journal.md |
the action log — one line per move |
claims/ |
leases — a worker writes one to own a task; stale ones auto-reap |
reports/ |
deliverables a human reads (persists across beats) |
asks/ answers/ |
the worker ↔ human mailbox (questions + answers) |
Workers are the hands. When a move needs real, multi-step work, looop spawns an agent session that runs detached, in parallel, and reconciles its task on its own. Workers that touch code provision their own sandbox first; looop itself knows nothing about repos.
Humans in the loop. looop decides on its own — you shape WHAT it pursues by
editing goals / the PLAYBOOK (it observes the change next beat). A worker that
needs a decision only a human can make runs looop _ ask and blocks; you answer
with looop _ answer — directly, or through a concierge (a pi/claude session
you point at looop that surfaces pending asks and helps you edit goals). The
concierge is an interface, not a decision-maker. Irreversible actions (merges,
deploys, deletes) always require your explicit approval via an ask.
Quick start
# (optional) run a concierge to watch + steer in plain language:
# relay pending asks, help me edit goals; read `looop --help`"
looop up starts the autonomous pulse — looop runs itself from there. You steer
by editing goals/PLAYBOOK and answering asks (looop _ answer); the concierge is
optional sugar for doing that in chat. looop up --json makes the pulse log
machine-readable NDJSON.
On the first run looop seeds a starter PLAYBOOK and a setup goal whose only job
is to invite you to configure it (a journal note the concierge relays) — run a
concierge (or edit goals/PLAYBOOK directly) to replace the starter with your real
work. After that it just runs.
Commands
# HUMAN (looop runs itself — this is nearly all you touch)
)
)
|)
| )
# STEER (you, or a concierge acting for you — looop does NOT need these to act)
|
The human surface is tiny — essentially up/down/watch (plus cost/config).
looop decides autonomously; the looop _ … STEER verbs let you (or a concierge)
inspect state, answer asks, and edit goals/sensors/PLAYBOOK by hand, and
workers self-report (ask, kill, claim, unclaim, cost) via the auto-injected
contract.
Shell integration
# Zsh (~/.zshrc)
# Bash (~/.bashrc)
This adds tab completion for looop's (small) human command surface.
To change judgment: run looop _ playbook write (or edit a goal) — looop picks it
up next beat.
Install
curl (recommended)
Downloads a prebuilt binary from GitHub Releases — no Rust toolchain needed:
|
Installs looop to ~/.local/bin/looop (override with LOOOP_INSTALL_DIR). The
script falls back to cargo install / nix profile install if no prebuilt
binary matches your platform. Make sure the install dir is on your PATH:
Cargo
Nix (flakes)
From git (latest main)
Verify
Runtime deps: just an LLM runner (pi or claude) — used for looop's per-beat
decide (tick) and to launch workers. looop is a single self-contained binary —
spawning, listing, killing and pruning sessions all run in-process, no extra
executable required.
Sessions are stored under $LOOOP_DATA_DIR/sessions, self-contained per profile:
looop sets no extra environment and shares no global state, and session ids are
bare (the pulse is pulse). (Workers that touch code also need git or box
to sandbox themselves, but that's a worker concern.)
Config & data
- Config —
$LOOOP_DATA_DIR/config.json(overrideLOOOP_CONFIG). Lives inside the data dir so a profile is fully self-contained. One file: runner wiring (atickcommand for the per-beat decide and aninteractivecommand to launch workers, per runner) plus the pulseintervaland optionalmax_daily_usdbudget. Default runner ispi;claudeis built in. - Data / memory —
$XDG_STATE_HOME/looop/(overrideLOOOP_DATA_DIR). A plain directory holding the PLAYBOOK, goals, journal, and sensors. looop does not version it for you —git initthe data dir yourself if you want history and rollback of your policy files. Worker and pulse sessions live undersessions/in the same dir, so a profile is fully self-contained. PointingLOOOP_DATA_DIRelsewhere gives you an isolated profile with its own sessions.
LLM spend is recorded in an append-only ledger — looop meters its own per-beat
decide in-process, and workers self-report via looop _ cost; see looop cost.
Set max_daily_usd in the config to arm a daily budget breaker that skips the AI
once today's spend hits the cap (clears at local midnight).