<?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: Lượng Lê</title>
    <description>The latest articles on DEV Community by Lượng Lê (@lng_l_95dcf5de35ba45ef).</description>
    <link>https://dev.to/lng_l_95dcf5de35ba45ef</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%2F2445746%2Fc42257da-48cb-47a8-9abb-899478e6407a.jpg</url>
      <title>DEV Community: Lượng Lê</title>
      <link>https://dev.to/lng_l_95dcf5de35ba45ef</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lng_l_95dcf5de35ba45ef"/>
    <language>en</language>
    <item>
      <title>I taught my game generator to make cozy farms, drawing every tile in Python. Here's what broke along the way</title>
      <dc:creator>Lượng Lê</dc:creator>
      <pubDate>Wed, 21 Oct 2026 15:16:17 +0000</pubDate>
      <link>https://dev.to/lng_l_95dcf5de35ba45ef/i-taught-my-game-generator-to-make-cozy-farms-drawing-every-tile-in-python-heres-what-broke-ca0</link>
      <guid>https://dev.to/lng_l_95dcf5de35ba45ef/i-taught-my-game-generator-to-make-cozy-farms-drawing-every-tile-in-python-heres-what-broke-ca0</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/leluong141996-dev/Spawn-Point" rel="noopener noreferrer"&gt;Spawn Point&lt;/a&gt; turns one sentence into a playable Godot 4 game. A small fine-tuned model writes the game design as JSON; plain Python and tested Godot components do everything else. This month I pointed it at one genre and tried to make it good: the cozy farm.&lt;/p&gt;

&lt;p&gt;Type &lt;code&gt;spawnpoint create "nông trại mùa đông, trồng bắp cải, có cáo"&lt;/code&gt; (a winter farm, cabbages, a fox) and you get this kind of thing:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flp1zqpnt317rcir9lany.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flp1zqpnt317rcir9lany.png" alt="A generated island farm: farmhouse, fenced field, animal pen, pond, woods" width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are no image files anywhere in that picture. Every tile, tree, chicken and cabbage is drawn by Python at build time. Here is how, and the parts that went wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The ground is one picture, not tiles
&lt;/h2&gt;

&lt;p&gt;My first instinct was a tileset with autotile rules. Instead, the whole map's ground is painted as one image:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The level is a grid of characters (&lt;code&gt;.&lt;/code&gt; grass, &lt;code&gt;:&lt;/code&gt; path, &lt;code&gt;~&lt;/code&gt; water).&lt;/li&gt;
&lt;li&gt;Each terrain type becomes a pixel mask, upscaled 16x and &lt;strong&gt;blurred, then thresholded with a little noise&lt;/strong&gt;. That one trick gives rounded corners and bumpy, hand-drawn-looking edges for free.&lt;/li&gt;
&lt;li&gt;Outlines are just "pixels outside the mask that touch it", in a darker colour.&lt;/li&gt;
&lt;li&gt;A cliff is "water pixels within 6 px below land". Islands get a brown cliff on their south side automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tufts, flowers, lily pads and reeds are sprinkled per cell with a seeded RNG, so the same farm always looks the same.&lt;/p&gt;

&lt;p&gt;The same code paints four seasons by swapping a colour table and the tree list:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F02vu1sigawa6pxmc37fr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F02vu1sigawa6pxmc37fr.png" alt="The same farm in spring, autumn, winter and the desert" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And in motion, winter and the desert:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftqaxt22xwswosjbxm049.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftqaxt22xwswosjbxm049.gif" alt="A winter farm in play" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqcqqamjd60qq4i8tmu5h.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqcqqamjd60qq4i8tmu5h.gif" alt="A desert farm in play" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What broke:&lt;/strong&gt; I used &lt;code&gt;scipy.ndimage&lt;/code&gt; for the blur and the distance transform. It worked on my machine and failed in CI, because scipy was never a dependency. I rewrote it with numpy (a separable blur and erosion), and the numpy erosion gave the map a thin light frame, because it treated "outside the picture" as land. The fix was padding with &lt;code&gt;mode="edge"&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Walking behind trees needs two small engine features
&lt;/h2&gt;

&lt;p&gt;A top-down farm looks flat until the farmer can walk &lt;em&gt;behind&lt;/em&gt; a tree. That needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Y-sorting&lt;/strong&gt;: entities lower on screen draw in front.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Footprints&lt;/strong&gt;: each object stands on the bottom of its drawing and only a small box at its feet collides. The collision used to be traced from the whole sprite, so a tree's crown blocked you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both became one-line options in the game graph (&lt;code&gt;"y_sort": true&lt;/code&gt;, &lt;code&gt;"base": [22, 14]&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feac5elfs6yq75ci6ape5.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feac5elfs6yq75ci6ape5.gif" alt="The farmer feeding the hens and the cow, then collecting eggs and milk" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. A season, not a level
&lt;/h2&gt;

&lt;p&gt;The farm became a season of a few days: 6:00 to 22:00, gold light in the evening, dark at night. You sleep in the farmhouse bed (or drop off in the fields), and overnight the crops you watered grow. Some days it rains and every crop stays watered; a winter farm gets snow instead. Seeds cost money, animals must be fed before they give eggs or milk, and a workbench sells tool upgrades.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm2a64osc8utgnztcfv96.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm2a64osc8utgnztcfv96.gif" alt="Evening turns to night on the farm" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuzyj2tji2dpacuu36mio.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuzyj2tji2dpacuu36mio.gif" alt="Walking into the farmhouse" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The farm also &lt;strong&gt;saves itself&lt;/strong&gt; to &lt;code&gt;user://&lt;/code&gt; (IndexedDB in the browser): the day, the money, every plot, the animals, which trees you chopped. Close the tab, come back, carry on. Two details that took a bug each:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The GIF recorder and the playtest bots must never touch the save, or the next recording starts from wherever the last bot left off.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;R&lt;/code&gt; (restart) has to clear the save, or "restart" just reloads the same day.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. The bug that made my farmer walk backwards
&lt;/h2&gt;

&lt;p&gt;After recording the demo GIFs, someone pointed out the farmer was moonwalking: walking left while facing right.&lt;/p&gt;

&lt;p&gt;The sprite flipping was fine. The &lt;strong&gt;GIF autopilot&lt;/strong&gt; set every player's aim to "the nearest enemy", which is right for a shooter. On a farm, the nearest enemy was a crow, so the farmer kept facing the crow while walking away from it. A farmer with a sword had the same bug in real play, because sword aim followed the mouse. The fix was two lines: a farmer always faces where it walks.&lt;/p&gt;

&lt;p&gt;Facing the right way now, and fishing from the pond's shore:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn1v6wnchsycbv715qmle.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn1v6wnchsycbv715qmle.gif" alt="Fishing: cast, wait for the bite, reel in" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The model kept ignoring the fox
&lt;/h2&gt;

&lt;p&gt;The model got "a farm with crows that eat the crops" right. It missed pests mentioned in passing: "&lt;em&gt;có cáo&lt;/em&gt;" ("there's a fox"), "&lt;em&gt;with a raccoon around&lt;/em&gt;". I wrote a sealed set of 16 such prompts (before any training) and the released model got 12.&lt;/p&gt;

&lt;p&gt;The first training patch fixed the fox, then put pests on &lt;em&gt;peaceful&lt;/em&gt; farms and started ignoring named farmers ("a cat farmer" came back as "Farmer"). The cause was in my synthetic data: &lt;strong&gt;every new example used the default farmer and almost all of them had pests.&lt;/strong&gt; The model learned exactly that.&lt;/p&gt;

&lt;p&gt;The second patch used half named farmers, more explicit "no pests / peaceful" farms, and more replay of the previous patch. Results, fully-right prompts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;set&lt;/th&gt;
&lt;th&gt;v5.1&lt;/th&gt;
&lt;th&gt;v5.2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;sealed: farm pests named in passing (16)&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30 real prompts (4-bit build you download)&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;td&gt;83%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sealed shooters (4-bit build)&lt;/td&gt;
&lt;td&gt;88%&lt;/td&gt;
&lt;td&gt;94%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;v5.2 is a prompt or two behind on two dev sets, which is within my run-to-run noise. After release, "nuôi gà và câu cá" ("raise chickens and fish") came back once with &lt;strong&gt;Fish&lt;/strong&gt; as a pest, so the normalizer now drops farm animals and fish from a farm's pest list. Some fixes belong in code, not in the weights.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Testing on a phone without a phone
&lt;/h2&gt;

&lt;p&gt;I checked the touch controls with headless Chrome at phone size. My first script dispatched synthetic &lt;code&gt;PointerEvent&lt;/code&gt;s at the on-screen buttons, and nothing happened. The game was fine: the touch code calls &lt;code&gt;setPointerCapture()&lt;/code&gt;, which throws for a pointer id the browser never saw. Real input through the DevTools protocol worked: the stick walked the farmer to the door and ACT went inside. The phone test did find one real problem: the seed buttons covered the middle of the screen. Seeds are now picked by tapping the hotbar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faoxofbfgrgvfidvr8ryq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faoxofbfgrgvfidvr8ryq.gif" alt="Working the field" width="480" height="270"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-U&lt;/span&gt; spawnpoint-studio
spawnpoint llm up      &lt;span class="c"&gt;# serves the model (12 GB NVIDIA GPU + Docker, or Ollama)&lt;/span&gt;
spawnpoint create &lt;span class="s2"&gt;"a cozy autumn farm with pumpkins, watch out for crows"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or play the demo farm in the browser, phone included: &lt;a href="https://leluong141996-dev.github.io/Spawn-Point/" rel="noopener noreferrer"&gt;leluong141996-dev.github.io/Spawn-Point&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/leluong141996-dev/Spawn-Point" rel="noopener noreferrer"&gt;https://github.com/leluong141996-dev/Spawn-Point&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Model: &lt;a href="https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-AWQ" rel="noopener noreferrer"&gt;https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-AWQ&lt;/a&gt; / &lt;a href="https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-GGUF" rel="noopener noreferrer"&gt;https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-GGUF&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've generated art procedurally, what was the trick that made it stop looking generated? For me it was blurring a mask and thresholding it with noise.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>python</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>My first fine-tuning patch made my model worse. The second one, with fewer examples and half the learning rate, fixed it</title>
      <dc:creator>Lượng Lê</dc:creator>
      <pubDate>Tue, 20 Oct 2026 18:40:10 +0000</pubDate>
      <link>https://dev.to/lng_l_95dcf5de35ba45ef/my-first-fine-tuning-patch-made-my-model-worse-the-second-one-with-fewer-examples-and-half-the-2cla</link>
      <guid>https://dev.to/lng_l_95dcf5de35ba45ef/my-first-fine-tuning-patch-made-my-model-worse-the-second-one-with-fewer-examples-and-half-the-2cla</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/leluong141996-dev/Spawn-Point" rel="noopener noreferrer"&gt;Spawn Point&lt;/a&gt; is an open-source game generator: a fine-tuned 7B model turns a sentence ("a platformer where a ninja rescues his mother") into a JSON game design, and plain Python draws every sprite and assembles a playable Godot 4 game. I recently shipped model &lt;strong&gt;v5.1&lt;/strong&gt; and Spawn Point &lt;strong&gt;0.3.3&lt;/strong&gt;. This post is about what I learned fixing a regression, and about a decision to make the project pixel art only.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgfx6qo48lkw5rw9xv0vu.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgfx6qo48lkw5rw9xv0vu.gif" alt="Animated monsters drawn by plain Python from their names" width="700" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The regression I shipped on purpose
&lt;/h2&gt;

&lt;p&gt;v5 added side-scrolling shooters and a per-genre output format. It was clearly better on the new prompts, but a bit worse than v4 on my older hand-written sets (30 real prompts: 87% to 80%). The cause was my last training patch. It taught the model about enemies that are implied rather than named ("a tank shooting monsters"), and the model over-corrected: when a prompt named the player only indirectly, it answered with the default "Hero".&lt;/p&gt;

&lt;p&gt;I released v5 anyway, wrote the weakness into the model card, and planned a patch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Patch 1: more data, same learning rate. Mixed results.
&lt;/h2&gt;

&lt;p&gt;My first v5.1 attempt added about 730 new examples (half of them naming the hero indirectly) on top of 800 replayed old rows, at the learning rate I'd used before. Result: the 30-prompt set and two dev sets improved a little, but my sealed shooter set, the cleanest benchmark I have, fell from 88% to 69%, and a second sealed set slipped too. Three prompts broke, and not because of the Hero problem: a wrong archetype, a genre confusion, a renamed rescue target. It was not a win, so I didn't ship it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Patch 2: less of everything
&lt;/h2&gt;

&lt;p&gt;The second attempt used about 370 new examples, 1,080 replayed rows, and half the learning rate (3e-5). Fully-right prompts, hand-written English and Vietnamese, whole pipeline:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;set&lt;/th&gt;
&lt;th&gt;v4&lt;/th&gt;
&lt;th&gt;v5&lt;/th&gt;
&lt;th&gt;v5.1&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;30 real-world prompts&lt;/td&gt;
&lt;td&gt;87%&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;td&gt;87%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;older sealed set (12)&lt;/td&gt;
&lt;td&gt;67%&lt;/td&gt;
&lt;td&gt;58%&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;set of 16 (farms, tower defense...)&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;94%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sealed shooter set (16)&lt;/td&gt;
&lt;td&gt;56%&lt;/td&gt;
&lt;td&gt;88%&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;newest set (12)&lt;/td&gt;
&lt;td&gt;92%&lt;/td&gt;
&lt;td&gt;92%&lt;/td&gt;
&lt;td&gt;83%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two honest caveats. Run-to-run noise at temperature 0.4 is one to three prompts per set, so a single row proves little; look at the pattern. And the shooter and newest sets still dip at full precision. The 4-bit AWQ build you actually download scored 88% on the sealed shooter set and 80% on the 30 prompts, which says as much about noise as about the model.&lt;/p&gt;

&lt;p&gt;What I'd take from it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A patch can fix one behavior and break another.&lt;/strong&gt; Re-run every evaluation set after every patch, not just the one you were targeting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gentler beat bigger.&lt;/strong&gt; Fewer new rows and a lower learning rate recovered the old sets without losing the new ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep a sealed set you don't look at while tuning.&lt;/strong&gt; It is the only reason I caught that patch 1 looked fine on the dev sets.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Pixel art only
&lt;/h2&gt;

&lt;p&gt;The other decision: Spawn Point used to offer two looks, pixel art and a smooth "cartoon" style. I'm dropping the second as a goal and focusing on pixel art. A prompt that asks for cartoon art now still gets pixel art, and the cartoon engine stays only for hand-written graphs. The practical reason is time: every new monster or item had to be drawn twice. The better reason is that a small project looks more coherent with one strong style.&lt;/p&gt;

&lt;p&gt;So 0.3.3 is a pixel art release:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Six new monsters&lt;/strong&gt;: a biting plant, a mushroom, a reaper, a flame spirit, a snail and an alien, each with idle, run and hurt animations. Attack and death are derived from existing frames, so a new creature gets them for free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Names pick the art&lt;/strong&gt;: &lt;code&gt;PiranhaPlant&lt;/code&gt;, &lt;code&gt;Mushroom&lt;/code&gt;, &lt;code&gt;Reaper&lt;/code&gt;, &lt;code&gt;FireSpirit&lt;/code&gt;, &lt;code&gt;GiantSnail&lt;/code&gt; and &lt;code&gt;Alien&lt;/code&gt; route to the new sprites.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;spawnpoint sprites parade&lt;/code&gt;&lt;/strong&gt; renders an animated sheet of heroes, monsters or items at a large scale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Closer, finer demos&lt;/strong&gt;: games use the finer pixel mode and zoom in, and every demo GIF is re-recorded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcs21oa2h9kg7j64vqdjy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcs21oa2h9kg7j64vqdjy.gif" alt="Animated heroes: knight, wizard, ranger, viking, king, princess, ninja" width="700" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also checked that adding the new nouns to the model's prompt didn't hurt it: the sealed shooter set held at 94% with the served model, and the 30 real prompts moved from 80% to 77%, which is one prompt of noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-U&lt;/span&gt; spawnpoint-studio
spawnpoint llm up        &lt;span class="c"&gt;# downloads the v5.1 model (12 GB NVIDIA GPU + Docker, or CPU via Ollama)&lt;/span&gt;
spawnpoint create &lt;span class="s2"&gt;"a platformer where a ninja rescues his mother, with mushrooms and snails"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also play nine generated games in the browser, with touch controls on a phone: &lt;a href="https://leluong141996-dev.github.io/Spawn-Point/" rel="noopener noreferrer"&gt;leluong141996-dev.github.io/Spawn-Point&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/leluong141996-dev/Spawn-Point" rel="noopener noreferrer"&gt;https://github.com/leluong141996-dev/Spawn-Point&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Model (AWQ): &lt;a href="https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-AWQ" rel="noopener noreferrer"&gt;https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-AWQ&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Model (GGUF for Ollama): &lt;a href="https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-GGUF" rel="noopener noreferrer"&gt;https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-GGUF&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A small ask
&lt;/h2&gt;

&lt;p&gt;The model files have been downloaded a few hundred times and the repo has no stars yet. I suspect most people run it, see what it does, and move on, which is fair. If you tried it and it did something useful or interesting, a star on GitHub or a like on the Hugging Face page helps other people find it. And if it did something wrong, an issue with the prompt you used helps me more than a star: my evaluation sets only grow from prompts that broke.&lt;/p&gt;

&lt;p&gt;What would you want a model like this to draw next?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>gamedev</category>
      <category>opensource</category>
      <category>python</category>
    </item>
    <item>
      <title>How splitting one giant JSON schema into per-genre schemas shrank my prompts 3x (and stopped a 7B model from planting wheat in platformers)</title>
      <dc:creator>Lượng Lê</dc:creator>
      <pubDate>Sat, 17 Oct 2026 15:58:39 +0000</pubDate>
      <link>https://dev.to/lng_l_95dcf5de35ba45ef/how-splitting-one-giant-json-schema-into-per-genre-schemas-shrank-my-prompts-3x-and-stopped-a-7b-2p32</link>
      <guid>https://dev.to/lng_l_95dcf5de35ba45ef/how-splitting-one-giant-json-schema-into-per-genre-schemas-shrank-my-prompts-3x-and-stopped-a-7b-2p32</guid>
      <description>&lt;p&gt;I'm building &lt;a href="https://github.com/leluong141996-dev/Spawn-Point" rel="noopener noreferrer"&gt;Spawn Point&lt;/a&gt;, an open-source 2D game generator. Instead of asking an LLM to write buggy GDScript or draw pixels, I fine-tuned a 7B model to output a game design as structured JSON. Plain Python and pre-tested Godot components then assemble that JSON into a playable game.&lt;/p&gt;

&lt;p&gt;This is the story of the &lt;strong&gt;v5 release&lt;/strong&gt;: one schema change that paid off more than any amount of training data, a brand-new genre, and a regression I knowingly shipped anyway.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsw0jt8xd3xx87l0lrxd7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsw0jt8xd3xx87l0lrxd7.gif" alt="Star Run, a side-scrolling shooter generated from a one-line prompt" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: One giant schema to rule them all
&lt;/h2&gt;

&lt;p&gt;Up until v4, the model filled out a single monolithic schema called &lt;code&gt;GameBrief&lt;/code&gt; with around 20 fields: &lt;code&gt;player&lt;/code&gt;, &lt;code&gt;enemies&lt;/code&gt;, &lt;code&gt;crops&lt;/code&gt;, &lt;code&gt;towers&lt;/code&gt;, &lt;code&gt;goal&lt;/code&gt;, &lt;code&gt;hazards&lt;/code&gt;, &lt;code&gt;length&lt;/code&gt;, &lt;code&gt;feel&lt;/code&gt;, &lt;code&gt;juice&lt;/code&gt;...&lt;/p&gt;

&lt;p&gt;For a platformer, half of those fields are meaningless. For a cozy farming game, &lt;code&gt;hazards&lt;/code&gt; and &lt;code&gt;jump_tiles&lt;/code&gt; make zero sense.&lt;/p&gt;

&lt;p&gt;Here's the catch with &lt;strong&gt;guided decoding&lt;/strong&gt;: it guarantees the output is &lt;em&gt;valid JSON&lt;/em&gt; matching your schema, but it doesn't guarantee the output makes &lt;em&gt;sense&lt;/em&gt;. My 7B model would happily design a hardcore platformer with &lt;code&gt;crops: ["Wheat"]&lt;/code&gt;, or a tower defense game where the player has a &lt;code&gt;double_jump&lt;/code&gt; ability. Both are 100% valid JSON. Both are completely broken game designs.&lt;/p&gt;

&lt;p&gt;On top of that, the system prompt was massive because it had to explain every single field across every genre, plus include a full few-shot example turn.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix: A Discriminated Union
&lt;/h2&gt;

&lt;p&gt;In v5, the model outputs &lt;code&gt;{"genre": "...", ...}&lt;/code&gt; first, and the schema immediately branches so &lt;strong&gt;only that genre's fields exist&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_Look&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;genre&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;          &lt;span class="c1"&gt;# Declared first so it leads every JSON object
&lt;/span&gt;    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;palette&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Palette&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;night&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlatformerW&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_Look&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;genre&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;platformer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PlatformerPlayer&lt;/span&gt;
    &lt;span class="n"&gt;enemies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PlatformerEnemy&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FarmW&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_Look&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;genre&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;farm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;crops&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;enemies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;FarmPest&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# A pest can only be a walker or chaser
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;_UNION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TypeAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Annotated&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PlatformerW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FarmW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...],&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;discriminator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;genre&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;Getting this to work smoothly with guided decoding took three specific tricks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;genre&lt;/code&gt; must come first in the token stream.&lt;/strong&gt; With guided decoding, once the model commits to the &lt;code&gt;"genre"&lt;/code&gt; token, the grammar mask locks in and only allows fields valid for that specific class. Since Pydantic orders inherited fields first, I declared &lt;code&gt;genre: str&lt;/code&gt; at the top of the base &lt;code&gt;_Look&lt;/code&gt; class and let subclasses narrow it down with &lt;code&gt;Literal[...]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Close every object and strip OpenAPI keywords.&lt;/strong&gt; Pydantic's &lt;code&gt;.model_json_schema()&lt;/code&gt; emits an OpenAPI-specific &lt;code&gt;discriminator&lt;/code&gt; keyword and leaves &lt;code&gt;additionalProperties&lt;/code&gt; open by default. I added a post-processing step to strip &lt;code&gt;discriminator&lt;/code&gt;, inject &lt;code&gt;additionalProperties: false&lt;/code&gt; everywhere, and wrap the root in &lt;code&gt;{"type": "object"}&lt;/code&gt; (since several inference servers require an object at the root).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat the LLM output as a wire format.&lt;/strong&gt; My game assembler, session manager, and test suite still use the monolithic &lt;code&gt;GameBrief&lt;/code&gt; internally. The new per-genre classes act strictly as a wire protocol, converting back and forth via &lt;code&gt;from_wire()&lt;/code&gt; and &lt;code&gt;to_wire()&lt;/code&gt;. This lets me iterate on what the LLM sees without refactoring the entire game engine.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Results
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;3x shorter prompts:&lt;/strong&gt; The system prompt is roughly a third of its old size: no more explaining a zoo of unrelated fields, and no more giant few-shot example turn.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;50% smaller edit prompts:&lt;/strong&gt; The entity-edit prompt dropped from &lt;strong&gt;4,182 to 2,075 characters&lt;/strong&gt; once I gave it a compact view of the entities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No more mixed-up genres inside one design:&lt;/strong&gt; Platformers with wheat crops are gone, simply because they are no longer expressible in the grammar. (The model can still pick the wrong genre for an ambiguous prompt; it just can't mix fields from two of them.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native Vietnamese support:&lt;/strong&gt; Previously, I ran a translation step in front of the model. v5 drops it and reads Vietnamese prompts directly, trained alongside English.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bonus: Adding a new genre is now dirt cheap
&lt;/h2&gt;

&lt;p&gt;The best part of per-genre schemas is how easy it is to expand the engine. To add a &lt;strong&gt;side-scrolling space shooter&lt;/strong&gt; in v5, all I needed was one small Pydantic model, a few synthetic data builders, and the Godot components.&lt;/p&gt;

&lt;p&gt;The new shooter genre includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Four enemy archetypes (&lt;code&gt;drifter&lt;/code&gt;, &lt;code&gt;weaver&lt;/code&gt;, &lt;code&gt;diver&lt;/code&gt;, &lt;code&gt;gunship&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Power-ups that add guns&lt;/li&gt;
&lt;li&gt;A three-layer parallax starfield&lt;/li&gt;
&lt;li&gt;Auto-firing weapons (making it super mobile-friendly)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My favorite piece of component reuse is the &lt;strong&gt;boss fight&lt;/strong&gt;: it's just a standard wave spawner with &lt;code&gt;autostart = false&lt;/code&gt; that triggers when the regular level spawner emits its &lt;code&gt;cleared&lt;/code&gt; signal. No new component required.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;You can play the generated games right in your browser here:&lt;/strong&gt; &lt;a href="https://leluong141996-dev.github.io/Spawn-Point/" rel="noopener noreferrer"&gt;Spawn Point Web Demo&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Honest Part: What got worse in v5
&lt;/h2&gt;

&lt;p&gt;I evaluate the model by running hand-written prompts through the entire pipeline and grading the final game. A test only passes if the playable game matches everything requested in the prompt. (At temperature &lt;code&gt;0.4&lt;/code&gt;, run-to-run variance is about 1–3 prompts per set, so I only care about swings larger than that.)&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evaluation set&lt;/th&gt;
&lt;th&gt;v4&lt;/th&gt;
&lt;th&gt;v5 (full precision)&lt;/th&gt;
&lt;th&gt;v5 (4-bit AWQ)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;New capabilities&lt;/strong&gt; (16 sealed prompts: shooters + v4 fixes)&lt;/td&gt;
&lt;td&gt;56%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;88%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;75%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Real-world prompts&lt;/strong&gt; (30 EN &amp;amp; VI human-written)&lt;/td&gt;
&lt;td&gt;87%&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;td&gt;77%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Older sealed set&lt;/strong&gt; (12 prompts, first nine genres)&lt;/td&gt;
&lt;td&gt;67%&lt;/td&gt;
&lt;td&gt;58%&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On held-out synthetic examples with exactly one right answer, v5 gets 73% of briefs, 98% of tweaks and 100% of entity edits (v5 format only, so there is no v4 number to compare).&lt;/p&gt;

&lt;p&gt;v4 couldn't be expected to know shooters, so the first row isn't a fair fight. The other two rows are real regressions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why did the older sets dip?
&lt;/h3&gt;

&lt;p&gt;In my final training patch for v5, I added examples to fix a bug where the model missed &lt;em&gt;implied&lt;/em&gt; enemies ("a tank shooting monsters"). I over-corrected: when the prompt names the player only indirectly, v5 now often answers with the default "Hero". Some old prompts are also genuinely ambiguous now that shooters and farms exist ("a space shooter" can be side-scrolling).&lt;/p&gt;

&lt;p&gt;I decided to ship v5 anyway, document the exact weakness in the model card, and fix it properly in a v5.1 patch. I'm a firm believer that a model card that honestly lists regressions is way more useful to developers than one that only cherry-picks wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  5 Takeaways for Structured LLM Outputs
&lt;/h2&gt;

&lt;p&gt;If you're building with small local models and constrained decoding, here's what I learned the hard way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Shrink the output space before adding data.&lt;/strong&gt; I spent a lot of time curating training data for v3 and v4. Making invalid state combinations unrepresentable in the schema achieved more in a single afternoon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put your discriminator token first.&lt;/strong&gt; Verify that your JSON schema actually forces the model to pick the branch before generating payload fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decouple your LLM wire format from your app's internal state.&lt;/strong&gt; Let the LLM read and write compact, specialized schemas while your backend works with a stable canonical model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Patches skew the model, even with replay.&lt;/strong&gt; My last fine-tune mixed old rows back in and still over-corrected on one behavior. Re-run &lt;em&gt;all&lt;/em&gt; your eval sets after every patch, not just the one you were fixing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track and publish your regressions.&lt;/strong&gt; Future you (and your users) will thank you for the honesty.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try it out locally
&lt;/h2&gt;

&lt;p&gt;If you have Python installed, you can spin up the local LLM and generate a playable Godot game in three commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;spawnpoint-studio

&lt;span class="c"&gt;# Downloads and serves the model:&lt;/span&gt;
&lt;span class="c"&gt;# Uses the 4-bit AWQ build on a 12GB+ NVIDIA GPU with Docker,&lt;/span&gt;
&lt;span class="c"&gt;# or falls back automatically to the GGUF build via Ollama.&lt;/span&gt;
spawnpoint llm up

spawnpoint create &lt;span class="s2"&gt;"a side-scrolling space shooter with a boss"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;💻 &lt;strong&gt;GitHub Repo:&lt;/strong&gt; &lt;a href="https://github.com/leluong141996-dev/Spawn-Point" rel="noopener noreferrer"&gt;leluong141996-dev/Spawn-Point&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🤗 &lt;strong&gt;Model (AWQ):&lt;/strong&gt; &lt;a href="https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-AWQ" rel="noopener noreferrer"&gt;smoky1496/SpawnPoint-Coder-7B-AWQ&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🤗 &lt;strong&gt;Model (GGUF for Ollama):&lt;/strong&gt; &lt;a href="https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-GGUF" rel="noopener noreferrer"&gt;smoky1496/SpawnPoint-Coder-7B-GGUF&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Question for the community:&lt;/strong&gt; If you've used guided decoding / structured outputs in production, did you go with one giant schema or break it down into smaller ones? What edge cases broke your setup? Let me know in the comments! 👇&lt;/p&gt;

</description>
      <category>ai</category>
      <category>gamedev</category>
      <category>opensource</category>
      <category>python</category>
    </item>
    <item>
      <title>I stopped letting the LLM write GDScript. Now one sentence becomes a playable Godot game in ~25 seconds</title>
      <dc:creator>Lượng Lê</dc:creator>
      <pubDate>Sat, 10 Oct 2026 04:06:28 +0000</pubDate>
      <link>https://dev.to/lng_l_95dcf5de35ba45ef/i-stopped-letting-the-llm-write-gdscript-now-one-sentence-becomes-a-playable-godot-game-in-25-333h</link>
      <guid>https://dev.to/lng_l_95dcf5de35ba45ef/i-stopped-letting-the-llm-write-gdscript-now-one-sentence-becomes-a-playable-godot-game-in-25-333h</guid>
      <description>&lt;p&gt;I'm an AI engineer, not a game developer. A few weeks ago I wanted to know: &lt;strong&gt;how fast can an AI agent go from "a ninja platformer where you collect stars" to something I'd actually want to play?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The usual answer is "let the LLM write the code". I tried that. It's slow, it burns tokens, and Godot 3/4 API mix-ups break the build all the time. Even when it compiles, the game feels flat: no coyote time, no screen shake, and hitboxes that don't match the sprites.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuewxqynwybtd162kertr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuewxqynwybtd162kertr.png" alt="This is fine: my LLM wrote 800 lines of GDScript, Godot 3 code in Godot 4" width="617" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;&lt;a href="https://github.com/leluong141996-dev/Spawn-Point" rel="noopener noreferrer"&gt;Spawn Point&lt;/a&gt;&lt;/strong&gt; around a different idea:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The LLM shouldn't write game code. It should &lt;em&gt;design&lt;/em&gt; the game, and a compiler should build it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flofc7o4z4lp2l2eskezi.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flofc7o4z4lp2l2eskezi.gif" alt="Pixel Arena, a generated top-down shooter" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;spawnpoint-studio
spawnpoint llm up      &lt;span class="c"&gt;# local model, ~11 GB VRAM (or any OpenAI-compatible API)&lt;/span&gt;
spawnpoint create &lt;span class="s2"&gt;"a ninja platformer where you collect stars"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Design&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ninja&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Star&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Dash"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platformer, palette candy, goal collect_all, short, difficulty &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;player&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;double_jump, stomp; jump 4.0 tiles; speed fast; feel floaty&lt;/span&gt;
  &lt;span class="na"&gt;enemies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;4x Goblin (walker), 2x Spider (hopper); pickups&lt;/span&gt;&lt;span class="na"&gt;: 10x Star; hazards&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;spikes&lt;/span&gt;
  &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;L0 core loop&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="s"&gt;ok in 5.5s&lt;/span&gt;
  &lt;span class="s"&gt;▶ playing at http://localhost:8060/&lt;/span&gt;
  &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;L1 goals &amp;amp; enemies&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="s"&gt;ok in 5.1s&lt;/span&gt;
  &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;L2 feel &amp;amp; polish&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="s"&gt;ok in 5.1s&lt;/span&gt;
&lt;span class="s"&gt;Done in 25.7s (LLM 2 call(s), 977 prompt + 256 completion tokens).&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser opens as soon as the first layer is playable and reloads as the next layers land. The whole game takes &lt;strong&gt;two LLM calls and about 1.2k tokens&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Then you iterate in plain language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spawnpoint tweak out/ninja-star-dash &lt;span class="s2"&gt;"jump 20% higher and add a dash, night colors"&lt;/span&gt;
&lt;span class="c"&gt;#   palette: "candy" -&amp;gt; "night"&lt;/span&gt;
&lt;span class="c"&gt;#   player.abilities: [...] -&amp;gt; ["double_jump", "stomp", "dash"]&lt;/span&gt;
&lt;span class="c"&gt;#   player.jump_tiles: 4.0 -&amp;gt; 4.8&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only what you asked for changes. The level layout stays the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works: LEGO, not prose
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjgpauf1jby7hw5600yyj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjgpauf1jby7hw5600yyj.png" alt="Drake meme: no to the LLM writing 800 lines of GDScript, yes to the LLM filling a JSON form while a compiler builds the game" width="600" height="939"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of generating hundreds of lines of GDScript, the pipeline looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Design.&lt;/strong&gt; The LLM fills a small, enumerated &lt;em&gt;GameBrief&lt;/em&gt; (genre, abilities, enemy archetypes, pickups, goal, feel) using &lt;strong&gt;guided JSON decoding&lt;/strong&gt;, so even a 7B model can't produce malformed output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assemble.&lt;/strong&gt; Deterministic code turns the brief into a &lt;em&gt;game graph&lt;/em&gt;: pre-tested Godot components, an ASCII level map and declarative rules. Levels are re-seeded until the compiler can prove that everything is reachable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compile.&lt;/strong&gt; A Python compiler validates the graph in about 1 ms, with error messages written for an LLM to fix. Then Godot runs headless and builds the scenes itself, and a smoke test plays the game for a second before you ever see it.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Typical "LLM writes code" agents&lt;/th&gt;
&lt;th&gt;Spawn Point&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;How code is produced&lt;/td&gt;
&lt;td&gt;GDScript from scratch&lt;/td&gt;
&lt;td&gt;Pre-tested components + JSON graph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to a playable build&lt;/td&gt;
&lt;td&gt;minutes to hours&lt;/td&gt;
&lt;td&gt;seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Game feel&lt;/td&gt;
&lt;td&gt;none by default&lt;/td&gt;
&lt;td&gt;coyote time, jump buffer, squash &amp;amp; stretch, hit-stop, screen shake, particles, SFX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hitboxes&lt;/td&gt;
&lt;td&gt;drift from the visuals&lt;/td&gt;
&lt;td&gt;traced from the sprite's pixels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure mode&lt;/td&gt;
&lt;td&gt;crashes, syntax errors&lt;/td&gt;
&lt;td&gt;structured diagnostics &lt;em&gt;before&lt;/em&gt; Godot runs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Things I didn't expect to build
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Bots that playtest your game and fix it.&lt;/strong&gt; Three bot skill levels (novice, average, expert) press real keys and navigate with a jump-aware path planner. Then a bounded tuner adjusts the game:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;before:  novice  won 1/3  deaths 5  (Spikes 4, Frog 1)
round 1: Player.Hurtbox.size_scale = 0.75   (Spikes caused 6/7 deaths)
         Player.Health.max_health = 4       (novice bots won only 1/3)
after:   average won 2/3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Right-click to edit.&lt;/strong&gt; &lt;code&gt;spawnpoint studio&lt;/code&gt; opens a local web cockpit with the game running in the middle. Right-click an enemy &lt;em&gt;inside the game&lt;/em&gt; and type &lt;em&gt;"double its health and make it shoot fireballs"&lt;/em&gt;. The model returns a patch scoped to that entity, the compiler checks it, and the game reloads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pixel art with matching collisions.&lt;/strong&gt; Sprites are generated procedurally, imported from your own sheets, or drawn by an image model through a plug-in (A1111 / any OpenAI-compatible images API). Collision polygons are traced from the pixels, so hitboxes always match what you see.&lt;/p&gt;

&lt;h2&gt;
  
  
  The model: SpawnPoint-Coder-7B
&lt;/h2&gt;

&lt;p&gt;I wanted the whole studio to run &lt;strong&gt;offline, for free, on a consumer GPU&lt;/strong&gt;. So I fine-tuned a 7B model (QLoRA, then AWQ 4-bit) just for these tasks: designing briefs, applying tweaks and editing entities. The compiler doubles as the data filter and the judge: every training example has to compile.&lt;/p&gt;

&lt;p&gt;On 30 game ideas written by hand (English and Vietnamese, 153 checks):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;v1&lt;/th&gt;
&lt;th&gt;v2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prompts fully right&lt;/td&gt;
&lt;td&gt;73%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;87%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Checks passed&lt;/td&gt;
&lt;td&gt;94%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;97%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Held-out briefs (exact match)&lt;/td&gt;
&lt;td&gt;36%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;88%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vietnamese → English&lt;/td&gt;
&lt;td&gt;52%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;97%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It's on Hugging Face: &lt;strong&gt;&lt;a href="https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-AWQ" rel="noopener noreferrer"&gt;smoky1496/SpawnPoint-Coder-7B-AWQ&lt;/a&gt;&lt;/strong&gt;. Claude, OpenAI or any OpenAI-compatible API work too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Honest limits:&lt;/strong&gt; it's tuned for 2D platformers and 2D top-down games. Ask for a 3D RPG and you'll get nothing special. The GDScript comes from the tested component library, not from the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;⭐ &lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/leluong141996-dev/Spawn-Point" rel="noopener noreferrer"&gt;https://github.com/leluong141996-dev/Spawn-Point&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🤗 &lt;strong&gt;Model:&lt;/strong&gt; &lt;a href="https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-AWQ" rel="noopener noreferrer"&gt;https://huggingface.co/smoky1496/SpawnPoint-Coder-7B-AWQ&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📦 &lt;code&gt;pip install spawnpoint-studio&lt;/code&gt; (needs Godot 4.6)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No GPU? &lt;code&gt;spawnpoint examples copy pixel_hopper &amp;amp;&amp;amp; spawnpoint build pixel_hopper.json --run&lt;/code&gt; builds an example game without any LLM.&lt;/p&gt;

&lt;p&gt;I'd love feedback, especially from people who actually make games: &lt;strong&gt;what's the first game you'd type in?&lt;/strong&gt; Drop it in the comments and I'll post what comes out.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>gamedev</category>
      <category>godot</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Three releases of my AI memory layer. The best bugs were found by other people.</title>
      <dc:creator>Lượng Lê</dc:creator>
      <pubDate>Fri, 09 Oct 2026 16:49:53 +0000</pubDate>
      <link>https://dev.to/lng_l_95dcf5de35ba45ef/three-releases-of-my-ai-memory-layer-the-best-bugs-were-found-by-other-people-4f9e</link>
      <guid>https://dev.to/lng_l_95dcf5de35ba45ef/three-releases-of-my-ai-memory-layer-the-best-bugs-were-found-by-other-people-4f9e</guid>
      <description>&lt;p&gt;I'm building &lt;a href="https://github.com/leluong141996-dev/Agent-Brain-Hub" rel="noopener noreferrer"&gt;Agent Brain Hub&lt;/a&gt;, an open-source shared memory layer for multi-agent systems.&lt;/p&gt;

&lt;p&gt;Imagine a travel agent, a repair agent, and a finance agent all talking to the same user. Each should know what the others learned, without leaking sensitive data (like health records or income) between them. The architecture borrows loosely from cognitive psychology: a short-term working memory per session, a long-term store for facts and episodes, and an asynchronous "sleep cycle" that consolidates one into the other.&lt;/p&gt;

&lt;p&gt;Over the last few days, I pushed three releases. Here's what broke, what the benchmarks showed, and why the best edge cases didn't come from my test suite. They came from people reading my posts.&lt;/p&gt;

&lt;h2&gt;
  
  
  v0.5: Point-in-time correctness (time-traveling memory)
&lt;/h2&gt;

&lt;p&gt;Until v0.5, a replaced or expired fact was deleted at the next sleep cycle. Now facts are invalidated with validity intervals: &lt;code&gt;"I moved to Saigon"&lt;/code&gt; closes &lt;code&gt;"I live in Hanoi"&lt;/code&gt; with a timestamp and a pointer to the superseding record.&lt;/p&gt;

&lt;p&gt;You can query the brain's state at any point in history:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;brain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recall&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Book a taxi from my home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;asOf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-10-01T09:00:00Z&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// Returns the address known at that timestamp, not today's address.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When debugging multi-agent loops, this is exactly the question you need to answer: &lt;em&gt;"What did the travel agent actually believe when it made that call?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bug the benchmark caught:&lt;/strong&gt; Adding temporal queries exposed a privacy leak before release. Querying past states skipped the privacy filter on recent conversation turns, so one agent's private conversation could reach another agent. The new temporal benchmark scenarios caught it before it ever shipped.&lt;/p&gt;




&lt;h2&gt;
  
  
  v0.6: An entity graph, and an honest benchmark
&lt;/h2&gt;

&lt;p&gt;Facts are now grouped into per-customer entities (&lt;code&gt;"my car"&lt;/code&gt; = &lt;code&gt;"xe"&lt;/code&gt; = &lt;code&gt;"車"&lt;/code&gt; = &lt;code&gt;"Honda Civic"&lt;/code&gt;), and retrieval follows the links between them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mia (personal agent):  "My car is a Honda Civic"
Kai (repair agent):    "The car broke down, it'll be in the shop for 3 days"

Penny (finance agent): "Should I set money aside for my Civic this week?"
→ availability: no car for 3 days (from Kai, 2 hours ago, expires in 4 days, via: Honda Civic)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that Penny's question never mentions "car" or "shop". The graph links "Civic" to the car, and the car to its repair. Traversal only runs over memories the asking agent is already allowed to see, so it can't carry a private fact across domains (a medical allergy never reaches the booking agent).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fev6mxm9749pbmh9f6onr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fev6mxm9749pbmh9f6onr.png" alt="The Graph tab: a trip linked to the car in the shop, the budget, the home city, and a private allergy only health agents can follow" width="800" height="729"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I ran the benchmark with &lt;code&gt;--no-graph&lt;/code&gt; to see if the graph actually pulls its weight:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Offline mode (hashing vectors)&lt;/th&gt;
&lt;th&gt;With graph&lt;/th&gt;
&lt;th&gt;Without graph&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-hop questions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;40%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;All 37 scenarios&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;97.3%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;81.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Leak rate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mean prompt tokens&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;427&lt;/td&gt;
&lt;td&gt;421&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The part most benchmark posts leave out:&lt;/strong&gt; if you switch from offline hashing vectors to an embedding model like &lt;code&gt;bge-m3&lt;/code&gt;, the multi-hop scenarios pass &lt;strong&gt;even without the graph&lt;/strong&gt;. The model already associates "the XPS" with "the laptop".&lt;/p&gt;

&lt;p&gt;Right now, the graph earns its keep in:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Zero-config offline mode, with no embedding model.&lt;/li&gt;
&lt;li&gt;Explainability: a deterministic trace of &lt;em&gt;why&lt;/em&gt; a fact was pulled in.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;My current scenarios aren't hard enough to show more than that against dense embeddings. Harder, adversarial scenarios are now in the backlog.&lt;/p&gt;




&lt;h2&gt;
  
  
  The edge cases I completely missed
&lt;/h2&gt;

&lt;p&gt;I posted an earlier devlog on &lt;a href="https://daily.dev/posts/your-agent-s-working-memory-is-probably-dropping-turns-mine-was--eummzloyr" rel="noopener noreferrer"&gt;daily.dev&lt;/a&gt; and opened the repo to contributions. Three real insights came straight from readers.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Stepping on a contributor's toes
&lt;/h3&gt;

&lt;p&gt;A contributor claimed an open issue and raised a PR. A few hours later, unaware of it, I shipped my own fix for the same issue.&lt;/p&gt;

&lt;p&gt;Their PR turned out to catch a case mine missed: in Vietnamese, "không thích … nữa" (&lt;em&gt;"don't like … anymore"&lt;/em&gt;) was still treated as a conflict instead of an update to the old preference. I rebased their PR onto main, kept their authorship, added a small follow-up, merged it and tagged &lt;code&gt;v0.6.1&lt;/code&gt;, the project's first outside contribution.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Takeaway:&lt;/em&gt; if your repo is public, check claimed issues and open PRs before you open your IDE.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. "Headroom rather than a guarantee" (the race condition)
&lt;/h3&gt;

&lt;p&gt;Working memory holds the last 40 turns. Once 24 unconsolidated turns pile up, the scheduler triggers a consolidation "sleep cycle".&lt;/p&gt;

&lt;p&gt;A commenter, Ahmet Özel, pointed out that the 16 turns between 24 and 40 are &lt;em&gt;"headroom rather than a guarantee"&lt;/em&gt;: a slow summarizer or a queue backlog can still let turns fall off the end.&lt;/p&gt;

&lt;p&gt;Following their suggestion, I wrote a burst test with a deliberately slow summarizer. Result: &lt;strong&gt;10 out of 26 turns vanished silently.&lt;/strong&gt; Turns arriving &lt;em&gt;during&lt;/em&gt; the summarization call weren't part of the current batch, and the buffer trim after consolidation dropped them anyway.&lt;/p&gt;

&lt;p&gt;Fixed in &lt;code&gt;v0.6.2&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A turn is only evicted once it's inside a stored episode.&lt;/li&gt;
&lt;li&gt;Hitting the 40-turn cap triggers a consolidation immediately instead of evicting turns.&lt;/li&gt;
&lt;li&gt;The burst test is now part of the regression suite.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. "What happens to a screenshot?"
&lt;/h3&gt;

&lt;p&gt;Another reader asked how consolidation handles a turn that carries a screenshot.&lt;/p&gt;

&lt;p&gt;Honest answer: it doesn't. The hub is text-only. And sending images to a remote multimodal model just to summarize them would expose private data and rack up API costs. Their suggestion, a local OCR pass before the consolidation loop, is now tracked in &lt;a href="https://github.com/leluong141996-dev/Agent-Brain-Hub/issues/25" rel="noopener noreferrer"&gt;issue #25&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;p&gt;Synthetic benchmarks catch the edge cases you anticipated when writing the code. Readers catch the assumptions you took for granted. Neither helps if you only share the numbers that make your project look good.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's next for v0.7
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open-schema extraction:&lt;/strong&gt; moving past the 19 hardcoded relation types.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conflict resolution between agents:&lt;/strong&gt; rules for when agent A and agent B assert contradictory facts about the same entity.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Question for anyone running multi-agent stacks:&lt;/strong&gt;&lt;br&gt;
How do you resolve conflicts when two independent agents write contradictory facts about the same entity? Last write wins? Confidence scores? CRDTs? Escalating to a human? I'd love to hear how you handle it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Agent Brain Hub is MIT-licensed, has no telemetry, and runs fully local with &lt;code&gt;docker compose up&lt;/code&gt;, or with any LLM provider (OpenAI-compatible, Anthropic, Gemini, Ollama…): &lt;a href="https://github.com/leluong141996-dev/Agent-Brain-Hub" rel="noopener noreferrer"&gt;github.com/leluong141996-dev/Agent-Brain-Hub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>opensource</category>
      <category>agents</category>
    </item>
    <item>
      <title>My embedding model couldn't find "order 48207". Here's the one-line fix and what LongMemEval said next.</title>
      <dc:creator>Lượng Lê</dc:creator>
      <pubDate>Thu, 08 Oct 2026 23:54:41 +0000</pubDate>
      <link>https://dev.to/lng_l_95dcf5de35ba45ef/my-embedding-model-couldnt-find-order-48207-heres-the-one-line-fix-and-what-longmemeval-said-1g2e</link>
      <guid>https://dev.to/lng_l_95dcf5de35ba45ef/my-embedding-model-couldnt-find-order-48207-heres-the-one-line-fix-and-what-longmemeval-said-1g2e</guid>
      <description>&lt;p&gt;I maintain &lt;a href="https://github.com/leluong141996-dev/Agent-Brain-Hub" rel="noopener noreferrer"&gt;Agent Brain Hub&lt;/a&gt;, an open-source memory layer that several AI agents share. Last release I added real embedding models, and recall on my benchmark went from 88.5% to 100%. Then I wrote a harder test, and the model fell over on something any keyword search gets right.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure
&lt;/h2&gt;

&lt;p&gt;A customer has twelve shopping conversations, one per order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;My order ORD-48200 for a blue backpack hasn't arrived yet
My order ORD-48201 for running shoes hasn't arrived yet
…
My order ORD-48211 for a water bottle hasn't arrived yet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then another agent asks the memory: &lt;strong&gt;"what is going on with order 48207"&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Retrieval&lt;/th&gt;
&lt;th&gt;Finds ORD-48207?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Local feature hashing (lexical)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;bge-m3&lt;/code&gt; embeddings&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To the embedding model all twelve conversations mean the same thing ("a late order"), so they scored almost identically, and recency decided which four made it into the context. The one distinguishing token, &lt;code&gt;48207&lt;/code&gt;, is exactly what dense embeddings blur. Hashing vectors are crude, but they match exact tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  What didn't work: taking the max
&lt;/h2&gt;

&lt;p&gt;My first fix took whichever signal was stronger: &lt;code&gt;max(model, lexical)&lt;/code&gt;. It still failed. The model's score for every order (~0.6) was above the lexical score of the right one, so the max was the model score everywhere and nothing changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What worked: adding them
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Hybrid (CombSUM): meaning + wording&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lexical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;cosine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hashVec&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;modelVecAvailable&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;qVec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;lexical&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lexical&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An item that matches in meaning &lt;strong&gt;and&lt;/strong&gt; wording now beats items that are merely on topic. The sum is never below either signal, so paraphrase matches ("Can I drive to the airport?" → "no car for 3 days") keep the score that made them work. I added an &lt;code&gt;exact_match&lt;/code&gt; category to the benchmark, with order numbers that differ by one digit, with and without the &lt;code&gt;ORD-&lt;/code&gt; prefix, plus device model names. A unit test proves the fix: it fails if you revert it.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;bge-m3&lt;/code&gt; on all 25 scenarios: &lt;strong&gt;96% pass, 100% recall, 0% leaks&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I asked a public dataset
&lt;/h2&gt;

&lt;p&gt;My own scenarios only compare versions of my own project. So v0.4.0 also runs &lt;a href="https://huggingface.co/datasets/xiaowu0162/longmemeval-cleaned" rel="noopener noreferrer"&gt;LongMemEval-S&lt;/a&gt; (MIT): 470 questions, each hidden among ~50 past chat sessions. Is the session that holds the answer near the top?&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Local hashing, no network&lt;/th&gt;
&lt;th&gt;top 1&lt;/th&gt;
&lt;th&gt;top 4&lt;/th&gt;
&lt;th&gt;top 10&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;All questions&lt;/td&gt;
&lt;td&gt;30.0%&lt;/td&gt;
&lt;td&gt;52.8%&lt;/td&gt;
&lt;td&gt;71.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"The assistant told me…"&lt;/td&gt;
&lt;td&gt;66.1%&lt;/td&gt;
&lt;td&gt;87.5%&lt;/td&gt;
&lt;td&gt;92.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preferences&lt;/td&gt;
&lt;td&gt;3.3%&lt;/td&gt;
&lt;td&gt;20.0%&lt;/td&gt;
&lt;td&gt;43.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Honest takeaway: about half the time the right session is in the top 4, and preference questions are bad. That number is the reason to have a public benchmark: it tells me where to work next instead of telling me I'm done. (The embedding-model run needs a GPU; on CPU it would take days, so it's next.)&lt;/p&gt;

&lt;h2&gt;
  
  
  And it got fast
&lt;/h2&gt;

&lt;p&gt;Retrieval used to scan every customer's memories, then filter. A per-customer index changed the tail:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;100,000 episodes&lt;/th&gt;
&lt;th&gt;before&lt;/th&gt;
&lt;th&gt;after&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;recall p50&lt;/td&gt;
&lt;td&gt;4.3 ms&lt;/td&gt;
&lt;td&gt;0.6 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;recall p95&lt;/td&gt;
&lt;td&gt;40.1 ms&lt;/td&gt;
&lt;td&gt;1.9 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/leluong141996-dev/Agent-Brain-Hub &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;Agent-Brain-Hub
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;                                   &lt;span class="c"&gt;# http://localhost:4317&lt;/span&gt;
npm run bench &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--url&lt;/span&gt; http://localhost:4317           &lt;span class="c"&gt;# benchmark your own running hub&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your agents' memory ever failed in a way like this (a code, a name, a date the retriever couldn't see), I'd love it as a benchmark scenario. It's one JSON file, and every future release has to pass it.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/leluong141996-dev/Agent-Brain-Hub" rel="noopener noreferrer"&gt;https://github.com/leluong141996-dev/Agent-Brain-Hub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>llm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>My AI agents' shared memory now sleeps on its own, and stopped losing conversations</title>
      <dc:creator>Lượng Lê</dc:creator>
      <pubDate>Wed, 07 Oct 2026 15:29:16 +0000</pubDate>
      <link>https://dev.to/lng_l_95dcf5de35ba45ef/my-ai-agents-shared-memory-now-sleeps-on-its-own-and-stopped-losing-conversations-1c4k</link>
      <guid>https://dev.to/lng_l_95dcf5de35ba45ef/my-ai-agents-shared-memory-now-sleeps-on-its-own-and-stopped-losing-conversations-1c4k</guid>
      <description>&lt;p&gt;I'm building &lt;a href="https://github.com/leluong141996-dev/Agent-Brain-Hub" rel="noopener noreferrer"&gt;Agent Brain Hub&lt;/a&gt;, an open-source memory that several AI agents share. It's modeled on the human brain, and like a brain it needs sleep: that's when conversations get consolidated into long-term memory, expired facts get forgotten, and the brain reflects on what it learned.&lt;/p&gt;

&lt;p&gt;Until this week, sleep only happened when someone clicked &lt;strong&gt;Run sleep cycle&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that pushed me
&lt;/h2&gt;

&lt;p&gt;While adding automatic sleep, I wrote a test for a long conversation and found a real problem. Working memory keeps only the last 40 turns. After 30 back-and-forth messages with nobody pressing the button, the first 10 questions were simply gone. They were never consolidated, so no agent could ever recall them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways the brain falls asleep now
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Trigger&lt;/th&gt;
&lt;th&gt;When&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Idle&lt;/td&gt;
&lt;td&gt;The customer has gone quiet, so the session is over&lt;/td&gt;
&lt;td&gt;30 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pressure&lt;/td&gt;
&lt;td&gt;Too many turns are waiting to be consolidated&lt;/td&gt;
&lt;td&gt;24 turns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nightly&lt;/td&gt;
&lt;td&gt;Once a day, for customers active since last night&lt;/td&gt;
&lt;td&gt;03:00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The core check is small:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const trigger =
  turns &amp;gt;= cfg.maxPendingTurns ? 'pressure'
  : idleMinutes &amp;gt;= cfg.idleMinutes ? 'idle'
  : null;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The 30-minute idle default isn't arbitrary: it's the same gap the brain already uses to decide that a new session has started. The pressure threshold stays below the 40-turn cap, so nothing is dropped.&lt;/p&gt;

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

&lt;p&gt;A few details I cared about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Manual and automatic sleeps share one queue&lt;/strong&gt;, so two runs never touch memory at the same time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No surprise bills.&lt;/strong&gt; On the first start, the server doesn't sleep every customer at once. With a paid LLM, that would mean a burst of summarization calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can watch it happen.&lt;/strong&gt; Automatic runs show up live in the brain view, with a note saying why the brain fell asleep.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker runs in UTC&lt;/strong&gt;, so set &lt;code&gt;TZ=Asia/Ho_Chi_Minh&lt;/code&gt; (or your zone), or "03:00" will be someone else's night.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Settings live in the UI (&lt;strong&gt;Settings → Sleep cycle&lt;/strong&gt;) or in &lt;code&gt;BRAIN_SLEEP_*&lt;/code&gt; environment variables. There are 9 new tests, including one that reproduces the lost-turns bug.&lt;/p&gt;

&lt;p&gt;Try it with &lt;code&gt;docker compose up&lt;/code&gt;. Feedback is very welcome, especially if you've solved memory consolidation differently in your own agents.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/leluong141996-dev/Agent-Brain-Hub" rel="noopener noreferrer"&gt;https://github.com/leluong141996-dev/Agent-Brain-Hub&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  ai, #opensource, #javascript, #llm, #agent, #memory
&lt;/h1&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>Why multi-agent systems struggle with memory—and how I tried to fix it with Agent-Brain-Hub</title>
      <dc:creator>Lượng Lê</dc:creator>
      <pubDate>Tue, 06 Oct 2026 19:18:51 +0000</pubDate>
      <link>https://dev.to/lng_l_95dcf5de35ba45ef/why-multi-agent-systems-struggle-with-memory-and-how-i-tried-to-fix-it-with-agent-brain-hub-20cd</link>
      <guid>https://dev.to/lng_l_95dcf5de35ba45ef/why-multi-agent-systems-struggle-with-memory-and-how-i-tried-to-fix-it-with-agent-brain-hub-20cd</guid>
      <description>&lt;p&gt;Hey everyone! 👋&lt;/p&gt;

&lt;p&gt;Over the past few months building and experimenting with LLM agents, I kept running into the same bottleneck: &lt;strong&gt;context fragmentation and state synchronization&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When you move beyond a single prompt-and-response loop to multi-agent workflows, managing shared state, dynamic context retrieval, and decision orchestration quickly gets messy. Most frameworks either bundle too much abstraction or leave memory management as an afterthought.&lt;/p&gt;

&lt;p&gt;To tackle this, I started building &lt;strong&gt;Agent-Brain-Hub&lt;/strong&gt;—a lightweight, centralized core designed to act as the cognitive layer for autonomous agents.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Agent-Brain-Hub?
&lt;/h3&gt;

&lt;p&gt;Think of it as the persistent coordination and memory center for your agent systems. Instead of each agent keeping isolated context or bloating the prompt window with raw history, the hub coordinates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Centralized Context &amp;amp; Memory&lt;/strong&gt;: Provides structured retrieval and persistent memory across execution turns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decision Coordination&lt;/strong&gt;: Helps decouple reasoning logic from agent-specific tooling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer-Friendly Integration&lt;/strong&gt;: Kept minimal so you can plug it into existing pipelines without rewriting your architecture.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Open Source?
&lt;/h3&gt;

&lt;p&gt;Agent architecture is evolving rapidly, and no single setup fits every use case. I built this to solve my own pain points, but I want to turn it into a solid tool the community can genuinely rely on.&lt;/p&gt;

&lt;p&gt;The codebase is completely open source:&lt;br&gt;
👉 &lt;strong&gt;GitHub Repo:&lt;/strong&gt; &lt;a href="https://github.com/leluong141996-dev/Agent-Brain-Hub" rel="noopener noreferrer"&gt;https://github.com/leluong141996-dev/Agent-Brain-Hub&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How You Can Help
&lt;/h3&gt;

&lt;p&gt;I’d love to get feedback from developers working with autonomous agents:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does this fit into your current memory/orchestration stack?&lt;/li&gt;
&lt;li&gt;What features or integrations (e.g., specific vector stores, frameworks) would make this indispensable for your workflows?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Check out the code, feel free to open issues or PRs, and if you find the concept interesting or useful, a ⭐️ on GitHub would mean the world to an indie open-source builder!&lt;/p&gt;

&lt;p&gt;Looking forward to your thoughts in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>memory</category>
      <category>agentskills</category>
    </item>
    <item>
      <title>I modeled AI agent memory on the human brain. Here's what worked and what didn't</title>
      <dc:creator>Lượng Lê</dc:creator>
      <pubDate>Mon, 05 Oct 2026 17:38:46 +0000</pubDate>
      <link>https://dev.to/lng_l_95dcf5de35ba45ef/i-modeled-ai-agent-memory-on-the-human-brain-heres-what-worked-and-what-didnt-2pn2</link>
      <guid>https://dev.to/lng_l_95dcf5de35ba45ef/i-modeled-ai-agent-memory-on-the-human-brain-heres-what-worked-and-what-didnt-2pn2</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5c1goihzvzbuhp5r4y7i.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5c1goihzvzbuhp5r4y7i.gif" alt="Agent Brain Hub live brain view" width="600" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every AI agent I deployed had the same problem: it started each conversation from zero. And as soon as there was more than one agent (one for support, one for bookings, one for billing), each of them learned things about the same customer and kept it to itself.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Agent Brain Hub&lt;/strong&gt;: one memory that many agents share. This post is about the design, the brain metaphor behind it, and the mistakes I made along the way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/leluong141996-dev/Agent-Brain-Hub" rel="noopener noreferrer"&gt;https://github.com/leluong141996-dev/Agent-Brain-Hub&lt;/a&gt; (MIT, &lt;code&gt;docker compose up&lt;/code&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  The goal in one example
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The customer tells &lt;strong&gt;Kai&lt;/strong&gt;, the repair agent: &lt;em&gt;"My car is a Honda Civic, it broke down and will be in the shop for 3 days."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Later they ask &lt;strong&gt;Atlas&lt;/strong&gt;, the travel agent: &lt;em&gt;"I need a flight to Da Nang next week, window seat please."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Atlas answers: &lt;em&gt;"I've got the context from Kai. I remember you have no car for 3 days, no need to repeat it. I'll look for a flight to Da Nang, window seat preferred. Also: a self-drive rental at the destination, would you like that?"&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Atlas never talked to the customer about the car. The fact "car unavailable for 3 days" was written by Kai into &lt;strong&gt;shared&lt;/strong&gt; memory, while the repair details stayed &lt;strong&gt;private&lt;/strong&gt; to Kai.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a brain?
&lt;/h2&gt;

&lt;p&gt;I needed a way to split a memory system into parts with clear jobs. The brain turned out to be a good map, and it made the live visualization explain itself. This is a metaphor, not neuroscience.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Region&lt;/th&gt;
&lt;th&gt;Job in the system&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Thalamus&lt;/td&gt;
&lt;td&gt;Routes the input: language, intent, which agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Brainstem&lt;/td&gt;
&lt;td&gt;Redacts PII, catches crisis messages before anything is stored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amygdala&lt;/td&gt;
&lt;td&gt;Salience: urgency and emotion raise priority&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prefrontal cortex&lt;/td&gt;
&lt;td&gt;Working memory for the current conversation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAS&lt;/td&gt;
&lt;td&gt;Retrieval: scope, then TTL, hybrid scoring, re-rank, token budget&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Neocortex&lt;/td&gt;
&lt;td&gt;Long-term facts and episodes, hot/warm/cold tiers, contradiction resolution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hippocampus&lt;/td&gt;
&lt;td&gt;Encodes new memories from each turn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cerebellum&lt;/td&gt;
&lt;td&gt;Skills: a playbook that succeeds 3 times in 7 days is promoted, versioned, can be rolled back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Basal ganglia&lt;/td&gt;
&lt;td&gt;Picks the next best action with Thompson sampling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Corpus callosum&lt;/td&gt;
&lt;td&gt;Handoff between agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forgetting, DMN&lt;/td&gt;
&lt;td&gt;The sleep loop: decay, consolidation, reflection&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every request is traced through these regions over Server-Sent Events, so in the UI you can watch which region did what and which memories the answer used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways an agent plugs in
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Native&lt;/strong&gt;: create it in the UI and the hub answers with its own LLM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REST or SDK&lt;/strong&gt;: your agent keeps its own LLM and calls the brain around each turn:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;brain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recall&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;myLLM&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mySystemPrompt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;promptBlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;brain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remember&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;traceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;traceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP&lt;/strong&gt;: &lt;code&gt;brain_recall&lt;/code&gt;, &lt;code&gt;brain_remember&lt;/code&gt;, &lt;code&gt;brain_profile&lt;/code&gt; and &lt;code&gt;brain_feedback&lt;/code&gt; tools for Claude Desktop, Claude Code, Cursor and agent frameworks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What worked
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Scopes and an audit log, from day one.&lt;/strong&gt; Every memory is private, shared or global, and every agent has read and write permissions. Every read, write and blocked access goes to an append-only log. Without this, "shared memory" is just a leak with extra steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One writer per kind of fact.&lt;/strong&gt; Only the repair domain writes &lt;code&gt;asset_issue&lt;/code&gt;, only travel writes &lt;code&gt;trip_destination&lt;/code&gt;. This removed a whole class of agents overwriting each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieval as a pipeline you can see.&lt;/strong&gt; Breaking recall into scope, TTL, scoring, re-rank and budget stages, and showing each stage in the trace, made ranking bugs obvious.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skills that earn promotion.&lt;/strong&gt; A playbook only becomes a reusable skill after it succeeds 3 times within 7 days, and every version is kept. Bad promotions can be rolled back.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I got wrong
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Private data leaked through side doors.&lt;/strong&gt; Retrieval respected scopes, but private details still reached other agents through the recent conversation turns, the handoff summary and the sleep-time reflection. Every path that builds a prompt needs the same visibility filter, not just the main retrieval.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keyword matching across languages is a trap.&lt;/strong&gt; Vietnamese "cửa sổ" (window) matched the keyword "sợ" (afraid) once accents were stripped, so a seat preference looked like anxiety. Matching now keeps diacritics when the input has them and uses word boundaries; Japanese uses substring matching because it has no spaces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bandit was too eager.&lt;/strong&gt; Thompson sampling sometimes let an irrelevant action win on a lucky draw. Narrowing the exploration range around relevance fixed it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rules and the LLM contradicted each other.&lt;/strong&gt; When both extracted a fact from the same message, single-valued facts could end up with two values. Now they're merged and deduplicated before writing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Embeddings are local feature hashing, so the hub runs with no external API. A real embedding model would be better.&lt;/li&gt;
&lt;li&gt;Fact extraction is rules plus an optional LLM pass.&lt;/li&gt;
&lt;li&gt;It's a single process on SQLite. PostgreSQL with pgvector is the next step for scale.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/leluong141996-dev/Agent-Brain-Hub
&lt;span class="nb"&gt;cd &lt;/span&gt;Agent-Brain-Hub
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;   &lt;span class="c"&gt;# → http://localhost:4317&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It runs offline with no configuration. Pick an LLM later in Settings: Claude, GPT, Gemini, DeepSeek, or local models through Ollama or vLLM. The UI speaks English, Vietnamese and Japanese.&lt;/p&gt;

&lt;p&gt;If you run several agents in production, I'd love to hear what you need from shared memory. Issues labelled &lt;code&gt;good first issue&lt;/code&gt; are open, and a ⭐ on the repo helps other people find it.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>memory</category>
      <category>ai</category>
      <category>python</category>
    </item>
  </channel>
</rss>
