Skip to content

alvarocortesdev/aperture-loop-skill

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Aperture — an unattended autonomous work loop for Claude Code

License: MIT

Hand Claude Code a goal and walk away. Aperture runs a budget-aware work loop that stops itself before it burns your usage limit, keeps all of its state on disk so the context window never bottlenecks, dispatches the heavy lifting to subagents, resumes across usage windows, and gates completion with an adversarial review council. In the morning you get a clean report — and an honest one.

It is a skill (a Markdown playbook + a few small shell scripts), not a service. Nothing runs in the cloud; nothing phones home.

The cake is a lie. The budget isn't.


Why

Long unattended runs fail in two boring ways:

  1. They blow the usage limit. Claude Code subscriptions have a rolling 5-hour window and a 7-day window. An eager agent will happily burn the whole thing at 2am and leave you locked out for the rest of the day.
  2. They drown the context window. A naive loop re-reads its own logs and state every iteration — quadratic cost — and the re-read content lingers in context, re-billed every turn until it compacts and loses the thread.

Aperture is built around fixing both:

  • A brake that watches your real subscription usage and winds the run down at a configurable threshold (default 70% of whichever window hits first), then schedules a resume for when the window resets.
  • A thin orchestrator + on-disk state + subagent labor design so the agent's context grows linearly and slowly, and a mid-run compaction or restart loses nothing.

What you get

  • Self-stopping at a usage threshold — soft brake at brake_at (70%), hard backstop hook at hard_at (82%) that blocks new heavy work. Reads the real 5h/7d signal; falls back conservatively if it can't.
  • Disk-externalized state — every run lives in .aperture/runs/<run-id>/ (STATE.json cursor, append-only JOURNAL.md, HANDOFF.md, MORNING-REPORT.md, lessons.md). Context loss becomes a non-event: the agent re-hydrates from disk.
  • Subagent labor — heavy I/O (reading sources, running tests, research) happens in subagents that return token-capped digests. The giant context dies with the subagent.
  • Resume across windows — at wind-down it computes the reset time and schedules a wake-up.
  • Adversarial COUNCIL — when the objective is declared done, a panel of opposed roles (Red Team, QA, Objective Owner, Architect, End-User rep, + domain roles) tries to refute that it's actually finished. A neutral Judge issues the verdict; unmet gaps become new tasks.
  • A real preflight — before going unattended it probes the usage signal, checks permissions, and flags missing logins/tokens/endpoints as NEEDS-HUMAN so 3am holds no surprises.
  • Sentinel-gated brake — the hook only acts during an Aperture run. Your normal interactive sessions are never throttled.
  • Spec-driven — uses GitHub Spec Kit if present, otherwise a built-in lightweight plan/tasks, scaled to the task.

How it works

stateDiagram-v2
    [*] --> INTAKE
    INTAKE --> PLAN: objective + mode + hours
    PLAN --> PREFLIGHT: plan / tasks (scaled)
    PREFLIGHT --> GO: checklist PASS / NEEDS-HUMAN
    GO --> WORK: explicit "go"
    WORK --> WORK: task -> subagent -> checkpoint -> check brake
    WORK --> WINDDOWN: budget>=70% | objective done | block
    WINDDOWN --> COUNCIL: if objective declared done
    WINDDOWN --> SLEEP: if work remains + budget spent
    COUNCIL --> WORK: verdict = not met + budget remains
    COUNCIL --> [*]: verdict = met
    SLEEP --> RESUME: wake-up @ reset + buffer
    RESUME --> WORK: re-hydrate from disk + re-probe
Loading

The core idea: the main session is a thin orchestrator. It holds a tiny in-context ledger and writes state to disk; it only reads disk on a cold start or resume. All heavy work is delegated to subagents that return ≤~150-token digests. So the orchestrator's context stays flat while arbitrarily much work happens around it — and adding new capabilities doesn't inflate the per-turn cost.

The brake works by a small chain:

statusLine JSON ──► statusline-writer.sh ──► ~/.claude/aperture/usage-cache.json
                                                     │
                          usage-probe.sh ◄───────────┘  (normalizes to signal_pct)
                                │
        soft brake: The Core reads it each loop iteration  ──► wind down at 70%
        hard brake: brake-hook.sh (PreToolUse) blocks heavy work at 82%  [sentinel-gated]

Requirements

  • Claude Code (CLI, desktop, or IDE).
  • A Claude Pro / Max subscription for the usage signal. On API-key / Bedrock / Vertex setups the signal isn't exposed, so the brake falls back to a conservative estimate (still safe, just blunt).
  • python3/python (preferred JSON engine for the scripts) or jq as fallback, plus bash. Tested on macOS (bash 3.2, BSD date) and Windows/Git Bash; should work on Linux. On corporate Windows, Device Guard / WDAC may block unsigned jq.exe — the scripts use Python in that case.
  • Optional: GitHub Spec Kit for richer planning artifacts.

Install

git clone https://github.com/alvarocortesdev/aperture-loop-skill.git
cd aperture-loop-skill
./install.sh

install.sh copies the skill to ~/.claude/skills/aperture/, installs the runtime scripts to ~/.claude/aperture/, and wires the statusLine + a sentinel-gated PreToolUse hook into ~/.claude/settings.json (with a backup). If you already have a custom statusLine it won't be clobbered — pass --force-statusline to replace it, or --no-wire to install without touching settings and wire it yourself.

One-time check. After your first message in a Claude Code session, run bash ~/.claude/aperture/usage-probe.sh discover. It prints the real fields of your statusLine JSON. If you don't see something like rate_limits.five_hour.used_percentage, your plan/version uses different names — adjust the field paths in statusline-writer.sh + usage-probe.sh and re-run install.sh. (The field names are the one thing that can vary between versions; everything else is robust to it.)


Usage

In Claude Code:

/aperture build out the CSV import feature and its tests

Aperture will:

  1. Intake — ask for the objective, constraints, deliverable format, mode (silent = fully unattended, reachable = away but contactable), and a soft hours estimate.
  2. Plan — produce a plan/tasks scaled to the work.
  3. Preflight — probe the usage signal, check permissions, surface anything that needs you now.
  4. Wait for your explicit "go."
  5. Work — loop autonomously, checkpointing to disk, stopping at the brake.
  6. Wind down — write the handoff + morning report, schedule a resume, then sleep.
  7. Resume — pick up from disk when the window reopens; convene the COUNCIL when the objective is met.

You can interrupt at any time — state is always on disk.


Configuration

  • Thresholdsbrake_at (default 70) and hard_at (default 82) live in each run's STATE.json under budget. The hard-brake hook reads hard_at live.
  • Autonomy ceilingSTATE.autonomy (default: non-destructive + push to feature branches + open PRs + deploy to staging). The defaults never push to the default branch and never spend money; anything beyond the ceiling is parked for you. Tune it per run.
  • Output location — runs go to ./.aperture/runs/ by default; override with the APERTURE_OUT environment variable.
  • Notification — Aperture is channel-agnostic. In reachable mode it asks The Core to notify you; wire whatever channel you like (a CLI, a webhook, a messaging integration).

Safety model

  • The brake hook is gated by a sentinel (~/.claude/aperture/RUN-ACTIVE). No sentinel → the hook is a no-op. It is created at "go" and removed when the run ends, so interactive sessions are never throttled.
  • The hard brake only ever blocks new heavy work and tells the agent to write its handoff — it never destroys anything.
  • Default autonomy is conservative: no pushes to main/master, no paid operations, no irreversible destructive ops without a human.
  • Secrets are never written into journals, digests, or reports.

Review skills/aperture/references/aperture-directives.md — that's the full set of laws the agent runs under.


Run artifacts

.aperture/runs/<run-id>/
  STATE.json         # small cursor (current state, overwritten)
  JOURNAL.md         # append-only telegraphic event log
  BRIEF.md           # the intake
  PREFLIGHT.md       # the go/no-go checklist
  HANDOFF.md         # self-contained resume contract
  lessons.md         # what it learned (read on resume)
  MORNING-REPORT.md  # your human-readable deliverable

Limitations & honest caveats

  • The usage signal depends on Claude Code exposing rate-limit data to the statusLine; field names may differ by version (see the one-time check above).
  • In-session resume assumes the machine stays awake and networked. The on-disk handoff makes a fresh- session resume possible too, but a fully cloud-resumed flow isn't included out of the box.
  • This is a methodology skill: its quality depends on the model driving it. It encodes good defaults and guardrails; it is not a guarantee of correctness. The COUNCIL exists precisely because autonomous work needs an adversarial check.

Contributing

Issues and PRs welcome. The architecture is designed to extend cheaply: keep SKILL.md a thin dispatcher, put detail behind references/, and grow the schemas additively. See §0.1 ("scaling contract") in the directives.

License

MIT.

Credits

Named after Aperture Science. Built as a personal tool and opened up in case it's useful to you. No testing tracks were harmed.

About

Unattended autonomous work loop skill for Claude Code: budget-aware self-stop at a usage threshold, disk-externalized state, subagent labor, resume across usage windows, and an adversarial review council.

Topics

Resources

License

Stars

2 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages