<?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: Gaurav Jha</title>
    <description>The latest articles on DEV Community by Gaurav Jha (@gaurav_jha_fbabb66ef5df4c).</description>
    <link>https://dev.to/gaurav_jha_fbabb66ef5df4c</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%2F4032503%2F45bbbbe5-4e26-41bd-a9ba-6a1ff1972011.jpg</url>
      <title>DEV Community: Gaurav Jha</title>
      <link>https://dev.to/gaurav_jha_fbabb66ef5df4c</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gaurav_jha_fbabb66ef5df4c"/>
    <language>en</language>
    <item>
      <title>I Built an Agentic AI System From Scratch: RAG, GraphRAG, 12 Tools &amp; Kubernetes</title>
      <dc:creator>Gaurav Jha</dc:creator>
      <pubDate>Mon, 17 Aug 2026 17:33:14 +0000</pubDate>
      <link>https://dev.to/gaurav_jha_fbabb66ef5df4c/i-built-an-agentic-ai-system-from-scratch-rag-graphrag-12-tools-kubernetes-20bp</link>
      <guid>https://dev.to/gaurav_jha_fbabb66ef5df4c/i-built-an-agentic-ai-system-from-scratch-rag-graphrag-12-tools-kubernetes-20bp</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Over five weeks, I built a full-stack Agentic AI system that can plan multi-step tasks, retrieve information using hybrid RAG and GraphRAG, call 12 tools, review its own intermediate results, learn from user feedback, and deploy to Kubernetes.&lt;/p&gt;

&lt;p&gt;The project ended up at roughly &lt;strong&gt;6,000 lines of code, 360 tests, and green CI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;More importantly, I built an evaluation harness so I could measure whether the system actually improved instead of just adding more AI components.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;code&gt;https://github.com/zda25m005-netizen/agentic-ai-os&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why I Built This
&lt;/h2&gt;

&lt;p&gt;A lot of agent demos follow this pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User → LLM → Tool → Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks impressive in a demo.&lt;/p&gt;

&lt;p&gt;But real-world questions are usually more complicated.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;"Summarize the Q3 business risks from these 40 PDFs, find the products that shipped to Berlin, and explain how those products are connected to the risks."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A single LLM call isn't enough.&lt;/p&gt;

&lt;p&gt;The system needs to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find the right information.&lt;/li&gt;
&lt;li&gt;Understand relationships between entities.&lt;/li&gt;
&lt;li&gt;Break the problem into multiple steps.&lt;/li&gt;
&lt;li&gt;Call different tools.&lt;/li&gt;
&lt;li&gt;Check whether each step actually worked.&lt;/li&gt;
&lt;li&gt;Retry when something goes wrong.&lt;/li&gt;
&lt;li&gt;Produce an answer with citations.&lt;/li&gt;
&lt;li&gt;Tell me how much the whole operation cost.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So I decided to build the system I would actually want to debug.&lt;/p&gt;

&lt;p&gt;Not just an agent demo.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;production-shaped Agentic AI OS&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built
&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;                    User Goal
                       │
                       ▼
                  ┌──────────┐
                  │  Planner │
                  └────┬─────┘
                       │
                       ▼
                ┌─────────────┐
                │   Executor  │
                └──────┬──────┘
                       │
            ┌──────────┼──────────┐
            ▼          ▼          ▼
          Tools       RAG      GraphRAG
            │          │          │
            └──────────┼──────────┘
                       │
                       ▼
                  ┌─────────┐
                  │  Critic │
                  └────┬────┘
                       │
                ┌──────┴──────┐
                │             │
             APPROVE        RETRY
                │             │
                ▼             └──→ Executor
             Finalize
                │
                ▼
       Answer + Trace + Metrics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend is built with &lt;strong&gt;FastAPI + LangGraph&lt;/strong&gt;, with a &lt;strong&gt;Next.js&lt;/strong&gt; frontend.&lt;/p&gt;

&lt;p&gt;For retrieval, I use both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hybrid RAG&lt;/li&gt;
&lt;li&gt;GraphRAG with Neo4j&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The system currently exposes &lt;strong&gt;12 tools&lt;/strong&gt;, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python execution&lt;/li&gt;
&lt;li&gt;SQL&lt;/li&gt;
&lt;li&gt;Web search&lt;/li&gt;
&lt;li&gt;RAG search&lt;/li&gt;
&lt;li&gt;Graph search&lt;/li&gt;
&lt;li&gt;HTTP requests&lt;/li&gt;
&lt;li&gt;File operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each tool is isolated and guarded according to its use case.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Part Most Agent Demos Skip: Evaluation
&lt;/h2&gt;

&lt;p&gt;This is probably the part I'm most proud of.&lt;/p&gt;

&lt;p&gt;It is very easy to build an AI system that &lt;em&gt;looks&lt;/em&gt; better.&lt;/p&gt;

&lt;p&gt;It is much harder to prove that it actually &lt;strong&gt;is&lt;/strong&gt; better.&lt;/p&gt;

&lt;p&gt;So I built the evaluation system alongside the product.&lt;/p&gt;

&lt;p&gt;The harness measures things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Recall@k&lt;/strong&gt; — did the correct source appear in the top-k results?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LLM-judge correctness&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Citation accuracy&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent task-success rate&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GraphRAG fact coverage&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Retrieval performance across different strategies&lt;/li&gt;
&lt;li&gt;Reranker improvements&lt;/li&gt;
&lt;li&gt;Fine-tuning before/after comparisons&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also included a retrieval ablation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vector Search
      ↓
BM25
      ↓
Hybrid RAG
      ↓
Hybrid + Reranker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters because I don't want to simply say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Hybrid RAG is better."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I want to be able to show:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Here are the test cases, here is the baseline, here is the hybrid result, and here is what changed."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And when something doesn't improve the baseline, the evaluation reports that too.&lt;/p&gt;

&lt;p&gt;I think &lt;strong&gt;honesty in evaluation is a feature&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Hybrid RAG: Why Use Two Retrieval Strategies?
&lt;/h2&gt;

&lt;p&gt;Dense vector search is excellent at understanding semantic meaning.&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 plaintext"&gt;&lt;code&gt;"How do I reset my password?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can retrieve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Steps for recovering access to your account"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;even though the wording is different.&lt;/p&gt;

&lt;p&gt;But semantic search can struggle with exact identifiers.&lt;/p&gt;

&lt;p&gt;Imagine searching for:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;That's where BM25 can be much better.&lt;/p&gt;

&lt;p&gt;So I combined both approaches.&lt;/p&gt;

&lt;p&gt;The system performs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        Query
          │
     ┌────┴────┐
     ▼         ▼
 Vector       BM25
 Search       Search
     │         │
     └────┬────┘
          ▼
 Reciprocal Rank
     Fusion
          │
          ▼
      Reranker
          │
          ▼
      Top Results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For fusion I use &lt;strong&gt;Reciprocal Rank Fusion (RRF)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of combining the raw scores from vector search and BM25, RRF combines their &lt;strong&gt;rankings&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The basic idea is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RRF score(d) = Σ 1 / (k + rank(d))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;rank(d)&lt;/code&gt; is the position of document &lt;code&gt;d&lt;/code&gt; in a result list.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;k&lt;/code&gt; controls how quickly the contribution decreases.&lt;/li&gt;
&lt;li&gt;I use &lt;code&gt;k = 60&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, if a document appears at rank 1 in one result list and rank 4 in another:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RRF score = 1 / (60 + 1) + 1 / (60 + 4)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that &lt;strong&gt;RRF uses rank rather than raw retrieval scores&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This makes it useful when combining vector search and BM25 because their raw scores don't necessarily live on the same scale.&lt;/p&gt;

&lt;p&gt;The actual implementation is tiny:&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;reciprocal_rank_fusion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result_lists&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payloads&lt;/span&gt; &lt;span class="o"&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;for&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result_lists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hit&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;hit&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="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hit&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="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;payloads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hit&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="n"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;ranked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;reverse&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;SearchHit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payloads&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ranked&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;limit&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;I intentionally kept the implementation small instead of hiding the ranking logic behind another abstraction.&lt;/p&gt;

&lt;p&gt;That makes the behavior easy to understand, test, and debug.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. GraphRAG: When Documents Aren't Enough
&lt;/h2&gt;

&lt;p&gt;Traditional RAG is great at retrieving relevant passages.&lt;/p&gt;

&lt;p&gt;But some questions are fundamentally about &lt;strong&gt;relationships&lt;/strong&gt;.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;"How is Product A connected to Customer B?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Which products are associated with the risks mentioned in this document?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finding individual passages isn't necessarily enough.&lt;/p&gt;

&lt;p&gt;So I added a knowledge graph.&lt;/p&gt;

&lt;p&gt;The ingestion pipeline looks roughly 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;Documents
    │
    ▼
Chunks
    │
    ▼
LLM Entity + Relationship Extraction
    │
    ▼
Neo4j Knowledge Graph
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Entities and relationships are extracted from the documents and stored in &lt;strong&gt;Neo4j&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The graph writes use &lt;code&gt;MERGE&lt;/code&gt;, making ingestion idempotent.&lt;/p&gt;

&lt;p&gt;At query time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Query
    │
    ▼
Entity Detection
    │
    ▼
k-hop Neighborhood
    │
    ▼
Relevant Graph Facts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The graph results are then combined with the normal RAG passages.&lt;/p&gt;

&lt;p&gt;The API supports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/ask?mode=vector
/ask?mode=graph
/ask?mode=fused
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also expose GraphRAG through a dedicated:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;tool.&lt;/p&gt;

&lt;p&gt;This gives the agent another way to answer questions where &lt;strong&gt;relationships matter more than isolated text passages&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. What Actually Makes It an Agent?
&lt;/h2&gt;

&lt;p&gt;This was an important design question for me.&lt;/p&gt;

&lt;p&gt;Calling a tool doesn't automatically make something an agent.&lt;/p&gt;

&lt;p&gt;The interesting part is the &lt;strong&gt;control loop&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;After the executor completes a step, a Critic evaluates the result.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step
 │
 ▼
Execute
 │
 ▼
Critic
 │
 ├── APPROVE ──→ Next Step
 │
 └── RETRY ────→ Execute Again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Critic can return:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RETRY: The retrieved evidence does not support the claim.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The retry mechanism is intentionally bounded.&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;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;review&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step&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="n"&gt;chat_fn&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&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;chat_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;_critic_messages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step&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="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approve&lt;/span&gt;&lt;span class="sh"&gt;"&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;APPROVE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;

    &lt;span class="n"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;raw&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;RETRY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And at the graph level:&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;if&lt;/span&gt; &lt;span class="n"&gt;verdict&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;RETRY&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;MAX_RETRIES&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cursor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retries&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last condition is important.&lt;/p&gt;

&lt;h3&gt;
  
  
  The loop must terminate.
&lt;/h3&gt;

&lt;p&gt;Agent systems can become expensive very quickly when they are allowed to keep reasoning forever.&lt;/p&gt;

&lt;p&gt;So I explicitly enforce retry limits.&lt;/p&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No unbounded loops. No runaway bills.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The 12-Tool Execution Loop
&lt;/h2&gt;

&lt;p&gt;The executor runs a function-calling loop over 12 tools.&lt;/p&gt;

&lt;p&gt;Some examples include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Python
SQL
Web Search
RAG
Graph Search
HTTP
Files
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also added safeguards around the tools.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Python execution is sandboxed.&lt;/li&gt;
&lt;li&gt;SQL is read-only.&lt;/li&gt;
&lt;li&gt;HTTP requests have SSRF protections.&lt;/li&gt;
&lt;li&gt;File access is path-guarded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another design decision I made:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A tool failure should not automatically kill the entire agent run.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tool Error → Crash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the system returns the error to the model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tool Error
    │
    ▼
Agent observes error
    │
    ▼
Agent decides what to do next
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives the agent an opportunity to recover when possible.&lt;/p&gt;

&lt;p&gt;It also makes debugging much easier because the error becomes part of the execution trace.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Learning From User Feedback
&lt;/h2&gt;

&lt;p&gt;The system also has a feedback loop.&lt;/p&gt;

&lt;p&gt;Users can give:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;and optionally provide a better answer.&lt;/p&gt;

&lt;p&gt;That feedback is used in two ways.&lt;/p&gt;

&lt;h3&gt;
  
  
  Learned Reranking
&lt;/h3&gt;

&lt;p&gt;The feedback can train a lightweight reranker.&lt;/p&gt;

&lt;p&gt;When there isn't enough feedback yet, the system falls back to the LLM reranker.&lt;/p&gt;

&lt;p&gt;That gives me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cold Start
    ↓
LLM Reranker
    ↓
User Feedback
    ↓
Learned Reranker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than deploying a learned model before there is enough data.&lt;/p&gt;

&lt;h3&gt;
  
  
  DPO Preference Pairs
&lt;/h3&gt;

&lt;p&gt;I also export preference data as JSONL.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chosen Answer
      vs
Rejected Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I intentionally describe this as &lt;strong&gt;DPO&lt;/strong&gt;, not RLHF.&lt;/p&gt;

&lt;p&gt;There is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No reward model&lt;/li&gt;
&lt;li&gt;No online reinforcement learning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is to keep the terminology technically accurate.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Observability: Because "It Feels Slow" Isn't a Metric
&lt;/h2&gt;

&lt;p&gt;One of my biggest goals was to make the system observable.&lt;/p&gt;

&lt;p&gt;The API exposes Prometheus metrics for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request count&lt;/li&gt;
&lt;li&gt;Latency&lt;/li&gt;
&lt;li&gt;Token usage&lt;/li&gt;
&lt;li&gt;Estimated USD cost&lt;/li&gt;
&lt;li&gt;Agent-node execution&lt;/li&gt;
&lt;li&gt;Tool execution&lt;/li&gt;
&lt;li&gt;Errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The metrics are visualized in Grafana.&lt;/p&gt;

&lt;p&gt;So instead of asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Why is this agent expensive?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I can look at the cost of individual parts of a run.&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 plaintext"&gt;&lt;code&gt;Request
  │
  ├── Planner       $0.002
  ├── RAG           $0.000
  ├── Tool #4       $0.001
  ├── Critic        $0.003
  └── Finalizer     $0.002
                  --------
                    $0.008
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Optional Langfuse traces&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/readyz&lt;/code&gt; dependency checks&lt;/li&gt;
&lt;li&gt;Structured JSON logs&lt;/li&gt;
&lt;li&gt;Request IDs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is that an agent run isn't just an answer.&lt;/p&gt;

&lt;p&gt;It's an &lt;strong&gt;answer + execution trace + metrics&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Kubernetes Deployment
&lt;/h2&gt;

&lt;p&gt;The project isn't just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also created a Helm deployment.&lt;/p&gt;

&lt;p&gt;The chart includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ingress&lt;/li&gt;
&lt;li&gt;API autoscaling&lt;/li&gt;
&lt;li&gt;ConfigMaps&lt;/li&gt;
&lt;li&gt;Secrets&lt;/li&gt;
&lt;li&gt;Non-root containers&lt;/li&gt;
&lt;li&gt;NetworkPolicies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The CI pipeline creates a &lt;strong&gt;kind&lt;/strong&gt; Kubernetes cluster and verifies the deployment.&lt;/p&gt;

&lt;p&gt;It checks the health endpoint after deployment.&lt;/p&gt;

&lt;p&gt;So the deployment isn't merely:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Here's a Helm chart. It should work."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The CI actually exercises it.&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;p&gt;A deployment configuration that looks correct is not the same thing as a deployment that has actually been tested.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. The Evaluation Harness
&lt;/h2&gt;

&lt;p&gt;The evaluation system is what ties the whole project together.&lt;/p&gt;

&lt;p&gt;I didn't want to evaluate the system only by asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Does the answer look good?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead, I separated evaluation into multiple dimensions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retrieval
&lt;/h3&gt;

&lt;p&gt;I measure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recall@k&lt;/li&gt;
&lt;li&gt;Vector-only retrieval&lt;/li&gt;
&lt;li&gt;BM25 retrieval&lt;/li&gt;
&lt;li&gt;Hybrid retrieval&lt;/li&gt;
&lt;li&gt;Hybrid + reranker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes it possible to see which retrieval strategy actually helps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Answer Quality
&lt;/h3&gt;

&lt;p&gt;I measure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LLM-judge correctness&lt;/li&gt;
&lt;li&gt;Citation accuracy&lt;/li&gt;
&lt;li&gt;Fact coverage&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Agent Performance
&lt;/h3&gt;

&lt;p&gt;For multi-step tasks, I measure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Task-success rate&lt;/li&gt;
&lt;li&gt;Successful tool execution&lt;/li&gt;
&lt;li&gt;Retry behavior&lt;/li&gt;
&lt;li&gt;Final answer quality&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GraphRAG
&lt;/h3&gt;

&lt;p&gt;For graph-based questions, I measure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Relevant entity retrieval&lt;/li&gt;
&lt;li&gt;Relationship coverage&lt;/li&gt;
&lt;li&gt;Fact coverage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most importantly, the evaluation reports the &lt;strong&gt;sample size&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I don't want to publish:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Our approach improved accuracy by 17%."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;without also saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"across N evaluation examples."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Numbers without context can be misleading.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Five Weeks Taught Me
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Measure From Day One
&lt;/h3&gt;

&lt;p&gt;The evaluation harness ended up being one of the most valuable parts of the project.&lt;/p&gt;

&lt;p&gt;Without it, I would have been guessing whether changes actually improved the system.&lt;/p&gt;

&lt;p&gt;Every new component creates another question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Did this actually help?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An evaluation gives you an answer.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. More AI Isn't Automatically Better
&lt;/h3&gt;

&lt;p&gt;Adding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RAG
+
GraphRAG
+
Reranker
+
Critic
+
12 Tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;sounds impressive.&lt;/p&gt;

&lt;p&gt;But every additional component introduces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Latency&lt;/li&gt;
&lt;li&gt;Cost&lt;/li&gt;
&lt;li&gt;Failure modes&lt;/li&gt;
&lt;li&gt;Complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The evaluation needs to justify the complexity.&lt;/p&gt;

&lt;p&gt;Sometimes the simplest solution wins.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Bound Your Agent Loops
&lt;/h3&gt;

&lt;p&gt;This is one of the easiest things to overlook.&lt;/p&gt;

&lt;p&gt;An agent that can endlessly retry is not robust.&lt;/p&gt;

&lt;p&gt;It's an expensive bug.&lt;/p&gt;

&lt;p&gt;Set explicit limits.&lt;/p&gt;

&lt;p&gt;Track the cost.&lt;/p&gt;

&lt;p&gt;Make the failure mode predictable.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Don't Hide Bad Results
&lt;/h3&gt;

&lt;p&gt;One of the most useful principles I learned from this project:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A benchmark that tells you your idea didn't work is still a successful benchmark.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If a reranker doesn't beat the baseline, report it.&lt;/p&gt;

&lt;p&gt;If GraphRAG doesn't help a particular query type, report it.&lt;/p&gt;

&lt;p&gt;If a model performs worse after fine-tuning, report it.&lt;/p&gt;

&lt;p&gt;That information is much more valuable than a perfect-looking README.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Keep Infrastructure Boring
&lt;/h3&gt;

&lt;p&gt;I also learned a less exciting but very practical lesson.&lt;/p&gt;

&lt;p&gt;Small infrastructure mistakes can waste hours.&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 plaintext"&gt;&lt;code&gt;docker compose up -d --build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;became part of my development routine.&lt;/p&gt;

&lt;p&gt;At one point, I spent an afternoon debugging behavior that turned out to be a &lt;strong&gt;stale Docker image running old code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The lesson:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When the behavior makes no sense, check what code you're actually running.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;After five weeks, the system looks roughly like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Implementation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;FastAPI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent orchestration&lt;/td&gt;
&lt;td&gt;LangGraph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Next.js&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vector retrieval&lt;/td&gt;
&lt;td&gt;Hybrid RAG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keyword retrieval&lt;/td&gt;
&lt;td&gt;BM25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fusion&lt;/td&gt;
&lt;td&gt;Reciprocal Rank Fusion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Knowledge graph&lt;/td&gt;
&lt;td&gt;Neo4j&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tools&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evaluation&lt;/td&gt;
&lt;td&gt;Automated eval harness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metrics&lt;/td&gt;
&lt;td&gt;Prometheus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dashboards&lt;/td&gt;
&lt;td&gt;Grafana&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tracing&lt;/td&gt;
&lt;td&gt;Langfuse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;Docker + Helm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes testing&lt;/td&gt;
&lt;td&gt;kind&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests&lt;/td&gt;
&lt;td&gt;~360&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code&lt;/td&gt;
&lt;td&gt;~6,000 LOC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;But the important part isn't the number of technologies.&lt;/p&gt;

&lt;p&gt;It's that each component exists for a reason and has a test or evaluation behind it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture at a Glance
&lt;/h2&gt;

&lt;p&gt;Putting everything together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         ┌──────────────┐
                         │     User     │
                         └──────┬───────┘
                                │
                                ▼
                         ┌──────────────┐
                         │    Planner   │
                         └──────┬───────┘
                                │
                                ▼
                    ┌──────────────────────┐
                    │       Executor       │
                    │   Function Calling   │
                    └──────────┬───────────┘
                               │
          ┌────────────────────┼────────────────────┐
          │                    │                    │
          ▼                    ▼                    ▼
     ┌─────────┐         ┌──────────┐        ┌───────────┐
     │  Tools  │         │ Hybrid   │        │ GraphRAG  │
     │   ×12   │         │   RAG    │        │  Neo4j    │
     └─────────┘         └────┬─────┘        └─────┬─────┘
                               │                    │
                               └─────────┬──────────┘
                                         │
                                         ▼
                                  ┌──────────────┐
                                  │    Critic    │
                                  └──────┬───────┘
                                         │
                                  ┌──────┴──────┐
                                  │             │
                               APPROVE        RETRY
                                  │             │
                                  ▼             │
                              Finalize ◄────────┘
                                  │
                                  ▼
                      ┌─────────────────────────┐
                      │ Answer + Citations      │
                      │ Execution Trace         │
                      │ Tokens + Cost           │
                      │ Evaluation Metrics      │
                      └─────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Around the system sits the infrastructure layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 ┌─────────────────────────┐
                 │      Observability       │
                 │ Prometheus + Grafana     │
                 │ Optional Langfuse        │
                 └─────────────────────────┘

                 ┌─────────────────────────┐
                 │       Deployment        │
                 │ Docker + Helm + K8s     │
                 │ kind CI verification    │
                 └─────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What I'd Build Next
&lt;/h2&gt;

&lt;p&gt;There are still several things I'd like to improve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better Evaluation Datasets
&lt;/h3&gt;

&lt;p&gt;The evaluation harness is only as good as the questions being evaluated.&lt;/p&gt;

&lt;p&gt;A larger, more diverse benchmark would make the results more meaningful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better Agent Cost Optimization
&lt;/h3&gt;

&lt;p&gt;The Critic and multiple retrieval stages add latency and token usage.&lt;/p&gt;

&lt;p&gt;A future version could dynamically decide when a review step is actually necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  More Deterministic Tool Policies
&lt;/h3&gt;

&lt;p&gt;Some tools can be made more deterministic by moving safety and validation logic outside the LLM.&lt;/p&gt;

&lt;p&gt;The model should decide &lt;strong&gt;what to do&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The infrastructure should decide &lt;strong&gt;what is allowed&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better Long-Term Memory
&lt;/h3&gt;

&lt;p&gt;The current feedback system is intentionally lightweight.&lt;/p&gt;

&lt;p&gt;A stronger memory layer could learn user preferences and task patterns while still keeping the system auditable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;The project is available here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;code&gt;https://github.com/zda25m005-netizen/agentic-ai-os&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Clone the repository and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend → http://localhost:3000
API      → http://localhost:8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Architecture documentation&lt;/li&gt;
&lt;li&gt;Setup instructions&lt;/li&gt;
&lt;li&gt;Evaluation harness&lt;/li&gt;
&lt;li&gt;Retrieval experiments&lt;/li&gt;
&lt;li&gt;GraphRAG implementation&lt;/li&gt;
&lt;li&gt;Agent orchestration&lt;/li&gt;
&lt;li&gt;Tool implementations&lt;/li&gt;
&lt;li&gt;Observability configuration&lt;/li&gt;
&lt;li&gt;Docker configuration&lt;/li&gt;
&lt;li&gt;Helm deployment&lt;/li&gt;
&lt;li&gt;Kubernetes CI verification&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;I started this project wanting to understand what it takes to move beyond a simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM + Prompt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and build something closer to a real AI system.&lt;/p&gt;

&lt;p&gt;The biggest lesson wasn't GraphRAG.&lt;/p&gt;

&lt;p&gt;It wasn't LangGraph.&lt;/p&gt;

&lt;p&gt;It wasn't even the multi-agent architecture.&lt;/p&gt;

&lt;p&gt;It was &lt;strong&gt;measurement&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Building an agent is relatively easy.&lt;/p&gt;

&lt;p&gt;Building an agent where you can answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Did this change actually make the system better?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;is much harder.&lt;/p&gt;

&lt;p&gt;That's the part I'm continuing to work on.&lt;/p&gt;

&lt;p&gt;If you're building agentic systems too, I'd especially love feedback on the &lt;strong&gt;evaluation methodology&lt;/strong&gt;, the retrieval experiments, and the trade-offs in the architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thanks for reading.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>python</category>
      <category>kubernetes</category>
    </item>
  </channel>
</rss>
