You've used Claude Code. Maybe Cursor too.
They're great — until they're not. The moment you want a tool they don't ship, or you disagree with how they format your commits, or you need a guardrail they didn't anticipate, you hit a wall.
I hit that wall. Not dramatically — just this slow realisation that the product had made choices for me, and I couldn't undo them. So I went looking for what was underneath.
What even is a harness?
Every AI coding tool you've used — Claude Code, Cursor, Copilot — has three layers, whether you see them or not.
At the bottom: the agent loop. Prompt goes to the LLM. Response comes back. If the model wants to run a tool, the tool executes, the result feeds back in. Repeat. The loop has two halves — the non-deterministic bit (what the model decides) and the deterministic bit (the code that actually runs the tool).
Above that: the harness. It decides which tools the model can see. What the system prompt says. Whether a tool call gets blocked before execution. What happens when context fills up. The harness is everything deterministic — the loop, the tools, the prompts, the guardrails.
Together, the model, harness, and UI form the agent — the full system. The model has no idea any of this machinery exists — it just sees messages, tools, and results.
On top: the UI — terminal components in Claude Code, a VSCode panel in Cursor.
Claude Code and Cursor are products because all three layers are fixed. You get the tools they ship, the guardrails they choose, the UI they designed. If their opinion matches yours, it's magic. If not — workaround time.
pi.dev has the same three layers. But only the UI is pre-built. The harness is yours to compose.
So what's a pi harness?
pi.dev doesn't ship a finished agent. It ships a harness SDK — tool registry, extension lifecycle hooks, system prompt assembly, guardrails, compaction. You wire these together.
Here's the bit that took me longest to internalize: the industry taxonomy. My mental model went: "pi is a terminal IDE" → "no wait, it's a platform" → "the agent is the loop" → "the harness wraps the loop." But the industry consensus (LangChain, Hugging Face, Anthropic, OpenAI) says: Agent = Model + Harness. The ReAct loop is deterministic code — it's part of the harness. The agent is the whole system.
The model never asks "what harness am I in?" It doesn't know the concept exists. It gets a prompt, some tools, and the harness loops it.
Two building blocks that are easy to confuse: skills and extensions.
A skill is static text. Load deep-dive, get teaching instructions injected into the system prompt. Done.
An extension hooks into lifecycle events — tool_call, tool_result, agent_start — and can register tools, swap active tool sets mid-session, or block dangerous commands. Skills are passive content. Extensions are active participants in the harness.
Here's a concrete example. By default, pi exposes only 4 tools to the model: read, bash, edit, write. The full registry has 15+. But every tool definition costs ~200–500 tokens in the system prompt. Sending all 15 burns ~2500 tokens per call — on tools the model probably won't use. So the harness filters to the active set.
Need web_search mid-session? An extension calls setActiveToolsByName() to add it. The model never knew it was missing — a new tool just appears.
Neovim, but for AI agents
The closest analogy isn't another AI tool. It's Neovim.
VSCode ships a complete experience. File tree, debugger, marketplace, settings UI. Neovim ships a Lua runtime and says "build your editor." Same destination possible, completely inverted philosophy.
pi.dev is Neovim for AI coding agents. Tool registration, extension hooks, provider abstraction, system prompt composition — all SDK, all yours. You decide which 4 tools are enough, what guardrails to enforce, how the model talks to you.
Cursor and Claude Code are VSCode. Polished. Opinionated. Done.
Neither approach is wrong. But if you've ever wished Claude Code had a different tool, or wanted to intercept a tool call before it runs, or needed the model to follow a custom teaching loop — you've already outgrown the product. The platform is waiting.
The pieces you actually need
A minimal pi harness has five components.
- Active tool set — which tools the model sees. Default is
[read, bash, edit, write]. Start there. Add only what you need. - System prompt — base prompt + skill text + tool guidelines. This shapes the agent more than your model choice.
- Guardrails — pre-execution validation. Model asks for
rm -rf /? Harness blocks it and feeds back a synthetic error. The model self-corrects. Never silently swallow a rejection. That's the bug class that corrupts everything — the model proceeds with bad assumptions and you don't find out until it's too late.
- Extensions — lifecycle hooks. Auto-format on write? Hook
tool_result. Block dangerous commands? Hooktool_call. - Provider config — which LLM, which API key. The harness doesn't care if it's Claude or Gemini behind the curtain.
One lesson stands above the rest: the harness is the security boundary, not the model. Model refusal training can be jailbroken. Deterministic guardrail code cannot. Trust the harness, not the LLM.
I built one — here's what happened
My harness lives at github.com/ajaypremshankar/pilear. It's a learning environment for technical topics — not a general-purpose coding agent.
The system prompt instructs the model to use Farnam Street learning loops, Feynman technique, deliberate practice. Skills like deep-dive and design-review inject teaching methodologies. Extensions handle file routing and knowledge graph indexing.
What surprised me: the harness felt invisible once it worked. The agent just… taught. I forgot tool filtering was happening. Forgot guardrails were running. That's the goal — infrastructure you stop noticing.
And that's exactly what you don't get with Claude Code. There, the product IS the harness. You can't forget it because you can't change it.