Skip to main content

the-loop CLI

A lightweight, extensible command-line companion to the the-loop plugin, written in Python (stdlib-only core — zero runtime dependencies). Python is intentional: it leaves room to add self-learning / ML capabilities later (mostly exposed as Python SDKs).

Install

From PyPI (published as the-loopy-one — the base name the-loop was taken; the import package and CLI keep the natural the_loop/the-loop):

pip install the-loopy-one            # or: uv pip install the-loopy-one
pip install "the-loopy-one[config]"  # + PyYAML, for reading .the-loop/config.yaml defaults
the-loop --help

For local development the-loop uses uv (its declared Python package manager). From the repo root:

uv sync                     # installs the workspace (this CLI + dev tooling) from uv.lock
uv run the-loop --help      # run the CLI

Or install this package on its own with any PEP 517 installer:

uv pip install -e .            # or: pip install -e .
uv pip install -e ".[config]"  # PyYAML, for reading .the-loop/config.yaml defaults
uv pip install -e ".[dev]"     # pytest + commitizen

This exposes the primary CLI: the-loop. Releases are automatic: on merge to main, .github/workflows/release.yml runs cz bump to derive the next version from the Conventional Commits / PR titles since the last tag (feat → minor, fix → patch, BREAKING CHANGE → major), tags it, and publishes to PyPI via Trusted Publishing (OIDC — no stored token). Merges with no feat/fix/breaking change publish nothing. See docs/decisions/decision-019.md.

Commands

gh-webhook — GitHub webhook receiver

the-loop gh-webhook start [--host 127.0.0.1] [--port 8787] [--path /gh-webhook] \
                          [--pidfile .the-loop/gh-webhook.pid] \
                          [--secret-env THE_LOOP_GH_WEBHOOK_SECRET] \
                          [--route | --no-route]
the-loop gh-webhook stop  [--pidfile .the-loop/gh-webhook.pid]
  • Verifies the GitHub X-Hub-Signature-256 HMAC when the secret env var is set (export THE_LOOP_GH_WEBHOOK_SECRET=...). The secret is read from the environment, never a flag, so it doesn't leak into process listings.
  • GET /health returns 200 ok.
  • Defaults can come from .the-loop/config.yaml (webhooks.ghWebhook) when PyYAML is installed; flags always override.
  • --route (default from webhooks.ghWebhook.routing.enabled) routes each verified event to the registered harness session working that item: the router extracts the work item(s) from the payload (issue/PR number, PR head-branch issue-<n> convention, closing keywords, workflow_run/check_* PRs), deduplicates on X-GitHub-Delivery, and the dispatcher resumes the matched session via its official CLI (claude -p … --resume <session-id> / cursor-agent -p … --resume <chat-id>), one event at a time per session, in parallel across sessions. Unmatched events follow routing.spawnOnUnmatched (never drops; always spawns + registers a session). Design: docs/specs/issue-15/design.md, docs/decisions/decision-016.md.
  • Config hot-reload: while the receiver runs, edits to webhooks.ghWebhook.routing / events are picked up on the next received event — no restart. The soft policy (events filter, label, spawn policy, harness/runner, per-harness args, prompt templates) swaps live; the dedup cache, per-session queues and registry are preserved. Infrastructural settings (host/port/path, secretEnv, maxConcurrentDispatches, dedupCacheSize, registryDir, webTerminal) still need a restart. An invalid edit is logged and the previous config kept.
  • Authorized-actor guard (prompt-injection remediation): the receiver acts only on actions by logins in routing.authorizedUsers — comments/reviews and issue/PR labels/opens from anyone else are dropped before dispatch (CI/system events, which carry no human instructions, still pass; a PR-close still auto-closes the session). Empty ⇒ falls back to ticketing.github.owner, else fails closed with a warning. Each operator runs their own instance for their own login(s). See docs/decisions/decision-023.md.

sessions — link work items to harness sessions (webhook routing)

the-loop sessions register --work-item github:OWNER/REPO#N --harness claude \
    --harness-session-id "$CLAUDE_SESSION_ID" [--cwd .] [--force]
the-loop sessions list  [--status active|closed] [--format table|json]
the-loop sessions close --work-item github:OWNER/REPO#N
  • The registry lives in webhooks.ghWebhook.routing.registryDir (default .the-loop/sessions/, git-ignored) as one human-inspectable JSON file per session; writes are atomic, so concurrent sessions on the same machine are safe.
  • One work item ↔ one active session; --force replaces a stale registration.
  • Claude Code sessions register with $CLAUDE_SESSION_ID; Cursor sessions register with the chat id they were launched with (non-interactive cursor-agent ls is unreliable for id discovery, so the id is captured at registration time).
  • When a work item's PR is merged or closed, the receiver auto-closes the session (on the pull_request closed event) — no manual sessions close needed.

Label-gated auto-execution (spawnOnUnmatched: labeled): give an issue/PR the configurable routing.autoExecuteLabel (default the-loop: auto-execute) and the receiver spawns a session and starts /the-loop:work-on on it — then routes that item's later activity (comments, reviews, CI, its linked PR) to the same session, and auto-closes on PR merge. Label presence is read straight from the webhook payload (no extra API call). A new issue without the label is received and ignored.

poll — pull ingress (provider-agnostic) when a webhook can't reach you

the-loop poll start [--interval 60] [--once] \
                    [--state-file .the-loop/poll-state.json] \
                    [--pidfile .the-loop/poll.pid]
the-loop poll stop  [--pidfile .the-loop/poll.pid]

A pull-based alternative to gh-webhook for hosts a webhook cannot reach (behind NAT/a firewall, a laptop, unreachable infra). Every --interval seconds it asks each configured provider for the label-gated work items in its scope and drives them through the same routing/dispatch/session stack the webhook receiver uses — so spawning, one-session-per-work-item, the tmux runner, harness adapters and prompt templates are all reused unchanged.

  • Provider-agnostic: the poller core and CLI carry no GitHub knobs. Which systems are polled is defined purely by polling.sources in .the-loop/config.yaml — each entry names a provider (GitHub ships; the seam admits others). GitHub is reached only through a configured source:

    polling:
      intervalSeconds: 60
      sources:
        - provider: github
          repos: [octo/repo]         # empty = fall back to ticketing.github
          monitor: { issues: true, pullRequests: true }
          label: ""                  # empty = reuse routing.autoExecuteLabel
    
  • Label-gated: only items carrying the configured label are polled. A source's label defaults to webhooks.ghWebhook.routing.autoExecuteLabel, so one label drives both ingresses.

  • No duplicate sessions: a session is spawned for a labelled item only when the registry has none; a live session is never doubled (the registry is the source of truth), so a work item maps to exactly one session — the same one on later polls.

  • Spawns tmux sessions when webhooks.ghWebhook.routing.runner: tmux — attach with the-loop sessions attach --work-item github:OWNER/REPO#N (issue-32).

  • New comments are forwarded to the item's session exactly once, deduped across polls and restarts via --state-file (git-ignored runtime state). The pre-existing thread is baselined on first sight, not replayed.

  • Config: ingress defaults come from polling in .the-loop/config.yaml (when PyYAML is installed); dispatch behaviour is reused from webhooks.ghWebhook.routing. Flags cover only the run loop.

  • Hot reload: edit polling.sources / intervalSeconds while it runs and the change is picked up on the next cycle — no restart. An invalid edit is logged and the previous config kept. (The shared dispatch config still needs a restart.)

  • Authorized-actor guard (prompt-injection remediation): the poller spawns only for items authored by a login in routing.authorizedUsers, and forwards only comments from authorized authors — everything else is ignored. Empty ⇒ falls back to ticketing.github.owner, else fails closed with a warning. See docs/decisions/decision-023.md.

  • --once runs a single cycle and exits (for a cron/systemd timer); otherwise it loops until poll stop (or SIGINT/SIGTERM), writing a pidfile like the receiver. Design: docs/specs/issue-34/design.md, docs/decisions/decision-022.md.

scenarios — query the Gherkin scenarios integration tests cover

the-loop scenarios [--root .] [--glob PATTERN ...] [--format table|markdown|json]
  • Scans integration-test files for the Gherkin-syntax docstrings the-loop requires (Feature: / Scenario: / Given-When-Then, plus an optional Requirement: link to a requirements.md) and presents them as a table — so a coding-agent harness can answer "what scenarios are tested?" without running anything.
  • Language-agnostic: Python docstrings, JS/TS block comments and Go comments all work.
  • Globs come from --glob (repeatable), else testing.integrationTestGlobs in .the-loop/config.yaml (when PyYAML is installed), else built-in defaults covering common layouts.
  • --format markdown emits a GitHub-flavoured table (for PR briefings); --format json is machine-readable (includes each scenario's steps and file:line).

Adding a command (extensibility)

  1. Create the_loop/commands/<your_command>.py.
  2. Subclass Command, set name/help, implement add_arguments and run, and decorate the class with @register.
  3. Import the module in the_loop/commands/__init__.py.

The CLI discovers registered commands automatically.

Test

pytest        # from this directory

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

the_loopy_one-0.5.1.tar.gz (68.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

the_loopy_one-0.5.1-py3-none-any.whl (59.8 kB view details)

Uploaded Python 3

File details

Details for the file the_loopy_one-0.5.1.tar.gz.

File metadata

  • Download URL: the_loopy_one-0.5.1.tar.gz
  • Upload date:
  • Size: 68.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for the_loopy_one-0.5.1.tar.gz
Algorithm Hash digest
SHA256 8b3b3799e9c1746e7c9c4b052e65b3d42484721ce1b77c74e65d9c9e5e8156ab
MD5 cfc12157b8447740cd97c62ec3788b17
BLAKE2b-256 e56dd40cacc452407a6720155526e879fe18e5c568cd95c317000c9b6c229d93

See more details on using hashes here.

Provenance

The following attestation bundles were made for the_loopy_one-0.5.1.tar.gz:

Publisher: release.yml on MadaraUchiha-314/the-loop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file the_loopy_one-0.5.1-py3-none-any.whl.

File metadata

  • Download URL: the_loopy_one-0.5.1-py3-none-any.whl
  • Upload date:
  • Size: 59.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for the_loopy_one-0.5.1-py3-none-any.whl
Algorithm Hash digest
SHA256 9d8e3a71cd930758db06c7f98a541caabf30f5f24e487153d4e0abd367168eda
MD5 4219147b9d2c510d3b3651ec648a4787
BLAKE2b-256 8baa183e5002fb19549b89380a6adc84c07115f31d755dd53b468d4d2e5f3af4

See more details on using hashes here.

Provenance

The following attestation bundles were made for the_loopy_one-0.5.1-py3-none-any.whl:

Publisher: release.yml on MadaraUchiha-314/the-loop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

13.6.0

2 files

13.5.0

2 files

13.4.0

2 files

13.3.1

2 files

13.3.0

2 files

13.2.0

2 files

13.1.1

2 files

13.1.0

2 files

13.0.1

2 files

13.0.0

2 files

12.1.0

2 files

12.0.0

2 files

11.6.1

2 files

11.6.0

2 files

11.5.0

2 files

11.4.0

2 files

11.3.1

2 files

11.3.0

2 files

11.2.0

2 files

11.1.3

2 files

11.1.2

2 files

11.1.1

2 files

11.1.0

2 files

11.0.1

2 files

11.0.0

2 files

10.6.0

2 files

10.5.0

2 files

10.4.1

2 files

10.4.0

2 files

10.3.1

2 files

10.3.0

2 files

10.2.5

2 files

10.2.4

2 files

10.2.3

2 files

10.2.2

2 files

10.2.1

2 files

10.2.0

2 files

10.1.0

2 files

10.0.0

2 files

9.15.0

2 files

9.14.0

2 files

9.13.0

2 files

9.12.0

2 files

9.11.0

2 files

9.10.0

2 files

9.9.0

2 files

9.8.0

2 files

9.7.1

2 files

9.7.0

2 files

9.6.3

2 files

9.6.2

2 files

9.6.1

2 files

9.6.0

2 files

9.5.1

2 files

9.5.0

2 files

9.4.0

2 files

9.3.0

2 files

9.2.0

2 files

9.1.0

2 files

9.0.0

2 files

8.1.0

2 files

8.0.0

2 files

7.4.1

2 files

7.4.0

2 files

7.3.0

2 files

7.2.0

2 files

7.1.1

2 files

7.1.0

2 files

7.0.0

2 files

6.2.1

2 files

6.2.0

2 files

6.1.1

2 files

6.1.0

2 files

6.0.0

2 files

5.2.1

2 files

5.2.0

2 files

5.1.0

2 files

5.0.0

2 files

4.2.0

2 files

4.1.0

2 files

4.0.0

2 files

3.0.3

2 files

3.0.2

2 files

3.0.1

2 files

3.0.0

2 files

2.1.0

2 files

2.0.1

2 files

2.0.0

2 files

1.0.0

2 files

0.22.1

2 files

0.22.0

2 files

0.21.0

2 files

0.20.0

2 files

0.19.0

2 files

0.18.1

2 files

0.18.0

2 files

0.17.0

2 files

0.16.0

2 files

0.15.0

2 files

0.14.2

2 files

0.14.1

2 files

0.14.0

2 files

0.13.0

2 files

0.12.2

2 files

0.12.1

2 files

0.12.0

2 files

0.11.0

2 files

0.10.0

2 files

0.9.0

2 files

0.8.0

2 files

0.7.0

2 files

0.6.0

2 files

This release

0.5.1 This release

2 files

0.5.0

2 files

0.4.0

2 files

0.3.1

2 files

0.3.0

2 files

0.2.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page