<?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: Dipankar Sarkar</title>
    <description>The latest articles on DEV Community by Dipankar Sarkar (@dipankar_sarkar).</description>
    <link>https://dev.to/dipankar_sarkar</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%2F4010138%2F11e8895b-f7f8-41d3-9b12-2720a266d472.jpg</url>
      <title>DEV Community: Dipankar Sarkar</title>
      <link>https://dev.to/dipankar_sarkar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dipankar_sarkar"/>
    <language>en</language>
    <item>
      <title>Running five coding agents in parallel is easy. Not losing their state when one crashes is the hard part</title>
      <dc:creator>Dipankar Sarkar</dc:creator>
      <pubDate>Tue, 21 Jul 2026 15:26:13 +0000</pubDate>
      <link>https://dev.to/dipankar_sarkar/running-five-coding-agents-in-parallel-is-easy-not-losing-their-state-when-one-crashes-is-the-hard-4pfp</link>
      <guid>https://dev.to/dipankar_sarkar/running-five-coding-agents-in-parallel-is-easy-not-losing-their-state-when-one-crashes-is-the-hard-4pfp</guid>
      <description>&lt;p&gt;Point one AI coding agent at your repo and it mostly works. Point five at it and the&lt;br&gt;
problem stops being the agents and starts being the coordination. Who is working on&lt;br&gt;
what. What happens when agent three's session dies mid-task. Where the state lives so&lt;br&gt;
that a crash does not lose the plan. How the finished work merges back without&lt;br&gt;
stepping on itself.&lt;/p&gt;

&lt;p&gt;Most setups answer those questions with a JSON file in the working tree and a hope.&lt;br&gt;
The file gets half-written when a process is killed. The working tree gets dirty and&lt;br&gt;
pollutes code review. Two agents edit the same state and one wins silently.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;brat&lt;/code&gt; is a Rust multi-agent harness built to answer those questions properly. The&lt;br&gt;
core promise: even if agents crash, your coordination state is always recoverable and&lt;br&gt;
auditable.&lt;/p&gt;
&lt;h2&gt;
  
  
  The core idea: coordination is an append-only event log
&lt;/h2&gt;

&lt;p&gt;Brat does not keep a mutable state file. It is built on &lt;code&gt;grite&lt;/code&gt;, an append-only event&lt;br&gt;
log stored in git refs (&lt;code&gt;refs/grite/wal&lt;/code&gt;). Every change to coordination state is an&lt;br&gt;
event appended to that log. State is not stored, it is derived by replaying events.&lt;/p&gt;

&lt;p&gt;That one decision is where the crash-safety comes from. An append-only log has no&lt;br&gt;
half-written mutable record to corrupt. If a process dies mid-operation, you rebuild&lt;br&gt;
deterministically from the last known-good point. And because the log lives in git&lt;br&gt;
refs, not tracked files, your working tree stays clean. No coordination JSON showing&lt;br&gt;
up in &lt;code&gt;git status&lt;/code&gt;, no metadata noise in code review.&lt;/p&gt;

&lt;p&gt;The problems it explicitly targets, from its own list:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;How Brat fixes it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dirty working trees&lt;/td&gt;
&lt;td&gt;Metadata lives in &lt;code&gt;refs/grite/*&lt;/code&gt;, never in tracked files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Silent failures&lt;/td&gt;
&lt;td&gt;All state changes recorded as events, fully observable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crash recovery&lt;/td&gt;
&lt;td&gt;Append-only log enables deterministic rebuild from any point&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Merge chaos&lt;/td&gt;
&lt;td&gt;The Refinery manages a queue with configurable policy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  How it works: a small cast of roles
&lt;/h2&gt;

&lt;p&gt;Brat models the work as a set of roles, and once you see them the mental model clicks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mayor&lt;/strong&gt; is the AI orchestrator. It analyzes the codebase, breaks down the work,
and creates convoys and tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Convoy&lt;/strong&gt; is a group of related tasks. Think sprint, epic, or feature branch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task&lt;/strong&gt; is a single work item assigned to one coding agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Witness&lt;/strong&gt; spawns and monitors the agent sessions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refinery&lt;/strong&gt; manages the merge queue, runs CI checks, and handles integration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deacon&lt;/strong&gt; is the background janitor: it cleans locks, syncs state, and detects
orphaned sessions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The flow reads top to bottom. The Mayor creates a Convoy that contains Tasks. The&lt;br&gt;
Witness spawns agents against queued tasks. The Refinery merges finished work. The&lt;br&gt;
Deacon keeps the whole thing from leaking locks and orphans.&lt;/p&gt;

&lt;p&gt;Underneath, the substrate does the unglamorous correctness work. Events are immutable.&lt;br&gt;
Each agent (actor) gets its own isolated data directory, so one agent's writes do not&lt;br&gt;
clobber another's. Engine operations have bounded, configurable timeouts, so a hung&lt;br&gt;
agent does not hang the harness. Resource coordination uses TTL-based lease locks, so&lt;br&gt;
a crashed agent's lock eventually expires instead of deadlocking everyone.&lt;/p&gt;

&lt;p&gt;And it is engine-agnostic. Brat drives your preferred coding tool through an adapter:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;&lt;code&gt;claude&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aider&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aider&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenCode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;opencode&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex&lt;/td&gt;
&lt;td&gt;&lt;code&gt;codex&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Continue&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cn&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemini&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Copilot&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gh copilot&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You pick the engine in &lt;code&gt;.brat/config.toml&lt;/code&gt; and Brat handles the orchestration around&lt;br&gt;
it.&lt;/p&gt;
&lt;h2&gt;
  
  
  A concrete run
&lt;/h2&gt;

&lt;p&gt;The loop is deliberately small. Initialize the substrate and the harness, start the&lt;br&gt;
Mayor, ask it to analyze code, then let the Witness run the tasks it created.&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="nb"&gt;cd &lt;/span&gt;your-project
grite init     &lt;span class="c"&gt;# initialize the grite substrate&lt;/span&gt;
brat init      &lt;span class="c"&gt;# initialize the Brat harness&lt;/span&gt;

&lt;span class="c"&gt;# start the AI orchestrator and give it a job&lt;/span&gt;
brat mayor start
brat mayor ask &lt;span class="s2"&gt;"Analyze src/ and create tasks for any bugs you find"&lt;/span&gt;

&lt;span class="c"&gt;# see what it created&lt;/span&gt;
brat status

&lt;span class="c"&gt;# spawn agents for the queued tasks&lt;/span&gt;
brat witness run &lt;span class="nt"&gt;--once&lt;/span&gt;
brat status &lt;span class="nt"&gt;--watch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because coordination is events in git, &lt;code&gt;brat status&lt;/code&gt; is not reading a fragile local&lt;br&gt;
file, it is querying derived state that can be rebuilt from the log at any time.&lt;/p&gt;

&lt;p&gt;You can also declare reusable workflows. A parallel convoy is just a set of legs that&lt;br&gt;
fan out and a synthesis step that pulls the results together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;code-review&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;convoy&lt;/span&gt;
&lt;span class="na"&gt;legs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;correctness&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Review&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;correctness"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Review&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;security"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;performance&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Review&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;performance"&lt;/span&gt;
&lt;span class="na"&gt;synthesis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Synthesize&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;review&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;findings"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three agents review three different concerns in parallel, and a fourth synthesizes.&lt;br&gt;
That is the shape multi-agent work actually wants, and it maps cleanly onto convoys&lt;br&gt;
and tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it does not fit
&lt;/h2&gt;

&lt;p&gt;Brat ships its own honest "what it does not solve" list, which is the main reason I&lt;br&gt;
trust it. Repeating it, because the trade-offs are the point.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It does not fix engine reliability.&lt;/strong&gt; API rate limits, auth failures, and vendor&lt;br&gt;
outages are outside Brat's control. If Claude or GPT is down, Brat cannot conjure a&lt;br&gt;
response. It can recover the coordination state around the failure, not the failure&lt;br&gt;
itself.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It does not resolve real merge conflicts.&lt;/strong&gt; The Refinery manages the merge queue&lt;br&gt;
and policy. Genuine code conflicts still need human judgment. Brat orders and gates&lt;br&gt;
the merges, it does not understand your code well enough to reconcile two&lt;br&gt;
contradictory diffs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It does not write your prompts.&lt;/strong&gt; Brat orchestrates agents. Prompt quality is&lt;br&gt;
still your job. Point it at a vague task and you get vague work, coordinated&lt;br&gt;
cleanly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It does not replace your CI.&lt;/strong&gt; Brat integrates with your existing CI, it does not&lt;br&gt;
become it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It is early and Rust-native.&lt;/strong&gt; You need the Rust toolchain to build from source,&lt;br&gt;
and you install &lt;code&gt;grite&lt;/code&gt; first as a prerequisite. This is infrastructure for people&lt;br&gt;
who want to run several agents seriously, not a one-click consumer app.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern across that list: Brat is honest about being a coordination substrate, not&lt;br&gt;
a magic wand. It makes the state crash-safe and auditable. It does not make the agents&lt;br&gt;
good.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;The hard part of multi-agent coding is not parallelism, it is state. An append-only
event log in git refs makes that state crash-recoverable and keeps your working tree
clean.&lt;/li&gt;
&lt;li&gt;Roles (Mayor, Convoy, Task, Witness, Refinery, Deacon) give you a mental model that
matches how the work actually decomposes.&lt;/li&gt;
&lt;li&gt;Actor isolation, bounded timeouts, and TTL lease locks are the boring correctness
details that decide whether a harness survives contact with a crash.&lt;/li&gt;
&lt;li&gt;Believe a project more when it publishes what it does not solve. Brat does.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Code, the role docs, and the demo are here:&lt;br&gt;
&lt;a href="https://github.com/neul-labs/brat" rel="noopener noreferrer"&gt;https://github.com/neul-labs/brat&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are already running multiple coding agents, I want to know how you handle a&lt;br&gt;
mid-task crash today. Kick the tyres, issues welcome.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rust</category>
      <category>agents</category>
      <category>opensource</category>
    </item>
    <item>
      <title>AI agents that browse the web need a fleet of isolated browsers, here is a brokerless scheduler for it</title>
      <dc:creator>Dipankar Sarkar</dc:creator>
      <pubDate>Sun, 19 Jul 2026 15:26:18 +0000</pubDate>
      <link>https://dev.to/dipankar_sarkar/ai-agents-that-browse-the-web-need-a-fleet-of-isolated-browsers-here-is-a-brokerless-scheduler-for-h8j</link>
      <guid>https://dev.to/dipankar_sarkar/ai-agents-that-browse-the-web-need-a-fleet-of-isolated-browsers-here-is-a-brokerless-scheduler-for-h8j</guid>
      <description>&lt;p&gt;Give an AI agent one browser and it is easy. Give a hundred agents their own browsers and you have an infrastructure problem.&lt;/p&gt;

&lt;p&gt;Browser automation at scale has an awkward shape. Each browser wants real resources, a real filesystem, and real isolation, because you are running untrusted pages, scraping targets that fight back, or agent sessions you do not want sharing cookies. So you cannot just spin up a thousand threads. You end up managing containers, then managing the machines the containers run on, and then you are writing a scheduler.&lt;/p&gt;

&lt;p&gt;Machineuse is that scheduler, built for exactly this workload. It is a Python platform that creates, schedules, and manages isolated browser instances across multiple worker nodes, with load balancing and a snapshot-based dormancy trick to reclaim resources from idle instances. This post covers how it places work, how dormancy works, and where the design constrains you.&lt;/p&gt;

&lt;p&gt;The project tags itself for &lt;code&gt;chromium&lt;/code&gt;, &lt;code&gt;browser-automation&lt;/code&gt;, and &lt;code&gt;mcp&lt;/code&gt;, so the agent-browser fleet is squarely the workload it has in mind. One thing to be precise about, though: machineuse schedules and isolates the containers. What runs inside, the browser and your automation or agent code, is yours. It is fleet infrastructure, not an agent framework. That separation is the point. You bring the agent, it brings the machines.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea
&lt;/h2&gt;

&lt;p&gt;The unit is an isolated browser instance running inside a &lt;code&gt;systemd-nspawn&lt;/code&gt; container with dedicated resources. The platform's job is to decide which node runs each instance and to keep the fleet healthy.&lt;/p&gt;

&lt;p&gt;Two design decisions define it.&lt;/p&gt;

&lt;p&gt;First, the messaging is pure NNG, with no external broker. There is no Redis or RabbitMQ in the middle. Nodes talk to a control plane over NNG sockets directly. That is one fewer stateful system to run and one fewer thing to fall over.&lt;/p&gt;

&lt;p&gt;Second, placement is intelligent rather than round-robin. The scheduler places instances based on node capabilities and current load, so a heavier node does not get handed work it cannot serve well.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the architecture fits together
&lt;/h2&gt;

&lt;p&gt;There are two roles. A control plane coordinates, and worker nodes run containers. You start a control plane bound to an NNG address, then point workers at it.&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;# Start the control plane&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; machineuse.nodes.control_plane &lt;span class="nt"&gt;--bind&lt;/span&gt; tcp://&lt;span class="k"&gt;*&lt;/span&gt;:5000

&lt;span class="c"&gt;# Start worker nodes on different machines&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; machineuse.nodes.agent worker-1 tcp://control-plane:5000
python &lt;span class="nt"&gt;-m&lt;/span&gt; machineuse.nodes.agent worker-2 tcp://control-plane:5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Storage is split by role, which is a sensible choice. Each node uses SQLite locally. The control plane can use PostgreSQL for shared metadata. DuckDB is used for analytics on the metrics side. So the local hot path stays embedded and simple, while shared state and analytics get the heavier stores only where they are actually needed.&lt;/p&gt;

&lt;p&gt;Reliability comes from auto-healing: the platform detects failures and migrates instances off a bad node. Real-time metrics feed time-series analytics so you can see utilization across the cluster rather than guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  How snapshot dormancy works
&lt;/h2&gt;

&lt;p&gt;This is the feature worth the price of admission. A browser instance you are not actively using still holds memory and CPU. Multiply that across a fleet and idle instances become the dominant cost.&lt;/p&gt;

&lt;p&gt;Machineuse handles this with snapshot dormancy: it pauses an instance and takes a filesystem snapshot, freeing the live resources, then revives it from the snapshot when you need it again. So an instance you might need later does not have to sit resident. It goes dormant, gives back its resources, and comes back when called.&lt;/p&gt;

&lt;p&gt;From the CLI that is two commands.&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;# Put an instance to sleep, reclaiming its resources&lt;/span&gt;
machineuse-cli dormant &amp;lt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;# Bring it back from its snapshot&lt;/span&gt;
machineuse-cli revive &amp;lt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rest of the CLI is what you expect for lifecycle and fleet management.&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;# Instance lifecycle&lt;/span&gt;
machineuse-cli create &lt;span class="nt"&gt;--image&lt;/span&gt; ubuntu:22.04
machineuse-cli list &lt;span class="nt"&gt;--node&lt;/span&gt; worker-1
machineuse-cli delete &amp;lt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;# Cluster view&lt;/span&gt;
machineuse-cli nodes list
machineuse-cli cluster status
machineuse-cli metrics &lt;span class="nt"&gt;--node&lt;/span&gt; worker-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using it from code
&lt;/h2&gt;

&lt;p&gt;There is a Python client that talks to the cluster and a REST API on port 8000. The library path is the one most automation would use, since it hands back the scheduling result directly.&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;machineuse.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ClusterManager&lt;/span&gt;

&lt;span class="c1"&gt;# Connect to the distributed cluster
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ClusterManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tcp://control-plane:5000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Create an instance and let the scheduler place it
&lt;/span&gt;&lt;span class="n"&gt;instance&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="nf"&gt;create_instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ubuntu:22.04&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;config&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;memory_gb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu_cores&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Instance &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; scheduled on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;node_id&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;span class="c1"&gt;# Check fleet-wide utilization
&lt;/span&gt;&lt;span class="n"&gt;status&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="nf"&gt;get_cluster_status&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cluster utilization: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utilization&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The REST surface mirrors this. A &lt;code&gt;POST /v2/instances&lt;/code&gt; with an image and config creates an instance, &lt;code&gt;GET /v2/instances&lt;/code&gt; lists them, and &lt;code&gt;GET /health&lt;/code&gt; is the readiness check. Deployment is Docker Compose, with a single-node &lt;code&gt;docker-compose.yml&lt;/code&gt; and a &lt;code&gt;docker-compose.distributed.yml&lt;/code&gt; for control plane plus workers plus PostgreSQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it does not fit
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;systemd-nspawn means Linux, systemd, and root.&lt;/strong&gt; The README is explicit: you need an Ubuntu or Debian system with systemd-nspawn support, Python 3.11+, and root or sudo access for container management. This is not going to run on macOS, and it is not going to run rootless. If your automation infrastructure is not systemd Linux you host yourself, this is the wrong tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There is a per-node ceiling.&lt;/strong&gt; &lt;code&gt;MACHINEUSE_MAX_INSTANCES&lt;/code&gt; defaults to 50 containers per node. That is a sensible default, and it also tells you the scaling model: you scale out by adding nodes, not by cramming a box. Plan capacity around instances-per-node times node-count, and remember each browser is a real resource consumer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dormancy is a trade, not free capacity.&lt;/strong&gt; Snapshotting to disk and reviving takes time and disk space. It is a win when instances are idle long enough to justify the pause and restore cost. For instances you cycle rapidly, the snapshot overhead may cost more than it saves. It shines for a large pool of intermittently-used sessions, not for high-churn throwaway instances.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Brokerless NNG puts coordination on you.&lt;/strong&gt; No external broker is a real operational win, but the control plane is then the coordination point you must keep available. It uses PostgreSQL for shared metadata, so your durability and HA story for the fleet is your durability and HA story for that database and that control-plane process.&lt;/p&gt;

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

&lt;p&gt;Machineuse is a focused answer to one real problem: running many isolated browsers across many machines without hand-rolling the scheduler and without standing up a message broker.&lt;/p&gt;

&lt;p&gt;The idea worth stealing is snapshot dormancy. Treating an idle heavy instance as something you pause to a filesystem snapshot and revive on demand is a clean way to decouple "instances that exist" from "resources currently spent." It turns idle capacity from a cost into a snapshot on disk.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/dotcommoners/machineuse" rel="noopener noreferrer"&gt;https://github.com/dotcommoners/machineuse&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you run browser automation at any real scale, stand up the single-node Compose stack, create a handful of instances, and measure your own dormant-to-revive time. Real revive latency on your storage is the number that decides whether dormancy pays off, and it is exactly the kind of issue worth filing.&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
    </item>
    <item>
      <title>A key-value store where the query language is Lua, and you can build RAG inside it</title>
      <dc:creator>Dipankar Sarkar</dc:creator>
      <pubDate>Fri, 17 Jul 2026 15:27:11 +0000</pubDate>
      <link>https://dev.to/dipankar_sarkar/a-key-value-store-where-the-query-language-is-lua-and-you-can-build-rag-inside-it-391e</link>
      <guid>https://dev.to/dipankar_sarkar/a-key-value-store-where-the-query-language-is-lua-and-you-can-build-rag-inside-it-391e</guid>
      <description>&lt;p&gt;Most embedded databases give you two verbs and a shrug. Put a value. Get a value.&lt;br&gt;
Anything cleverer than that, filtering, transforming, combining reads, you do in&lt;br&gt;
your application language, which means pulling data across the boundary, working on&lt;br&gt;
it, and pushing it back.&lt;/p&gt;

&lt;p&gt;That round-trip is fine until the logic gets interesting. Increment a counter&lt;br&gt;
atomically. Read a document, embed it, store the vector. Do a similarity search and&lt;br&gt;
feed the top hit into an LLM. Every one of those becomes several client calls with&lt;br&gt;
your app code as the glue.&lt;/p&gt;

&lt;p&gt;Liath takes a different bet. It is an embedded key-value store where the query&lt;br&gt;
language is Lua, a real programming language, running next to the data. You send&lt;br&gt;
logic to the data instead of dragging the data to your logic.&lt;/p&gt;
&lt;h2&gt;
  
  
  The core idea
&lt;/h2&gt;

&lt;p&gt;The pitch is three lines to a working database:&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;liath&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;EmbeddedLiath&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EmbeddedLiath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user:1&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;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&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="s"&gt;Alice&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="s"&gt;role&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="s"&gt;admin&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user:1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the boring half. The interesting half is that you can hand Liath a Lua&lt;br&gt;
script and it runs against the store in one call, with the &lt;code&gt;db&lt;/code&gt; object and a set of&lt;br&gt;
&lt;code&gt;plugins&lt;/code&gt; available inside the script:&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_lua&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;&lt;span class="s"&gt;
    -- Store data
    db:put(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;counter&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="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)

    -- Read and modify
    local count = tonumber(db:get(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;counter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;))
    db:put(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;counter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, tostring(count + 1))

    return db:get(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;counter&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The read, the modify, and the write happen server-side in one execution instead of&lt;br&gt;
three Python calls. Lua is a good choice for this. It is tiny, it embeds cleanly,&lt;br&gt;
and it is the same language people already accept inside Redis and Nginx for exactly&lt;br&gt;
this reason.&lt;/p&gt;
&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Liath is a Python package with a pluggable storage backend and a plugin system that&lt;br&gt;
extends the Lua runtime.&lt;/p&gt;

&lt;p&gt;Storage is not fixed. You get LevelDB for development and RocksDB for production, and&lt;br&gt;
a &lt;code&gt;storage_type&lt;/code&gt; of &lt;code&gt;auto&lt;/code&gt;, &lt;code&gt;rocksdb&lt;/code&gt;, or &lt;code&gt;leveldb&lt;/code&gt; picks the engine. For a&lt;br&gt;
production deployment you point it explicitly:&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;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EmbeddedLiath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;data_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/var/lib/liath&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;storage_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rocksdb&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;The capabilities that make Liath more than a KV wrapper come from plugins, exposed&lt;br&gt;
inside Lua under the &lt;code&gt;plugins&lt;/code&gt; table. Some are always present, others you install&lt;br&gt;
as extras:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plugin&lt;/th&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Install&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;db&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Core CRUD&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;file&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;File read/write&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cache&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Query result caching&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backup&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Backup/restore&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;monitor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;System monitoring&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;embed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Text/image embeddings&lt;/td&gt;
&lt;td&gt;&lt;code&gt;liath[embed]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vdb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Vector similarity search&lt;/td&gt;
&lt;td&gt;&lt;code&gt;liath[vdb]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;llm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;LLM completions/chat&lt;/td&gt;
&lt;td&gt;&lt;code&gt;liath[llm]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So &lt;code&gt;pip install liath[embed,vdb,llm]&lt;/code&gt; turns a key-value store into something that&lt;br&gt;
can embed text, index vectors, and call a model, all reachable from a Lua script.&lt;/p&gt;

&lt;p&gt;Namespaces give you multi-tenant isolation without running separate databases. You&lt;br&gt;
create a namespace, switch to it, and keys written there do not collide with another&lt;br&gt;
namespace:&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_namespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;production&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_namespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;production&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;config&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;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;debug&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: false}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_namespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;development&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;config&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;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;debug&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: true}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Separate from production
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also write your own plugin in Python by subclassing &lt;code&gt;PluginBase&lt;/code&gt; and&lt;br&gt;
exposing Lua-callable functions, then load it with a &lt;code&gt;plugins_dir&lt;/code&gt;. That is the&lt;br&gt;
escape hatch when the built-ins are not enough.&lt;/p&gt;
&lt;h2&gt;
  
  
  RAG without leaving the database
&lt;/h2&gt;

&lt;p&gt;The example that shows why co-locating logic and data matters is retrieval-augmented&lt;br&gt;
generation. In most stacks that is an orchestration script gluing an embedding&lt;br&gt;
service, a vector database, and an LLM API. In Liath the README does it in one Lua&lt;br&gt;
script per phase.&lt;/p&gt;

&lt;p&gt;Indexing creates a vector index, stores a document, embeds it, and adds the vector:&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_lua&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;&lt;span class="s"&gt;
    local json = require(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cjson&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)

    plugins.vdb.vdb_create_index(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, 384)

    local text = &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Liath is a programmable database with Lua queries.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;
    db:put(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;doc:1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, text)

    local emb = json.decode(plugins.embed.embed(text)).embedding
    plugins.vdb.vdb_add(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docs&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="s"&gt;doc:1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, emb)

    return &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Indexed&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Querying embeds the question, searches for neighbours, pulls the matching document,&lt;br&gt;
builds a prompt, and calls the model, all in one server-side script:&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;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_lua&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;&lt;span class="s"&gt;
    local json = require(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cjson&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)
    local query = &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is Liath?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;

    local q_emb = json.decode(plugins.embed.embed(query)).embedding
    local results = json.decode(plugins.vdb.vdb_search(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, q_emb, 3))

    local context = db:get(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;doc:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; .. results.results[1].id)

    local prompt = &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Context: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; .. context .. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;nQuestion: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; .. query
    return plugins.llm.llm_complete(prompt)
&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;The embedding backend is FastEmbed, the vector search is USearch, and the LLM plugin&lt;br&gt;
targets OpenAI or Llama. The point is not that any one of those is novel. It is that&lt;br&gt;
the retrieve, augment, generate loop runs where the data already sits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it does not fit
&lt;/h2&gt;

&lt;p&gt;Now the honest part.&lt;/p&gt;

&lt;p&gt;Liath is single-node and embedded. There is a &lt;code&gt;liath-server&lt;/code&gt; with an HTTP API and a&lt;br&gt;
&lt;code&gt;liath-cli&lt;/code&gt;, but the architecture is an embedded store, not a distributed cluster.&lt;br&gt;
If you need horizontal scale, replication, or failover, this is the wrong tool.&lt;/p&gt;

&lt;p&gt;Running application logic inside the database is a trade you should make with your&lt;br&gt;
eyes open. It is the same tension people have argued about with Redis Lua and&lt;br&gt;
Postgres stored procedures for years. The logic is fast because it is next to the&lt;br&gt;
data, but it also lives in a place that is harder to test, version, and debug than&lt;br&gt;
your normal application code. A Lua string in a Python file is not a first-class&lt;br&gt;
citizen of your test suite unless you make it one.&lt;/p&gt;

&lt;p&gt;The AI plugins are powerful and they are also new surface area. Embeddings, vector&lt;br&gt;
search, and LLM calls each pull in their own dependency and their own failure modes.&lt;br&gt;
An LLM call from inside a Lua script still has network latency, rate limits, and&lt;br&gt;
cost. Co-location removes round-trips between your services. It does not remove the&lt;br&gt;
model API on the far side.&lt;/p&gt;

&lt;p&gt;And Lua is a real language, which means Lua is real rope. Expressive server-side&lt;br&gt;
scripting is exactly the kind of power that turns into an unmaintainable pile if the&lt;br&gt;
team does not treat those scripts with the same discipline as the rest of the code.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Sending logic to the data instead of data to the logic is a genuinely different
shape for an embedded store, and Lua is a proven choice for it.&lt;/li&gt;
&lt;li&gt;The plugin model is the real story. Optional &lt;code&gt;embed&lt;/code&gt;, &lt;code&gt;vdb&lt;/code&gt;, and &lt;code&gt;llm&lt;/code&gt; extras let
the same KV store host a full RAG loop.&lt;/li&gt;
&lt;li&gt;Pluggable RocksDB or LevelDB backends and namespace isolation make it more
production-shaped than a toy, within single-node limits.&lt;/li&gt;
&lt;li&gt;Treat in-database Lua like stored procedures: powerful, fast, and easy to abuse.
Test them, version them, or regret them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install it, the code, and the plugin reference are here:&lt;br&gt;
&lt;a href="https://github.com/incredlabs/liath" rel="noopener noreferrer"&gt;https://github.com/incredlabs/liath&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are running a RAG pipeline as three separate services today, I would like to&lt;br&gt;
know whether collapsing it into one embedded store is a relief or a footgun for your&lt;br&gt;
workload. Try &lt;code&gt;pip install liath[embed,vdb,llm]&lt;/code&gt;, run the RAG example, and tell me&lt;br&gt;
where it breaks.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>llm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>A package.lock for the prompts hiding in your codebase</title>
      <dc:creator>Dipankar Sarkar</dc:creator>
      <pubDate>Wed, 15 Jul 2026 15:24:31 +0000</pubDate>
      <link>https://dev.to/dipankar_sarkar/a-packagelock-for-the-prompts-hiding-in-your-codebase-2hom</link>
      <guid>https://dev.to/dipankar_sarkar/a-packagelock-for-the-prompts-hiding-in-your-codebase-2hom</guid>
      <description>&lt;p&gt;Prompts are dependencies. We just refuse to treat them like it. A production LLM&lt;br&gt;
app has prompt strings scattered across a dozen files, concatenated inline, and&lt;br&gt;
edited by whoever touched that feature last. Nobody knows which prompts exist,&lt;br&gt;
which version is live, or whether the one in &lt;code&gt;summarize.py&lt;/code&gt; matches the one the&lt;br&gt;
eval ran against. We version our libraries down to the patch. Our prompts get&lt;br&gt;
a &lt;code&gt;content = "Summarize: " + text&lt;/code&gt; and a shrug.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;blogus&lt;/code&gt; is a &lt;code&gt;package.lock&lt;/code&gt; for AI prompts. It extracts the prompts already in&lt;br&gt;
your code, versions them like dependencies, locks them with content hashes, and&lt;br&gt;
syncs changes back to your source files. The tagline is exactly that:&lt;br&gt;
"&lt;code&gt;package.lock&lt;/code&gt; for AI prompts."&lt;/p&gt;
&lt;h2&gt;
  
  
  The core idea: discover, do not migrate
&lt;/h2&gt;

&lt;p&gt;Most prompt-management tools ask you to move your prompts into their system&lt;br&gt;
first. That migration is the reason they never get adopted. blogus inverts it.&lt;br&gt;
It scans your existing code and finds the LLM calls where they already live, so&lt;br&gt;
adoption is a scan, not a rewrite.&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="nv"&gt;$ &lt;/span&gt;blogus scan

Found 3 LLM API calls:
  src/chat.py:15         OpenAI      unversioned
  src/summarize.py:42    OpenAI      unversioned
  lib/translate.js:28    Anthropic   unversioned
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That output is the whole pitch in one screen. It found calls across Python and&lt;br&gt;
JavaScript, identified the provider, and flagged every one as unversioned. You&lt;br&gt;
did not move anything. You just found out what you have.&lt;/p&gt;
&lt;h2&gt;
  
  
  The workflow
&lt;/h2&gt;

&lt;p&gt;blogus runs as a five-step loop that maps cleanly onto how you already think&lt;br&gt;
about dependencies: extract, version, lock, sync, verify.&lt;/p&gt;

&lt;p&gt;Versioning turns a discovered prompt into a &lt;code&gt;.prompt&lt;/code&gt; file with front matter,&lt;br&gt;
so the prompt becomes a real artifact with a name, a model config, and typed&lt;br&gt;
variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# prompts/summarize.prompt&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;summarize&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gpt-4o&lt;/span&gt;
  &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.3&lt;/span&gt;
&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;text&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;Summarize in 2-3 sentences&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;text&lt;/span&gt;&lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Locking generates &lt;code&gt;prompts.lock&lt;/code&gt;, which is the mechanism that makes this a&lt;br&gt;
dependency system rather than a folder of text files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# prompts.lock&lt;/span&gt;
&lt;span class="na"&gt;prompts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;summarize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;hash&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sha256:a1b2c3d4...&lt;/span&gt;
    &lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;4903f76&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each prompt gets a &lt;code&gt;sha256&lt;/code&gt; content hash and the commit it was locked at. Now&lt;br&gt;
"which version of the summarize prompt is in production" has an answer you can&lt;br&gt;
diff, not a guess.&lt;/p&gt;
&lt;h2&gt;
  
  
  How the sync works
&lt;/h2&gt;

&lt;p&gt;The step that makes this more than a linter is &lt;code&gt;fix&lt;/code&gt;. It rewrites your source&lt;br&gt;
code to load from the versioned prompt instead of carrying an inline string:&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="c1"&gt;# Before
&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summarize: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;

&lt;span class="c1"&gt;# After
# @blogus:summarize sha256:a1b2c3d4
&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;summarize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inline string becomes a &lt;code&gt;load_prompt&lt;/code&gt; call annotated with the prompt name&lt;br&gt;
and its hash. That comment is the link between code and lock file. Once it is&lt;br&gt;
there, &lt;code&gt;blogus verify&lt;/code&gt; can run in CI and fail the build if the code has drifted&lt;br&gt;
from the locked prompt:&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="nv"&gt;$ &lt;/span&gt;blogus verify &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the whole value. The hash in the code comment must match the hash in the&lt;br&gt;
lock file. If someone edits the inline usage or the &lt;code&gt;.prompt&lt;/code&gt; file changes&lt;br&gt;
without a re-lock, verify catches it before merge. It is the same contract as a&lt;br&gt;
lockfile check on your dependencies, applied to the prompts that actually drive&lt;br&gt;
your model behavior.&lt;/p&gt;

&lt;p&gt;The CLI has the surface you would expect around that loop: &lt;code&gt;scan&lt;/code&gt;, &lt;code&gt;init&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;prompts&lt;/code&gt; to list, &lt;code&gt;exec &amp;lt;name&amp;gt;&lt;/code&gt; to run a prompt with variables, &lt;code&gt;analyze&lt;/code&gt; to&lt;br&gt;
evaluate effectiveness, &lt;code&gt;test&lt;/code&gt; to generate test cases, &lt;code&gt;lock&lt;/code&gt;, &lt;code&gt;verify&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;check&lt;/code&gt; to find unversioned prompts, and &lt;code&gt;fix&lt;/code&gt;. There is also a web UI&lt;br&gt;
(&lt;code&gt;uvx --with blogus[web] blogus-web&lt;/code&gt; on port 8000) and an interactive TUI demo.&lt;br&gt;
Install is &lt;code&gt;uv add blogus&lt;/code&gt;, or &lt;code&gt;uvx blogus scan&lt;/code&gt; to try it with zero install.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it does not fit
&lt;/h2&gt;

&lt;p&gt;A few honest limits.&lt;/p&gt;

&lt;p&gt;Start with what it can even see. The scanner detects OpenAI SDK calls&lt;br&gt;
(&lt;code&gt;openai.chat.completions.create&lt;/code&gt;) and Anthropic SDK calls&lt;br&gt;
(&lt;code&gt;anthropic.messages.create&lt;/code&gt;), in Python and JavaScript/TypeScript files. That&lt;br&gt;
is the detection surface. If your calls go through LiteLLM, Bedrock, a provider&lt;br&gt;
router, or your own thin wrapper, or if your service is Go or Rust, &lt;code&gt;scan&lt;/code&gt;&lt;br&gt;
finds nothing and the whole pitch evaporates. Check that your stack is in that&lt;br&gt;
window before you judge the idea by an empty scan.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;analyze&lt;/code&gt; command does LLM-powered effectiveness scoring and the &lt;code&gt;test&lt;/code&gt;&lt;br&gt;
command generates test cases. This is not a hidden detail, it is in the&lt;br&gt;
signature: &lt;code&gt;analyze&lt;/code&gt; takes a &lt;code&gt;--judge-model&lt;/code&gt;. A model is grading your prompt.&lt;br&gt;
LLM-as-judge is noisy, so treat those scores as a signal to investigate, not a&lt;br&gt;
gate to block on. The trustworthy part of blogus is the deterministic core:&lt;br&gt;
scan, lock, verify. The hash contract is exact. The quality scoring is fuzzy by&lt;br&gt;
nature, and you should hold the two to different standards.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;fix&lt;/code&gt; step edits your source files. Rewriting inline strings into&lt;br&gt;
&lt;code&gt;load_prompt&lt;/code&gt; calls is a code transformation, and any codemod can get an edge&lt;br&gt;
case wrong. There is a &lt;code&gt;blogus fix --dry-run&lt;/code&gt; that previews the rewrite, and&lt;br&gt;
you should use it before the real thing. Run it in a clean working tree, review&lt;br&gt;
the diff, and keep it behind a PR. This is not a tool to run with uncommitted&lt;br&gt;
changes.&lt;/p&gt;

&lt;p&gt;It also introduces a &lt;code&gt;load_prompt&lt;/code&gt; runtime dependency into your app. Your&lt;br&gt;
prompts now live in &lt;code&gt;.prompt&lt;/code&gt; files that have to ship and load at runtime. That&lt;br&gt;
is a fair trade for versioning, but it is a trade. If your prompts are trivial&lt;br&gt;
and never change, the lockfile ceremony is overhead you do not need. The value&lt;br&gt;
scales with how many prompts you have and how often they drift.&lt;/p&gt;

&lt;p&gt;And it manages the prompt text and model config. It does not manage what the&lt;br&gt;
model actually returns. A locked, verified prompt can still produce a worse&lt;br&gt;
answer after a model provider updates their model under the same name. blogus&lt;br&gt;
pins your side of the contract, not the provider's.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Prompts are dependencies, and the reason they rot is that nothing treats them
like one. A content hash plus a lockfile fixes the "which version is live"
question.&lt;/li&gt;
&lt;li&gt;Discover-in-place instead of migrate is why this can actually get adopted. The
first command is a scan, not a rewrite.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;verify&lt;/code&gt; step in CI is the payoff. Code drift from the locked prompt fails
the build, same as a dependency lock check.&lt;/li&gt;
&lt;li&gt;Trust the deterministic core (scan, lock, verify) more than the LLM-powered
analyze and test features. Different reliability, different standards.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your codebase has more prompts than anyone can list from memory, &lt;code&gt;blogus&lt;br&gt;
scan&lt;/code&gt; is a five-second way to find out how many. Code and CLI reference are&lt;br&gt;
here: &lt;a href="https://github.com/Skelf-Research/blogus" rel="noopener noreferrer"&gt;https://github.com/Skelf-Research/blogus&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I would genuinely like to know the highest prompt count anyone's scan turns up.&lt;br&gt;
Kick the tyres, issues welcome.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>python</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>I threw 750 autonomous LLM exploit attempts at a $10k sandbox bounty. Zero escapes.</title>
      <dc:creator>Dipankar Sarkar</dc:creator>
      <pubDate>Mon, 13 Jul 2026 15:24:30 +0000</pubDate>
      <link>https://dev.to/dipankar_sarkar/i-threw-750-autonomous-llm-exploit-attempts-at-a-10k-sandbox-bounty-zero-escapes-3j7n</link>
      <guid>https://dev.to/dipankar_sarkar/i-threw-750-autonomous-llm-exploit-attempts-at-a-10k-sandbox-bounty-zero-escapes-3j7n</guid>
      <description>&lt;p&gt;Pydantic put up a $10,000 bounty called &lt;a href="https://hackmonty.com" rel="noopener noreferrer"&gt;Hack Monty&lt;/a&gt;: escape the sandbox of their Monty runtime. That is a clean, adversarial, unforgiving target. Either you get a sandbox escape or you do not. No partial credit, no hand-waving.&lt;/p&gt;

&lt;p&gt;I did not try to hack it by hand. I built an autonomous LLM loop to hack it, and then I rebuilt that loop four times as I learned what did and did not work. This is the honest write-up of what an agent swarm actually found, which is not a $10k escape, and why the result is still worth publishing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea
&lt;/h2&gt;

&lt;p&gt;An autonomous hacking loop is a simple pattern with hard details. An LLM proposes an exploit, a harness runs it against the target, an evaluator scores the result, and that score feeds back to steer the next attempt. Repeat for hundreds of iterations.&lt;/p&gt;

&lt;p&gt;The engine here started on the &lt;a href="https://github.com/karpathy/autoresearch" rel="noopener noreferrer"&gt;autoresearch&lt;/a&gt; pattern and evolved through four versions: from a single loop, to XBOW-style parallel swarms, and finally to a &lt;a href="https://github.com/tokenworm/tokenworm" rel="noopener noreferrer"&gt;tokenworm&lt;/a&gt; plus MCP architecture. Each version was a bet about what the bottleneck was. Early on it was idea diversity, so I went parallel. Later it was tool discipline, so I moved to MCP with a tightly-scoped boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  How V4 works
&lt;/h2&gt;

&lt;p&gt;The final architecture is three layers with a hard boundary in the middle.&lt;/p&gt;

&lt;p&gt;At the top, tokenworm is the LLM agent harness. It runs a native Ollama provider, four role-focused &lt;code&gt;SKILL.md&lt;/code&gt; files, and its own sandbox (&lt;code&gt;bwrap&lt;/code&gt; plus network). It speaks to the outside world only through an MCP client over stdio.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tokenworm (LLM agent harness)
  Provider: ollama_native
  Skills: 4 role-focused SKILL.md
  Sandbox: bwrap + network
        │  MCP Client (stdio)
        ▼
hackmonty_mcp_server.py
  17 boundary tools (run, evaluate, bandit, state)
        │
        ▼
hackmonty.com   Ollama Cloud   GitHub Issues   Filesystem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The middle layer, &lt;code&gt;hackmonty_mcp_server.py&lt;/code&gt;, is the interesting design decision. It exposes exactly 17 tools and nothing else. The agent cannot touch the target, run code, score an attempt, or read state except through those 17 tools. That boundary is the whole safety and control model. It is what lets an autonomous loop run 500 iterations without the harness itself becoming the vulnerability.&lt;/p&gt;

&lt;p&gt;Two more pieces make the loop learn rather than flail. A UCB1 bandit (&lt;code&gt;bandit.py&lt;/code&gt;) selects which attack strategy to try next, treating exploit families as arms and their scores as rewards, so the loop spends its budget on what is paying off. And an evaluator (&lt;code&gt;evaluate.py&lt;/code&gt;) scores each attempt 0 to 5 and enriches the context, so the next proposal is grounded in what the last one actually did.&lt;/p&gt;

&lt;p&gt;Running it is a shell script or the Python SDK:&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;# Production (tokenworm Zig binary), needs ollama serve running&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;USER_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-passphrase
./run.sh            &lt;span class="c"&gt;# 500 iterations&lt;/span&gt;
./run.sh 20         &lt;span class="c"&gt;# 20 iterations (test)&lt;/span&gt;
./run.sh &lt;span class="nt"&gt;-i&lt;/span&gt;         &lt;span class="c"&gt;# interactive REPL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Python SDK showcase (InlineSkill, MCP, native Ollama Cloud)&lt;/span&gt;
uv run python run.py 20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is also an SSE remote mode for the MCP server: &lt;code&gt;uv run python -B hackmonty_mcp_server.py --sse --port 8765&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The results, honestly
&lt;/h2&gt;

&lt;p&gt;Here is where I refuse to inflate anything, because the whole point of an adversarial bounty is that the target does not lie and neither should I.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;750+ exploit attempts across the four orchestrator versions.&lt;/li&gt;
&lt;li&gt;0 sandbox escapes found.&lt;/li&gt;
&lt;li&gt;1 latent unsafe bug found: a &lt;code&gt;heap_read_boxed&lt;/code&gt; provenance mismatch.&lt;/li&gt;
&lt;li&gt;43 unsafe blocks audited, 9 GHSA advisories reviewed, 6 CPython divergences documented.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the headline number is zero. The loop did not win the bounty. It threw 750 attempts at a hardened sandbox and the sandbox held.&lt;/p&gt;

&lt;p&gt;But zero escapes is itself a result. A 750-attempt autonomous red-team that finds no escape is real evidence about the target's robustness, and along the way it surfaced a genuine latent unsafe bug (the provenance mismatch went into the bounty submission) plus a paper-level, 12-section security assessment. The full write-up is in the repo's REPORT.md and SUBMISSION.md.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this approach does NOT fit
&lt;/h2&gt;

&lt;p&gt;The honest limits, and they are the actual lesson.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Autonomous loops are good at breadth, weak at depth.&lt;/strong&gt; 750 attempts sounds like a lot until you realize a real sandbox escape is often one deep, precise chain of insights, not a wide search. The loop is excellent at exploring the surface and terrible at the kind of sustained, single-thread reasoning a human exploit dev brings to one hard bug. Zero escapes across four architectures is partly a statement about the target and partly a statement about the method.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evaluation is the ceiling.&lt;/strong&gt; The loop is only as smart as &lt;code&gt;evaluate.py&lt;/code&gt;. A 0 to 5 score plus a bandit steers toward whatever the evaluator rewards, which means a blind spot in scoring is a blind spot in the whole search. Building the evaluator is harder than building the agent, and it is where most of the real engineering went.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It is bespoke to this target.&lt;/strong&gt; The 17 boundary tools, the skills, the client all encode Hack Monty. This is a case study in building an autonomous red-team, not a turnkey scanner you point at your own code tomorrow. Reuse the pattern, not the config.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost and noise are real.&lt;/strong&gt; 750 LLM-driven attempts is a lot of tokens for one latent bug and a report. Whether that trade is worth it depends entirely on the value of the target.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;The loop pattern is simple: propose, run, score, feed back. The hard parts are the evaluator and the tool boundary.&lt;/li&gt;
&lt;li&gt;A single MCP server exposing exactly 17 tools is the control model that makes 500-iteration autonomy safe to run.&lt;/li&gt;
&lt;li&gt;A UCB1 bandit plus a 0 to 5 evaluator is what turns a flailing loop into one that concentrates its budget.&lt;/li&gt;
&lt;li&gt;Result: 0 escapes in 750+ attempts, but 1 latent unsafe bug, a bounty submission, and a full assessment. Zero is still data.&lt;/li&gt;
&lt;li&gt;Autonomous agents are breadth-first. Deep single-bug exploitation is still where humans win.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/dipankar/hackmonty" rel="noopener noreferrer"&gt;https://github.com/dipankar/hackmonty&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Honest ask: if you build autonomous agent loops, read the evaluator and the bandit selection and tell me where you would have steered the search differently. The escape count is zero, so the interesting critique is about the method, not the score. Issues and discussion welcome.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
      <category>llm</category>
    </item>
    <item>
      <title>737x faster LangGraph checkpoints, and the case where Rust lost</title>
      <dc:creator>Dipankar Sarkar</dc:creator>
      <pubDate>Sat, 11 Jul 2026 15:24:04 +0000</pubDate>
      <link>https://dev.to/dipankar_sarkar/737x-faster-langgraph-checkpoints-and-the-case-where-rust-lost-2ci6</link>
      <guid>https://dev.to/dipankar_sarkar/737x-faster-langgraph-checkpoints-and-the-case-where-rust-lost-2ci6</guid>
      <description>&lt;p&gt;Run a LangGraph agent long enough and the model call stops being your bottleneck.&lt;br&gt;
The plumbing takes over. Every step, the graph serializes its state to a&lt;br&gt;
checkpoint so you can resume, replay, or recover. LangGraph does that with&lt;br&gt;
Python's &lt;code&gt;deepcopy&lt;/code&gt;. For a small dict that is fine. For a 250KB agent state with&lt;br&gt;
nested messages, tool outputs, and accumulated context, &lt;code&gt;deepcopy&lt;/code&gt; is brutally&lt;br&gt;
slow, and you pay it on every single step of a long run.&lt;/p&gt;

&lt;p&gt;So I built &lt;code&gt;fast-langgraph&lt;/code&gt;: a set of Rust accelerators for the hot paths in&lt;br&gt;
LangGraph, packaged as drop-in components that keep full API compatibility.&lt;/p&gt;
&lt;h2&gt;
  
  
  Lead with the numbers, including the ones that hurt
&lt;/h2&gt;

&lt;p&gt;Here is what the Rust paths actually buy you, measured against the Python&lt;br&gt;
equivalents:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Complex checkpoint (250KB)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;737x&lt;/strong&gt; faster than deepcopy&lt;/td&gt;
&lt;td&gt;Large agent state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complex checkpoint (35KB)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;178x&lt;/strong&gt; faster&lt;/td&gt;
&lt;td&gt;Medium state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sustained state updates&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;13-46x&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Long-running graphs, many steps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM response caching&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;10x&lt;/strong&gt; at 90% hit rate&lt;/td&gt;
&lt;td&gt;Repeated prompts, RAG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;End-to-end graph execution&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2-3x&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Production workloads with checkpointing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And the automatic mode, the one that needs zero code changes, lands around&lt;br&gt;
&lt;strong&gt;2.8x&lt;/strong&gt; for a typical invocation.&lt;/p&gt;

&lt;p&gt;Now the honest part. These are not "Rust is faster at everything" numbers. The&lt;br&gt;
checkpoint speedup scales with state size. It is a serialization story. For a&lt;br&gt;
small, flat dict, Python's built-in &lt;code&gt;dict&lt;/code&gt; is implemented in C and already fast.&lt;br&gt;
Rust does not win there, and the README says so plainly. The 737x is a large&lt;br&gt;
complex-state number, not a headline you get on a toy graph.&lt;/p&gt;
&lt;h2&gt;
  
  
  The core idea: reimplement the critical paths, keep the API
&lt;/h2&gt;

&lt;p&gt;LangGraph is good. I did not want to fork it or replace it. I wanted to swap out&lt;br&gt;
the three operations that dominate a real workload:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Checkpoint serialization.&lt;/strong&gt; &lt;code&gt;deepcopy&lt;/code&gt; on complex nested state is the single
biggest cost in a long run. Rust does a structured serialize instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State management at scale.&lt;/strong&gt; High-frequency updates accumulate overhead. A
Rust merge path handles append-heavy state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repeated LLM calls.&lt;/strong&gt; Identical prompts waste real API money. A cache in
front of the model kills the redundant calls.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything else stays Python. The Rust code is behind a compatible surface.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two ways to turn it on
&lt;/h2&gt;

&lt;p&gt;The easiest is automatic acceleration. One flag, or one function call, and your&lt;br&gt;
existing graph runs faster with no code changes:&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="c1"&gt;# At the top of your application
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;fast_langgraph&lt;/span&gt;
&lt;span class="n"&gt;fast_langgraph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;patch_langgraph&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Rest of your code unchanged - runs 2-3x faster
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langgraph.graph&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StateGraph&lt;/span&gt;
&lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or set &lt;code&gt;FAST_LANGGRAPH_AUTO_PATCH=1&lt;/code&gt; in the environment, which is the path I&lt;br&gt;
prefer in production because it needs zero code diff.&lt;/p&gt;

&lt;p&gt;For the biggest wins you reach for the components directly. The checkpointer is a&lt;br&gt;
drop-in replacement:&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;fast_langgraph&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RustSQLiteCheckpointer&lt;/span&gt;

&lt;span class="c1"&gt;# 5-6x faster than the default checkpointer
&lt;/span&gt;&lt;span class="n"&gt;checkpointer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RustSQLiteCheckpointer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkpointer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;checkpointer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM cache is a decorator, and it reports its own hit rate so you can prove the&lt;br&gt;
win instead of guessing:&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;fast_langgraph&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt;

&lt;span class="nd"&gt;@cached&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&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;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# First call: hits the API (~500ms)
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is LangGraph?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Second identical call: returns from cache (~0.01ms)
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is LangGraph?&lt;/span&gt;&lt;span class="sh"&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;call_llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache_stats&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="c1"&gt;# {'hits': 1, 'misses': 1, 'size': 1}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That cache is where the 10x number comes from, and only at a high hit rate. If&lt;br&gt;
your prompts are all unique, the cache does nothing. It is a RAG-and-retry&lt;br&gt;
optimization, not magic.&lt;/p&gt;
&lt;h2&gt;
  
  
  How it works under the hood
&lt;/h2&gt;

&lt;p&gt;The design constraint that mattered most: nobody rewrites their agent to try an&lt;br&gt;
accelerator. So the whole thing is built as compatible shims over Rust&lt;br&gt;
implementations.&lt;/p&gt;

&lt;p&gt;The shim patches LangGraph's hot functions at import time. &lt;code&gt;apply_writes&lt;/code&gt;, the&lt;br&gt;
channel batch update, gets a Rust version. The executor caching path reuses a&lt;br&gt;
&lt;code&gt;ThreadPoolExecutor&lt;/code&gt; across invocations instead of rebuilding it. Neither of those&lt;br&gt;
is a huge single win (1.2x and 2.3x respectively), but they compose into that&lt;br&gt;
~2.8x automatic number because they sit on the path every invocation walks.&lt;/p&gt;

&lt;p&gt;The manual components are where the large numbers live. &lt;code&gt;RustSQLiteCheckpointer&lt;/code&gt;&lt;br&gt;
replaces the serialize-and-persist path. &lt;code&gt;langgraph_state_update&lt;/code&gt; does the state&lt;br&gt;
merge in Rust with explicit append keys:&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;fast_langgraph&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;langgraph_state_update&lt;/span&gt;

&lt;span class="n"&gt;new_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;langgraph_state_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;current_state&lt;/span&gt;&lt;span class="p"&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;messages&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="n"&gt;new_message&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
    &lt;span class="n"&gt;append_keys&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;messages&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That merge path is the 13-46x sustained-update number for long graphs that append&lt;br&gt;
to &lt;code&gt;messages&lt;/code&gt; hundreds of times.&lt;/p&gt;

&lt;p&gt;There is also a profiler, so you can find your own bottleneck before you port&lt;br&gt;
anything:&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;fast_langgraph.profiler&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GraphProfiler&lt;/span&gt;

&lt;span class="n"&gt;profiler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GraphProfiler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;profiler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;profile_run&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;profiler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print_report&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Profile first. The wins are in specific hot paths, not "the code."&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this does NOT fit
&lt;/h2&gt;

&lt;p&gt;I would rather tell you where it loses than have you find out in production.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Small, simple state.&lt;/strong&gt; If your agent state is a flat dict of a few keys,
Python's C-implemented &lt;code&gt;dict&lt;/code&gt; already wins. The Rust checkpoint path pays off
when state is large and nested, and the speedup scales with size. On a toy
graph you will see close to nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All-unique prompts.&lt;/strong&gt; The &lt;code&gt;@cached&lt;/code&gt; decorator is a 10x win at a 90% hit rate.
If every prompt is different, the cache is dead weight. It is for repeated
prompts and RAG, not open-ended chat with high entropy inputs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You are not checkpointing.&lt;/strong&gt; Half the value is faster persistence. If your
graph runs once and exits without saving state, you skip the operation that
benefits most, and you are left with the smaller 2-3x end-to-end range.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need a specific LangGraph internal that is not patched.&lt;/strong&gt; The shim covers
the hot paths, not the entire surface. Automatic mode is conservative for a
reason, and it falls back to Python where it is not confident.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your agents are short and cheap, this is not for you. If you run long,&lt;br&gt;
stateful, checkpoint-heavy graphs in production, the serialization path is where&lt;br&gt;
your time actually goes.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;The bottleneck in a long agent run is usually the plumbing, not the model.
&lt;code&gt;deepcopy&lt;/code&gt; on big state is a real tax you pay every step.&lt;/li&gt;
&lt;li&gt;Big speedups here are a data-and-serialization story, not a language story.
737x is a 250KB-state number and it scales down with your state.&lt;/li&gt;
&lt;li&gt;Measure the small cases. Rust loses to C-backed Python dicts on flat state, and
pretending otherwise gets you caught.&lt;/li&gt;
&lt;li&gt;Make it a drop-in with automatic fallback, or it never gets adopted. One import,
or one env var, is the whole onboarding.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Works with any LangGraph version, Python 3.9+. Code, the full benchmark&lt;br&gt;
breakdown, and the architecture notes are here:&lt;br&gt;
&lt;a href="https://github.com/neul-labs/fast-langgraph" rel="noopener noreferrer"&gt;https://github.com/neul-labs/fast-langgraph&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you run stateful LangGraph agents at volume, I would love to know how big your&lt;br&gt;
checkpoint state actually gets. Run the profiler, tell me your hot path. Kick the&lt;br&gt;
tyres, issues welcome.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>python</category>
      <category>ai</category>
      <category>llm</category>
    </item>
    <item>
      <title>Why I built a C-like language for agent-written code</title>
      <dc:creator>Dipankar Sarkar</dc:creator>
      <pubDate>Thu, 09 Jul 2026 15:24:38 +0000</pubDate>
      <link>https://dev.to/dipankar_sarkar/why-i-built-a-c-like-language-for-agent-written-code-30li</link>
      <guid>https://dev.to/dipankar_sarkar/why-i-built-a-c-like-language-for-agent-written-code-30li</guid>
      <description>&lt;p&gt;Most of the code my tools write now is written by an agent and read by me.&lt;/p&gt;

&lt;p&gt;That single shift breaks a lot of assumptions baked into the languages we use. C&lt;br&gt;
trusts the author completely. It has no idea whether a function does I/O, whether&lt;br&gt;
an integer just wrapped, or whether a build script is about to run &lt;code&gt;curl | sh&lt;/code&gt; on&lt;br&gt;
your laptop. When a human wrote every line, that trust was earned slowly. When an&lt;br&gt;
LLM emits 200 lines in two seconds, the trust model is wrong.&lt;/p&gt;

&lt;p&gt;So I built fastC. It is a small systems language that compiles to readable C11,&lt;br&gt;
and it is designed around one question: what does a language look like when you&lt;br&gt;
assume the author is an agent and the reviewer is a tired human?&lt;/p&gt;
&lt;h2&gt;
  
  
  The problem with C for generated code
&lt;/h2&gt;

&lt;p&gt;C's failure modes are exactly the ones an agent hits.&lt;/p&gt;

&lt;p&gt;Silent integer overflow is the classic one. Ask an open-weight model to "sum an&lt;br&gt;
array" and it will happily hand you code that wraps at scale and returns a wrong&lt;br&gt;
answer with no warning. I measured this. On a "sum 1..100000" task with GLM,&lt;br&gt;
three trials each:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go silently wrapped 3/3.&lt;/li&gt;
&lt;li&gt;Rust wrapped 2/3.&lt;/li&gt;
&lt;li&gt;fastC and Zig either refused to compile or computed correctly. Neither shipped
a silently-wrong binary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The other failure mode is ambient authority. In C, any function can open a socket&lt;br&gt;
or read a file. There is no signal in the type of a function that says "this one&lt;br&gt;
touches the network." When you are reviewing agent output, you want that signal&lt;br&gt;
badly. You want to look at a signature and know the blast radius.&lt;/p&gt;

&lt;p&gt;And then there is the build step. &lt;code&gt;build.rs&lt;/code&gt;, &lt;code&gt;build.zig&lt;/code&gt;, &lt;code&gt;cgo&lt;/code&gt; all run&lt;br&gt;
arbitrary code at build time. That is a supply-chain hole that an agent pulling&lt;br&gt;
a dependency will walk straight into. fastC ships a side-by-side demo where&lt;br&gt;
&lt;code&gt;cargo build&lt;/code&gt; executes a malicious &lt;code&gt;build.rs&lt;/code&gt;, and &lt;code&gt;fastc.toml&lt;/code&gt; rejects the same&lt;br&gt;
shape at parse time because it structurally cannot execute anything at build time.&lt;/p&gt;
&lt;h2&gt;
  
  
  The core idea: capabilities as typed arguments
&lt;/h2&gt;

&lt;p&gt;The center of fastC is that I/O capabilities are typed function arguments, not&lt;br&gt;
ambient powers.&lt;/p&gt;

&lt;p&gt;If a function wants to reach the network, it must declare a &lt;code&gt;CapNetConnect&lt;/code&gt;&lt;br&gt;
parameter. A function that does not take that parameter structurally cannot open&lt;br&gt;
a socket. The type system makes it impossible, not discouraged. The same holds&lt;br&gt;
for reading files with &lt;code&gt;CapFsRead&lt;/code&gt; and spawning processes with &lt;code&gt;CapProcSpawn&lt;/code&gt;.&lt;br&gt;
The capability set is finite and named.&lt;/p&gt;

&lt;p&gt;The capability tokens can only be minted in &lt;code&gt;main&lt;/code&gt;, through &lt;code&gt;caps::init()&lt;/code&gt;.&lt;br&gt;
Library code never fabricates them, and the compiler has a fabrication check that&lt;br&gt;
enforces exactly that. So when you read a fastC signature, the capability&lt;br&gt;
arguments are an honest, compiler-checked manifest of what the function can do.&lt;/p&gt;

&lt;p&gt;That changes code review. A pure transform has no cap arguments, and you can&lt;br&gt;
trust that claim without reading the body. That is the whole wedge for reviewing&lt;br&gt;
code you did not write.&lt;/p&gt;
&lt;h2&gt;
  
  
  Hello world, and how it differs from C
&lt;/h2&gt;

&lt;p&gt;Here is the smallest program.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn main() -&amp;gt; i32 {
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compile it and run it through a normal C compiler:&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="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'fn main() -&amp;gt; i32 { return 0; }'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; hello.fc
fastc compile hello.fc &lt;span class="nt"&gt;-o&lt;/span&gt; hello.c
cc hello.c &lt;span class="nt"&gt;-o&lt;/span&gt; hello &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./hello &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;fastC emits C11. That is deliberate. It means any C cross-compiler in the world&lt;br&gt;
can target a fastC binary. The stripped hello binary is 53 KB, in the same class&lt;br&gt;
as C and Zig, not the 342 KB of a Rust hello or the 2.4 MB of a Go one. A fastC&lt;br&gt;
binary that does real work fits inside a container cold-start budget and inside&lt;br&gt;
embedded flash. That is a real constraint for a lot of the systems I care about.&lt;/p&gt;

&lt;p&gt;The differences from C that matter day to day:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Memory safety without a garbage collector.&lt;/li&gt;
&lt;li&gt;Capability-typed I/O, as above.&lt;/li&gt;
&lt;li&gt;Mandatory pre- and postconditions on public APIs, &lt;code&gt;@requires&lt;/code&gt; and &lt;code&gt;@ensures&lt;/code&gt;,
discharged through a three-tier pipeline: a syntactic checker that is always
on, an opt-in Z3 tier for linear-integer tautologies, and a runtime fallback.
Proven obligations cost zero at runtime.&lt;/li&gt;
&lt;li&gt;Vendored, content-hashed dependencies with no central registry and no
executable build steps.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Contracts, concretely
&lt;/h2&gt;

&lt;p&gt;Contracts are not a bolt-on. They are compile-time obligations on public APIs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fastc compile &lt;span class="nt"&gt;--prove&lt;/span&gt; program.fc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That runs every &lt;code&gt;@requires&lt;/code&gt; and &lt;code&gt;@ensures&lt;/code&gt; clause through the three tiers. The&lt;br&gt;
syntactic discharger constant-folds and catches tautological comparisons with no&lt;br&gt;
Z3 dependency at all. The Z3 tier handles linear-integer cases with a 500 ms&lt;br&gt;
per-obligation budget. Anything neither tier can prove falls back to a runtime&lt;br&gt;
&lt;code&gt;fc_trap&lt;/code&gt;. The build emits a &lt;code&gt;discharge.json&lt;/code&gt; per-obligation report with&lt;br&gt;
proven/runtime/unknown counts and the tier that handled each one. If Z3 is not on&lt;br&gt;
your PATH, the obligation degrades to the runtime tier with a structured reason.&lt;br&gt;
The build does not break.&lt;/p&gt;

&lt;p&gt;The point is that "prove what you can, trap the rest, never silently drop a&lt;br&gt;
check" is the right default when you cannot assume the author reasoned carefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest numbers
&lt;/h2&gt;

&lt;p&gt;fastC compile time is around 30 to 40 percent faster than Rust to a release&lt;br&gt;
binary. Runtime matches C on floating-point-heavy work. It is about 26 percent&lt;br&gt;
slower on recursive integer work like fib(40) because of overflow-check cost.&lt;br&gt;
That is the trade. You pay a little on integer-heavy recursion to never ship a&lt;br&gt;
silent wrap.&lt;/p&gt;

&lt;p&gt;On agent first-compile success for a &lt;code&gt;sum_array&lt;/code&gt; task across four open-weight&lt;br&gt;
models, fastC scored 12/12, matching or beating C, Rust, Zig, and Go. But there&lt;br&gt;
is a sharp caveat in that number, and it leads into the trade-offs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where fastC does not fit
&lt;/h2&gt;

&lt;p&gt;This is the part that matters most, so I will be blunt.&lt;/p&gt;

&lt;p&gt;The agent success number is only 12/12 when the cheatsheet shipped with the&lt;br&gt;
prompt is faithful. An earlier run against an inaccurate cheatsheet scored 0/9.&lt;br&gt;
The language is new, so models do not know it from training. You have to feed&lt;br&gt;
them an accurate worked example and a "common mistakes" guide. Without that&lt;br&gt;
scaffolding, an agent flounders, because there is no Stack Overflow corpus for&lt;br&gt;
fastC yet. That is a real adoption cost.&lt;/p&gt;

&lt;p&gt;fastC also loses on ecosystem. Rust has 150K crates. fastC has an eleven-package&lt;br&gt;
core set in preview. If your project needs a mature library for something&lt;br&gt;
specific, you will be writing FFI bindings or doing without.&lt;/p&gt;

&lt;p&gt;The contract and capability model is friction. If your team ships internal&lt;br&gt;
scripts fast and does not review agent output carefully, all of this ceremony is&lt;br&gt;
overhead you will resent. fastC pays off precisely when the review burden is real&lt;br&gt;
and the blast radius matters.&lt;/p&gt;

&lt;p&gt;And on raw single-threaded integer throughput, plain C at -O2 is still ahead. If&lt;br&gt;
that 26 percent is your hot path and you trust your authors, use C.&lt;/p&gt;

&lt;p&gt;The honest framing, including which rows fastC loses on, is in the repo's&lt;br&gt;
MANIFESTO.md. I would rather you read that than my pitch.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Assume the author is an agent, and the language design changes. You want I/O in
the type signature and you want overflow to be loud.&lt;/li&gt;
&lt;li&gt;Compiling to readable C11 buys you portability and small binaries for free.&lt;/li&gt;
&lt;li&gt;"Prove what you can, trap the rest" beats both "assume it is fine" and "block
the build."&lt;/li&gt;
&lt;li&gt;New languages have no training corpus. That is a first-class adoption problem,
not a footnote.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;fastC is v1.0 feature-complete with 337+ passing tests. It is MIT licensed.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/fastc-lang/fastc" rel="noopener noreferrer"&gt;https://github.com/fastc-lang/fastc&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The honest ask: clone it, run the supply-chain demo and the cross-lang&lt;br&gt;
benchmarks, and tell me where the capability model gets in your way. The failure&lt;br&gt;
reports are more useful to me than the stars. Issues welcome.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ai</category>
      <category>programming</category>
      <category>compilers</category>
    </item>
    <item>
      <title>Learning how LLMs actually work by building 18 of them in Zig</title>
      <dc:creator>Dipankar Sarkar</dc:creator>
      <pubDate>Tue, 07 Jul 2026 10:27:17 +0000</pubDate>
      <link>https://dev.to/dipankar_sarkar/learning-how-llms-actually-work-by-building-18-of-them-in-zig-53cl</link>
      <guid>https://dev.to/dipankar_sarkar/learning-how-llms-actually-work-by-building-18-of-them-in-zig-53cl</guid>
      <description>&lt;p&gt;Most people who ship features on top of LLMs have never seen the inside of one. I&lt;br&gt;
was in that group. I could call an API, tune a prompt, quantize a GGUF file, and&lt;br&gt;
still not tell you what RMSNorm does or why RoPE exists. The model was a black box&lt;br&gt;
with a temperature knob.&lt;/p&gt;

&lt;p&gt;The tutorials did not help. They either hand you a 40-line PyTorch script that hides&lt;br&gt;
everything behind &lt;code&gt;nn.Linear&lt;/code&gt;, or they drop you into a production C++ engine where&lt;br&gt;
the math is buried under kernel dispatch and memory pools. Neither one lets you see&lt;br&gt;
the whole path from a tensor to a token.&lt;/p&gt;

&lt;p&gt;So I built &lt;code&gt;zigllm&lt;/code&gt;: an educational implementation of transformer architectures in&lt;br&gt;
Zig, from raw tensor operations all the way up to text generation. It implements 18&lt;br&gt;
model families (LLaMA, Mistral, GPT-2, Falcon, Mamba, BERT and more) and ships 285+&lt;br&gt;
tests that double as executable documentation. Every component is written to teach&lt;br&gt;
&lt;em&gt;why&lt;/em&gt; it works, not just to run fast.&lt;/p&gt;
&lt;h2&gt;
  
  
  The core idea: six layers, each built on the last
&lt;/h2&gt;

&lt;p&gt;The whole project is organized as a stack. You start at the bottom and work up, and&lt;br&gt;
each layer only depends on the ones below it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 6. Inference         Text generation, sampling, KV caching, streaming
 5. Models            LLaMA, GPT-2, Mistral, Falcon, GGUF loading, tokenization
 4. Transformers      Multi-head attention, feed-forward networks, full blocks
 3. Neural Primitives Activations (SwiGLU, GELU), normalization (RMSNorm), RoPE
 2. Linear Algebra    SIMD matrix ops, K-quantization, IQ-quantization (18+ formats)
 1. Foundation        Tensors, memory management, memory mapping
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That ordering is the pedagogy. You cannot understand attention until you understand&lt;br&gt;
matrix multiplication. You cannot understand a full LLaMA block until you understand&lt;br&gt;
attention plus normalization plus RoPE. By the time you reach layer 6 and generate&lt;br&gt;
your first token, nothing above you is magic, because you built every layer under it.&lt;/p&gt;

&lt;p&gt;The 18 architectures on top of that stack cover roughly 80% of real-world LLM usage.&lt;br&gt;
The point is not to run all of them in production. In practice LLaMA is the one path&lt;br&gt;
wired end to end for real inference; the other families are there to read and&lt;br&gt;
compare, which is exactly the point. You get to see that LLaMA, Mistral, Falcon and&lt;br&gt;
GPT-2 are mostly the same machine with different knobs, and that Mamba and BERT are&lt;br&gt;
genuinely different animals.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Zig, and how the engineering holds up
&lt;/h2&gt;

&lt;p&gt;Zig is an odd choice until you sit with it. It gives you manual memory control,&lt;br&gt;
comptime generics, and first-class SIMD, all with no runtime and no garbage&lt;br&gt;
collector. That combination is exactly what inference wants. You get deterministic&lt;br&gt;
memory (which matters a lot when you are learning, because you can see every&lt;br&gt;
allocation) and you get SIMD intrinsics without dropping into inline assembly.&lt;/p&gt;

&lt;p&gt;The optimizations in the repo are the real ones you find in production engines,&lt;br&gt;
just written to be readable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;KV caching, so you do not recompute attention over the whole sequence for every
new token.&lt;/li&gt;
&lt;li&gt;SIMD acceleration (AVX, AVX2, NEON auto-detection), for a 3-5x speedup on matrix
ops.&lt;/li&gt;
&lt;li&gt;18+ quantization formats, up to 95% memory reduction.&lt;/li&gt;
&lt;li&gt;Memory-mapped model loading, so a large GGUF file does not have to be read into
RAM up front.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of those is a lesson. KV caching teaches you why generation is quadratic&lt;br&gt;
without it. Quantization teaches you the trade between memory and precision in a way&lt;br&gt;
no blog post ever made click for me.&lt;/p&gt;
&lt;h2&gt;
  
  
  What using it looks like
&lt;/h2&gt;

&lt;p&gt;Getting started is one clone and one command, because the tests &lt;em&gt;are&lt;/em&gt; the tutorial:&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/cognisoc/zigllm.git
&lt;span class="nb"&gt;cd &lt;/span&gt;zigllm
zig build &lt;span class="nb"&gt;test&lt;/span&gt;                    &lt;span class="c"&gt;# All 285+ tests&lt;/span&gt;
zig build test-foundation         &lt;span class="c"&gt;# Foundation layer only&lt;/span&gt;
zig build test-linear-algebra     &lt;span class="c"&gt;# Linear algebra layer only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running a single layer's tests is how you study that layer. Each test demonstrates a&lt;br&gt;
concept and validates the math, so a failing assertion is a lesson about what the&lt;br&gt;
math actually requires.&lt;/p&gt;

&lt;p&gt;The examples are the guided tour, and the inference layer's own docs&lt;br&gt;
(&lt;code&gt;docs/06-inference/&lt;/code&gt;) walk the per-token cost down as each optimization comes on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Without optimizations:  ~200ms/token   (naive)
With KV caching:         ~50ms/token    (4x)
With all optimizations:  ~5ms/token     (40x total)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole value of building it yourself. The number is not a claim in a&lt;br&gt;
README, it is something you can reproduce and then explain: KV caching alone is the&lt;br&gt;
4x, and the rest comes from SIMD and quantization stacked on top.&lt;/p&gt;

&lt;p&gt;The same docs are honest about memory, and this is worth internalizing. For a&lt;br&gt;
7B-class model in Q4 the parameters are ~3.5GB, but the KV cache for a 2k context&lt;br&gt;
adds ~1.5GB per sequence, so the real floor is ~8GB, not 3.5GB. The parameter count&lt;br&gt;
is the number people quote; the working set is the number that OOMs your box. Seeing&lt;br&gt;
both fall out of code you wrote is the point.&lt;/p&gt;

&lt;p&gt;There are more examples in &lt;code&gt;examples/&lt;/code&gt;: &lt;code&gt;simple_demo.zig&lt;/code&gt; for the overview,&lt;br&gt;
&lt;code&gt;gguf_demo.zig&lt;/code&gt; for loading a real pre-trained model, and&lt;br&gt;
&lt;code&gt;model_architectures_demo.zig&lt;/code&gt; for comparing all 18 families side by side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this does NOT fit
&lt;/h2&gt;

&lt;p&gt;This is the part that matters, so I will be blunt.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;zigllm&lt;/code&gt; is not a production inference engine, and you should not deploy it as one.&lt;br&gt;
Its own parity analysis rates it around 40% of llama.cpp on production concerns:&lt;br&gt;
quantization coverage is a fraction of what llama.cpp offers, hardware acceleration&lt;br&gt;
is CPU-focused, and broad multi-model serving is not the goal. If you need to serve&lt;br&gt;
a 70B model to real users, use llama.cpp or vLLM. That is what they are for.&lt;/p&gt;

&lt;p&gt;It is also not the fastest thing you can write in Zig. The README is explicit that&lt;br&gt;
educational clarity takes priority over micro-optimization. When there was a choice&lt;br&gt;
between a readable loop and a clever one, the readable one won. That is correct for&lt;br&gt;
a teaching project and wrong for a serving one.&lt;/p&gt;

&lt;p&gt;And if you do not want to learn the internals, this buys you nothing over just&lt;br&gt;
running Ollama. There is no shortcut here. The value is entirely in reading the code&lt;br&gt;
and running the tests. If you skip that, you skip the whole thing.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;The fastest way to stop treating LLMs as magic is to build one from tensors up,
in a language that does not hide memory from you.&lt;/li&gt;
&lt;li&gt;A progressive layer stack (foundation, linear algebra, primitives, transformers,
models, inference) is a better teaching order than any single top-down script.&lt;/li&gt;
&lt;li&gt;Tests as executable documentation is underrated. 285+ tests that each prove one
concept beat any amount of prose.&lt;/li&gt;
&lt;li&gt;Zig turns out to be a genuinely good fit for this: comptime, SIMD, and manual
memory, no runtime in the way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code, the docs for all six layers, and every example are here:&lt;br&gt;
&lt;a href="https://github.com/cognisoc/zigllm" rel="noopener noreferrer"&gt;https://github.com/cognisoc/zigllm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have wanted to actually understand attention and KV caching instead of just&lt;br&gt;
using them, clone it and run &lt;code&gt;zig build test-transformers&lt;/code&gt;. If a layer's explanation&lt;br&gt;
is unclear, that is a bug in the teaching, and I would genuinely like the issue.&lt;br&gt;
Kick the tyres, PRs that improve educational clarity are the most welcome kind.&lt;/p&gt;

</description>
      <category>zig</category>
      <category>ai</category>
      <category>llm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Sandboxing untrusted agent code with gVisor costs ~200ms per cold start. Blocking syscalls instead of emulating them costs ~8ms</title>
      <dc:creator>Dipankar Sarkar</dc:creator>
      <pubDate>Sun, 05 Jul 2026 16:24:18 +0000</pubDate>
      <link>https://dev.to/dipankar_sarkar/sandboxing-untrusted-agent-code-with-gvisor-costs-200ms-per-cold-start-blocking-syscalls-instead-2500</link>
      <guid>https://dev.to/dipankar_sarkar/sandboxing-untrusted-agent-code-with-gvisor-costs-200ms-per-cold-start-blocking-syscalls-instead-2500</guid>
      <description>&lt;p&gt;You are running code you did not write. It might be an AI agent executing an&lt;br&gt;
LLM's output, a CI job running &lt;code&gt;npm install&lt;/code&gt; across dependencies nobody audited,&lt;br&gt;
or a plugin that insists it needs shell access. A normal container does almost&lt;br&gt;
nothing for you here. It is namespaces and cgroups, and the full kernel attack&lt;br&gt;
surface is still sitting right there. Every &lt;code&gt;runc&lt;/code&gt; escape CVE is the reminder.&lt;/p&gt;

&lt;p&gt;The usual heavy answer is gVisor. It puts a userspace kernel in front of the&lt;br&gt;
workload and emulates syscalls. It works. It also costs you 5-250x syscall&lt;br&gt;
overhead, roughly 200ms cold starts, and about 50MB per container. For a&lt;br&gt;
high-throughput API or a serverless function, that overhead is the whole budget.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;zviz&lt;/code&gt; takes a different route. It is an OCI-compatible container runtime&lt;br&gt;
written in Zig that uses selective denial instead of emulation. Most syscalls&lt;br&gt;
reach the host kernel at native speed. A small set of dangerous ones are blocked&lt;br&gt;
before any kernel code runs. One is argument-filtered inline. No userspace&lt;br&gt;
kernel, no emulation, no daemon.&lt;/p&gt;
&lt;h2&gt;
  
  
  The core idea: allow, deny, broker
&lt;/h2&gt;

&lt;p&gt;gVisor's model is that every syscall goes through its Sentry process, which&lt;br&gt;
emulates it. zviz's model is a filter that makes one of three decisions per&lt;br&gt;
syscall:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gVisor:  App -&amp;gt; Sentry (emulates ~300 syscalls) -&amp;gt; Host kernel (~70 syscalls)
zviz:    App -&amp;gt; BPF filter -&amp;gt; ALLOW (native speed) / DENY (EPERM) / BROKER (mediated)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Allowed syscalls hit the kernel directly, so they run at native speed. Dangerous&lt;br&gt;
ones are denied immediately with EPERM, so exploit code fails on the spot rather&lt;br&gt;
than being safely emulated. A tiny set gets routed to a userspace broker for&lt;br&gt;
inspection. The &lt;code&gt;socket&lt;/code&gt; syscall is filtered on its arguments inline.&lt;/p&gt;

&lt;p&gt;The philosophical difference matters for compatibility. gVisor emulates a&lt;br&gt;
dangerous syscall safely. zviz refuses it. Both isolate, but the failure modes&lt;br&gt;
are opposite: emulation stays compatible, denial stays strict and fast.&lt;/p&gt;
&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;The enforcement is five layers, applied in a specific order, and the order is&lt;br&gt;
the interesting part:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Namespaces (user, pid, mount, ipc, uts)&lt;/td&gt;
&lt;td&gt;Resource isolation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Capabilities (all 41 dropped)&lt;/td&gt;
&lt;td&gt;Privilege elimination&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Landlock LSM&lt;/td&gt;
&lt;td&gt;Filesystem access control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Seccomp-BPF (124 instructions)&lt;/td&gt;
&lt;td&gt;Syscall filtering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;cgroups v2&lt;/td&gt;
&lt;td&gt;Resource limits&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Capabilities drop before seccomp loads, and Landlock applies before seccomp,&lt;br&gt;
so the security setup syscalls do not get caught by the very filter they are&lt;br&gt;
installing. Get that ordering wrong and the runtime blocks itself while arming.&lt;br&gt;
The default profile drops all 41 Linux capabilities, applies a Landlock ruleset,&lt;br&gt;
mounts &lt;code&gt;/proc&lt;/code&gt;, &lt;code&gt;/sys&lt;/code&gt;, and &lt;code&gt;/dev&lt;/code&gt; privately, and runs the workload as PID 1 of a&lt;br&gt;
fresh user, PID, mount, IPC, and UTS namespace.&lt;/p&gt;

&lt;p&gt;The whole seccomp policy is 124 BPF instructions. That is small enough to audit&lt;br&gt;
by hand, which is a real security property. You can read the exact filter that&lt;br&gt;
stands between untrusted code and your kernel.&lt;/p&gt;
&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;

&lt;p&gt;You build with Zig 0.15.0+ on Linux 5.13+ (5.13 is where Landlock landed), then&lt;br&gt;
run any OCI bundle. The README's example extracts a Redis image and runs it:&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/Skelf-Research/zviz.git
&lt;span class="nb"&gt;cd &lt;/span&gt;zviz &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; zig build &lt;span class="nt"&gt;-Doptimize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ReleaseSafe

&lt;span class="c"&gt;# Build an OCI bundle from any Docker image&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/zviz-bundle/rootfs
docker create &lt;span class="nt"&gt;--name&lt;/span&gt; extract redis:alpine
docker &lt;span class="nb"&gt;export &lt;/span&gt;extract | &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; ~/zviz-bundle/rootfs &lt;span class="nt"&gt;-xf&lt;/span&gt; -
docker &lt;span class="nb"&gt;rm &lt;/span&gt;extract

&lt;span class="c"&gt;# Run it, verbose logs every blocked syscall&lt;/span&gt;
./zig-out/bin/zviz run my-container ~/zviz-bundle &lt;span class="nt"&gt;--verbose&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--verbose&lt;/code&gt; flag logs every blocked syscall, which is exactly what you need&lt;br&gt;
when an agent workload hits a restriction you did not expect. There are built-in&lt;br&gt;
profiles for the common cases: &lt;code&gt;ci-runner&lt;/code&gt; (the balanced default), &lt;code&gt;web-server&lt;/code&gt;&lt;br&gt;
(network allowed), &lt;code&gt;batch-job&lt;/code&gt; (no network, 8G memory), &lt;code&gt;hostile-tenant&lt;/code&gt;&lt;br&gt;
(maximum restrictions), and &lt;code&gt;development&lt;/code&gt; (allows ptrace, explicitly not for&lt;br&gt;
production).&lt;/p&gt;

&lt;p&gt;zviz auto-mounts the pseudo-filesystems so you do not have to declare them:&lt;br&gt;
&lt;code&gt;/proc&lt;/code&gt; as procfs with nosuid, nodev, noexec, &lt;code&gt;/sys&lt;/code&gt; as read-only sysfs, and&lt;br&gt;
&lt;code&gt;/dev&lt;/code&gt; as a private tmpfs with the standard device nodes bind-mounted in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;p&gt;The README reports these, tested against real escape techniques:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;zviz&lt;/th&gt;
&lt;th&gt;gVisor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Escape tests blocked&lt;/td&gt;
&lt;td&gt;19/19 (100%)&lt;/td&gt;
&lt;td&gt;11/19 (58%)*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cold start&lt;/td&gt;
&lt;td&gt;~8ms&lt;/td&gt;
&lt;td&gt;~200ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory per container&lt;/td&gt;
&lt;td&gt;~2MB&lt;/td&gt;
&lt;td&gt;~50MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Policy compatibility&lt;/td&gt;
&lt;td&gt;98.2%&lt;/td&gt;
&lt;td&gt;baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Syscall latency is where selective denial pays off. &lt;code&gt;clock_gettime&lt;/code&gt; is 20ns on&lt;br&gt;
zviz versus 4,982ns on gVisor, a 249x gap, because zviz lets it hit the kernel&lt;br&gt;
directly while gVisor routes it through Sentry. &lt;code&gt;read&lt;/code&gt; is 20.7x faster, &lt;code&gt;getpid&lt;/code&gt;&lt;br&gt;
4.1x. The asterisk on the escape numbers is important and the README is honest&lt;br&gt;
about it: gVisor "allows" some syscalls like ptrace and mount but emulates them&lt;br&gt;
safely, which is a different philosophy with an equivalent security outcome for&lt;br&gt;
those operations, not a straight loss.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it does not fit
&lt;/h2&gt;

&lt;p&gt;The README has a whole "when to use gVisor instead" section, which is the right&lt;br&gt;
instinct.&lt;/p&gt;

&lt;p&gt;If your workload needs &lt;code&gt;ptrace&lt;/code&gt;, zviz blocks it. strace, debuggers, and anything&lt;br&gt;
that traces another process will not run. gVisor emulates it safely, so for&lt;br&gt;
debugging-heavy workloads gVisor wins. If you need &lt;code&gt;mount&lt;/code&gt; or &lt;code&gt;unshare&lt;/code&gt; for&lt;br&gt;
Docker-in-Docker, or you run Bazel or Nix builds that create their own internal&lt;br&gt;
namespaces, zviz denies the syscalls those need. Nested containerization is a&lt;br&gt;
gVisor job.&lt;/p&gt;

&lt;p&gt;The 1.8% policy gap is a deliberate choice: zviz defaults network egress to&lt;br&gt;
deny, gVisor allows it. That is stricter, but it means a workload that expects&lt;br&gt;
outbound network fails closed until you pick a profile that opens it. On Ubuntu&lt;br&gt;
24.04+ there is an extra setup step, because the kernel's&lt;br&gt;
&lt;code&gt;apparmor_restrict_unprivileged_userns&lt;/code&gt; sysctl blocks the bind mount&lt;br&gt;
&lt;code&gt;pivot_root&lt;/code&gt; needs. Without installing the provided AppArmor profile, zviz falls&lt;br&gt;
back to chdir-only filesystem isolation, which is weaker. And it is Linux-only,&lt;br&gt;
kernel 5.13+, cgroups v2 required. There is no macOS or Windows story.&lt;/p&gt;

&lt;p&gt;The honest framing from the README: if you need nested containers or process&lt;br&gt;
tracing, use gVisor. Otherwise zviz is faster and stricter.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Selective denial beats emulation on speed because allowed syscalls hit the
kernel at native speed. That is the 249x &lt;code&gt;clock_gettime&lt;/code&gt; gap.&lt;/li&gt;
&lt;li&gt;Denial also fails safer for exploits. Malicious code gets EPERM immediately
rather than a safely-emulated success.&lt;/li&gt;
&lt;li&gt;Layer ordering is load-bearing. Capabilities and Landlock go before seccomp so
the runtime does not block its own setup.&lt;/li&gt;
&lt;li&gt;The cost is compatibility. No ptrace, no nested containers, no Bazel/Nix
internal sandboxing. That is the trade for ~8ms cold starts and a 124-
instruction filter you can actually read.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you run untrusted or agent-generated code and your workloads do not need&lt;br&gt;
ptrace or nested containers, zviz is worth a benchmark against your current&lt;br&gt;
sandbox. Code, the threat model, and the comparison docs are here:&lt;br&gt;
&lt;a href="https://github.com/Skelf-Research/zviz" rel="noopener noreferrer"&gt;https://github.com/Skelf-Research/zviz&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am curious which blocked syscall trips up the first real agent workload you&lt;br&gt;
throw at it. Run it with &lt;code&gt;--verbose&lt;/code&gt; and open an issue with the trace.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
    <item>
      <title>We let an AI agent hit a database 1034 times. Text-to-SQL ran 23 unsafe ops. The policy layer ran zero</title>
      <dc:creator>Dipankar Sarkar</dc:creator>
      <pubDate>Thu, 02 Jul 2026 16:23:54 +0000</pubDate>
      <link>https://dev.to/dipankar_sarkar/we-let-an-ai-agent-hit-a-database-1034-times-text-to-sql-ran-23-unsafe-ops-the-policy-layer-ran-3nfc</link>
      <guid>https://dev.to/dipankar_sarkar/we-let-an-ai-agent-hit-a-database-1034-times-text-to-sql-ran-23-unsafe-ops-the-policy-layer-ran-3nfc</guid>
      <description>&lt;p&gt;The moment you give an AI agent database access, you inherit every question a junior&lt;br&gt;
engineer with production credentials raises, except the agent does not get tired and&lt;br&gt;
does not ask permission.&lt;/p&gt;

&lt;p&gt;What if it reads a column full of PII. What if it writes a query that scans the whole&lt;br&gt;
table and bills you for it. What if a user tricks it, through prompt injection, into&lt;br&gt;
deleting rows. How do you prove, after the fact, what it actually did.&lt;/p&gt;

&lt;p&gt;The common answer is text-to-SQL: let the model write SQL, run it. That is exactly&lt;br&gt;
the wrong layer to enforce safety, because by the time you have a SQL string you have&lt;br&gt;
already lost. We benchmarked it against the Spider dataset, 1034 natural language&lt;br&gt;
queries. Text-to-SQL executed 23 unsafe operations and left no audit trail. SQL&lt;br&gt;
injection was possible.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ormai&lt;/code&gt; is a different bet. Enforce at the ORM layer, not the prompt layer. On the&lt;br&gt;
same 1034 queries it executed 0 unsafe operations, SQL injection was not possible,&lt;br&gt;
and every call was logged.&lt;/p&gt;
&lt;h2&gt;
  
  
  The core idea: agents get typed tools, not a SQL prompt
&lt;/h2&gt;

&lt;p&gt;OrmAI wraps your existing ORM models in a policy-enforced runtime. The agent never&lt;br&gt;
sees or writes raw SQL. Instead it gets a small set of typed tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read-safe: &lt;code&gt;db.query&lt;/code&gt;, &lt;code&gt;db.get&lt;/code&gt;, &lt;code&gt;db.aggregate&lt;/code&gt;, &lt;code&gt;db.describe_schema&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Write-safe: &lt;code&gt;db.create&lt;/code&gt;, &lt;code&gt;db.update&lt;/code&gt;, &lt;code&gt;db.delete&lt;/code&gt;, &lt;code&gt;db.bulk_update&lt;/code&gt;, each gated by
policy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every request the agent makes is compiled into a parameterized ORM query. The&lt;br&gt;
database never receives an agent-authored SQL string. That single architectural choice&lt;br&gt;
is what closes the SQL injection hole: there is no string to inject into.&lt;/p&gt;
&lt;h2&gt;
  
  
  How it works: a runtime between the agent and the ORM
&lt;/h2&gt;

&lt;p&gt;OrmAI sits as a layer between the agent and your ORM. A request flows through three&lt;br&gt;
enforcement stages before it ever reaches the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your Agent
   | calls a typed tool
OrmAI Runtime
   [ Policy Enforcer ] [ Audit Logger ] [ Tenant Scope Filter ]
   | parameterized queries only
Your ORM (SQLAlchemy / Prisma / Drizzle / ...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;policy enforcer&lt;/strong&gt; decides what is allowed: which models, which fields, which&lt;br&gt;
operations, and how much. Field-level policies hide passwords and mask emails&lt;br&gt;
automatically. Query budgets cap rows, include depth, and statement timeouts so a&lt;br&gt;
runaway query cannot run away. Writes are off unless you explicitly enable them, and&lt;br&gt;
you can require a reason or human approval for sensitive models.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;tenant scope filter&lt;/strong&gt; auto-injects a tenant predicate into every query.&lt;br&gt;
&lt;code&gt;.tenantScope('tenant_id')&lt;/code&gt; means a request in tenant A's context physically cannot&lt;br&gt;
return tenant B's rows, whether or not the agent remembered to filter. Isolation is&lt;br&gt;
built in, not bolted on and not left to the prompt.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;audit logger&lt;/strong&gt; records every call with the principal, tenant, trace ID, input,&lt;br&gt;
and output. When someone asks "what did the agent do", you have the answer.&lt;/p&gt;

&lt;p&gt;You describe all of this with a &lt;code&gt;PolicyBuilder&lt;/code&gt;, which reads like the security review&lt;br&gt;
you would want to write anyway:&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;ormai.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PolicyBuilder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DEFAULT_PROD&lt;/span&gt;

&lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;PolicyBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_PROD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register_models&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deny_fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*password*&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;*secret*&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;*token*&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;mask_fields&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&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;phone&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;tenant_scope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tenant_id&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;enable_writes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&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;Order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;require_reason&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="nf"&gt;build&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;Presets ship for the common postures: &lt;code&gt;DEFAULT_DEV&lt;/code&gt; is permissive, &lt;code&gt;DEFAULT_INTERNAL&lt;/code&gt;&lt;br&gt;
is moderate, &lt;code&gt;DEFAULT_PROD&lt;/code&gt; is strict. You start from one and tighten.&lt;/p&gt;
&lt;h2&gt;
  
  
  A concrete wiring
&lt;/h2&gt;

&lt;p&gt;The Python path is deliberately short. Point it at your existing SQLAlchemy engine and&lt;br&gt;
session, hand it a policy, and you get a toolset the agent can use:&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;ormai.quickstart&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mount_sqlalchemy&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ormai.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DEFAULT_DEV&lt;/span&gt;

&lt;span class="c1"&gt;# your existing SQLAlchemy models + session
&lt;/span&gt;&lt;span class="n"&gt;toolset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mount_sqlalchemy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;session_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_DEV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# done. the agent now has db.query, db.get, db.aggregate, db.describe_schema
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is not Python-only. There is a TypeScript/Node.js implementation too, with the same&lt;br&gt;
policy model expressed through the same builder pattern, and it plugs into Vercel AI&lt;br&gt;
SDK, LangChain.js, and MCP among others. On the ORM side the coverage is wide: on&lt;br&gt;
Python it supports SQLAlchemy, SQLModel, Django ORM, Tortoise, and Peewee; on&lt;br&gt;
TypeScript it supports Prisma, Drizzle, and TypeORM. Schema introspection is automatic,&lt;br&gt;
so it discovers your models, fields, relations, and primary keys rather than making&lt;br&gt;
you redeclare them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it does not fit
&lt;/h2&gt;

&lt;p&gt;The mandatory honesty section.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It constrains what your agent can do, on purpose.&lt;/strong&gt; OrmAI is about safe, typed,&lt;br&gt;
policy-bounded access. If your use case genuinely needs arbitrary analytical SQL,&lt;br&gt;
window functions, recursive CTEs, hand-tuned joins, the typed tool surface will feel&lt;br&gt;
like a cage. That is the trade you are buying: expressiveness for safety.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The policy is only as good as you write it.&lt;/strong&gt; OrmAI enforces your rules. It does&lt;br&gt;
not guess which fields are sensitive. Ship a policy that forgets to deny a secret&lt;br&gt;
column and the runtime will faithfully expose it. The presets help, but the review&lt;br&gt;
is still yours.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It is another layer in the request path.&lt;/strong&gt; Requests compile through the runtime&lt;br&gt;
before hitting the ORM. For most agent workloads that overhead is irrelevant next to&lt;br&gt;
the model call, but if you are chasing raw database latency on a hot path, measure&lt;br&gt;
it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It assumes you use one of the supported ORMs.&lt;/strong&gt; The value comes from wrapping an&lt;br&gt;
existing ORM. If your data access is hand-rolled SQL or an unsupported ORM, there is&lt;br&gt;
nothing for OrmAI to wrap yet.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It reduces risk, it does not eliminate it.&lt;/strong&gt; Zero unsafe ops on the Spider&lt;br&gt;
benchmark is a strong signal, not a proof for every schema and every prompt. Treat&lt;br&gt;
it as defense in depth, keep your database-level permissions tight anyway.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Safety belongs at the ORM layer, not the prompt layer. Once you have a SQL string
from a model, you have already lost the injection fight.&lt;/li&gt;
&lt;li&gt;Typed tools plus parameterized queries is what took SQL injection off the table in
the 1034-query benchmark, not a cleverer prompt.&lt;/li&gt;
&lt;li&gt;Field masking, tenant scoping, and query budgets are the three controls that turn
"the agent can read the database" into "the agent can read exactly what policy
allows".&lt;/li&gt;
&lt;li&gt;Audit everything. The question "what did the agent do" only has a good answer if you
logged the principal, tenant, and trace on every call.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Code, the Spider benchmark demo, and the policy docs are here:&lt;br&gt;
&lt;a href="https://github.com/neul-labs/ormai" rel="noopener noreferrer"&gt;https://github.com/neul-labs/ormai&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have wired an agent to a production database, I want to know how you are&lt;br&gt;
scoping tenants today. Kick the tyres, issues welcome.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>llm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I put a Rust layer under LiteLLM. Here is where it actually helped (and where it did not)</title>
      <dc:creator>Dipankar Sarkar</dc:creator>
      <pubDate>Wed, 01 Jul 2026 22:42:18 +0000</pubDate>
      <link>https://dev.to/dipankar_sarkar/i-put-a-rust-layer-under-litellm-here-is-where-it-actually-helped-and-where-it-did-not-4pja</link>
      <guid>https://dev.to/dipankar_sarkar/i-put-a-rust-layer-under-litellm-here-is-where-it-actually-helped-and-where-it-did-not-4pja</guid>
      <description>&lt;p&gt;LiteLLM is the glue a lot of us reach for when an app has to talk to more than one&lt;br&gt;
model provider. One interface, dozens of backends. It is great. But once you run it&lt;br&gt;
under real load, the hot path stops being the model call and starts being the&lt;br&gt;
plumbing around it: connection pooling, rate limiting, token counting on big&lt;br&gt;
inputs. That plumbing is pure Python, and under concurrency it shows.&lt;/p&gt;

&lt;p&gt;So I built &lt;code&gt;fast-litellm&lt;/code&gt;: a drop-in Rust acceleration layer that swaps the hot&lt;br&gt;
paths out for PyO3 extensions and falls back to Python everywhere else. This is the&lt;br&gt;
honest write-up, including the cases where Rust lost.&lt;/p&gt;
&lt;h2&gt;
  
  
  Lead with the numbers, even the bad ones
&lt;/h2&gt;

&lt;p&gt;These compare production-grade Python (thread-safe implementations) against the&lt;br&gt;
Rust versions:&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;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Connection pool&lt;/td&gt;
&lt;td&gt;3.2x faster (lock-free DashMap)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate limiting&lt;/td&gt;
&lt;td&gt;1.6x faster (atomic ops)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large-text token counting&lt;/td&gt;
&lt;td&gt;1.5 to 1.7x faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High-cardinality rate limits (1000+ keys)&lt;/td&gt;
&lt;td&gt;42x less memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Small-text token counting&lt;/td&gt;
&lt;td&gt;0.5x, Python wins (FFI overhead dominates)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Routing with complex Python objects&lt;/td&gt;
&lt;td&gt;0.4x, Python wins&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last block is the important part. Crossing the Python to Rust boundary is not&lt;br&gt;
free. For a 12-token chat message, the FFI overhead is bigger than the work you&lt;br&gt;
saved, so Rust loses. Anyone who tells you their native extension is faster at&lt;br&gt;
everything is not measuring the small cases.&lt;/p&gt;

&lt;p&gt;Where it wins, it wins because of data structures, not because "Rust is fast." The&lt;br&gt;
connection pool uses a lock-free &lt;code&gt;DashMap&lt;/code&gt; so concurrent workers stop serializing&lt;br&gt;
on a global lock. The high-cardinality rate limiter holds 1000+ unique keys in a&lt;br&gt;
fraction of the Python footprint. 42x less memory is a memory-layout story, not a&lt;br&gt;
language story.&lt;/p&gt;
&lt;h2&gt;
  
  
  The architecture: accelerate the hot path, never break the app
&lt;/h2&gt;

&lt;p&gt;The design constraint I cared about most: nobody rewrites their app to try this,&lt;br&gt;
and nobody ships a native extension that can take prod down. So the layer has two&lt;br&gt;
halves.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LiteLLM (Python)
  └─ fast_litellm (Python integration layer)
       ├─ monkeypatches the hot paths
       ├─ feature flags + gradual rollout
       ├─ performance monitoring
       └─ automatic fallback to Python
  └─ Rust components (PyO3)
       ├─ connection_pool
       ├─ rate_limiter
       ├─ tokens
       └─ core (routing)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Python side does the patching, watches the metrics, and owns the safety net.&lt;br&gt;
The Rust side does the work. If an accelerated component throws or looks wrong, the&lt;br&gt;
integration layer falls back to the original Python implementation instead of&lt;br&gt;
propagating the failure. That single decision is what makes a native accelerator&lt;br&gt;
safe to actually turn on in production.&lt;/p&gt;
&lt;h2&gt;
  
  
  Drop-in, or it does not get used
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;fast_litellm&lt;/span&gt;  &lt;span class="c1"&gt;# accelerates LiteLLM automatically
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;litellm&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;litellm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;completion&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;gpt-3.5-turbo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&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;role&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;user&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;content&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;Hello!&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;One import before &lt;code&gt;litellm&lt;/code&gt;. It patches the hot paths on load, and every&lt;br&gt;
accelerated component has that automatic fallback. Installation is the boring part,&lt;br&gt;
which is the point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv add fast-litellm   &lt;span class="c"&gt;# or: pip install fast-litellm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the risky path is turning native code on in prod, rollout is gated. Feature&lt;br&gt;
flags let you send a percentage of traffic through the Rust path first, watch the&lt;br&gt;
monitoring, and widen it only when the numbers hold. If you run the LiteLLM proxy&lt;br&gt;
under gunicorn, a two-line wrapper with &lt;code&gt;--preload&lt;/code&gt; applies the acceleration before&lt;br&gt;
the workers fork:&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="c1"&gt;# app.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;fast_litellm&lt;/span&gt;  &lt;span class="c1"&gt;# apply before litellm loads
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;litellm.proxy.proxy_server&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where it does not fit
&lt;/h2&gt;

&lt;p&gt;Be honest about who should not bother with this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If your traffic is short prompts and simple routing, the FFI overhead can make
you slightly slower, not faster. The table above is not marketing, it is a
warning label. Measure your own payload sizes first.&lt;/li&gt;
&lt;li&gt;If you are not concurrency-bound, the connection-pool win shrinks. The 3.2x comes
from removing lock contention. No contention, no prize.&lt;/li&gt;
&lt;li&gt;It is another native dependency in your build. For a single-process, low-QPS
script, the operational cost is probably not worth the millisecond.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sweet spot is the opposite of all that: many workers, many unique rate-limit&lt;br&gt;
keys, long inputs, connection-pool pressure. That is where the data-structure wins&lt;br&gt;
compound.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would take from this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Profile before you port. The win was in three specific hot paths, not "the code."&lt;/li&gt;
&lt;li&gt;Measure the small inputs too. FFI overhead is real and it will embarrass you.&lt;/li&gt;
&lt;li&gt;Make it a drop-in or it dies on the vine. Zero-config plus automatic fallback is
what turns "interesting Rust project" into something a team will actually run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Code, the full benchmark breakdown, and the PyO3 architecture are here:&lt;br&gt;
&lt;a href="https://github.com/neul-labs/fast-litellm" rel="noopener noreferrer"&gt;https://github.com/neul-labs/fast-litellm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you run LiteLLM at any real volume, I would love to know which path is your&lt;br&gt;
bottleneck, and whether the small-input penalty bites you. Kick the tyres, issues&lt;br&gt;
welcome.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>python</category>
      <category>ai</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
