Skip to content

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 same skills/, commands/ and templates (skills follow the Agent Skills open standard, so one SKILL.md serves 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 subclassing Command, @register-ing it, and dropping the module under the_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-256 HMAC (secret from an env var), exposes GET /health, and logs deliveries. Defaults come from webhooks.ghWebhook in the CLI config (cli-config.yaml — resolved via --config/env/cwd/home; see cli/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-repo collaborators.yaml but declared, never looked up) and the daemon-side notifications.events filters (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_run result, …) 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 follow routing.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, default the-loop: auto-execute) opts a work item into autonomous execution. Labelling an issue/PR spawns a session that runs /the-loop:work-on on 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. Set routing.workspace.root to turn it on (leave empty to keep the legacy static spawnWorkdir), then pick a strategy:

    • worktree (default) — one shared clone per repo under <root>/<host>/<owner>/<repo> (host = github.com or 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 is git 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: true to keep it for post-mortem; in worktree strategy 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 own closingIssuesReferences (the Development panel), the issue-<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-permissions included) 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 (honouring CLAUDE_CONFIG_DIR), and — only when your harnessArgs already 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 as workspace.trusted, and best-effort: a failure warns and still spawns. Opt out with harnessTrust.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 closed event (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-tasks closes the ticket, and the-loop sessions close is 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. N is 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#N

    Registration 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 (issues closed), or the PR itself when the PR is the work item (pull_request closed) — the receiver auto-closes the session, so a finished work item never leaves a dangling active session.

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 rule rules/the-loop.mdc carries 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:

  1. 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/).
  2. 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.
  3. 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 into learnings/topics/<category>.md.
  4. Inject. Load the capped index (first maxIndexLines lines) 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.

Released under the MIT License.