<?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: Anbu Taco</title>
    <description>The latest articles on DEV Community by Anbu Taco (@anbu_taco_8fcb10bbb30d7ed).</description>
    <link>https://dev.to/anbu_taco_8fcb10bbb30d7ed</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3648618%2Fd0c6860e-a420-4750-a8ba-bc6d4be92a93.jpg</url>
      <title>DEV Community: Anbu Taco</title>
      <link>https://dev.to/anbu_taco_8fcb10bbb30d7ed</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anbu_taco_8fcb10bbb30d7ed"/>
    <language>en</language>
    <item>
      <title>How to use Elasticsearch as the Neural Backbone of a Multi-Agent AI Manufacturing and Monitoring Platform</title>
      <dc:creator>Anbu Taco</dc:creator>
      <pubDate>Wed, 04 Mar 2026 17:54:11 +0000</pubDate>
      <link>https://dev.to/anbu_taco_8fcb10bbb30d7ed/how-to-use-elasticsearch-as-the-neural-backbone-of-a-multi-agent-ai-manufacturing-and-monitoring-1lop</link>
      <guid>https://dev.to/anbu_taco_8fcb10bbb30d7ed/how-to-use-elasticsearch-as-the-neural-backbone-of-a-multi-agent-ai-manufacturing-and-monitoring-1lop</guid>
      <description>&lt;p&gt;&lt;strong&gt;Using Elasticsearch as a unified vector store + event bus for a 7-agent AI manufacturing platform — architecture breakdown&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I want to share a detailed write-up of how I used Elasticsearch as the core vector database in FactoryOS, a multi-agent AI platform I built for my final year project. This isn't a "I used pgvector" post — I want to get into the actual index design, retrieval strategy, and some non-obvious architectural choices.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The Setup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;7 autonomous agents, each handling a distinct manufacturing lifecycle stage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Procurement Agent — supplier selection, PO generation&lt;/li&gt;
&lt;li&gt;Model Analysis Agent — product spec comparison&lt;/li&gt;
&lt;li&gt;Digital Twin Agent — real-time factory floor state&lt;/li&gt;
&lt;li&gt;Incoming Orders Agent — delivery timeline prediction&lt;/li&gt;
&lt;li&gt;Invoice Management Agent — duplicate/anomaly detection&lt;/li&gt;
&lt;li&gt;Treasury Agent — autonomous inventory reordering&lt;/li&gt;
&lt;li&gt;Defect Analysis Agent — RAG-based root cause analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All agents share a single Elasticsearch cluster on Elastic Cloud. No agent has a private vector store. Elasticsearch is their collective long-term memory.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Why Elasticsearch over Pinecone / Weaviate / Qdrant?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The honest answer: manufacturing data doesn't fit the pure-vector-DB model well.&lt;/p&gt;

&lt;p&gt;You're dealing with two fundamentally different query patterns simultaneously:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Semantic queries&lt;/strong&gt;: "Find suppliers that have delivered corrosion-resistant fasteners for marine environments" — the document says "stainless M8 bolt, ISO 9227 salt-spray certified." Pure kNN handles this.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Exact / structured queries&lt;/strong&gt;: SKU lookups, batch ID filters, date range queries on invoice archives, threshold checks on inventory levels. Dedicated vector DBs are awkward here — you end up bolting on a separate DB or doing metadata filtering that degrades recall.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Elasticsearch's hybrid search via &lt;strong&gt;Reciprocal Rank Fusion (RRF)&lt;/strong&gt; solved both in a single query. BM25 handles the structured/keyword side, kNN handles the semantic side, and RRF fuses the ranked lists without requiring you to manually tune alpha weights. In practice this outperformed both pure kNN and pure BM25 significantly on our eval set of supplier matching queries.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Index Design&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each agent owns one or more indices. All use the same embedding model (&lt;code&gt;all-MiniLM-L6-v2&lt;/code&gt;, 384 dims) so cross-index semantic queries are coherent.&lt;/p&gt;

&lt;p&gt;Procurement index mapping (abbreviated):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"embedding":        dense_vector, dims=384, similarity=cosine, indexed=true
"product_category": text, analyzer=english
"invoice_summary":  text
"supplier_name":    keyword
"reliability_score": float
"avg_lead_time_days": float
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Defect index mapping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"embedding":           dense_vector, dims=384, similarity=cosine, indexed=true
"defect_description":  text
"batch_id":            keyword
"root_cause":          text
"severity":            keyword (enum: low/medium/high/critical)
"corrective_action":   text
"timestamp":           date
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inventory index (used by Treasury Agent):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"sku":               keyword
"current_stock":     integer
"safety_threshold":  integer
"unit_cost":         float
"last_updated":      date
"embedding":         dense_vector, dims=384 (for semantic reorder suggestions)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Hybrid Search Query (Procurement Agent)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the actual retriever structure used when the Procurement Agent needs to find best-fit suppliers for a new order:&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;"retriever"&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;"rrf"&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;"retrievers"&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;"standard"&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;"query"&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;"multi_match"&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;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;order description&amp;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;"fields"&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;"product_category"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invoice_summary"&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="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;"knn"&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;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"embedding"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"query_vector"&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="err"&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;"num_candidates"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"k"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&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;span class="nl"&gt;"rank_window_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"rank_constant"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&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;&lt;code&gt;rank_constant: 60&lt;/code&gt; is the standard RRF default and worked well without tuning. We experimented with lower values (20–40) but saw marginal gains that didn't justify the complexity.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;RAG Pipeline — Defect Analysis Agent&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the most interesting retrieval use case in the project. When a new defect report comes in:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Embed the defect description using the same sentence-transformer model&lt;/li&gt;
&lt;li&gt;kNN search against the defect index, &lt;code&gt;k=5&lt;/code&gt;, &lt;code&gt;num_candidates=50&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Retrieve &lt;code&gt;defect_description&lt;/code&gt;, &lt;code&gt;root_cause&lt;/code&gt;, &lt;code&gt;corrective_action&lt;/code&gt;, &lt;code&gt;batch_id&lt;/code&gt; for each hit&lt;/li&gt;
&lt;li&gt;Construct a prompt: system context + top-5 historical defect docs + new defect&lt;/li&gt;
&lt;li&gt;LLM (GPT-4o-mini) generates a root cause hypothesis + recommended corrective action&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The quality of retrieval here was highly sensitive to embedding model choice. A generic model caused semantic drift on technical terminology — "flux contamination" and "welding residue" weren't being retrieved together. Fine-tuning on a small corpus of manufacturing maintenance docs (scraped from public CMMS datasets) cut false negatives by ~40%.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Non-obvious Choice: Elasticsearch as the Agent Message Bus&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of Kafka or a task queue, agents communicate through a &lt;code&gt;factoryos-events&lt;/code&gt; index. Events are timestamped documents:&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;"event_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;"reorder_triggered"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"M8-SS-BOLT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"quantity_needed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"handled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"triggered_by"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"treasury_agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-15T09:32:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"embedding"&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="err"&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;Agents poll with &lt;code&gt;bool&lt;/code&gt; queries filtering on &lt;code&gt;event_type&lt;/code&gt; + &lt;code&gt;handled: false&lt;/code&gt;. On pickup, they update &lt;code&gt;handled: true&lt;/code&gt; with a partial update.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this worked better than expected:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full audit trail of every inter-agent action, queryable in Kibana&lt;/li&gt;
&lt;li&gt;Replay: re-run any agent's decision by replaying unhandled events from a timestamp&lt;/li&gt;
&lt;li&gt;Cross-event semantic search: "find all events semantically related to flux contamination issues" actually works because events are embedded&lt;/li&gt;
&lt;li&gt;Zero additional infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The downside: polling latency (we ran polls every 5s) and no push-based triggering. For a real-time production system you'd add a watcher or use Elasticsearch's percolate API to trigger agents on index writes.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Treasury Agent — Autonomous Reordering Logic&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Script query to find items below threshold:&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;"query"&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;"script"&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;"script"&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;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"doc['current_stock'].value &amp;lt; doc['safety_threshold'].value"&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;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;For each result, the agent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Runs a hybrid search on the procurement index to rank suppliers by semantic fit + reliability score&lt;/li&gt;
&lt;li&gt;Filters by &lt;code&gt;avg_lead_time_days &amp;lt; required_lead_time&lt;/code&gt; using a post-filter&lt;/li&gt;
&lt;li&gt;Generates a PO document and indexes it to &lt;code&gt;factoryos-orders&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Publishes a &lt;code&gt;purchase_order_created&lt;/code&gt; event to &lt;code&gt;factoryos-events&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Procurement Agent picks up the event, verifies supplier availability via an external API call, and either confirms or triggers a fallback supplier search.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What I'd Do Differently&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ELSER instead of sentence-transformers&lt;/strong&gt;: Elastic's learned sparse encoder is better suited for domain-specific industrial text without requiring fine-tuning. I didn't use it because I wanted full local control over embeddings, but for a production system ELSER would reduce the embedding infrastructure overhead significantly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Percolate API for event-driven triggers&lt;/strong&gt;: Polling every 5s works but is inelegant. Percolate queries registered per agent type would allow true push-based agent activation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ILM from day one&lt;/strong&gt;: I set up Index Lifecycle Management policies late in the project. The events and defect indices grew fast. Should have been day-one config.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Happy to go deep on any specific part — the hybrid search tuning, the embedding model choices, or the event bus design.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Stack: Node.js, Elasticsearch 8.x (Elastic Cloud), sentence-transformers, GPT-4o-mini, FastAPI&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Elasticsearch #VectorSearch #HybridSearch #RAG #AIAgents #VectorDatabase #ElasticBlogathon
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9wagoxc5tihm4dkgbo6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9wagoxc5tihm4dkgbo6.png" alt=" " width="800" height="455"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnfcu858y9g7lmnj5et0w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnfcu858y9g7lmnj5et0w.png" alt=" " width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>elasticsearch</category>
      <category>elasticblogathon</category>
      <category>vectordatabase</category>
      <category>ai</category>
    </item>
    <item>
      <title>COBOL in Big 25</title>
      <dc:creator>Anbu Taco</dc:creator>
      <pubDate>Fri, 05 Dec 2025 23:23:44 +0000</pubDate>
      <link>https://dev.to/anbu_taco_8fcb10bbb30d7ed/cobol-in-big-25-33gl</link>
      <guid>https://dev.to/anbu_taco_8fcb10bbb30d7ed/cobol-in-big-25-33gl</guid>
      <description>&lt;p&gt;DarkLedger: Precision Payroll Infrastructure&lt;/p&gt;

&lt;p&gt;&lt;a href="https://darkledger.vercel.app" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Request a Demo&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;💀 The Inspiration: The Floating Point Ghost&lt;/p&gt;

&lt;p&gt;In the world of Web3 and modern SaaS, we have forgotten the old gods. We build financial systems on JavaScript and Python, languages that rely on IEEE 754 floating-point arithmetic.&lt;/p&gt;

&lt;p&gt;In a standard modern environment, simple addition can yield terrifying results due to binary approximation errors:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;0.1+0.2=0.30000000000000004
0.1 + 0.2 = 0.30000000000000004
&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.1&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.2&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.30000000000000004&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;In a payroll run of $10,000,000, these "micro-pennies" accumulate. In the traditional banking world, this leads to audit failures. In the crypto world, where transactions are immutable, this leads to irreversible financial loss.&lt;/p&gt;

&lt;p&gt;We asked ourselves: "What if we brought the dead back to life to save the future?"&lt;/p&gt;

&lt;p&gt;⚡ What It Is&lt;/p&gt;

&lt;p&gt;DarkLedger (formerly Ledger-De-Main) is a "Frankenstein" architecture. We stitched together the oldest, most reliable financial engine (COBOL) with the newest, fastest settlement layer (Base L2).&lt;/p&gt;

&lt;p&gt;We refuse to modernize the math. We run the core payroll logic in a compiled COBOL binary—the same technology that powers 95% of the world's ATM swipes—ensuring 100% decimal precision. We then "stitch" this brain to a Python nervous system that executes payouts on the blockchain.&lt;/p&gt;

&lt;p&gt;🏗 How We Built It (The Architecture)&lt;/p&gt;

&lt;p&gt;We utilized Kiro's Agentic IDE to bridge two incompatible eras of computing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Brain: COBOL (Legacy Core)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We generated a GnuCOBOL program using Vibe Coding. This component handles the Gross-to-Net logic using Fixed-Point Arithmetic.&lt;/p&gt;

&lt;p&gt;Unlike floating point, our COBOL engine treats money as integers scaled by a power of 10. For a value ( V ), stored as an integer ( I ):&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;V=I102
V = \frac{I}{10^2}
&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;V&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;0&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;I&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;This ensures that our tax calculations (15% Federal, 5% State) are exact:&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;Net Pay=(Hours×Rate)−⌊Gross×0.15⌋−⌊Gross×0.05⌋
\text{Net Pay} = (\text{Hours} \times \text{Rate}) - \lfloor \text{Gross} \times 0.15 \rfloor - \lfloor \text{Gross} \times 0.05 \rfloor
&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;Net Pay&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;Hours&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;Rate&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;⌊&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;Gross&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.15&lt;/span&gt;&lt;span class="mclose"&gt;⌋&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;⌊&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;Gross&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.05&lt;/span&gt;&lt;span class="mclose"&gt;⌋&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;The Stitch: Python &amp;amp; Agent Hooks&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The biggest challenge was connecting a modern JSON-based frontend to a legacy binary that expects fixed-width text files.&lt;/p&gt;

&lt;p&gt;We used Kiro Agent Hooks to automate the "Stitching" process.&lt;/p&gt;

&lt;p&gt;The Hook: On every save of payroll.cbl, Kiro parses the DATA DIVISION, extracts the byte positions (e.g., PIC X(10)), and auto-generates a Python struct parser.&lt;/p&gt;

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

&lt;p&gt;Input: 23 Bytes (Employee ID, Hours, Rate, Tax Code)&lt;/p&gt;

&lt;p&gt;Output: 60 Bytes (ID, Gross, Taxes, Net, Status)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Hands: Settlement on Base&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once the math is verified by the "Brain," the "Body" (Python) uses the Coinbase CDP SDK to convert the Net Pay into a USDC transaction on the Base L2 network.&lt;/p&gt;

&lt;p&gt;🧟‍♂️ Challenges &amp;amp; Lessons Learned&lt;/p&gt;

&lt;p&gt;
  Click to expand our learnings
  &lt;p&gt;Challenge 1: The Language Barrier&lt;/p&gt;

&lt;p&gt;COBOL does not speak JSON. It speaks Bytes.&lt;/p&gt;

&lt;p&gt;Lesson: We learned that modern ease-of-use has made us lazy with data types. We had to build a rigid byte-level contract (defined in our design.md spec) to ensure Python didn't send garbage to the mainframe.&lt;/p&gt;

&lt;p&gt;Challenge 2: Containerizing a Monster&lt;/p&gt;

&lt;p&gt;Running COBOL in the cloud isn't standard.&lt;/p&gt;

&lt;p&gt;Lesson: We built a custom Docker container that acts as a time machine. It installs a lightweight Linux OS, pulls the gnucobol compiler dependencies, compiles the legacy code on build, and then spins up a modern FastAPI server to listen for requests.&lt;/p&gt;

&lt;p&gt;Challenge 3: "Audit-Proofing"&lt;/p&gt;

&lt;p&gt;We learned that "close enough" isn't good enough for payroll. By enforcing Banker's Rounding in COBOL (COMPUTE ROUNDED), we achieved a level of precision that standard JavaScript libraries struggle to replicate without heavy dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;🚀 Usage&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run the Container&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;docker build -t ledger-de-main .&lt;br&gt;
docker run -p 8000:8000 --env-file .env ledger-de-main&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Execute Payroll&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Navigate to our Retro-Terminal UI and execute a batch command:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;RUN PAYROLL --BATCH 2025-10-31&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The system will:&lt;/p&gt;

&lt;p&gt;Generate a fixed-width input.dat.&lt;/p&gt;

&lt;p&gt;Spawn a subprocess to run cobol/bin/payroll.&lt;/p&gt;

&lt;p&gt;Read the output.rpt.&lt;/p&gt;

&lt;p&gt;Execute a gasless USDC transfer on Base.&lt;/p&gt;

&lt;p&gt;📜 Kiro Implementation Details&lt;/p&gt;

&lt;p&gt;Specs: Used requirements.md to define the 23-byte input constraint.&lt;/p&gt;

&lt;p&gt;Steering: Enforced a "Sacred Timeline" rule in .kiro/steering/tech.md that forbade refactoring COBOL logic into Python.&lt;/p&gt;

&lt;p&gt;Hooks: Automated the binary compilation and Python model updates.&lt;/p&gt;

&lt;p&gt;Built with 💚 (and 🧟‍♂️) for Kiroween 2025&lt;/p&gt;

</description>
      <category>kiro</category>
      <category>legacy</category>
      <category>ai</category>
      <category>web3</category>
    </item>
  </channel>
</rss>
