L2 · 07 · Agentic Coding Workflows in Depth
Level 1 introduced agents. This page is about using them as a senior engineer would on real codebases: spec-driven development, test-first loops, large refactors and migrations, parallel agents, and reviewing at speed without lowering the bar.
By the end of this page you can
- Write a spec that lets an agent deliver a feature in one pass.
- Run a test-first loop where the agent cannot "cheat".
- Plan and execute large refactors and a Scala 2 → 3 migration with agents.
- Run several agents in parallel safely with git worktrees.
- Review agent output efficiently and measure whether it actually helps.
The senior workflow
Spec-driven development
A spec is a short Markdown file that states what to build and how you'll know it's right. It is the single best lever for agent quality, and it doubles as documentation and PR description. Draft it with the AI (ask it to interview you), then edit it yourself.
# Spec: Pickup reminder notifications ## Goal Reduce uncollected pickup orders. Customers get a reminder 48h before their hold expires. ## Behaviour 1. A daily job finds orders with status READY_FOR_PICKUP whose hold expires in 44-52h. 2. For each, publish a `PickupReminderRequested(orderId, customerId, expiresAt)` event. 3. Never send more than one reminder per order (idempotent; store `reminderSentAt`). 4. Skip orders whose customer opted out of notifications. ## Out of scope Sending the actual email/push (the Notifications service consumes the event). ## Constraints - Scala 3, Cats Effect, existing `OrderRepository` and `EventPublisher` traits. - No new dependencies. No schema change except nullable `reminder_sent_at` column (migration V42). ## Acceptance tests (must exist and pass) - Expiring in 48h -> one event; running the job twice -> still one event. - Opted-out customer -> no event. - Expiring in 10h or 4 days -> no event. ## Verification `sbt scalafmtCheckAll "core/test" "db/test"`
Read specs/pickup-reminders.md and AGENTS.md. Propose an implementation plan as a numbered list: files to create/change, the order of steps, the tests you'll write first, and risks/open questions. Do NOT write code yet. Wait for my approval.
Test-first loops agents can't game
- Write or approve the tests first. Either write them yourself, or let the agent write them in a separate step that you review carefully.
- Commit the tests. Now they're a contract.
- Instruct: "Make these tests pass. Do not modify files under
src/test/. If a test seems wrong, stop and explain why." - Enforce it: a hook or CI check that fails if test files changed in the implementation commit (L1 page 07).
- Check test strength: ask the agent to break the implementation in 3 plausible ways and confirm the tests catch each one (mutation testing by hand; tools in page 10).
Large refactors
Agents are excellent at repetitive, wide changes, if you keep each step small and verifiable.
| Step | What | Why |
|---|---|---|
| 1. Safety net | Have the agent add characterisation tests that pin current behaviour | Refactors must not change behaviour |
| 2. Inventory | "List every call site of LegacyPriceCalc, grouped by module, with a risk note" | Size the work; find surprises early |
| 3. Pattern first | Do one representative change together; save it as the reference example | The agent copies a reviewed pattern |
| 4. Batches | Apply module by module, one commit/PR each, tests green every time | Small diffs are reviewable and revertible |
| 5. Mechanical → tools | For purely syntactic changes, ask the agent to write a scalafix rule or script instead of editing by hand | Deterministic, repeatable, reviewable once |
| 6. Clean-up | Remove the old code path once all call sites are migrated | Avoid two ways of doing things |
Playbook: Scala 2.13 → Scala 3 migration with agents
- Assess. Ask the agent to list dependencies without Scala 3 artefacts, macro usage, and compiler plugins (the usual blockers). Check the official Scala 3 migration guide.
- Cross-build. Add Scala 3 to
crossScalaVersionsand get++3.x compilerunning module by module, leaves first. - Let the compiler rewrite. The Scala 3 compiler can rewrite much old syntax automatically with
-source:3.0-migration -rewrite. The Scala Center's migration tooling (e.g. the sbt-scala3-migrate plugin) helps with types and scalac options. Use these before asking an agent to edit by hand. - Agent for the rest. "Fix the remaining Scala 3 compile errors in module
coreonly. Keep behaviour identical. Prefergiven/usingfor new code but don't rewrite working implicits unless required." - Tests on both versions until you drop 2.13.
- Idiomatic pass later (enums, opaque types, new syntax) as separate, optional PRs. Don't mix it with the migration.
-rewrite can do wastes time and adds review burden.Parallel agents with git worktrees
Several agents can work at once if each has its own working directory and branch. Git worktrees give you that without cloning the repo again.
git worktree add ../store-reminders -b feat/pickup-reminders git worktree add ../store-price-fix -b fix/rounding-vat git worktree list # open each folder in its own IDE window / terminal agent session # ...when merged: git worktree remove ../store-reminders
- Give each agent an independent task (different modules). Overlapping edits mean painful merges.
- Your bottleneck becomes review. Two or three parallel tasks is usually the useful maximum for one person.
- Background / cloud agents work the same way: one branch per issue, results arrive as PRs. Write issues like specs.
Keeping long tasks on track
- Progress file: ask the agent to maintain
PROGRESS.md(done / next / decisions / open questions). It survives context resets and new sessions. - Checkpoints: commit after each green step. Revert rather than argue with a confused agent.
- Fresh context per phase: plan in one session, implement each step in a new session seeded with the spec + progress file.
- Sub-agents for exploration: "Use a sub-agent to find all places we compute tax; report file:line only." This keeps the main context clean.
Reviewing agent output fast (without lowering the bar)
- Read the summary and plan first, then the tests, then the code. Tests tell you what was actually built.
- Ask for a guided tour: "Walk me through this diff file by file. Flag anything risky, any behaviour change, and anything you weren't sure about."
- Second-opinion review: run a reviewer agent (L1 page 07) with a fresh context. It isn't anchored on the implementation.
- Check the usual suspects: error handling, concurrency, time zones, money, null/empty inputs, logging of personal data, new dependencies.
- Own it: if you can't explain a line, don't merge it.
Measuring whether it helps
Feeling faster isn't the same as being faster. Track a few honest signals for your team:
- Lead time from ticket start to merge, and PR size.
- Review rounds and rework per PR.
- Defects found after merge, and reverts.
- Time spent reviewing vs writing.
Compare similar tasks with and without agents over a few weeks. Expect big wins on boilerplate, tests, migrations and unfamiliar code, and smaller ones on novel design work.
- Pick a small real feature. Have the AI interview you, then write a spec in the format above.
- Get a plan, edit it, then write or approve tests first and commit them.
- Let the agent implement with "don't touch tests". Do the 3-ways-to-break check.
- Run a fresh reviewer agent on the diff. Note what it found that you missed, and vice versa.
Key takeaways
- Humans own the gates (spec, plan, merge). Agents run the loop between them.
- A good spec with acceptance tests is the biggest quality lever.
- Commit tests first and forbid editing them. Check test strength by breaking the code.
- Refactor in small verified batches. Use compiler rewrites and scalafix before agents.
- Parallelise with worktrees, but review capacity is the real limit.