Skip to content

Repository files navigation

looplens

Declarative orchestration and deep observability for autonomous agent loops, built on the Claude Agent SDK.

You describe a loop — a stable goal, an iteration prompt, stop conditions, and safety caps — and looplens runs it inside a local daemon (loops survive your terminal), while capturing a full, queryable trace: per-iteration token/cost accounting, a live event stream, an immutable audit log, and automatic "is this loop stuck?" detection — all visible in a live React UI.

New here? Read GETTING-STARTED.md — a hands-on walkthrough: the simulator tour, loop-file syntax explained line by line, daemon lifecycle, and the phone setup (ntfy notifications + Tailscale remote control).

Writing your own loops? Start with docs/CREATE-A-LOOP.md — no coding required: loop new "<what you want, in plain english>" drafts, validates, and registers a loop for you, or paste the included interview prompt into Claude Code to design one conversationally. The full authoring reference is LOOPS.md; worked examples live in loops/.

When to reach for looplens

Claude Code is pair programming. looplens is delegation. A task is loop-shaped when (1) "done" is verifiable by a command or a number, (2) progress comes in repeatable units, and (3) you wouldn't enjoy babysitting it. Nightly dependency audits, post-refactor cleanup, migrations, issue triage, doc upkeep — and building an MVP overnight from a plain-English spec (loops/build-from-spec.loop.ts). For everything else, just talk to Claude.

// loops/fix-tests.loop.ts
import { defineLoop } from "@looplens/core";

export default defineLoop({
  name: "fix-failing-tests",
  goal: "Make the entire test suite pass without weakening any test.",
  prompt: (ctx) => ctx.iter === 1
    ? "Run the suite, find the first failing test, fix the root cause."
    : `Last result: "${ctx.lastResultText}". Run the suite again and fix the next failure.`,
  allowedTools: ["Bash", "Read", "Edit", "Grep", "Glob"],
  permissionMode: "acceptEdits",
  maxIterations: 8,
  budgetUsd: 5,                       // hard spend ceiling — run is killed if crossed
  progressSignal: (ctx) => (ctx.lastResultText.match(/(\d+)\s+failing/)?.[1] ?? "0"),
  stop: [
    { kind: "predicate", name: "all-pass", fn: (ctx) => /all tests passing/i.test(ctx.lastResultText) },
  ],
});
# Install (published package — https://www.npmjs.com/package/looplens)
npm install -g looplens

# Or work from this repo instead:
#   npm install && npm run build:ui
#   npm link -w @looplens/cli     # installs the global `loop` command (one time)

# Run for real (uses your Claude Code login or ANTHROPIC_API_KEY). Auto-starts the daemon.
loop run loops/fix-tests.loop.ts

# Or prove the observability with the built-in simulator — no API spend, no auth
loop run loops/fix-tests.loop.ts --sim converging
loop run loops/fix-tests.loop.ts --sim thrashing   # watch stuck-detection fire

loop dash          # open the UI (http://127.0.0.1:4317)
loop list          # run history
loop status        # daemon + active runs
loop kill <runId>  # abort an active run
loop daemon stop   # stop the daemon

Without linking, use npm run loop -- <args> from the repo root instead.

Why this exists

Raw agent harnesses give weak headless observability: a completion notification and whatever you write to a state file. looplens treats the SDK's structured message stream as a trace source and builds the missing layer on top: history, traces, live progress, cost tracking, stuck-detection.

Architecture

Daemon-first. Loops execute inside loopd, a local daemon that owns the trace store and all active runs — the CLI and UI are just clients. Close your terminal; the loop keeps going.

packages/
  core/    engine abstraction, loop runner, trace store, health engine
  server/  loopd: run supervisor + HTTP/WS API + hosts the built UI
  ui/      React + Vite — run list, KPIs, health panel, live event stream
  cli/     loop run|list|kill|status|dash|daemon — thin daemon client
defineLoop()                          declarative author config
      │
  Supervisor (in loopd) ─ LoopRunner ─ AgentEngine ┬─ SdkEngine  (real Claude Agent SDK)
      │                                            └─ SimEngine  (deterministic scripted streams)
      │      normalize() → one NormalizedEvent vocabulary
      ▼
  TraceStore ─┬─ events.jsonl   source of truth + audit (per run, replayable)
              ├─ SQLite index   derived, queryable (rebuildable from JSONL)
              └─ live tail      WS pushes events-since-seq deltas to UI clients

Key design choices:

  • The SDK event stream is the only trace source. Every assistant turn carries token usage; the result message carries authoritative total_cost_usd. No transcript scraping.
  • Each iteration resumes the same SDK session (resume), so agent context carries across iterations for free; the daemon process holds loop state between them.
  • JSONL is canonical; SQLite is a rebuildable index (store.rebuildFromJsonl).
  • The engine is injectable. SimEngine exercises the whole stack deterministically with zero API spend — that's what the test suite and demos run on.
  • Safety rails are first-class: hard total/per-iteration spend caps (SDK-native maxBudgetUsd), tool allowlists, permission modes, loop kill, and an append-only audit trail.
  • Localhost-only, no auth — single-user by design; the API binds 127.0.0.1.

Stuck detection

No harness gives you a "stuck" signal — it must be derived. Five detectors (see packages/core/src/health.ts):

Signal Fires when
stall no activity for N seconds mid-iteration (hung tool / wedged agent)
thrash the same tool call and result repeats — circling with no change
repeated_error the same tool error recurs — not learning from feedback
plateau grader score flat for N iterations — converged short of the goal
cost_no_progress spend climbs while the declared progress signal is unchanged

The thrash detector keys on call + result together, so a healthy loop that legitimately re-runs npm test each iteration (with a changing failure count) is not flagged — only a loop getting the same result from the same action is.

Stop conditions

  • predicate — author code over accumulated context
  • command — a shell command's exit code (tests, linters, build)
  • grader — an LLM sub-agent scores the work 0–100 against a rubric; stop at a threshold

Remote control & notifications

Start a loop on the desktop, walk away, get pinged on your phone. See SPEC.md for the full design; $LOOPLENS_HOME/config.json:

{
  "remote": {
    "bind": "0.0.0.0",              // default 127.0.0.1; use your Tailscale/LAN address
    "token": "long-random-string"   // REQUIRED for any non-loopback bind (daemon refuses otherwise)
  },
  "notifications": {
    "ntfy": { "topic": "my-secret-loops-topic" },   // install the ntfy app, subscribe, done
    "webhook": { "url": "https://example.com/hook" } // or integrate with anything
  }
}
  • Notifications fire on transitions, not states: loop stuck (with the exact signals), budget 80% crossed, run finished/killed — one ping each, deduped in the daemon.
  • Auth: loopback clients are never asked for a token; everything else needs Authorization: Bearer <token>. The web UI prompts for it on 401 and remembers it.
  • Recommended transport: Tailscale — bind the tailnet address, open the UI from your phone, install it as a PWA (manifest included). No ports exposed to the internet.
  • Killing a runaway loop from your phone is one tap.

Development

npm test                      # core e2e suite, all on the simulator (5 tests)
npm run typecheck             # all four packages
npm run dev -w @looplens/ui   # UI with HMR (proxies /api + /ws to the daemon)
npm run build:ui              # production UI → served by the daemon

Status / roadmap

Working: daemon, CLI, React UI, live streaming, health engine, budget kills, sim + real engines.

Next: worktree-per-loop isolation, scheduled re-invocation mode (cron-style discovery loops), native notifications on stuck/killed, packaged binaries, optional desktop (Tauri tray) shell over the same daemon API.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages