looop — a tiny, portable, autonomous control loop for agent-driven 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
  looop is an AUTONOMOUS control loop. 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
  (looop observes them next beat); 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, relays
  worker questions to you, and helps you edit goals/answer. The concierge is an
  INTERFACE, not a decision-maker: looop decides, the concierge just talks.

    role          who                   what it does
    ----          ---                   ------------
    judgment      the pulse (looop)      sense every beat; decide + execute ONE move
    hands         worker sessions        real agents doing multi-step work; when they
                                         need a human decision they `ask` and wait
    interface     the concierge          OPTIONAL human-facing pi/claude: read state,
                  (a pi/claude YOU run)   relay asks, help you steer — never decides
    memory        the data dir           PLAYBOOK + goals + journal + mailbox = files

HOW IT RUNS
  1. `looop up` — start the pulse: a detached, autonomous loop that senses,
     decides ONE move per changed beat, and runs the worker fleet. That is looop.
  2. (optional) start a concierge to watch + steer in chat:
        pi      # then: "you are my looop concierge — show me `looop _ state`,
                #         relay any pending asks, and help me edit goals; looop
                #         itself decides. read `looop --help` first."
  3. `looop down` — stop the pulse and every live worker.
  You can skip the concierge entirely and just edit goals/PLAYBOOK files +
  answer asks with `looop _ answer`.

ONE BEAT (sense → decide → act)
  Each beat the pulse wipes + re-runs every `sensors/*.sh` so `snapshots/`
  reflects the world. If the world is UNCHANGED since last beat it stops there —
  no LLM call, nearly free. When it changed, looop hands the PLAYBOOK + goals +
  readings + pending asks to the configured `tick` runner, which emits ONE typed
  action; looop executes it (and gates risky ones). It is the SOLE senser +
  decider (single-instance flock), so beats never race. Cadence is one interval
  (config `interval`, default 60s); a move may nudge the next beat sooner. A
  daily budget breaker (`max_daily_usd`) caps spend.

============================================================================
HOW YOU STEER  (you-the-human, or the concierge acting for you)
============================================================================
looop decides on its own — you do NOT drive it beat by beat. You shape WHAT it
pursues and answer what only you can:

  • Edit goals / the PLAYBOOK — declaratively say what "good" looks like. looop
    observes the change next beat and steers toward it. Do this with the verbs
    below (single-writer) or let the concierge do it for you.
  • Answer worker asks — a worker blocked on a human decision shows up as a
    pending ask; resolve it with `looop _ answer <ask_id> "<text>"`.
  • Watch — `looop watch` (live colored log + session selector) or
    `looop _ state --json` for a structured snapshot.

VERBS (for you / the concierge — looop does NOT need these to act):
  looop _ state [--json] | _ wait [--json]   read state (blocking with `_ wait`)
  looop _ answer <ask_id> "<text>"   resolve a worker's pending ask
  looop _ goal write <id> [body|stdin] | _ goal archive <id>
  looop _ sensor write <name> [script|stdin]
  looop _ playbook write [body|stdin]
  looop _ notify <message…>          surface a notice (journaled)
  (multi-line bodies: omit the inline body and pipe it on stdin / heredoc.)

WORKER CONTRACT (auto-injected into every worker — for reference)
  A worker that needs a human decision runs ONE blocking call and waits:
     answer=$("$LOOOP_BIN" _ ask "$LOOOP_SESSION_ID" --prompt "…" [--ref P] [--options a,b])
  It needs no terminal/attach — `ask` returns when answered (you reply with
  `looop _ answer`). Workers write only to claims/, reports/ and their own code
  sandbox; they end themselves with `_ kill`, lease shared resources with
  `_ claim`/`_ unclaim`, and self-report spend with `_ cost`.

CODE / CONFIG / DATA are cleanly separated (all overridable by env)
  EXEC    this one binary                          the program (portable)
  CONFIG  <DATA>/config.json                       one file: runner wiring
          └ override with $LOOOP_CONFIG            (seeded inline if absent)
            A runner needs TWO commands — `tick` (run one disposable decide move,
            prompt on stdin) and `interactive` (launch a worker session;
            `{{prompt_file}}` is its brief). Optional: `interval` (cadence),
            `max_daily_usd` (budget breaker), `session_ttl` (corpse retention).
  DATA    ${XDG_STATE_HOME:-~/.local/state}/looop/  the file-based memory
          └ override with $LOOOP_DATA_DIR
            PLAYBOOK.md  goals/  journal.md  sensors/*.sh   <- durable memory
            asks/ answers/   <- the worker <-> human mailbox (relayed by concierge)
            snapshots/ runs/ .lock .last-tick-hash .tick-backoff  <- scratch
            reports/         <- worker deliverables a human reads (persist)
            sessions/<id>/   <- worker + pulse sessions (babysit state, per
                                profile; auto-reaped after session_ttl, 3d).

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 goal's top priority is for looop to interview you (via a notification /
  ask) and rewrite the seed into your real goals + PLAYBOOK.

DEPENDENCIES
  Just the configured agent CLI (claude or pi) — used BOTH for looop's per-beat
  decide (`tick`) and to launch workers (and, if you run one, the concierge).
  Session management (babysit) is LINKED IN as a library and runs in-process; no
  babysit binary is required. A worker that touches code makes its own sandbox
  (box if available, else git worktree).
