<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Fabio Nonato de Paula — Blog</title>
  <subtitle>Thoughts on AI/ML infrastructure, developer tools, cybersecurity, and open source.</subtitle>
  <link href="https://nonatofabio.github.io/feed.xml" rel="self"/>
  <link href="https://nonatofabio.github.io/blog/" rel="alternate"/>
  <id>https://nonatofabio.github.io/</id>
  <updated>2026-03-04T00:00:00Z</updated>
  <author>
    <name>Fabio Nonato de Paula</name>
    <uri>https://nonatofabio.github.io/</uri>
  </author>
  <entry>
    <title>I Built My Own AI Agent (And Open-Sourced It)</title>
    <link href="https://nonatofabio.github.io/blog/posts/luna_agent.html" rel="alternate"/>
    <id>https://nonatofabio.github.io/blog/posts/luna_agent.html</id>
    <published>2026-03-04T00:00:00Z</published>
    <updated>2026-03-04T00:00:00Z</updated>
    <summary>Why I rejected every agent framework and built Luna — a ~2,300-line Python AI agent with SQLite hybrid-search memory, MCP tools, and Discord, fully local.</summary>
    <category term="ai"/>
    <category term="agents"/>
    <category term="open-source"/>
    <category term="python"/>
    <category term="homelab"/>
    <content type="html">&lt;p&gt;This started because I wanted a Discord bot that could remember things.&lt;/p&gt;
&lt;p&gt;Not a chatbot — I have plenty of those. I wanted an agent that could hold a conversation across days, search the web, run shell commands, and actually &lt;em&gt;learn&lt;/em&gt; who I am over time. The kind of thing where you message it on Tuesday about a project and on Friday it remembers the context without you re-explaining everything.&lt;/p&gt;
&lt;p&gt;So I went looking for a framework. That&amp;#39;s where the trouble started.&lt;/p&gt;
&lt;h2&gt;The Framework Problem&lt;/h2&gt;
&lt;p&gt;There are approximately ten thousand AI agent frameworks right now, and every week someone on Hacker News launches a new one. They all promise the same thing: &amp;quot;Build powerful AI agents in minutes!&amp;quot; I evaluated three seriously.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Framework&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;OpenClaw&lt;/td&gt;
&lt;td&gt;~400,000 lines&lt;/td&gt;
&lt;td&gt;42,000 exposed instances on Shodan. Impossible to audit.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZeroClaw&lt;/td&gt;
&lt;td&gt;~2,000 lines&lt;/td&gt;
&lt;td&gt;9 days old. No community, uncertain future.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NanoClaw&lt;/td&gt;
&lt;td&gt;~500 lines&lt;/td&gt;
&lt;td&gt;Too thin. Missing memory, MCP, observability. Would rebuild most of it anyway.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The core needs — LLM chat, persistent memory, tool calling, Discord interface, structured logging — are individually simple and well-understood problems. No 400K-line framework needed. The risk of building from scratch was spending a few days writing Python. The risk of a framework was inheriting its complexity, its security surface, and its opinions about how agents should work.&lt;/p&gt;
&lt;p&gt;Easy tradeoff.&lt;/p&gt;
&lt;h2&gt;What Luna Actually Does&lt;/h2&gt;
&lt;p&gt;Luna is a custom AI agent that runs entirely on local hardware — two RTX 3090s (48GB total VRAM) running Qwen3-Coder-Next via llama-server, with no cloud APIs and no ongoing costs. The architecture is deliberately boring:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Discord (discord.py)
     |
     v
+-----------------------+
|    Luna Agent Core    |
|                       |
|  agent.py             |  agent loop: msg -&amp;gt; memory -&amp;gt; prompt -&amp;gt; LLM -&amp;gt; tools -&amp;gt; respond
|    +-- llm.py         |  single LLM client, configurable endpoint
|    +-- memory.py      |  SQLite + FTS5 + sqlite-vec hybrid search
|    +-- tools.py       |  native tools: bash, files, web fetch, web search
|    +-- tool_output.py |  smart output pipeline for large results
|    +-- mcp_manager.py |  MCP client for community tool servers
|    +-- observe.py     |  structured JSON logging
|                       |
+-----------------------+
         |
         v
   llama-server          any OpenAI-compatible endpoint
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One process, one database file, one config file, eight runtime dependencies. The whole thing is ~2300 lines of Python including tests. Every line is auditable because there aren&amp;#39;t that many lines to audit.&lt;/p&gt;
&lt;h2&gt;The Design Choices That Matter&lt;/h2&gt;
&lt;p&gt;Building from scratch means you own every decision, which is both the privilege and the burden. Here are the ones that shaped Luna the most:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SQLite for everything.&lt;/strong&gt; Messages, memories, full-text search, and vector search all live in a single file. FTS5 is built into SQLite, and sqlite-vec adds vector search without needing a separate vector database. The entire memory system backs up with &lt;code&gt;cp&lt;/code&gt;. I spent exactly zero hours configuring Postgres, managing Redis, or debugging connection pools. For a single-user agent running on a homelab, this is exactly the right level of infrastructure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hybrid search with Reciprocal Rank Fusion.&lt;/strong&gt; Memory retrieval combines FTS5 keyword search with sqlite-vec semantic search. Keyword search catches the exact matches that embeddings miss — things like &amp;quot;error code E1234.&amp;quot; Vector search catches the semantic matches that keywords miss — &lt;em&gt;&amp;quot;the bug where the server crashes&amp;quot;&lt;/em&gt; finds a memory about a segfault even though neither word appears. RRF fuses the two result sets with one line of math per result, no trained model needed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One LLM endpoint, firewall-ready.&lt;/strong&gt; All traffic flows through a single &lt;code&gt;LLMClient&lt;/code&gt; pointing at one configurable URL. Today that&amp;#39;s &lt;code&gt;localhost:8001&lt;/code&gt;. When I&amp;#39;m ready to add an AI firewall — an input/output filtering proxy — I change the URL to &lt;code&gt;localhost:9000&lt;/code&gt; and put the proxy in front of the real LLM. Zero code changes required. I didn&amp;#39;t build the firewall, but I didn&amp;#39;t block the insertion point either.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Conversation compression.&lt;/strong&gt; Every 50 messages, the LLM summarizes the conversation and extracts facts with importance scores. Important facts go into long-term memory, and the summary keeps the conversation coherent across sessions. This gives effectively infinite conversation length — the agent always has context, even if the verbatim messages were summarized away days ago.&lt;/p&gt;
&lt;h2&gt;What I Didn&amp;#39;t Build (On Purpose)&lt;/h2&gt;
&lt;p&gt;Honestly, this is the list I&amp;#39;m most proud of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No AI firewall &lt;em&gt;(future — just don&amp;#39;t block the insertion point)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;No web dashboard &lt;em&gt;(the structured logs are ready for one when I want it)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;No multi-user auth &lt;em&gt;(single user)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;No cloud LLM fallback &lt;em&gt;(local only)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;No Docker &lt;em&gt;(systemd is simpler for a single-user Python process)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;No abstractions for hypothetical future requirements&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every feature I &lt;em&gt;didn&amp;#39;t&lt;/em&gt; build is a feature I don&amp;#39;t have to maintain, secure, or debug at 2am. Sophisticated is the enemy of simple, complex is the enemy of valuable. The right amount of complexity is the minimum needed for the current problem.&lt;/p&gt;
&lt;h2&gt;How Memory Actually Works&lt;/h2&gt;
&lt;p&gt;This is the part I&amp;#39;m most technically proud of, because it&amp;#39;s the thing that makes Luna feel like more than a stateless chatbot. The memory system has three layers that work together:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Message history&lt;/strong&gt; is the raw conversation, stored per session. This is what gives the agent short-term context — the last 20 messages in the current thread.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Extracted memories&lt;/strong&gt; are facts the LLM identifies as worth remembering, each with an importance score from 1 to 10. A score of 10 (&lt;em&gt;&amp;quot;user&amp;#39;s name is Fabio&amp;quot;&lt;/em&gt;) always surfaces when relevant. A score of 2 (&lt;em&gt;&amp;quot;the weather was nice&amp;quot;&lt;/em&gt;) fades quickly. These persist across sessions — the agent builds a growing understanding of the world over time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Session summaries&lt;/strong&gt; are LLM-generated compressions of old message blocks. When the conversation gets long, older messages get summarized so the agent retains the gist without eating the entire context window.&lt;/p&gt;
&lt;p&gt;When the agent needs to recall something, search combines all three signals:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;final_score = rrf_score + (recency_weight * 2^(-age_days / 7)) + (importance / 10 * 0.1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The recency decay has a 7-day half-life — a week-old memory scores half as much as a fresh one. All of these parameters live in &lt;code&gt;config.toml&lt;/code&gt;, so you can experiment with the tradeoffs without touching code.&lt;/p&gt;
&lt;p&gt;For embeddings, I went with nomic-embed-text-v1.5: 22M parameters, loads in seconds, runs entirely on CPU without touching GPU memory. It supports Matryoshka representations, which means I can use 384 dimensions now and scale to 768 later without re-embedding everything.&lt;/p&gt;
&lt;h2&gt;Tools: Native and MCP&lt;/h2&gt;
&lt;p&gt;Luna ships with six built-in tools — bash (with safety guardrails), file read/write, directory listing, web fetch, and web search. The bash tool checks commands against blocked patterns before execution, enforces a 30-second timeout, and caps output at 50KB. No &lt;code&gt;rm -rf /&lt;/code&gt;, no &lt;code&gt;mkfs&lt;/code&gt;, no fork bombs.&lt;/p&gt;
&lt;p&gt;For everything beyond the builtins, there&amp;#39;s MCP. The Model Context Protocol lets you connect community tool servers by editing a JSON config:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;servers&amp;quot;: {
    &amp;quot;browser&amp;quot;: {
      &amp;quot;command&amp;quot;: &amp;quot;npx&amp;quot;,
      &amp;quot;args&amp;quot;: [&amp;quot;-y&amp;quot;, &amp;quot;@playwright/mcp&amp;quot;],
      &amp;quot;transport&amp;quot;: &amp;quot;stdio&amp;quot;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Need browser automation? Add the Playwright MCP server. Need filesystem tools? Add that server. Each one runs as a separate process with natural isolation, and tool names get namespaced automatically (&lt;code&gt;browser__navigate&lt;/code&gt;, &lt;code&gt;filesystem__read_file&lt;/code&gt;) so nothing collides. Adding a new capability to the agent is editing JSON, not writing code.&lt;/p&gt;
&lt;h2&gt;The Numbers&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;~2300 lines&lt;/strong&gt; of Python (agent + tests)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;106 tests&lt;/strong&gt;, all passing — no GPU or running LLM required to run them&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;8 runtime dependencies&lt;/strong&gt; — discord.py, openai, mcp, sentence-transformers, einops, sqlite-vec, html2text, duckduckgo-search&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~187 tokens/sec&lt;/strong&gt; prompt processing, &lt;strong&gt;~81 tokens/sec&lt;/strong&gt; generation on 2x RTX 3090&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Why Open Source It&lt;/h2&gt;
&lt;p&gt;I built Luna for my homelab, and it works well for what I need. But the decisions behind it — custom build over framework, SQLite over Postgres, local LLM over cloud API, deliberate simplicity over feature checklists — those aren&amp;#39;t unique to my setup. Anyone with a GPU and a desire to actually &lt;em&gt;understand&lt;/em&gt; their AI agent stack could use this as a starting point, or at least steal the ideas they like.&lt;/p&gt;
&lt;p&gt;The code is MIT licensed. The &lt;a href=&quot;https://github.com/nonatofabio/luna-agent/blob/main/DESIGN.md&quot;&gt;DESIGN.md&lt;/a&gt; explains the reasoning behind every major architectural decision. The tests mock the LLM client so you can run them on a laptop. The config is a single TOML file with sensible defaults.&lt;/p&gt;
&lt;p&gt;If you&amp;#39;re tired of agent frameworks that are bigger than the applications they power, maybe start with something you can actually read.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/nonatofabio/luna-agent&quot;&gt;GitHub: nonatofabio/luna-agent&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;The repo has a few &lt;a href=&quot;https://github.com/nonatofabio/luna-agent/issues?q=is%3Aopen+label%3A%22good+first+issue%22&quot;&gt;good first issues&lt;/a&gt; if you want to contribute. And if you build something interesting with it, I&amp;#39;d genuinely love to hear about it.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Thanks for reading.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Keep it Awesome!&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>I Co-Authored a Research Paper With an AI Agent</title>
    <link href="https://nonatofabio.github.io/blog/posts/ai_coauthor.html" rel="alternate"/>
    <id>https://nonatofabio.github.io/blog/posts/ai_coauthor.html</id>
    <published>2026-02-04T00:00:00Z</published>
    <updated>2026-02-04T00:00:00Z</updated>
    <summary>How I ran an ML research project with an AI agent as a real collaborator — GaLore continual-learning experiments, a rank scaling law, and a co-authored paper.</summary>
    <category term="ai"/>
    <category term="ml"/>
    <category term="research"/>
    <category term="agents"/>
    <category term="collaboration"/>
    <content type="html">&lt;p&gt;Look, I&amp;#39;m going to be honest with you. This whole thing started as an experiment within an experiment.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;official&lt;/em&gt; research question was: &amp;quot;Can a language model learn continuously from human conversations without forgetting everything it already knows?&amp;quot; But the &lt;em&gt;real&lt;/em&gt; experiment? Whether I could run an entire ML research project - from hypothesis to paper - with an AI agent as my co-pilot. Not as a fancy autocomplete. As an actual collaborator.&lt;/p&gt;
&lt;p&gt;Spoiler: It worked. &lt;a href=&quot;../artifacts/paper.pdf&quot;&gt;We wrote a paper&lt;/a&gt;. And the process was, aham, &lt;em&gt;weird&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;The Setup: Human + Agent = ???&lt;/h2&gt;
&lt;p&gt;Here&amp;#39;s how it worked. I&amp;#39;m a scientist with 15+ years in ML. I had some research intuition, the domain knowledge, and access to 8x A100 GPUs on a remote server. What I didn&amp;#39;t have was infinite time to write boilerplate code, babysit training runs, and manually parse log files at 2am.&lt;/p&gt;
&lt;p&gt;Enter the agent.&lt;/p&gt;
&lt;p&gt;My AI collaborator could execute bash commands, write and modify code, SSH into the training server, run experiments, parse results, and—critically: &lt;em&gt;remember context across our entire conversation&lt;/em&gt;. It wasn&amp;#39;t just answering questions. It was maintaining state. Tracking what we&amp;#39;d tried. Suggesting next steps.&lt;/p&gt;
&lt;p&gt;The workflow looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Me: &amp;quot;Let&amp;#39;s try reducing the GaLore rank to 64 and see if it helps with forgetting&amp;quot;
Agent: *writes config file*
Agent: *SSHs to server*
Agent: *launches training run*
Agent: *monitors logs*
Agent: &amp;quot;Training complete. MMLU dropped 3.0% vs 4.8% before. Want me to run holdout eval?&amp;quot;
Me: &amp;quot;Yes&amp;quot;
Agent: *runs eval, parses results, updates experiment notes*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I was the advisor. The agent was the grad student who never sleeps and never complains about running &amp;quot;just one more ablation.&amp;quot;&lt;/p&gt;
&lt;h2&gt;Day 1: The Naive Approach (We Both Got It Wrong)&lt;/h2&gt;
&lt;p&gt;Our first attempt was embarrassingly simple. Train a 0.5B model on conversation data. Check if it learned. Check if it forgot. The agent set everything up: data pipelines, training loop, GaLore optimizer. I reviewed the code, made some suggestions, and we kicked off a 500-step run.&lt;/p&gt;
&lt;p&gt;The training loss looked beautiful. Smooth curves. Decreasing numbers. Then we ran the benchmarks.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MMLU: 48.26% → 43.45% (-4.8%)
Holdout perplexity: +14% to +22% WORSE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The model got dumber.&lt;/strong&gt; Classic overfitting. We&amp;#39;d both missed it.&lt;/p&gt;
&lt;p&gt;Here&amp;#39;s the thing: the agent didn&amp;#39;t try to hide the failure or spin it. It just... reported the results and asked what we should try next. No ego. No defensiveness. Just &amp;quot;well, that didn&amp;#39;t work. Here are some hypotheses.&amp;quot; That&amp;#39;s when I realized this collaboration might actually work.&lt;/p&gt;
&lt;h2&gt;The Design of Experiments: Where the Agent Earned Its Keep&lt;/h2&gt;
&lt;p&gt;I decided we needed a proper factorial experiment. Three hyperparameters, eight combinations, run in parallel. The kind of thing that&amp;#39;s conceptually simple but logistically annoying.&lt;/p&gt;
&lt;p&gt;Me: &amp;quot;Let&amp;#39;s do a 2³ DOE. Factors are rank reduction, LR scheduler, and weight decay.&amp;quot; The agent:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Generated all 8 config files&lt;/li&gt;
&lt;li&gt;Wrote a bash script to launch them in parallel across GPUs&lt;/li&gt;
&lt;li&gt;Wrote an eval script to benchmark all 8 models&lt;/li&gt;
&lt;li&gt;Created a results table in our experiment notes&lt;/li&gt;
&lt;li&gt;Ran the whole thing while I went to get coffee&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;[Insert Code Snippet: The DOE launch script the agent wrote in about 30 seconds - please, Claude Intern 1.0, fix this!]&lt;/p&gt;
&lt;p&gt;When I came back, I had this:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Config&lt;/th&gt;
&lt;th&gt;Rank&lt;/th&gt;
&lt;th&gt;Scheduler&lt;/th&gt;
&lt;th&gt;Weight Decay&lt;/th&gt;
&lt;th&gt;MMLU&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;3.0&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;td&gt;44.15%&lt;/td&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;64&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;45.26%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;WINNER&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.6&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;0.2&lt;/td&gt;
&lt;td&gt;43.90%&lt;/td&gt;
&lt;td&gt;Worst&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The agent had already identified the pattern: &lt;strong&gt;rank was the dominant factor&lt;/strong&gt;. Scheduler hurt. Weight decay did nothing. &lt;em&gt;I didn&amp;#39;t have to parse a single log file.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;The Scaling Crisis: When We Hit a Wall&lt;/h2&gt;
&lt;p&gt;Feeling confident, we tried the winning config on bigger models. TinyLlama (1.1B) worked perfectly. Then Gemma-2 (2.6B) broke everything.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MMLU drop: -5.67%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The agent flagged it immediately: &amp;quot;This exceeds the 5% threshold. The rank-64 setting may be too permissive for larger models.&amp;quot; This is where the human-agent dynamic got interesting. The agent had the data. I had the intuition. Together, we hypothesized that bigger models need &lt;em&gt;lower&lt;/em&gt; rank, meaning more constraint, not less.&lt;/p&gt;
&lt;p&gt;Me: &amp;quot;Try rank=32 on Gemma&amp;quot;
Agent: &lt;em&gt;runs experiment&lt;/em&gt;
Agent: &amp;quot;MMLU now -3.85%. Within threshold.&amp;quot;&lt;/p&gt;
&lt;p&gt;We&amp;#39;d discovered a &amp;quot;scaling law&amp;quot;: &lt;code&gt;rank ∝ 1/√params&lt;/code&gt;. The agent helped me formalize it into a table:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model Size&lt;/th&gt;
&lt;th&gt;Optimal Rank&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;0.5B-1.1B&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2B-3B&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8B&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Neither of us would have gotten there alone. I needed the agent to run the experiments fast enough to iterate. The agent needed me to recognize the pattern and propose the hypothesis.&lt;/p&gt;
&lt;h2&gt;The 8B Moment: When It Actually Worked&lt;/h2&gt;
&lt;p&gt;Time for the real test. Qwen3-8B. 8.2 billion parameters. Rank=8. 1000 training steps. The agent ran it overnight. I woke up to this message:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Training complete. 5:40 duration, 775 tokens/sec.
MMLU: 74.93% → 75.07% (+0.14%)
Holdout PPL: 5.34 → 4.87 (-8.8%)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wait. &lt;strong&gt;The model got smarter?&lt;/strong&gt; I didn&amp;#39;t believe it. I asked the agent to run a statistical significance test.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Paired t-test: t=7.12, p&amp;lt;0.0001
Significant: True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It was real. The model learned from conversations &lt;em&gt;and&lt;/em&gt; improved on benchmarks. Not just &amp;quot;acceptable forgetting&amp;quot;, it got net positive knowledge transfer. The agent&amp;#39;s response: &amp;quot;This proves the core hypothesis. Want me to update the experiment notes and commit?&amp;quot;&lt;/p&gt;
&lt;p&gt;Yes. Yes I did.&lt;/p&gt;
&lt;h2&gt;The LoRA Showdown: A Plot Twist&lt;/h2&gt;
&lt;p&gt;I had a nagging question: how does this compare to LoRA, the thing everyone actually uses? The agent ran the comparison. Same model, same data, same steps, same rank.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Efficiency:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LoRA: 22GB VRAM, 982 tokens/sec ✅&lt;/li&gt;
&lt;li&gt;GaLore: 39GB VRAM, 775 tokens/sec&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Learning quality:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LoRA holdout PPL: +530% (catastrophic failure)&lt;/li&gt;
&lt;li&gt;GaLore holdout PPL: -8.8% (genuine learning)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;LoRA was faster and lighter. It also &lt;em&gt;completely failed to learn anything generalizable&lt;/em&gt;. The model memorized training data and forgot how to generalize. The agent&amp;#39;s analysis was spot-on: &amp;quot;LoRA freezes base weights and only trains adapters. It can&amp;#39;t integrate new knowledge—only patch outputs. GaLore projects gradients but updates all weights, enabling genuine learning.&amp;quot;&lt;/p&gt;
&lt;p&gt;That insight made it into the paper almost verbatim.&lt;/p&gt;
&lt;h2&gt;Writing the Paper: The Final Boss&lt;/h2&gt;
&lt;p&gt;After six phases of experiments, we had results. Now we needed a paper. This is where I expected the collaboration to break down. Writing is &lt;em&gt;hard&lt;/em&gt;. It requires judgment, narrative, argumentation. Surely an AI can&amp;#39;t...&lt;/p&gt;
&lt;p&gt;The agent drafted the abstract in one shot. It was... good? Like, actually good. It captured the key findings, the methodology, the implications. I edited maybe 20%.&lt;/p&gt;
&lt;p&gt;We went section by section:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I&amp;#39;d outline what needed to be said&lt;/li&gt;
&lt;li&gt;The agent would draft it&lt;/li&gt;
&lt;li&gt;I&amp;#39;d revise and push back&lt;/li&gt;
&lt;li&gt;The agent would incorporate feedback&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The related work section was particularly impressive. The agent pulled relevant citations, summarized them, and positioned our work in the literature. I added a few papers it missed, but the structure was solid.&lt;/p&gt;
&lt;p&gt;If you look at my &lt;code&gt;git log&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;eda30d8 exp006: Add statistical significance test - p&amp;lt;0.0001, t=7.12
0151460 exp006 Phase 3: GaLore vs LoRA comparison - GaLore wins
53a2c88 exp006 Phase 2: Add learning measurement
9b1e689 exp006 Phase 2: Extended training shows +0.14% MMLU improvement
f385da7 Update paper with exp006 8B validation
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every commit was a collaboration. Every result was verified. Every claim was backed by an experiment we&amp;#39;d run together.&lt;/p&gt;
&lt;h2&gt;What I Learned About Human-Agent Collaboration&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. The agent is a force multiplier, not a replacement.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I couldn&amp;#39;t have run this many experiments this fast alone. But the agent couldn&amp;#39;t have designed the experiments or recognized the patterns without me. We were genuinely complementary.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Context is everything.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The agent remembered our entire conversation, every failed experiment, every hypothesis, every decision. It could say &amp;quot;remember when we tried X and it didn&amp;#39;t work because Y?&amp;quot; That continuity was invaluable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. The agent has no ego.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When experiments failed, the agent just... moved on. No defensiveness. No excuses. Just &amp;quot;that didn&amp;#39;t work, here&amp;#39;s what we could try next.&amp;quot; It&amp;#39;s weirdly refreshing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Trust but verify.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I checked every result. Every claim. Every number. The agent made mistakes, small ones, big ones. But the verification loop kept us honest.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. The meta-irony is not lost on me.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We built a system for AI to learn from human conversations. We did it &lt;em&gt;through&lt;/em&gt; human-AI conversation. The research method mirrored the research question.&lt;/p&gt;
&lt;h2&gt;The Takeaway&lt;/h2&gt;
&lt;p&gt;The paper&amp;#39;s conclusion is about GaLore and continuous learning. But my conclusion is different.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We&amp;#39;re entering an era where research itself can be collaborative between humans and AI agents.&lt;/strong&gt; Not AI replacing researchers. Not humans doing everything manually. Something in between - a partnership where each side contributes what they&amp;#39;re good at.&lt;/p&gt;
&lt;p&gt;I brought intuition, judgment, and experience. The agent brought speed, memory, and tireless execution. Together, we wrote a paper that neither of us could have written alone.&lt;/p&gt;
&lt;p&gt;The answer to &amp;quot;Can AI learn continuously from human conversations?&amp;quot; turned out to be yes.&lt;/p&gt;
&lt;p&gt;But the more interesting answer? &amp;quot;Can humans and AI agents do research together?&amp;quot;&lt;/p&gt;
&lt;p&gt;Also yes.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;a href=&quot;../artifacts/paper.pdf&quot;&gt;The paper is available here&lt;/a&gt;. The code we wrote is in: &lt;a href=&quot;https://github.com/nonatofabio/continuous-learning&quot;&gt;Continuous Learning&lt;/a&gt;. And I&amp;#39;m going to go touch grass now, something my co-author will never need to do.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Thanks for reading.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Keep it Awesome!&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Welcome to My Blog</title>
    <link href="https://nonatofabio.github.io/blog/posts/welcome.html" rel="alternate"/>
    <id>https://nonatofabio.github.io/blog/posts/welcome.html</id>
    <published>2026-02-04T00:00:00Z</published>
    <updated>2026-02-04T00:00:00Z</updated>
    <summary>Why I'm starting a blog: practical lessons from AWS-scale ML infrastructure, developer tools like MCP servers, cybersecurity AI, and open-source deep dives.</summary>
    <category term="meta"/>
    <category term="introduction"/>
    <content type="html">&lt;p&gt;I&amp;#39;ve been meaning to come back to writing for a while now. After years of building tools, shipping features, and breaking AI/ML infrastructure, I realized I learned a lot of stuff that might be useful to share.&lt;/p&gt;
&lt;h2&gt;What to Expect&lt;/h2&gt;
&lt;p&gt;This blog will cover topics around my interests:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AI/ML Infrastructure&lt;/strong&gt; - Practical lessons from building and scaling ML systems at AWS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Developer Tools&lt;/strong&gt; - Building useful things like MCP servers and local AI tooling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cybersecurity&lt;/strong&gt; - Threat intelligence and security applications of machine learning&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Open Source&lt;/strong&gt; - Deep dives into projects I&amp;#39;m working on&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Why Write?&lt;/h2&gt;
&lt;p&gt;Writing forces clarity. When you have to explain something, you find gaps in your own understanding. I&amp;#39;m hoping this blog helps me think more clearly, while maybe helping others along the way.&lt;/p&gt;
&lt;h2&gt;The Tech Stack&lt;/h2&gt;
&lt;p&gt;This blog is intentionally simple: Markdown files rendered client-side with marked.js, hosted on GitHub Pages. No build step, no database, no complexity. Just text files and a browser.&lt;/p&gt;
&lt;p&gt;Sometimes the best tool is the simplest one that gets the job done.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Stay tuned for more posts. You can find me on &lt;a href=&quot;https://github.com/nonatofabio&quot;&gt;GitHub&lt;/a&gt; or &lt;a href=&quot;https://www.linkedin.com/in/fabiononato/&quot;&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
</feed>
