A working Windsurf Cascade workflow for multi-file features
Published 2026-05-08 by Owner
Cascade is Windsurf’s agent mode. The pitch is simple: you describe a feature, Cascade plans it, edits the files, and shows you a diff to accept or reject. In practice, the difference between a useful Cascade run and a wasted 4 minutes is mostly about how you brief it.
This guide is the workflow I’ve landed on after about six weeks of using Windsurf as my primary editor on a Next.js + Supabase codebase. Roughly 60 production Cascade runs, of which maybe 35 produced output I kept.
Cascade vs Cmd+I: pick one before you start
Windsurf gives you two distinct AI surfaces:
- Cmd+I (inline edit) — single-file, single-function changes. Fast, narrow, deterministic.
- Cascade (agent panel) — multi-file, planning-required changes. Slower, broader, asks for confirmation.
The mistake I made for the first two weeks was using Cascade for everything. Cascade is overkill for “rename this variable and fix the callers in this file” — Cmd+I does that in 5 seconds without a planning step.
The rule I use now: if I can hold the change in my head as one diff in one file, it’s Cmd+I. Otherwise it’s Cascade.
The Cascade brief template
Cascade’s planning quality is bounded by how well you describe the goal. The default chat input invites a one-liner (“add a search endpoint”), which is exactly the prompt that leads to messy output.
The brief I now write before every non-trivial Cascade run:
GOAL
<one sentence — what the user-visible outcome is>
FILES IN SCOPE
<list of 3–8 files Cascade should be allowed to modify>
INTERFACES TO PRESERVE
<types, function signatures, or routes that must not change>
WHAT GOOD LOOKS LIKE
<the specific success criterion: a passing test, a working request, etc.>
OUT OF SCOPE
<things Cascade often drifts into — tests, docs, refactors — that you don't want this run>
That’s about 30 seconds of typing. It cuts run time roughly in half because Cascade stops re-planning mid-execution, and it makes the diff reviewable instead of overwhelming.
A real example: adding a saved-searches endpoint
Here’s the brief I gave for a feature last week:
GOAL
Add a POST /api/saved-searches endpoint that persists a user's search filters to Supabase.
FILES IN SCOPE
- src/app/api/saved-searches/route.ts (new)
- src/lib/db/searches.ts (new)
- src/lib/db/schema.sql (add migration at the end)
- src/types/search.ts (add SavedSearch type)
INTERFACES TO PRESERVE
- The existing SearchFilters type in src/types/search.ts must not change.
- All existing routes in src/app/api/* are out of scope.
WHAT GOOD LOOKS LIKE
- POST with a valid SearchFilters payload returns 201 and the new row.
- POST without a session returns 401.
- A unit test in src/lib/db/__tests__/searches.test.ts that covers both cases.
OUT OF SCOPE
- The frontend that calls this endpoint.
- Updating README or API docs.
Cascade produced a 4-file diff in about 90 seconds. The migration was correct, the route had proper auth checks, and the test passed on first run. I accepted the diff without modification.
That outcome is not typical for vague briefs. It is typical for briefs that look like the one above.
Reviewing the diff: what to actually check
Cascade’s diff view shows you everything it changed. The temptation is to scan, see green-and-red, and click accept. Don’t.
The three things I check on every Cascade diff:
- Did it change anything outside the FILES IN SCOPE? If yes, reject and re-prompt with a stronger scope constraint. Cascade sometimes “helpfully” updates a related file you didn’t ask about, and those edits are usually wrong.
- Are the imports actually used? Cascade adds imports it then doesn’t reference. This breaks the lint pass.
- Did it preserve the interfaces you said to preserve? Search the diff for the type names you listed — if they appear in the
-lines, Cascade modified them despite the instruction. This is the most common failure mode.
If all three pass, accept and run your tests. If any fail, reject the whole diff (don’t try to fix it inline) and rewrite the brief with the missing constraint promoted to its own line.
When Cascade falls over
Three task patterns that consistently fail for me:
Visual changes. Anything CSS, layout, or design-system related. Cascade can change the code, but it has no preview loop. The diff looks plausible and the result looks wrong. Use Cmd+I in the relevant component file with the dev server open in a second window.
Tasks that need runtime knowledge. “This endpoint is slow, optimize it” — Cascade has no profile data and will rewrite something that isn’t the bottleneck. Diagnose first, give Cascade the diagnosis, then ask for the fix.
Cross-cutting refactors. “Replace all usages of X with Y across the codebase” — this is the wrong tool. Use Aider for refactors that span 20+ files, where the git-native commit-per-step model is what you want. Cascade tries to do everything in one diff, and one of the 20 files always has a quirk that breaks the whole thing.
The session discipline
A Cascade session works best as a series of small, scoped runs rather than one ambitious one. After each accepted diff:
- Commit it (
git commit -m "...") - Run the relevant tests
- Start a new Cascade brief for the next slice
This way you’re never more than one revert away from a known-good state. The git history reads like a feature being built incrementally, which is also how the next person on the codebase will want to read it.
What this is not
Cascade does not make you a faster developer at things you couldn’t already describe precisely. The brief format above is essentially a mini-spec, and writing a mini-spec is most of the work. What Cascade saves you is the typing, the file-switching, and the holding of multi-file context in your head.
If you can’t write the brief, the answer is not “give Cascade a vague version and hope.” The answer is: think harder about what you actually want, then come back. The discipline of writing the brief is the productivity gain. The agent execution is the bonus.