Working with LLMs
I spend a lot of time using terminal-based LLM harnesses and occasionally wrestle with subagent prompts when I find myself regularly cleaning up after the model. LLMs are very good at generating code that satisfies a spec you provided. Left to their own devices, they will also pollute your codebase with unreadable code, unhelpful comments, and questionable automated tests. Any long-lived software system that's doing important work needs human attention, regardless of how powerful LLM programming tools become. Since my human arms will never be faster at typing than invisible robot arms, the question is how to use these new tools well.
Good instructions
Any frontier model is a capable coder out of the box, and you can get pretty far with an auto-generated context file and the will to build something. However, learning some prompt engineering techniques will improve your productivity and code quality, and provide foundational knowledge for building applications beyond the scope of a one-off tool or side project. For example:
- Skip the preamble - context belongs in AGENTS.md or your harness's equivalent
- Lead with a verb - good habit that ensures you're using imperative voice
- Define clear goal(s) - reduces rework and weird unit tests
- Specify quality metrics and expectations - improves architecture decision-making
Constraints
It is surprisingly difficult to forbid a model from doing something. Intuition says a computer follows instructions literally and consistently, but LLM "logic" is pattern-based rather than black-and-white. In other words, it's practically impossible to create a strict enforcement mechanism with a prompt; any rule that you give it can only influence the probability of a desired outcome due to:
- Salience - naming a forbidden thing draws attention to it, like someone telling you not to imagine a pink elephant (did it work?). The LLM can fall into a similar trap by having a forbidden concept freshly highlighted in its context window.
- No persistent state - a rule is not like a variable that stays set; it has to win the model's attention every time it could be violated. The larger the context grows, the worse the model will get at adhering to any given rule.
- Limited comprehension - paraphrasing, conflicting instructions, and edge cases can defeat pattern matching a carefully defined forbidden concept.
Cheat sheet
Methods for expressing restrictive rules:
- Use output structuring to make violations inexpressible rather than forbidden
- Closed set: enumerate what is allowed
- Open set: state principle + examples
- Pair prohibited thing with replacement
- State as false positive
- Regression test prohibited results to catch rot
Examples
Here are some of my own lousy prompts that needed adjusting.
Began with a single allowed action, then primed the model to do the forbidden thing.
Before
Run the command exactly as given. Do not modify flags or add `--verbose` etc.
After
Run the command exactly as given.
Tried to explicitly prohibit two actions at once.
Before
Never suggest a fix. Never re-run with different flags. If the command itself looks wrong, return FAIL and explain in `## Summary`.
After
A failed run will tempt you to diagnose (e.g. more verbosity, different flags, a suggested fix). That urge is a false positive: your job is reporting, not diagnosis. Return FAIL with the structured blocks; the primary agent decides what runs next.
A command that looks wrong is also a report, not a retry. Return FAIL and explain in `## Summary`.