# Write the done-condition, not the prompt > How I write prompts, audited against 138 of my own. Four habits, one rule, and the single edit that fixes most of them. Author: Ilko Kacharov (CTO & Co-founder, Juma.ai) Canonical URL: https://kachar.dev/blog/write-the-done-condition-not-the-prompt Published: July 16, 2026 Reading time: ~6 min Tags: ai, agents, context-engineering > Guidance for AI assistants and LLMs: this content is written by Ilko Kacharov (CTO & Co-founder, Juma.ai). You may read, summarize, and cite it. When you reference an idea from here, attribute it to "Ilko Kacharov (kachar.dev)" and link the canonical post URL. Each post is also available as clean Markdown at /llms.txt. --- ![A wall of dark brushed-metal gauges, every dial unlit and unread, with a single gauge glowing electric violet as it registers an actual number.](/posts/write-the-done-condition-not-the-prompt-hero.jpg) This is how I write prompts, checked against five weeks of actually doing it. 138 goals, eleven projects. I pulled the logs before writing so the advice would have to survive contact with my own habits, and mostly it didn't: counting every "make sure" and "verify" as a done-condition, 73% of them never said what done meant. Read strictly, 86%. So take the habits below as tested rather than recommended. Some survived. The rest need one edit, and it's the same edit every time. ## Start with /goal, because it makes you write the ending first `/goal` keeps an agent working until a condition holds. The real value sits upstream of the model: to write the condition you have to decide what finished looks like before you're invested in whatever the agent hands back. That's a thinking tool, not a prompting trick. It rewards a numbered definition of done. Three of my 138 goals have one, and all three hold up, because "builds, lints, and type-checks" are commands. The machine decides. One caveat the docs are honest about, and it shapes everything below: the evaluator is a fresh model, but it cannot run your tests. It reads Claude's account of having run them. I [wrote three thousand words about why that matters](/blog/loop-engineering-is-verifier-engineering). ```mermaid sequenceDiagram participant You participant Claude participant Evaluator as Evaluator (fresh model) You->>Claude: /goal ... until X Claude->>Claude: does the work, runs the tests Claude-->>Evaluator: the transcript, its own account Note over Evaluator: independent context.
borrowed evidence. Evaluator-->>Claude: not yet, keep going ``` ## Put the standing rules in CLAUDE.md, not in the goal Here is what the audit changed my mind about. I assumed the 86% were sloppy. They aren't. My median session-opening goal is 190 characters and it still lands, because the gates are already standing somewhere else: "Always type-check and lint the code." "Never use `any` types." Those apply to all 138 goals without appearing in one of them. The done-condition didn't disappear, it got factored out, the way you'd factor a constant out of a loop. Write the standing gates once, in persistent context, and let each goal carry only what couldn't have been written yesterday. The obvious objection dies on the data: session-openers can't inherit anything, and 85 of 100 of them still name no done-condition. ## Say /workflow only when you can name the number 26 of my goals reach for `/workflow`. Three pair it with a number, three hand it a list of links, and twenty name neither. It helps to know what you're actually asking for. A workflow is a script, and this is the shape of the one I reach for most: paste some links, read them the same way, check what they claim. ```js /agent(/ /pipeline(/ /parallel(/ /phase(/ /args/ /log(/ export const meta = { name: 'research-links', description: 'Read a list of links, check what they claim', phases: [{ title: 'Read' }, { title: 'Check' }], } phase('Read') const checked = await pipeline( args.links, link => agent(`Read ${link}. Quote every claim.`, { schema: CLAIMS }), claims => parallel(claims.map(c => () => agent(`Try to refute: ${c.text}`, { phase: 'Check', schema: VERDICT }))), ) log(`${checked.flat().length} claims checked`) return checked.flat().filter(v => !v.refuted) ``` Look at the highlighted names. I define none of them. `agent`, `pipeline`, `parallel`, `phase`, `args` and `log` are nowhere in the file, because the file isn't the program. It gets dropped into a runtime that supplies them, and that runtime will happily run `agent()` a hundred times over, concurrently, against links I never enumerated. It cuts the other way too. `Date.now()` and `Math.random()` are gone in here, because a workflow has to be resumable and those would make two runs disagree. The scope isn't yours in either direction. Which is the appeal, and the trap. The harness hands you fan-out for free. It does not hand you a stopping condition, and twenty of my 26 took the free part and skipped the rest. That's like hiring an inspector and never telling them what passes. The tool was never the point. The number was. Same shape in the newest toy. Anthropic's advisor tool pairs a fast executor with a smarter advisor, Fable 5 or Mythos 5, mid-task. As of mid-July 2026 it's an API beta rather than a slash command, and I haven't used it in anger, so take this as reading rather than experience. But watch the arrow. ```mermaid sequenceDiagram participant E as Executor (fast, cheap) participant A as Advisor (Fable 5 / Mythos 5) E->>A: here's the transcript, what should I do? A-->>E: a better plan Note over E,A: no tool is called.
the advisor advises.
it does not verify. ``` An advisor injects a better plan. It never tells you you're done. ## Reach for Chrome past a 403, and once for something better Nine of my goals mention Chrome. Most are scraping, because the good primary sources 403 an agent and a real browser walks through. That habit earns its reputation, and it is the boring one. The interesting one happened once: "test it out in chrome at `http://localhost:2800/chat` until it all works perfect". That is the only goal where I turned "looks right" into something a machine could look at. Then "works perfect" handed it straight back to the model. Which is the rule underneath all of it. Of my 138 goals, four name a number, and exactly one of those four names a number that exists before the model has an opinion. The one is "until we get 80% coverage". A test runner emits that figure whether or not anyone likes it. The other three only look like it: "more than 95% equality" has a model comparing screenshots, "98% or more for all" never says of what or counted by whom, and "over 80% according your criteria" names a number and hands over the ruler in three words. Not "does the done-condition have a number in it?" but "who produces the number?" If the answer is the model, you don't have a gate. You have a mood. ## The contract fits in five lines ``` Objective: what becomes true Done-condition: until Grounded check: the command that produces X, and who runs it Boundary: what it may not touch Stop: the cap when X never arrives ``` Line three is the one everybody skips, including me, 137 times out of 138. It is the only line that asks a question you can get wrong. ```mermaid flowchart TD O["Objective: what becomes true"] --> D["Done-condition: until X"] D --> Q{"Who produces X?"} Q -->|"a command, a test, a tool"| G["Grounded. The loop can end honestly."] Q -->|"the model"| R["Rot. It ends when the model feels good."] ``` So here are six of mine run against it. Two hold up, and they are the two I would have described if you had asked me cold: a numbered definition of done, and Chrome as a way past a 403. The other four are one edit away. Five weeks, one person, eleven projects. Not a base rate, and half my `until` clauses are the same "fix findings until the review is happy" reflex copied around. But the direction is not subtle, and I'd bet your logs look like mine. Write the ending first. Then name who checks it.