14 min read

Bounded Chaos

Most "AI writes your code" demos end right where the hard part begins: the second hour. When a new model comes in, it can draft a feature in one shot. Fable 5, for instance, is capable of building whole games from one prompt! You can open it and play right away. Do you want special assets? Music? Just name it. It's really impressive.

But it's also not the job. Real feature work isn't a cold-start demo; it's the second hour, and the tenth, spent extending a system that already has a shape and opinions.

Without control, boundaries, and validations, a model tends to ignore the spec, skip the tests, and quietly ship the thing you didn't ask for. You can't fix that by trusting the model more. The trouble is structural: an LLM is non-deterministic by construction, and no amount of prompting makes it otherwise.

My workflow makes a different bet: contain, don't trust. The spec and the gates are a containment vessel. The model does real work inside it and can't talk its way past the walls. Every rule I can make deterministic is one less thing riding on the model behaving. The long game is to keep pushing that boundary outward - every gate, every spec, every promoted rule is territory reclaimed from chance.

This post walks the one flow I run for real feature work, end to end. It's a set of skills and review agents that turn a loose intent into a reviewed, shipped pull request. Each phase is another wall of the vessel; I'll walk through what every phase is for and the state machine the work moves through.

The feature flow#

For any work that earns a spec, the whole chain runs in a fixed order. Each phase is a command I run explicitly. Nothing auto-advances. Partly that keeps me in the loop, but there's a mechanical reason too: an auto-advancing chain balloons the context fast (each validation pass dumps its findings into the same session the next phase inherits), and a runaway context means a token blowup, which means a doomed run. So the workflow stops between phases. Continuity lives on disk: every phase starts in a fresh, isolated session and picks up the state the previous one left behind. No single context carries the whole feature from start to finish; each phase is bounded and resumable.

Each command owns one phase of the flow. Here's what every phase is for:

explore - capture requirements, size the work#

This phase runs a relentless "grill" interview to pin down intent. It then grounds that intent in the domain: it updates CONTEXT.md - the project's living domain glossary, the canonical vocabulary the model has to speak - and writes ADRs for the hard-to-reverse, surprising calls. Finally it infers the tier and feature config once, up front (the tier dials are spelled out in The spec config below).

propose - turn intent into a contract#

Here a Security Engineer writes a threat model before any spec exists. The spec.md that follows carries functional requirements, API contracts, a data model, and BDD scenarios. With that in place, we run specialized agents that attack the step from different angles:

  • Project Manager decomposes the spec into vertical slices - each a piece of work that delivers a small working element of the whole feature.
  • Test Strategist designs the cross-task test strategy: which test proves which scenario, and at which level - all before any of it reaches implement.

[Human gate] Spec & tasks review

Before a line of code gets written, I read the spec, the design, and the task breakdown end to end. This is where I catch a wrong contract or a bad slice while it's still cheap - a misread requirement here costs minutes; the same mistake caught after implement costs a rebuild.

implement - build one task, test-first#

This phase builds exactly one eligible task, on its own branch, through a red-green-refactor loop; the next eligible task is picked off the dependency graph - the build order already set in propose. It runs inline or routes to the implementer agent named in the task, driving the task todo → in-progress → implemented and opening a draft PR.

[Human gate] Draft-PR review

This is the first place in the implementation phase where I stop and read code. I open the draft PR, pull the diff into context, and make sure I understand what the model actually built. At this point it doesn't have to be deep - just enough to grasp the changes made. That's how I'm ready to rule on the validation findings later instead of trusting them blind.

validate - stop trusting one perspective#

This phase runs the deterministic gates and fans out a parallel panel of advisory agents, plus a coverage audit that asks whether the diff actually covers the task's acceptance criteria. One rule resolves all of it: every gate has to end pass before a task is done. A gate that errors gets re-run. A gate that raises findings sends the task to review. Only when every gate comes back clean does the task go straight to done - and on that clean path it ships inline, no separate step.

review-and-ship - rule on the findings, then ship#

The findings from the validate phase get addressed in two ways: automatic and manual. Structural mappings I built into the process route mechanical findings to automatic fixes; everything else is delegated to me for manual approval. The AUTO bucket clears itself first: mechanical findings (formatting, unused import) and coverage gaps are fixed. Everything else is grouped by code region and presented for acceptance or rejection, one region at a time, and a reasoned rejection can be promoted into a reusable knowledge-base (KB) rule on the spot. Then it ships inline: one commit covering the applied fixes, a push, and the draft PR flipped to ready - into the feature integration branch, so the feature accrues as a reviewable whole before it reaches main. Shipping is the tail of this phase now, not a command of its own; the task lands review → done and shipped in one move.

learn-from-reports - never the same mistake twice#

This phase mines validation output for recurring findings and repeated rejections, then promotes the patterns into KB rules so the same mistake gets caught earlier next time.

validate-impl - final spec-completion audit#

This phase runs once every task is done. An auditor reads the cumulative diff against the spec's full FR list, checking what was actually built against what the spec claimed and flagging gaps, then asks the end-question: did we build the spec, with no orphan code, nothing missing? A clean verdict ships; a reopen loops the gaps back as follow-up tasks until the verdict is complete.

[Human gate] Feature review before main

Once the spec audits complete, the feature integration branch gets a full human review before it reaches main. The per-task gates and the coverage audit get the work ready for that review; they don't replace it.

The unit of work: tasks and their states#

The thing that actually flows through that chain is the task, one at a time. A feature is decomposed into several tasks, and each task moves through its own lifecycle independently. That lifecycle is an explicit state machine:

The state machine isn't decoration. A task can't go in-progress while its dependencies are unfinished, and finishing a task drops every task that was blocked on it down to todo. The order is enforced, not suggested.

The spec config#

Every feature carries a config.yml, inferred once in explore and editable by hand. It's a small set of dials, and the same machinery runs at every setting - the config only decides how much ceremony and which checks the feature earns. This post - built as a feature through the same flow - used this config:

tier: medium            # small | medium | large  - how much ceremony the feature earns
track: feature          # feature | technical
branch_strategy: per-task
tags: [blog, mdx, ssg, nextjs, content]
gates: [lint, format-check, type-check, unit-test, e2e-test]
agents:
  explore:   [engineering/frontend-developer, engineering/software-architect]
  propose:   [engineering/software-architect]
  implement: [code-quality-pragmatist]
  validate:  [engineering/security-engineer, code-quality-pragmatist]
  pr-review: [engineering/code-reviewer]
repos:
  - name: portfolio
    path: ~/projects/portfolio
    role: Next.js portfolio site; hosts the MDX blog

The dials:

  • tier {small | medium | large} sets how much ceremony the feature earns. small produces task files only; medium adds a full spec.md; large adds a design.md and a dedicated test-strategy.md on top. small also skips the advisory review panel and the final spec audit - a one-file change shouldn't pay for a threat model.
  • track {feature | technical} picks the shape of the work. feature runs the full business-spec flow above. technical is for refactors and debt, where the intent is already clear and there's nothing to discover - so it skips the spec and design artifacts and grounds itself in ADRs and CONTEXT.md instead.
  • gates is the gate ceiling for the feature - the deterministic checks any task is allowed to run. The set that actually runs on a given task is this ceiling intersected with that task's languages, so a Rust task never runs the TypeScript linter.
  • branch_strategy {per-task | single-branch} chooses between a draft PR per task off a feature integration branch, or one branch that accumulates commits into a single PR, shipped at the tail of review-and-ship.
  • validate_scope {per-task | per-spec | both} decides when the gates run: after each task, once over the finished feature, or both.
  • agents binds an agent to each phase - which lenses run in explore, who decomposes in propose, who reviews in validate.
  • tier_ceiling can override the default task and file budgets when a feature genuinely needs more room.

The agents review my code before I do#

validate is worth zooming in on, because it's the phase where a single perspective stops being enough. Beyond the deterministic gates, it dispatches a panel of specialized review agents in parallel, each looking at the diff through one lens. Their findings, plus the gate results, are what review-and-ship then walks me through:

The gates are mechanical - lint, type-check, tests, format - and they run before any agent weighs in. The advisory agents each read the diff through one lens (security, code quality, architecture), and Odium checks it against the task's acceptance criteria. Everything lands in review-and-ship, where I rule on the disagreements instead of grading my own homework.

The knowledge base#

The rules the workflow enforces don't live in the prompts. They live in one knowledge base, split by topic directory - architecture/, testing/, security/, frontend/, languages/, style/, and a few more. One store, so a rule has exactly one home and gets cited the same way everywhere.

The system watches itself#

Every phase leaves a trail on disk, so the containment is inspectable after the fact.

  • An event log per feature records what happens across its lifecycle: task transitions, TDD red and green, coverage-audit start and finish, gate skips, the inferred tier, and PR lifecycle.
  • A findings report per task and gate holds what the gate found. Each finding carries a severity, a category, a source (tool or model), a confidence, and a review status - pending, accepted, rejected, or noted.
  • An audit record captures the spec-completion verdict: complete, or reopen.

On top of that sits a learning loop. /learn-from-reports mines those reports across runs for recurring categories, reasoned rejections, and accepted fixes general enough to reuse, then promotes them into the knowledge base under the matching topic. The next run starts with the rule already in place, so the same mistake gets caught one phase earlier.

The loop is mechanical and the inputs are files you can read. A finding the model raised and I rejected with a reason doesn't vanish; it becomes evidence the next mining pass weighs. Over time the perimeter moves on its own: judgment calls I made once turn into rules the system enforces without me.

The parts that don't bend#

Four rules are enforced by the workflow itself, not left to per-run discretion:

  • Test-first is non-negotiable. Red before green - a failing test has to exist before the implementing code. LLMs are poor at adding coverage after the code exists: they tend to write tests that merely pass and chase a coverage number, with behavioral quality all over the map. Test-first forces the behavior to be specified before the code that satisfies it.
  • Phases run in isolated sessions. Each phase is bounded and scoped, with state handed off on disk. No single runaway context carries the whole feature.
  • Gates hard-block. A failed gate stops the flow. There's no "assert and warn" escape hatch that lets a known failure slide through to shipping.
  • The dependency graph is enforced. A task can't start until the tasks it's blocked_by are done. The build order is a property of the system, not a thing I have to remember.

Where this is going#

Everything above is the version I run daily: a pile of bash and slash commands wired to one AI runtime. It works, and it has three problems I can't ignore.

Vendor lock-in. The whole thing is married to Anthropic's models. If the pricing or the product moves the wrong way, my workflow moves with it. Thin observability. I read the trail after the fact, in YAML. I don't have a live view of what the model is actually doing while it does it. No control over the system prompts. The tool injects its own heavy prompts on every call. I can't see them, can't trim them, and I pay for the tokens.

Bondsmith is the answer I'm building. It's a provider-neutral core - a Rust binary, plus a set of target-neutral .flow contracts - that drives this same workflow across swappable runtime adapters. Two adapters are planned: the first migrates the Claude Code flow I run today; the second is built from scratch for Pi, a minimal agent harness. The point is that the process outlives any one model or vendor: swap the adapter, keep the scaffold. More on Bondsmith soon.

AIWorkflow

Thanks for reading

More like it are on the way - stick around or say hi.

Get new posts by email