Automation & distribution reference
How the-loop is delivered and the automation capabilities it provides in your project. This describes what the-loop does today, in your project.
Distribution
- the-loop ships as a plugin for Claude Code and Cursor. Personas across the PDLC (PM, design, architect, dev, QA) all work through an agent harness, so the-loop meets them there.
- It is hosted on GitHub and installed via each harness's marketplace construct, directly from GitHub — no bespoke marketplace publishing. The Claude Code manifest lives in
.claude-plugin/, the Cursor manifest in.cursor-plugin/; both share the sameskills/,commands/and templates (skills follow the Agent Skills open standard, so oneSKILL.mdserves both harnesses).
Footprint tracking
- Every file the-loop creates/maintains/tracks in your repo is listed in
.the-loop/manifest.yaml. Meta files the-loop uses live under.the-loop/.
CLI companion (the-loop, Python)
the-loop is primarily a plugin, but it ALSO ships a lightweight, extensible Python CLI (cli/, package the_loop) for quality-of-life commands the plugin itself can use. Python is deliberate — future self-learning/ML capabilities are mostly exposed as Python SDKs. The core has exactly one runtime dependency — PyYAML, because the CLI's whole configuration is YAML (decision-038) — and is stdlib otherwise.
Primary CLI:
the-loop. Add a command by subclassingCommand,@register-ing it, and dropping the module underthe_loop/commands/.GitHub webhook receiver:
the-loop gh-webhook start [--host --port --path --pidfile --secret-env --route]the-loop gh-webhook stop [--pidfile]- Verifies the GitHub
X-Hub-Signature-256HMAC (secret from an env var), exposesGET /health, and logs deliveries. Defaults come fromwebhooks.ghWebhookin the CLI config (cli-config.yaml— resolved via--config/env/cwd/home; seecli/README.md) — not this project's.the-loop/harness-config.yaml; the daemon is not tied to any one repo (decision-032). The CLI config also carries the operator's own notification recipients (collaborators, same structure as the per-repocollaborators.yamlbut declared, never looked up) and the daemon-sidenotifications.eventsfilters (work-item-spawned, dispatch-failed, session-died, event-dropped-unauthorized) — issue-82, decision-035.
Webhook → session routing (
--route;webhooks.ghWebhook.routing): a received event (PR/issue comment,workflow_runresult, …) is matched to the registered session working that item and delivered by resuming that session through its official CLI (claude -p … --resume/cursor-agent -p … --resume) with a prompt that embeds the payload as untrusted data. Per-session FIFO, parallel across sessions; duplicates (X-GitHub-Delivery) processed at most once; unmatched events followrouting.spawnOnUnmatched. Design:docs/specs/issue-15/design.md, decision:docs/decisions/decision-016.md.Label-gated auto-execution (
spawnOnUnmatched: labeled): a configurable label (routing.autoExecuteLabel, defaultthe-loop: auto-execute) opts a work item into autonomous execution. Labelling an issue/PR spawns a session that runs/the-loop:work-onon it; the item's later activity (and that of every PR linked to it) resumes that session; the item being closed auto-closes it. An unlabelled new issue is received and ignored. Label presence is read from the webhook payload (no extra API call).Per-work-item checkout workspace (
routing.workspace, issue-76): the CLI daemon runs independent of any repo, so a spawned session needs a checkout of the repo an event concerns. Setrouting.workspace.rootto turn it on (leave empty to keep the legacy staticspawnWorkdir), then pick astrategy:worktree(default) — one shared clone per repo under<root>/<host>/<owner>/<repo>(host=github.comor an enterprise domain), plus a git worktree per work item (quarantined under<root>/.worktrees/…), so N concurrent work items on one repo share objects instead of paying for N full clones. The shared clone stays on the default branch and isgit fetched to stay fresh; a fresh issue gets a detached worktree (the harness makes its own branch), a PR event seeds the worktree from its head branch.clone— one folder per work item under<root>/.work-items/<slug>/, with a full clone of each repo it touches. Self-contained and simpler to reason about/clean up when a work item spans multiple repos, at the cost of a full clone per work item.
When the work item ends the checkout is removed (set
keepCheckoutOnClose: trueto keep it for post-mortem; inworktreestrategy the shared clone is always kept). One of the item's PRs merging does not remove it — the next PR in the series is written from the same checkout (issue-101). Auth is your own git credentials (e.g.gh auth setup-git). Design:docs/specs/issue-76/design.md, decision:docs/decisions/decision-034.md.An event on a PR resolves the PR's linked issue first. A PR is the vehicle for a work item, not the work item itself, so any event on it (comment — including a PR conversation comment, which GitHub delivers as
issue_comment— review, CI result) is routed to the issue(s) the PR is linked to before the PR's own number. The linkage is read from GitHub's ownclosingIssuesReferences(the Development panel), theissue-<n>head-branch convention, and closing keywords in the PR body (Closes #N,Fixes: #N,Closes OWNER/REPO#N,GH-N, an issue URL; a reference to another repository is ignored). So the PR's activity reuses the existing session for the issue — the same tmux session, keeping continuity — and an unmatched event spawns against the issue's ref, never a second session for the same work. Decision:docs/decisions/decision-036.md(issue-93).Pre-spawn harness preparation (
routing.harnessTrust, issue-90): a brand-new checkout is a directory the harness has never seen, and Claude Code's workspace-trust dialog — plus the one-time bypass-permissions disclaimer — are not permission rules, so no CLI flag (--dangerously-skip-permissionsincluded) silences them. An unattended session therefore used to sit on a modal forever. Before every spawn/respawn the dispatcher marks that exact directory trusted in the harness's own user config (honouringCLAUDE_CONFIG_DIR), and — only when yourharnessArgsalready ask for bypass mode — records the disclaimer acceptance too. Writes are narrow and non-destructive (those keys only, merged, atomic, skipped when already set, never applied to a file that does not parse), scoped to the spawn directory and never a parent, audited asworkspace.trusted, and best-effort: a failure warns and still spawns. Opt out withharnessTrust.enabled: false. Design:docs/specs/issue-90/design.md, decision:docs/decisions/decision-037.md.The label works on PRs directly — the ticketing system need not be GitHub. A PR carrying the auto-execute label is routed as its own work item (
github:OWNER/REPO#<pr-number>) when it is linked to no GitHub issue. This is the supported path when work items live in Jira or another provider: the ticket itself can't be routed, but the PR delivering it is still monitorable by the-loop's CLI./the-loop:work-on <jira-id>applies this automatically — once the PR is opened it adds the label to the PR and registers the session against the PR's ref, so PR comments/reviews/CI resume the session and that PR's merge/close auto-closes it, identical to the GitHub-ticketed flow.One work item, many PRs. A work item is frequently delivered by more than one PR (a spec PR then an implementation PR, a stacked series, a follow-up after review, one PR per repository). Every one of them is labelled and routes back to the same session, and — because a PR is a delivery vehicle, not the work item — a PR closing or merging ends only the session registered against that PR itself. A session registered against the issue the PR is linked to is left running; the issue's own
closedevent (or the poller's closure reconciliation) is what ends it. The operational consequence: a PR that merges without closing its ticket leaves the session active until the ticket closes —/the-loop:finish-taskscloses the ticket, andthe-loop sessions closeis the manual escape hatch. Decision:docs/decisions/decision-039.md(issue-101).Session registration is a workflow step. When the harness starts executing a work item (execute-tasks / work-on), it registers itself so events can find it — and closes the registration in finish-tasks.
Nis the GitHub issue number — or, for a non-GitHub-ticketed item (Jira, …), the PR number once the PR exists (the ref names the routable GitHub object, not the ticket):bash# Claude Code (session id is exposed to hooks/commands as $CLAUDE_SESSION_ID) the-loop sessions register --work-item github:OWNER/REPO#N \ --harness claude --harness-session-id "$CLAUDE_SESSION_ID" # Cursor (use the chat id this agent was launched with) the-loop sessions register --work-item github:OWNER/REPO#N \ --harness cursor --harness-session-id "<chat-id>" # on completion the-loop sessions close --work-item github:OWNER/REPO#NRegistration is best-effort: if it fails, routing degrades to log-and-drop (or spawn), never blocking the session's own work. When the registered work item is closed — the issue (
issuesclosed), or the PR itself when the PR is the work item (pull_requestclosed) — the receiver auto-closes the session, so a finished work item never leaves a danglingactivesession.
Predictability & execution guarantees
The PDLC is largely fixed; the harness should not re-derive it each run. Steps are made predictable via:
- Harness hooks — force steps to run at lifecycle points. In Claude Code:
hooks/hooks.json(SessionStart reminder). In Cursor: the always-applied rulerules/the-loop.mdccarries the same reminder (Cursor hook events have no SessionStart equivalent). - Custom code/scripts (the CLI is a natural home) where hooks are insufficient.
Self-improvement (learnings lifecycle)
the-loop is not expected to be perfect from the start; it captures learnings in your repo so it measurably stops repeating mistakes — without letting the index grow unbounded and blow the context budget. Learnings live in learnings/learnings.md (index) + learnings/learning-<nnn>.md (detail), with overflow in learnings/topics/<category>.md. Tuned by config.selfImprovement (enabled, maxIndexLines, writeGateOccurrences). Sources: user feedback (requirements/design/tasks iteration, PR reviews) and system feedback (repeated failures or insights). The skill implements this today; the Python CLI can harden it later. Four stages:
- Capture. At logical checkpoints the loop emits learning candidates from the pass/fail signals it already produces (a red check, a rejected review, a repeated reviewer comment) into a git-ignored pending queue (
.the-loop/learnings-pending/). - Write-gate. A candidate becomes a durable, injected learning only once it recurs (
writeGateOccurrences, rule-of-three) — or immediately for a clearly high-severity one-off. This keeps one-off noise out of the index. - Consolidate. At the end of a run, classify each candidate against the existing index as ADD / UPDATE / DELETE (on contradiction) / NOOP, then prune to the size cap (
maxIndexLines) by evicting the least-important/least-recent entries intolearnings/topics/<category>.md. - Inject. Load the capped index (first
maxIndexLineslines) into each agent's prompt at the start of a run; overflow detail is read on demand from the topic files.
Everything durable is checked in so you can review it and give feedback.
Reporting problems with the-loop
Feedback and bug reports about the-loop itself are filed as GitHub issues on the the-loop repository.