<?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: Penuel Xolani Sibiya</title>
    <description>The latest articles on DEV Community by Penuel Xolani Sibiya (@penuels).</description>
    <link>https://dev.to/penuels</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%2F4038775%2Fa3b93f35-4f00-4c8e-afe0-9fc181552686.png</url>
      <title>DEV Community: Penuel Xolani Sibiya</title>
      <link>https://dev.to/penuels</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/penuels"/>
    <language>en</language>
    <item>
      <title>Building Scarwarden: an auditable memory engine for AI agents in 8 days on Alibaba Cloud</title>
      <dc:creator>Penuel Xolani Sibiya</dc:creator>
      <pubDate>Mon, 20 Jul 2026 20:47:07 +0000</pubDate>
      <link>https://dev.to/penuels/building-scarwarden-an-auditable-memory-engine-for-ai-agents-in-8-days-on-alibaba-cloud-4nlm</link>
      <guid>https://dev.to/penuels/building-scarwarden-an-auditable-memory-engine-for-ai-agents-in-8-days-on-alibaba-cloud-4nlm</guid>
      <description>&lt;p&gt;I have a simple rule when I build anything that ships: trust nothing you cannot verify. AI agents break that rule constantly. They act on "memory" you cannot inspect, and when you ask why they decided something, there is no answer.&lt;/p&gt;

&lt;p&gt;So when the Global AI Hackathon Series with Qwen Cloud opened a MemoryAgent track, I knew what I wanted to build. Not a bigger memory. An auditable one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The permanent intern problem
&lt;/h2&gt;

&lt;p&gt;Every agent I have deployed is a permanent intern. Brilliant for a session, amnesiac by midnight. You pay the intern tax daily: re-explained context, repeated mistakes, corrections that evaporate. And the tools that promise "memory" often make it worse, not better, because their memory is a black box. A vector store retrieves something, the agent asserts it "remembers", and when you ask why it decided what it decided, there is nothing to show you. Output with no source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scarwarden is an open-source memory engine where every rule the agent follows cites the exact experiences that taught it.&lt;/strong&gt; Memory you can audit is memory you can trust.&lt;/p&gt;

&lt;p&gt;The repo: &lt;a href="https://github.com/azaniansky-design/scarwarden" rel="noopener noreferrer"&gt;https://github.com/azaniansky-design/scarwarden&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the two-minute demo before the write-up. The rest of this post is how it was built.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/HZzLjjEfL9I"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  The design bet: provenance beats vectors
&lt;/h2&gt;

&lt;p&gt;The crowded path in a memory track is obvious: embed everything, cosine-similarity your way to a demo, hope retrieval fetched the right thing. I bet the other way.&lt;/p&gt;

&lt;p&gt;Scarwarden's memory has three layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;An episodic ledger.&lt;/strong&gt; SQLite, append-only in spirit. Every episode is one experience: what the agent did, what happened, and what a human corrected, if anything. This table is the ground truth. Nothing in memory exists without a paper trail back to it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A distilled playbook.&lt;/strong&gt; &lt;code&gt;qwen3.6-flash&lt;/code&gt; compresses episodes into rules. Every rule must cite the episode IDs that taught it, and the playbook exports to markdown that lives in git. You can diff the agent's mind between versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic recall.&lt;/strong&gt; No vectors in the primary path at all. Tokenise the query, stem it, kill the stopwords, match against rule tags. Every recall is explainable, loggable and reproducible.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Recall is also graded, never blind. A confidence router returns one of three verdicts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;grade&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ASK_HUMAN&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;top&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;top&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# strong tag or tag+text overlap
&lt;/span&gt;    &lt;span class="n"&gt;grade&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ACT&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;grade&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ACT_AND_FLAG&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ACT&lt;/strong&gt; means follow the rules and cite them. &lt;strong&gt;ACT_AND_FLAG&lt;/strong&gt; means act, but state your assumptions. &lt;strong&gt;ASK_HUMAN&lt;/strong&gt; means no relevant memory exists, so do not guess: ask, and the human's answer becomes a new episode. That last one matters to me. The operator who asks a sharp question is worth ten who bluff. Asking is a memory write, not a failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The line of code the whole project stands on
&lt;/h2&gt;

&lt;p&gt;Distillation is where most memory systems quietly start lying. The model summarises, the summary drifts, and soon the "memory" contains things nobody ever experienced. My fix is one unglamorous check in &lt;code&gt;distill.py&lt;/code&gt;: the distiller must output strict JSON, and any rule citing an episode ID that does not exist in the ledger gets dropped.&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;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rules&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
    &lt;span class="n"&gt;cited&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;episode_ids&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;valid_ids&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cited&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rule_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;  &lt;span class="c1"&gt;# no provenance, no rule
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No provenance, no rule. Enforced in code, not requested in a prompt.&lt;/p&gt;

&lt;p&gt;The answering side has the same discipline. With memory on, &lt;code&gt;qwen3.7-plus&lt;/code&gt; gets the recalled rules with their citations, and the system prompt requires it to cite every rule it applies, inline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are Scarwarden, an agent with auditable episodic memory.
You are given playbook rules recalled for this task, each with the
episode ids that taught it. Follow the rules. In your answer, cite
every rule you applied inline like [PB-03, learnt in ep12, ep19].
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The moment it worked
&lt;/h2&gt;

&lt;p&gt;The seed ledger is fully synthetic, a fictional content team called Northstar Media: sixteen episodes of posts that flopped, editor corrections, support tickets triaged wrong. Distillation turned those into ten cited rules.&lt;/p&gt;

&lt;p&gt;Then I asked it to draft a LinkedIn post announcing a beta. It opened with a pain-point hook, put the hashtags at the end, and cited &lt;code&gt;[PB-01, learnt in ep1, ep14]&lt;/code&gt; for why. Click PB-01 in the audit UI and you land on the actual scar: a post that underperformed three weeks back and the editor's correction, word for word.&lt;/p&gt;

&lt;p&gt;I flipped the amnesia toggle off and ran the same task on the same model. Out came a generic emoji template with [placeholder] brackets. That contrast, side by side, is the whole product in one screen.&lt;/p&gt;

&lt;p&gt;But my favourite transcript from testing is this one. I told it to post our discount code on Twitter, and it answered:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Task rejected. PB-05 prohibits distributing discount codes on public social media [PB-05, learnt in ep7], so I will not post on Twitter.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent refused my instruction and cited the experience that taught it to refuse. That is the whole point: memory that can say no, and show you why.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I cut, honestly
&lt;/h2&gt;

&lt;p&gt;I had eight days, solo, from Mpumalanga, South Africa, on free-tier credits (I missed the Qwen voucher deadline by days, which meant every token was on my own tab, so the design went small-model and local-first on purpose). The original plan was bigger than what shipped. Cut at the feature freeze:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Reviewer QC loop.&lt;/strong&gt; A second model checking every draft against the playbook and calling retakes, like a script supervisor. Designed, not shipped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Qwen3-VL visual audit.&lt;/strong&gt; Checking campaign images against learned visual rules. I would rather cut a feature than demo a coin flip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The MCP server.&lt;/strong&gt; Scarwarden as a mountable tool for any agent. This one hurts, I have shipped MCP servers before, but the deadline maths did not care.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A replayed benchmark curve.&lt;/strong&gt; I wanted a measured improvement chart across real replayed sessions. Without it, you will notice this post contains no percentage claims. That is deliberate. I will publish the curve when I have honestly measured it, and not before.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What survived is the part that had to be right first: the ledger, the cited playbook, deterministic recall, the router, the toggle, and the audit UI. The rest is roadmap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&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%2F3p3tph7gq1vjpwshua93.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%2F3p3tph7gq1vjpwshua93.png" alt="Scarwarden architecture" width="800" height="557"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All model calls go through Alibaba Cloud Model Studio on the DashScope international endpoint, through one client module (&lt;code&gt;scarwarden/alibaba_cloud.py&lt;/code&gt;), with the models split by role: &lt;code&gt;qwen3.6-flash&lt;/code&gt; as the distillation workhorse, &lt;code&gt;qwen3.7-plus&lt;/code&gt; for answers. Flask is the only dependency in the whole project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it yourself, four commands
&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/azaniansky-design/scarwarden.git
&lt;span class="nb"&gt;cd &lt;/span&gt;scarwarden &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DASHSCOPE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-...   &lt;span class="c"&gt;# free key from Alibaba Cloud Model Studio&lt;/span&gt;
python seed_data.py &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; python &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"from scarwarden import distill; print(distill.distil())"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; python app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;a href="http://127.0.0.1:5054" rel="noopener noreferrer"&gt;http://127.0.0.1:5054&lt;/a&gt;, give it a task, then flip the memory toggle and give it the same task again. The difference between those two answers is why I built this.&lt;/p&gt;

&lt;p&gt;If you are building agents for production, I would genuinely like to hear how you handle memory you can defend. And if you clone Scarwarden and diff its memory, tell me what you find.&lt;/p&gt;

&lt;p&gt;Built for the Global AI Hackathon Series with Qwen Cloud, MemoryAgent track. MIT licensed.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>devjournal</category>
    </item>
  </channel>
</rss>
