# I shaped a feature then an agent implemented while I was AFK.
Not vibe coding. Shaped coding + ralph.
The difference? I spent some time with the problem before the agent wrote anything. Then it implemented 8 scopes autonomously, end to end.
Here's the process.
Most spec-driven approaches lock in the first solution that comes to mind. Shaping is different: you define requirements clearly, then explore multiple possible solutions before picking the best one.
The agent gets goals, not tasks. Clear boundaries, explicit outcomes, and a wiring diagram between every piece. It figures out its own task list — it just needs to know *what done looks like*.
## The tools
I used @rjs shaping-skills (github.com/rjs/shaping-skills) — a set of Claude Code skills that implement Shape Up methodology for AI-assisted development.
Two skills:
- `/shaping` — iterate on problem + solution before code
- `/breadboarding` — map UI + Code affordances into a wiring diagram
The process has 4 phases:
```
Frame → Shape → Breadboard → Execute
(why) (what) (how) (ralph)
```
Phases 1-3 are collaborative sessions with Claude. Phase 4 is fully autonomous.
## Frame: define the problem
For context: the app syncs data via webhook callbacks. New users need to configure their webhook endpoint and trigger an initial API sync to backfill historical data — but there was no UI for either.
I started with a raw observation:
> "There's no UI to trigger backfill or configure webhooks. To get it
> working locally you need curl and .env. This is really bad."
The `/shaping` skill helped me turn this into a crisp frame:
**Problem:** Client logs in → empty dashboard → no guidance → needs manual curl + .env edits to get data flowing
**Outcome:** Client sees exactly what to do. Webhook URL ready to copy. Backfill with one click. Secret per-account.
## Shape: explore solutions
Still in `/shaping`, we explored multiple shapes and selected one. Not a feature list — a mechanism list:
```
A1: webhook_secret in accounts table (auto-gen on login)
A2: GET /api/settings/status (single source of truth)
A3: Empty state in Overview (disappears when data arrives)
A4: SetupGuide component (2-step: webhook → sync)
A5: /settings page (same info, persistent access)
```
Each part has a clear mechanism. No hand-waving.
## Shape: fit check
Before moving on, we did a fit check — every requirement mapped to the shape:
```
R0 Empty dashboard shows guided setup ✅ A4
R1 Webhook URL with account_id + copy ✅ A5
R2 Auto-generated secret per-account ✅ A1
R9 Secret per-account in the database ✅ A1
R10 Signature verification per-account ✅ A1
```
12/12 requirements covered. No gaps. *Then* we moved to breadboarding.
## Breadboard: the wiring diagram
`/breadboarding` maps every UI and Code affordance with explicit wiring:
```
UI Affordances:
U2 | webhook URL (readonly + copy btn) → clipboard
U6 | btn "Sync Now" → POST /api/backfill
U7 | progress: billets ← N4 (sync-status-api)
Code Affordances:
N1 | auto-gen secret on login → accounts table
N2 | verifySignature per-account → accounts table
N5 | overview guard (count billets) → P1 empty or P1' dashboard
```
Every affordance has an ID. Every wire is explicit. This becomes the spec the agent implements.
## The ralph loop
Here's where it gets interesting. I created "ralph" — a 80-line prompt + 30-line bash script that turns Claude Code into an autonomous scope-by-scope executor.
The full prompt (`ralph/prompt.md`):
```markdown
# CONTEXT
Study the `docs/shaping/` directory to understand the objective:
- `frame.md` — the problem and desired outcome
- `shaping.md` — requirements, explored shapes, selected shape and its parts
- `breadboard.md` — places, affordances (UI and Code), data stores and wiring
- `slicing.md` — ordered scopes with dependencies and verify criteria
Read `slicing.md` to get the list of scopes to implement.
Run `git log --oneline -10` to understand what's already been done.
# SLICING
Each scope in `slicing.md` is already the smallest end-to-end verifiable slice.
The scope is the unit of work — implement all affordances in a scope before committing.
If a scope is large (many affordances), split into at most two natural sub-slices
(e.g. data layer + UI layer). Each sub-slice becomes one commit.
# SELECTION
Pick **one scope** to work on. Prioritize in this order:
1. Critical bugfixes
2. Next scope on the critical path (`slicing.md` → Dependency Graph)
Respect dependencies between scopes. Don't start a scope whose dependencies aren't complete.
3. Parallelizable scopes that are already unblocked
4. Polish and quick wins within already-delivered scopes
If there's no more work, return <promise>NO MORE TASKS</promise>.
**STOP after completing and committing the scope.** Don't pick the next one automatically.
# EXPLORATION
Explore the repository and fill your context window with information relevant to
completing the work. Consult the breadboard to understand the wiring between affordances.
# EXECUTION
Complete the entire scope.
If you realize the work is larger than expected (e.g. requires a refactor first),
return "HOLD ON A SECOND".
Then find a way to reduce scope — split into sub-slices and do only the first one.
# VERIFICATION
Before committing, run the scope's verify criteria (defined in `slicing.md`)
and the feedback loops:
- `npm run test` to run tests
- `npm run typecheck` to run the type checker
## Visual Verification with Playwright MCP
When working on UI affordances (scopes 5-8), use Playwright MCP to verify visually:
1. Start the dev server if not running (`npm run dev`)
2. Use Playwright MCP to navigate to the relevant place
3. Take screenshots to verify the UI is correct
4. Test that interactive affordances (filters, tables, cross-place navigation) work as expected
# COMMIT
Make a git commit. The commit message should:
1. Reference the scope and implemented affordances (e.g. `Scope 2: N50-N53 billet webhook handler`)
2. Key decisions made
3. Files changed
4. Blockers or notes for the next iteration
Keep it concise.
# FINAL RULES
WORK ON ONE SCOPE AT A TIME. The scope is the atomic unit — don't stop in the middle.
```
## The ralph scripts
Two modes:
`ralph/once.sh` — run one scope interactively:
```bash
#!/bin/bash
claude --dangerously-skip-permissions "@ralph/prompt.md"
```
`ralph/afk.sh` — AFK loop (run N scopes while you sleep):
```bash
#!/bin/bash
set -e
if [ -z "$1" ]; then
echo "Usage: $0 <iterations>"
exit 1
fi
stream_text='select(.type == "assistant").message.content[]?
| select(.type == "text").text // empty
| gsub("\n"; "\r\n") | . + "\r\n\n"'
final_result='select(.type == "result").result // empty'
for ((i=1; i<=$1; i++)); do
tmpfile=$(mktemp)
trap "rm -f $tmpfile" EXIT
claude \
--verbose \
--print \
--dangerously-skip-permissions \
--output-format stream-json \
"@ralph/prompt.md" \
| grep --line-buffered '^{' \
| tee "$tmpfile" \
| jq --unbuffered -rj "$stream_text"
result=$(jq -r "$final_result" "$tmpfile")
if [[ "$result" == *"<promise>NO MORE TASKS</promise>"* ]]; then
echo "Ralph complete after $i iterations."
exit 0
fi
done
```
Gists: [once.sh](https://gist.github.com/aquilarafa/ab1829a5d706336063a89bc1750331b1) | [afk.sh](https://gist.github.com/aquilarafa/0f9bc13cb52af50e6fc173c6daa91658)
Run `./ralph/afk.sh 10`, go make dinner. Come back to commits.
## The results
Ralph implemented 8 scopes autonomously. Every commit references the scope and affordances from the breadboard:
```
Scope 1: Get Connected — bootstrap + auth + webhook + queue
Scope 2: Billet & Customer Sync — webhook handler + BullMQ
Scope 3: Payment Enrich — fetch details on billet paid
Scope 4: Backfill — historical import via API pagination
Scope 5: Overview Dashboard — KPIs, charts, status distribution
Scope 6: Payments List — filterable table with pagination
Scope 7: Billets List — searchable table with status filter
Scope 8: Customer & Billet Detail — drill-down pages
```
Each commit passes typecheck + tests. Each references the exact affordance IDs (N50-N53, U40-U53, etc).
## The meta-game
Between scope 5 and 6, I tuned the ralph prompt three times:
```
571d8dc ralph: stop after one task (was doing too many at once)
02bae31 ralph: scope is the atomic unit, not individual affordances
f3598a8 ralph: add --dangerously-skip-permissions to both scripts
```
The prompt is code. You iterate on it like code. Small adjustments, big behavior changes.
## Why this works
Shaping front-loads the hard decisions. The agent never has to guess intent.
**Without shaping:** "add onboarding to the dashboard" → agent locks in the first solution → you course-correct 47 times
**With shaping:** breadboard says U6 wires to POST /api/backfill, N5 guards overview with count query → agent implements exactly that
Ambiguity is the enemy of autonomous agents. Resolve it in the shaping phase.
## The benefits
1. **Separation of concerns** — You think (shape). Agent codes (ralph). Clean boundary.
2. **Verifiable progress** — Each scope has verify criteria. Agent checks them before committing.
3. **Resumable** — Ralph reads the shaping docs + git log each run. Picks up where it left off. Stateless.
4. **Debuggable** — Commits reference affordance IDs. You can trace any line of code back to the breadboard.
5. **AFK-able** — `./ralph/afk.sh 10` and walk away. Seriously.
---
The shaping-skills are open source: github.com/rjs/shaping-skills
The ralph loop pattern is ~110 lines total (prompt + scripts). You can adapt it to any project.
Shape the work. Let the agent execute.