<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Lasantha Kularatne</title>
  <subtitle>Personal website of Lasantha Kularatne - Principal Software Engineer specializing in AI/ML, distributed systems, and reliability engineering</subtitle>
  <link href="https://www.lasantha.org/feed.xml" rel="self"/>
  <link href="https://www.lasantha.org/"/>
  <updated>2026-02-18T00:00:00Z</updated>
  <id>https://www.lasantha.org/</id>
  <author>
    <name>Lasantha Kularatne</name>
    <email>hello@lasantha.org</email>
  </author>
  <entry>
    <title>Future of Software Engineering — Thoughtworks</title>
    <link href="https://www.lasantha.org/blog/future-of-software-engineering-thoughtworks/"/>
    <updated>2026-02-18T00:00:00Z</updated>
    <id>https://www.lasantha.org/blog/future-of-software-engineering-thoughtworks/</id>
    <content xml:lang="en" type="html">&lt;h1&gt;Future of Software Engineering — Retreat Key Takeaways&lt;/h1&gt;
&lt;p&gt;Martin Fowler shared today on his blog page about the Thoughtworks Future of Software Development Retreat held in February 2026. As I read the details, one document captured my attention:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.thoughtworks.com/content/dam/thoughtworks/documents/report/tw_future%20_of_software_development_retreat_%20key_takeaways.pdf&quot;&gt;tw_future_of_software_development_retreat_key_takeaways.pdf&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I felt like, this is a good state-of-the-union for the current AI driven software development. I thought sharing a summerized view would be benefitial for everyone.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. Rigor Isn&#39;t Gone — It Moved&lt;/h2&gt;
&lt;p&gt;Engineering discipline hasn&#39;t disappeared; it migrated &lt;strong&gt;upstream to specs&lt;/strong&gt;, into &lt;strong&gt;test suites&lt;/strong&gt;, &lt;strong&gt;type systems&lt;/strong&gt;, &lt;strong&gt;risk mapping&lt;/strong&gt;, and &lt;strong&gt;continuous comprehension&lt;/strong&gt;. TDD is now the strongest form of prompt engineering — tests written before code prevent agents from cheating by validating broken behavior. Spec quality is now the highest-leverage artifact.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. There&#39;s a New &amp;quot;Middle Loop&amp;quot; Nobody Has Named Yet&lt;/h2&gt;
&lt;p&gt;We now operate in three loops — inner (coding), outer (CI/CD), and a newly forming &lt;strong&gt;middle loop&lt;/strong&gt;: directing, evaluating, and correcting AI agent output. The engineers thriving here think in delegation and orchestration, have strong architectural mental models, and can assess output quality without reading every line. Career ladders don&#39;t recognize this yet — and that&#39;s a problem.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Conway&#39;s Law Now Applies to Agents Too&lt;/h2&gt;
&lt;p&gt;Agent topology mirrors team topology. Agents can be cloned across teams instantly, but they &lt;strong&gt;drift&lt;/strong&gt; over time, accumulating different patterns per context. Speed mismatches are real — agents clear backlogs in days but then hit human-speed bottlenecks (architecture reviews, approvals). Decision fatigue is the new delivery constraint.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. Security Is Dangerously Underdeveloped&lt;/h2&gt;
&lt;p&gt;This received the lowest session attendance — which is itself the warning sign. Giving an agent email access alone is enough for full account takeover. Platform engineering must enforce &lt;strong&gt;secure defaults by design&lt;/strong&gt;, not rely on developers making safe choices individually. AI-speed attacks demand AI-speed defenses.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. Self-Healing Systems Are Still Far Off&lt;/h2&gt;
&lt;p&gt;The vision of agents autonomously resolving incidents is real — but the prerequisites aren&#39;t met. Most orgs lack: a full change ledger, agent identity/permission controls, and well-defined fitness functions. The hardest problem is &lt;strong&gt;latent knowledge&lt;/strong&gt; — senior engineers carry decades of pattern-matching in their heads that&#39;s almost never documented. Building an &amp;quot;agent subconscious&amp;quot; from years of post-mortems is the path forward.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6. Developer Productivity and Developer Experience Are Decoupling&lt;/h2&gt;
&lt;p&gt;Organizations can get more output from AI tools even when developers report &lt;strong&gt;lower satisfaction and higher cognitive load&lt;/strong&gt;. This weakens the business case for investing in developer experience — unless we reframe it as &lt;strong&gt;&amp;quot;agent experience.&amp;quot;&lt;/strong&gt; (Conveniently, the conditions that help agents perform well largely overlap with those that help humans perform well.)&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;7. Junior Devs Are More Valuable, Not Less&lt;/h2&gt;
&lt;p&gt;AI gets juniors past the initial net-negative phase faster. They&#39;re better at adopting AI tools than seniors. The real risk is &lt;strong&gt;mid-level engineers&lt;/strong&gt; who may lack the fundamentals to thrive — no one has solved retraining this cohort yet. Staff engineers, meanwhile, are most impactful as &lt;strong&gt;friction killers&lt;/strong&gt;, not just deep technical contributors.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;8. PM and Developer Roles Are Converging&lt;/h2&gt;
&lt;p&gt;Nobody can clearly define what PMs will do in an AI-first world. Some orgs are training PMs in Markdown and developer tooling. Others see roles diverging further. What&#39;s clear: AI is exposing &lt;strong&gt;pre-existing dysfunctions&lt;/strong&gt; in the PM-developer relationship, not creating new ones.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;9. Knowledge Graphs &amp;amp; Semantic Layers Are Having a Moment&lt;/h2&gt;
&lt;p&gt;Decades-old technologies are suddenly critical as the grounding layer for domain-aware agents. One example: a large telecom captured its entire domain ontology in ~286 concepts. LLMs can auto-generate event storming artifacts from legacy code, compressing weeks of discovery into days.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;10. Agile Is Evolving, Not Dying&lt;/h2&gt;
&lt;p&gt;Sprint cadences are compressing. XP practices (pair programming, ensemble development) are being rediscovered. The real threat is &lt;strong&gt;governance&lt;/strong&gt; — faster teams just hit the same approval walls sooner. Also flagged as a regression: AI is enabling large batch releases again, reversing a decade of DORA research on deployment stability.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Open Questions Left Unanswered&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;How do engineers find meaning when code-writing is no longer the job?&lt;/li&gt;
&lt;li&gt;Does faster agent output mean we need fewer (or differently-skilled) middle managers?&lt;/li&gt;
&lt;li&gt;Can test suites + constraints ever fully replace human code review?&lt;/li&gt;
&lt;li&gt;Are we accumulating &lt;strong&gt;cognitive debt&lt;/strong&gt; faster than we realize?&lt;/li&gt;
&lt;li&gt;How do we govern systems where agents move faster than humans can decide?&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
</content>
  </entry>
  <entry>
    <title>Fun with Shell Emojis</title>
    <link href="https://www.lasantha.org/blog/fun-with-shell-emojis/"/>
    <updated>2026-02-06T00:00:00Z</updated>
    <id>https://www.lasantha.org/blog/fun-with-shell-emojis/</id>
    <content xml:lang="en" type="html">&lt;p&gt;This article is nothing technical. I want to show the fun side of shell development.&lt;/p&gt;
&lt;p&gt;Your shell environment doesn&#39;t need to be boring. We can decorate the shell&#39;s user messages with fun emojis that are random each time. It&#39;s a small touch that helps break the monotony of everyday terminal work.&lt;/p&gt;
&lt;p&gt;Let me walk you through how to set this up on Linux, macOS, and Windows.&lt;/p&gt;
&lt;h2&gt;Linux (Debian/Ubuntu)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Shell environment:&lt;/strong&gt; Bash&lt;/p&gt;
&lt;p&gt;Add the following function to your &lt;code&gt;~/.bashrc&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;rand_emoji() {
  local imgs=(&amp;quot;😊&amp;quot; &amp;quot;👻&amp;quot; &amp;quot;😽&amp;quot; &amp;quot;😺&amp;quot; &amp;quot;😌&amp;quot; &amp;quot;🙃&amp;quot; &amp;quot;😃&amp;quot; &amp;quot;🎂&amp;quot; &amp;quot;😎&amp;quot; &amp;quot;🤗&amp;quot; &amp;quot;😈&amp;quot; &amp;quot;🤡&amp;quot; &amp;quot;😻&amp;quot; &amp;quot;💩&amp;quot; &amp;quot;🤓&amp;quot; &amp;quot;🥳&amp;quot; &amp;quot;🤩&amp;quot; &amp;quot;🤑&amp;quot; &amp;quot;🙀&amp;quot; &amp;quot;😱&amp;quot; &amp;quot;🙈&amp;quot; &amp;quot;🧙&amp;quot; &amp;quot;🦄&amp;quot; &amp;quot;🧚&amp;quot; &amp;quot;🤖&amp;quot; &amp;quot;🐶&amp;quot; &amp;quot;🥂&amp;quot; &amp;quot;🍭&amp;quot; &amp;quot;🍿&amp;quot; &amp;quot;🎉&amp;quot; &amp;quot;🎊&amp;quot; &amp;quot;🕺&amp;quot; &amp;quot;🏮&amp;quot; &amp;quot;🎏&amp;quot; &amp;quot;🪔&amp;quot; &amp;quot;🔮&amp;quot; &amp;quot;🏆&amp;quot;)

  # Bash arrays are 0-based by default.
  # $RANDOM % length returns a number from 0 to length-1.
  local img_id=$(( RANDOM % ${#imgs[@]} ))

  echo &amp;quot;${imgs[$img_id]:-🙂}&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;How it works&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;local imgs=(...)&lt;/code&gt;&lt;/strong&gt; — Declares a local array of emoji strings. Using &lt;code&gt;local&lt;/code&gt; keeps the variable scoped to the function so it doesn&#39;t leak into your shell session.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;${#imgs[@]}&lt;/code&gt;&lt;/strong&gt; — Returns the total number of elements in the array.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;$RANDOM&lt;/code&gt;&lt;/strong&gt; — A built-in Bash variable that produces a random integer between 0 and 32767 each time it&#39;s referenced.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;$(( RANDOM % ${#imgs[@]} ))&lt;/code&gt;&lt;/strong&gt; — The modulo (&lt;code&gt;%&lt;/code&gt;) operation constrains the random number to a valid array index (0 to length-1), since Bash arrays are zero-based.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;${imgs[$img_id]:-🙂}&lt;/code&gt;&lt;/strong&gt; — Looks up the emoji at the random index. The &lt;code&gt;:-&lt;/code&gt; syntax is a fallback — if the value is empty or unset for any reason, it defaults to 🙂.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;macOS&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Shell environment:&lt;/strong&gt; Zsh&lt;/p&gt;
&lt;p&gt;Add the following function to your &lt;code&gt;~/.zshrc&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-zsh&quot;&gt;rand_emoji() {
  local imgs=(&amp;quot;😊&amp;quot; &amp;quot;👻&amp;quot; &amp;quot;😽&amp;quot; &amp;quot;😺&amp;quot; &amp;quot;😌&amp;quot; &amp;quot;🙃&amp;quot; &amp;quot;😃&amp;quot; &amp;quot;🎂&amp;quot; &amp;quot;😎&amp;quot; &amp;quot;🤗&amp;quot; &amp;quot;😈&amp;quot; &amp;quot;🤡&amp;quot; &amp;quot;😻&amp;quot; &amp;quot;💩&amp;quot; &amp;quot;🤓&amp;quot; &amp;quot;🥳&amp;quot; &amp;quot;🤩&amp;quot; &amp;quot;🤑&amp;quot; &amp;quot;🙀&amp;quot; &amp;quot;😱&amp;quot; &amp;quot;🙈&amp;quot; &amp;quot;🧙&amp;quot; &amp;quot;🦄&amp;quot; &amp;quot;🧚&amp;quot; &amp;quot;🤖&amp;quot; &amp;quot;🐶&amp;quot; &amp;quot;🥂&amp;quot; &amp;quot;🍭&amp;quot; &amp;quot;🍿&amp;quot; &amp;quot;🎉&amp;quot; &amp;quot;🎊&amp;quot; &amp;quot;🕺&amp;quot; &amp;quot;🏮&amp;quot; &amp;quot;🎏&amp;quot; &amp;quot;🪔&amp;quot; &amp;quot;🔮&amp;quot; &amp;quot;🏆&amp;quot;)

  # Zsh arrays are 1-based.
  # We add +1 so the range becomes 1 to Length (instead of 0 to Length-1)
  local img_id=$(( ($RANDOM % ${#imgs[@]}) + 1 ))

  echo &amp;quot;${imgs[$img_id]:-🙂}&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;How it works&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;local imgs=(...)&lt;/code&gt;&lt;/strong&gt; — Same as Bash, declares a local array of emojis.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;${#imgs[@]}&lt;/code&gt;&lt;/strong&gt; — Returns the array length, just like in Bash.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;$RANDOM&lt;/code&gt;&lt;/strong&gt; — Works the same way as in Bash, producing a random integer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;$(( ($RANDOM % ${#imgs[@]}) + 1 ))&lt;/code&gt;&lt;/strong&gt; — This is where Zsh differs. Zsh arrays are &lt;strong&gt;1-based&lt;/strong&gt; (the first element is at index 1, not 0). The modulo gives us 0 to length-1, so we add 1 to shift the range to 1 through length — matching Zsh&#39;s indexing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;${imgs[$img_id]:-🙂}&lt;/code&gt;&lt;/strong&gt; — Looks up the emoji at the random index. The :- syntax is a fallback — if the value is empty or unset for any reason, it defaults to 🙂.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Windows&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Shell environment:&lt;/strong&gt; PowerShell&lt;/p&gt;
&lt;p&gt;Add the following function to your PowerShell profile (&lt;code&gt;$PROFILE&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;function rand_emoji {
	$emojis = @(&#39;😊&#39;, &#39;👻&#39;, &#39;😽&#39;, &#39;😺&#39;, &#39;😌&#39;, &#39;🙃&#39;, &#39;😃&#39;, &#39;🎂&#39;, &#39;😎&#39;, &#39;🤗&#39;, &#39;😈&#39;, &#39;🤡&#39;, &#39;😻&#39;, &#39;💩&#39;, &#39;🤓&#39;, &#39;🥳&#39;, &#39;🤩&#39;, &#39;🤑&#39;, &#39;🙀&#39;, &#39;😱&#39;, &#39;🙈&#39;, &#39;🧙&#39;, &#39;🦄&#39;, &#39;🧚&#39;, &#39;🤖&#39;, &#39;🐶&#39;, &#39;🥂&#39;, &#39;🍭&#39;, &#39;🍿&#39;, &#39;🎉&#39;, &#39;🎊&#39;, &#39;🕺&#39;, &#39;🏮&#39;, &#39;🎏&#39;, &#39;🪔&#39;, &#39;🔮&#39;, &#39;🏆&#39;)
	return $emojis[(Get-Random -Minimum 0 -Maximum $emojis.Count)]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;How it works&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;$emojis = @(...)&lt;/code&gt;&lt;/strong&gt; — Creates a PowerShell array of emoji strings. The &lt;code&gt;@()&lt;/code&gt; syntax explicitly defines an array. Unlike Bash/Zsh, PowerShell variables declared inside a function are local by default — no &lt;code&gt;local&lt;/code&gt; keyword needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;$emojis.Count&lt;/code&gt;&lt;/strong&gt; — Returns the number of elements in the array. PowerShell arrays are zero-based, like Bash.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Get-Random -Minimum 0 -Maximum $emojis.Count&lt;/code&gt;&lt;/strong&gt; — PowerShell&#39;s built-in cmdlet for generating random numbers. The &lt;code&gt;-Minimum&lt;/code&gt; is inclusive and &lt;code&gt;-Maximum&lt;/code&gt; is exclusive, so this produces a value from 0 to length-1 — exactly what we need for a zero-based index.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;$emojis[...]&lt;/code&gt;&lt;/strong&gt; — Standard array indexing to retrieve the emoji at the random position.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Try it out&lt;/h2&gt;
&lt;p&gt;Once you&#39;ve added the function to your shell config, reload it (or open a new terminal) and run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;echo &amp;quot;Hi $(rand_emoji)&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each time you run it, you&#39;ll get a different greeting — &lt;code&gt;Hi 🦄&lt;/code&gt;, &lt;code&gt;Hi 💩&lt;/code&gt;, &lt;code&gt;Hi 🙃&lt;/code&gt;, and so on.&lt;/p&gt;
&lt;h2&gt;What can you do with this?&lt;/h2&gt;
&lt;p&gt;Here are a few ways to put &lt;code&gt;rand_emoji&lt;/code&gt; to use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Customize your shell prompt&lt;/strong&gt; — Prepend a random emoji to your prompt string (&lt;code&gt;PS1&lt;/code&gt; in Bash/Zsh, &lt;code&gt;prompt&lt;/code&gt; function in PowerShell) so every new command line feels a little different.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Liven up script output&lt;/strong&gt; — Sprinkle &lt;code&gt;rand_emoji&lt;/code&gt; into your build scripts, deployment tools, or CI logs to make status messages more glanceable and less wall-of-text.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Git commit hooks&lt;/strong&gt; — Add an emoji to your pre-commit or post-commit messages for a quick visual cue in your log history.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Why bother?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;It reduces monotony&lt;/strong&gt; — Staring at a plain terminal all day gets old. A small, random visual change keeps things just interesting enough to stay engaged.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It&#39;s a low-effort morale boost&lt;/strong&gt; — It takes two minutes to set up and costs nothing. A tiny 💩 or 🦄 popping up in your prompt can genuinely make you smile mid-debugging-session.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It&#39;s a gateway to shell customization&lt;/strong&gt; — Once you start tweaking your shell config for fun, you&#39;ll naturally learn more about how your environment works — arrays, variables, random numbers, profile scripts — all useful knowledge.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Make your shell environment more fun --
Try it!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Cost-Effective Multi-Agent AI: Separating Reasoning from Execution</title>
    <link href="https://www.lasantha.org/blog/cost-effective-multi-agent-ai-separating-reasoning-from-execution/"/>
    <updated>2026-01-31T00:00:00Z</updated>
    <id>https://www.lasantha.org/blog/cost-effective-multi-agent-ai-separating-reasoning-from-execution/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Building AI agents that actually work in production is expensive. Every API call to GPT-4 or Claude costs money, and when you&#39;re running agentic loops with multiple tool calls, those costs add up fast. But what if you could use expensive models only where they truly matter, and run everything else locally for free?&lt;/p&gt;
&lt;p&gt;That&#39;s exactly what I built. A multi-agent system that uses powerful cloud LLMs for planning and tiny local models for execution.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/lasanthak/ai-agent-strands&quot; class=&quot;button is-primary is-small&quot;&gt;&lt;i class=&quot;fab fa-github mr-2&quot;&gt;&lt;/i&gt;View Code on GitHub&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;The Problem with Current Approaches&lt;/h2&gt;
&lt;p&gt;Most AI agent frameworks treat all tasks equally. Need to plan a complex workflow? Call GPT-4. Need to fetch weather data? Call GPT-4. Need to format a response? Call GPT-4 again.&lt;/p&gt;
&lt;p&gt;This is wasteful. Planning and decomposition require genuine intelligence. But once you have a clear, small task like &amp;quot;Call this API and extract the temperature&amp;quot;, a 2B parameter model running on your laptop can handle it just fine.&lt;/p&gt;
&lt;p&gt;There&#39;s also a privacy angle. Your reasoning agent only needs to understand the problem structure. It doesn&#39;t need to see customer PII, payment data, or internal API responses. By splitting reasoning from execution, sensitive data never leaves your infrastructure.&lt;/p&gt;
&lt;h2&gt;The Architecture&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB
    subgraph Cloud[&amp;quot;☁️ Cloud / Powerful Model&amp;quot;]
        RA[&amp;quot;🧠 Reasoning Agent&amp;lt;br/&amp;gt;(external LLM)&amp;lt;br/&amp;gt;───────────&amp;lt;br/&amp;gt;• Decomposes problems&amp;lt;br/&amp;gt;• No tool access&amp;lt;br/&amp;gt;• No sensitive data&amp;quot;]
    end
    
    User[&amp;quot;👤 User Query&amp;quot;] --&amp;gt; RA
    
    RA --&amp;gt;|&amp;quot;Task 1&amp;quot;| EA1
    RA --&amp;gt;|&amp;quot;Task 2&amp;quot;| EA2
    RA --&amp;gt;|&amp;quot;Task 3&amp;quot;| EA3
    
    subgraph Local[&amp;quot;🏠 Local Infrastructure&amp;quot;]
        EA1[&amp;quot;⚡ Execution Agent 1&amp;lt;br/&amp;gt;(local LLM)&amp;quot;]
        EA2[&amp;quot;⚡ Execution Agent 2&amp;lt;br/&amp;gt;(local LLM)&amp;quot;]
        EA3[&amp;quot;⚡ Execution Agent 3&amp;lt;br/&amp;gt;(local LLM)&amp;quot;]
        
        EA1 --&amp;gt; Tools1[&amp;quot;🔧 Weather API&amp;quot;]
        EA2 --&amp;gt; Tools2[&amp;quot;🔧 City Database&amp;quot;]
        EA3 --&amp;gt; Tools3[&amp;quot;🔧 Internal APIs&amp;quot;]
    end
    
    EA1 --&amp;gt;|&amp;quot;Result 1&amp;quot;| Synth
    EA2 --&amp;gt;|&amp;quot;Result 2&amp;quot;| Synth
    EA3 --&amp;gt;|&amp;quot;Result 3&amp;quot;| Synth
    
    Synth[&amp;quot;📋 Synthesize Results&amp;quot;] --&amp;gt; Response[&amp;quot;✅ Final Response&amp;quot;]
    
    style Cloud fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    style Local fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
    style RA fill:#fff3e0,stroke:#f57c00,stroke-width:2px
    style EA1 fill:#f3e5f5,stroke:#7b1fa2
    style EA2 fill:#f3e5f5,stroke:#7b1fa2
    style EA3 fill:#f3e5f5,stroke:#7b1fa2

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The system has two types of agents:&lt;/p&gt;
&lt;p&gt;🧠 &lt;strong&gt;Reasoning Agent&lt;/strong&gt; - Uses a capable model (like cloud-based &lt;code&gt;GPT-4o&lt;/code&gt; or local &lt;code&gt;gemma3:12b&lt;/code&gt;) to decompose complex problems into independent, atomic tasks. This agent never touches sensitive data or makes tool calls. It just plans.&lt;/p&gt;
&lt;p&gt;⚡ &lt;strong&gt;Execution Agents&lt;/strong&gt; - Use lightweight local models (like local &lt;code&gt;granite4:tiny-h&lt;/code&gt; with tool-call support) to execute individual tasks. These agents have access to tools and APIs, handle sensitive data, and run entirely on your hardware.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from strands import Agent
from strands.models.openai import OpenAIModel
from strands.models.ollama import OllamaModel

# Reasoning agent - smarter model, no tools, no sensitive data
reasoning_model = OpenAIModel(
        model_name=&amp;quot;gpt-4o&amp;quot;,
        temperature=0.5
    )
reasoning_agent = Agent(
        model=reasoning_model,
        system_prompt=&amp;quot;&amp;quot;&amp;quot;You are a planning agent. Break down complex 
        problems into small, independent tasks. Output a JSON list of 
        tasks. Do NOT execute anything yourself.&amp;quot;&amp;quot;&amp;quot;
    )

# Execution agent - tiny model, full tool access
execution_model = OllamaModel(
        host=&amp;quot;http://localhost:11434&amp;quot;, 
        model_id=&amp;quot;granite4:tiny-h&amp;quot;,
        temperature=0.3
    )
execution_agent = Agent(
        model=execution_model,
        tools=[get_city_info, get_weather, search_books]
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;How It Works&lt;/h2&gt;
&lt;p&gt;Take a query like &amp;quot;Find cities similar to Austin, TX based on climate and population.&amp;quot;&lt;/p&gt;
&lt;p&gt;The reasoning agent breaks this into atomic tasks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get Austin&#39;s weather data&lt;/li&gt;
&lt;li&gt;Get Austin&#39;s population&lt;/li&gt;
&lt;li&gt;Search for cities with similar climate&lt;/li&gt;
&lt;li&gt;Search for cities with similar population&lt;/li&gt;
&lt;li&gt;Find the intersection&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each task is then dispatched to an execution agent. These can run in parallel since the tasks are independent.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def orchestrate(user_query: str):
    # Step 1: Plan with the smart model
    plan = reasoning_agent(f&amp;quot;Break this into tasks: {user_query}&amp;quot;)
    tasks = json.loads(plan.message)
    
    # Step 2: Execute with cheap local models
    results = []
    for task in tasks:
        result = execution_agent(task[&amp;quot;instruction&amp;quot;])
        results.append(result)
    
    # Step 3: Synthesize (can use reasoning or execution agent)
    return execution_agent(f&amp;quot;Combine these results: {results}&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Real Examples&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Example 1: Similar Cities Finder&lt;/strong&gt; (&lt;a href=&quot;https://github.com/lasanthak/ai-agent-strands/blob/main/examples/sample1_cities_gemma3_granite4tiny.txt&quot;&gt;output&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;I used &lt;code&gt;gemma3:12b&lt;/code&gt; for reasoning and &lt;code&gt;granite4:tiny-h&lt;/code&gt; for execution. The reasoning agent decomposed &amp;quot;find similar cities to Austin, TX&amp;quot; into weather lookups, population queries, and comparison tasks. Execution agents made the actual API calls.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example 2: Book Recommendations with Geopolitical Context&lt;/strong&gt; (&lt;a href=&quot;https://github.com/lasanthak/ai-agent-strands/blob/main/examples/sample2_books_granite4tiny.txt&quot;&gt;output&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;For another complex task combining book data with current events, I used &lt;code&gt;granite4:tiny-h&lt;/code&gt; for both reasoning and execution. The key insight: even a tiny model can plan effectively when the problem domain is well-defined and the output format is constrained.&lt;/p&gt;
&lt;h2&gt;The Trade-offs&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Significant cost reduction. Cloud API calls drop by 80-90% in typical workflows&lt;/li&gt;
&lt;li&gt;Privacy by design. Sensitive data stays on your infrastructure&lt;/li&gt;
&lt;li&gt;Parallelization. Independent tasks can run concurrently&lt;/li&gt;
&lt;li&gt;Fault isolation. One failed execution doesn&#39;t break the whole workflow&lt;/li&gt;
&lt;li&gt;Model flexibility. Swap reasoning or execution models without changing architecture&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Latency overhead. The planning step adds one round-trip before execution starts&lt;/li&gt;
&lt;li&gt;Task decomposition quality matters. Bad plans lead to bad results (garbage in, garbage out)&lt;/li&gt;
&lt;li&gt;Local model limitations. Some tasks genuinely need stronger models&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Where This Applies&lt;/h2&gt;
&lt;p&gt;This pattern works well for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Enterprise workflows&lt;/strong&gt; with strict data residency requirements&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;High-volume applications&lt;/strong&gt; where API costs are a real concern&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hybrid cloud setups&lt;/strong&gt; where you want cloud intelligence with local execution&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regulated industries&lt;/strong&gt; (healthcare, finance) where data exposure is a compliance issue&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Edge deployments&lt;/strong&gt; where bandwidth to cloud APIs is limited&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&#39;s less suitable for tasks that require tight reasoning-execution coupling, or when the problem can&#39;t be cleanly decomposed.&lt;/p&gt;
&lt;h2&gt;Technical Notes&lt;/h2&gt;
&lt;p&gt;I built this using the &lt;a href=&quot;https://github.com/strands-agents/sdk-python&quot;&gt;Strands Agents SDK&lt;/a&gt;, which handles the agentic loop, tool calling, and model abstraction cleanly. Strands supports Ollama, LM Studio, llama.cpp, and cloud providers through a unified interface, making it easy to mix and match models.&lt;/p&gt;
&lt;p&gt;The key to making this work is good task decomposition prompts. The reasoning agent needs clear instructions on output format and what constitutes an &amp;quot;atomic&amp;quot; task. I found structured output (JSON or XML-like tagging) for explicit task breakdown works better than free-form text.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Not every AI task needs a frontier model. By separating reasoning from execution, you can build agents that are cheaper, faster, more private, and often just as effective. The cloud LLM handles the hard part—understanding what to do. Local models handle the easy part—actually doing it.&lt;/p&gt;
&lt;p&gt;The infrastructure is ready. Ollama makes running local models trivial. Strands makes building agents straightforward. The missing piece was the architecture to combine them effectively.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Think big, execute small.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Understanding the Monad Design Pattern in Kotlin</title>
    <link href="https://www.lasantha.org/blog/understanding-the-monad-design-pattern-in-kotlin/"/>
    <updated>2026-01-10T00:00:00Z</updated>
    <id>https://www.lasantha.org/blog/understanding-the-monad-design-pattern-in-kotlin/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Monads have a reputation for being difficult to understand, often explained with abstract mathematical concepts that leave developers more confused than enlightened. In this post, I&#39;ll take a practical approach - implementing monads in Kotlin to show how they solve real problems.&lt;/p&gt;
&lt;h2&gt;What Problem Do Monads Solve?&lt;/h2&gt;
&lt;p&gt;Consider this common scenario: you have a chain of operations, each of which might fail or return null. Traditional approaches lead to nested null checks or try-catch blocks:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun getUserEmail(userId: String): String? {
    val user = findUser(userId) ?: return null
    val profile = user.profile ?: return null
    val email = profile.email ?: return null
    return email
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Monads provide a cleaner way to chain these operations while handling the &amp;quot;failure&amp;quot; case automatically.&lt;/p&gt;
&lt;h2&gt;Starting with Functors&lt;/h2&gt;
&lt;p&gt;Before understanding monads, we need to understand functors. A functor is simply a container that supports a &lt;code&gt;map&lt;/code&gt; operation - it lets you transform the value inside without unwrapping it manually.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;interface Functor&amp;lt;out A&amp;gt; {
    fun &amp;lt;B&amp;gt; map(f: (A) -&amp;gt; B): Functor&amp;lt;B&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From category theory, functors must satisfy two laws:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Identity&lt;/strong&gt;: &lt;code&gt;functor.map { it }&lt;/code&gt; should equal &lt;code&gt;functor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Composition&lt;/strong&gt;: &lt;code&gt;functor.map(f).map(g)&lt;/code&gt; should equal &lt;code&gt;functor.map { g(f(it)) }&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The Monad Interface&lt;/h2&gt;
&lt;p&gt;A monad extends functor by adding &lt;code&gt;flatMap&lt;/code&gt; (also known as &lt;code&gt;bind&lt;/code&gt;). This is the key operation that enables chaining:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;interface Monad&amp;lt;out A&amp;gt; : Functor&amp;lt;A&amp;gt; {
    override fun &amp;lt;B&amp;gt; map(f: (A) -&amp;gt; B): Monad&amp;lt;B&amp;gt;
    fun &amp;lt;B&amp;gt; flatMap(f: (A) -&amp;gt; Monad&amp;lt;B&amp;gt;): Monad&amp;lt;B&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The difference between &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;flatMap&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;map&lt;/code&gt; transforms &lt;code&gt;A -&amp;gt; B&lt;/code&gt; inside the container&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flatMap&lt;/code&gt; transforms &lt;code&gt;A -&amp;gt; Monad&amp;lt;B&amp;gt;&lt;/code&gt;, then flattens the result&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Implementing MayBe: A Practical Monad&lt;/h2&gt;
&lt;p&gt;Let&#39;s implement a &lt;code&gt;MayBe&lt;/code&gt; monad (inspired by Haskell&#39;s &lt;code&gt;Maybe&lt;/code&gt;) for handling optional values:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;sealed interface MayBe&amp;lt;out A&amp;gt; {
    fun &amp;lt;B&amp;gt; map(f: (A) -&amp;gt; B): MayBe&amp;lt;B&amp;gt;
    fun &amp;lt;B&amp;gt; flatMap(f: (A) -&amp;gt; MayBe&amp;lt;B&amp;gt;): MayBe&amp;lt;B&amp;gt;
    val isJust: Boolean

    fun getOrNull() = when (this) {
        is Just -&amp;gt; value
        is None -&amp;gt; null
    }

    data class Just&amp;lt;out A&amp;gt;(val value: A) : MayBe&amp;lt;A&amp;gt; {
        override fun &amp;lt;B&amp;gt; map(f: (A) -&amp;gt; B) = Just(f(value))
        override fun &amp;lt;B&amp;gt; flatMap(f: (A) -&amp;gt; MayBe&amp;lt;B&amp;gt;) = f(value)
        override val isJust = true
    }

    data object None : MayBe&amp;lt;Nothing&amp;gt; {
        override fun &amp;lt;B&amp;gt; map(f: (Nothing) -&amp;gt; B) = None
        override fun &amp;lt;B&amp;gt; flatMap(f: (Nothing) -&amp;gt; MayBe&amp;lt;B&amp;gt;) = None
        override val isJust = false
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The beauty here is in how &lt;code&gt;None&lt;/code&gt; handles operations - it simply returns &lt;code&gt;None&lt;/code&gt;, short-circuiting the entire chain without explicit null checks.&lt;/p&gt;
&lt;h2&gt;An Alternative: Optional Monad&lt;/h2&gt;
&lt;p&gt;Here&#39;s another implementation with a factory method for easy construction:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;sealed interface Optional&amp;lt;out A&amp;gt; {
    fun &amp;lt;B&amp;gt; map(f: (A) -&amp;gt; B): Optional&amp;lt;B&amp;gt;
    fun &amp;lt;B&amp;gt; flatMap(f: (A) -&amp;gt; Optional&amp;lt;B&amp;gt;): Optional&amp;lt;B&amp;gt;

    fun getOrNull() = when (this) {
        is Some -&amp;gt; value
        is None -&amp;gt; null
    }

    companion object {
        fun &amp;lt;B&amp;gt; of(value: B?): Optional&amp;lt;B&amp;gt; =
            if (value == null) None else Some(value)
    }

    data class Some&amp;lt;out A&amp;gt;(val value: A) : Optional&amp;lt;A&amp;gt; {
        override fun &amp;lt;B&amp;gt; map(f: (A) -&amp;gt; B) = of(f(value))
        override fun &amp;lt;B&amp;gt; flatMap(f: (A) -&amp;gt; Optional&amp;lt;B&amp;gt;) = f(value)
    }

    data object None : Optional&amp;lt;Nothing&amp;gt; {
        override fun &amp;lt;B&amp;gt; map(f: (Nothing) -&amp;gt; B) = None
        override fun &amp;lt;B&amp;gt; flatMap(f: (Nothing) -&amp;gt; Optional&amp;lt;B&amp;gt;) = None
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Using Monads in Practice&lt;/h2&gt;
&lt;p&gt;Now our earlier example becomes elegant:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun getUserEmail(userId: String): Optional&amp;lt;String&amp;gt; {
    return Optional.of(findUser(userId))
        .map { it.profile }
        .map { it.email }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice how clean this is - just chained &lt;code&gt;map&lt;/code&gt; calls! This works because our &lt;code&gt;Optional.map&lt;/code&gt; implementation uses &lt;code&gt;of()&lt;/code&gt; internally:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;override fun &amp;lt;B&amp;gt; map(f: (A) -&amp;gt; B) = of(f(value))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If any step returns null, &lt;code&gt;of()&lt;/code&gt; converts it to &lt;code&gt;None&lt;/code&gt;, and subsequent operations short-circuit automatically.&lt;/p&gt;
&lt;p&gt;So when do you need &lt;code&gt;flatMap&lt;/code&gt;? When your function already returns an &lt;code&gt;Optional&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun findUserProfile(userId: String): Optional&amp;lt;Profile&amp;gt; { ... }

fun getUserEmail(userId: String): Optional&amp;lt;String&amp;gt; {
    return Optional.of(findUser(userId))
        .flatMap { findUserProfile(it.id) }  // returns Optional&amp;lt;Profile&amp;gt;
        .map { it.email }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The rule:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;map&lt;/code&gt;&lt;/strong&gt; - when your function returns a plain value (&lt;code&gt;A -&amp;gt; B&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;flatMap&lt;/code&gt;&lt;/strong&gt; - when your function already returns an &lt;code&gt;Optional&amp;lt;B&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No null checks, no early returns - just a clean pipeline of transformations.&lt;/p&gt;
&lt;h2&gt;Why Not Just Use Kotlin&#39;s Nullable Types?&lt;/h2&gt;
&lt;p&gt;Kotlin&#39;s &lt;code&gt;?.&lt;/code&gt; safe call operator is excellent, but monads offer additional benefits:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Composability&lt;/strong&gt; - Monads can be combined and chained in powerful ways&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Abstraction&lt;/strong&gt; - The same pattern works for error handling, async operations, and more&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explicit semantics&lt;/strong&gt; - &lt;code&gt;Optional.None&lt;/code&gt; vs &lt;code&gt;null&lt;/code&gt; makes intent clearer&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Railway-oriented programming&lt;/strong&gt; - Easy to build pipelines with automatic error propagation&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Beyond Optional: Other Monads&lt;/h2&gt;
&lt;p&gt;The monad pattern applies to many scenarios:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Result/Either&lt;/strong&gt; - For operations that can fail with an error&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IO&lt;/strong&gt; - For encapsulating side effects&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Future/Promise&lt;/strong&gt; - For async operations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;List&lt;/strong&gt; - Yes, lists are monads too!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Source Code&lt;/h2&gt;
&lt;p&gt;The full implementation is available on GitHub: &lt;strong&gt;&lt;a href=&quot;https://github.com/lasanthak/kotlin-demo/tree/main/src/main/kotlin/org/lasantha/kotlindemo/functional_programming&quot;&gt;kotlin-demo&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The repository includes additional functional programming examples like &lt;code&gt;ProducerFunctor&lt;/code&gt; and &lt;code&gt;TripletFunctor&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Monads aren&#39;t magic - they&#39;re a design pattern for composing operations on wrapped values. Once you understand &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;flatMap&lt;/code&gt;, you&#39;ve understood the core concept. The rest is just applying it to different scenarios.&lt;/p&gt;
&lt;p&gt;The key insight is that monads let you focus on the &amp;quot;happy path&amp;quot; while the container handles the edge cases (null, errors, async completion) automatically.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Monad_(functional_programming)&quot;&gt;Monad (functional programming) - Wikipedia&lt;/a&gt; - Comprehensive overview of monads in functional programming&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html&quot;&gt;Functors, Applicatives, And Monads In Pictures&lt;/a&gt; - Excellent visual guide to understanding these concepts&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ericlippert.com/2013/02/21/monads-part-one/&quot;&gt;Monads, Part One - Eric Lippert&lt;/a&gt; - In-depth series on monads from a C# perspective&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Happy functional programming!&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Generating Mandelbrot Set Images with Kotlin</title>
    <link href="https://www.lasantha.org/blog/generating-mandelbrot-set-images-with-kotlin/"/>
    <updated>2025-09-15T00:00:00Z</updated>
    <id>https://www.lasantha.org/blog/generating-mandelbrot-set-images-with-kotlin/</id>
    <content xml:lang="en" type="html">&lt;p&gt;There&#39;s something deeply satisfying about generating fractal images through code. The Mandelbrot set, in particular, has fascinated mathematicians and programmers alike since Benoit Mandelbrot first visualized it in 1980. I built a Kotlin library to explore this mathematical wonder, and I&#39;d like to share both the results and the approach.&lt;/p&gt;
&lt;h2&gt;What is the Mandelbrot Set?&lt;/h2&gt;
&lt;p&gt;The Mandelbrot set is defined by a deceptively simple formula: for each point &lt;em&gt;c&lt;/em&gt; in the complex plane, iterate &lt;em&gt;z = z² + c&lt;/em&gt; starting from &lt;em&gt;z = 0&lt;/em&gt;. If the sequence stays bounded (doesn&#39;t escape to infinity), that point is in the Mandelbrot set.&lt;/p&gt;
&lt;p&gt;The magic happens at the boundary - points that take longer to escape produce the intricate, infinitely detailed patterns that make fractals so mesmerizing.&lt;/p&gt;
&lt;h2&gt;Why Kotlin?&lt;/h2&gt;
&lt;p&gt;I chose Kotlin for this project for several reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Coroutines&lt;/strong&gt; - Mandelbrot generation is embarrassingly parallel. Each pixel can be computed independently, making it perfect for concurrent processing. Kotlin&#39;s coroutines provide an elegant way to parallelize the computation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clean syntax&lt;/strong&gt; - Complex number operations and iteration logic read naturally in Kotlin.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JVM performance&lt;/strong&gt; - The heavy numerical computation benefits from JVM optimizations.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Sample Renders&lt;/h2&gt;
&lt;p&gt;Here are some images generated by the library at various zoom levels and color mappings:&lt;/p&gt;
&lt;img src=&quot;https://raw.githubusercontent.com/lasanthak/Mandelbrot-Kotlin/master/samples/1626882863360.png&quot; alt=&quot;Mandelbrot Set - Full View&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;&lt;em&gt;The classic Mandelbrot set view - the iconic &amp;quot;bug&amp;quot; shape with infinite complexity at its edges.&lt;/em&gt;&lt;/p&gt;
&lt;img src=&quot;https://raw.githubusercontent.com/lasanthak/Mandelbrot-Kotlin/master/samples/1626892189752.png&quot; alt=&quot;Julia Set&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;&lt;em&gt;A Julia set render - a close relative of the Mandelbrot set, also supported by the library.&lt;/em&gt;&lt;/p&gt;
&lt;img src=&quot;https://raw.githubusercontent.com/lasanthak/Mandelbrot-Kotlin/master/samples/1736378382629.png&quot; alt=&quot;Mandelbrot Set - Detail 2&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;&lt;em&gt;Different color mappings highlight various aspects of the escape-time algorithm.&lt;/em&gt;&lt;/p&gt;
&lt;img src=&quot;https://raw.githubusercontent.com/lasanthak/Mandelbrot-Kotlin/master/samples/1736379067986.png&quot; alt=&quot;Mandelbrot Set - Detail 3&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;&lt;em&gt;The deeper you zoom, the more intricate structures emerge.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;How It Works&lt;/h2&gt;
&lt;p&gt;The core algorithm is straightforward:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun isInMandelbrotSet(c: Complex, maxIterations: Int): Int {
    var z = Complex(0.0, 0.0)
    for (i in 0 until maxIterations) {
        z = z * z + c
        if (z.magnitudeSquared() &amp;gt; 4.0) {
            return i  // Escaped - return iteration count for coloring
        }
    }
    return maxIterations  // Didn&#39;t escape - in the set
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The iteration count for escaped points determines the color, creating those beautiful gradient bands around the set.&lt;/p&gt;
&lt;p&gt;Using Kotlin coroutines, the image is divided into chunks that are processed concurrently:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;coroutineScope {
    for (chunk in imageChunks) {
        launch {
            computeChunk(chunk)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach scales well across CPU cores, significantly reducing render times for high-resolution images.&lt;/p&gt;
&lt;h2&gt;Anti-Aliasing with Super Sampling&lt;/h2&gt;
&lt;p&gt;One challenge with fractal rendering is &lt;strong&gt;aliasing&lt;/strong&gt; - the jagged edges and moiré patterns that appear when infinite detail meets finite pixels. This is especially noticeable at the boundary of the Mandelbrot set where detail exists at every scale.&lt;/p&gt;
&lt;p&gt;To address this, I implemented &lt;strong&gt;super sampling anti-aliasing (SSAA)&lt;/strong&gt;. Instead of computing a single point per pixel, we sample multiple points within each pixel and take the median value:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun computePixelWithSuperSampling(x: Int, y: Int, samples: Int): Double {
    val values = mutableListOf&amp;lt;Double&amp;gt;()

    for (sy in 0 until samples) {
        for (sx in 0 until samples) {
            // Sub-pixel offset
            val offsetX = (sx + 0.5) / samples
            val offsetY = (sy + 0.5) / samples

            val c = pixelToComplex(x + offsetX, y + offsetY)
            values.add(calculatePoint(c))
        }
    }

    values.sort()
    val mid = values.size / 2
    return if (values.size % 2 == 0) {
        (values[mid - 1] + values[mid]) / 2.0
    } else {
        values[mid]
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With a 2x2 or 4x4 sample grid, edges become smooth and the fine details render more accurately. The trade-off is computation time - 4x4 super sampling means 16 times more calculations per pixel. But combined with coroutines for parallelization, the results are worth it for high-quality renders.&lt;/p&gt;
&lt;h2&gt;Try It Yourself&lt;/h2&gt;
&lt;p&gt;The full source code is available on GitHub: &lt;strong&gt;&lt;a href=&quot;https://github.com/lasanthak/Mandelbrot-Kotlin&quot;&gt;Mandelbrot-Kotlin&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The project is licensed under Apache 2.0, so feel free to use it, modify it, or learn from it. The library is designed to be extensible - you can experiment with different fractal formulas, color schemes, and rendering parameters.&lt;/p&gt;
&lt;h2&gt;Interactive Features&lt;/h2&gt;
&lt;p&gt;The library includes an interactive viewer built with Java Swing that supports:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Real-time zooming and panning&lt;/strong&gt; - explore the infinite detail of these fractals&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multiple color palettes&lt;/strong&gt; - right-click to cycle through different color mappings with instant re-rendering&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What&#39;s Next?&lt;/h2&gt;
&lt;p&gt;Ideas I&#39;m considering for future enhancements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GPU acceleration&lt;/strong&gt; - using Kotlin/Native or compute shaders for even faster rendering&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Arbitrary precision support&lt;/strong&gt; - replacing Double with BigDecimal or similar to enable deep zooming. Currently, Double precision limits how far you can zoom before pixelation artifacts appear due to floating-point precision loss&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Fractals remind us that simple rules can produce infinite complexity - a lesson that applies well beyond mathematics.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Happy coding, and happy exploring!&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Building This Website with Eleventy and Bulma</title>
    <link href="https://www.lasantha.org/blog/building-this-website-with-eleventy-and-bulma/"/>
    <updated>2025-08-12T00:00:00Z</updated>
    <id>https://www.lasantha.org/blog/building-this-website-with-eleventy-and-bulma/</id>
    <content xml:lang="en" type="html">&lt;p&gt;After years of writing on Medium and creating internal documentation at work, I decided it was time to build my own personal website. As someone who values simplicity and maintainability in system design, I wanted the same principles to apply here.&lt;/p&gt;
&lt;h2&gt;Why Build a Personal Site?&lt;/h2&gt;
&lt;p&gt;Throughout my career, I&#39;ve benefited enormously from engineers who shared their knowledge publicly. From blog posts that helped me understand distributed systems to open-source tools that accelerated my work, the community has given me a lot. This site is my way of contributing back.&lt;/p&gt;
&lt;p&gt;Plus, having a central place to share my thoughts on AI/ML, reliability engineering practices, and software architecture felt overdue.&lt;/p&gt;
&lt;h2&gt;The Tech Stack&lt;/h2&gt;
&lt;h3&gt;Eleventy (11ty)&lt;/h3&gt;
&lt;p&gt;For the static site generator, I chose &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt;. Here&#39;s why:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zero client-side JavaScript by default&lt;/strong&gt; - Fast, accessible pages&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flexible templating&lt;/strong&gt; - Supports Nunjucks, Markdown, and more&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simple data cascade&lt;/strong&gt; - Easy to manage global and page-specific data&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Great developer experience&lt;/strong&gt; - Fast builds and hot reloading&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Having worked with complex build systems at scale, I appreciate Eleventy&#39;s simplicity. The configuration is minimal yet powerful:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;module.exports = function(eleventyConfig) {
  eleventyConfig.addPassthroughCopy(&amp;quot;src/assets&amp;quot;);

  eleventyConfig.addCollection(&amp;quot;posts&amp;quot;, function(collectionApi) {
    return collectionApi.getFilteredByGlob(&amp;quot;src/blog/posts/*.md&amp;quot;)
      .sort((a, b) =&amp;gt; b.date - a.date);
  });

  return {
    dir: { input: &amp;quot;src&amp;quot;, output: &amp;quot;_site&amp;quot; }
  };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Bulma CSS&lt;/h3&gt;
&lt;p&gt;For styling, I went with &lt;a href=&quot;https://bulma.io/&quot;&gt;Bulma&lt;/a&gt; - a modern CSS framework that&#39;s:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pure CSS&lt;/strong&gt; - No JavaScript dependencies&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modular&lt;/strong&gt; - Use only what you need&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Responsive&lt;/strong&gt; - Mobile-first design out of the box&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Beautiful defaults&lt;/strong&gt; - Looks great with minimal customization&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Loading it via CDN keeps things simple:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Alpine.js&lt;/h3&gt;
&lt;p&gt;For the bits of interactivity I needed (mobile menu, image lightbox), &lt;a href=&quot;https://alpinejs.dev/&quot;&gt;Alpine.js&lt;/a&gt; is perfect:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lightweight&lt;/strong&gt; - Just 15KB minified&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Declarative&lt;/strong&gt; - Behavior lives in your HTML&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No build step&lt;/strong&gt; - Works directly in the browser&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Project Structure&lt;/h2&gt;
&lt;p&gt;I organized the project to be intuitive and maintainable:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;src/
├── _data/          # Global site data
├── _includes/      # Layouts and partials
├── assets/         # CSS, images, etc.
├── blog/           # Blog posts in Markdown
├── photography/    # Photo gallery
├── resume/         # Resume page
├── about.njk       # About page
└── index.njk       # Home page
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Built with AI Assistance&lt;/h2&gt;
&lt;p&gt;I used Claude Code to help scaffold and iterate on this site. It&#39;s a great example of how AI tools can accelerate development - from generating boilerplate to debugging CSS issues. As someone who&#39;s led AI adoption initiatives, I practice what I preach.&lt;/p&gt;
&lt;h2&gt;Lessons Learned&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Start simple&lt;/strong&gt; - You can always add complexity later&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content first&lt;/strong&gt; - The tech should serve the content, not the other way around&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Progressive enhancement&lt;/strong&gt; - The site works without JavaScript enabled&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Force simplicity&lt;/strong&gt; - A principle I follow at work applies here too&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;What&#39;s Next?&lt;/h2&gt;
&lt;p&gt;Some features I&#39;m considering:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dark mode toggle&lt;/li&gt;
&lt;li&gt;Blog search functionality&lt;/li&gt;
&lt;li&gt;RSS feed&lt;/li&gt;
&lt;li&gt;More content on AI/ML and reliability engineering topics&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But for now, I&#39;m happy with this foundation. It&#39;s fast, accessible, and easy to maintain - exactly what a personal site should be.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Feel free to reach out on &lt;a href=&quot;https://linkedin.com/in/lasanthak&quot;&gt;LinkedIn&lt;/a&gt; if you want to chat about the tech stack or anything else!&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Welcome to My Blog</title>
    <link href="https://www.lasantha.org/blog/welcome-to-my-blog/"/>
    <updated>2025-06-01T00:00:00Z</updated>
    <id>https://www.lasantha.org/blog/welcome-to-my-blog/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Welcome to my corner of the internet! After 22+ years in software engineering, I&#39;ve finally decided to create a dedicated space to share my thoughts on technology, leadership, and the lessons learned along the way.&lt;/p&gt;
&lt;h2&gt;What to Expect&lt;/h2&gt;
&lt;p&gt;This blog will cover topics I&#39;m passionate about and have hands-on experience with:&lt;/p&gt;
&lt;h3&gt;AI/ML &amp;amp; Developer Productivity&lt;/h3&gt;
&lt;p&gt;Having led AI adoption initiatives across global engineering teams, I&#39;ll share insights on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Practical applications of LLMs and AI tools in software development&lt;/li&gt;
&lt;li&gt;Prompt engineering techniques that actually work&lt;/li&gt;
&lt;li&gt;Building developer tools with Model Context Protocol (MCP)&lt;/li&gt;
&lt;li&gt;Lessons from driving cultural shifts toward AI-powered engineering&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Reliability Engineering&lt;/h3&gt;
&lt;p&gt;With years of experience in reliability engineering, expect posts on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Achieving high availability in distributed systems&lt;/li&gt;
&lt;li&gt;Incident management and blameless postmortems&lt;/li&gt;
&lt;li&gt;SLOs, error budgets, and reliability practices&lt;/li&gt;
&lt;li&gt;Operational readiness and capacity planning&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Software Architecture &amp;amp; Leadership&lt;/h3&gt;
&lt;p&gt;From my journey through individual contributor to Principal Engineer roles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Designing scalable microservices architectures&lt;/li&gt;
&lt;li&gt;Technical leadership and mentorship&lt;/li&gt;
&lt;li&gt;Building high-performing engineering teams&lt;/li&gt;
&lt;li&gt;Career growth in software engineering&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Photography&lt;/h3&gt;
&lt;p&gt;When I&#39;m not coding, I explore the world through my camera lens. Occasional posts about photography adventures and perspectives.&lt;/p&gt;
&lt;h2&gt;Why Start a Blog?&lt;/h2&gt;
&lt;p&gt;I&#39;ve always believed in sharing knowledge. Some of my biggest breakthroughs came from reading others&#39; experiences. I&#39;ve previously written on Medium about topics like application couplings, data science, and error budget policies - this blog extends that journey.&lt;/p&gt;
&lt;p&gt;Writing also forces clarity of thought. When you explain something for others, you discover gaps in your own understanding.&lt;/p&gt;
&lt;h2&gt;Stay Connected&lt;/h2&gt;
&lt;p&gt;Feel free to reach out if you have questions, want to collaborate, or just want to say hi. You can find me on &lt;a href=&quot;https://linkedin.com/in/lasanthak&quot;&gt;LinkedIn&lt;/a&gt; and &lt;a href=&quot;https://github.com/lasanthak&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks for stopping by!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Happy coding!&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
</feed>
