1.4 · Prompt Engineering for Agents

1.4 · 25 min

Prompt Engineering for Agents

"Improve the error handling" is a prompt that works fine in a chat window, where you read the answer and course-correct in the next message. Hand the same sentence to an agent that will run forty loop iterations before you look again, and you have signed a blank check. Somewhere around iteration 15 it decides "improve" includes a new error-handling framework, and by iteration 40 you own one. Prompting an agent is not prompting a chatbot with extra steps. The prompt is the only steering input for the entire unobserved run, and it has to be built like it.

Three things, three placements

Most bad agent prompts fail by mixing three different kinds of content into one undifferentiated paragraph. Pull them apart:

Instructions say what to do: the task, its scope, its definition of done. They belong in the prompt, stated once, specifically. Instructions are task-mortal; when the task ends, they should die with it.

Constraints say what must not happen: don't touch this directory, never commit to main, no new dependencies. Constraints outlive tasks, which is why (per Lesson 1.3) durable ones belong in CLAUDE.md, and why the ones that truly cannot be violated eventually graduate to hooks in Part 2, where they stop being requests and become physics. A constraint that exists only as a sentence in a long prompt is a constraint on borrowed time.

Context is what the agent needs to know: the relevant file, the API contract, the error log. It belongs on disk or at a URL, named in the prompt and retrieved just in time, not pasted wholesale. You learned the attention economics of this in 1.3.

Sort your content into those three bins before writing a word, and half of prompt engineering is done. The rest is craft, and Anthropic's own guidance is unusually concrete about it.

The craft, from the source

Anthropic's prompting best practices open with the core principle: Claude responds best to clear, explicit instructions. Their framing is worth adopting wholesale: treat Claude as a brilliant but new employee, one with no context on your norms. And their golden rule is a usable test, not a platitude: show your prompt to a colleague with minimal context and ask them to follow it. If they would be confused, Claude will be too.

Specificity beats politeness, length, and cleverness. "Make sure the code is good" contains zero bits of steering. "All exported functions get JSDoc; errors are returned, never thrown, matching the pattern in src/result.ts" contains many. Note what the second version does: it defines the vague word by pointing at a checkable pattern that already exists in the repo.

Examples are the strongest steering tool you have. The docs are direct about this: a few well-crafted examples (few-shot prompting) improve accuracy and consistency more reliably than paragraphs of description. Three to five, mirroring your actual case, diverse enough that the model does not latch onto an accidental pattern, wrapped in <example> tags so they read as demonstrations rather than instructions. When you want output in a shape, show the shape. One example of a perfect commit message outperforms a hundred words describing one.

Structure is the delivery mechanism. XML-style tags separate instructions from context from examples so nothing bleeds together, and for agents there is a placement rule with teeth: put long reference material at the top of the prompt and the instructions after it, which the docs recommend for long-context prompts. And Anthropic's context-engineering guidance adds the calibration principle for standing rules: the right altitude. Not brittle if-this-then-that pseudocode that shatters on the first case you did not anticipate, and not vague vibes that steer nothing. Specific enough to act on, flexible enough to generalize: heuristics, not scripts.

Why rules decay over a long loop, and what survives

Here is the agent-specific problem no chatbot prompting guide covers. Your prompt is written once, but the model re-reads the whole window every iteration, and the window grows every iteration. At iteration 1 your rules are most of what exists. At iteration 30 they are a paragraph floating in a sea of tool results, competing for the same attention budget as everything else (Lesson 1.3, context rot). Rules do not decay because the model is careless; they decay because their share of attention shrinks mathematically.

You cannot stop that, but you can write rules that degrade gracefully:

  • Few and specific beats many and general. Ten crisp constraints survive; forty aspirational guidelines become noise that dilutes the ten that mattered.
  • Checkable beats aspirational. "No new dependencies" can be re-verified by the model at any iteration by looking at package.json. "Keep the code clean" cannot be re-derived from anything; once it fades, it is gone.
  • A verification target outlasts any instruction. This is the deepest one, straight from the Claude Code best-practices guidance: give Claude a check it can run, and the loop closes on itself. A test suite the agent runs every few iterations re-asserts your intent mechanically, at full strength, no matter how full the window gets. The best prompt engineering is often writing the check instead of more prose.

In practice: the same task at three qualities

The wish

"Add rate limiting to the API."

Unstated: which routes, which algorithm, where state lives, what happens on limit, what must not change, how to know it works. The agent decides all six, confidently, over forty iterations. Some decisions will be good. You find out which ones were not by reading everything.

The dispatch

"Add rate limiting to the two /api/public/* routes. Fixed window, 60 req/min per IP, in-memory store (single instance, no Redis). Return 429 with a Retry-After header. Constraints: no new dependencies, no changes outside src/middleware/. Write tests proving the 61st request in a window gets 429 and a fresh window resets. Run the suite; done means green."

Every decision is either made or explicitly delegated, and done is checkable.

The right-hand version is the dispatch pattern: goal, scope, constraints, verification, in one block. Part 3.1 formalizes it into the unit of work this whole course runs on; what you are seeing here is why it exists. Every element is a decay countermeasure: scope bounds drift, named constraints survive review even if attention fades, and the verification criterion keeps re-asserting itself every time the agent runs it.

Lablab-1-4Three prompts, one task, diff the damage

Goal: Run the same feature through a vague, a medium, and a dispatch-grade prompt, and diff what the agent decided on your behalf each time.

Prereqs: the agentic-practice repo.

  1. Pick the task: a stopwatch.ts module with start/stop/lap/reset, plus tests. Small enough to run three times, open enough to leave real decisions.
  2. Run 1, the wish. Fresh session: "Add a stopwatch module to this project." Accept whatever happens, commit as prompt-v1.
  3. Run 2, partial. Fresh session, delete the module first: name the file, the four functions, and "include tests". Nothing else. Commit as prompt-v2.
  4. Run 3, the dispatch. Fresh session, delete again. Write the full block: goal, exact API with types, constraints (no dependencies, node:test, nothing outside stopwatch.ts and stopwatch.test.ts), an <example> of one test in your preferred style, and the verification line: "run the tests; done means green, show me the run." Commit as prompt-v3.
  5. Now diff the three commits. For each, list the decisions the agent made that you did not: API shape, dependency choices, files touched, test style, error behavior. Count them.

Verify

  • Three commits, three implementations of the same idea.
  • A written tally of agent-made decisions per run; it should fall, sharply, from v1 to v3.
  • v3's session ends with a test run you can see in the transcript, because you made "show me the run" part of done.
>Troubleshooting
  • v1 turned out fine: sometimes the dice land well; that is variance, not vindication (Lesson 1.1). Check the decisions tally anyway. Fine-this-time and specified are different properties, and only one of them repeats.
  • v3 felt slow to write: time it next run; the block takes two or three minutes once the pattern is habit. Compare against reviewing v1's surprises.
  • The agent ignored a constraint even in v3: keep the receipt. Prose constraints are best-effort, which is the honest limit of prompting and the opening argument for hooks in Part 2.6.

Where it breaks

Prompt engineering has a ceiling, and pretending otherwise is how people end up with 2,000-word mega-prompts that read like legal documents and still get violated at iteration 35. Prose steering is probabilistic. It loses potency as context fills, and no phrasing fixes that, because the mechanism is attention, not comprehension. When you catch yourself writing the same constraint for the fifth time in increasingly stern language, the constraint is telling you it wants to be a hook or a test, not a better sentence. The craft in this lesson gets you most of the way; the discipline of Part 3 exists for everything the craft cannot hold.

Knowledge check

Knowledge check

Q1Sort into placements: (a) 'rename getUByI to getUserById in exactly these three files', (b) 'this repo never uses default exports', (c) the OpenAPI spec for the service being integrated.
Q2Why is one good example of your desired commit-message format usually stronger steering than a paragraph describing the format?
Q3Which standing rule is written at the right altitude for an agent that will run long loops?
Q4At iteration 35 of a long session, Claude violates a constraint from your opening prompt that it honored for 30 iterations. What is the accurate diagnosis?

Sources