feat: add /ralph-loop skill (subscription-based runner)

Subscription-based counterpart to `agentic-toolkit run`. Instead of
sandcastle + Docker + Anthropic API, dispatches each ready ticket to
a fresh Claude Code subagent (general-purpose) — same fresh-context
property as per-container sandcastle runs, but zero infra.

Trade-off: no sandbox isolation. Recommend running on a clean checkout.

Mirrors the CLI runner's project schema, phase logic, branch naming,
status transitions, and idempotency. v1 fails on first error (no retry
state machine yet) — failure-handler.ts parity is future work.

README updated with two-path workflow diagram and comparison table.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-05 11:08:31 +00:00
parent 66aa00532c
commit 3f27d10fb6
2 changed files with 223 additions and 2 deletions

View file

@ -50,10 +50,21 @@ Modes:
## Workflow
```
/grill-me → /to-prd → /to-issues → /to-project → agentic-toolkit run --project N --mode <variant>
┌─→ agentic-toolkit run --project N --mode <variant> (Docker + ANTHROPIC_API_KEY)
/grill-me → /to-prd → /to-issues → /to-project ─┤
└─→ /ralph-loop project=N mode=<variant> (Claude Code subscription, no Docker)
```
`to-project` lives in `skills/engineering/to-project/SKILL.md` and is installed by `setup.sh`.
`to-project` and `ralph-loop` skills live under `skills/engineering/` and are installed by `setup.sh`.
### Pick a runner
| Path | Auth | Sandbox | Cost | Parallelism |
|------|------|---------|------|-------------|
| `agentic-toolkit run` (sandcastle) | `ANTHROPIC_API_KEY` | Docker container | API metered | Per-container |
| `/ralph-loop` (skill) | Claude Code subscription | None — host repo | Subscription flat | Serial (one ticket at a time per Claude session) |
`/ralph-loop` is the zero-infra path: no Docker, no API key. It dispatches each ticket to a fresh Claude Code subagent (clean context per tick) using the same project schema and phase logic as the CLI runner. Trade-off: no sandbox isolation — run on a clean checkout. See `skills/engineering/ralph-loop/SKILL.md`.
## Architecture (modules in `src/modules/`)

View file

@ -0,0 +1,210 @@
---
name: ralph-loop
description: Run a GitHub Project (v2) of issues against the current repo using Claude Code subagents — one fresh subagent per ticket. Subscription-only alternative to `agentic-toolkit run` (which uses sandcastle + Docker + Anthropic API). Use after `/to-project` once the project is ready.
---
# Ralph Loop
Implement a GitHub Project's issues by dispatching each `Ready` issue to a fresh Claude Code subagent (`subagent_type=general-purpose`). Each subagent gets a clean context window — the "ralph loop" property that keeps long-running multi-ticket work coherent.
This skill is the subscription-based counterpart to `agentic-toolkit run`. Pick the path that fits your setup:
| Path | Auth | Sandbox | Cost | Parallelism |
|------|------|---------|------|-------------|
| `agentic-toolkit run` (sandcastle) | `ANTHROPIC_API_KEY` | Docker container | API metered | Per-container |
| `/ralph-loop` (this skill) | Claude Code subscription | None — host repo | Subscription flat | Serial (one per Claude session) |
**Trade-offs you accept by using this skill:**
- No sandbox isolation. The subagent has full tool access on your host repo. **Run on a clean checkout** with no uncommitted personal work.
- Serial only — one ticket at a time per Claude Code session. Cannot parallelise across machines.
- Visible: every tick happens in the foreground Claude Code UI. You can interrupt anytime.
## Inputs
Confirm the following before starting. If anything is missing, ask the user.
- **Project number** (e.g. `2`).
- **Mode**`per-ticket` or `single-pr`. Same semantics as the runner CLI.
- **Owner** — defaults to current `gh` repo owner (`gh repo view --json owner --jq .owner.login`).
- **Repo** — defaults to current `gh` repo (`gh repo view --json name --jq .name`).
- **Target repo path** — the local checkout the subagent will work in. Defaults to the current working directory.
- **Base branch** — defaults to current HEAD of the target repo (`git -C <path> rev-parse --abbrev-ref HEAD`).
Pre-flight:
- `gh auth status` shows scopes `project`, `read:project`, `repo`. If `project` missing, run `gh auth refresh -s project,read:project --hostname github.com`.
- The target repo working tree is clean (`git -C <path> status --porcelain` is empty). If not, halt and tell the user.
## Algorithm
The loop runs in the current Claude Code conversation. The orchestrator (you) coordinates state via `gh` and dispatches one subagent per ticket via the Agent tool.
### 1. Read project state
Run a GraphQL query equivalent to the runner's `PROJECT_QUERY`:
```graphql
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
projectV2(number: $number) {
id
field(name: "Status") { ... on ProjectV2SingleSelectField { id options { id name } } }
items(first: 100) {
nodes {
id
fieldValues(first: 20) {
nodes { ... on ProjectV2ItemFieldSingleSelectValue { name field { ... on ProjectV2SingleSelectField { name } } } }
}
content {
... on Issue { id number title body labels(first: 20) { nodes { name } } assignees(first: 5) { nodes { login } } }
}
}
}
}
}
}
```
Parse each item to: `{ number, nodeId, itemId, title, body, labels, status, blockedBy, kind }` where:
- `kind = "HITL"` if labels include `hitl` or `ready-for-human`, else `"AFK"`.
- `blockedBy` = numbers extracted from the `## Blocked by` section of the body.
If the Status field is missing any of the six required options (Backlog, Ready, In progress, In review, Needs human, Done), halt and tell the user to run `/to-project`.
### 2. Autopromote
For each issue with `kind === "AFK"` AND `status === "Backlog"` AND every `blockedBy` is either `Done` in this project OR not present in this project: set its Status to `Ready`.
### 3. Compute current phase + pick next
Topologically partition issues by `Blocked by` (issues with all blockers in earlier phases form phase N). The current phase = lowest-indexed phase with any non-Done issue.
Within the current phase, `pickNextReady` = the lowest-numbered issue with `status === "Ready"` AND `kind === "AFK"`.
If no such issue:
- `single-pr`: jump to step 9 (finalise).
- `per-ticket`: exit with "Phase N complete. Merge open PRs, then re-invoke."
### 4. Claim
- Set the issue's Status to `In progress`.
- Assign to the current viewer: `gh api user --jq .login` -> `gh issue edit <N> --add-assignee <login>` (best-effort; ignore failure).
### 5. Prepare branch
Project slug = `<repo>-p<projectNumber>` (matches CLI's `projectSlugFrom`).
Branch name:
- `per-ticket`: `claude/<projectSlug>/<issueNumber>-<title-slug>` (slug = lowercased, non-alphanumeric -> `-`, trimmed, max 50 chars).
- `single-pr`: `claude/<projectSlug>` (reused across tickets).
In the target repo:
```sh
git fetch origin
git checkout <base-branch>
git pull
if branch exists: git checkout <branch>
else: git checkout -b <branch>
```
### 6. Dispatch subagent
Use the Agent tool. `subagent_type=general-purpose`. The prompt MUST be fully self-contained — the subagent has no memory of this conversation.
Prompt template:
````
You are implementing GitHub issue #<NUMBER> in <OWNER>/<REPO>.
# Issue title
<TITLE>
# Issue body
<BODY>
# Working environment
- Target repo path: <TARGET-REPO-PATH>
- You are on branch: <BRANCH-NAME>
- Branch base: <BASE-BRANCH>
- Do NOT change branches. Do NOT push. Do NOT open PRs. The orchestrator handles those.
# Your task
1. Implement the issue's acceptance criteria fully.
2. If the repo has a test suite, run it and resolve failures before reporting complete.
3. Make commits as you go using the repo's commit style. Don't squash; the orchestrator may amend.
4. If you encounter ambiguity that blocks progress (e.g. unclear acceptance criteria, missing context, conflicting requirements), commit any progress made and report blocked rather than guessing.
# Reporting back
Your FINAL message must be exactly one of:
- `TICKET_COMPLETE: <one-line summary of what was implemented>`
- `TICKET_BLOCKED: <one-line reason and what's needed to unblock>`
Do not include any text after that line. The orchestrator parses it.
````
### 7. Validate result
After the subagent returns:
- Read the last line of the subagent's final message.
- Count commits: `git -C <target> rev-list --count <base-branch>..HEAD`.
- Success = subagent reported `TICKET_COMPLETE` AND commit count > 0.
- Otherwise: failure (treat `TICKET_BLOCKED`, missing tag, or zero commits all as failure).
### 8. After-success actions
`per-ticket`:
- `git -C <target> push -u origin <branch>`.
- `gh pr create --base <base> --head <branch> --title "<title> (#<N>)" --body "Closes #<N>\n\nImplemented by /ralph-loop."`.
- Set Status = `In review`.
- Loop back to step 1 (state may have changed; re-read).
`single-pr`:
- Leave branch local — no push, no PR yet.
- Loop back to step 1.
### 9. Finalise (single-pr only)
When `pickNextReady` returns nothing:
- `commits = git -C <target> rev-list --count <base>..<branch>`.
- If `commits === 0`: halt with "No commits to PR. Nothing was implemented."
- Else: `git push -u origin <branch>`; open ONE PR `Implement project #<projectNumber>` with body listing `Closes #<number>` for every non-Done issue. Set those issues' Status to `In review`.
- Print PR URL.
## Failure handling (v1)
Halt on first failure. No retry. Specifically, when step 7 detects failure:
1. Set the failing issue's Status to `Needs human`.
2. Post a comment on the issue:
```
### Automated run failed (/ralph-loop)
<subagent's TICKET_BLOCKED reason or "Subagent returned without completing">
<details><summary>Last subagent message</summary>
<last 6000 chars of subagent's final message>
</details>
```
3. Stop the loop. Print: `Halted on issue #<N>: <reason>`. Tell the user to fix the underlying issue and re-invoke.
Future versions can add retry logic mirroring `failure-handler.ts` in the toolkit's `src/modules/`.
## Idempotency
Re-invoking the skill is safe and resumes from current state:
- Issues already `Done` are excluded from phase computation.
- Issues `In review` are skipped (their PR is awaiting human merge).
- The loop picks up at the next `Ready` issue.
In `single-pr` mode, the shared branch is reused across invocations — do not delete it between runs unless you're abandoning the project.
## Notes
- The skill needs `gh` CLI authed with `project + read:project + repo` scopes. The runner's CLI shares the same requirement; if you've already used `agentic-toolkit run` on the same machine, you're set.
- This skill must run with `Bash` and `Agent` tools enabled. The subagent uses standard `general-purpose` permissions (file edit, bash, etc.).
- For privacy/security, the subagent prompt includes the full issue body — do not put secrets in issue descriptions.
- The orchestrator (you, the calling Claude session) keeps full conversation context across the entire loop. Only the subagents reset. If the orchestrator's context fills up, the user can `/clear` and re-invoke; idempotency handles resumption.