Notebook — Field Note

A Harness in Eighty Lines.

The smallest loop that calls a model, dispatches its tools, keeps a memory, and decides when to stop — and what each of those choices is actually saying.

MAY 11, 20267 MINNOTEBOOK

The companion to last week's essay is an exercise: write the smallest harness you can stand behind. Not a framework, not an SDK — sixty or eighty lines that you understand entirely, that fail in ways you can name. What follows is mine. It is in Python because Python reads, on a good day, like a careful sentence. The same shape works in anything.

The four things a harness actually does are: it calls the model; it routes the model's tool calls; it decides what to keep between turns; and it decides when to stop. Most production systems are these four things wearing a coat. The coat is important. The four things are more important.

I. The loop is the harness

Before anything else, there is a loop. The model speaks; the harness listens; the harness decides what to do with what was said; the model speaks again. It is unglamorous, and it is most of the work.

def run(task: str, tools: dict, memory: Memory,
        policy: Policy, max_turns: int = 12) -> str | None:
    messages = [{"role": "user", "content": task}]

    for turn in range(max_turns):
        reply = call_model(messages, tools, memory.surface())

        if reply.stop_reason == "end_turn":
            memory.commit(reply.text)
            return reply.text

        for call in reply.tool_calls:
            result = dispatch(call, tools, policy)
            memory.observe(call, result)
            messages.append(as_tool_message(call, result))

        if should_stop(turn, memory, reply):
            return memory.summarize_so_far()

    return None  # ran out of turns; that is also an answer
harness.py — the loop, in one breath

Notice what is not in this loop. There is no router, no agent type, no role-playing system prompt, no "planner" that writes a plan before doing the work. None of those are wrong; none of them are the harness. The harness is the four lines that decide what happens between two model calls.

II. Memory is curation, not capture

The temptation, when the context window is large, is to put everything in it. Resist. A memory that stores everything is not a memory; it is a hopper. The job is to choose — what to keep, in what form, surfaced under what conditions.

class Memory:
    """Curatorial. Keeps less than it sees."""

    def __init__(self, budget_tokens: int = 4000):
        self.budget = budget_tokens
        self.events: list[Event] = []   # tool calls + results
        self.notes:  list[str]   = []   # the model's own summaries

    def observe(self, call, result):
        # Summarize on the way in. The raw result rarely earns its tokens.
        summary = summarize(result, target_tokens=120)
        self.events.append(Event(call=call, summary=summary))

    def commit(self, final_text: str):
        self.notes.append(distill(final_text))

    def surface(self) -> list[str]:
        # Return what's likely relevant right now, within budget.
        recent  = self.events[-8:]
        salient = top_k_by_relevance(self.events[:-8], k=3)
        return [render(e) for e in (*salient, *recent)]
memory.py — a curatorial store, not a hopper

surface() is the interesting function. It is the part of the system that says: of everything we know, here is what this model, at this moment, should be standing on. The rest is archive. The harness's opinion about what counts as relevant lives inside three lines of Python, and changing them changes the system's character more than a new model would.

“A memory that stores everything is not a memory; it is a hopper.”

III. Stopping is a judgment

Most failed agents fail by not stopping. They loop, they re-fetch what they have already fetched, they refine a sentence that was fine an hour ago. A max_turns cap is the floor, not the ceiling, of the judgment we owe here.

def should_stop(turn: int, memory: Memory, reply) -> bool:
    # 1. The model thinks it's done.
    if reply.stop_reason == "end_turn":
        return True

    # 2. The last three actions are duplicates of earlier ones.
    if is_looping(memory.events, window=3):
        return True

    # 3. We're spending more than the task is worth.
    if memory.cost_so_far() > memory.cost_budget:
        return True

    # 4. We've drifted off the original task.
    if drift_score(memory.notes, memory.original_task) > 0.6:
        return True

    return False
stopping.py — four ways to know you're done

Each of these conditions is a sentence about what the system is for. (1) trusts the model. (2) does not. (3) admits that an answer can be too expensive to be worth having. (4) is the one most systems forget: drift is silent, and the model cannot, by itself, notice that it is no longer doing the thing it was asked to do.

IV. Tools have a politics

The last layer is the smallest and the most consequential. It is the part of the system that says: not that, not now, not without asking. Without it, a tool-using model is a fast intern with the master key.

class Policy:
    """The harness's conscience. Lives outside the model."""

    def __init__(self, rules):
        self.rules = rules

    def allows(self, call) -> bool:
        return all(rule(call) for rule in self.rules)

    def reason(self, call) -> str:
        for rule in self.rules:
            if not rule(call):
                return rule.__doc__ or "refused by policy"
        return ""


def dispatch(call, tools, policy):
    if not policy.allows(call):
        return f"refused: {policy.reason(call)}"
    try:
        return tools[call.name](**call.input)
    except Exception as e:
        # Errors are also context. Surface them; don't swallow.
        return f"error: {type(e).__name__}: {e}"
policy.py — the part that says no

I have come to think of the policy as the most honest part of the system, because it is the only part that has to say what it believes. A model can equivocate; a rule cannot. A rule is a promise the system has made to its user, in advance, in writing.

That is, roughly, the whole thing. About eighty lines, if you count the imports. It will not impress anyone in a demo. It will, with a little care, do most of what the demos do, in a shape you can stand behind on a Tuesday morning when someone asks you why it did what it did.

The point of writing it small is not minimalism. It is legibility. The reason to keep the harness on a page you can read in one sitting is so that the judgment it encodes — about memory, about stopping, about refusal — can be argued with. Everything else in a system can be borrowed. The judgment is yours to defend.

Workbench study: a single page of Python, a notebook, and a quiet lamp — the harness on a surface you can read in one sitting.
Workbench study: a single page of Python, a notebook, and a quiet lamp — the harness on a surface you can read in one sitting.
ThemesPythonAgentsEngineering
Back to thinking