deployment
Agentic Planning For Cross Package Refactors Design Tradeoffs: practical systems guide
Technical field guide on agentic planning for cross package refactors design tradeoffs for teams building dependable AI coding workflows.
# Agentic Planning For Cross Package Refactors Design Tradeoffs: practical systems guide
Large refactors that touch multiple packages expose a tension at the heart of agentic coding systems: should the agent see the entire workspace upfront and generate one massive plan, or should it discover dependencies incrementally as it works? Neither extreme works in production. The first overwhelms token budgets and collapses under cascading failures. The second wastes time and creates circular dependency traps when package B depends on the new signature in package A, but the agent hasn't refactored A yet.
This guide walks through the practical design space for planning cross-package refactors in agent loops, grounded in the compile-first constraints that actually catch breaks before they ship.
The token budget wall in workspace-wide planning
An agent that loads every file across ten packages into context to build a refactor plan will burn 50K+ tokens before writing a single line of code. Even if your LLM supports that context window, the plan quality degrades: the model starts confusing which interface belongs to which package, proposes changes that conflict with themselves three sections later, or generates a 47-step plan where steps 12–31 are all variations of "update imports."
Worse, when any single step in that monolithic plan fails—say, a type error in package C—the agent faces a choice: re-plan the entire workspace (another 50K tokens) or try to patch locally and hope the rest of the plan still holds. In practice, teams see agent loops spiral into expensive replan cycles or produce PRs that break tests in packages the agent never revisited.
The boundary isn't arbitrary. Cross-package refactors hit the point where **upfront global planning costs more in tokens and fragility than incremental planning costs in backtracking**.
Dependency graph traversal vs. lazy discovery
One pragmatic middle ground: let the agent traverse the dependency graph explicitly, but load only the entry points of each package initially. If you're renaming a function in a shared utilities package, the agent can:
1. Parse `package.json` or `go.mod` to identify which packages import the target module.
2. Load just the import statements and exported signatures from each dependent package.
3. Generate a two-phase plan: refactor the source package first (updating exports, tests, and docs), then iterate through dependents in topological order.
This keeps the planning token cost proportional to the number of package boundaries crossed, not the total lines of code. It also surfaces a key tradeoff: **topological ordering gives you predictable progress but delays feedback if a downstream package has an unexpected usage pattern**. If package F uses your utility function in a weird macro that breaks your new signature, you won't discover that until phase two.
Lazy discovery flips this: the agent refactors the source package, runs global tests, sees failures in package F, and only *then* loads F's implementation. This finds edge cases earlier but risks ping-ponging. Goatfied's validate-then-retry loop leans into the latter—run linters and tests after every small diff, catch breaks immediately, expand context only when a constraint fails. The cost is more compile/test cycles; the payoff is never accumulating a dozen broken packages before you notice.
Constraint checkpoints as backpressure
Cross-package refactors have a deceptive failure mode: the agent completes phase one, all tests pass, commits, moves to phase two, breaks package D, and now needs to revisit phase one because the real signature should have been slightly different. Without checkpoints, you're doing archaeology to figure out which of the 19 files in phase one needs to change.
Explicit checkpoints—compile, lint, and test runs after each package-level edit—create backpressure that surfaces mismatches early:
# Example constraint gate in Goatfied config
constraints:
- stage: per_package
run: |
npm run build
npm run lint
npm test -- --coverage=false
on_fail: expand_context_and_retry
When package B fails to compile against the new package A interface, the agent gets immediate signal. It can load more of B's implementation, adjust A's signature, or mark a dependency as "needs manual review" if the constraint can't be automatically satisfied. The key is that **each package's success becomes a verified invariant before the agent moves on**, not a hopeful assumption.
This is slower in the happy path where the initial plan was perfect. It's radically faster in the common path where the initial plan was 80% correct and needs local corrections.
Partial vs. atomic merge strategies
Should the agent open one PR with all packages updated, or a chain of PRs where each package is individually mergeable?
Atomic PRs (one PR, all changes) keep the codebase consistent at every commit and avoid "in-between" states where half the packages use the old API. They're easier to reason about in code review. The cost: if package G has a subtle issue, the entire PR is blocked. You can't merge the successful refactor of packages A–F while you fix G.
Partial PRs (one PR per package, with dependency ordering) let you merge validated changes incrementally. Package A gets merged, package B depends on merged A and gets merged next, and so on. If package G breaks, the blast radius is contained. The cost: you must maintain backward compatibility at each step, or explicitly mark some packages as "transitional" and accept that main is broken until the chain completes. Most teams avoid the broken-main path, which means **partial strategies force you to design refactors that can land in stages**—often a useful constraint on its own.
Goatfied's small-diff bias pushes toward partial PRs. The agent naturally produces a series of focused changes, each with its own validate gate. If your refactor truly can't be staged (e.g., a breaking protocol change across tightly coupled packages), you're better off acknowledging that upfront and designing a feature flag or compatibility shim, not trying to force an agent to produce a perfect 3,000-line atomic PR.
When to fall back to human-guided decomposition
Agentic planning breaks down when the refactor requires architectural judgment that isn't expressible as a local constraint. Splitting a monolithic package into three new packages, each with different dependency rules, needs a human to draw the boundaries. The agent can execute the split once you've defined the module map, but it won't invent a coherent decomposition from first principles.
Similarly, refactors that change concurrency models, swap out entire libraries, or involve performance tradeoffs need human decomposition. The agent can handle "rename this class and update all call sites" or "change this function signature and fix type errors" because the constraints are computable. It struggles with "is it better to use a mutex here or redesign this as message-passing?"
The practical pattern: human defines the phase boundaries and invariants, agent executes each phase under compile/lint/test gates. Don't ask the agent to be a software architect. Ask it to be a meticulous, fast implementer that never forgets to update an import.
Related reading
- [Self-hosted AI coding assistants: a complete Goatfied deployment guide](/blog/self-hosted-ai-coding-assistants-goatfied-deployment-guide)
- [Deployment playbook #2: practical Goatfied tactics for shipping PRs](/blog/goatfied-deployment-playbook-2)
