Goal: change what the agent says, remembers or refuses — without touching the loop.
Work through these in order. The first one that fits is the right one, and the lower you go the more you pay.
Most behaviour problems are tool-surface problems wearing a prompt-shaped disguise. If the agent guesses a path, retries an impossibility, or fails to chain two calls, the fix is almost always in the tool, not in the prompt:
| Symptom | Fix, in the tool |
|---|---|
| guesses identifiers | say in the description where the identifier comes from |
| retries a refusal forever | the error must say do not retry |
| reads a file five times | return a hint naming the next call |
| uses the wrong tool | say in each description what the other one is for |
This is cheaper than a prompt rule in a way that compounds: a tool description is paid for once, in the tool list, and it is right next to the thing it describes. A prompt rule is paid for on every turn of every conversation, including the ones where it is irrelevant.
The system prompt is five files in orchestrator/src/context/sections/, one per
section, so editing prose never touches code.
Before adding a rule, check it cannot be an example instead. The three examples in the prompt are its highest-value part: one per behaviour that rules alone do not produce — targeted retrieval, confirmation before an effect, and stopping on a permission refusal. A rule states; an example demonstrates, and models follow demonstrations more reliably.
Do not add: the tool list, argument schemas, or per-tool rules. Those arrive through the protocol or belong in a description.
Facts live in their own store, are read on every later turn, and are wrapped as untrusted data like everything else external.
memory_write is annotated destructive even though it destroys nothing. That
is deliberate and should not be "corrected": memory is a persistent instruction
channel, so a fact written without the user seeing it would turn a one-shot
injection into a permanent backdoor.
→ why
To purge everything the agent attributed to itself, filter on the recorded source rather than deleting the store — user-supplied facts are not the problem.
Two rungs, both inside the same hook so there is one seam rather than two. If you touch them, three constraints are load-bearing and each fails in its own way:
Which tools' results are considered heavy is injected from the tool registry. The pruner hardcodes no tool name, and keeping it that way is what stops "add a tool" from becoming "edit the context layer".
DEFAULT_MODEL in the environment, in the gateway's creator/model form. A
provider key set directly takes precedence over the gateway for that provider.
Gateway model ids drift — one disappeared between two releases — so if every turn fails at once, list what the gateway actually offers before debugging anything in this codebase.
The loop. It is about thirty-five lines and should never need to move again. If a task needs a fixed sequence, that sequence is a tool that happens to contain a loop, not a new layer. → Workflows are tools
Where approval comes from. It is derived from tool annotations and fails closed. Three separate ways of "simplifying" it have silently disabled it in the past. → Verified facts
Then have a real conversation and read what it recorded — a behaviour change that does not show up in the failure kinds probably did not happen. → Follow one message