Decision 057: the-loop install/upgrade drives the harness's own installer (Claude Code first), and falls back only to a documented route
- Status: proposed
- Date: 2026-08-05
- Deciders: @MadaraUchiha-314 (issue #152)
- Work item: issue-152
- Spec:
docs/specs/issue-152/ - Extends: decision-054 (the settings keys the daemon writes before a spawn, reused here as the Claude fallback) · decision-019 (the
the-loopy-onedistribution name) - Deferred: Cursor installation — issue-157 (owner decision on PR #153)
Context
Issue #152: "the-loop's CLI should provide a command to install and upgrade the CLI as well as the claude/cursor plugin … it should also allow for installation at project or user level."
the-loop ships two artifacts — this CLI and the plugin — and had no command for either. Installing meant knowing each harness's marketplace incantation and typing it inside an interactive session; upgrading meant remembering which installer owned the copy you were running (exactly the failure in issue-78); and scope could not be expressed at all, although both harnesses distinguish user- from project-level installs. decision-054 already had to solve one corner of this for the daemon, because a spawned session without the plugin has no loop at all.
The design question is not whether to install, but who does the installing. Two credible answers: the-loop writes the harness's configuration files itself (it already knows how — decision-054), or the-loop drives the harness's own plugin CLI.
Decision
Add the-loop install and the-loop upgrade — one implementation, two verbs — that build a plan of steps and drive the harness's own installer, falling back only to a route this repository already documents.
- Claude Code only, for now.
cliandclaudeare the components;cursoris rejected as unknown rather than half-supported. the-loop is a Cursor plugin (decision-015), but installing one from a terminal is a different problem — see Cursor, parked below — and it is tracked as issue-157. The module is harness-shaped, so adding it later is aBINARIESentry plus a planner, not a new command. - The harness owns installing. Where
claudeexposes a plugin surface, the-loop shells out to it (plugin marketplace add|update,plugin install|update,--scope) and lets the harness own fetching, versioning and scope. The surface is asked for (<binary> plugin --help, and a workingplugin install --help— the command actually driven), never inferred from a version number. - One fallback, already documented. No usable plugin surface → the decision-054 settings keys (user file, or the project's
.claude/settings.jsonat project scope, through the same single non-destructive writer). Where a requested scope cannot be expressed, the component is skipped with instructions, never invented. - A plan, then its execution. Every run is an ordered list of steps carrying the exact argv (or file) and one outcome each (
applied·already·skipped·failed, plusplannedunder--dry-run).--dry-runis the same plan with the execution left out, so preview and reality cannot drift. - Scope is honored or refused. A scope that cannot be expressed is skipped, never widened to the user account.
- The CLI's installer is detected, not declared —
uv tool/pipx/pipfrom where the running package lives, and a source checkout is skipped rather than installed over. Project scope installs into the project's.venv, deliberately notuv add: installing a tool must not rewrite the operator's dependency manifest. - One marketplace source.
--from→routing.harnessPlugins.marketplaceRepo→ the shipped default, validated asowner/repobefore it can reach an argv, a URL or a settings file, and printed in the plan before anything is trusted.
Consequences
Easier. A machine (or a CI job, or a Dockerfile) gets the-loop with one non-interactive command, at a scope it chooses; upgrading no longer depends on remembering how the thing was installed; the daemon and a human install now agree on one marketplace source; and every step is auditable before it runs.
Harder / accepted costs.
- Coupled to an external CLI. Mitigated by probing rather than assuming, and by a fallback that keeps working when the surface is absent — but a harness that renames its subcommands still needs a change here. The probe is where that shows up, and it fails to a documented route rather than to nonsense.
- A shipped command that executes package managers. Bounded deliberately: argv lists with no shell, a validated marketplace source, no privilege elevation, and writes confined to the harness's own config files or the named project.
- Cursor, parked (issue-157). The first cut supported it through a local clone under
~/.cursor/plugins/local/. Review parked that: as of Cursor 2.5 (Feb 2026) plugins are installed from the marketplace site or with/add-pluginin the editor;cursor-agent plugin marketplace addis reported to exist, but no CLI install command is documented, and there is no documented project-local plugin directory — so a Cursor component would have been a clone-and-hope with a permanently skipped project scope. Better one harness done properly than two half-done, with the gap tracked in the open. The research left one lasting improvement: the probe now requires a workingplugin install, not merely amarketplacecommand, so any harness that splits those two takes the fallback instead of running something that cannot work. - Two "upgrade" verbs now exist (
the-loop upgradefor the software,/the-loop:upgrade-the-loopfor a project's files). They are documented next to each other for exactly that reason.
Alternatives considered
- Always write the harness's config files (extend decision-054's writer to a user-facing command). Simplest, and it already exists — but it registers the plugin without fetching it, and it pins the-loop to today's file format. Kept as the fallback.
- A
curl … | shbootstrap installer. A second distribution channel to keep correct, and useless for the upgrade case that motivated the issue — the CLI is already there. - Wrap the harness's interactive flow (drive
/plugininside a session). Not scriptable, not CI-able, and it is the very thing the issue asks to be freed from. uv add the-loopy-onefor project scope. Rewrites the operator'spyproject.toml; installing into the project's existing virtualenv reaches the same result without editing their manifest.