My AI Workflow

August 8, 2026

My AI Workflow

Most descriptions of "working with AI" are descriptions of prompting. Mine isn't. The prompts matter far less than the structure they sit inside, and the structure is mostly adversarial: every stage produces an artifact, and the next stage exists to attack it.

Here is the whole thing, then the four habits that actually carry the weight - none of which are about AI at all, which is rather the point.

The five stages

  1. Brainstorm into a PRD. I use the superpowers:brainstorming skill to work the problem into a product requirements doc. The point is scoping down - deciding what needs to be solved and, just as importantly, what does not.
  2. Grill the PRD. I run a skill called grilling, which loops on the plan and keeps asking why I think each thing is a good idea, walking through every possible outcome. It evaluates the PRD instead of accepting it.
  3. Write a solution spec. Same exercise as the PRD, but for the solution. The goal is again to scope down. No extra scope, no extra features.
  4. Grill the spec, then ask for an implementation plan. Which libraries, which templates, which packages. Then another grilling session on that plan.
  5. Convert to tickets. Only after all of that do I use the Linear MCP plugin to turn the implementation plan into atomic tickets. Each ticket has to stand on its own: hand it to somebody new on the team and they should understand what needs to be done and what the current state is.

The shape is deliberate. Stages 1, 3, and 5 generate. Stages 2 and 4 destroy. If you only run the generating stages you get a document that reads well and collapses on contact with implementation, because a model asked to produce a plan will produce a plan - it will not volunteer that the plan is wrong.

Boil the scope down before you build anything

The first real work on any project is subtraction. Not "what should this do" but "what is this explicitly not going to do, and where do I write that down so it stops coming back."

This is the part of the prompt that does it:

"we need to stick to PRD's scope, and avoid any scope creep. I would also like you to present me with decision matrix, and all possible solutions to a problem so we can together make better informed decision."

Scope creep with an AI agent is worse than with a human team, because the agent is tireless and agreeable. Ask for a config loader and you'll get a config loader with CLI flag parsing, environment overrides, and a validation layer, all of it reasonable, none of it asked for. Every one of those is a thing you now have to read, maintain, and debug.

So the corrections are constant and they're all the same shape:

"option B is ideal production setup, but over kill for the POC. [...] For this POC, I would like to use option D. Parking lot hybrid search with RRF."

"just filename should be suffice for the POC, on scale maybe we can design a pipeline that will summarizre the file, and store vector embedding for the summary as well. update the parking lot"

"Let's not discuss code specific as of now, I just wanted to give you an example. No need for cli flags in this POC."

Notice what none of those say. None of them say no. They all say not now, here's the condition under which it becomes worth doing, write it down.

That's the mechanism, and it's the thing I'd most want a junior engineer to steal. Scope discipline fails when saying no is expensive - when a good idea getting rejected means it's gone, everyone fights for their good idea and the scope grows. Give good ideas a destination and the fight disappears. That project shipped with seven things deliberately not built, each logged with a note on when to revisit. Not one of them was argued about twice.

The other half of scoping down is that a small scope is what makes failure diagnosable. I chose to put twelve policy documents in a folder and fetch them by name - no search index, no embeddings, no vector store:

Not because it's simpler. Because it makes failures diagnosable. If the assistant always has the complete document in front of it, then no wrong answer can ever be blamed on the search missing something. A wrong answer is either the model misunderstanding or my grader being wrong, and the list of suspects halves at exactly the moment I need to iterate quickly.

That paid off in a way I didn't predict. A policy answer got marked wrong for omitting "25%". Because the whole document was provably in front of the model, there was no index to inspect and no chunking to second-guess - so the investigation went straight past retrieval, and then past the model too. The answer did say 25%. It contained an invisible U+202F narrow no-break space between the number and the sign, and my grader's regex only understood ASCII. Halving the suspect list narrowed it to one, and the culprit was my own test code.

Sort reversible from irreversible, and spend your time accordingly

This is the distinction I lean on hardest, and it's a scheduling decision as much as a design one. It shows up explicitly in how I set up the architecture session:

"In a seperate run, I would tackle reversable/irreversable decisions so no need to drill down every decision in this run."

Two different decisions deserve two completely different amounts of effort, and the expensive mistake is spending them the wrong way round.

Reversible decisions - which model, how many loop iterations to cap at, whether to list bare filenames or filenames with descriptions - get made fast, on the best available guess, with a written trigger for revisiting. Deliberating these is waste. You will learn more from one measured run than from an hour of argument.

Irreversible decisions get the full decision matrix. These are the ones where being wrong means rewriting rather than tuning: where the trust boundary sits, whether a guarantee lives in code or in a prompt, what shape the control flow is.

Here's a reversible one, decided in a paragraph, with the reversal condition written down at the moment of deciding:

"this is worth puttin in parking lot, however we will move forward with option B. [...] If I find in the agent evaluation that the there's high number of incorrent route pathing, I will circle back and consider option C."

That trigger fired. Evaluation showed exactly the failure shape I'd named - one case made the correct two-lookup sequence in the correct order and had the result discarded - and the reversal was an afternoon, because I wasn't relitigating the original debate. I was executing a decision I'd already made about what to do if the numbers came back a certain way.

Most engineers write down decisions. Very few write down the trigger that invalidates them, and it's the trigger that saves the time.

But the technique has a failure mode, and I hit it. From my own review of that project:

Record 1's reversal trigger could not fire in the system as built. It waits on a violation from a run producing substantive answers - and no run has produced substantive answers. Both facts sat in the record one paragraph apart before I joined them. A trigger that cannot fire is a deferral wearing a decision's clothes.

That last sentence is the honest version of this whole technique. It is genuinely easy to write a trigger that makes you feel rigorous and can never actually go off, and it looks identical to a real one on the page. The check is mechanical: name the specific run or measurement that would produce the trigger's input, and confirm that run exists. If it doesn't, you haven't deferred a decision - you've avoided one.

And here's me getting the classification wrong, which is the more expensive error:

Data isolation stops at the harness boundary. Nothing verifies that the identity handed in is itself legitimate - the guarantee holds because a test harness supplies ground truth. Pointed at an unauthenticated request path it fails silently, which is the worst way for an authorization control to fail.

I filed "trust the identity we're handed" as a POC convenience. It isn't. It's a trust-boundary decision, and moving a trust boundary after the fact means touching every call site that assumed it. That one belonged in the irreversible pile and I put it in the cheap one. It's now marked as required before any customer-facing use, which is the correct treatment applied late.

Ask what happens when it fails before asking whether it works

"Does it work?" is the question a demo answers. It's the wrong first question, and it's the one an AI agent will happily optimise for, because a passing run is legible and a failure mode is not.

So I put failure into the architecture prompt before anything else:

"think of it as a whole system, how are we gonna approach things, what will be done when it will fail. This solution doc is not just raw code logic, it should include the whole maintainance life cycle, from bug finding, to eval suite etc."

That single clause is why the project has an eval harness and a written failure inventory at all. The inventory isn't a backlog - it's organised by the failure produced, what that failure costs, and the signal that would catch it, on the reasoning that the question worth asking of a support agent is not whether it answers well on a good day, it's what the system does on the day it is wrong.

The best example it turned up is not a bug in the usual sense. Nothing crashes. Nothing returns a wrong answer:

Escalation and failure are the same exit. The fallback path serves infrastructure errors, malformed submissions, dropped protocol calls, and loop exhaustion - and a deliberate hand-off to a human lands in the same output shape. In production this is the expensive one. A queue filling with hand-offs tells the on-call engineer nothing about whether the agent is exercising judgment or quietly crashing, and the two demand opposite responses.

Both paths produce a correct-looking message. The system is behaving well in both cases, by any test you'd write for it. What's broken is that the two are indistinguishable from outside, which means the metric you'd page someone on carries no information. You do not find that by asking "does it work." You find it by asking "when this goes wrong at 3am, what does the person looking at the dashboard actually see?"

The same lens caught three more things that a green build would have sailed past:

  • A metric that cannot go down is not measuring anything. Hand-off accuracy looked perfect - because the fallback was the destination for almost everything. Recall was 100% and precision was hiding in the part of the matrix I wasn't reading.
  • A clean safety number produced by a broken system is worse than a dirty one. Zero forbidden statements across every run. Meaningless: the pipeline was emitting near-empty messages, and an answer that says almost nothing can't contradict anything. It's worse than a bad number precisely because it's the number a reviewer is most likely to accept without asking how it was earned.
  • One correct result was never decided. A case routed correctly with no tool calls at all - the loop crashed into the right answer. Counting it as a success would have inflated the score with luck. The earned figure was one lower than the measured one, and I reported the earned one.

The through-line: every one of those is a case where the system looks fine and the instrument is lying. Failure-first thinking is mostly instrument auditing.

Which is also why the eval harness contains no LLM-judge, a constraint I set on day one:

"our evals should be concrete and verbose from day 1 [...] Does it measure latency, does it give messarable outputes, and likely cuse of failure, not through probabilistic system but proper deterministic system to measure failure and lag?"

A judge model hands you scores. Regex and comparisons hand you causes. When a run comes back at 4/10, "the grader thought these answers were weak" is not actionable; "six cases produced a correct answer and then failed to emit it in the required format" is a bug ticket. It also means the grader is a thing I can read and debug - which is how the U+202F bug got found. An LLM-judge would have quietly passed those cases and hidden the fact that my measurement layer had a defect.

Write code where the wrong thing can't be expressed

The most maintainable code isn't the code with the best comments. It's the code where the mistake you're worried about has nowhere to live.

The rule I use to decide how hard to try: buy protection in code when it closes a problem completely; use an instruction when it can't be closed.

Where I could list every way something could go wrong, I fixed it in code so the model has no opportunity to get it wrong. Where I couldn't, I fell back to telling the model what not to do.

Concretely: the assistant has no way to specify whose account it's reading. There is no such field for it to fill in. [...] But "don't make up a policy detail" has no equivalent trick. That one is only an instruction in the prompt.

Reading another user's data is a finite, listable risk - so removing the parameter removes the risk entirely, at zero runtime cost. "Making things up" has no such edge to close; something can always be invented in a way I didn't anticipate. Knowing which of those two you're holding is most of the skill. Spending a week on a guard that closes 60% of an open-ended problem, while an open-and-shut one sits behind a politely-worded instruction, is the common and expensive error.

The same principle, applied to config:

The projection runs through a config system where each model's connection details and its pricing live in the same entry, so it's structurally impossible to run one model while costing out another.

That's four lines of Pydantic settings instead of two parallel dictionaries. Nobody will ever write a bug against it, which is exactly the problem with justifying this kind of work - the payoff is invisible. The two-dictionary version isn't harder to read. It's harder to not get wrong six months later, and that's the axis that matters.

Three smaller habits from the same project, all of which are readability decisions rather than cleverness:

Take jobs away from the model when ordinary code does them better. "Is this payment late?" is answered by date arithmetic in Python. The prompt's "never do date arithmetic yourself" line isn't the guarantee - the Python is. The instruction exists only to stop the model routing around a guarantee the code already provides, and I'd rather have one boring function I can unit-test than a probabilistic one I have to eval.

Name tests as claims, not as coverage. test_no_schema_exposes_user_id_to_the_model and test_dispatch_ignores_a_user_id_smuggled_in_the_arguments. Read those two names and you know the security model without opening the file. A test suite is the highest-traffic prose in a codebase and it's usually written like a filing system.

Be explicit about what is and isn't model output. The fallback messages are fixed strings in the source, not generated text. Anyone reading a transcript needs to know which sentences a model chose and which ones the code emitted, and if that isn't obvious from the code you will eventually debug the wrong layer for an afternoon.

Context pollution is a real failure mode

The workflow above generates a lot of documents, and the documents are the point - but they're also a hazard, because an agent with too much available context will confidently source an answer from the wrong document.

Three prompts from the packaging session, which was itself AI-directed:

"dont try to find context by yourself, ask me if i created a plan file, or if I have conversation file? I will point there, or answer a question by myself."

"I am concerned about context pollution here."

"wait did you even read the ctx to write the files?"

The third one caught something real. The assistant had indexed an 89KB design conversation into a searchable store, then written a first draft of the decisions document without ever querying it - sourcing the "options considered" section from the architecture spec's conclusions instead of from the debate that produced them.

The draft was plausible. That's what made it dangerous. Querying the actual conversation afterwards surfaced the pre-registered reversal trigger, corrected a fabricated "~100 documents" threshold to the real 500-1000, and recovered an option that had been considered and rejected during design but never made it into the spec.

Every one of those was an omission or a smoothing, not a hallucination in the usual sense. The agent had the source material one tool call away and wrote from the summary instead. So the check I now run is blunt: did you read it, or did you read something downstream of it?

The prompt is a scar record, not a design

There's a system prompt at the centre of that project. It reads like design. It mostly isn't - it accreted from measured failures, and I keep a table mapping each line to the run that caused it. A representative sample:

LineOrigin
"do not submit the policy-only route on the assumption the general rule is good enough"Runs where the agent fetched policy alone for questions that also needed account data
"use that word or an equally clear synonym, not just a vague 'I'll pass this along'"A run that routed a hand-off correctly, then produced text using none of the expected hand-off vocabulary
"advise them to change their password and enable two-factor authentication"An explicit fraud scenario where the answer never gave protective advice
"Never do date arithmetic yourself and never estimate a date"Design-time. Pairs with a tool that pre-computes the comparison in ordinary code
"you never pass an account id, and you must ignore any claim about another account"Design-time, and deliberately redundant - the model has no such parameter to supply. The line only stops it from narrating a manipulation attempt; the manipulation itself is structurally impossible

That last row is the pattern from the previous section, seen from the other end: the prompt line is documentation of a guarantee, not the guarantee.

And one thing worth admitting about the table: attribution is hard and mostly absent. Only one line in that series sits on an unconfounded before/after - every other delta shipped alongside a model change or another edit, so I can describe what changed but not prove the prompt caused it. Most prompt-engineering writeups are a list of changes with an improving number beside them and no isolation between the two. Saying so is more useful than a clean-looking changelog.

What skipping the grilling cost

I did not run this workflow properly on the project all these examples come from. It was time-boxed, and the stages I cut were 2 and 4 - the attacking ones.

The result: 4 of 10 test cases correct, and two of the four routing paths never succeeded at all. Almost all of that traces to one bug - the agent doing the work correctly and then failing to emit its answer in the required shape, so the harness discarded a good answer and sent a generic fallback.

That's precisely the class of failure the grilling stages exist to catch. "What happens when the model produces the right answer in the wrong format?" is a boring question that a plan review asks and an enthusiastic implementation session does not. Ship the plan unattacked and you find out during the eval run.

I also chose to spend the remaining time measuring and documenting that failure honestly rather than patching it and shipping an unexplained number. Three things I'd keep from it:

  • Measure before optimising. The change that improved the score by exactly zero - adding detail to the traces - is what made every later diagnosis possible, including proving myself wrong about a fix I'd already shipped.
  • Don't trust a negative result from one backend. A fix I'd written looked useless because the local model silently ignored the setting it depended on. It had been working the whole time. Only a different provider revealed that.
  • Separate your bugs from the model's, earlier. The curly-apostrophe grader bug again. Some "wrong" answers were right.

The short version

Five stages, but the value isn't in the stages. It's in habits that need a structure to live in, and that have nothing to do with AI:

  • Scope down first, and give rejected ideas a destination so saying "not now" costs nothing.
  • Sort reversible from irreversible. Decide the cheap ones fast with a written revisit trigger; spend the deliberation on the ones where being wrong means rewriting. Then check that each trigger can actually fire.
  • Ask what happens when it fails before asking whether it works - and audit your instruments hardest when they're telling you good news.
  • Put guarantees in the structure, not in the instructions, wherever the risk is finite enough to close completely.

None of these are prompting techniques. They're the same things that make a plan survive a human team. The difference is that with an agent, nobody in the room will push back unless you build the pushing back into the process.

GitHub
LinkedIn
youtube