mirror of
https://github.com/Hestia-Homes/agentic-toolkit.git
synced 2026-06-08 11:37:26 +00:00
Two issues found running /ralph-loop end-to-end on a real project: 1. single-pr mode mirrored the toolkit's autopromote rule (blockers must be `Done`), which halts after each phase since tickets stay `In progress` until the final PR merges. That contradicts the README's "one PR for the whole DAG" semantics. Now `single-pr` counts a blocker resolved at status `In progress | In review | Done` so the loop completes the whole DAG in one invocation. `per-ticket` keeps the strict `Done` rule. 2. v1 failure handling treated all failures as ticket-failures and parked the issue as `Needs human` with a failure comment — wrong for environmental failures (Claude usage limit, network, interrupt) where the issue itself is fine. Now distinguished: env failures reset issue to `Ready` + un-assign; ticket failures park as before. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
225 lines
10 KiB
Markdown
225 lines
10 KiB
Markdown
---
|
|
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
|
|
|
|
Mode-specific blocker-resolution rule:
|
|
|
|
- **`per-ticket`** — a blocker counts as resolved only if its Status is `Done` (work has merged). Mirrors the toolkit CLI `autopromoteAfk` exactly.
|
|
- **`single-pr`** — a blocker counts as resolved if its Status is one of `{ In progress, In review, Done }`. The shared branch already carries the blocker's commits, so downstream tickets can stack on top within the same invocation.
|
|
|
|
For each issue with `kind === "AFK"` AND `status === "Backlog"` AND every `blockedBy` is either resolved (per the rule above) OR not present in this project: set its Status to `Ready`.
|
|
|
|
This rule lets `single-pr` mode complete the whole DAG in one invocation, matching the README's "one PR for the whole DAG" semantics. `per-ticket` mode keeps the strict gate so reviewers see one merged PR before downstream work begins.
|
|
|
|
### 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. Distinguish two kinds of failure:
|
|
|
|
**Environmental failure** — subagent didn't get a fair chance (Claude usage limit hit, network drop, runtime crash, user interrupt). The issue itself isn't blocked.
|
|
- Reset the issue's Status from `In progress` back to `Ready`.
|
|
- Un-assign the viewer (`gh issue edit <N> --remove-assignee @me`).
|
|
- Print: `Halted on issue #<N> (environmental: <reason>). Re-invoke when conditions are clear.`
|
|
- Do NOT post a `Needs human` comment — nothing for a human to action.
|
|
|
|
**Ticket failure** — subagent reported `TICKET_BLOCKED`, returned without `TICKET_COMPLETE`, or produced zero commits. The issue needs human investigation.
|
|
|
|
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.
|