The Loop Decisions You Only Make Once

Most agent design choices are cheap to revise. Six aren't — and in every case the answer that ages well is the same one, for the same reason.

Most agent design choices are cheap to revise. A prompt line, a tool description, a model tier — change it, re-run the evals, move on.

A few are structural. They shape everything built on top, and revising them later means rewriting the parts that assumed them. Worth identifying early, because the cost of getting them wrong is paid over the whole life of the system.

1. Where run state lives

The decision: in the message array, or in a structure the loop owns and regenerates each turn.

Why it's hard to reverse: everything downstream reads state from wherever you put it. Completion checking, progress reporting, resumption, compaction — all assume a location. Moving it means touching all of them.

The choice that ages well: the loop owns it. State in the conversation gets summarized away, can't be checked mechanically, and doesn't survive a restart. State the loop regenerates is checkable, durable, and compaction-proof.

2. Whether tool dispatch goes through a seam

The decision: does every tool call route through one function you control, or do call sites invoke tools directly?

Why it's hard to reverse: the seam is what makes replay, recording, permission checks, logging, and rate limiting possible. Retrofitting it means finding every call site and changing it.

The choice that ages well: always have the seam. ✅ It costs a function and it's the prerequisite for testing, which is the prerequisite for everything else.

3. Whether runs are resumable

The decision: can a run stop and continue, or is it all-or-nothing?

Why it's hard to reverse: resumability requires idempotent side effects, durable state, and identifiable work items. Those are properties of your tools and your work model, not of the loop — so adding it later means revisiting every write tool.

The choice that ages well: design for it from the start if runs will ever be long, wait for a human, or matter enough to survive a deploy. ⚠️ Retrofitting resumability is one of the more expensive migrations available, because it isn't a loop change.

4. What the unit of work is

The decision: does a run handle one item or many? Is the ledger a list of items or a single task?

Why it's hard to reverse: it determines what partial success means, how progress is reported, how retries work, and how parallelism is expressed. Changing from single to multi later means reworking all four.

The choice that ages well: model work as a list of items even when there's currently one. The overhead is negligible and it makes partial results, resumption, and fan-out natural rather than bolted on.

5. Whether the result type can express partial

The decision: success/failure, or success/partial/failure with per-item detail.

Why it's hard to reverse: every caller handles the shape you chose. Adding a third state means updating every consumer, and until you do, partial work is being reported as one of the other two — which is the silent-partial failure.

The choice that ages well: three states from the beginning, with per-item reasons.

6. Where the permission boundary sits

The decision: does the tool layer enforce policy, or does the agent's reasoning?

Why it's hard to reverse: if policy lives in prompts, moving it to handlers means auditing every action and writing the checks — and until then you have no real boundary, which is a security position rather than a design preference.

The choice that ages well: enforcement in handlers, from the start, even when the policy is trivial.

💡 The common thread

Every one of these is about where a responsibility lives — state, dispatch, durability, work modelling, result shape, policy. And in each case the durable answer is the same: put it in code the loop owns rather than in the model's context or reasoning.

That's not an accident. Things the loop owns are checkable, testable, and immune to summarization and prompt drift. Things that live in the conversation or in the model's judgment are none of those.

✅ The cheap version

If you're starting and don't want to over-engineer:

  • A dispatch(call) function everything routes through.
  • A run_state dict the loop regenerates each turn.
  • Work modelled as a list, even of one.
  • A result type with three states.
  • Policy checks in handlers, even if they currently return true.

An hour of structure. It doesn't slow you down, and it means the six decisions above are already made in the direction that ages well.

The takeaway

Six decisions are expensive to revise: state location, tool dispatch seam, resumability, work unit, result shape, permission boundary. Each is cheap to get right early and costly to retrofit, and in every case the answer that ages well is the same — put the responsibility in code the loop owns, not in the context or in the model's reasoning.

Keep reading

Similar posts

Matched on shared tags and category — the more bars, the stronger the overlap with what you just read.