looop — a tiny, portable, Kubernetes-shaped control loop for your work.
This single binary IS the whole program: principles, rules, and defaults all
live inside it. Install one binary and run it — no README, no helper dir.
THE IDEA
Arrive in the morning, look at the state of the world, make the single most
important move, repeat. Now a machine does that. It is a reconciliation loop,
exactly like Kubernetes — with one deliberate twist (see RULE 1):
Kubernetes looop
---------- -----
desired state (YAML) goals/*.md how things should be (inert files)
etcd the data dir PLAYBOOK + goals + journal = memory
informers / watch sensors/*.sh scripts that print the world as JSON
reconcile loop the pulse (looop) observe -> diff -> one move per tick
scheduler policy PLAYBOOK.md priorities + guardrails, in prose
Job / Pod worker session a real agent doing hands-on work
Pod sandbox box workspace a git worktree per session
THE THREE NOUNS
PLAYBOOK.md judgment in prose: priorities, guardrails, rules. Humans AND the
AI grow it. It is data (lives with goals + journal).
goals/*.md the desired state. One file per goal: a 'goal:' frontmatter line
+ free-form dated notes. The AI creates/updates/archives them;
they are evaluated every tick but NEVER executed.
session an agent actually working — born only for hands-on work, runs in
a box workspace under babysit, gone when finished.
ONE BEAT (a tick)
1. run sensors/*.sh -> snapshot the world as JSON (fresh every tick)
2. if nothing changed since last tick -> do nothing (cheap, no AI call)
("changed" = PLAYBOOK + goals + each sensor's .signal + session transitions.
NOTE: editing a sensor SCRIPT does not itself wake the loop — only the
reading it emits does. PLAYBOOK/goals edits wake the loop immediately.)
3. else hand PLAYBOOK + goals + sensor readings + sessions to the AI, which
EMITS exactly ONE typed action (to .decision.json); looop executes it and
appends ONE line to journal.md
4. the AI process is disposable — all memory is the files in the data dir
RULES (the invariants that keep this safe and legible)
1. ONE TICK = ONE MOVE — enforced. The AI only EMITS one typed action; looop is
the SOLE executor, so a tick does at most one move and leaves one journal
line no matter how the model misbehaves ("do nothing" = the noop action).
2. THE PULSE IS UNBREAKABLE SHELL, JUDGMENT IS THE AI, MEMORY IS FILES. This
program never "thinks"; it only gathers context and runs the AI once.
3. NEVER DO IRREVERSIBLE THINGS AUTOMATICALLY (merge, public comments, closing
issues, deleting data). Prepare fully, then a worker session raises a
flag (looop flag) and waits for a human. Encode this in the PLAYBOOK.
4. BE INQUISITIVE. When a worker lacks context it asks (flag + wait) rather
than guessing. Asking is cheaper than a wrong irreversible move.
5. ADD STRUCTURE LATER. Only harden a drift into a PLAYBOOK rule or a check
once it actually hurts. Never build machinery in advance.
6. LEVEL-TRIGGERED. Each tick re-observes from scratch; the loop keeps no
"what I was waiting for" state. Re-running after days off is just a tick.
7. SINGLE-WRITER POLICY FILES. The pulse (the tick AI) is the sole writer of
PLAYBOOK.md, goals/ and sensors/. Workers write only to claims/, reports/
and their own code sandbox — never racing the pulse on policy files. The
lone exception is a human-gated meta session (setup / playbook grooming),
which shows a diff and waits for approval (RULE 3) before touching policy.
DRIVING A SESSION (a session is a live terminal, not a black box)
A worker runs under a real PTY, so you can both WATCH it and TALK to it from
outside — the same primitives a human or a script would use:
observe looop shot <id> the current screen
looop log <id> -f follow its output (tail -f)
looop status pulse + all sessions at a glance
drive looop send <id> TEXT type into its stdin
looop key <id> KEY named keys (Enter, C-c, Up, …)
looop expect <id> RE block until a regex appears (scriptable)
looop wait[-idle] <id> block until exit / until output settles
manage looop resize / restart / attach / detach
This makes a session driveable by hand, by a shell script (expect-style), or by
the pulse itself as one move. Human-gated decisions still go through ⚑flags
(RULE 3/4) — driving is for steering and automation, not for answering a flag.
CODE / CONFIG / DATA are cleanly separated (all overridable by env)
EXEC this one binary the program (portable)
CONFIG ${XDG_CONFIG_HOME:-~/.config}/looop.json one file: runner wiring
└ override with $LOOOP_CONFIG (seeded inline if absent)
optional knobs: interval/busy_interval/active_interval (cadence),
session_ttl (corpse retention), max_daily_usd (skip ticks once the
day's metered spend hits the cap — a billing circuit breaker).
DATA ${XDG_STATE_HOME:-~/.local/state}/looop/ the file-based memory
└ override with $LOOOP_DATA_DIR
PLAYBOOK.md goals/ journal.md sensors/*.sh <- the durable memory
snapshots/ prompts/ runs/ .lock .last-tick-hash <- scratch
sessions/<id>/ <- worker + pulse sessions (babysit state, per
profile; no $BABYSIT_DIR, no shared ~/.babysit).
System scratch: auto-reaped after a retention
window ($LOOOP_SESSION_TTL / session_ttl, def 3d).
Deliverables go to reports/, NOT here.
(runs/<id>/{prompt.md,output.log} = a replayable archive per beat)
BOOTSTRAP
A fresh data dir is seeded once with a starter PLAYBOOK + goals/setup.md +
goals/playbook-daily.md + sensors/today.sh, all embedded in this binary. The
starter PLAYBOOK's top priority is an interactive setup session (goals/setup.md)
that interviews you and rewrites the seed into your real config. Setup is just
a goal; the program only seeds.
DEPENDENCIES
Just the configured agent CLI (claude or pi). Session management (babysit) is
LINKED IN as a library and runs in-process — no babysit binary is required.
The pulse provisions NO workspaces; a worker that touches code makes its own
sandbox (box if available, else git worktree), so box/git are worker concerns,
not pulse prerequisites. (gh and other tools are used only by sensor scripts /
worker agents — looop never requires them.)