<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>Tran Duc Phu — Blog</title>
<link>https://meotomo.com/me/blog/</link>
<atom:link href="https://meotomo.com/me/blog/feed.xml" rel="self" type="application/rss+xml"/>
<description>Writing on AI, tech, books, philosophy and psychology.</description>
<language>en</language>
<lastBuildDate>Tue, 03 Nov 2026 06:27:00 +0000</lastBuildDate>
<item><title>Notes on building LLM agents that don&#x27;t fall over</title><link>https://meotomo.com/me/blog/llm-agents-that-dont-fall-over/</link><guid isPermaLink="true">https://meotomo.com/me/blog/llm-agents-that-dont-fall-over/</guid><pubDate>Fri, 16 Oct 2026 09:00:00 +0700</pubDate><category>AI</category><category>Tech</category><description>Agents fail in boring, predictable ways: unbounded loops, malformed tool calls, and silent drift. A few patterns that make them sturdier, with a minimal Python loop.</description><content:encoded><![CDATA[<p>Every LLM agent demo looks magical for the first five minutes. Then someone asks it a question slightly outside the happy path, and it calls the same search tool eleven times, invents a function argument, and confidently answers a question nobody asked.</p>
<p>Most agent failures I&rsquo;ve seen are not exotic. They are the same handful of boring problems, and boring problems have boring solutions. Here are the ones I keep coming back to.</p>
<h2 id="1-bound-everything">1. Bound everything<a class="headerlink" href="#1-bound-everything" title="Permanent link">#</a></h2>
<p>An agent is a <code>while</code> loop whose exit condition is decided by a probabilistic text generator. Read that sentence again and the first rule writes itself: <strong>never let the model be the only thing that decides when to stop.</strong></p>
<p>Put hard limits on steps, wall-clock time, tokens and cost. When a limit trips, fail loudly with whatever partial state you have, rather than silently returning the last message.</p>
<h2 id="2-treat-tool-calls-as-untrusted-input">2. Treat tool calls as untrusted input<a class="headerlink" href="#2-treat-tool-calls-as-untrusted-input" title="Permanent link">#</a></h2>
<p>The model&rsquo;s tool call is user input wearing a nice suit. Validate it with a schema, and when validation fails, feed the error back to the model <em>as an observation</em> instead of crashing. Models are surprisingly good at fixing their own arguments when told exactly what was wrong.</p>
<p>Here&rsquo;s the skeleton I start from. It&rsquo;s deliberately small — no framework — because the important ideas fit on one screen:</p>
<div class="hl"><pre><span></span><code><span class="kn">import</span><span class="w"> </span><span class="nn">json</span><span class="o">,</span><span class="w"> </span><span class="nn">time</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">pydantic</span><span class="w"> </span><span class="kn">import</span> <span class="n">BaseModel</span><span class="p">,</span> <span class="n">ValidationError</span>

<span class="k">class</span><span class="w"> </span><span class="nc">Search</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
    <span class="n">query</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">top_k</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">5</span>

<span class="n">TOOLS</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"search"</span><span class="p">:</span> <span class="p">(</span><span class="n">Search</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">a</span><span class="p">:</span> <span class="n">search_index</span><span class="p">(</span><span class="n">a</span><span class="o">.</span><span class="n">query</span><span class="p">,</span> <span class="n">a</span><span class="o">.</span><span class="n">top_k</span><span class="p">))}</span>

<span class="k">def</span><span class="w"> </span><span class="nf">run_agent</span><span class="p">(</span><span class="n">llm</span><span class="p">,</span> <span class="n">task</span><span class="p">,</span> <span class="n">max_steps</span><span class="o">=</span><span class="mi">8</span><span class="p">,</span> <span class="n">budget_s</span><span class="o">=</span><span class="mi">60</span><span class="p">):</span>
    <span class="n">history</span> <span class="o">=</span> <span class="p">[{</span><span class="s2">"role"</span><span class="p">:</span> <span class="s2">"user"</span><span class="p">,</span> <span class="s2">"content"</span><span class="p">:</span> <span class="n">task</span><span class="p">}]</span>
    <span class="n">seen</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">monotonic</span><span class="p">()</span>
    <span class="k">for</span> <span class="n">step</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">max_steps</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">time</span><span class="o">.</span><span class="n">monotonic</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span> <span class="o">&gt;</span> <span class="n">budget_s</span><span class="p">:</span>
            <span class="k">return</span> <span class="p">{</span><span class="s2">"status"</span><span class="p">:</span> <span class="s2">"timeout"</span><span class="p">,</span> <span class="s2">"history"</span><span class="p">:</span> <span class="n">history</span><span class="p">}</span>
        <span class="n">msg</span> <span class="o">=</span> <span class="n">llm</span><span class="p">(</span><span class="n">history</span><span class="p">,</span> <span class="n">tools</span><span class="o">=</span><span class="nb">list</span><span class="p">(</span><span class="n">TOOLS</span><span class="p">))</span>
        <span class="n">history</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">msg</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"tool_call"</span><span class="p">):</span>
            <span class="k">return</span> <span class="p">{</span><span class="s2">"status"</span><span class="p">:</span> <span class="s2">"done"</span><span class="p">,</span> <span class="s2">"answer"</span><span class="p">:</span> <span class="n">msg</span><span class="p">[</span><span class="s2">"content"</span><span class="p">]}</span>

        <span class="n">name</span><span class="p">,</span> <span class="n">raw</span> <span class="o">=</span> <span class="n">msg</span><span class="p">[</span><span class="s2">"tool_call"</span><span class="p">][</span><span class="s2">"name"</span><span class="p">],</span> <span class="n">msg</span><span class="p">[</span><span class="s2">"tool_call"</span><span class="p">][</span><span class="s2">"args"</span><span class="p">]</span>
        <span class="n">key</span> <span class="o">=</span> <span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">raw</span><span class="p">,</span> <span class="n">sort_keys</span><span class="o">=</span><span class="kc">True</span><span class="p">))</span>
        <span class="k">if</span> <span class="n">key</span> <span class="ow">in</span> <span class="n">seen</span><span class="p">:</span>  <span class="c1"># same call twice = probably looping</span>
            <span class="n">obs</span> <span class="o">=</span> <span class="s2">"You already called this with identical arguments. Use the earlier result or try something different."</span>
        <span class="k">elif</span> <span class="n">name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">TOOLS</span><span class="p">:</span>
            <span class="n">obs</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">"Unknown tool '</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">'. Available: </span><span class="si">{</span><span class="nb">list</span><span class="p">(</span><span class="n">TOOLS</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">schema</span><span class="p">,</span> <span class="n">fn</span> <span class="o">=</span> <span class="n">TOOLS</span><span class="p">[</span><span class="n">name</span><span class="p">]</span>
            <span class="k">try</span><span class="p">:</span>
                <span class="n">obs</span> <span class="o">=</span> <span class="n">fn</span><span class="p">(</span><span class="n">schema</span><span class="p">(</span><span class="o">**</span><span class="n">raw</span><span class="p">))</span>
            <span class="k">except</span> <span class="n">ValidationError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
                <span class="n">obs</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">"Invalid arguments: </span><span class="si">{</span><span class="n">e</span><span class="o">.</span><span class="n">errors</span><span class="p">()</span><span class="si">}</span><span class="s2">"</span>
            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>  <span class="c1"># tool failures are observations, not crashes</span>
                <span class="n">obs</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">"Tool error: </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">"</span>
        <span class="n">seen</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
        <span class="n">history</span><span class="o">.</span><span class="n">append</span><span class="p">({</span><span class="s2">"role"</span><span class="p">:</span> <span class="s2">"tool"</span><span class="p">,</span> <span class="s2">"name"</span><span class="p">:</span> <span class="n">name</span><span class="p">,</span> <span class="s2">"content"</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">obs</span><span class="p">)[:</span><span class="mi">4000</span><span class="p">]})</span>
    <span class="k">return</span> <span class="p">{</span><span class="s2">"status"</span><span class="p">:</span> <span class="s2">"max_steps"</span><span class="p">,</span> <span class="s2">"history"</span><span class="p">:</span> <span class="n">history</span><span class="p">}</span>
</code></pre></div>

<p>Notice what the loop does <em>not</em> trust: the tool name, the arguments, the tool itself, the length of the output, or the model&rsquo;s sense of when it&rsquo;s finished.</p>
<h2 id="3-detect-loops-cheaply">3. Detect loops cheaply<a class="headerlink" href="#3-detect-loops-cheaply" title="Permanent link">#</a></h2>
<p>The duplicate-call check above catches a surprising fraction of runaway agents. It&rsquo;s crude — a model can loop with slightly different arguments — but crude and always-on beats clever and never-shipped. A next step is to track whether the agent&rsquo;s state is actually changing: new information retrieved, new files touched, new sub-goals completed.</p>
<h2 id="4-keep-the-context-honest">4. Keep the context honest<a class="headerlink" href="#4-keep-the-context-honest" title="Permanent link">#</a></h2>
<p>Long-running agents drown in their own transcripts. Truncate tool outputs (note the <code>[:4000]</code>), summarize old steps, and keep the original task pinned near the top. A lot of &ldquo;the agent forgot what it was doing&rdquo; bugs are really &ldquo;we buried the instruction under 30k tokens of JSON&rdquo;.</p>
<h2 id="5-make-it-observable-before-you-make-it-smart">5. Make it observable before you make it smart<a class="headerlink" href="#5-make-it-observable-before-you-make-it-smart" title="Permanent link">#</a></h2>
<p>Before tuning prompts, log every step: input, tool call, observation, latency, tokens. Then read the traces. Actually read them. In my experience, one afternoon of reading fifty failed traces teaches more than a week of tweaking prompts by vibes, because you start to see that failures cluster into maybe three or four categories.</p>
<h2 id="6-evaluate-trajectories-not-just-answers">6. Evaluate trajectories, not just answers<a class="headerlink" href="#6-evaluate-trajectories-not-just-answers" title="Permanent link">#</a></h2>
<p>A correct final answer reached through nine redundant tool calls is a latency and cost bug waiting to happen. Score the path too: number of steps, invalid calls, repeated calls. Those metrics are cheap to compute from the logs you already have from point 5.</p>
<h2 id="the-meta-lesson">The meta-lesson<a class="headerlink" href="#the-meta-lesson" title="Permanent link">#</a></h2>
<p>Agents look like an AI problem, but making them reliable is mostly a software engineering problem: input validation, timeouts, idempotency, observability, tests. The model is the most interesting component, and also the one you should trust least.</p>
<p>None of this is glamorous. That&rsquo;s sort of the point.</p>]]></content:encoded></item>
<item><title>Stoicism and the art of debugging</title><link>https://meotomo.com/me/blog/stoicism-and-debugging/</link><guid isPermaLink="true">https://meotomo.com/me/blog/stoicism-and-debugging/</guid><pubDate>Tue, 13 Oct 2026 09:00:00 +0700</pubDate><category>Philosophy</category><category>Psychology</category><category>Tech</category><description>The dichotomy of control, the view from above, and premeditatio malorum — three Stoic ideas that turned out to be excellent debugging tools.</description><content:encoded><![CDATA[<p>At 1am, staring at a training run whose loss has just turned into <code>NaN</code> for the third time, I am not at my most philosophical. But some of the most useful debugging advice I&rsquo;ve received comes from people who died nearly two thousand years ago.</p>
<p>The Stoics — Epictetus, Seneca, Marcus Aurelius — were not writing about software, obviously. They were writing about how to keep a clear head when things go wrong. That turns out to be most of debugging.</p>
<h2 id="the-dichotomy-of-control">The dichotomy of control<a class="headerlink" href="#the-dichotomy-of-control" title="Permanent link">#</a></h2>
<p>Epictetus opens his <em>Enchiridion</em> with a simple split: some things are up to us, and some things are not. Our judgments and actions are up to us. Everything else — other people, the weather, outcomes — is not.</p>
<p>A broken system is the perfect place to practice this. The bug already exists; being annoyed about it does not make it exist less. What <em>is</em> up to me is the next action: reading the stack trace, forming a hypothesis, writing a smaller reproduction.</p>
<p>When I&rsquo;m frustrated, I&rsquo;ve noticed my debugging becomes random. I change three things at once, rerun, and hope. The Stoic move is to drop the emotional commentary (&ldquo;why does this <em>always</em> happen&rdquo;) and return to the only thing in my control: one deliberate, testable step.</p>
<h2 id="judgments-not-events-disturb-us">Judgments, not events, disturb us<a class="headerlink" href="#judgments-not-events-disturb-us" title="Permanent link">#</a></h2>
<p>Another line from Epictetus: people are disturbed not by things, but by their judgments about things.</p>
<p>The production incident is an event. &ldquo;I&rsquo;m going to look incompetent&rdquo; is a judgment layered on top. In my experience the second one does far more damage, because it pushes you toward the fastest fix rather than the right one — the patch that makes the alert go away instead of the change that addresses the cause.</p>
<p>Separating the event from the story I&rsquo;m telling about it is a skill, and like any skill it gets easier with repetition. Incidents provide plenty of repetitions.</p>
<h2 id="premeditatio-malorum">Premeditatio malorum<a class="headerlink" href="#premeditatio-malorum" title="Permanent link">#</a></h2>
<p>Seneca recommended imagining in advance the things that could go wrong — exile, loss, illness — not to wallow in them, but to blunt their shock and prepare.</p>
<p>Engineers reinvented this and called it a <strong>pre-mortem</strong>: before launching, imagine it&rsquo;s six months later and the project has failed. What happened? It&rsquo;s remarkable how quickly a team can list the actual risks once it has permission to be pessimistic. The rate limiter nobody tested. The model that degrades on long inputs. The data pipeline with a single owner who&rsquo;s about to go on holiday.</p>
<p>The Stoic version adds a personal layer: also imagine your own reaction. If this fails, how do I want to behave? Deciding that in advance, while calm, is far easier than deciding it at 1am.</p>
<h2 id="the-view-from-above">The view from above<a class="headerlink" href="#the-view-from-above" title="Permanent link">#</a></h2>
<p>Marcus Aurelius often zoomed out — picturing the earth as a speck, his own life as a moment in a long stream of time. In <em>Meditations</em> it&rsquo;s a tool for humility.</p>
<p>In debugging it is a tool for escaping tunnel vision. After an hour deep in one function, I try to step back and ask: what is the <em>system</em> doing? Is the bug even in this layer? More than once, the bug I spent an afternoon chasing in model code was a stale cache, a misconfigured environment variable, or a dataset that changed under me. The view from above would have found it faster.</p>
<h2 id="a-caveat">A caveat<a class="headerlink" href="#a-caveat" title="Permanent link">#</a></h2>
<p>Stoicism is sometimes misread as &ldquo;suppress your emotions and push through&rdquo;. That&rsquo;s not what the texts say, and it&rsquo;s not healthy advice for engineers who already overwork. Frustration is information — often that the task is too big, or that you need sleep. The Stoic practice is to notice it and choose what to do with it, which sometimes means closing the laptop.</p>
<h2 id="the-short-version">The short version<a class="headerlink" href="#the-short-version" title="Permanent link">#</a></h2>
<p>When something breaks:</p>
<ol>
<li>Separate what happened from the story about what it means.</li>
<li>Focus on the next controllable step, and make it small and testable.</li>
<li>Zoom out before you dig deeper.</li>
<li>Next time, imagine the failure before it happens.</li>
</ol>
<p>Marcus Aurelius wrote that the impediment to action advances action; what stands in the way becomes the way. I doubt he imagined it being applied to a <code>NaN</code> loss. I think he&rsquo;d approve anyway.</p>]]></content:encoded></item>
<item><title>Thinking, Fast and Slow, read by an engineer</title><link>https://meotomo.com/me/blog/thinking-fast-and-slow-for-engineers/</link><guid isPermaLink="true">https://meotomo.com/me/blog/thinking-fast-and-slow-for-engineers/</guid><pubDate>Sat, 10 Oct 2026 09:00:00 +0700</pubDate><category>Books</category><category>Psychology</category><description>Kahneman&#x27;s System 1 and System 2 map uncomfortably well onto how we build, evaluate and trust machine learning systems — and onto how I make decisions at work.</description><content:encoded><![CDATA[<p>I first read <em>Thinking, Fast and Slow</em> as a student and filed it under &ldquo;interesting psychology&rdquo;. Rereading it years into a career building machine learning systems, it reads less like a psychology book and more like a bug report about the hardware I use to do my job.</p>
<p>Here are the ideas that stuck the second time around.</p>
<h2 id="two-systems-two-kinds-of-model">Two systems, two kinds of model<a class="headerlink" href="#two-systems-two-kinds-of-model" title="Permanent link">#</a></h2>
<p>Kahneman&rsquo;s central framing is that we run two modes of thought. <strong>System 1</strong> is fast, automatic and associative: it reads faces, completes &ldquo;bread and&hellip;&rdquo; and flinches at loud noises. <strong>System 2</strong> is slow, effortful and deliberate: it multiplies 17 × 24 and checks whether an argument actually holds.</p>
<p>The tempting engineering analogy is obvious — a single forward pass of a language model looks a lot like System 1, and chain-of-thought or tool use looks a bit like System 2. I don&rsquo;t want to push the analogy too far; brains are not transformers. But it&rsquo;s a useful lens on <em>my</em> behavior: most of my judgments about a model&rsquo;s quality are System 1 judgments. I look at five outputs, they feel good, and a confident story assembles itself.</p>
<h2 id="wysiati-what-you-see-is-all-there-is">WYSIATI: What You See Is All There Is<a class="headerlink" href="#wysiati-what-you-see-is-all-there-is" title="Permanent link">#</a></h2>
<p>The most useful phrase in the book is Kahneman&rsquo;s acronym <strong>WYSIATI</strong>. System 1 builds the most coherent story it can from the information in front of it, and it does not stop to ask what information is missing.</p>
<p>In ML work, WYSIATI is the cherry-picked demo. It&rsquo;s the evaluation set that quietly shares a distribution with the training data. It&rsquo;s the dashboard that shows accuracy but not the slice where the model fails for a specific group of users. The story feels complete precisely because you can&rsquo;t see the gaps.</p>
<p>My practical defense is a habit I now apply before trusting any result: <em>write down what data would make this conclusion wrong, then go look for it.</em></p>
<h2 id="substitution">Substitution<a class="headerlink" href="#substitution" title="Permanent link">#</a></h2>
<p>When faced with a hard question, System 1 quietly answers an easier one. &ldquo;Is this model better for our users?&rdquo; becomes &ldquo;Do I like these outputs more?&rdquo; &ldquo;Will this project succeed?&rdquo; becomes &ldquo;Does the team seem excited?&rdquo;</p>
<p>Substitution is why vibes-based evaluation is so seductive. The easy question has an immediate, satisfying answer. Recognizing substitution doesn&rsquo;t make the hard question easier, but it does make me notice when I&rsquo;ve stopped asking it.</p>
<h2 id="the-planning-fallacy">The planning fallacy<a class="headerlink" href="#the-planning-fallacy" title="Permanent link">#</a></h2>
<p>Kahneman describes a curriculum project where the team estimated two years and took eight. The fix he proposes is the <strong>outside view</strong>: instead of reasoning from the specifics of your plan, ask how long projects <em>like this</em> usually take.</p>
<p>Every engineer has lived this. &ldquo;Fine-tune, evaluate, ship — two weeks.&rdquo; The inside view lists the steps; the outside view remembers that data cleaning alone took three weeks last time. I now try to estimate twice — once from the plan, once from the reference class — and take the uglier number seriously.</p>
<h2 id="where-the-book-has-aged">Where the book has aged<a class="headerlink" href="#where-the-book-has-aged" title="Permanent link">#</a></h2>
<p>It would be dishonest not to mention that some studies cited in the book, especially in the chapter on priming, have not replicated well. Kahneman himself acknowledged this. For me this made the book <em>more</em> useful, not less: it&rsquo;s a reminder that even careful experts fall for compelling stories with small samples. That&rsquo;s precisely the trap the book warns about, which is a strange and humbling kind of recursion.</p>
<h2 id="what-changed-in-how-i-work">What changed in how I work<a class="headerlink" href="#what-changed-in-how-i-work" title="Permanent link">#</a></h2>
<p>A few small habits came out of this reread:</p>
<ul>
<li><strong>Pre-register expectations.</strong> Before running an eval, I write down what result I expect. Surprises are only informative if you recorded what you expected.</li>
<li><strong>Look at failures first.</strong> Random samples of errors before any aggregate score.</li>
<li><strong>Slow down for irreversible decisions.</strong> System 1 is fine for choosing a variable name. It is not fine for deciding which model goes to production.</li>
</ul>
<p>The book&rsquo;s most lasting gift is not a list of biases to memorize — I forget most of them. It&rsquo;s a persistent, slightly uncomfortable awareness that the feeling of being sure is not evidence of anything.</p>]]></content:encoded></item>
<item><title>Why I&#x27;m starting this blog</title><link>https://meotomo.com/me/blog/why-im-starting-this-blog/</link><guid isPermaLink="true">https://meotomo.com/me/blog/why-im-starting-this-blog/</guid><pubDate>Mon, 05 Oct 2026 09:00:00 +0700</pubDate><category>Life/Other</category><category>Tech</category><description>Writing is how I find out what I actually think. Here&#x27;s what this blog is for, what I&#x27;ll write about, and what I won&#x27;t pretend to know.</description><content:encoded><![CDATA[<p>For years my notes lived in a graveyard of half-finished Markdown files, Jupyter notebooks named <code>untitled_final_v3.ipynb</code>, and chat messages to myself. Some of those notes were good. Most of them were never read again, including by me.</p>
<p>This blog is an attempt to fix that, and to hold myself to a slightly higher standard than &ldquo;it made sense at 2am&rdquo;.</p>
<h2 id="writing-as-debugging-for-thoughts">Writing as debugging for thoughts<a class="headerlink" href="#writing-as-debugging-for-thoughts" title="Permanent link">#</a></h2>
<p>When I explain a system to a colleague, I usually find the bug halfway through the sentence. Rubber-duck debugging works because speaking forces you to serialize a fuzzy mental model into a linear sequence of claims, and fuzzy models do not survive serialization.</p>
<p>Writing is the same trick with a longer attention span. If I can&rsquo;t write a clear paragraph about why an agent loop keeps failing, I probably don&rsquo;t understand why it keeps failing. The paragraph is the test.</p>
<p>So the first audience of this blog is me. If anyone else finds it useful, that&rsquo;s a bonus I&rsquo;ll gladly take.</p>
<h2 id="what-to-expect">What to expect<a class="headerlink" href="#what-to-expect" title="Permanent link">#</a></h2>
<p>I work as an AI engineer, mostly around large language models and agentic systems, so a good share of posts will be about that:</p>
<ul>
<li><strong>Tech and AI</strong> — practical notes on building with LLMs: evaluation, agents, latency, the unglamorous plumbing that decides whether a demo survives contact with real users. Code where it helps.</li>
<li><strong>Books</strong> — I read outside my field on purpose. Expect reflections on books like <em>Thinking, Fast and Slow</em> or <em>Meditations</em>, read through the eyes of someone who spends the day staring at loss curves.</li>
<li><strong>Philosophy and psychology</strong> — how old ideas about attention, judgment and character turn out to be surprisingly useful when you are debugging a model or yourself.</li>
<li><strong>Life and other things</strong> — the occasional post that doesn&rsquo;t fit anywhere else.</li>
</ul>
<p>You can filter by any of these on the <a href="../">blog page</a>, or subscribe via <a href="../feed.xml">RSS</a> if you still believe in RSS (I do).</p>
<h2 id="what-i-wont-do">What I won&rsquo;t do<a class="headerlink" href="#what-i-wont-do" title="Permanent link">#</a></h2>
<p>I won&rsquo;t write &ldquo;Top 10 AI tools you need in 2026&rdquo;. I won&rsquo;t summarize papers I haven&rsquo;t read carefully. And I&rsquo;ll try to be explicit about confidence: when something is a hunch, I&rsquo;ll call it a hunch; when it&rsquo;s a result I&rsquo;ve reproduced, I&rsquo;ll say so.</p>
<p>I also won&rsquo;t write about the internal details of the work I do for employers. The ideas here are my own, built from public knowledge, side projects and general experience — not anyone&rsquo;s confidential roadmap.</p>
<h2 id="a-note-on-being-wrong">A note on being wrong<a class="headerlink" href="#a-note-on-being-wrong" title="Permanent link">#</a></h2>
<p>A blog is a public record of what you believed at a certain time. Some of what I write will age badly; the field moves fast enough that a confident post about best practices can look naive within a year. I&rsquo;m fine with that. I would rather leave a trail of honest, dated thinking than a polished set of opinions that never risked anything.</p>
<p>If you spot a mistake, please tell me — the <a href="../../#contact">contact links</a> are on the homepage. Being corrected is the cheapest education there is.</p>
<h2 id="the-setup-briefly">The setup, briefly<a class="headerlink" href="#the-setup-briefly" title="Permanent link">#</a></h2>
<p>For the curious: this site is plain HTML and CSS. Posts are Markdown files with a bit of front matter; a small Python script turns them into pages, an RSS feed and a sitemap. No framework, no build step on the server. I wanted publishing to be as low-friction as writing, because friction is where good intentions go to die.</p>
<p>Let&rsquo;s see how long I keep this up. My goal is modest: one thoughtful post a month, written carefully enough that I would want to read it a year from now.</p>]]></content:encoded></item>
</channel>
</rss>
