Prompts Stop an Agent From Doing Things. They Don't Make It Finish Things.
Table of Contents
- Layer one: prohibitions really do work in a prompt
- Layer two: completion duties that prompts could not move
- Why prohibitions hold and completion duties do not
- What we actually built
- The line is not absolute
- Spend a few minutes auditing your own agent
- Four safety valves when you force a retry
- Checklist
- Four sentences for this chapter
Most writing on agent guardrails says the same thing: put the rules in the system prompt. Forbid this, require that, here is what to do in edge cases. We did exactly that. Our rulebook ran close to four thousand characters, seventeen numbered rules, every one of them written after a real incident.
Then across two work sessions five days apart, the same class of problem bit us four times. The first three landed inside twenty seven minutes of a single afternoon, and a prompt edit fixed each one. The fourth, five days later, survived two prompt rewrites without moving.
Laying the four fixes side by side afterwards, a clean line appeared: among rules written into the same prompt, "do not do X" usually works and "make sure you finish X" often does not. We had been mixing the two without noticing.
Layer one: prohibitions really do work in a prompt
Our agent is called Aochuang. It serves insurance and financial advisors. Our founder used it once himself and reported back: "I asked how to sell life insurance and it handed me substantive taxation doctrine."
The root cause was almost silly. One rule read: "never write exaggerated or non compliant sales talk such as guaranteed, 100 percent, completely tax free." The words "sales talk" were sitting inside a prohibition, so the model generalised it into "sales talk equals forbidden." Ask about selling, and it deflected, then redirected you to legal research.
We rewrote that rule to say the ban is on false content, not on helping with sales, and added a rule stating that sales practice is squarely in scope. Deflection dropped to zero.
Two more of the same kind landed that day:
It invented a client. Asked only "how do I open a conversation about life insurance," with no client details at all, it produced "assume Mr. Chen, 45, earning NT$80,000 a month" and ran three calculators on this fictional person. We added: when key information is missing, ask one question first, and do not call calculators yet. It stopped inventing.
It named clients by itself. In sample dialogue it kept addressing the client as "Chairman Wang." Our first attempt just said "do not invent names." Measured over six runs, four still produced "Chairman Wang." The second attempt prescribed the replacement instead: always use the polite second person, and when a form of address is unavoidable, write a blank placeholder. Six out of six clean after that.
All three were fixed in the prompt alone. What they share is that each asks the model not to do something. A prohibition only needs the model to think of it at the moment of writing, and that is exactly what a well placed prompt line improves.
Layer two: completion duties that prompts could not move
The other thread, five days later, was policy review. An advisor uploads a coverage summary and the agent analyses it. We had just built a tool that matches the whole policy list against our clause database, so the agent could then read the actual policy wording before writing its review.
First live test. The tool fired, and then the agent stopped:
"All 6 policies were found in the clause database. Next, you can ask specific questions about each policy... we suggest you use the clause reading tool to check the original wording."
The advisor wanted a review report and got a status update. It also named an internal tool to a user who has no way to operate tools.
We inspected our own wording. The tool's return message said "next you should use the clause reading tool to verify," and the model read that as advice to pass along. We changed it to "you must finish this in this same turn, do not stop, do not tell the advisor to ask you questions."
Second live test:
"Aochuang has found the clauses for these 6 policies and will now perform the review. Please wait, Aochuang is organising the clause content."
Still stopped, and this time more openly: it announced that it was organising the content, then ended its turn.
That was where we stopped and realised we had seen this shape before.
Five days earlier, during the afternoon of prohibition fixes, the knowledge card feature had been blocked by its provenance guard and replied "please wait, I will verify first and then produce it for you," then ended its turn, leaving the advisor waiting for a card that never came. The same afternoon carried another one: the five section structure of an answer got split across two turns, the first turn wrapped up after the first half, and the second half never arrived.
Three different features, one shape: the model converts the work into a to do item and ends its turn. Seeing the same shape a third time is what made us accept this was not a wording problem.
Why prohibitions hold and completion duties do not
The mechanism explains itself once you look at it.
A prohibition needs the model to recall it at one moment: the moment of producing output. That is a single point event, and putting the rule somewhere prominent raises the hit rate a lot.
A completion duty is different. It requires the model to hold the same intent across several turns: call a tool, read the result, judge that the work is unfinished, call the next tool, and only then wrap up. Anywhere along that chain, if the model decides "let me give the user a progress update first," the chain breaks. And giving a progress update looks like perfectly reasonable conversational behaviour, which is why a prompt struggles to drive it to zero.
More importantly: the server already knows whether the work is done. Was the tool called, was the card created, were the clauses read. Those are facts we hold. Treating facts as the source of truth beats treating the prompt as the source of truth.
What we actually built
A pending obligation flag inside the agent loop.
When the matching tool finds products, we record "a review is owed." When the clause reading tool is called, the obligation clears. If the model tries to wrap up, meaning a turn with no tool calls at all, while the obligation is still open, we do not let it wrap up. We push a system message back in and run another round.
After that change the tool chain became one match plus six clause reads, followed by a complete review report of well over two thousand characters. Three consecutive runs behaved identically.
We used the same technique on three other holes:
Internal identifiers leaking. A rule explicitly forbids revealing internal identifiers, and it still wrote a product's internal id into advisor facing text. We added a server side scrub. There is a trap worth flagging here: our citation matching needs that id, so scrubbing before matching would wipe the entire citation list and break the links back to sources. The fix returns two versions, a scrubbed one for the advisor and an untouched one for internal matching.
Claiming success that never happened. When the knowledge card feature was blocked by its provenance guard, the agent told the advisor "your card is ready" and pasted the content, while no card existed. The advisor would go looking for something that was never created. The server knows whether a card was produced this turn, so a success claim without a card now gets an honest correction appended.
The required disclaimer going missing. A review report must end with one of two sentences. If the clauses were genuinely read: "the coverage conditions in this analysis have been checked against the official clauses; claims remain subject to the policy wording and the insurer's determination." If they were not: "analysis based on the coverage summary; exclusions and details are subject to the official clauses." That single sentence tells the advisor whether the clauses were actually consulted, and the model would drop it. The server now picks the correct sentence based on whether clauses were genuinely read and whether any product was missing from the database.
The line is not absolute
Time for a counterexample, otherwise this piece becomes an oversimplification.
That first afternoon carried a completion duty too: "if you called a calculator, the resulting number must appear in your answer." We measured it failing one run in three, leaving the advisor with a paragraph of adjectives and no figure. That one went from four out of six to six out of six with a single added prompt line.
So the accurate statement is not that prompts never carry completion duties. It is:
A prompt raises the probability. It does not guarantee. When the cost of not finishing is too high to gamble on, move the judgement to the server.
The deciding factor is cost. A missing number is visible to the advisor and they can ask again. Stopping halfway with no report, claiming work that does not exist, or dropping a disclaimer so the advisor believes the clauses were checked, are all invisible to the advisor and go straight into what they tell a client.
There is also a practical signal: when the same failure shape turns up in a third different feature, change technique rather than rewriting the wording again. Both of our earlier attempts assumed the wording simply was not forceful enough; the third occurrence is what made the shape visible.
Spend a few minutes auditing your own agent
01 Sort your rules into two piles: prohibitions and completion duties
Open the system prompt and label every line. Anything starting with "never," "do not," "must not" is a prohibition. Anything phrased as "always verify first," "must finish before," "you must output," "after completing" is a completion duty. For every item in the second pile, ask one question: if the model stops halfway to report progress, would you ever know?
Red flag: more than half your rules are completion duties and all of them rest on the prompt alone.
02 Measure the rate, never a single run
Defects of this kind are almost always intermittent. Every one of ours needed five or six runs to surface: calculated but never reported, one in three; invented client names, four in six; false card success, one in five. Run it once, hit a good sample, and you will announce it fixed.
# Same prompt N times; watch whether the tool chain and output stay stable
# Send Content-Type explicitly: with -d, curl defaults to form-urlencoded,
# the server never sees `message`, all five return 400, and `null | length`
# in jq is 0. You get five tidy "0 0" lines that look like a measurement.
for i in 1 2 3 4 5; do
curl -s -X POST "$API/agent" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d "{\"message\":\"$Q\"}" | jq -r '[(.toolsUsed|length), (.answer|length)] | @tsv'
done
Red flag: you ran it once and committed.
03 Reconcile claims against facts
The model says it finished. Does the artifact exist? Pull the replies containing completion language and check the database for the corresponding records.
# Claimed completion, but nothing was actually produced that turn
# Do not require a perfect-tense marker. The line we actually caught in
# production had none. Our first regex demanded one and reported 0 of 5
# while the agent was lying on every run.
grep -cEi '(have|has|already) *(created|made|produced|prepared)|(created|made|prepared) (your|the|this)|your .* is ready|here is your' answers.log
# Compare against the count of records actually written
Red flag: the two numbers disagree and nothing in your system would ever notice. A second red flag is your detector returning zero. Confirm that means it never happened, rather than that your pattern is too narrow.
04 Collect your stalling phrases
Gather the sentences the model uses to defer: "please wait," "I am now organising," "next you can," "let me check first and come back to you." When one of these appears, the model has converted a task into a to do item and ended its turn. Turn them into a detection condition rather than one more line on the banned list.
Four safety valves when you force a retry
If you are going to force another round inside the loop, do not skip any of these. Each one exists to prevent a specific bad outcome:
Force only once. Otherwise a model that keeps refusing puts you in an infinite loop.
Leave at least one round for it to wrap up. You need space for the answer after you have made it finish the work.
Do not fire when the precondition fails. In our case, if none of the products exist in the clause database there are no clauses to read, and forcing a retry would be wrong.
Charge the budget normally on the extra round. A forced round still has to pass the cost circuit breaker. Do not route around the mechanism that protects your spend.
Checklist
Run this against your own agent:
- Of the completion duties in your system prompt, how many can the server actually verify
- In how many different features have you now seen the same failure shape. Still rewriting wording at the third one is the signal
- Does your acceptance test run once or five times
- When the model claims completion, is anything checking that against reality
- Can your forced retry path loop forever
Drawing this line took four bites from the same class of problem, across two sessions five days apart. Once the two categories are separated, prompts for prohibitions and structure for completion, the rest gets a lot simpler.
Four sentences for this chapter
- A prohibition only needs the model to recall it at the moment of output, so a prominent prompt line genuinely works. A completion duty needs the same intent held across several turns, and any turn where it decides to report progress first breaks the chain.
- A prompt raises the probability, it does not guarantee. When the cost of not finishing is invisible to the user and goes straight into what they tell a client, move the judgement to the server.
- The server already knows whether the work is done: was the tool called, was the card created, were the clauses read. Facts beat prompts as a source of truth.
- When the same failure shape appears in a third different feature, stop rewriting the wording. Both of our earlier attempts assumed the prompt was not forceful enough.
Source location: api/agent.ts (the Aochuang main loop in Ultra Advisor). The pending obligation flag and the forced retry live in the agent loop, with four safety valves: force only once, leave at least one round to wrap up, do not fire when the precondition fails, and charge the budget normally on the forced round. The three server side post checks (internal identifier scrubbing, honest correction of false success claims, and the review disclaimer) sit in the output post processing chain of the same file, and their order matters because citation matching needs the unscrubbed text. The live test records are spread across two commit batches, 2026-08-04 and 2026-08-09.
This is part of the Agentic Design Patterns × Lobster Fleet series. We systematise a solo company's AI agent fleet chapter by chapter, then run an adversarial audit on ourselves. Every chapter we claim to have implemented gets verified again, and the investigation and the fix are written up as steps you can run. The credibility of this series comes from our willingness to publish our own failures.