<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Daryl Lim</title><description>I build AI that runs on your own machine, and agents that show their work.</description><link>https://darylalim.com/</link><language>en</language><item><title>The model never does the math</title><link>https://darylalim.com/blog/the-model-never-does-the-math/</link><guid isPermaLink="true">https://darylalim.com/blog/the-model-never-does-the-math/</guid><description>In my agent projects, every number comes from code, not the model. Here is how that works in a financial planner and a grant writer, and why it reaches further than arithmetic.</description><pubDate>Sun, 01 Nov 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;What’s the monthly payment on a $300,000 mortgage at 6% over 30 years? One reasonable-looking method gives $1,768.11. It’s in the right range, it’s to the cent, and it’s wrong. The bank’s figure is $1,798.65.&lt;/p&gt;
&lt;p&gt;The difference comes from one detail: how you turn an annual rate into a monthly one. It’s about $30 a month, nearly $11,000 over the life of the loan, and a reader has no way to spot it. A language model can make exactly this kind of mistake, with a confident explanation attached. That’s why my &lt;a href=&quot;https://github.com/darylalim/financial-planner-agent&quot;&gt;financial planner agent&lt;/a&gt; follows one rule above all others: &lt;strong&gt;the model never does the math.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-rule&quot;&gt;The rule&lt;/h2&gt;
&lt;p&gt;I built the planner to learn how language models and agents handle math calculations. It reads your own bank and brokerage exports, projects savings and retirement scenarios, and compares ways to pay off debt. It runs on Claude, but every figure it reports comes from a tool, never from the model’s own arithmetic. The system prompt states it plainly:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light-default github-dark-default&quot; style=&quot;--shiki-light-bg:#ffffff;--shiki-dark-bg:#0d1117;color:light-dark(#1f2328, #e6edf3);--shiki-light:#1f2328;--shiki-dark:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Never calculate a financial figure yourself. Not compound growth, not&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;a loan payment, not a payoff timeline, not an inflation adjustment.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Always call the calculator tool. A number you produce by reasoning&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;will look plausible and be wrong, and the person reading it cannot&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;tell the difference.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The prompt asks; the code makes it work. Most of the math lives in &lt;a href=&quot;https://github.com/darylalim/financial-planner-agent/blob/main/src/financial_planner/finance.py&quot;&gt;&lt;code&gt;finance.py&lt;/code&gt;&lt;/a&gt;, a plain Python module with no LangChain imports, and every calculation in it is pinned by a test.&lt;/p&gt;
&lt;h2 id=&quot;where-the-30-a-month-goes&quot;&gt;Where the $30 a month goes&lt;/h2&gt;
&lt;p&gt;Here are the two functions behind the mortgage example (docstrings shortened). They convert an annual rate to a monthly one in two different ways, on purpose:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light-default github-dark-default&quot; style=&quot;--shiki-light-bg:#ffffff;--shiki-dark-bg:#0d1117;color:light-dark(#1f2328, #e6edf3);--shiki-light:#1f2328;--shiki-dark:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:light-dark(#CF222E, #FF7B72);--shiki-light:#CF222E;--shiki-dark:#FF7B72&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:light-dark(#8250DF, #D2A8FF);--shiki-light:#8250DF;--shiki-dark:#D2A8FF&quot;&gt; monthly_rate&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;(annual_rate: &lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;float&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;) -&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;float&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:light-dark(#0A3069, #A5D6FF);--shiki-light:#0A3069;--shiki-dark:#A5D6FF&quot;&gt;    &quot;&quot;&quot;Convert an annual rate to its equivalent monthly compounding rate.&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;    _check_rate(&lt;/span&gt;&lt;span style=&quot;color:light-dark(#0A3069, #A5D6FF);--shiki-light:#0A3069;--shiki-dark:#A5D6FF&quot;&gt;&quot;annual_rate&quot;&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;, annual_rate)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:light-dark(#CF222E, #FF7B72);--shiki-light:#CF222E;--shiki-dark:#FF7B72&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;1.0&lt;/span&gt;&lt;span style=&quot;color:light-dark(#CF222E, #FF7B72);--shiki-light:#CF222E;--shiki-dark:#FF7B72&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt; annual_rate) &lt;/span&gt;&lt;span style=&quot;color:light-dark(#CF222E, #FF7B72);--shiki-light:#CF222E;--shiki-dark:#FF7B72&quot;&gt;**&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;1.0&lt;/span&gt;&lt;span style=&quot;color:light-dark(#CF222E, #FF7B72);--shiki-light:#CF222E;--shiki-dark:#FF7B72&quot;&gt; /&lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt; 12.0&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:light-dark(#CF222E, #FF7B72);--shiki-light:#CF222E;--shiki-dark:#FF7B72&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt; 1.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:light-dark(#CF222E, #FF7B72);--shiki-light:#CF222E;--shiki-dark:#FF7B72&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:light-dark(#8250DF, #D2A8FF);--shiki-light:#8250DF;--shiki-dark:#D2A8FF&quot;&gt; apr_monthly_rate&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;(apr: &lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;float&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;) -&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;float&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:light-dark(#0A3069, #A5D6FF);--shiki-light:#0A3069;--shiki-dark:#A5D6FF&quot;&gt;    &quot;&quot;&quot;Convert a quoted APR to a monthly rate the way lenders do: ``apr / 12``.&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;    _check_rate(&lt;/span&gt;&lt;span style=&quot;color:light-dark(#0A3069, #A5D6FF);--shiki-light:#0A3069;--shiki-dark:#A5D6FF&quot;&gt;&quot;apr&quot;&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;, apr)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:light-dark(#CF222E, #FF7B72);--shiki-light:#CF222E;--shiki-dark:#FF7B72&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt; apr &lt;/span&gt;&lt;span style=&quot;color:light-dark(#CF222E, #FF7B72);--shiki-light:#CF222E;--shiki-dark:#FF7B72&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt; 12.0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both are correct, for different things. An “expected 7% return” means 7% effective, so twelve monthly periods have to compound to exactly 7%. Dividing by 12 instead gives 7.23% a year, which overstates a 30-year projection. A loan’s APR is different: US lenders quote it as a nominal rate compounded monthly, so dividing by 12 is exactly what the bank does. Use the investment formula on the mortgage and you get the $1,768.11 from the start of this post.&lt;/p&gt;
&lt;p&gt;A model can know both conventions and still pick the wrong one in the middle of a long answer. A function can’t. And a test makes sure it never drifts:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light-default github-dark-default&quot; style=&quot;--shiki-light-bg:#ffffff;--shiki-dark-bg:#0d1117;color:light-dark(#1f2328, #e6edf3);--shiki-light:#1f2328;--shiki-dark:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:light-dark(#CF222E, #FF7B72);--shiki-light:#CF222E;--shiki-dark:#FF7B72&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;color:light-dark(#8250DF, #D2A8FF);--shiki-light:#8250DF;--shiki-dark:#D2A8FF&quot;&gt; test_matches_standard_mortgage_payment&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;(self):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:light-dark(#0A3069, #A5D6FF);--shiki-light:#0A3069;--shiki-dark:#A5D6FF&quot;&gt;    &quot;&quot;&quot;$300k at 6% over 30y is $1,798.65/mo on any lender&apos;s calculator.&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;    result &lt;/span&gt;&lt;span style=&quot;color:light-dark(#CF222E, #FF7B72);--shiki-light:#CF222E;--shiki-dark:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt; amortize_loan(&lt;/span&gt;&lt;span style=&quot;color:light-dark(#953800, #FFA657);--shiki-light:#953800;--shiki-dark:#FFA657&quot;&gt;principal&lt;/span&gt;&lt;span style=&quot;color:light-dark(#CF222E, #FF7B72);--shiki-light:#CF222E;--shiki-dark:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;300_000&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:light-dark(#953800, #FFA657);--shiki-light:#953800;--shiki-dark:#FFA657&quot;&gt;apr&lt;/span&gt;&lt;span style=&quot;color:light-dark(#CF222E, #FF7B72);--shiki-light:#CF222E;--shiki-dark:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;0.06&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:light-dark(#953800, #FFA657);--shiki-light:#953800;--shiki-dark:#FFA657&quot;&gt;years&lt;/span&gt;&lt;span style=&quot;color:light-dark(#CF222E, #FF7B72);--shiki-light:#CF222E;--shiki-dark:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;30&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:light-dark(#CF222E, #FF7B72);--shiki-light:#CF222E;--shiki-dark:#FF7B72&quot;&gt;    assert&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt; result.monthly_payment &lt;/span&gt;&lt;span style=&quot;color:light-dark(#CF222E, #FF7B72);--shiki-light:#CF222E;--shiki-dark:#FF7B72&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt; pytest.approx(&lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;1798.65&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:light-dark(#953800, #FFA657);--shiki-light:#953800;--shiki-dark:#FFA657&quot;&gt;abs&lt;/span&gt;&lt;span style=&quot;color:light-dark(#CF222E, #FF7B72);--shiki-light:#CF222E;--shiki-dark:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;0.01&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:light-dark(#CF222E, #FF7B72);--shiki-light:#CF222E;--shiki-dark:#FF7B72&quot;&gt;    assert&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt; result.months &lt;/span&gt;&lt;span style=&quot;color:light-dark(#CF222E, #FF7B72);--shiki-light:#CF222E;--shiki-dark:#FF7B72&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt; 360&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some tests pin values anyone can check, like that mortgage payment. Others pin properties that must always hold: paying off the highest rate first (avalanche) never costs more interest than paying off the smallest balance first (snowball).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;_check_rate&lt;/code&gt; is worth a mention too. It rejects any rate outside -100% to 100%, so if the model passes &lt;code&gt;7&lt;/code&gt; when it means &lt;code&gt;0.07&lt;/code&gt;, the tool returns an error instead of a projection at 700%.&lt;/p&gt;
&lt;h2 id=&quot;the-rule-reaches-further-than-youd-think&quot;&gt;The rule reaches further than you’d think&lt;/h2&gt;
&lt;p&gt;Once every number has to come from a tool, a few less obvious things follow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ratios count as math.&lt;/strong&gt; A savings rate or a category’s share of income is a division, so the model can’t do it either. The spending summary tool returns those ratios already, because the model can only follow the rule if a tool offers the number it needs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tools have to match how people talk.&lt;/strong&gt; The retirement tool used to take only an annual withdrawal. People say “I’ll spend $5,000 a month”, so the model multiplied by 12 itself and quoted the result, a number no tool had produced. Now the tool accepts either and returns both.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Summaries can break the rule.&lt;/strong&gt; In a long conversation, the agent replaces older turns with a summary. If the summary rounds or rewrites a figure, that figure comes back into the conversation with no tool behind it. So the planner uses its own summary prompt, which copies every amount, rate and date word for word, names the tool that produced each one, and never calculates.&lt;/p&gt;
&lt;h2 id=&quot;beyond-math-let-the-model-judge-but-make-it-show-its-evidence&quot;&gt;Beyond math: let the model judge, but make it show its evidence&lt;/h2&gt;
&lt;p&gt;The same idea works where the answer isn’t arithmetic. My &lt;a href=&quot;https://github.com/darylalim/grant-writer-agent&quot;&gt;grant writer agent&lt;/a&gt; finds funding opportunities and scores how well each one fits an organization. A fit score is a judgement, not a calculation, so the model has to be involved. The question is which part it does.&lt;/p&gt;
&lt;p&gt;I wrote grants at Pacific Gateway Center, a nonprofit in Honolulu, and the process was complex to coordinate, time-consuming and expensive. I built the grant writer to help nonprofits move through that work faster.&lt;/p&gt;
&lt;p&gt;The model rates each opportunity on six criteria, from eligibility to timeline, and for each one it writes a block like this, with a single verdict word and exact quotes as evidence:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light-default github-dark-default&quot; style=&quot;--shiki-light-bg:#ffffff;--shiki-dark-bg:#0d1117;color:light-dark(#1f2328, #e6edf3);--shiki-light:#1f2328;--shiki-dark:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;## eligibility&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- Verdict: STRONG&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- Citation (opportunity): &quot;&amp;lt;exact quote from the funding announcement&amp;gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- Citation (org profile): &quot;&amp;lt;exact quote from the organization&apos;s profile&amp;gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s all it writes. Each criterion has a weight in the code (&lt;a href=&quot;https://github.com/darylalim/grant-writer-agent/blob/main/src/grant_writer/opportunities.py&quot;&gt;&lt;code&gt;opportunities.py&lt;/code&gt;&lt;/a&gt;): eligibility 20, mission alignment 25, program fit 20, track record 15, award size 10 and timeline 10. The weights add up to 100, so the total reads as a percentage, and they never appear in the prompt. The verdict decides what share of a criterion’s weight it earns:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light-default github-dark-default&quot; style=&quot;--shiki-light-bg:#ffffff;--shiki-dark-bg:#0d1117;color:light-dark(#1f2328, #e6edf3);--shiki-light:#1f2328;--shiki-dark:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;python&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;_VERDICT_FRACTION&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;: dict[&lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;str&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;, tuple[&lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;]] &lt;/span&gt;&lt;span style=&quot;color:light-dark(#CF222E, #FF7B72);--shiki-light:#CF222E;--shiki-dark:#FF7B72&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:light-dark(#0A3069, #A5D6FF);--shiki-light:#0A3069;--shiki-dark:#A5D6FF&quot;&gt;    &quot;STRONG&quot;&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;: (&lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:light-dark(#0A3069, #A5D6FF);--shiki-light:#0A3069;--shiki-dark:#A5D6FF&quot;&gt;    &quot;MODERATE&quot;&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;: (&lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:light-dark(#0A3069, #A5D6FF);--shiki-light:#0A3069;--shiki-dark:#A5D6FF&quot;&gt;    &quot;WEAK&quot;&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;: (&lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:light-dark(#0A3069, #A5D6FF);--shiki-light:#0A3069;--shiki-dark:#A5D6FF&quot;&gt;    &quot;NONE&quot;&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;: (&lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:light-dark(#0550AE, #79C0FF);--shiki-light:#0550AE;--shiki-dark:#79C0FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:light-dark(#1F2328, #E6EDF3);--shiki-light:#1F2328;--shiki-dark:#E6EDF3&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So MODERATE on mission alignment earns 3/5 of 25, or 15 points. As the module’s own notes put it, a made-up total isn’t caught; it can’t be written down at all, because the format has nowhere to put one.&lt;/p&gt;
&lt;p&gt;Then the code checks the evidence. Every quote is looked up in the file it names, ignoring line wrapping, formatting and capitalization, and any quote that can’t be found there is flagged. If a fact is unknown, the model writes &lt;code&gt;[NEEDS INPUT: &amp;lt;question&amp;gt;]&lt;/code&gt; instead of inventing one.&lt;/p&gt;
&lt;h2 id=&quot;the-pattern&quot;&gt;The pattern&lt;/h2&gt;
&lt;p&gt;Both agents split the work the same way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The model&lt;/strong&gt; reads, decides which tool to call, judges with evidence, and explains the result in plain language.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The code&lt;/strong&gt; does anything a reader can’t check by eye: numbers, scores, and whether a quote really appears in its source.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tests&lt;/strong&gt; pin the code, so the rule holds after every change.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A prompt can ask a model not to do something. Code that gives it nowhere to do it is stronger, and that’s the kind of guarantee I want before anyone makes a decision based on what an agent tells them.&lt;/p&gt;
&lt;p&gt;If you’re building an agent, list every number and judgement it will show a person, and ask where each one comes from. Anything a reader can’t check by eye belongs in code, with a test behind it.&lt;/p&gt;
&lt;p&gt;If you work at a nonprofit and grant writing takes more time than you have, I’d like to hear from you. Find me on &lt;a href=&quot;https://www.linkedin.com/in/daryl-lim-862a815&quot;&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;
</content:encoded><category>Agents</category><category>Claude</category><category>Python</category></item><item><title>Why I build AI that runs on your own machine</title><link>https://darylalim.com/blog/why-i-build-ai-that-runs-on-your-own-machine/</link><guid isPermaLink="true">https://darylalim.com/blog/why-i-build-ai-that-runs-on-your-own-machine/</guid><description>Much of what I build runs entirely on a Mac. Here is why, what it costs, and when I use hosted models anyway.</description><pubDate>Sat, 31 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It started while I was learning to build applications with models from Hugging Face. I came across &lt;a href=&quot;https://huggingface.co/papers/2511.05502&quot;&gt;a paper comparing local inference on Apple Silicon&lt;/a&gt;, which benchmarked five runtimes on a Mac Studio: MLX, MLC-LLM, Ollama, llama.cpp and PyTorch MPS. MLX had the highest sustained generation throughput of the five, so I started experimenting with MLX models on my own Mac.&lt;/p&gt;
&lt;p&gt;Two things kept me there. I didn’t have to pay for hosted services or manage API keys, and my data stayed private.&lt;/p&gt;
&lt;p&gt;Many of my projects are built that way now. &lt;a href=&quot;https://github.com/darylalim/granit&quot;&gt;granit&lt;/a&gt;, for one, transcribes meetings, converts documents and answers questions with citations, and once the models are downloaded it makes no network requests at all.&lt;/p&gt;
&lt;h2 id=&quot;what-on-your-own-machine-means&quot;&gt;What “on your own machine” means&lt;/h2&gt;
&lt;p&gt;It means the model weights are downloaded once, from Hugging Face at pinned revisions, and after that your files never leave the Mac. On Apple Silicon, &lt;a href=&quot;https://github.com/ml-explore/mlx&quot;&gt;MLX&lt;/a&gt; makes this practical, and &lt;a href=&quot;https://github.com/Blaizzy/mlx-vlm&quot;&gt;mlx-vlm&lt;/a&gt;, an open-source library from &lt;a href=&quot;https://neywalabs.ai/&quot;&gt;Neywa Labs&lt;/a&gt;, does the same for vision models: a recent Mac has enough unified memory for everything granit uses, from a speech model and a document model to an 8B language model, embeddings, a reranker and a guard model.&lt;/p&gt;
&lt;h2 id=&quot;why-it-matters&quot;&gt;Why it matters&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Privacy becomes a property, not a promise.&lt;/strong&gt; A hosted service can promise not to keep your data. A program that never sends it doesn’t need to promise anything, and you can check it for yourself. For me, it matters most in two places: recordings of private meetings, and work with sensitive and confidential documents. Those are the two things granit was built to handle.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It works offline.&lt;/strong&gt; This one matters to me for a local reason. This year, severe weather and storms in Hawaii caused power and internet outages. A tool that depends on a cloud service stops the moment the connection drops; one that runs on the machine keeps working. I want to help local businesses with tools that solve their problems, and those tools should still work on the day the internet doesn’t.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It costs nothing per use.&lt;/strong&gt; Once a model is downloaded, running it a thousand times costs the same as running it once, so you can try things freely.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The model stays the same.&lt;/strong&gt; It can’t be quietly swapped, rate-limited or retired from under you. A pinned model gives the same kind of answer next year as it does today.&lt;/p&gt;
&lt;h2 id=&quot;what-it-costs&quot;&gt;What it costs&lt;/h2&gt;
&lt;p&gt;Local isn’t free, though. The costs come up front, and they’re real:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hardware.&lt;/strong&gt; granit needs an Apple Silicon Mac with 32 GB of memory. &lt;a href=&quot;https://github.com/darylalim/granite-document-intelligence&quot;&gt;Granite Document Intelligence&lt;/a&gt; fits in 16 GB, peaking at about 6.7 GB of model memory.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Disk and setup.&lt;/strong&gt; granit’s model weights take about 30 GB. The first run is a long download.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Speed.&lt;/strong&gt; Granite Document Intelligence reads about 10 pages a minute on an M2 Pro. A hosted service would be faster, and the paper that got me started reached the same conclusion: local runtimes on Apple Silicon still trail NVIDIA GPU systems such as vLLM, though they are becoming a real option for private inference.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Smaller models.&lt;/strong&gt; An 8B model on a Mac is not a frontier model in a data center. So granit leans on structure instead of size: cited answers, and a guard model that checks each answer against its sources.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;when-i-use-hosted-models-anyway&quot;&gt;When I use hosted models anyway&lt;/h2&gt;
&lt;p&gt;I start local, and reach for a hosted model when a task needs a bigger one. My agent projects use Claude for that reason. Some projects use other hosted services too: the &lt;a href=&quot;https://github.com/darylalim/rag-pipeline&quot;&gt;RAG pipeline&lt;/a&gt; stores and searches documents in MongoDB Atlas, and &lt;a href=&quot;https://github.com/darylalim/deepgram-clinical-transcription&quot;&gt;clinical transcription&lt;/a&gt; runs on Deepgram. When a project sends data somewhere, its README should say exactly what leaves your machine and where it goes, the way the RAG pipeline’s does. That’s the point: the choice should be yours, and you can only make it if you know.&lt;/p&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;
&lt;p&gt;Next, I want to build AI tools that work offline for businesses anywhere the connection can’t be taken for granted, starting at home in Hawaii. They should keep running through the next outage and keep private data on the business’s own machine.&lt;/p&gt;
&lt;p&gt;If you run a business and have a problem you think a tool like this could solve, I’d like to hear about it. Find me on &lt;a href=&quot;https://www.linkedin.com/in/daryl-lim-862a815&quot;&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;
</content:encoded><category>On-device AI</category><category>Privacy</category><category>MLX</category></item></channel></rss>