<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pramoda Sahu</title>
    <description>The latest articles on DEV Community by Pramoda Sahu (@pramod_sahu_d5bd2e6de82d1).</description>
    <link>https://dev.to/pramod_sahu_d5bd2e6de82d1</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3440481%2F1cfb9605-321e-407e-9e91-64194300417c.png</url>
      <title>DEV Community: Pramoda Sahu</title>
      <link>https://dev.to/pramod_sahu_d5bd2e6de82d1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pramod_sahu_d5bd2e6de82d1"/>
    <language>en</language>
    <item>
      <title>The Nuts and Bolts of Voice AI Agents</title>
      <dc:creator>Pramoda Sahu</dc:creator>
      <pubDate>Mon, 24 Aug 2026 11:27:55 +0000</pubDate>
      <link>https://dev.to/pramod_sahu_d5bd2e6de82d1/the-nuts-and-bolts-of-voice-ai-agents-4gge</link>
      <guid>https://dev.to/pramod_sahu_d5bd2e6de82d1/the-nuts-and-bolts-of-voice-ai-agents-4gge</guid>
      <description>&lt;h3&gt;
  
  
  Why a talking chatbot and a real voice agent are not the same thing
&lt;/h3&gt;

&lt;p&gt;A voice AI agent looks deceptively simple from the outside. You speak. It listens. It thinks. It responds. But underneath that simple exchange sits a real-time distributed system juggling audio streams, speech detection, transcription, reasoning, tool execution, state, speech synthesis, interruptions, latency budgets, and — sometimes — a handoff to a human.&lt;/p&gt;

&lt;p&gt;This post breaks down what's actually happening under the hood, and why the hardest problems in voice AI today have less to do with the language model and more to do with the runtime around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not a chatbot with a microphone
&lt;/h2&gt;

&lt;p&gt;A text chatbot is fundamentally a request-response system: a message comes in, the model reasons, a response goes out. A voice agent has to operate continuously in time. Audio comes in as a stream, and the system must detect speech, figure out whether the user is actually finished talking, transcribe, reason, possibly call a tool, generate a response, synthesize it into audio, and play it back — all while still listening.&lt;/p&gt;

&lt;p&gt;That last part matters more than it sounds. The user might interrupt with "wait," or "no, I meant tomorrow," or "stop, that's not what I asked." A good voice agent has to notice that, stop itself mid-sentence, hold onto the relevant context, and pick up from the new input. This is called &lt;strong&gt;barge-in&lt;/strong&gt;, and modern voice runtimes treat it — along with turn detection — as a first-class architectural concern, not an afterthought.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who has the floor?
&lt;/h2&gt;

&lt;p&gt;Human conversation feels effortless partly because our brains are constantly solving one question: &lt;em&gt;who should be speaking right now?&lt;/em&gt; Voice agents have to solve the same problem computationally, and it's harder than it looks.&lt;/p&gt;

&lt;p&gt;Consider someone saying "I'd like to book an appointment..." and then pausing. Are they done, or mid-thought? If they continue with "...for tomorrow afternoon," the pause meant nothing. But if they'd said the whole sentence and stopped, the agent should respond. Detecting &lt;em&gt;speech&lt;/em&gt; isn't the same as detecting a &lt;em&gt;finished thought&lt;/em&gt; — and that distinction is the root of two related but different technologies: VAD and turn detection.&lt;/p&gt;

&lt;h2&gt;
  
  
  VAD: is there speech?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Voice Activity Detection (VAD)&lt;/strong&gt; answers one narrow question: does this audio currently contain speech? It analyzes incoming audio frames and estimates whether what it's hearing is a human voice versus silence, a keyboard, a fan, traffic, or a door slamming.&lt;/p&gt;

&lt;p&gt;Its limitation is important: VAD doesn't understand &lt;em&gt;what&lt;/em&gt; is being said. It can tell you "there appears to be speech," but it can't tell you "the user has finished expressing their thought." That's a different problem entirely — which is why modern systems pair VAD with endpointing or model-based turn detection rather than relying on it alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Endpointing: how long is long enough?
&lt;/h2&gt;

&lt;p&gt;Once VAD detects speech turning into silence, the system has to decide how much silence means "the user is done." Is 200 milliseconds enough? 500ms? A full second? There's no universal answer, and getting it wrong in either direction breaks the experience.&lt;/p&gt;

&lt;p&gt;Wait too little, and the agent jumps in on "I need to book—" before the user finishes. Wait too long, and a simple exchange starts to feel sluggish and robotic. This is the endpointing problem, and while a simplified version just runs a silence timer against a fixed threshold, production systems increasingly use models that weigh both the acoustic signal and the semantic content of the utterance to decide whether a thought is actually complete. LiveKit's documentation on turn detection is a good example of this hybrid approach in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backchannels: the "yeah" problem
&lt;/h2&gt;

&lt;p&gt;Humans constantly emit small sounds while listening — "yeah," "uh-huh," "okay," "right." These aren't new conversational turns; they're backchannels, signals that the listener is still engaged. If an agent mid-sentence hears "yeah" and interprets it as an interruption, it'll stop talking unnecessarily and the conversation starts to feel broken.&lt;/p&gt;

&lt;p&gt;So the real classification problem an interruption system faces isn't just "is there speech," it's: is this a genuine interruption, a backchannel, background noise, or an echo of the agent's own voice? That's a meaningfully harder problem than measuring volume, and it's an active area of development — LiveKit's adaptive interruption handling is one example of a system built specifically to make this distinction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Barge-in: where voice agents become conversational
&lt;/h2&gt;

&lt;p&gt;Barge-in — letting the user interrupt the agent mid-response — is the moment a voice agent starts to feel genuinely conversational rather than scripted. But implementing it well requires tight coordination across several components at once. When the agent is announcing an appointment confirmation and the user cuts in with "wait!", the system has to detect the incoming speech, confirm it's a real interruption, halt text-to-speech playback, flush any queued audio, capture and transcribe the new input, preserve the conversation state, decide what happens to whatever operation was in flight, and generate the next response — all within a fraction of a second.&lt;/p&gt;

&lt;p&gt;This is why a production voice agent is, at its core, a real-time event-driven system rather than a simple pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The echo problem nobody thinks about
&lt;/h2&gt;

&lt;p&gt;There's a subtler issue lurking in the audio layer: the agent's own voice, played through the user's speaker, can travel back into the microphone. Without proper handling, the system ends up hearing itself — and can effectively interrupt its own response. Solving this requires acoustic echo cancellation, noise suppression, gain control, and careful audio routing.&lt;/p&gt;

&lt;p&gt;It's easy to overlook, but if the audio pipeline is weak, the AI can look bad even when the underlying model is excellent. The audio layer is, in a very practical sense, part of the agent's intelligence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why streaming changes everything
&lt;/h2&gt;

&lt;p&gt;A naive voice pipeline waits for each stage to fully complete before starting the next: the user finishes speaking, transcription completes, the LLM finishes generating, speech synthesis completes, and only then does audio play. That creates a lot of dead air.&lt;/p&gt;

&lt;p&gt;Production pipelines instead treat each handoff — STT to LLM, LLM to TTS — as a streaming interface. Partial transcripts feed the LLM as they arrive; partial LLM output feeds text-to-speech as it's generated; audio plays in chunks rather than waiting for the whole response. LiveKit describes this distinction explicitly, framing streaming across every stage boundary as a defining trait of production-quality pipelines. The effect on perceived latency is substantial — a system can feel instantaneous even when the total compute time hasn't changed much, simply because nothing is waiting on anything else to fully finish.&lt;/p&gt;

&lt;h2&gt;
  
  
  Latency isn't one number
&lt;/h2&gt;

&lt;p&gt;Claims like "our voice agent has 800ms latency" don't mean much without more context, because a single voice interaction is made up of several latency contributors stacked together: audio capture, VAD and turn detection, speech-to-text, LLM reasoning, tool execution, text-to-speech, network transport, and playback.&lt;/p&gt;

&lt;p&gt;Teams building serious voice systems typically track each of these separately — time to speech detection, time to end-of-turn, STT latency, LLM time-to-first-token, TTS time-to-first-audio, tool latency, and end-to-end latency — because a single blended number hides which part of the pipeline is actually slow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The agent runtime: the part hiding behind "AI voice agent"
&lt;/h2&gt;

&lt;p&gt;Everything described so far — turn detection, interruption handling, streaming, state, tools — needs to be coordinated by something. That something is the &lt;strong&gt;agent runtime&lt;/strong&gt;: the system responsible for state, memory, policies, tool orchestration, turn management, recovery, interruptions, and observability, all wrapped around the STT/LLM/TTS core.&lt;/p&gt;

&lt;p&gt;Frameworks like LiveKit Agents exist specifically to provide these abstractions — the realtime audio pipeline, turn detection, interruption handling, tool calling, and orchestration — so teams don't have to build them from scratch. The LLM provides the reasoning; the runtime is what makes that reasoning usable in a live, real-time environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools turn a voice bot into an agent
&lt;/h2&gt;

&lt;p&gt;A voice assistant that can only answer questions is limited. An agent can act — check availability, book an appointment, update a CRM record. But that requires more than the LLM generating plausible-looking JSON for a function call. The runtime has to validate the arguments, execute the tool, handle errors, enforce permissions, update state, decide whether to retry, and determine what the user should actually hear as a result. Tool orchestration is where a voice bot starts to become a genuine agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  State and memory are not the same thing
&lt;/h2&gt;

&lt;p&gt;These two concepts get conflated constantly, but they answer different questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State&lt;/strong&gt; is: what is happening right now in this conversation? Current intent, current step, information collected so far.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory&lt;/strong&gt; is: what information should persist across a longer period — the customer's name, their preferred branch, their last appointment date?&lt;/p&gt;

&lt;p&gt;Neither should simply mean "stuff everything into the prompt." Treating state and memory as distinct engineering concerns — with their own storage, lifecycle, and update logic — is what lets an agent handle interruptions, tool failures, human handoffs, dropped calls, and changes of mind without losing the thread of the conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when a tool fails?
&lt;/h2&gt;

&lt;p&gt;This is where demos turn into production systems. If a booking API times out, the worst possible response is "Your appointment is booked!" when it isn't. A better system says "I'm having trouble confirming that right now, let me try again" or escalates to a human. The runtime needs explicit policies for timeouts, retries, fallback paths, and human handoff.&lt;/p&gt;

&lt;p&gt;There's a subtler trap here too: what if the booking actually succeeded, but the &lt;em&gt;response&lt;/em&gt; timed out? Blindly retrying in that case could create a duplicate booking. This is why idempotency — ensuring an operation can be safely retried without side effects — becomes a serious design concern. Production voice agents need to reason about failure at the workflow level, not just the model level.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interruptions during tool calls are even harder
&lt;/h2&gt;

&lt;p&gt;Imagine the user says "book Friday at 3," the agent starts the booking, and mid-call the user says "actually, make it 4." What happens to the operation already in flight? There are a few real options: cancel it if cancellation is supported, let it complete and modify afterward, temporarily disallow interruption for irreversible actions, or require explicit confirmation before executing anything irreversible.&lt;/p&gt;

&lt;p&gt;The underlying principle is that not every part of an agent should be interruptible — and deciding which parts aren't is a deliberate design choice, not a default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Human handoff is part of the agent, not a failure of it
&lt;/h2&gt;

&lt;p&gt;A production agent needs to know when &lt;em&gt;not&lt;/em&gt; to be autonomous. Common triggers include repeated misunderstanding, low confidence, sensitive requests, an upset customer, tool failures, or an explicit request to speak to a person.&lt;/p&gt;

&lt;p&gt;A good handoff isn't just "I'll transfer you" followed by dumping a confused human into a blank conversation. The human agent should receive a summary: who the customer is, what they wanted, what's already been tried, and what the recommended next action is. The AI doesn't disappear at the handoff — it prepares the human to pick up where it left off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability: knowing why a call actually failed
&lt;/h2&gt;

&lt;p&gt;When something goes wrong, "call unsuccessful" isn't a useful answer. A proper trace breaks a call down by component — how many turns, how many false endpoints, average STT and LLM latency, which tool calls succeeded or failed and how long they took, TTS latency, and the final outcome. With that level of detail, an engineer can actually ask useful questions: why was this call slow, why are users getting interrupted incorrectly, which tool is failing most often. Observability is what turns a voice agent from a mysterious black box into an engineering system that can be debugged and improved.&lt;/p&gt;

&lt;h2&gt;
  
  
  The voice agent as a state machine
&lt;/h2&gt;

&lt;p&gt;One useful way to think about the whole runtime is as a state machine cycling through idle, listening, thinking, optionally executing a tool, speaking, and — if the user interrupts — back to listening via a barge-in path. That loop, running continuously, is a better mental model than the simple "input → LLM → output" picture most people start with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managed platforms vs. building your own runtime
&lt;/h2&gt;

&lt;p&gt;This all creates a real architectural decision: build the runtime yourself, or use a managed voice platform that provides much of it out of the box?&lt;/p&gt;

&lt;p&gt;Managed platforms typically handle audio transport, VAD, and turn detection for you, and provide integrations for STT, LLM, and TTS providers — trading flexibility for speed. Building your own runtime means owning state, memory, recovery, and observability directly, at the cost of more engineering effort. Neither is inherently better: shipping an appointment-booking agent quickly favors a managed platform, while building differentiated voice infrastructure favors owning more of the stack. Frameworks like LiveKit sit deliberately close to the runtime layer, giving developers an open framework and letting them choose their own AI providers rather than locking them in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real engineering trade-off
&lt;/h2&gt;

&lt;p&gt;The most important question in voice AI today isn't "which LLM is best?" It's: &lt;em&gt;which runtime architecture gives the best combination of latency, reliability, flexibility, and cost for this specific workload?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Optimizing for &lt;strong&gt;latency&lt;/strong&gt; means streaming STT, LLM, and TTS, using preemptive generation, and tuning turn detection to be fast. Optimizing for &lt;strong&gt;reliability&lt;/strong&gt; means explicit state, typed tools, retries, timeouts, idempotency, fallback paths, and human escalation. Optimizing for &lt;strong&gt;cost&lt;/strong&gt; means smaller models where they're sufficient, model routing, shorter prompts, and caching. Optimizing for &lt;strong&gt;control&lt;/strong&gt; means open frameworks, owned infrastructure, and provider abstraction. There's no single optimal architecture — only an optimal one for a given workload.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why voice AI is becoming a runtime problem
&lt;/h2&gt;

&lt;p&gt;Voice AI's early questions were about the model: can it talk, can it understand speech, can it hold a conversation? The questions that matter now are about everything &lt;em&gt;around&lt;/em&gt; the model: can it interrupt naturally, recover from errors, execute tools safely, maintain state, run with low latency, scale, hand off to a human, and be observed when something breaks?&lt;/p&gt;

&lt;p&gt;The LLM is still critical — but it's no longer the whole product. The interesting engineering work has moved to the runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;A production voice agent isn't "microphone → LLM → speaker." It's audio infrastructure, VAD, turn detection, speech-to-text, an agent runtime with state and memory, the LLM itself, tool orchestration, failure recovery, speech synthesis, barge-in handling, observability, and human escalation — all cooperating in real time.&lt;/p&gt;

&lt;p&gt;Building a convincing voice demo is relatively easy. Building a voice agent that can handle ten thousand messy, real-world conversations without breaking is a completely different engineering problem — and that's where the interesting work is happening now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One sentence to remember:&lt;/strong&gt; a voice agent isn't an LLM that can talk. It's a real-time control system that happens to use an LLM.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Further reading: LiveKit's Agents documentation covers realtime voice agent architecture, turn detection, and orchestration in more depth; their turn detection and adaptive interruption handling docs go deeper into endpointing and backchannel detection specifically; NVIDIA has also published on barge-in architectures using continuous ASR during TTS playback.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>llm</category>
      <category>coding</category>
    </item>
    <item>
      <title>DeepSeek Harness: What Happens When the Agent Runtime Becomes the Product</title>
      <dc:creator>Pramoda Sahu</dc:creator>
      <pubDate>Mon, 24 Aug 2026 06:29:31 +0000</pubDate>
      <link>https://dev.to/pramod_sahu_d5bd2e6de82d1/deepseek-harness-what-happens-when-the-agent-runtime-becomes-the-product-1012</link>
      <guid>https://dev.to/pramod_sahu_d5bd2e6de82d1/deepseek-harness-what-happens-when-the-agent-runtime-becomes-the-product-1012</guid>
      <description>&lt;p&gt;&lt;em&gt;How an "everything is a plugin" architecture reframes what an AI agent actually is — and what it teaches builders of any agent stack.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Agent Is More Than the Model
&lt;/h2&gt;

&lt;p&gt;There's a persistent shorthand in agent engineering: take a capable LLM, give it a system prompt and a handful of tools, and call the result an "agent." That shorthand works for demos. It falls apart the moment an agent needs to run for more than a few minutes, survive a restart, call a sub-agent, recover from a failed tool call, or let a human inspect what it actually did three hours ago.&lt;/p&gt;

&lt;p&gt;Once you cross that line, an agent needs a lot of infrastructure that has nothing to do with the model itself: a place to execute tools safely, a way to keep state across turns, a policy for what context the model sees on each call, a mechanism for delegating work to other agents, a sandbox to contain what the agent can touch, a way to recover from partial failures, and a record of what happened that a person — or an evaluation harness — can replay later.&lt;/p&gt;

&lt;p&gt;Collectively, this surrounding machinery is often called the &lt;strong&gt;agent harness&lt;/strong&gt;: the runtime that sits between the model and the world, and that actually determines how the agent behaves in practice. Two agents built on the same underlying model can behave completely differently depending on the harness wrapped around it — how it manages context, what tools it exposes, how it recovers from errors, and how it schedules work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DeepSeek Harness&lt;/strong&gt; (&lt;code&gt;dsh&lt;/code&gt;), an open-source project released by DeepSeek AI in developer preview under the MIT license, is a useful concrete example of where this thinking leads when taken seriously. It didn't invent the idea of an agent harness — Anthropic's Claude Code, OpenAI's Codex CLI, and various open-source agent frameworks have been converging on similar territory. What makes DeepSeek Harness worth a close read is how far it pushes a single architectural commitment — "everything is a plugin" — and what that commitment forces the rest of the design to look like.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What Is DeepSeek Harness?
&lt;/h2&gt;

&lt;p&gt;In plain terms: DeepSeek Harness is a runtime for building and running coding/automation agents. You install it, point it at a model provider, and get a working agent — with file editing, shell access, web search, sub-agents, and a web UI — out of the box. It's explicitly model-agnostic: alongside DeepSeek's own models, the provider catalog covers Anthropic, OpenAI, AWS Bedrock, Azure, and Google's Gemini Enterprise Agent Platform, plus custom OpenAI-compatible endpoints. Nothing in the design ties the harness to DeepSeek's own models — a telling signal about what the project is actually trying to be.&lt;/p&gt;

&lt;p&gt;Technically, the separation is stricter than "the code happens to support multiple providers." DeepSeek Harness is built on a general-purpose plugin framework called &lt;strong&gt;Cordis&lt;/strong&gt;, whose composition model is described in a paper titled &lt;em&gt;"A Programming Paradigm for Spatiotemporal Composability"&lt;/em&gt; by researchers from Peking University and DeepSeek. Cordis provides plugins with a shared context (&lt;code&gt;ctx&lt;/code&gt;), through which they contribute services, typed events, and — notably — &lt;em&gt;reversible effects&lt;/em&gt;. According to the project's own architecture documentation, "every part of the product is a plugin, including the model adapter, the tool registry, the session log, and the agent loop itself." There is, by design, no privileged core to patch: extending the harness means mounting a new plugin beside the existing ones, and every registration is an effect that cleanly unwinds when its plugin unloads.&lt;/p&gt;

&lt;p&gt;This separation matters for a simple reason: it turns the model into an interchangeable component rather than the organizing principle of the system. The runtime doesn't just call an LLM — it owns the session, the tool pipeline, and the execution history independently of which model happens to be answering right now. That's the practical meaning of "model-vs-harness separation," and it's the assumption that makes the rest of the architecture legible.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Architecture
&lt;/h2&gt;

&lt;p&gt;At boot, a running &lt;code&gt;dsh&lt;/code&gt; instance is a plugin tree assembled from ordered layers. A &lt;strong&gt;profile&lt;/strong&gt; (the docs ship &lt;code&gt;web&lt;/code&gt; and &lt;code&gt;headless&lt;/code&gt; templates) lists which &lt;strong&gt;bundles&lt;/strong&gt; it stacks; a bundle is a distribution unit of Cordis configuration plus the code it mounts. &lt;code&gt;dsh-base&lt;/code&gt; is the foundational bundle every profile includes — model adapters, tools, persistence, sandbox and approval policy, credentials, telemetry — and &lt;code&gt;dsh-web-app&lt;/code&gt; or &lt;code&gt;dsh-headless&lt;/code&gt; add a browser UI or a one-shot runner on top. Layering is deterministic and inspectable: you can run &lt;code&gt;dsh --profile web --dump-config&lt;/code&gt; and see the exact plugin tree your machine will boot, then override any row with your own patch file.&lt;/p&gt;

&lt;p&gt;A handful of core packages anchor this tree (each owning a distinct piece of &lt;code&gt;ctx&lt;/code&gt;, the shared plugin context):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Owns&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;core/session&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The append-only session-event log and in-memory store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;core/system-prompt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prompt-section and tool-schema assembly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;core/tools&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The scoped tool registry and guarded execution pipeline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;core/agent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;Agent&lt;/code&gt; interface, live registry, and lifecycle events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;core/agent-loop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The default driver implementing that interface&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;llm/llm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Message/stream vocabulary and the model-adapter seam&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Zoom out from the package table and the shape is straightforward: a Cordis kernel sits at the center, and the agent loop, the model adapter (&lt;code&gt;ctx.llm&lt;/code&gt;), the tool registry (&lt;code&gt;ctx.tools&lt;/code&gt;), skills, the subagent runtime (&lt;code&gt;ctx.subagents&lt;/code&gt;), the sandbox and filesystem layer, and the session log (&lt;code&gt;ctx.sessions&lt;/code&gt;) all hang off it as sibling plugins. The tool registry talks to the sandbox to actually execute anything; the session log receives events from everywhere else and is what fork, resume, and the trajectory UI all read from. Crucially, the agent loop itself is just one more plugin in that list, not a privileged core the others report to — which is precisely the point. The runtime has no single "agent class" you subclass; it has a composition of independently swappable services.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The Plugin Architecture: Why Bother?
&lt;/h2&gt;

&lt;p&gt;It would be easy to read "everything is a plugin" as an engineering slogan. The more interesting question is what problem it actually solves.&lt;/p&gt;

&lt;p&gt;A monolithic agent framework typically hard-wires its tool list, its context-management policy, and its loop logic into one execution path. That's fine until you need to change one dimension without touching the others — swap the sandbox for a remote one, add a new model provider, or give a subset of sessions a different toolset. In a monolith, those changes ripple through shared code paths and are hard to test in isolation.&lt;/p&gt;

&lt;p&gt;DeepSeek Harness's answer is what its docs call a &lt;strong&gt;capability seam&lt;/strong&gt;: a swappable capability defined by three roles — a &lt;em&gt;Service Definition&lt;/em&gt; (the interface), a &lt;em&gt;Service Provider&lt;/em&gt; (an implementation), and a &lt;em&gt;Consumer&lt;/em&gt; (typically a model-facing tool). Filesystem and subprocess access are one seam; because Bash, PTY access, and code-navigation tools all consume that same seam, pointing it at a remote sandbox moves all three together, with no need to fork any of the individual tools. Subagents are a different seam — one where &lt;em&gt;multiple&lt;/em&gt; provider implementations coexist by name in the same context (a locally spawned child, a forked child sharing conversation history, a delegated Claude Code or Codex session), because different delegation strategies are genuinely useful side by side, not mutually exclusive.&lt;/p&gt;

&lt;p&gt;This is the architectural difference from a typical monolithic framework: capabilities aren't conditionally-compiled features of one big class, they're independently loaded plugins that contribute to a shared context and can be added, removed, or replaced without touching the runtime's source. The project's own extension guidance is concrete about this — adding a model provider means registering an adapter on &lt;code&gt;ctx.llm&lt;/code&gt;; adding a model-facing capability means registering on &lt;code&gt;ctx.tools&lt;/code&gt;; confining spawned processes means providing a &lt;code&gt;ctx.sandbox&lt;/code&gt; backend that tool consumers wrap around before spawning. None of these require modifying the agent loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The Agent Loop
&lt;/h2&gt;

&lt;p&gt;DeepSeek Harness's documentation describes execution in terms of &lt;strong&gt;turns&lt;/strong&gt; and &lt;strong&gt;steps&lt;/strong&gt;, not a single flat request-response cycle. A &lt;em&gt;step&lt;/em&gt; is one model request plus whatever tools it calls; a &lt;em&gt;turn&lt;/em&gt; is zero or more steps, opening when input is first claimed and closing once nothing is owed to the model.&lt;/p&gt;

&lt;p&gt;The documented turn flow runs like this. A turn opens by claiming the next input plus any queued message, then assembles the prompt sections and tool schemas that plugins have registered. That claim passes through an &lt;code&gt;agent/pre-step&lt;/code&gt; event, which can reject or rewrite it before the model ever sees it — and even a rejected or empty first claim still closes a durable turn, so the attempt is recorded rather than silently vanishing. Once a step starts, the request goes out (&lt;code&gt;agent/request&lt;/code&gt; → &lt;code&gt;llm/stream&lt;/code&gt;), and if the model calls a tool, that call runs through a three-stage pipeline — &lt;code&gt;tools/pre-execute&lt;/code&gt;, &lt;code&gt;tools/execute&lt;/code&gt;, &lt;code&gt;tools/post-execute&lt;/code&gt; — before the result comes back and the step ends. If more work is owed, or new input has arrived, the loop claims again and opens another step; otherwise it fires &lt;code&gt;agent/turn-stopping&lt;/code&gt; and closes the turn.&lt;/p&gt;

&lt;p&gt;A few details are worth calling out because they explain &lt;em&gt;why&lt;/em&gt; the loop is shaped this way rather than as a simpler while-loop. &lt;code&gt;agent/pre-step&lt;/code&gt; is a waterfall event: listeners can rewrite or outright reject the messages a step is about to see, which is the extension point for things like injected context, guardrails, or compaction — all without touching the loop's own code. Even a rejected first claim still closes a durable turn, so the attempt is recorded rather than silently disappearing. And the three tool-pipeline events (&lt;code&gt;pre-execute&lt;/code&gt;, &lt;code&gt;execute&lt;/code&gt;, &lt;code&gt;post-execute&lt;/code&gt;) are also waterfalls, meaning any plugin can intercept a tool call in flight — for approval gating, cost tracking, or rewriting arguments — by hooking a well-defined seam instead of forking the tool implementation.&lt;/p&gt;

&lt;p&gt;This is the general argument for lifecycle hooks in a production agent: reliability work — retries, guardrails, cost caps, human approval — is almost never expressible as "add another &lt;code&gt;if&lt;/code&gt; statement to the main loop." It needs defined interception points that don't require understanding or modifying the whole control flow. A framework that doesn't expose those points forces every operational concern into ad hoc wrapper code around the model call.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Sessions, Events, and Replay
&lt;/h2&gt;

&lt;p&gt;Perhaps the least flashy and most consequential design decision in DeepSeek Harness is that the session is an &lt;strong&gt;append-only event log&lt;/strong&gt;, not a stored conversation transcript. &lt;code&gt;deriveMessages()&lt;/code&gt; reconstructs whatever the model actually sees by projecting model history from that log; separate raw streaming events are retained purely for replay and UI fidelity. The docs state a hard invariant: anything that reaches a model request must be reconstructable from the log — a new kind of model-visible input requires a new session-event type, not a side-channel.&lt;/p&gt;

&lt;p&gt;Concretely, the log accumulates a strict sequence of typed events per turn — a &lt;code&gt;user/message&lt;/code&gt;, one or more &lt;code&gt;assistant/message&lt;/code&gt; events, any &lt;code&gt;tool/call&lt;/code&gt; and &lt;code&gt;tool/result&lt;/code&gt; pairs, and a closing &lt;code&gt;turn/end&lt;/code&gt; — and everything downstream reads from that same sequence rather than from a separate cache: &lt;code&gt;deriveMessages()&lt;/code&gt; projects the model-visible history from it, forking branches off any completed-turn boundary in it, cold resume restarts a session by replaying it, and the trajectory view inspects it event-by-event, filtered by source.&lt;/p&gt;

&lt;p&gt;Why build it this way instead of just storing the final message history? A plain transcript answers "what did the conversation look like," but it can't answer "what did the model actually see at step 12," "what would have happened if we'd used a different model from this point," or "replay exactly this trajectory for an eval." An append-only, typed event log can answer all three, because every fact — including tool calls, subagent scheduling, and context injections — is a durable, individually addressable record rather than a flattened string.&lt;/p&gt;

&lt;p&gt;This is what makes &lt;strong&gt;forking&lt;/strong&gt; tractable: the subagent system, for example, can seed a new child session with a "balanced completed-turn prefix" of a parent's log — the events up through its last completed turn, deliberately excluding any in-flight, unbalanced turn — and the runtime's own invariants will accept that seed as valid replay input. The same mechanism underlies session resume after a process restart, and the "Trajectory view" the project's UI exposes for inspecting a run event-by-event, filtered by source. None of this is bolted on after the fact; it falls directly out of treating the session as a log rather than a cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Tools, Skills, and Subagents
&lt;/h2&gt;

&lt;p&gt;DeepSeek Harness distinguishes these abstractions cleanly, and it's worth being precise about the difference, since the terms get blurred in casual agent talk elsewhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tools&lt;/strong&gt; are the model-facing, single-call capabilities registered on &lt;code&gt;ctx.tools&lt;/code&gt; — file edits, shell execution, search. They're the atomic unit the model invokes and the pipeline guards (pre-execute, execute, post-execute).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skills&lt;/strong&gt; are reusable, composable instructions or procedures the agent can draw on — closer to a library of "how to do X well" than a callable function.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subagents&lt;/strong&gt; are a distinct capability seam for delegating entire pieces of work to a &lt;em&gt;child agent&lt;/em&gt;, with its own session, its own turn loop, and (per the docs) either a one-shot lifecycle or a &lt;strong&gt;continuable&lt;/strong&gt; one that can receive follow-up messages across multiple activations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The subagent design is unusually deep for a developer-preview project. Multiple named providers can coexist behind the same interface — a locally spawned child, a forked child that inherits the parent's conversation history, or a delegated session running inside Claude Code or Codex via their own SDKs — and a caller can request start-time capabilities like an output schema, a depth limit, or a restricted tool set, which the runtime validates against the chosen provider &lt;em&gt;before&lt;/em&gt; starting the child rather than silently ignoring what isn't supported. Continuable children maintain a durable session that can be resumed cold, interrupted, or reported back to their parent through a distinct "report" channel, deliberately separated from ordinary conversation so a transcript never confuses "what the runtime observed" with "what the child actually said."&lt;/p&gt;

&lt;p&gt;The practical lesson: tools, skills, and subagents solve different problems — atomic actions, reusable know-how, and delegated autonomy — and conflating them (e.g., implementing delegation as "just another tool call with no state") tends to produce systems that can't resume, can't be interrupted cleanly, and can't distinguish a child's own words from the runtime's bookkeeping.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Code Mode
&lt;/h2&gt;

&lt;p&gt;Standard tool-calling means every operation — read a file, then grep, then edit, then run tests — is a separate round trip through the model: it sees a result, decides the next call, and pays for another full context pass each time. &lt;strong&gt;Code mode&lt;/strong&gt; is one of DeepSeek Harness's four shipped presets (alongside Standard, Minimal, and Creator), and it changes this by exposing the same tool set as a generated TypeScript SDK. Instead of calling tools one at a time, the model writes a short program against that SDK and the harness executes it, so a sequence that would otherwise take five round trips can run as a single call.&lt;/p&gt;

&lt;p&gt;The claimed advantages are the obvious ones for anyone who has watched an agent burn tokens re-deciding the same next step five times: fewer model round trips, the ability to use real control flow (loops, conditionals, batching) instead of forcing every branch through the model, and more deterministic composition of operations that don't individually need a fresh judgment call.&lt;/p&gt;

&lt;p&gt;The trade-offs are just as real and worth stating plainly rather than glossing over. Letting a model write and execute code — even against a curated SDK — expands the attack surface and the sandboxing burden relative to a fixed menu of individually-validated tool calls; it shifts some debugging burden from "which tool call went wrong" to "which line of generated code went wrong"; and it depends on the model reliably producing correct, well-scoped programs, which is a different and not strictly easier reliability problem than reliably picking the next tool call. It's telling that DeepSeek's own published benchmarking for its models reportedly used &lt;strong&gt;Minimal&lt;/strong&gt; mode — a stripped two-tool (bash plus &lt;code&gt;str_replace_editor&lt;/code&gt;) preset — rather than Code mode, which suggests the project itself treats Code mode as a genuinely different, not strictly superior, execution model.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. DeepSeek Harness vs. LangGraph
&lt;/h2&gt;

&lt;p&gt;It's tempting to put these side by side as competitors, but they operate at different layers, and the comparison is more useful read that way.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;LangGraph&lt;/th&gt;
&lt;th&gt;DeepSeek Harness&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Core abstraction&lt;/td&gt;
&lt;td&gt;A directed (often cyclic) state graph of nodes and edges&lt;/td&gt;
&lt;td&gt;A Cordis plugin tree; the agent loop itself is one plugin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent loop&lt;/td&gt;
&lt;td&gt;You define nodes/edges explicitly; the graph &lt;em&gt;is&lt;/em&gt; the control flow&lt;/td&gt;
&lt;td&gt;A built-in turn/step loop with waterfall hook points (&lt;code&gt;agent/pre-step&lt;/code&gt;, &lt;code&gt;tools/*&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;A typed shared state object flowing through graph nodes&lt;/td&gt;
&lt;td&gt;An append-only session event log; state is derived, not stored directly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tools&lt;/td&gt;
&lt;td&gt;Callables wired into graph nodes&lt;/td&gt;
&lt;td&gt;A registered, guarded tool pipeline with pre/execute/post events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistence&lt;/td&gt;
&lt;td&gt;Checkpointers (e.g., Postgres) snapshot graph state after each step&lt;/td&gt;
&lt;td&gt;The session log itself is the persistence layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replay/recovery&lt;/td&gt;
&lt;td&gt;Resume from a checkpoint by thread ID; re-execute from a historic checkpoint to branch&lt;/td&gt;
&lt;td&gt;Fork from any completed-turn boundary in the log; cold-resume continuable sessions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subagents&lt;/td&gt;
&lt;td&gt;Multi-agent patterns (supervisor, swarm) built as graph structures&lt;/td&gt;
&lt;td&gt;A first-class capability seam with named providers and continuable children&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extensibility&lt;/td&gt;
&lt;td&gt;Add nodes/edges to the graph; swap components via Python/TS code&lt;/td&gt;
&lt;td&gt;Add or swap plugins via Cordis configuration without touching source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;A Python/TS library you embed and orchestrate yourself&lt;/td&gt;
&lt;td&gt;A standalone runtime with its own CLI, web UI, and process model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best use case&lt;/td&gt;
&lt;td&gt;Modeling explicit, inspectable control flow for workflows and multi-step pipelines&lt;/td&gt;
&lt;td&gt;Running a full, extensible coding/automation agent with tools, sandboxing, and a UI out of the box&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The honest framing is that LangGraph is primarily an &lt;strong&gt;orchestration and state-machine framework&lt;/strong&gt;: you bring the model calls and tools, and it gives you a graph structure, checkpointing, and human-in-the-loop interrupts for controlling execution flow explicitly. DeepSeek Harness aims at something broader — a &lt;strong&gt;complete agent runtime&lt;/strong&gt;, with the tools, sandbox, session storage, subagent transport, and UI already assembled, extensible through plugins rather than through graph authorship. You could, in principle, build a LangGraph node that calls out to a DeepSeek Harness session, or vice versa; they're not mutually exclusive so much as answers to different questions ("how do I control this workflow's flow" vs. "what infrastructure does my agent run inside of").&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Where It Sits in the Broader Harness Ecosystem
&lt;/h2&gt;

&lt;p&gt;DeepSeek Harness arrives alongside — and explicitly interoperates with — a growing set of coding-agent harnesses: it reads &lt;code&gt;AGENTS.md&lt;/code&gt; and &lt;code&gt;CLAUDE.md&lt;/code&gt; files, ships an MCP client and Agent Client Protocol support, and its subagent system can delegate to Claude Code or Codex sessions directly through their own SDKs rather than treating them as black-box competitors. That interoperability is itself a signal: the emerging convention isn't "one harness to rule them all," but a shared vocabulary (MCP for tools, AGENTS.md for repo-level instructions, ACP for cross-agent communication) that different harnesses are converging on independently. Compared to something like Claude Code, the philosophical difference DeepSeek Harness leans into hardest is depth of plugin surface — its own docs note there is "no privileged core to patch," which is a stronger claim about extensibility than most comparable tools make about themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. What DeepSeek Gets Right
&lt;/h2&gt;

&lt;p&gt;A few architectural choices stand out as genuinely well-reasoned, not just well-marketed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Treating the harness as infrastructure, not prompt glue.&lt;/strong&gt; The plugin boundary is enforced at the framework level (Cordis), not left as a convention developers are trusted to follow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An event log as the single source of truth.&lt;/strong&gt; Deriving model-visible context from the log, rather than storing it separately, closes a whole category of "the UI shows something different from what the model saw" bugs by construction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capability-checked delegation.&lt;/strong&gt; The subagent system validates requested capabilities (schema support, depth limits, tool filters) against a provider &lt;em&gt;before&lt;/em&gt; starting a child, rejecting loudly rather than silently ignoring an unsupported request — a small detail that prevents a common class of "it looked like it worked" failures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuration-level extensibility.&lt;/strong&gt; Swapping a sandbox backend, adding a model provider, or scoping a session's toolset are all documented as configuration changes, not source patches.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  12. What Is Still Difficult
&lt;/h2&gt;

&lt;p&gt;None of this makes autonomous agents reliable by default, and the project doesn't claim otherwise — it's explicitly a developer preview with, in its own words, compatibility-breaking changes still ahead.&lt;/p&gt;

&lt;p&gt;A better harness doesn't solve model reliability: a model that hallucinates a file path or misreads a diff will do so regardless of how well-designed the surrounding plugin system is. Context growth over long sessions is still a real cost and latency problem that a good event log doesn't eliminate, only makes visible. Tool failures and partial states still need application-level handling — the pipeline gives you hooks, not automatic correctness. Sandboxing a model that can write and execute arbitrary code (as Code mode does) is a genuinely harder security problem than sandboxing a fixed menu of tools, and it's not clear the field has converged on a satisfying answer yet. Long-running, multi-hour agent tasks still accumulate cost and drift in ways that better session bookkeeping only helps you &lt;em&gt;observe&lt;/em&gt;, not prevent. And evaluating or debugging a complex multi-turn, multi-subagent trajectory remains labor-intensive even with a trajectory viewer — you still have to read it.&lt;/p&gt;

&lt;h2&gt;
  
  
  13. What Developers Can Learn From This — Even Without Using It
&lt;/h2&gt;

&lt;p&gt;Several of these principles generalize well beyond this specific project:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Separate the model from the runtime.&lt;/strong&gt; Treat the model as a swappable adapter, not the organizing abstraction of your system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat tool execution as a first-class subsystem&lt;/strong&gt;, with its own guarded pipeline — not inline logic in your agent loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make agent state durable&lt;/strong&gt; and reconstructable, not just cached in memory for the current process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Record execution trajectories&lt;/strong&gt;, not just final outputs — you cannot debug or evaluate what you didn't log.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design for replay and recovery from the start&lt;/strong&gt;; retrofitting it into a stateless design is much harder than building it in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use plugins or defined interfaces instead of hard-coded integrations&lt;/strong&gt;, so capabilities can be swapped without touching core logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat skills as composable capabilities distinct from tools&lt;/strong&gt; — reusable know-how is a different abstraction from a callable function.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make observability part of the runtime&lt;/strong&gt;, not an afterthought layered on top via logging statements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design failure handling explicitly&lt;/strong&gt;, with defined lifecycle hooks — don't rely on wrapping the whole loop in a try/catch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the harness model-agnostic where practical&lt;/strong&gt; — it future-proofs the investment in everything else you build.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  14. A Minimal Agent Harness Architecture
&lt;/h2&gt;

&lt;p&gt;If you strip this down to the smallest version worth building yourself, it looks roughly like this. A swappable model adapter sits behind an agent loop that owns the turn/step lifecycle and exposes hook points at each stage. That loop draws on three sibling capabilities: context assembled fresh from the log on each step, a guarded tool-execution pipeline, and a library of composable skills. The tool pipeline in turn depends on an isolation seam — a sandbox — for anything that touches the filesystem or a shell, and a separate delegation seam for spawning or resuming subagents. Every one of those components writes to a single append-only event log, which is the system's actual source of truth, and that log is what makes replay, forking, resuming a crashed session, and general observability possible after the fact — rather than something you have to reconstruct from scattered application logs.&lt;/p&gt;

&lt;p&gt;Not every project needs all of this on day one. But knowing which piece you're skipping — and what you're giving up by skipping it — is a much better position than discovering the gap in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;DeepSeek Harness is not a new model, and it doesn't need to be evaluated as one. It's a bet about where the differentiation in AI agents is heading: less about which model answers a given call, and more about the runtime that decides what that model sees, what it's allowed to do, how its work is recorded, and how failures are recovered from. That bet isn't unique to DeepSeek — it's visible across the current generation of coding-agent harnesses converging on shared protocols like MCP and AGENTS.md — but the "everything is a plugin" commitment, enforced by a real framework rather than a convention, makes DeepSeek Harness a clear and fairly rigorous illustration of it.&lt;/p&gt;

&lt;p&gt;The next generation of AI agents may be differentiated less by which model they call and more by the runtime that surrounds the model. If that's right, the interesting engineering work isn't in the model API call at all — it's in everything this article just walked through.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sources &amp;amp; Further Reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/deepseek-ai/deepseek-harness" rel="noopener noreferrer"&gt;DeepSeek Harness — official GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/architecture.md" rel="noopener noreferrer"&gt;DeepSeek Harness — architecture documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/subsystems/subagent.md" rel="noopener noreferrer"&gt;DeepSeek Harness — subagent subsystem documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cordiverse/cordis" rel="noopener noreferrer"&gt;Cordis — plugin framework repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cordiverse/paper" rel="noopener noreferrer"&gt;Cordis design paper: "A Programming Paradigm for Spatiotemporal Composability"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://deepseek.com/harness/en/" rel="noopener noreferrer"&gt;Official DeepSeek Harness project page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thenewstack.io/deepseek-harness-open-source-plugins/" rel="noopener noreferrer"&gt;The New Stack: "DeepSeek open sources an agent harness where everything is a plugin"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.marktechpost.com/2026/08/17/deepseek-ai-releases-deepseek-harness-in-developer-preview/" rel="noopener noreferrer"&gt;MarkTechPost coverage of the developer-preview release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://atlan.com/know/ai-agent/ai-agent-memory/what-is-langgraph/" rel="noopener noreferrer"&gt;LangGraph documentation and production-use overviews (Atlan, Mastra, Spheron) for comparison context&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;DeepSeek Harness is in active developer preview at the time of writing; APIs, plugin interfaces, and preset behavior are explicitly expected to change before a stable release.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>agents</category>
      <category>automation</category>
    </item>
    <item>
      <title>How I Built a Compounding Markdown Wiki for Research: When Standard RAG Stops Being Enough</title>
      <dc:creator>Pramoda Sahu</dc:creator>
      <pubDate>Thu, 25 Jun 2026 10:42:17 +0000</pubDate>
      <link>https://dev.to/pramod_sahu_d5bd2e6de82d1/how-i-built-a-compounding-markdown-wiki-for-research-when-standard-rag-stops-being-enough-1bnd</link>
      <guid>https://dev.to/pramod_sahu_d5bd2e6de82d1/how-i-built-a-compounding-markdown-wiki-for-research-when-standard-rag-stops-being-enough-1bnd</guid>
      <description>&lt;p&gt;Most AI knowledge systems have a hidden failure mode: they keep rediscovering what they already know.&lt;/p&gt;

&lt;p&gt;That is fine for lightweight lookup. It becomes a problem in long-horizon, multi-source research. Once you move beyond a handful of documents, the usual pattern of “retrieve some chunks, ask the model, hope it synthesizes correctly” starts to break down. Answers get fuzzier. Citations blur together. Contradictions slip through. And every new document increases cost without necessarily increasing understanding.&lt;/p&gt;

&lt;p&gt;I ran into this while working with large research corpora: papers, transcripts, articles, and project-specific source material that needed to stay usable over time. &lt;strong&gt;RAG&lt;/strong&gt; solved one problem — fitting large corpora into a model workflow — but it did not solve the deeper one. It gave me search, not cumulative knowledge.&lt;/p&gt;

&lt;p&gt;So I built something different: a &lt;strong&gt;compounding markdown wiki&lt;/strong&gt; inspired by &lt;strong&gt;Recursive Language Model (RLM)&lt;/strong&gt; principles, where ingestion happens once, structure accumulates over time, and the 100th document makes the first 99 more useful. This is not a literal implementation of the RLM paper; it is an adaptation of the core idea that useful state should live outside the model context window and be updated incrementally.&lt;/p&gt;

&lt;p&gt;In this post, I’ll break down the failure mode I was seeing, why standard chunk-retrieval RAG was not enough for this kind of work, and how this architecture produces more stable, cited, human-readable research outputs at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem: Context Rot
&lt;/h2&gt;

&lt;p&gt;If you’ve used AI agents on research material, you’ve probably seen some version of this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You feed one paper into ChatGPT. It answers your question well.&lt;/li&gt;
&lt;li&gt;You feed a second paper. It still works.&lt;/li&gt;
&lt;li&gt;You ask about both papers together. The synthesis gets weaker.&lt;/li&gt;
&lt;li&gt;You add a tenth paper. Hallucinations start creeping in.&lt;/li&gt;
&lt;li&gt;You add a fiftieth. Earlier sources start getting dropped without warning.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is &lt;strong&gt;context rot&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The issue is not that the model “forgets” in a clean, binary way. It is that as the context window fills up, attention gets diluted across more and more tokens. Earlier material does not vanish; it just becomes harder for the model to use precisely. The degradation is gradual and messy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Answers become more generic&lt;/li&gt;
&lt;li&gt;Citations get mixed up&lt;/li&gt;
&lt;li&gt;Contradictions go undetected&lt;/li&gt;
&lt;li&gt;Earlier evidence stops influencing later responses reliably&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That matters in research workflows, where the hard part usually is not finding a sentence that mentions a term. It is maintaining a consistent understanding across many sources.&lt;/p&gt;

&lt;h3&gt;
  
  
  What context rot looks like in practice
&lt;/h3&gt;

&lt;p&gt;A typical agent loop looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Build system prompt (persona, instructions, tool schemas)
2. Add conversation history (growing every turn)
3. Add retrieved context (RAG chunks, file contents)
4. Send to model
5. Get response
6. Append response to history
7. Repeat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By turn 10, you may already be around 30K tokens. By turn 30, you can be well past 100K. Even if your model technically supports a large context window, it does not attend equally well to everything inside it.&lt;/p&gt;

&lt;p&gt;The symptoms are familiar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The answer shifts from specific claims to vague summaries&lt;/li&gt;
&lt;li&gt;The model contradicts something it said a few turns earlier&lt;/li&gt;
&lt;li&gt;It attributes one source’s claim to the wrong person&lt;/li&gt;
&lt;li&gt;It refuses to answer because it detects inconsistency but cannot resolve it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the hidden tax on long-running AI research sessions. And it is what led me to rethink the default architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Standard RAG Solves Scale but Not Cumulative Research
&lt;/h2&gt;

&lt;p&gt;The standard answer to context rot is &lt;strong&gt;Retrieval-Augmented Generation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of stuffing everything into the prompt, you embed documents, retrieve the most relevant chunks for the current question, and pass only those chunks to the model. That absolutely helps with scale. It keeps prompts smaller and avoids dumping entire corpora into context.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;basic top-K chunk-retrieval RAG pipelines&lt;/strong&gt; have a different limitation: they often treat each query as a mostly fresh synthesis problem.&lt;/p&gt;

&lt;p&gt;For a query like “What did Smith say about the merger?”, a standard RAG pipeline typically does this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Embed the question&lt;/li&gt;
&lt;li&gt;Search for similar chunks across all documents&lt;/li&gt;
&lt;li&gt;Return the top-K matches&lt;/li&gt;
&lt;li&gt;Ask the model to synthesize an answer&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That works well for retrieval. It works much less well for cumulative understanding.&lt;/p&gt;

&lt;p&gt;Production systems can add memory layers, rerankers, metadata filters, cached summaries, or knowledge graphs. But once you add those pieces, you are already moving beyond plain chunk retrieval and toward persistent state — which is the shift this post is about.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where standard RAG falls short for research
&lt;/h3&gt;

&lt;h4&gt;
  
  
  No persistent cross-source memory by default
&lt;/h4&gt;

&lt;p&gt;If &lt;strong&gt;Document A&lt;/strong&gt; mentions Smith’s stance on regulation, and &lt;strong&gt;Document B&lt;/strong&gt; records Jones pushing the opposite position, basic RAG does not persist that relationship. It may retrieve both chunks if your query is good enough, but it does not maintain an explicit “Smith vs. Jones” structure over time.&lt;/p&gt;

&lt;p&gt;That means comparisons are synthesized from scratch on demand, not built into the knowledge system.&lt;/p&gt;

&lt;h4&gt;
  
  
  No compounding
&lt;/h4&gt;

&lt;p&gt;Adding &lt;strong&gt;Document C&lt;/strong&gt; does not automatically make the system smarter about &lt;strong&gt;Documents A and B&lt;/strong&gt;. The vector index just gets larger.&lt;/p&gt;

&lt;p&gt;In practice, that often means more possible matches, more noise, and more opportunity for irrelevant chunks to crowd out the useful ones. As the corpus grows, retrieval quality can degrade unless you keep tuning chunking, ranking, metadata filters, and prompts.&lt;/p&gt;

&lt;h4&gt;
  
  
  Weak provenance
&lt;/h4&gt;

&lt;p&gt;RAG can usually tell you which file a chunk came from. It is much worse at maintaining fine-grained provenance over time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which paragraph supported this claim?&lt;/li&gt;
&lt;li&gt;Is the claim directly stated or inferred?&lt;/li&gt;
&lt;li&gt;Do two sources disagree on the same point?&lt;/li&gt;
&lt;li&gt;Was this information updated later by a more reliable source?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those questions matter in research, and standard chunk retrieval does not answer them by itself.&lt;/p&gt;

&lt;h4&gt;
  
  
  Query-time cost keeps accumulating
&lt;/h4&gt;

&lt;p&gt;As more documents enter the system, you are not only searching more material. You are also often retrieving more candidates, performing more ranking, and building prompts that grow in complexity. Latency and token costs rise with corpus size, and the burden shifts to query time.&lt;/p&gt;

&lt;p&gt;That is acceptable for search. It is inefficient for ongoing research programs with hundreds of documents per project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shift: Knowledge Should Compound
&lt;/h2&gt;

&lt;p&gt;What I wanted was not a better search engine. I wanted a &lt;strong&gt;knowledge base&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;More specifically, I wanted a system where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;50th document&lt;/strong&gt; is processed in the context of what the first 49 already taught the system&lt;/li&gt;
&lt;li&gt;Query cost depends more on the &lt;strong&gt;selected wiki pages&lt;/strong&gt; than on the full raw corpus&lt;/li&gt;
&lt;li&gt;Every claim traces back to a &lt;strong&gt;specific source paragraph&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Contradictions are &lt;strong&gt;flagged for review&lt;/strong&gt; instead of being silently overwritten&lt;/li&gt;
&lt;li&gt;The resulting knowledge is &lt;strong&gt;human-readable markdown&lt;/strong&gt;, not an opaque index&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the real distinction here:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A search engine finds relevant documents. A knowledge base preserves relationships between them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The RLM Pattern Behind the Design
&lt;/h2&gt;

&lt;p&gt;The deeper design pattern I built on is &lt;strong&gt;Recursive Language Models&lt;/strong&gt; (&lt;a href="https://arxiv.org/abs/2512.24601" rel="noopener noreferrer"&gt;arXiv:2512.24601&lt;/a&gt;). Again, I am using the paper as inspiration rather than claiming a direct implementation.&lt;/p&gt;

&lt;p&gt;The core idea I borrowed is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep &lt;strong&gt;persistent external state&lt;/strong&gt; outside the model’s context window&lt;/li&gt;
&lt;li&gt;use the model for &lt;strong&gt;bounded operations&lt;/strong&gt; on that state&lt;/li&gt;
&lt;li&gt;avoid making the model reconstruct everything from raw text on every query&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my case, that persistent state is a markdown wiki.&lt;/p&gt;

&lt;p&gt;Instead of asking the LLM to rediscover structure from raw documents over and over, I let it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;extract entities from a chunk&lt;/li&gt;
&lt;li&gt;update a specific page&lt;/li&gt;
&lt;li&gt;merge structured facts&lt;/li&gt;
&lt;li&gt;synthesize from pre-resolved references&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That shifts the expensive reasoning from “every query forever” to “once at ingestion, then maintained incrementally.”&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture: A Compounding Markdown Wiki
&lt;/h2&gt;

&lt;p&gt;The system is organized as a &lt;strong&gt;markdown wiki&lt;/strong&gt; with three layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wiki/
├── SCHEMA.md           # Domain conventions, tag taxonomy
├── index.md            # Content catalog
├── log.md              # Audit trail
├── raw/                # Layer 1: Immutable source files
│   ├── articles/
│   ├── papers/
│   └── transcripts/
├── entities/           # Layer 2: People, orgs, products
├── concepts/           # Layer 2: Topics, ideas
├── comparisons/        # Layer 2: Side-by-side analyses
└── queries/            # Layer 2: Preserved Q&amp;amp;A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure is deliberately plain. No proprietary store. No hidden schema behind an API. The wiki itself is the database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1: Raw sources
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;raw/&lt;/code&gt; directory contains immutable source material. The system can read these files, but it does not rewrite them.&lt;/p&gt;

&lt;p&gt;Each source gets a &lt;strong&gt;SHA-256 hash&lt;/strong&gt;, which makes drift detection straightforward. If I ingest the same source again and the content hash has not changed, the pipeline can safely skip expensive work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2: The wiki
&lt;/h3&gt;

&lt;p&gt;This is the agent-owned knowledge layer.&lt;/p&gt;

&lt;p&gt;Every major &lt;strong&gt;entity&lt;/strong&gt; gets its own page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;people&lt;/li&gt;
&lt;li&gt;organizations&lt;/li&gt;
&lt;li&gt;products&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every major &lt;strong&gt;concept&lt;/strong&gt; gets its own page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;topics&lt;/li&gt;
&lt;li&gt;definitions&lt;/li&gt;
&lt;li&gt;theories&lt;/li&gt;
&lt;li&gt;techniques&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pages are linked using &lt;code&gt;[[wikilinks]]&lt;/code&gt;, which makes relationships explicit and navigable. This is where the compounding happens: as new sources arrive, they enrich existing pages rather than living as isolated chunks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 3: The schema
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;SCHEMA.md&lt;/code&gt; defines domain conventions, taxonomy, and page structure. This matters because consistency is what allows incremental updates to stay clean over time. Without a schema, you do not get a knowledge base — you get a pile of markdown.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why markdown matters
&lt;/h3&gt;

&lt;p&gt;One of the most practical design decisions here is also one of the least flashy: &lt;strong&gt;everything is stored as files&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That gives you several advantages immediately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can inspect the knowledge base without custom tooling&lt;/li&gt;
&lt;li&gt;You can open it directly in &lt;a href="https://obsidian.md/" rel="noopener noreferrer"&gt;Obsidian&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;You can version it with &lt;a href="https://git-scm.com/" rel="noopener noreferrer"&gt;Git&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;You can edit pages by hand when needed&lt;/li&gt;
&lt;li&gt;You are not locked into a vector database or vendor-specific format&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point matters for long-lived research systems. If your knowledge base outlives your current model stack, it should still be readable.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Ingestion Works
&lt;/h2&gt;

&lt;p&gt;The ingestion pipeline is where the system earns its keep. Instead of postponing structure until query time, it resolves as much as possible up front.&lt;/p&gt;

&lt;p&gt;When I ingest a source — whether it is a URL, PDF, or transcript — the pipeline does the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fetch and normalize&lt;/strong&gt; the content into clean markdown&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write to &lt;code&gt;raw/&lt;/code&gt;&lt;/strong&gt; with metadata and a SHA-256 hash&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check drift&lt;/strong&gt; and skip unchanged sources&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chunk semantically&lt;/strong&gt; instead of by arbitrary token count alone&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract per chunk&lt;/strong&gt; using a smaller, cheaper model call&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aggregate&lt;/strong&gt; and deduplicate the extracted structures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-reference&lt;/strong&gt; against existing wiki pages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate or update&lt;/strong&gt; pages via LLM-assisted editing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Update the index and append to the audit log&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key difference from RAG is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Extraction happens once at ingest time, not repeatedly at query time.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  What “semantic chunking” means here
&lt;/h3&gt;

&lt;p&gt;In this system, chunking is heuristic rather than magical. I usually split by the strongest available content boundary first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;document headings and subheadings for articles or papers&lt;/li&gt;
&lt;li&gt;paragraph groups for prose-heavy text&lt;/li&gt;
&lt;li&gt;speaker turns for transcripts&lt;/li&gt;
&lt;li&gt;sentence windows only as a fallback when no stronger boundary exists&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is to keep each chunk as a coherent unit of thought. That improves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;entity resolution&lt;/li&gt;
&lt;li&gt;fact extraction&lt;/li&gt;
&lt;li&gt;citation accuracy&lt;/li&gt;
&lt;li&gt;contradiction checks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What gets extracted per chunk
&lt;/h3&gt;

&lt;p&gt;Each chunk is processed into structured JSON containing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Entities&lt;/strong&gt;: people, organizations, products&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concepts&lt;/strong&gt;: topics and definitions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Facts&lt;/strong&gt;: claims with certainty markers and provenance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the kind of structure this stage is designed to produce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"entities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"person"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"aliases"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"J. Smith"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"concepts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"merger regulation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"definition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Regulatory constraints affecting merger approval."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"facts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"claim"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Smith argued that the merger would face regulatory friction."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"certainty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source_paragraph"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"p12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"q3-call-2026-04"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact schema can vary by domain, but the point is the same: ingestion produces structured evidence, not just searchable text.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Compact End-to-End Example
&lt;/h2&gt;

&lt;p&gt;This is the shortest example I have found that shows the full idea.&lt;/p&gt;

&lt;h3&gt;
  
  
  1) Source excerpt
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;[Source: raw/transcripts/q3-call-2026-04.md]

Paragraph p12:
"John Smith said the merger would likely face regulatory friction in the EU,
but still described the deal as strategically necessary."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2) Extraction output
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"entities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"person"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EU"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"organization"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"concepts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"merger regulation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"definition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Regulatory constraints that may affect merger approval."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"facts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"claim"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Smith said the merger would likely face regulatory friction in the EU."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"certainty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"q3-call-2026-04"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source_paragraph"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"p12"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"claim"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Smith described the deal as strategically necessary."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"certainty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"q3-call-2026-04"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source_paragraph"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"p12"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3) Wiki page update
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# John Smith&lt;/span&gt;

tags: [person, executive]

&lt;span class="gu"&gt;## Positions&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Smith said the merger would likely face regulatory friction in the EU.  
  Source: [[q3-call-2026-04#p12]]
&lt;span class="p"&gt;
-&lt;/span&gt; Smith described the deal as strategically necessary.  
  Source: [[q3-call-2026-04#p12]]

&lt;span class="gu"&gt;## Related&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; [[merger regulation]]
&lt;span class="p"&gt;-&lt;/span&gt; [[EU]]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4) Query response
&lt;/h3&gt;

&lt;p&gt;If I later ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What did Smith say about the merger in the Q3 call?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The system can answer from the wiki first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;John Smith said the merger would likely face regulatory friction in the EU,
while still describing the deal as strategically necessary.

Citations:
&lt;span class="p"&gt;-&lt;/span&gt; [[John Smith]]
&lt;span class="p"&gt;-&lt;/span&gt; [[q3-call-2026-04#p12]]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the compounding behavior in miniature: source text becomes structured evidence, evidence updates a persistent page, and later questions hit the page instead of forcing the model to rediscover the same relationship from raw text.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Contradiction Detection Works
&lt;/h2&gt;

&lt;p&gt;This is the part I had to be careful not to overstate.&lt;/p&gt;

&lt;p&gt;I do &lt;strong&gt;not&lt;/strong&gt; treat contradiction detection as perfect. In my implementation, it is a review-oriented mechanism, not a guarantee of truth.&lt;/p&gt;

&lt;p&gt;At ingestion time, new facts are compared against existing page facts with a mix of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;entity matching&lt;/li&gt;
&lt;li&gt;claim-type matching from the schema&lt;/li&gt;
&lt;li&gt;provenance checks&lt;/li&gt;
&lt;li&gt;an LLM judgment step that labels a new fact as &lt;strong&gt;supports&lt;/strong&gt;, &lt;strong&gt;updates&lt;/strong&gt;, &lt;strong&gt;conflicts with&lt;/strong&gt;, or &lt;strong&gt;unclear&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the system sees a likely conflict, it does not auto-resolve it into one canonical statement. It adds both claims to the relevant page and marks the conflict for review.&lt;/p&gt;

&lt;p&gt;A simplified page fragment might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Open conflicts&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Smith said the merger was strategically necessary.  
  Source: [[q3-call-2026-04#p12]]
&lt;span class="p"&gt;
-&lt;/span&gt; Smith said the merger should be delayed until regulatory conditions improve.  
  Source: [[investor-interview-2026-05#p4]]

Status: needs review
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So when I say contradictions are “flagged,” I mean &lt;strong&gt;candidate contradictions are surfaced and preserved instead of being silently smoothed over&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Querying Works
&lt;/h2&gt;

&lt;p&gt;Once the wiki exists, queries become much lighter.&lt;/p&gt;

&lt;p&gt;Instead of searching raw chunks across the entire corpus, the system searches the &lt;strong&gt;structured wiki&lt;/strong&gt; first. In many cases, that means &lt;strong&gt;keyword, title, and tag-based search&lt;/strong&gt;, with no embeddings required for the first pass.&lt;/p&gt;

&lt;p&gt;The query flow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Search the wiki&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Select a strategy&lt;/strong&gt; using a fast model call&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execute&lt;/strong&gt; the strategy with the appropriate model&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Return&lt;/strong&gt; an answer with &lt;code&gt;[[Page Name]]&lt;/code&gt; or source-anchor citations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The strategy selection matters because not every question needs the same level of work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query strategies
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Lookup
&lt;/h4&gt;

&lt;p&gt;For a single fact from one page.&lt;/p&gt;

&lt;h4&gt;
  
  
  Synthesis
&lt;/h4&gt;

&lt;p&gt;For compare/contrast tasks across multiple pages or sources.&lt;/p&gt;

&lt;h4&gt;
  
  
  Deep-dive
&lt;/h4&gt;

&lt;p&gt;For more extensive analysis across a small set of selected pages plus raw-source verification if needed.&lt;/p&gt;

&lt;p&gt;In practice, query cost is &lt;strong&gt;much closer to wiki size and selected pages than to total raw document count&lt;/strong&gt;. It is not literally constant, but it scales more gently than repeatedly searching and prompting over the full corpus.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where raw retrieval still fits
&lt;/h3&gt;

&lt;p&gt;This is not an anti-RAG purity test. I still use raw-document retrieval in edge cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cold-start questions before enough wiki structure exists&lt;/li&gt;
&lt;li&gt;verification against the underlying source text&lt;/li&gt;
&lt;li&gt;cases where a page summary looks incomplete or stale&lt;/li&gt;
&lt;li&gt;exploratory search across new material before ingestion rules are tuned&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For me, the best framing is not “replace RAG everywhere.” It is “stop making raw retrieval do all the memory work.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Beats Plain RAG for Research
&lt;/h2&gt;

&lt;p&gt;Here is the comparison that mattered in practice:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Plain chunk-retrieval RAG&lt;/th&gt;
&lt;th&gt;Compounding Wiki&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Knowledge growth&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Re-synthesizes per query&lt;/td&gt;
&lt;td&gt;Accumulates across sources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cross-referencing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Implicit (similar chunks)&lt;/td&gt;
&lt;td&gt;Explicit (entity pages + wikilinks)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Query cost trend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Usually grows with retrieval/ranking complexity&lt;/td&gt;
&lt;td&gt;Tied more to selected pages than total corpus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Provenance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Often file/chunk-level&lt;/td&gt;
&lt;td&gt;Page and paragraph-anchor level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contradictions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Usually handled inside answer generation&lt;/td&gt;
&lt;td&gt;Surfaced as reviewable conflicts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Human-readable&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Often opaque to humans&lt;/td&gt;
&lt;td&gt;Plain markdown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Edit maintenance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Often requires re-indexing&lt;/td&gt;
&lt;td&gt;Often localized to page updates&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few of these deserve emphasis.&lt;/p&gt;

&lt;h3&gt;
  
  
  Provenance becomes operational, not cosmetic
&lt;/h3&gt;

&lt;p&gt;In many RAG systems, “citation” means attaching a filename or chunk excerpt to an answer. That is useful, but it is not enough for research-quality traceability.&lt;/p&gt;

&lt;p&gt;In the wiki model, claims can point back to specific paragraphs, and disagreements can remain visible instead of being collapsed into one model-generated summary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contradictions stop disappearing
&lt;/h3&gt;

&lt;p&gt;One of the worst behaviors in AI research pipelines is silent reconciliation. If two sources disagree, the model often smooths them into a single answer.&lt;/p&gt;

&lt;p&gt;By maintaining persistent pages and structured fact updates, likely conflicts can be surfaced and flagged instead of overwritten. That changes the system from “helpful summarizer” into something closer to a research assistant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Edits become cheaper
&lt;/h3&gt;

&lt;p&gt;In a vector-centric workflow, changing a source often means re-embedding and re-indexing. In this setup, a targeted edit can mean updating one source, recomputing only the affected structures, and revising the relevant pages.&lt;/p&gt;

&lt;p&gt;That is not free, but it is usually more localized.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Performance Notes
&lt;/h2&gt;

&lt;p&gt;The original draft of this post was too absolute here, so let me be precise.&lt;/p&gt;

&lt;p&gt;The exact latency and cost depend on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the model provider&lt;/li&gt;
&lt;li&gt;whether extraction uses a small local model or an API model&lt;/li&gt;
&lt;li&gt;corpus size and document shape&lt;/li&gt;
&lt;li&gt;how aggressively you verify against raw sources&lt;/li&gt;
&lt;li&gt;the size of the wiki pages being synthesized at query time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my own testing, the main gain was not “zero scaling cost.” It was moving a large share of the work to ingestion and keeping many queries small because they hit pre-structured pages instead of raw corpora.&lt;/p&gt;

&lt;p&gt;So when I say the system is more stable at scale, I mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;qualitatively&lt;/strong&gt; more consistent on repeated research questions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;operationally&lt;/strong&gt; easier to trace and debug&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;economically&lt;/strong&gt; better when the same corpus gets queried many times&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you publish hard benchmark numbers, include your corpus, models, prompt strategy, and what counts as an “active project.” Without that context, qualitative claims are more honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Enables
&lt;/h2&gt;

&lt;p&gt;The compounding wiki model changes what kinds of workflows are practical.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-source research
&lt;/h3&gt;

&lt;p&gt;You can ask questions across large document sets and get answers tied back to the exact paragraph that supports them.&lt;/p&gt;

&lt;p&gt;If you ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What did Smith say about the merger in the Q3 call?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;the goal is not to retrieve a vaguely relevant transcript chunk. The goal is to resolve “Smith,” locate the relevant page or source anchor, and return the supporting paragraph.&lt;/p&gt;

&lt;h3&gt;
  
  
  Project-specific memory for teams and writers
&lt;/h3&gt;

&lt;p&gt;Each new interview, paper, or article enriches what the system already knows.&lt;/p&gt;

&lt;p&gt;If the third transcript creates a &lt;code&gt;[[John Smith]]&lt;/code&gt; page, the fiftieth transcript should update that page, not create one more disconnected chunk.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better structure as the corpus matures
&lt;/h3&gt;

&lt;p&gt;As the graph gets denser, the wiki tends to become more useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;more entity resolution&lt;/li&gt;
&lt;li&gt;better cross-referencing&lt;/li&gt;
&lt;li&gt;richer concept pages&lt;/li&gt;
&lt;li&gt;stronger comparison pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the compounding effect plain RAG does not naturally provide on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Packaging and Tooling
&lt;/h2&gt;

&lt;p&gt;The system is open source here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/pksw4u/rlm-wiki" rel="noopener noreferrer"&gt;https://github.com/pksw4u/rlm-wiki&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the time of writing, I would describe it as a practical, evolving project rather than a finished universal framework.&lt;/p&gt;

&lt;p&gt;It works in several modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;As a &lt;strong&gt;Hermes Agent&lt;/strong&gt; skill by dropping &lt;code&gt;SKILL.md&lt;/code&gt; into your skills directory&lt;/li&gt;
&lt;li&gt;As a &lt;strong&gt;standalone Python library&lt;/strong&gt; via &lt;code&gt;pip install rlm-wiki&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;As a native &lt;strong&gt;Obsidian&lt;/strong&gt; vault because the wiki is plain markdown&lt;/li&gt;
&lt;li&gt;Alongside &lt;strong&gt;llm-wiki&lt;/strong&gt;, using the same format so they can coexist&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are not familiar with those names:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hermes Agent&lt;/strong&gt; is the agent runtime this project was designed to plug into.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;llm-wiki&lt;/strong&gt; is a related markdown-wiki workflow that shares a compatible file format.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the wiki is just files, the data is not trapped inside a database that only one runtime understands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Minimal usage example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;rlm-wiki
rlm-wiki ingest ./docs/q3-call.pdf &lt;span class="nt"&gt;--project&lt;/span&gt; ./wiki
rlm-wiki ask &lt;span class="s2"&gt;"What did Smith say about the merger?"&lt;/span&gt; &lt;span class="nt"&gt;--project&lt;/span&gt; ./wiki
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example entity page format
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# John Smith&lt;/span&gt;

tags: [person]
aliases: [J. Smith]

&lt;span class="gu"&gt;## Summary&lt;/span&gt;
Executive involved in merger discussions.

&lt;span class="gu"&gt;## Claims&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Smith argued the merger would face regulatory friction in the EU.  
  Source: [[q3-call-2026-04#p12]]

&lt;span class="gu"&gt;## Related&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; [[merger regulation]]
&lt;span class="p"&gt;-&lt;/span&gt; [[EU]]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the operational point I care about most:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The wiki is just markdown files. No required database, no vendor lock-in, and no mandatory embedding index to keep the core state readable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That means your knowledge base remains usable even if your model stack changes later.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Deeper Point: Search Is Not Understanding
&lt;/h2&gt;

&lt;p&gt;A lot of AI tooling still treats every question as a fresh event:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;search&lt;/li&gt;
&lt;li&gt;retrieve&lt;/li&gt;
&lt;li&gt;answer&lt;/li&gt;
&lt;li&gt;forget&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a useful pattern for document access. It is a poor pattern for research.&lt;/p&gt;

&lt;p&gt;Research is cumulative. If you are trying to understand Smith’s position, the answer usually does not live in one chunk. It emerges from multiple interviews, repeated claims, revisions over time, and contradictions with other people’s statements.&lt;/p&gt;

&lt;p&gt;A system that compounds can do things a query-time retrieval stack struggles with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;resolve some relationships once instead of rediscovering them repeatedly&lt;/li&gt;
&lt;li&gt;preserve explicit links across sources&lt;/li&gt;
&lt;li&gt;keep provenance attached to claims&lt;/li&gt;
&lt;li&gt;improve existing pages as new evidence appears&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is not just a better implementation detail. It is a different idea of what an AI knowledge system should be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RAG is good for retrieval, but plain chunk-retrieval RAG is weak for cumulative research&lt;/strong&gt; because it treats many questions as fresh synthesis tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context rot is a real scaling problem&lt;/strong&gt; in long-running AI research workflows, not just a context-window marketing issue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A markdown wiki can serve as persistent external state&lt;/strong&gt;, replacing vector-only memory with readable, editable files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ingestion-time extraction changes the economics&lt;/strong&gt; by doing the hard structuring work once instead of on every query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RLM-style bounded operations keep the working set small&lt;/strong&gt; even as document collections grow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid systems are often the practical answer&lt;/strong&gt;: persistent wiki first, raw retrieval when needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;What I ended up building was not “RAG, but better.” It was a different architecture with a different goal.&lt;/p&gt;

&lt;p&gt;RAG helps models find relevant text. A compounding wiki helps a system build and preserve relationships, provenance, and working understanding over time. That distinction starts to matter once your corpus gets large, your questions become comparative, and your tolerance for vague synthesis drops.&lt;/p&gt;

&lt;p&gt;If you work with research-heavy AI workflows, this is the assumption I would revisit: maybe the model should not be responsible for reconstructing knowledge from scratch on every question. Maybe the system should already know how your sources relate before the question is even asked.&lt;/p&gt;

&lt;p&gt;That is the promise of a compounding knowledge base. The more you feed it, the more useful its prior structure becomes — not because the model got bigger, but because the system got better at remembering.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>rag</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Designing a Self-Prompting Agent Harness with Per-Task Prompt, Tool, and Strategy Synthesis</title>
      <dc:creator>Pramoda Sahu</dc:creator>
      <pubDate>Fri, 19 Jun 2026 08:25:59 +0000</pubDate>
      <link>https://dev.to/pramod_sahu_d5bd2e6de82d1/designing-a-self-prompting-agent-harness-with-per-task-prompt-tool-and-strategy-synthesis-4b7a</link>
      <guid>https://dev.to/pramod_sahu_d5bd2e6de82d1/designing-a-self-prompting-agent-harness-with-per-task-prompt-tool-and-strategy-synthesis-4b7a</guid>
      <description>&lt;p&gt;Most agent stacks have matured in roughly the same direction: we version the code, test the tools, constrain the runtime, and instrument the loop. But one part of the system still often lives as an unversioned artifact copied between docs, chats, and notebooks: the prompt.&lt;/p&gt;

&lt;p&gt;That mismatch gets harder to ignore once you start treating the &lt;strong&gt;agent harness&lt;/strong&gt; as the real product. If the harness is what determines reliability, cost, safety, and task success, it is strange that the prompt is often the least engineered part of the stack.&lt;/p&gt;

&lt;p&gt;That question led me to build &lt;a href="https://github.com/pksw4u/synthagent" rel="noopener noreferrer"&gt;&lt;strong&gt;SynthAgent&lt;/strong&gt;&lt;/a&gt;: a small framework that generates a task-specific prompt, tool plan, and runtime strategy at task time instead of relying on one fixed prompt and one fixed loop for every task.&lt;/p&gt;

&lt;p&gt;This post is best read as an &lt;strong&gt;architecture exploration&lt;/strong&gt;, not a benchmark report. I have not yet run the A/B test that would justify a strong performance claim over a fixed-prompt baseline. What I do have is a working harness, a clear design thesis, and a set of implementation lessons that were useful enough to write down.&lt;/p&gt;

&lt;p&gt;I’ll walk through the architecture, show what the synthesized artifacts actually look like, explain the tradeoffs behind the design, and point out where the current version is still weak.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Idea: Generate the Harness at Task Time
&lt;/h2&gt;

&lt;p&gt;Here’s the one-line version of the project:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SynthAgent takes &lt;code&gt;(task, success_criteria)&lt;/code&gt; and, instead of running a fixed prompt through a fixed loop, generates a custom Prompt &lt;strong&gt;P&lt;/strong&gt;, Tool Plan &lt;strong&gt;T&lt;/strong&gt;, and Strategy &lt;strong&gt;S&lt;/strong&gt; for that specific task. It then runs them through a Plan-Execute-Verify loop, scores the result, reflects on the failure, and tries again with revised components.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the core thesis: &lt;strong&gt;prompts should not be static artifacts when the rest of the harness is dynamic&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This setup is inspired by recent work on agent harnesses and automated agent design. In particular, it resembles the plan-execute-verify style control loop discussed in &lt;em&gt;Code as Agent Harness&lt;/em&gt; (&lt;a href="https://arxiv.org/abs/2605.18747" rel="noopener noreferrer"&gt;arXiv:2605.18747&lt;/a&gt;) and the meta-level search perspective in ADAS / Meta Agent Search (&lt;a href="https://arxiv.org/abs/2408.08435" rel="noopener noreferrer"&gt;Hu et al., ICLR 2025&lt;/a&gt;). The difference is scope: instead of trying to invent entirely new agents, SynthAgent tries to invent a &lt;strong&gt;per-task harness&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  SynthAgent Architecture
&lt;/h2&gt;

&lt;p&gt;At a high level, the system looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                            ┌──────────────────────────┐
   TASK (free-form)  ───►   │  META-PROMPT GENERATOR   │   ◄── the only
   + success criteria       │  (MPG)                   │       hand-written
                            └────────────┬─────────────┘       instruction
                                         │ synthesizes         (about how
                                         ▼                     to write
                            ┌──────────────────────────┐       prompts,
                            │  TASK PROMPT P           │       not the
                            │  + TOOL PLAN T           │       task itself)
                            │  + STRATEGY S            │
                            └────────────┬─────────────┘
                                         │ runs inside
                                         ▼
        ┌──────────────────────────────────────────────────────────────┐
        │                       THE PEV LOOP                           │
        │                                                              │
        │    ┌─────────┐    plan     ┌─────────┐   tool calls   ┌─────┐ │
        │    │ PLANNER │ ──────────► │EXECUTOR │ ─────────────► │ENV  │ │
        │    └────┬────┘             └────┬────┘                └──┬──┘ │
        │         ▲                       │ observations           │    │
        │         │ replan                ▼                        │    │
        │         │                 ┌─────────┐                    │    │
        │         └──────────────── │MEMORY / │ ◄──────────────────┘    │
        │                           │STATE    │                          │
        │                           └────┬────┘                          │
        │                                │ trajectory                    │
        │                                ▼                               │
        │                         ┌─────────────┐                        │
        │                         │  VERIFIER V │                        │
        │                         └──────┬──────┘                        │
        │                                │ score + critique              │
        │                                ▼                               │
        │                         ┌─────────────┐                        │
        │                         │ REFLECTOR R │ ──► revise P/T/S       │
        │                         └─────────────┘                        │
        └──────────────────────────────────────────────────────────────┘
                                         │
                            loop until V=Pass or budget exhausted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are six components, each in its own file and each intentionally small. The point is not abstraction for its own sake. It is to make the harness legible to a human debugger so that when something fails, you can inspect the trajectory, the prompt, the plan, and the verifier output directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Concrete Example of Synthesized &lt;code&gt;P/T/S&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Before going component by component, it helps to see the core artifact.&lt;/p&gt;

&lt;p&gt;Here is a &lt;strong&gt;trimmed, representative&lt;/strong&gt; example of the JSON object the Meta-Prompt Generator emits for a simple task like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Task: Summarize the repository's Python modules and write the summary to &lt;code&gt;SUMMARY.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Success criteria: Cover each top-level &lt;code&gt;.py&lt;/code&gt; file once, do not invent files, and produce a concise markdown summary.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prompt_p"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You are the planner for a repository-analysis task. First inspect the directory, then read each relevant Python file, extract its purpose conservatively, and only summarize files you actually observed. Before finishing, verify that every top-level .py file has been covered exactly once."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tool_plan_t"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"list_directory"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"read_file"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"write_file"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"strategy_s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"plan_execute"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"notes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Enumerate files before summarizing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Do not infer missing modules"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Validate file coverage before final write"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That example is not meant as benchmark evidence. It is there to make the mechanism concrete: the harness is generating a task-specific instruction, an intended tool sequence, and a strategy hint for the runtime.&lt;/p&gt;

&lt;p&gt;A representative failure-and-retry sequence for that kind of task looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Attempt 1&lt;/strong&gt; lists the directory and reads several files, but misses one module.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Verifier&lt;/strong&gt; fails the run because the output does not satisfy the “cover each top-level &lt;code&gt;.py&lt;/code&gt; file once” criterion.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Reflector&lt;/strong&gt; revises &lt;code&gt;prompt_p&lt;/code&gt; to explicitly require a checklist of discovered files before writing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attempt 2&lt;/strong&gt; re-runs with the stricter instruction, covers the missing file, and passes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That does not prove per-task synthesis is better than a fixed prompt. It does show the shape of the feedback loop and the kind of failure it is designed to repair.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Meta-Prompt Generator Is the Only Hand-Written Prompt
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;mpg.py&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Meta-Prompt Generator (MPG)&lt;/strong&gt; is the only place in the system that contains a human-authored prompt.&lt;/p&gt;

&lt;p&gt;That prompt is not a task instruction. It is a &lt;strong&gt;meta-prompt&lt;/strong&gt;: an instruction for how to write task-specific prompts. Given a task description, success criteria, an available tool catalog, and any prior lessons retrieved from memory, the MPG emits a JSON object with three fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;prompt_p&lt;/code&gt; — a detailed task-specific instruction for the Planner&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tool_plan_t&lt;/code&gt; — which tools are relevant and the intended order of use&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strategy_s&lt;/code&gt; — which loop pattern to prefer, such as ReAct, Plan-and-Execute, or Decompose-and-Solve&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This recursive structure is deliberate: the system is generating instructions about how to solve the task, not solving the task directly in the meta-prompt itself. That is part of what attracted me to the approach described in &lt;em&gt;Meta Prompting for AI Systems&lt;/em&gt;, which also discusses recursive meta-prompting (&lt;a href="https://arxiv.org/abs/2311.11482" rel="noopener noreferrer"&gt;Zhang et al.&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;I chose this route over prompt optimization frameworks like &lt;a href="https://github.com/stanfordnlp/dspy" rel="noopener noreferrer"&gt;DSPy&lt;/a&gt; for one reason: &lt;strong&gt;I wanted the harness behavior to stay inspectable&lt;/strong&gt;. If a run goes wrong, I want to read the synthesized prompt and the execution trace. I do not want the optimization process hidden behind a compiled graph or framework abstraction that makes the final behavior harder to audit.&lt;/p&gt;

&lt;p&gt;That transparency shaped most of the rest of the system too.&lt;/p&gt;

&lt;h2&gt;
  
  
  The PEV Loop Turns Prompt Synthesis Into Runtime Behavior
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;harness.py&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Once the MPG emits &lt;strong&gt;P&lt;/strong&gt;, &lt;strong&gt;T&lt;/strong&gt;, and &lt;strong&gt;S&lt;/strong&gt;, those artifacts are fed into the inner &lt;strong&gt;Plan-Execute-Verify&lt;/strong&gt; loop.&lt;/p&gt;

&lt;p&gt;The loop works like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Planner&lt;/strong&gt; decides the next action given the current prompt, tool plan, strategy, and trajectory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Executor&lt;/strong&gt; calls the selected tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory&lt;/strong&gt; persists the step immediately.&lt;/li&gt;
&lt;li&gt;The cycle repeats until the Planner finishes, the Verifier passes the result, or the attempt budget runs out.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the operational center of the system. A synthesized prompt is only interesting if it changes downstream behavior in a controlled way. The PEV loop is what gives that prompt something to steer.&lt;/p&gt;

&lt;p&gt;Two implementation details mattered more than I expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Structured planner output matters more than planner creativity
&lt;/h3&gt;

&lt;p&gt;Where provider support allows it, the Planner output is requested as a JSON object using a response format like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"json_object"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is less glamorous than tuning reasoning quality, but it matters more. In an agent harness, the Planner is not writing prose for a human reader. It is producing a &lt;strong&gt;decision interface&lt;/strong&gt; that the Executor must parse reliably.&lt;/p&gt;

&lt;p&gt;In practice, this is not uniformly standardized across all OpenAI-compatible providers. Tool calling, &lt;code&gt;response_format&lt;/code&gt;, streaming, and provider-specific parameters still vary. For the subset of plain chat-completion behavior used in this project, though, structured JSON output was stable enough to be worth enforcing where supported and recovering heuristically where it was not.&lt;/p&gt;

&lt;p&gt;The system therefore prioritizes structure over style. Reliability beats expressiveness here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tool argument normalization prevents embarrassingly common failures
&lt;/h3&gt;

&lt;p&gt;The Executor is intentionally thin, but it does one very practical thing: it resolves argument-name aliases like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;filepath&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;file_path&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filename&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Models get these wrong all the time, even when the tool schema is explicit. Rather than pretending this will not happen, the harness normalizes common variants before dispatch. That small tolerance layer ended up being more useful than a more elaborate execution abstraction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hard step limits are non-negotiable
&lt;/h3&gt;

&lt;p&gt;The default is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;max_steps_per_attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is enforced unconditionally in the current design.&lt;/p&gt;

&lt;p&gt;The Planner can return &lt;code&gt;action: "finish"&lt;/code&gt; when it believes the task is done, but if it gets stuck in a loop, the harness terminates the attempt. Self-prompting agents are not immune to looping. If anything, giving them the ability to rewrite their own instructions can make unmanaged loops more likely.&lt;/p&gt;

&lt;p&gt;A hard cap is therefore part of the product, not just a debugging safeguard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Persisting every step to disk changes failure recovery
&lt;/h3&gt;

&lt;p&gt;After every step, the trajectory is written to disk at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agent_memory/runs/&amp;lt;uuid&amp;gt;.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That persistence layer matters for more than observability. If the process crashes mid-task, the run history still exists. That history becomes the source of truth for verification, reflection, debugging, and future lessons.&lt;/p&gt;

&lt;p&gt;In other words, the trace is not just logging. It is part of the system state.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Much Does &lt;code&gt;strategy_s&lt;/code&gt; Change Runtime Behavior Today?
&lt;/h2&gt;

&lt;p&gt;One gap in many “dynamic strategy” writeups is that the strategy field sounds more powerful than it really is.&lt;/p&gt;

&lt;p&gt;That is worth being explicit about here.&lt;/p&gt;

&lt;p&gt;Today, &lt;code&gt;strategy_s&lt;/code&gt; is &lt;strong&gt;partly operative and partly advisory&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is &lt;strong&gt;operative&lt;/strong&gt; in the sense that it changes the planner context and nudges the loop toward a different decomposition style.&lt;/li&gt;
&lt;li&gt;It is &lt;strong&gt;not yet a fully separate runtime policy engine&lt;/strong&gt; with deeply different execution branches for each strategy family.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So if the MPG emits something like “ReAct” versus “Plan-and-Execute,” the current implementation mostly changes how the Planner is instructed to proceed, not an entirely different harness implementation under the hood.&lt;/p&gt;

&lt;p&gt;That still matters, but it is narrower than “the runtime swaps in a wholly different agent architecture.” If I extend the project, strategy branching is one of the first places I would make more explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Verifier Is the Weakest Part of the Current Design
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;verifier.py&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Right now, the Verifier is &lt;strong&gt;LLM-as-judge&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It evaluates the final output against the provided success criteria and emits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a score from &lt;code&gt;0.0&lt;/code&gt; to &lt;code&gt;1.0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a critique&lt;/li&gt;
&lt;li&gt;suggested fixes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The current pass threshold is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.85
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works well enough to support iterative refinement, but it is also the most obvious weakness in the architecture.&lt;/p&gt;

&lt;p&gt;When the agent and the verifier come from the same model family, the system can end up &lt;strong&gt;hill-climbing on the verifier's blind spots&lt;/strong&gt;. A prompt can get “better” according to the judge while the actual task result stays wrong in ways the judge fails to detect.&lt;/p&gt;

&lt;p&gt;One mitigation in SynthAgent is that the Verifier gets the &lt;strong&gt;full trajectory&lt;/strong&gt;, not just the final answer. That gives it a better chance of spotting failure modes like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sloppy execution after a good initial plan&lt;/li&gt;
&lt;li&gt;bad tool usage followed by confident synthesis&lt;/li&gt;
&lt;li&gt;premature commitment to an answer without validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But that is still a mitigation, not a solution.&lt;/p&gt;

&lt;p&gt;The real fix is to support &lt;strong&gt;deterministic, pluggable verifiers&lt;/strong&gt; wherever the task class allows it. For code, that might be &lt;a href="https://docs.pytest.org/" rel="noopener noreferrer"&gt;&lt;code&gt;pytest&lt;/code&gt;&lt;/a&gt;. For SQL, it might be execution against a test database. For structured output, it might be &lt;a href="https://python-jsonschema.readthedocs.io/" rel="noopener noreferrer"&gt;&lt;code&gt;jsonschema.validate&lt;/code&gt;&lt;/a&gt;. In some environments, the environment itself can serve as the oracle.&lt;/p&gt;

&lt;p&gt;That lesson also shows up in recent harness work. The &lt;em&gt;AutoHarness&lt;/em&gt; authors report that a smaller model with a stronger synthesized harness can outperform a larger model in constrained game-like environments because the environment itself supplies a deterministic feedback signal. That is the part I find most important—not a specific leaderboard result, but the fact that the verifier is external, cheap, and hard to game.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Design your verifier first. Everything else is downstream.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the biggest architectural lesson in the repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reflection Works Best at the Prompt Level, Not the Response Level
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;reflector.py&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;When verification fails, the &lt;strong&gt;Reflector (R)&lt;/strong&gt; is invoked.&lt;/p&gt;

&lt;p&gt;It receives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the original task&lt;/li&gt;
&lt;li&gt;the current &lt;code&gt;P/T/S&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the full trajectory&lt;/li&gt;
&lt;li&gt;the verifier score&lt;/li&gt;
&lt;li&gt;the verifier critique&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Its job is to identify the likely failure mode and revise the harness accordingly.&lt;/p&gt;

&lt;p&gt;That raises an important design question: &lt;strong&gt;what exactly should reflection operate on?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are three obvious levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Response-level reflection&lt;/strong&gt; — edit the final answer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt-level reflection&lt;/strong&gt; — revise the task prompt &lt;code&gt;P&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Harness-level reflection&lt;/strong&gt; — re-run synthesis and regenerate &lt;code&gt;P/T/S&lt;/code&gt; from scratch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not equivalent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Response-level reflection is usually too shallow
&lt;/h3&gt;

&lt;p&gt;Editing the answer after the fact is the weakest form of reflection. For example, if the agent wrote a flawed final summary, response-level reflection would just rewrite that summary.&lt;/p&gt;

&lt;p&gt;It can improve phrasing or patch a local omission, but it does not fix the process that produced the failure. This is close to the &lt;a href="https://arxiv.org/abs/2303.17651" rel="noopener noreferrer"&gt;Self-Refine&lt;/a&gt; pattern, and for a system like SynthAgent it is not where the real leverage is.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt-level reflection is the current sweet spot
&lt;/h3&gt;

&lt;p&gt;Right now, SynthAgent revises the prompt-level artifacts. In the repository-summary example above, that might mean changing the Planner instruction from “summarize the repo” to “enumerate files first, maintain a checklist, and do not finish until every discovered &lt;code&gt;.py&lt;/code&gt; file has been covered.”&lt;/p&gt;

&lt;p&gt;That is a useful middle ground because it lets the system improve behavior without paying the full cost of fresh synthesis every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Full re-synthesis is stronger, but more expensive and riskier
&lt;/h3&gt;

&lt;p&gt;The strongest option would be to re-call the MPG and regenerate all of &lt;code&gt;P/T/S&lt;/code&gt; from scratch. In the same example, that might swap not just the prompt wording but the tool sequence and decomposition strategy too.&lt;/p&gt;

&lt;p&gt;That is the next experiment I would try, but not the current default.&lt;/p&gt;

&lt;p&gt;The reason is practical: full re-synthesis is more expensive, and in early testing it often overfit to the most recent failure rather than learning a stable improvement. Prompt-level revisions turned out to be the better default tradeoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory Stores Both Raw Runs and Reusable Lessons
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;memory.py&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The memory layer has two stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Runs&lt;/strong&gt; — &lt;code&gt;runs/&amp;lt;uuid&amp;gt;.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lessons&lt;/strong&gt; — &lt;code&gt;lessons.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The runs store captures the full trajectory, prompt/tool/strategy history, verifier scores, and reflections for each attempt. It is a forensic record of what happened.&lt;/p&gt;

&lt;p&gt;The lessons store is different. It contains &lt;strong&gt;distilled takeaways&lt;/strong&gt; from completed runs and indexes them for retrieval using embeddings. When a new task arrives, the MPG gets the top-5 most similar lessons as additional context.&lt;/p&gt;

&lt;p&gt;By default, the embedding model is NVIDIA's:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nv-embed-v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the implementation is flexible enough to work with other OpenAI-style embedding endpoints too.&lt;/p&gt;

&lt;p&gt;This is one area where I am still not convinced the architecture is earning its complexity. For a small lesson corpus, &lt;strong&gt;cosine similarity over lesson text is doing a lot of work&lt;/strong&gt;. It may be that a simpler baseline like “last 5 lessons” performs just as well. I would not claim embedding-based retrieval here is a decisive win without a proper A/B test.&lt;/p&gt;

&lt;p&gt;That uncertainty matters. Not every component that sounds architecturally elegant turns out to matter in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tool Surface Defines the Agent's Legal Action Space
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;tools.py&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;SynthAgent ships with five tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;tavily_search&lt;/code&gt; — web search via &lt;a href="https://tavily.com/" rel="noopener noreferrer"&gt;Tavily&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;read_file&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;write_file&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;list_directory&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;execute_python&lt;/code&gt; — sandboxed Python via subprocess with a 30-second timeout&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tools are deliberately boring.&lt;/p&gt;

&lt;p&gt;That is by design. The aim was not to build a flashy tool ecosystem. It was to make the action space constrained, legible, and safe enough to reason about.&lt;/p&gt;

&lt;p&gt;A few implementation details matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;file tools enforce a &lt;strong&gt;project-root sandbox&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;execute_python&lt;/code&gt; runs in a subprocess&lt;/li&gt;
&lt;li&gt;the subprocess has a &lt;strong&gt;hard 30s timeout&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;stdout and stderr are returned&lt;/li&gt;
&lt;li&gt;sandboxed Python has &lt;strong&gt;no network access by default&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where the harness perspective becomes concrete: the &lt;strong&gt;tool surface is the legal action space&lt;/strong&gt;. If a tool is not in the registry, the agent cannot call it. That is not just a convenience; it is a safety boundary.&lt;/p&gt;

&lt;p&gt;It also prevents a whole class of failures that appear in less constrained frameworks, where the model “discovers” capabilities the runtime should never have exposed in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model Selection Uses Any OpenAI-Compatible Endpoint
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;llm.py&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The LLM client began with NVIDIA NIM hardcoded, but that quickly felt too limiting. I wanted to test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Groq&lt;/strong&gt; for latency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; for privacy-sensitive local runs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenRouter&lt;/strong&gt; for model flexibility&lt;/li&gt;
&lt;li&gt;standard &lt;strong&gt;OpenAI-compatible&lt;/strong&gt; providers without rewriting client logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So &lt;code&gt;llm.py&lt;/code&gt; became a thin wrapper around any OpenAI-compatible &lt;code&gt;/v1/chat/completions&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;p&gt;Here is the basic configuration shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# OpenAI&lt;/span&gt;
&lt;span class="nv"&gt;LLM_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://api.openai.com/v1
&lt;span class="nv"&gt;LLM_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gpt-4o-mini

&lt;span class="c"&gt;# Groq&lt;/span&gt;
&lt;span class="nv"&gt;LLM_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://api.groq.com/openai/v1
&lt;span class="nv"&gt;LLM_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;llama-3.3-70b-versatile

&lt;span class="c"&gt;# OpenRouter&lt;/span&gt;
&lt;span class="nv"&gt;LLM_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://openrouter.ai/api/v1
&lt;span class="nv"&gt;LLM_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;anthropic/claude-3.5-sonnet

&lt;span class="c"&gt;# Local Ollama&lt;/span&gt;
&lt;span class="nv"&gt;LLM_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:11434/v1
&lt;span class="nv"&gt;LLM_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;llama3.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In practice, there were only two provider quirks that deserved special handling.&lt;/p&gt;

&lt;h3&gt;
  
  
  NVIDIA embedding requests need &lt;code&gt;input_type&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;NVIDIA's embedding endpoint expects an &lt;code&gt;input_type&lt;/code&gt; field. Other providers reject that same field. The client detects the NVIDIA case and only sends it there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chat completions are similar enough for this project's core flow
&lt;/h3&gt;

&lt;p&gt;The more encouraging result is that, for the subset of &lt;strong&gt;non-streaming chat-completion calls&lt;/strong&gt; used in this project, the interface was similar enough across providers that one small client wrapper worked with only minor conditionals.&lt;/p&gt;

&lt;p&gt;That is a narrower claim than “all OpenAI-compatible APIs are standardized.” They are not. Tool calling, structured output modes, reasoning-token controls, and provider-specific parameters still vary. But for the core request/response flow used here, treating model choice as configuration rather than architecture worked well.&lt;/p&gt;

&lt;p&gt;In my testing, the entire &lt;code&gt;llm.py&lt;/code&gt; implementation stayed small and worked across the providers above with only lightweight provider-specific handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the Repository
&lt;/h2&gt;

&lt;p&gt;The repo is intentionally small and direct:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;main.py&lt;/code&gt; — CLI entry point with onboarding wizard&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;config.py&lt;/code&gt; — environment loader with backward-compatibility aliases&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;llm.py&lt;/code&gt; — OpenAI-compatible client with retry and robust JSON extraction&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tools.py&lt;/code&gt; — tool implementations and tool registry schema&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mpg.py&lt;/code&gt; — Meta-Prompt Generator&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;verifier.py&lt;/code&gt; — LLM-as-judge verifier&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reflector.py&lt;/code&gt; — failure diagnosis and &lt;code&gt;P/T/S&lt;/code&gt; revision&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;harness.py&lt;/code&gt; — PEV loop and outer attempt loop&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;memory.py&lt;/code&gt; — runs and lessons persistence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dependencies are minimal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;requests&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Python &lt;code&gt;3.8+&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no &lt;a href="https://www.langchain.com/" rel="noopener noreferrer"&gt;LangChain&lt;/a&gt;, no &lt;a href="https://www.llamaindex.ai/" rel="noopener noreferrer"&gt;LlamaIndex&lt;/a&gt;, no DSPy, and no heavyweight framework hidden underneath. That constraint was part of the point. I wanted every line of behavior to be readable in an afternoon.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned Building SynthAgent
&lt;/h2&gt;

&lt;p&gt;The architecture is the headline, but the implementation taught me a few things that mattered more than the conceptual framing.&lt;/p&gt;

&lt;h3&gt;
  
  
  JSON parsing is harder than model selection
&lt;/h3&gt;

&lt;p&gt;The single biggest source of early failures was malformed JSON from the Planner step.&lt;/p&gt;

&lt;p&gt;The failure patterns were painfully familiar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;markdown code fences around JSON&lt;/li&gt;
&lt;li&gt;prose before the object&lt;/li&gt;
&lt;li&gt;raw newlines inside strings&lt;/li&gt;
&lt;li&gt;trailing commas&lt;/li&gt;
&lt;li&gt;partial object emission&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To make the system resilient, &lt;code&gt;llm.generate_json()&lt;/code&gt; now uses a three-stage recovery pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;direct parse&lt;/li&gt;
&lt;li&gt;strip markdown&lt;/li&gt;
&lt;li&gt;brace-matching with control-character sanitization&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A simplified sketch of the idea looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;recover_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# 1. Try direct parse
&lt;/span&gt;    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;

    &lt;span class="c1"&gt;# 2. Remove markdown fences
&lt;/span&gt;    &lt;span class="n"&gt;cleaned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;strip_markdown_fences&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cleaned&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;

    &lt;span class="c1"&gt;# 3. Extract brace-matched object and sanitize control chars
&lt;/span&gt;    &lt;span class="n"&gt;candidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_first_brace_matched_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cleaned&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;candidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sanitize_control_chars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those recovery paths are not elegant, but they are extremely practical. In a harness like this, robust JSON extraction matters more than almost any model-level tuning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reflection needs the trajectory, not just the score
&lt;/h3&gt;

&lt;p&gt;If you tell the Reflector only that the verifier score was &lt;code&gt;0.4&lt;/code&gt;, you are not giving it enough to improve anything meaningful.&lt;/p&gt;

&lt;p&gt;If you show it the actual trace and make the failure concrete—for example, that it called &lt;code&gt;tavily_search&lt;/code&gt; with the wrong query argument on step 3 and then committed to an unsupported answer on step 5—it has something operational to fix.&lt;/p&gt;

&lt;p&gt;That is why the full trace gets passed through the system. Reflection without trajectory is mostly guesswork.&lt;/p&gt;

&lt;h3&gt;
  
  
  The smallest prompts are often the highest-leverage components
&lt;/h3&gt;

&lt;p&gt;The most valuable hand-written parts of the system are also the smallest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the MPG prompt in &lt;code&gt;mpg.py&lt;/code&gt; is about 20 lines&lt;/li&gt;
&lt;li&gt;the Verifier prompt is about 15 lines&lt;/li&gt;
&lt;li&gt;the Reflector prompt is about 20 lines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those prompts earn their place because they define behavior at the right level of abstraction. They are not trying to solve the task directly. They are specifying how the system should generate, evaluate, and revise task-solving behavior.&lt;/p&gt;

&lt;p&gt;Everything else is composed around those few instructions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Self-prompting without a real verifier is hallucination with extra steps
&lt;/h3&gt;

&lt;p&gt;This was the most sobering lesson.&lt;/p&gt;

&lt;p&gt;I watched the loop improve prompts across multiple attempts, tighten wording, raise the verifier score from &lt;code&gt;0.7&lt;/code&gt; to &lt;code&gt;0.85&lt;/code&gt;, and still produce a confidently wrong answer. The system looked like it was learning, but it was really optimizing against an imperfect judge.&lt;/p&gt;

&lt;p&gt;That does not make self-prompting useless. It just means it cannot rescue a broken oracle.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A better harness cannot compensate for a verifier that does not track reality.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If I had to summarize the whole project in one cautionary line, that would be it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I'd Take the Architecture Next
&lt;/h2&gt;

&lt;p&gt;The current version works, but a few next steps feel much more important than adding more tools or more prompt tricks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pluggable verifiers
&lt;/h3&gt;

&lt;p&gt;This is the most obvious gap.&lt;/p&gt;

&lt;p&gt;Right now, verification is LLM-as-judge only. The next version should expose a verifier interface where task-specific checks can be plugged in, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pytest&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sql_execute&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jsonschema.validate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;custom rubric evaluators&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The current structure already supports this direction. &lt;code&gt;verifier.py&lt;/code&gt; mainly needs to be refactored into a strategy-style interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better lesson-store retrieval
&lt;/h3&gt;

&lt;p&gt;Lessons are currently appended and retrieved by naive cosine similarity. That may be enough for now, but I suspect even a small reranker—or a baseline like &lt;a href="https://en.wikipedia.org/wiki/Okapi_BM25" rel="noopener noreferrer"&gt;BM25&lt;/a&gt;—could outperform raw embedding similarity on a small corpus.&lt;/p&gt;

&lt;p&gt;This is another place where I would want to measure before claiming the design is sound.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost ceilings
&lt;/h3&gt;

&lt;p&gt;Termination currently depends on either pass/fail outcome or attempt limit. That is useful, but not sufficient for unattended runs.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;token-cost ceiling per task&lt;/strong&gt; would make the system much safer to leave running in the background, especially when testing across providers with different latency and pricing profiles.&lt;/p&gt;

&lt;h3&gt;
  
  
  A/B testing whether MPG actually helps
&lt;/h3&gt;

&lt;p&gt;This is the experiment the project still owes itself.&lt;/p&gt;

&lt;p&gt;I have not yet run a clean A/B test comparing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a fixed, hand-written prompt with the same harness&lt;/li&gt;
&lt;li&gt;the synthesized &lt;code&gt;P/T/S&lt;/code&gt; approach&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That comparison is overdue. It would either validate the central thesis or force me to narrow it. Either outcome would be useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Try SynthAgent
&lt;/h2&gt;

&lt;p&gt;If you want to run it locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/pksw4u/synthagent
&lt;span class="nb"&gt;cd &lt;/span&gt;synthagent
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
python main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The onboarding flow prompts for an LLM endpoint. &lt;strong&gt;Most OpenAI-compatible endpoints should work for the core chat flow used here&lt;/strong&gt;, with occasional provider-specific adjustments. The default points to NVIDIA NIM, but you can switch to OpenAI, Groq, OpenRouter, Ollama, or a local &lt;code&gt;llama.cpp&lt;/code&gt;-style server by setting two environment variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;LLM_BASE_URL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LLM_MODEL&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That portability was one of the more satisfying parts of the project. It lets the harness stay stable while the model backend remains easy to swap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SynthAgent treats prompt synthesis as an architectural primitive&lt;/strong&gt;, not a static authoring step.&lt;/li&gt;
&lt;li&gt;The system generates a task-specific &lt;strong&gt;Prompt P&lt;/strong&gt;, &lt;strong&gt;Tool Plan T&lt;/strong&gt;, and &lt;strong&gt;Strategy S&lt;/strong&gt;, then runs them through a &lt;strong&gt;Plan-Execute-Verify&lt;/strong&gt; loop with reflection.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Meta-Prompt Generator&lt;/strong&gt; is the only human-authored prompt in the system, which keeps harness logic transparent and inspectable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured planner output and robust JSON recovery&lt;/strong&gt; mattered more in practice than most model-selection debates.&lt;/li&gt;
&lt;li&gt;The current &lt;strong&gt;LLM-as-judge verifier is the weakest link&lt;/strong&gt;; deterministic, task-specific verifiers are the most important next step.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;tool registry defines the legal action space&lt;/strong&gt;, which is both a capability boundary and a safety boundary.&lt;/li&gt;
&lt;li&gt;The architecture is deliberately lightweight: &lt;strong&gt;Python 3.8+&lt;/strong&gt;, &lt;code&gt;requests&lt;/code&gt;, and a small set of readable modules.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;SynthAgent started from a simple discomfort: if we already accept that the harness matters more than the model in many real-world agent systems, it makes little sense to keep the prompt frozen as a manually maintained artifact while everything else evolves around it.&lt;/p&gt;

&lt;p&gt;Building the project made that intuition sharper, but it also exposed the limits of the idea. Prompt synthesis is useful. Reflection is useful. Per-task harness generation is promising. But none of those can substitute for a verifier that actually tracks correctness. If the oracle is weak, the loop just gets better at fooling itself.&lt;/p&gt;

&lt;p&gt;That is why I think the most important question for agent design is shifting. It is no longer just “what model should I use?” or even “what tools should I expose?” Increasingly, it is: &lt;strong&gt;what parts of the harness should be generated, what parts should be fixed, and how do we verify the result without getting gamed?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If that question interests you, take a look at the repo, try it on a task class you care about, and compare it against your own fixed-prompt baseline. The central claim only matters if it pays for itself in practice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/pksw4u/synthagent" rel="noopener noreferrer"&gt;github.com/pksw4u/synthagent&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Code as Agent Harness: Toward Executable, Verifiable, and Stateful Agent Systems&lt;/em&gt; — Ning et al., 2026 (&lt;a href="https://arxiv.org/abs/2605.18747" rel="noopener noreferrer"&gt;arXiv:2605.18747&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Automated Design of Agentic Systems (ADAS)&lt;/em&gt; — Hu, Lu, Clune, ICLR 2025 (&lt;a href="https://arxiv.org/abs/2408.08435" rel="noopener noreferrer"&gt;arXiv:2408.08435&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Meta Prompting for AI Systems&lt;/em&gt; — Zhang, Yuan, Yao (&lt;a href="https://arxiv.org/abs/2311.11482" rel="noopener noreferrer"&gt;arXiv:2311.11482&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Self-Refine: Iterative Refinement with Self-Feedback&lt;/em&gt; — Madaan et al. (&lt;a href="https://arxiv.org/abs/2303.17651" rel="noopener noreferrer"&gt;arXiv:2303.17651&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>llm</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Understanding the Agent Loop: How Tool-Using LLM Systems Actually Work</title>
      <dc:creator>Pramoda Sahu</dc:creator>
      <pubDate>Thu, 18 Jun 2026 10:21:25 +0000</pubDate>
      <link>https://dev.to/pramod_sahu_d5bd2e6de82d1/understanding-the-agent-loop-how-tool-using-llm-systems-actually-work-2mb5</link>
      <guid>https://dev.to/pramod_sahu_d5bd2e6de82d1/understanding-the-agent-loop-how-tool-using-llm-systems-actually-work-2mb5</guid>
      <description>&lt;p&gt;If you are building with tool-calling models, the most important design decision is often not the prompt. It is the loop around the model.&lt;/p&gt;

&lt;p&gt;An LLM can decide it wants to use a tool, but it cannot execute that tool by itself. The surrounding application or SDK has to assemble context, inspect the model response, run tools, append results, and continue until a final answer is produced. That runtime cycle is the &lt;strong&gt;agent loop&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This article explains what the agent loop actually is, where the model stops and the harness begins, how tool calling works step by step, and which engineering tradeoffs show up once you move beyond demos.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An agent loop is the execution cycle that lets a model inspect context, request tools, observe results, and continue until it reaches a final answer.&lt;/li&gt;
&lt;li&gt;The model is only one part of the system. The harness or SDK owns orchestration: prompt assembly, tool execution, retries, approvals, and termination.&lt;/li&gt;
&lt;li&gt;State management matters as much as prompting. If you lose prior tool outputs or conversation continuity, the agent will behave like it forgot what just happened.&lt;/li&gt;
&lt;li&gt;Performance depends heavily on prompt growth control, stable prompt prefixes, caching, and bounded tool output.&lt;/li&gt;
&lt;li&gt;Safe agent design requires validation, approval gates for side effects, and clear rules for concurrency and history propagation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Agent Loop Is the System, Not Just the Model
&lt;/h2&gt;

&lt;p&gt;The core problem is simple: a one-shot model call cannot inspect the world, act on it, and adapt to the result unless something outside the model manages that cycle.&lt;/p&gt;

&lt;p&gt;That is the harness's job.&lt;/p&gt;

&lt;p&gt;OpenAI's Codex architecture describes a user interaction as a turn, but a single turn may contain multiple internal iterations of model inference and tool execution. The OpenAI Agents SDK describes the same idea directly: invoke the agent, check whether there is final output, handle handoffs if needed, otherwise execute tool calls and re-run.&lt;/p&gt;

&lt;p&gt;A practical mental model looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build the input state.&lt;/li&gt;
&lt;li&gt;Call the model.&lt;/li&gt;
&lt;li&gt;Inspect the response.&lt;/li&gt;
&lt;li&gt;If the model requested tools, validate and execute them.&lt;/li&gt;
&lt;li&gt;Append tool results back into context.&lt;/li&gt;
&lt;li&gt;Call the model again.&lt;/li&gt;
&lt;li&gt;Stop only when the model returns a final answer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That means the harness, not the model alone, is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prompt assembly&lt;/li&gt;
&lt;li&gt;Message history management&lt;/li&gt;
&lt;li&gt;Tool schema registration&lt;/li&gt;
&lt;li&gt;Tool execution&lt;/li&gt;
&lt;li&gt;Validation and error handling&lt;/li&gt;
&lt;li&gt;Retry logic&lt;/li&gt;
&lt;li&gt;Approval workflows&lt;/li&gt;
&lt;li&gt;State persistence&lt;/li&gt;
&lt;li&gt;Loop termination&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why two systems using the same model can behave very differently. Their harnesses may make different decisions about context, tool ordering, truncation, approvals, and continuation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Goes Into a Single Turn
&lt;/h2&gt;

&lt;p&gt;Before the loop can run, the system needs to define what the model sees.&lt;/p&gt;

&lt;h3&gt;
  
  
  The input state
&lt;/h3&gt;

&lt;p&gt;A typical turn includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;System or developer instructions&lt;/li&gt;
&lt;li&gt;Tool definitions or schemas&lt;/li&gt;
&lt;li&gt;Previous messages&lt;/li&gt;
&lt;li&gt;Previous tool-call results&lt;/li&gt;
&lt;li&gt;The current user request&lt;/li&gt;
&lt;li&gt;Sometimes environment state, session metadata, or hidden runtime instructions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters because follow-up reasoning depends on prior observations being present. If the model requested a tool in one iteration and the result is not added back correctly, the next iteration cannot build on that work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inner loop vs outer loop
&lt;/h3&gt;

&lt;p&gt;There are really two loops to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Inner loop&lt;/strong&gt;: model inference and tool execution inside a single user turn&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outer loop&lt;/strong&gt;: the broader multi-turn conversation across user follow-ups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This distinction shows up clearly in Codex-style architectures. A user asks for something once, but the agent may internally perform several tool steps before replying. Then the next user message arrives, and the entire conversation thread continues from that accumulated state.&lt;/p&gt;

&lt;p&gt;That is why state continuity is not optional. Without it, the outer loop breaks and the inner loop starts reasoning from an incomplete view of reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Model Decides Between Text and a Tool Call
&lt;/h2&gt;

&lt;p&gt;Once the harness provides the current turn state, the model has a decision boundary: answer directly, or request one or more tools.&lt;/p&gt;

&lt;p&gt;Tool calling works because the model is given structured tool definitions. Instead of producing only natural language, it can emit a structured request indicating which tool it wants and which arguments it wants to pass.&lt;/p&gt;

&lt;p&gt;At that point, the model is effectively yielding control back to the application.&lt;/p&gt;

&lt;p&gt;With custom tools, the client harness must take over, run the tool, and return the result. With hosted tools, more of that orchestration can happen inside the API itself.&lt;/p&gt;

&lt;p&gt;This is an important architectural choice:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool type&lt;/th&gt;
&lt;th&gt;Who orchestrates execution?&lt;/th&gt;
&lt;th&gt;Main tradeoff&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hosted tool&lt;/td&gt;
&lt;td&gt;API/runtime handles more of the loop&lt;/td&gt;
&lt;td&gt;Simpler orchestration, less direct control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom function tool&lt;/td&gt;
&lt;td&gt;Client harness executes it&lt;/td&gt;
&lt;td&gt;More flexibility, more operational responsibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP tool&lt;/td&gt;
&lt;td&gt;Depends on integration and discovery flow&lt;/td&gt;
&lt;td&gt;Adds discovery and caching concerns&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The advantage of client-side orchestration is control. The cost is that you now own the failure modes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool Execution Mechanics in Practice
&lt;/h2&gt;

&lt;p&gt;Once the model emits a tool request, the harness needs to do more than just run it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validate before execution
&lt;/h3&gt;

&lt;p&gt;A safe harness should validate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tool name&lt;/li&gt;
&lt;li&gt;Argument structure&lt;/li&gt;
&lt;li&gt;Argument types&lt;/li&gt;
&lt;li&gt;Permission rules&lt;/li&gt;
&lt;li&gt;Whether the tool is read-only or mutating&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not just a security concern. It is also a quality concern. If the model asks for a tool with invalid arguments, returning an explicit tool error often gives it enough signal to self-correct on the next loop iteration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Return the observation in the right format
&lt;/h3&gt;

&lt;p&gt;The model needs a structured observation that closes the action-observation cycle.&lt;/p&gt;

&lt;p&gt;A minimal pattern looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;initial_question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;MODEL_DEFAULTS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;function_responses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;invoke_functions_from_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;function_responses&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;More reasoning required, continuing...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;function_responses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;previous_response_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;MODEL_DEFAULTS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key detail is not just the loop itself. It is that the next request continues from the previous response and includes the tool outputs produced by the harness.&lt;/p&gt;

&lt;p&gt;A more explicit observation payload looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;function_call_output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;call_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tool_call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;response_2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;o3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reasoning.encrypted_content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response_2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;function_call_output&lt;/code&gt; item is the observation that lets the model continue reasoning with the tool result now available in context.&lt;/p&gt;

&lt;h2&gt;
  
  
  State Management Patterns: Where Many Agents Fail
&lt;/h2&gt;

&lt;p&gt;One of the easiest ways to break an agent is to lose state continuity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common state strategies
&lt;/h3&gt;

&lt;p&gt;There are several patterns in current OpenAI tooling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full history replay managed by the client&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;previous_response_id&lt;/code&gt; for server-managed continuation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;conversation_id&lt;/code&gt; for conversation continuity&lt;/li&gt;
&lt;li&gt;SDK-managed session persistence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each approach has tradeoffs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Full replay vs server-managed continuation
&lt;/h3&gt;

&lt;p&gt;With full replay, the client sends all prior messages and tool results every time. This is simple to reason about, but payload size grows quickly.&lt;/p&gt;

&lt;p&gt;With server-managed continuation, the client can send the new input along with a continuation identifier such as &lt;code&gt;previous_response_id&lt;/code&gt;. That reduces payload size and offloads some history management.&lt;/p&gt;

&lt;p&gt;This example from the Agents SDK shows response chaining:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Runner&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Reply very concisely.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;previous_response_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;user_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Setting auto_previous_response_id=True enables response chaining
&lt;/span&gt;        &lt;span class="c1"&gt;# automatically for the first turn, even when there is no actual
&lt;/span&gt;        &lt;span class="c1"&gt;# previous response ID yet.
&lt;/span&gt;        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;previous_response_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;previous_response_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;auto_previous_response_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;previous_response_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_response_id&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Assistant: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;final_output&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is convenient, but you still need to choose a consistent state strategy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do not mix incompatible modes
&lt;/h3&gt;

&lt;p&gt;The Agents SDK documentation explicitly warns against combining session persistence with &lt;code&gt;conversation_id&lt;/code&gt;, &lt;code&gt;previous_response_id&lt;/code&gt;, or &lt;code&gt;auto_previous_response_id&lt;/code&gt; in the same run path.&lt;/p&gt;

&lt;p&gt;That is a practical design rule: pick one continuity model per call flow.&lt;/p&gt;

&lt;p&gt;If you mix them, debugging becomes much harder because it is no longer obvious which state the model is actually seeing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompt Growth, Caching, and Why Stable Prefixes Matter
&lt;/h2&gt;

&lt;p&gt;As the loop continues, context grows.&lt;/p&gt;

&lt;p&gt;Every new model call may include prior instructions, tool schemas, user messages, and tool outputs. If you simply keep appending everything forever, the number of bytes sent over the lifetime of a conversation can grow quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Codex emphasizes prompt prefixes
&lt;/h3&gt;

&lt;p&gt;The Codex architecture discussion highlights a useful principle: keep old prompt content as an exact prefix of the new prompt whenever possible. That improves prompt-cache reuse.&lt;/p&gt;

&lt;p&gt;In practical terms, stable ordering matters for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;System instructions&lt;/li&gt;
&lt;li&gt;Tool definitions&lt;/li&gt;
&lt;li&gt;Environment metadata&lt;/li&gt;
&lt;li&gt;Prior messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If these move around between calls, cacheability drops. The same issue affects reproducibility. Even tool-definition ordering bugs can introduce cache misses and inconsistent behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compaction strategies
&lt;/h3&gt;

&lt;p&gt;A production harness usually needs some combination of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Truncating verbose tool output&lt;/li&gt;
&lt;li&gt;Summarizing old history&lt;/li&gt;
&lt;li&gt;Keeping static instructions stable and early&lt;/li&gt;
&lt;li&gt;Bounding shell or retrieval output&lt;/li&gt;
&lt;li&gt;Preserving only the most relevant observations verbatim&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters even more for shell, retrieval, or computer-use tasks, where output can become noisy very quickly.&lt;/p&gt;

&lt;p&gt;The goal is not just lower cost. It is maintaining a usable reasoning substrate for the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safety and Control in the Loop
&lt;/h2&gt;

&lt;p&gt;The more powerful the tools, the more important the harness becomes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Approval gates and side effects
&lt;/h3&gt;

&lt;p&gt;Read-only tool calls are different from side-effectful operations.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetching documentation is relatively low risk&lt;/li&gt;
&lt;li&gt;Sending an email, editing a file, or executing a deployment is high risk&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mutating actions should often be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Serialized instead of run concurrently&lt;/li&gt;
&lt;li&gt;Approval-gated&lt;/li&gt;
&lt;li&gt;Sandboxed when possible&lt;/li&gt;
&lt;li&gt;Logged with enough metadata for auditability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is one reason agent frameworks expose concurrency settings and approval workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validate arguments, not intentions
&lt;/h3&gt;

&lt;p&gt;You cannot safely assume that a tool request is correct just because it came from the model. Validate the arguments before execution, and return structured error feedback when something is wrong.&lt;/p&gt;

&lt;p&gt;That gives the loop a chance to recover without silently doing the wrong thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do not over-prompt reasoning models
&lt;/h3&gt;

&lt;p&gt;OpenAI's function-calling guidance for reasoning models notes that you should not force extra "think more before every function call" prompting. Reasoning models already perform internal reasoning, and excessive prompting can degrade performance.&lt;/p&gt;

&lt;p&gt;That is a useful reminder that harness quality is often more important than prompt verbosity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Agent Extensions and Their Tradeoffs
&lt;/h2&gt;

&lt;p&gt;Once a single-agent loop works, teams often add handoffs or agent-as-tool patterns.&lt;/p&gt;

&lt;p&gt;Conceptually, the loop stays the same:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Invoke one agent.&lt;/li&gt;
&lt;li&gt;Detect whether it produced final output, a tool request, or a handoff.&lt;/li&gt;
&lt;li&gt;Route execution accordingly.&lt;/li&gt;
&lt;li&gt;Continue until termination.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Agents SDK summarizes the semantics clearly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The agent will run in a loop until a final output is generated. The loop runs like so:

1. The agent is invoked with the given input.
2. If there is a final output (i.e. the agent produces something of type `agent.output_type`), the loop terminates.
3. If there's a handoff, we run the loop again, with the new agent.
4. Else, we run tool calls (if any), and re-run the loop.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tricky part is not the idea of handoffs. It is history propagation.&lt;/p&gt;

&lt;p&gt;Recent community discussions show that when one agent is exposed as a tool to another, developers are often unsure how much history is forwarded automatically. In practice, this means you should not assume that all relevant context follows the handoff unless your framework explicitly guarantees it.&lt;/p&gt;

&lt;p&gt;For multi-agent systems, explicit context composition is often safer than implicit inheritance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Failure Modes and Debugging Strategies
&lt;/h2&gt;

&lt;p&gt;Most agent bugs look obvious in hindsight.&lt;/p&gt;

&lt;h3&gt;
  
  
  Failure mode 1: losing continuity
&lt;/h3&gt;

&lt;p&gt;Symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The agent repeats itself&lt;/li&gt;
&lt;li&gt;It forgets prior tool results&lt;/li&gt;
&lt;li&gt;MCP tool discovery keeps happening again&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check whether you are correctly passing &lt;code&gt;previous_response_id&lt;/code&gt;, &lt;code&gt;conversation_id&lt;/code&gt;, or full message history.&lt;/p&gt;

&lt;h3&gt;
  
  
  Failure mode 2: context flooding
&lt;/h3&gt;

&lt;p&gt;Symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Long, low-quality responses&lt;/li&gt;
&lt;li&gt;Poor tool selection&lt;/li&gt;
&lt;li&gt;The model misses relevant facts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check whether tool output is too verbose. Cap output size, summarize logs, and keep only useful observations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Failure mode 3: unstable prompt construction
&lt;/h3&gt;

&lt;p&gt;Symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cache misses&lt;/li&gt;
&lt;li&gt;Inconsistent behavior across similar runs&lt;/li&gt;
&lt;li&gt;Higher token usage than expected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check the ordering of instructions, tool schemas, and environment metadata.&lt;/p&gt;

&lt;h3&gt;
  
  
  Failure mode 4: unsafe tool execution
&lt;/h3&gt;

&lt;p&gt;Symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Invalid API calls&lt;/li&gt;
&lt;li&gt;Accidental side effects&lt;/li&gt;
&lt;li&gt;Hard-to-reproduce failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Validate tool names and arguments before execution. Treat tool requests as proposals, not commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Failure mode 5: incorrect concurrency
&lt;/h3&gt;

&lt;p&gt;Symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Race conditions&lt;/li&gt;
&lt;li&gt;Conflicting writes&lt;/li&gt;
&lt;li&gt;Non-deterministic outcomes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run read-only operations concurrently only when safe. Serialize or approval-gate mutating operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Architecture Takeaways
&lt;/h2&gt;

&lt;p&gt;The recent OpenAI ecosystem changes make one thing clear: the important boundary is no longer just model prompting. It is orchestration design.&lt;/p&gt;

&lt;p&gt;The Responses API, Agents SDK, MCP integrations, and Codex harness examples all point to the same execution model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The model chooses actions&lt;/li&gt;
&lt;li&gt;The harness controls reality&lt;/li&gt;
&lt;li&gt;State continuity determines coherence&lt;/li&gt;
&lt;li&gt;Prompt discipline determines scalability&lt;/li&gt;
&lt;li&gt;Safety controls determine whether the system is usable in practice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are building an agent today, the fastest path to a better system is often not a new prompt. It is a better loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The agent loop is the action-observation cycle that makes tool-using LLM systems possible.&lt;/li&gt;
&lt;li&gt;The harness owns orchestration: context assembly, tool execution, validation, retries, approvals, and termination.&lt;/li&gt;
&lt;li&gt;State continuity is critical. Losing prior responses or tool outputs breaks reasoning quality quickly.&lt;/li&gt;
&lt;li&gt;Server-managed continuation can simplify history handling, but you should choose one state strategy consistently.&lt;/li&gt;
&lt;li&gt;Prompt growth is an engineering problem. Stable prefixes, truncation, compaction, and bounded tool output all matter.&lt;/li&gt;
&lt;li&gt;Hosted tools and custom tools shift the orchestration boundary in different ways.&lt;/li&gt;
&lt;li&gt;Multi-agent patterns introduce history propagation and control-flow complexity that should be designed explicitly.&lt;/li&gt;
&lt;li&gt;Safe execution requires argument validation, side-effect controls, and careful concurrency handling.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;p&gt;If you want to go deeper, these resources are worth reading next:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI function-calling guide&lt;/li&gt;
&lt;li&gt;OpenAI reasoning function-calls cookbook&lt;/li&gt;
&lt;li&gt;OpenAI Agents SDK running agents documentation&lt;/li&gt;
&lt;li&gt;OpenAI's Codex architecture write-up on the agent loop&lt;/li&gt;
&lt;li&gt;OpenAI MCP tool guide&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;An agent loop is not a small implementation detail. It is the core runtime pattern that turns a model into a working system.&lt;/p&gt;

&lt;p&gt;Once you see the loop clearly, many design decisions make more sense: why history management matters, why tool output must be bounded, why prompt ordering affects cacheability, and why side effects need approval and validation.&lt;/p&gt;

&lt;p&gt;If you are building with tool-calling models, make the loop explicit first. Define how state is carried forward, how tools are validated, how observations are appended, and how the run terminates. In practice, that foundation will usually improve reliability more than any prompt tweak.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Understanding Pi Coding Agent: A Minimal, Extensible Architecture for Terminal-First AI Coding Workflow</title>
      <dc:creator>Pramoda Sahu</dc:creator>
      <pubDate>Thu, 18 Jun 2026 09:17:18 +0000</pubDate>
      <link>https://dev.to/pramod_sahu_d5bd2e6de82d1/understanding-pi-coding-agent-a-minimal-extensible-architecture-for-terminal-first-ai-coding-40d4</link>
      <guid>https://dev.to/pramod_sahu_d5bd2e6de82d1/understanding-pi-coding-agent-a-minimal-extensible-architecture-for-terminal-first-ai-coding-40d4</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pi Coding Agent is built as a layered TypeScript toolkit, not a sealed coding assistant product.&lt;/li&gt;
&lt;li&gt;Its architecture separates provider access, agent runtime, coding workflow, and terminal UI into distinct packages.&lt;/li&gt;
&lt;li&gt;Context engineering is a first-class feature through &lt;code&gt;AGENTS.md&lt;/code&gt;, &lt;code&gt;SYSTEM.md&lt;/code&gt;, &lt;code&gt;APPEND_SYSTEM.md&lt;/code&gt;, skills, and extension hooks.&lt;/li&gt;
&lt;li&gt;Pi can run interactively, headlessly over JSONL RPC, or be embedded through its SDK using the same underlying runtime.&lt;/li&gt;
&lt;li&gt;The flexibility comes with tradeoffs: no built-in sandbox, strict RPC framing rules, and extension authors need to understand trust and compaction behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Most coding agents present themselves as finished products: you install them, learn their commands, and work within the boundaries the authors chose. That can be fine if the built-in workflow matches your needs. It becomes limiting when you want to change how prompts are assembled, how tools are registered, how sessions are summarized, or how the agent is embedded inside your own application.&lt;/p&gt;

&lt;p&gt;Pi Coding Agent takes a different path.&lt;/p&gt;

&lt;p&gt;Based on the official Pi homepage, documentation, and repository, Pi from Earendil Works is better understood as a minimal agent harness with a coding-oriented runtime than as a fixed end-user product. It ships with useful defaults, but its architecture assumes users may want to replace or extend large parts of the workflow. The project explicitly positions advanced behavior such as plan-like workflows, extra commands, and other higher-level capabilities as things that can live in extensions or packages instead of being hardcoded into the core.&lt;/p&gt;

&lt;p&gt;That design choice matters for engineers building AI tooling. It affects maintainability, portability, and how easily the system can adapt to terminals, IDE wrappers, automation pipelines, or internal developer platforms.&lt;/p&gt;

&lt;p&gt;In this article, we will look at how Pi is structured, why its layering matters, how its context pipeline works, and what tradeoffs appear once you start using extensions, RPC mode, or SDK embedding.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem Pi is trying to solve
&lt;/h2&gt;

&lt;p&gt;A coding agent has to do several jobs at once:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Talk to one or more model providers.&lt;/li&gt;
&lt;li&gt;Maintain an agent loop with tool calls and state.&lt;/li&gt;
&lt;li&gt;Manage coding-specific concerns such as filesystem access, shell execution, session history, and context limits.&lt;/li&gt;
&lt;li&gt;Provide a user interface or integration surface.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Many tools solve all four inside one tightly coupled application. That can make the initial experience simple, but it often makes customization expensive. If you want to change prompt composition or session summarization, you may end up forking the project or working against internal assumptions.&lt;/p&gt;

&lt;p&gt;Pi’s architecture addresses this by splitting responsibilities into layers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pi stack: four layers instead of one monolith
&lt;/h2&gt;

&lt;p&gt;According to the repository README, Pi is organized as a monorepo with distinct packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@earendil-works/pi-ai&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@earendil-works/pi-agent-core&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@earendil-works/pi-coding-agent&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@earendil-works/pi-tui&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This package split is the clearest way to understand the system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1: &lt;code&gt;pi-ai&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is the provider abstraction layer. Its role is to present a unified interface across multiple model providers.&lt;/p&gt;

&lt;p&gt;Why this layer exists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The agent loop should not depend directly on one provider SDK.&lt;/li&gt;
&lt;li&gt;Provider switching should not require rewriting the coding runtime.&lt;/li&gt;
&lt;li&gt;Frontends and extension systems should remain provider-agnostic where possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a standard but important decision. If provider-specific details leak into higher layers, the whole system becomes harder to test and evolve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2: &lt;code&gt;pi-agent-core&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is the runtime layer for core agent behavior, including tool calling and state management.&lt;/p&gt;

&lt;p&gt;Why this matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tool execution is a runtime concern, not a terminal UI concern.&lt;/li&gt;
&lt;li&gt;State transitions in the loop should be reusable in both CLI and embedded modes.&lt;/li&gt;
&lt;li&gt;A headless integration should get the same agent behavior as the interactive one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Architecturally, this is the part that keeps Pi from being “just a CLI.”&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 3: &lt;code&gt;pi-coding-agent&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is where Pi becomes a coding agent rather than a generic agent harness.&lt;/p&gt;

&lt;p&gt;This layer includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;coding workflow behavior&lt;/li&gt;
&lt;li&gt;sessions and persistence&lt;/li&gt;
&lt;li&gt;built-in file and shell tools&lt;/li&gt;
&lt;li&gt;compaction and summarization&lt;/li&gt;
&lt;li&gt;extensions&lt;/li&gt;
&lt;li&gt;skills&lt;/li&gt;
&lt;li&gt;mode-specific runtime assembly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This package is the operational center of the project. It contains the logic that most users think of as “Pi,” while still remaining separable from the lower-level runtime and the higher-level UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 4: &lt;code&gt;pi-tui&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is the terminal UI layer.&lt;/p&gt;

&lt;p&gt;Its presence as a distinct package is important because it suggests the user interface is not the agent itself. The same runtime can support different frontends.&lt;/p&gt;

&lt;p&gt;That leads directly to one of Pi’s strongest architectural decisions: frontend/runtime separation.&lt;/p&gt;

&lt;h2&gt;
  
  
  One runtime, multiple modes
&lt;/h2&gt;

&lt;p&gt;The official docs describe four major usage modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;interactive&lt;/li&gt;
&lt;li&gt;print/JSON&lt;/li&gt;
&lt;li&gt;RPC&lt;/li&gt;
&lt;li&gt;SDK embedding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means Pi is not tied to its terminal interface, even if the terminal is the primary experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interactive mode
&lt;/h3&gt;

&lt;p&gt;This is the user-facing CLI workflow most people will start with. It combines the runtime with the terminal UI and built-in commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Print and JSON modes
&lt;/h3&gt;

&lt;p&gt;These modes are useful for automation or simple scripting where you want structured output without a long-lived interactive session.&lt;/p&gt;

&lt;h3&gt;
  
  
  RPC mode
&lt;/h3&gt;

&lt;p&gt;RPC mode exposes Pi through a JSONL protocol over stdin/stdout. This is the mode that makes IDE integrations, editor plugins, and service wrappers plausible without reimplementing the core runtime.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pi &lt;span class="nt"&gt;--mode&lt;/span&gt; rpc &lt;span class="o"&gt;[&lt;/span&gt;options]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"req-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello, world!"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a strong design choice because subprocess embedding is often the easiest integration path for tools written in another language or running in another environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  SDK mode
&lt;/h3&gt;

&lt;p&gt;For Node.js and TypeScript applications, Pi can be embedded in-process through its SDK.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CreateAgentSessionRuntimeFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createAgentSessionFromServices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createAgentSessionRuntime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createAgentSessionServices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;getAgentDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;runRpcMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;SessionManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@earendil-works/pi-coding-agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createRuntime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CreateAgentSessionRuntimeFactory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sessionManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sessionStartEvent&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;services&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createAgentSessionServices&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;cwd&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createAgentSessionFromServices&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;sessionManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;sessionStartEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})),&lt;/span&gt;
    &lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;diagnostics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;diagnostics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createAgentSessionRuntime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;createRuntime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;agentDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getAgentDir&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;sessionManager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SessionManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;runRpcMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This snippet shows the decomposition clearly: services, session manager, runtime creation, then a mode runner on top.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core runtime flow: prompt, tools, persistence, compaction
&lt;/h2&gt;

&lt;p&gt;For AI agents, architecture is really about workflow under constraints. Pi’s runtime appears to follow a loop like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Load startup context and trust-sensitive configuration.&lt;/li&gt;
&lt;li&gt;Assemble the system prompt and working context.&lt;/li&gt;
&lt;li&gt;Run extension hooks before the model call.&lt;/li&gt;
&lt;li&gt;Send the provider request.&lt;/li&gt;
&lt;li&gt;Receive model output, including possible tool calls.&lt;/li&gt;
&lt;li&gt;Execute tool calls and attach results.&lt;/li&gt;
&lt;li&gt;Repeat until the assistant completes.&lt;/li&gt;
&lt;li&gt;Persist session entries.&lt;/li&gt;
&lt;li&gt;Compact older context when token pressure increases.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The interesting part is that this pipeline is not fully hardcoded. The extension system lets you intercept multiple stages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Extension hooks make the loop observable and adjustable
&lt;/h3&gt;

&lt;p&gt;The extension docs describe lifecycle events around startup, provider requests, tool calls, compaction, tree navigation, and shutdown. Examples mentioned in the source material include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;session_start&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;before_agent_start&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tool_call&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;before_provider_request&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;after_provider_response&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;session_before_compact&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;session_compact&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;session_before_tree&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;session_tree&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;session_shutdown&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That event model suggests a publish/subscribe architecture around the core loop instead of a single monolithic pipeline. This is one of the biggest reasons Pi feels more like a toolkit than a product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context engineering is built into the architecture
&lt;/h2&gt;

&lt;p&gt;A lot of agent systems treat prompt engineering as text pasted into a config file. Pi treats it as infrastructure.&lt;/p&gt;

&lt;p&gt;According to the docs and homepage, Pi can load:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AGENTS.md&lt;/code&gt; and &lt;code&gt;CLAUDE.md&lt;/code&gt; from user/global and project directories&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SYSTEM.md&lt;/code&gt; to replace the default system prompt&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;APPEND_SYSTEM.md&lt;/code&gt; to append to it&lt;/li&gt;
&lt;li&gt;skills loaded on demand&lt;/li&gt;
&lt;li&gt;prompt templates&lt;/li&gt;
&lt;li&gt;extension-provided prompt modifications&lt;/li&gt;
&lt;li&gt;project trust state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not a minor convenience feature. It changes how the system is operated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why on-demand skills matter
&lt;/h3&gt;

&lt;p&gt;Skills are loaded only when needed instead of always being included in the prompt. That helps avoid bloating context windows and prompt caches.&lt;/p&gt;

&lt;p&gt;This is a practical tradeoff:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always-loaded instructions are simpler.&lt;/li&gt;
&lt;li&gt;On-demand loading is more efficient and gives finer control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pi chooses the second option, which fits its broader design: minimal default core, dynamic behavior at runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt customization through extensions
&lt;/h3&gt;

&lt;p&gt;Pi also allows extensions to modify the assembled system prompt before model execution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;promptCustomizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExtensionAPI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;before_agent_start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;systemPromptOptions&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;addToolGuidance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;systemPromptOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appendSection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mergeWithUserAppend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;systemPromptOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;customPrompt&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;appendSection&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a strong example of Pi’s philosophy. Prompt composition is not just a file-loading step; it is part of the runtime and open to modification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sessions, JSONL persistence, and branching
&lt;/h2&gt;

&lt;p&gt;Pi stores sessions in JSONL and supports commands such as &lt;code&gt;/resume&lt;/code&gt;, &lt;code&gt;/new&lt;/code&gt;, &lt;code&gt;/tree&lt;/code&gt;, &lt;code&gt;/fork&lt;/code&gt;, and &lt;code&gt;/clone&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That combination implies that the session model is not a flat transcript. It supports branching workflows where a user can explore alternate paths.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why JSONL is a sensible choice
&lt;/h3&gt;

&lt;p&gt;JSONL is a practical format for agent session storage because it is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;append-friendly&lt;/li&gt;
&lt;li&gt;easy to inspect&lt;/li&gt;
&lt;li&gt;easy to process line by line&lt;/li&gt;
&lt;li&gt;convenient for event-like histories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For terminal-first tools, that is often a better fit than requiring a heavier database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Branching changes the context story
&lt;/h3&gt;

&lt;p&gt;The source material notes that branch summarization is used when switching branches so that context from the abandoned branch can be injected into the new branch’s working context.&lt;/p&gt;

&lt;p&gt;That matters because branching is not just a UI feature. It affects memory and continuity.&lt;/p&gt;

&lt;p&gt;Pi also distinguishes between full history and in-memory working context. Compaction affects the latter, not the underlying stored session history. That is an important operational detail if you are debugging behavior or writing extensions that depend on prior entries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compaction is not just token trimming
&lt;/h2&gt;

&lt;p&gt;Most agent systems eventually need summarization because context windows are finite. Pi exposes compaction as a visible architectural feature rather than hiding it as internal bookkeeping.&lt;/p&gt;

&lt;p&gt;The docs describe two summarization mechanisms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;auto/manual compaction&lt;/li&gt;
&lt;li&gt;branch summarization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They also define cut-point rules. For example, tool results must remain attached to their tool calls, so valid compaction boundaries are restricted.&lt;/p&gt;

&lt;p&gt;That is exactly the kind of implementation detail extension authors need to know. If your extension assumes history can be split anywhere, you may break tool-call coherence.&lt;/p&gt;

&lt;p&gt;Pi even allows custom compaction logic through hooks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;session_before_compact&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;preparation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;branchEntries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customInstructions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Cancel:&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// Custom summary:&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;compaction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;firstKeptEntryId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;preparation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstKeptEntryId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;tokensBefore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;preparation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokensBefore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes compaction a policy surface, not just an implementation detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tradeoffs of customizable compaction
&lt;/h3&gt;

&lt;p&gt;The flexibility is useful, but it increases the burden on extension authors.&lt;/p&gt;

&lt;p&gt;You need to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;firstKeptEntryId&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tokensBefore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;serialized and truncated tool outputs&lt;/li&gt;
&lt;li&gt;valid cut points&lt;/li&gt;
&lt;li&gt;how repeated compactions relate to earlier kept boundaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you ignore those details, summaries may be technically valid but operationally misleading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extensions are the real center of Pi’s design
&lt;/h2&gt;

&lt;p&gt;Pi’s homepage explicitly says it skips some built-in features and expects users to add them through extensions or packages. That is one of the most unusual and important aspects of the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic tool registration
&lt;/h3&gt;

&lt;p&gt;Tools are not fixed at compile time. An extension can register them during session startup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ExtensionAPI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@earendil-works/pi-coding-agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;typebox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ECHO_PARAMS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Message to echo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;dynamicToolsExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExtensionAPI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;registeredToolNames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;registerEchoTool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;registeredToolNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;registeredToolNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Echo a message with prefix: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;promptSnippet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Echo back user-provided text with &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt; prefix`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;promptGuidelines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Use echo_session when the user asks for exact echo output.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ECHO_PARAMS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_toolCallId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
          &lt;span class="na"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nx"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;session_start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;registerEchoTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;echo_session&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Echo Session&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[session] &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Registered dynamic tool: echo_session&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;info&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a clear signal that Pi’s workflow surface is intended to be extended, not merely configured.&lt;/p&gt;

&lt;h3&gt;
  
  
  What extensions can change
&lt;/h3&gt;

&lt;p&gt;Based on the provided material, extensions can influence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;commands&lt;/li&gt;
&lt;li&gt;tools&lt;/li&gt;
&lt;li&gt;provider request/response handling&lt;/li&gt;
&lt;li&gt;prompt assembly&lt;/li&gt;
&lt;li&gt;compaction behavior&lt;/li&gt;
&lt;li&gt;tree navigation behavior&lt;/li&gt;
&lt;li&gt;UI interactions&lt;/li&gt;
&lt;li&gt;workflow logic around session lifecycle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is unusually broad. It also explains why Pi can remain small at the core while still supporting highly specialized workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Headless integrations: RPC mode and its sharp edges
&lt;/h2&gt;

&lt;p&gt;RPC mode is one of Pi’s most practical features for teams building wrappers or custom frontends. But the protocol details matter.&lt;/p&gt;

&lt;p&gt;The docs specify strict JSONL semantics with LF as the record delimiter.&lt;/p&gt;

&lt;p&gt;The source material calls out a concrete gotcha: Node’s &lt;code&gt;readline&lt;/code&gt; is not protocol-compliant for this use case because it can split on Unicode line separators such as &lt;code&gt;U+2028&lt;/code&gt; and &lt;code&gt;U+2029&lt;/code&gt;, which are valid inside JSON strings.&lt;/p&gt;

&lt;p&gt;That means a robust client should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;split records on &lt;code&gt;\n&lt;/code&gt; only&lt;/li&gt;
&lt;li&gt;accept optional &lt;code&gt;\r\n&lt;/code&gt; by stripping the trailing &lt;code&gt;\r&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;avoid generic line readers that reinterpret other Unicode characters as line boundaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a good example of a small but important systems detail. If you are embedding Pi inside an editor extension or orchestrator, protocol correctness matters more than convenience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security and operational concerns
&lt;/h2&gt;

&lt;p&gt;Pi’s flexibility does not remove operational risk.&lt;/p&gt;

&lt;h3&gt;
  
  
  No built-in sandbox
&lt;/h3&gt;

&lt;p&gt;The repository README states that Pi does not provide a built-in permission system for filesystem, process, network, or credential access. It runs with the launching user’s permissions.&lt;/p&gt;

&lt;p&gt;That has an obvious implication: if you need stronger isolation, you should containerize or otherwise sandbox it externally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trust model affects what loads
&lt;/h3&gt;

&lt;p&gt;Before trust is granted, Pi loads only a subset of context and extension sources. According to the docs, project-local extensions, package-managed project extensions, and project settings are loaded only after trust resolution.&lt;/p&gt;

&lt;p&gt;In non-interactive modes, trust prompts are not shown, so automation behavior depends on defaults or explicit CLI overrides.&lt;/p&gt;

&lt;p&gt;If you are building tooling around Pi, document this clearly. Otherwise, a project may behave differently in interactive use versus CI-like or subprocess-driven environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Extension lifecycle resets on fork and clone
&lt;/h3&gt;

&lt;p&gt;After &lt;code&gt;/fork&lt;/code&gt; or &lt;code&gt;/clone&lt;/code&gt;, Pi emits &lt;code&gt;session_shutdown&lt;/code&gt; for the old extension instance, reloads and rebinds extensions, and then emits &lt;code&gt;session_start&lt;/code&gt; for the new session.&lt;/p&gt;

&lt;p&gt;That means in-memory extension state is not automatically preserved. If state matters, persist it into session entries or rebuild it during startup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this architecture matters in practice
&lt;/h2&gt;

&lt;p&gt;Pi’s design is especially useful when you need one of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a terminal-first agent that is still scriptable&lt;/li&gt;
&lt;li&gt;a reusable runtime for editor or service integration&lt;/li&gt;
&lt;li&gt;custom prompt assembly without forking the core project&lt;/li&gt;
&lt;li&gt;organization-specific commands, tools, or policies through extensions&lt;/li&gt;
&lt;li&gt;session storage that is inspectable and easy to process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, Pi is less about delivering one ideal workflow and more about providing a stable substrate for many workflows.&lt;/p&gt;

&lt;p&gt;That is the real architectural difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pi is best understood as a layered toolkit for coding agents, not a fixed assistant product.&lt;/li&gt;
&lt;li&gt;The package split separates providers, agent runtime, coding workflow, and terminal UI in a clean way.&lt;/li&gt;
&lt;li&gt;Context engineering is deeply integrated through files, skills, prompt templates, and hooks.&lt;/li&gt;
&lt;li&gt;Sessions are durable and branch-aware through JSONL persistence and summarization mechanisms.&lt;/li&gt;
&lt;li&gt;Extensions are central to the design and can reshape tools, prompts, compaction, and workflow behavior.&lt;/li&gt;
&lt;li&gt;RPC and SDK modes make the same runtime usable in terminals, subprocess integrations, and custom applications.&lt;/li&gt;
&lt;li&gt;Operational safety is your responsibility: sandboxing, trust configuration, and extension-state handling all need deliberate design.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Pi Coding Agent stands out because it treats extensibility as the default architecture rather than an afterthought. The minimal core is not a limitation by accident; it is the mechanism that keeps the system adaptable.&lt;/p&gt;

&lt;p&gt;That makes Pi especially interesting for engineers who want more than a terminal chatbot. If you need a coding agent that can be embedded, wrapped, or reshaped without forking the entire application, Pi’s layered design is worth studying.&lt;/p&gt;

&lt;p&gt;The practical next step is to evaluate it in the mode closest to your real use case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you want a terminal workflow, start with interactive mode.&lt;/li&gt;
&lt;li&gt;If you want editor or service integration, inspect RPC framing carefully.&lt;/li&gt;
&lt;li&gt;If you want deep control over behavior, study the extension lifecycle and compaction hooks before writing custom logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Pi, the architecture is the product.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
