<?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>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>
