The Rails AI Harness now writes plans I actually read
Hey. I owe you an update on what's going on with superpowers-rails - my plugin for Claude Code (and other agents). The plugin is the Rails AI Harness: conventions the agent must follow, plans I review before any code exists, and automated review on every diff. The March post has the full setup this builds on; the story below stands on its own.
Short version of this update: implementation plans used to hit 4,000 lines. The last one was 129 lines of vertical slices, and I actually read it. (The implementation plan is the markdown file a planning agent writes and I review before any code gets written.) Back in March I cut the pasted code snippets, which made plans 3-5 times shorter; this update changes their shape.
Implementation plans
The thing that annoyed me most in the Rails AI Harness was the implementation plan. In the original superpowers - Jesse Vincent's plugin that superpowers-rails builds on - the process is:
brainstorming (me + Claude) -> review (me) -> implementation plan (a planning agent) -> execution (implementer subagents)
And the implementation plan was done with the following prompt:
Write comprehensive implementation plans assuming the engineer has zero context for our codebase and questionable taste. Document everything they need to know: which files to touch for each task, code, testing, docs they might need to check, how to test it. Give them the whole plan as bite-sized tasks. DRY. YAGNI. TDD. Frequent commits.
Assume they are a skilled developer, but know almost nothing about our toolset or problem domain. Assume they don't know good test design very well.
It included every single method. Every test. A file as big as 4_000 lines of code wasn't something unusual. It also was impossible to review.
Which led to a death spiral.
If I missed something during the brainstorming phase and made a mistake, the implementer subagent - the fresh AI session that picks up the plan and writes the actual code - was forced to do it this way. (Fresh, because AI sessions degrade as their context fills.) When a human sees a problem, they stop. When an LLM sees a problem they keep going and try to make it work. With workarounds. Most often nasty, overengineered workarounds.
If only I was able to review the implementation plan properly... Oh wait, it's so long it's impossible to review and I hated my life every time I had to do it. It doesn't even make logical sense, reviewing exact code snippets disconnected from a working application. The code evolves once we connect it to the living system.
I know, I know - that was the design all along. Jesse's plans are this detailed so that even a dumb implementer model - think Haiku, small and cheap - can execute them (see this discussion in the superpowers repo).
But in my case it wasn't working.
What I own, what I delegate
There's more and more a model can decide well on its own. I'd very much own the architecture, database design and acceptance criteria (written as acceptance tests). I don't really care about the exact method body. Everything else I care about I encode as skills once and forget about: short documents the agent loads before touching code - how we structure controllers, where validations go, what our test setup looks like, how we handle authorization. A separate review agent checks every diff against those conventions and nothing else.
That's what made the March cut possible: intent-level steps instead of code snippets. "Add presence validation for email", and the convention skills carry the how. But the shape stayed the same: a flat list of bite-sized tasks.
From TDD and bite-sized tasks to vertical slices
Another annoying LLM feature is building layer by layer: first database, then models, controllers and views. Everything wired in the end. And it has one major drawback: you test the whole feature at the very end. We can do better with vertical slices.
The idea is not new. Shape Up calls it getting one piece done - small but meaningful, built end-to-end, demoable (huge fan of that book ever since I read it). The Pragmatic Programmer called it tracer bullets, and Matt Pocock recently applied the name to agents: "The principles apply harder to AI than they ever did to humans."
So the cut rule now lives in the plugin's planning skill (skills carry process steps too, not just conventions). The old prompt said "document everything they need to know". The rewritten writing-plans skill says the opposite:
A slice is one user-facing capability cut through every layer it needs (migration + model + controller + view + test) — shippable on its own and testable end-to-end on its own. [...] The spec already says WHAT and WHY. The plan adds only three things: how the work is sliced, in what order, and the end-to-end behavior that proves each slice works. Assume a skilled executor with the codebase and the convention skills — they know HOW. Do not script it.
And this is the part that does the heavy lifting - the skill's test for a correct cut. A slice counts only if all three are yes:
- Shippable on its own - merge it and the app still works. Slices are ordered, so a later one may lean on an earlier one.
- End-to-end testable on its own - it carries the test that proves it, written and green inside the slice, never deferred to a "write the tests" task at the end.
- One capability - something you'd describe to a user ("a member can now..."), not "the model layer".
If a task produces only a model, only migrations, or only policies, it's a layer - the planner has to fold it into the first slice that uses it. Shared foundations ride in the first slice that needs them. There's no linter behind any of this; the enforcement is the review step, because a layer-shaped task is easy to spot in a 129-line plan. And easy to veto.
Every slice's acceptance criteria become that test - the gate before the next slice starts. We build something small, prove it works, add the next slice on top - and the plan that describes all this becomes short enough to actually review.
TDD survived the change; it moved up a level. Each slice starts with one acceptance test: written first, watched failing, built to green. Unit tests don't disappear - they still get written under it, inside the slice. Rails old-timers will recognize outside-in testing from the Cucumber era - except now the plan format forces it.
What it looks like in practice
We shipped the dots mentoring platform with this workflow - dots is a mentoring product I'm building with two co-founders. The feature below is a progress tracker, so a learner never loses her place in the course. The whole plan is 129 lines and four items:
- A "Krok N z M" ("Step N of M") course counter in the lesson header - the deliberately tiny first slice.
- A persistent progress tracker on the exercise screen.
- A post-save orientation cue ("saved - here's what's next").
- Housekeeping: docs and a green bin/ci. Not a slice, and the plan says so - not everything is a user capability; plans carry plain tasks too.
Here's slice 1, the tiny one:
## Slice 1: A learner sees the whole arc — "Krok N z M" in the header
- [ ] Delivers: the lesson and review headers read "Krok N z M"
(today just "Krok N"), so the size of the whole course is visible.
- [ ] Touches: app/models/step_reader.rb (add modules_total),
app/views/chats/show.html.erb, app/views/modules/show.html.erb, tests.
- [ ] End-to-end test (write first, watch it fail, then build to green):
integration on chats#show — a freshly-registered learner requests
chat_path and the rendered header reads "Krok 1 z 3".
Plus a StepReader unit test: modules_total == 3.
- [ ] Notes: modules_total is a thin read (course.modules.size); the eyebrow
is a string built in the view, so the header component is untouched.
rails-model + rails-view conventions.
(Yes, the product speaks Polish. "Krok 1 z 3" is "Step 1 of 3", and the "eyebrow" is the small line above the header.)
And here's the one that earns the format - slice 2, seven files across three layers, condensed:
## Slice 2: A learner sees where she is while working an exercise
- [ ] Delivers: the exercise header carries a persistent strip —
"Krok N · done/total" + a segment bar — that expands into a per-item
checklist, the current item tagged "(tutaj)".
- [ ] Touches: a new SegmentBarComponent (extracted from the module cards),
a new ExerciseProgress presenter (pure reads, no new domain logic),
a new ProgressTrackerComponent, a Stimulus toggle controller,
exercises_controller#show, the show view, five test files.
- [ ] End-to-end test (write first, watch it fail, then build to green):
integration on exercises#show — module 1, nothing done → the strip
shows "Krok 1 · 0/2" and exactly 2 segments; the checklist names both
exercises by heading, the current one tagged "(tutaj)".
Non-regression: the module index still renders its segment bars
after the component extraction.
- [ ] Notes: the Stimulus toggle is manual-verify — the render test only
asserts its wiring (hidden panel, aria-expanded, data-action).
One honest note on "end-to-end" here: the slice's test runs at the highest level the project's test stack goes. dots deliberately has no browser tests - a project-specific call, declared in the plan's conventions header - so a slice is proven by an integration test on the real request path plus component render tests, and the Stimulus toggle is verified by hand. On a project with system specs or Playwright, the slice test would be one of those.
Notice what the plan still names: files, classes, the order, the one dependency (the cue needs the header's counter). That's not scripting - naming things is architecture, and architecture is mine. What the plan never contains anymore is method bodies and test code: that's the how, and the how is the model's. "Do not script it" draws the line there. The planning agent proposes the cut; I approve it.
The plan is the cheapest review surface I have. That's why I keep a plan at all instead of handing the agent a spec and looping until green: a bad cut vetoed at this stage costs one read. The same mistake caught in the diff costs a rebuilt feature - that's the death spiral from the beginning of this post.
I review what each slice delivers and the test that proves it works. The result is better code, for a boring reason: bad ideas get caught while they're still one line in a plan.
A green acceptance test isn't the whole gate, by the way. Every slice still passes multiple verification gates - the four checks from the March post (spec compliance, code quality, conventions, local CI) plus my own diff at the end. So far, no pleasing-but-fake green has survived them to a merge.
Now the caveats - the two places where this gets harder:
- Big codebases. dots is small, but this is not a greenfield-only trick: we run this setup on every client project at fryga now, and client codebases come in all sizes. A thin slice in a big monolith can cross three god objects - so the brainstorming step matters more there, not less: nail down what actually has to change before anyone writes a plan. And the better the setup for that specific project - convention skills that know its quirks - the better the whole thing performs. Start with a small, contained feature and see how it cuts: a new table with its own controller and views slices cleanly; a feature threaded through User probably doesn't.
- 'Dumb' implementer models. The whole point of Jesse's detailed plans was that even Haiku could execute them. Slices give that up: the plan trusts the model to decide the how, and it leans on the whole March stack - convention skills written for your codebase, the review agents, local CI. We can only keep plans this thin because of what backs them up. I'm fine with this trade (at the moment of writing: Opus 4.8 for coding, sometimes Fable 5 for planning - the big ones).
See it in action
The change is live in superpowers-rails - the exact setup we run on our client projects. I'm giving it away. Update the plugin and the writing-plans skill produces slice plans; execution runs one implementer subagent per slice, in order (not in parallel - slices are allowed to lean on earlier ones), through the same loop as before - implement, review agents, local CI, commit - each subagent starting clean against the codebase as it now is. If you're setting up from scratch, the README covers it.
Also: fryga is now sponsoring the work on the plugin. Full disclosure - fryga is my own company, so I'm mostly thanking myself. Thanks, Marcin.
How do you plan work for your agents - full snippets, intent-level tasks, vertical slices? Reach out on LinkedIn or at marcin@fryga.io. I'd love to compare notes.