Framework Below, Harness Above

Share
Framework Below, Harness Above

A framework gives you a callable agent. To turn that into something a person can actually use — and reuse, and trust, and come back to tomorrow — you need a harness.

By framework I mean the runtime that takes a list of messages, dispatches tool calls, and streams a response. Microsoft Agent Framework, in my case, sitting on top of the Anthropic SDK. By harness I mean everything else.

I built a small Claude Code-style agent on top of MAF in seventeen steps. Looking back at what the workshop produced, six pieces stand out — together they're what make the result feel like a harness rather than a console wrapped around an API call.

What follows is each piece: what we built, why it's there, and what it contributes.

The interactive surface

The visible layer where the human and the agent meet. The chat loop, streaming output, a touch of formatting (just enough to set code blocks apart from prose), the spinner that hovers above an in-progress response, and the slash commands the user uses to control the harness rather than the agent.

Without this layer you have an API client — a script that takes input, makes a call, prints output. The harness is what turns calling the model into having a conversation.

What we did: a console chat loop that reads input line by line. A streaming output renderer that tints fenced code blocks in dim cyan as they arrive, so they read distinct from the prose around them. A small spinner that disappears the instant the first token arrives. And a registry of slash commands — /help, /clear, /list, /plan, /governance, and so on — so meta-commands don't pollute the prompt.

The choices here look cosmetic and aren't. Word-by-word streaming reads like a conversation; a finished paragraph delivered all at once reads like a lookup. Code blocks set off in their own colour let the structure breathe; the same text without that separation reads as a wall.

The interactive surface is where the agent stops feeling like a CLI and starts feeling like a colleague who can type.

The trust gates

The set of mechanisms that decide when the agent acts directly, and when it asks the human first.

A framework that can call tools can call any of them, immediately, with whatever arguments the model decided on. That is fine for unattended automation. It is unwise for an interactive agent with access to your filesystem, your shell, your browser. The harness is where you decide what is worth pausing for.

We shipped three gates, working at different levels.

Approval prompts before every tool with side effects. Three states — yes for this one call, no to refuse it, always to approve every future call to that same tool for the rest of the session. Yes alone is exhausting once the agent is working on something real; no alone is exhausting once you trust the agent on a particular kind of action. Three states is the minimum that respects both.

Plan mode as a higher-level gate. The agent enters a state where it can read and think but cannot write or run anything. It produces a plan; the human approves or redirects; only then do tools unlock.

Yolo mode as the explicit escape hatch. Turns approval off entirely. Earns its keep only because the default is conservative.

Three gates, one principle: the harness defaults to show me. The framework can call anything; the harness decides what's worth pausing for.

The persistence layer

The mechanism that lets conversations survive past the end of a single program run — saved, listable, resumable.

Without persistence, every run starts cold. The agent has no memory of yesterday's work, no thread to pick up, no way for the user to keep a project alive across days. This is the line between an agent that is a tool and an agent that is a colleague.

What we did: every turn, save the session to a JSON file under sessions/, tagged with a short id, a timestamp, and a one-line preview of the first user message. The framework's session blob sits inside that wrapper, unchanged. We never read into it, never touch its fields, never assume anything about its shape. We just hand it back to the framework on resume.

That separation matters. The framework version will change; ours does not have to. The wrapper is ours — id, preview, list view, prefix-resume in git checkout abc style. The contents are the framework's.

Layered on top of that, compaction. Long sessions eventually fill the context window. The framework gives you a compaction strategy; you tell it when and how to trim. The strategy itself is mostly framework. The choice of when and how visibly is harness.

The wrapper is yours. The contents are the framework's. That line is the one not to cross.

The tool boundary

The control surface that limits what tools can return into the conversation.

Tool output flows into the context window. A grep across a folder of JSON files can produce a quarter of a megabyte of text. A web fetch can return a multi-megabyte page. A file read can return a binary blob. Whatever a tool returns lands in the next turn's history and counts against the next turn's context budget. Uncapped, this is how a one-line user question crashes an agent with a prompt too long error.

We added per-line caps, total-output caps, and explicit truncation markers after one of those crashes taught us they belonged there. The cap belongs in the harness, not in the tool. Each individual tool author will not think about this; the harness has to.

Every tool that returns content is a boundary, and every boundary needs a cap.

The extensibility surface

The set of hooks through which the harness grows without modifying its centre.

A static harness becomes obsolete the first time a new capability shows up — a new tool, a new external service, a new way to extend the model's reach. If adding any of those requires editing the chat loop, the harness is fragile.

What we did: lean on the framework's provider chain and add five kinds of extension point.

  • Tools — function tools registered with the agent, gated by approval.
  • Skills — folders on disk containing markdown instruction sets the model can load on demand.
  • Memory — model-written notes that persist across sessions, indexed automatically.
  • Sub-agents — delegating agents the main agent can spawn for parallel work.
  • MCP servers — external tool sources connected over the Model Context Protocol.

Each extension point lives behind a registration call. Adding a new tool is one file plus a one-line register. Adding a new skill is one folder. The chat loop never grows.

Extensibility is not a feature you bolt on at the end. It is a shape you maintain from step one.

The observability layer

The mechanism that tells you what the agent is doing, what it is costing, and what is failing.

You cannot trust what you cannot see. An agent making tool calls and burning tokens in the background — without you knowing where the budget is going or which tools are firing — is a liability waiting to happen.

What we did: three small things that compound. Token usage per turn (input and output) plus running totals. Approximate USD pricing per turn based on the model's published rates. A structured log file that records every tool call and every error. Plus a /governance slash command that surfaces the audit trail produced by the policy layer — policy decisions and rate-limit events that don't pass through the log.

None of this is glamorous. Without it, you operate blind. With it, the harness is honest about what just happened — which is the prerequisite for everything else being trustable.

The six components, together

Six pieces, together a harness.

  • Interactive surface — how the human and the agent talk.
  • Trust gates — when the agent acts and when it asks.
  • Persistence — how yesterday's conversation comes back.
  • Tool boundary — what protects the context window from what tools return.
  • Extensibility — how the harness grows without bloating its centre.
  • Observability — what the harness tells you about itself.

A different agent would put different pieces here. A server-side worker running unattended would care more about sandboxes and code execution than about slash commands and approval prompts. Ours is the interactive kind; these six are what it needed to feel finished.

The framework is the same for everyone using it. The harness is where the agent gets its voice, its restraint, and its honesty about what it knows and doesn't. It is a different kind of work than writing the framework would be. Less algorithmic, more taste. Smaller files, more of them.

A framework gets you a callable agent. The harness is everything that happens next.

The workshop is on GitHub.


Drafted by Claude, shaped by me.