<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://kyrylo.org/feed.xml" rel="self" type="application/atom+xml" /><link href="https://kyrylo.org/" rel="alternate" type="text/html" /><updated>2026-07-14T00:17:46+08:00</updated><id>https://kyrylo.org/feed.xml</id><title type="html">Kyrylo Silin</title><subtitle>Software engineer, web developer, entrepreneur</subtitle><author><name>Kyrylo Silin</name><email>silin@kyrylo.org</email></author><entry><title type="html">I built Flag Click</title><link href="https://kyrylo.org/projects/2026/07/07/i-built-flag-click.html" rel="alternate" type="text/html" title="I built Flag Click" /><published>2026-07-07T00:00:00+08:00</published><updated>2026-07-07T10:07:01+08:00</updated><id>https://kyrylo.org/projects/2026/07/07/i-built-flag-click</id><content type="html" xml:base="https://kyrylo.org/projects/2026/07/07/i-built-flag-click.html"><![CDATA[<p>I built a small web game called <a href="https://flagclick.com">Flag Click</a>.</p>

<p>The idea is almost embarrassingly simple: you click your country flag, your flag
climbs up the pole, and you try to overtake everyone else. That’s the whole
game.</p>

<p><img src="/assets/images/posts/i-built-flag-click/01.webp" alt="Flag Click on a phone, with flags climbing a pole in space" /></p>

<p>I keep getting drawn to these tiny internet toys. Not the serious kind of game
where you need a tutorial, a season pass, and three currencies before you are
allowed to have fun. I mean the stupid little thing that makes sense
immediately. You open it, you understand it, and before your brain has time to
ask why, you are already clicking.</p>

<p>Flag Click grew out of <a href="https://flagmatch.com">Flag Match</a>, my flag matching
game. Flag Match at least has the noble excuse of being educational. You can
tell yourself you are learning geography. Flag Click does not have that excuse.
It is the less responsible cousin. You click a flag because you want your flag
to be higher than the other flags.</p>

<p>That sounds silly, but that is exactly why flags work so well here. A flag is
not just a random image on a button. People instantly recognize it. It carries
just enough identity to make the number feel personal, but not enough gravity to
turn the thing into something serious. It stays in that nice internet zone where
everyone knows it does not matter, but it still kind of does.</p>

<p>The first version was not a flagpole. It was a big field of flags where the
winning countries slowly took over the screen. In my head, this was clearly the
right mechanic. On the screen, it was ugly. Flags got squished, the layout
looked noisy, and the whole thing had no drama. It was technically doing what I
asked it to do, which is often the worst kind of failure because the idea itself
is the problem.</p>

<p>The flagpole fixed it.</p>

<p>Once countries started climbing instead of occupying space, the game suddenly
made sense. The screen had a direction. Being #1 meant being at the top. Smaller
countries could sit lower on the pole. On mobile, it also felt much more natural
because the entire game became vertical, which is where your thumb already
lives.</p>

<p>Most of the work after that was not about adding more features. It was about
making the click feel right. The flag had to wave. The score had to react
quickly. Overtaking another country needed a little bit of theatre. The sound
could not be annoying after five seconds. Mobile tapping had to feel good
because this is absolutely a phone game first.</p>

<p>That kind of polish sounds optional until you build something this small. Then
you realize the polish is the product. If the button feels bad, there is no game
left to hide behind.</p>

<p>I also had to think about fake scores, because of course I did. Any public
clicker invites people to mess with it. That is part of the genre. I do not
think you can make this kind of thing perfectly cheat-proof, and I do not want
to turn a silly flag game into airport security. The goal is simpler: make
casual cheating boring enough that normal players do not feel like the board is
immediately ruined.</p>

<p>I have no idea if Flag Click will go anywhere. Maybe it gets shared, maybe it
doesn’t. Maybe one country wakes up and turns the leaderboard into a national
emergency for twelve minutes. Maybe nobody cares. All outcomes are acceptable.</p>

<p>What I like is that it exists. It is small, weird, and self-contained. It made
me care about sounds, animation, phone taps, flags, and one very opinionated
pole. That is enough for me.</p>

<p>Go click your flag: <a href="https://flagclick.com">flagclick.com</a>.</p>]]></content><author><name>Kyrylo Silin</name><email>silin@kyrylo.org</email></author><category term="projects" /><summary type="html"><![CDATA[I built a small web game called Flag Click.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kyrylo.org/assets/images/posts/i-built-flag-click/01.webp" /><media:content medium="image" url="https://kyrylo.org/assets/images/posts/i-built-flag-click/01.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The artificial programmer</title><link href="https://kyrylo.org/2026/06/23/the-artificial-programmer.html" rel="alternate" type="text/html" title="The artificial programmer" /><published>2026-06-23T00:00:00+08:00</published><updated>2026-06-23T12:36:57+08:00</updated><id>https://kyrylo.org/2026/06/23/the-artificial-programmer</id><content type="html" xml:base="https://kyrylo.org/2026/06/23/the-artificial-programmer.html"><![CDATA[<p>I recently got six months of free access to OpenAI’s advanced coding models
because of my open-source contributions. I
<a href="https://x.com/kyrylo/status/2068857794955223259">posted</a> about it on X. It’s an
incredibly generous offer, and I’ve been using Codex every day since.</p>

<p>The convenience is ridiculous.</p>

<p>You describe what you want, and it produces working code. You paste a stack
trace, and it explains the bug and offers a fix. You ask it to add a feature,
and it updates the relevant files, writes tests, and sometimes even suggests the
migration. What used to take an hour of focused work can now take ten minutes of
prompting and reviewing.</p>

<p>This speed is genuinely useful. I am not going to pretend otherwise. But it also
makes it dangerously easy to stop doing the real work yourself.</p>

<p>The muscle that actually makes you a programmer is not typing code. It is the
ability to hold a mental model of a system, reason about trade-offs, spot the
smell in an abstraction before it spreads, and debug by thinking instead of
throwing prompts at the wall.</p>

<p>That muscle needs exercise.</p>

<p>With AI, many problems get solved before you ever have to think hard about them.
The friction disappears, and with it goes some of the growth. You still ship the
change, but you may not have learned much. You still get the green tests, but
you may not understand why the solution is shaped the way it is.</p>

<p>At the same time, the power on the other side is absurd. These tools are no
longer just autocomplete. They can act as agents: explore a codebase, make
coordinated changes across files, run commands, observe results, and iterate. It
is like having a tireless pair of hands that can execute at a speed no human can
match.</p>

<p>The leverage is real. Used well, it lets a single developer ship things that
previously required a small team.</p>

<p>The problem is the gap between those two realities. The tools reward you for
staying in the loop of prompting and accepting. They do not punish you for
slowly losing the ability to work without them. You can produce more output
while your underlying capability quietly erodes. You become very good at
directing an artificial programmer instead of remaining a real one yourself.</p>

<p>I have written before that
<a href="/software/2025/11/26/ai-amplifies-programmers-not-replaces-them.html">AI amplifies programmers rather than replacing them</a>.
I still believe that. But amplification only works if there is still something
strong on the receiving end. If the human side atrophies, you do not get 10×
output. You get 10× of whatever shallow thinking you brought to the prompt.</p>

<p>I also still believe that you should
<a href="/software/2025/03/28/learn-to-code-ignore-ai-then-use-ai-to-code-even-better.html">learn to code, ignore AI, then use AI to code even better</a>.
Maybe even more now.</p>

<p>The practical question is not whether to use these tools. You would be foolish
not to. The question is whether you are still doing enough deliberate, unaided
work to keep your own reasoning sharp.</p>

<p>Sometimes that means closing the AI tab and wrestling with the problem the old
way first. Sometimes it means using the model only after you have already formed
your own approach. Sometimes it means reading the generated code line by line
instead of accepting the diff because the tests passed.</p>

<p>The tools are too good to ignore, but they are also too good at letting you
coast.</p>

<p>The programmers who will matter in the next few years are not the ones who
prompt the best. They are the ones who stay sharp enough to know what to prompt
for, when to use the output, and when the output is wrong.</p>

<p>Everything else is just faster typing.</p>]]></content><author><name>Kyrylo Silin</name><email>silin@kyrylo.org</email></author><summary type="html"><![CDATA[I recently got six months of free access to OpenAI’s advanced coding models because of my open-source contributions. I posted about it on X. It’s an incredibly generous offer, and I’ve been using Codex every day since.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kyrylo.org/assets/images/kyrylo-silin@2x.webp" /><media:content medium="image" url="https://kyrylo.org/assets/images/kyrylo-silin@2x.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Stick with defaults</title><link href="https://kyrylo.org/2026/05/20/stick-with-defaults.html" rel="alternate" type="text/html" title="Stick with defaults" /><published>2026-05-20T00:00:00+08:00</published><updated>2026-05-20T20:24:14+08:00</updated><id>https://kyrylo.org/2026/05/20/stick-with-defaults</id><content type="html" xml:base="https://kyrylo.org/2026/05/20/stick-with-defaults.html"><![CDATA[<p>There is a strange instinct among developers to distrust defaults. The moment we
install a new tool, framework, editor, or operating system, many of us
immediately begin searching for “the real setup”. We hunt for configuration
guides, optimization tricks, must-have plugins, and dotfiles maintained by
people with anime avatars and 700-line configuration files.</p>

<p>Somewhere along the way, software customization became a hobby of its own. This
reflects the same pattern behind <a href="/software/2025/08/21/why-do-software-developers-love-complexity.html">why developers love
complexity</a>.</p>

<p>But the longer I write software, the more I appreciate defaults.</p>

<p>The default path is where every user begins. It is the most exercised, most
documented, and most battle-tested part of the product. Thousands or millions of
people have already walked that road before you. Bugs are discovered there
first. Edge cases are encountered there first. Performance issues become visible
there first. When something breaks, the maintainers notice because everyone is
using it.</p>

<p>Defaults are not arbitrary. They are usually the result of years of accumulated
experience and hard-won trade-offs between performance, maintainability,
usability, and predictability. When Rails ships with a certain project
structure, or SQLite enables certain behaviors by default, or a browser decides
how something should work out of the box, there is often more practical
reasoning behind those choices than most developers assume.</p>

<p>The customized path, by contrast, is lonely.</p>

<p>The more you diverge from the defaults, the more you become responsible for your
own ecosystem. Documentation becomes less relevant because your environment no
longer matches what everyone else is using. Tutorials stop applying cleanly.
Upgrades become riskier. You start carrying a collection of tweaks whose
original purpose you no longer remember.</p>

<p><strong>I know this because I used to do exactly that.</strong> I spent countless hours
configuring editors, terminals, Linux desktops, tiling window managers, browser
setups, and JavaScript toolchains in pursuit of some ideal workflow that never
actually arrived. At some point, the setup itself quietly became the project. I
was investing more energy into perfecting the environment than using it to build
things.</p>

<p>Meanwhile, the person using the defaults quietly ships their work.</p>

<p>This is not an argument against customization entirely. Sometimes defaults
genuinely do not fit your needs. Some workloads require tuning. Some preferences
matter. But customization should come from real friction encountered over time,
not from the assumption that the default experience must be inferior simply
because it is common.</p>

<p>There is also a psychological trap here. Developers often associate
customization with expertise. Using the default setup can feel unsophisticated,
as if you are leaving performance or productivity on the table. But many
experienced engineers eventually arrive at the opposite conclusion: simplicity
has value, and every deviation from the standard path carries a maintenance
cost.</p>

<p>A good default reduces cognitive load. It lets you focus on the actual work
instead of endlessly shaping the environment around the work.</p>

<p>This becomes more obvious as software matures. Modern tools are significantly
better than they used to be. Browsers work well without extensions. Rails
defaults are remarkably solid. SQLite handles workloads that people once
considered impossible for it. Even operating systems have become harder to
misconfigure catastrophically.</p>

<p>The industry has slowly learned that good defaults are a feature.</p>

<p>In many cases, the default path is not merely the easiest option. It is the
option refined through the largest amount of real-world usage. That matters.
Software behaves differently under millions of users than it does in theory.</p>

<p>So these days, I try to resist the instinct to immediately optimize and
customize everything. I start with the defaults and stay there until something
repeatedly becomes a real problem. Most of the time, that moment never comes.</p>

<p>And when it does, I at least understand exactly why I am changing something.</p>]]></content><author><name>Kyrylo Silin</name><email>silin@kyrylo.org</email></author><summary type="html"><![CDATA[There is a strange instinct among developers to distrust defaults. The moment we install a new tool, framework, editor, or operating system, many of us immediately begin searching for “the real setup”. We hunt for configuration guides, optimization tricks, must-have plugins, and dotfiles maintained by people with anime avatars and 700-line configuration files.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kyrylo.org/assets/images/kyrylo-silin@2x.webp" /><media:content medium="image" url="https://kyrylo.org/assets/images/kyrylo-silin@2x.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The magic of typing terminal commands</title><link href="https://kyrylo.org/2026/05/11/the-magic-of-typing-terminal-commands.html" rel="alternate" type="text/html" title="The magic of typing terminal commands" /><published>2026-05-11T00:00:00+08:00</published><updated>2026-05-20T20:24:14+08:00</updated><id>https://kyrylo.org/2026/05/11/the-magic-of-typing-terminal-commands</id><content type="html" xml:base="https://kyrylo.org/2026/05/11/the-magic-of-typing-terminal-commands.html"><![CDATA[<p>There is a quiet magic in the terminal.</p>

<p>The screen is black. The cursor blinks — steady and patient. You lean in and
begin to type, your fingers moving with intention across the keys. A few simple
words appear: <code class="language-plaintext highlighter-rouge">ls</code>, <code class="language-plaintext highlighter-rouge">cd</code>, <code class="language-plaintext highlighter-rouge">mkdir</code>, <code class="language-plaintext highlighter-rouge">grep</code>.</p>

<p><img src="/assets/images/posts/the-magic-of-typing-terminal-commands/01.webp" alt="Empty terminal window" /></p>

<p>Then comes the moment of anticipation — that brief pause before you press
<code class="language-plaintext highlighter-rouge">Enter</code>.</p>

<p>And when you do, the terminal answers.</p>

<p>Results appear line by line, precise and immediate. There is something deeply
satisfying about the exchange: you speak in commands, and the machine responds
with clarity.</p>

<p>This is the beauty of the terminal: its power lies in simplicity. A few letters
typed on a black screen can search vast amounts of data, launch processes, and
reshape entire systems.</p>

<p>Here, you are not a passive user. You are the one who speaks — and the machine
listens. Every time you type a command in the terminal, you feel it: the
ancient, elegant magic of turning thought into action with nothing but words.</p>]]></content><author><name>Kyrylo Silin</name><email>silin@kyrylo.org</email></author><summary type="html"><![CDATA[There is a quiet magic in the terminal.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kyrylo.org/assets/images/kyrylo-silin@2x.webp" /><media:content medium="image" url="https://kyrylo.org/assets/images/kyrylo-silin@2x.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">AI will help you climb the wrong ladder faster</title><link href="https://kyrylo.org/2026/04/15/ai-will-help-you-climb-the-wrong-ladder-faster.html" rel="alternate" type="text/html" title="AI will help you climb the wrong ladder faster" /><published>2026-04-15T00:00:00+08:00</published><updated>2026-04-15T10:28:31+08:00</updated><id>https://kyrylo.org/2026/04/15/ai-will-help-you-climb-the-wrong-ladder-faster</id><content type="html" xml:base="https://kyrylo.org/2026/04/15/ai-will-help-you-climb-the-wrong-ladder-faster.html"><![CDATA[<p>One of my biggest disappointments with AI is this:</p>

<p>It will confidently help you build a ladder… even if it’s leaning against the
wrong wall.</p>

<h2 id="the-problem">The problem</h2>

<p>AI keeps moving forward, even when the direction is off. It supports your line
of thinking, refines it, and adds detail, but rarely stops to say:</p>

<blockquote>
  <p>“Now that the requirements are clearer, we should reassess.”</p>
</blockquote>

<p>Instead, it continues optimizing the current path — whether or not it still
makes sense.</p>

<h2 id="why-this-happens-factually">Why this happens (factually)</h2>

<ul>
  <li><strong>Objective mismatch:</strong> AI is optimized to be helpful and responsive, not to
interrupt or challenge direction.</li>
  <li><strong>Weak global reasoning:</strong> It doesn’t reliably maintain a high-level model of
changing requirements unless prompted.</li>
  <li><strong>User intent bias:</strong> It assumes your framing is correct and works within it.</li>
  <li><strong>Reactive nature:</strong> It responds to input, but it doesn’t proactively step
back or redirect.</li>
</ul>

<h2 id="the-result">The result</h2>

<p>If your initial framing is off, the AI will often:</p>

<ul>
  <li>Refine the wrong solution</li>
  <li>Add convincing detail to a flawed approach</li>
  <li>Stay anchored to outdated assumptions</li>
</ul>

<p>Even if you ask it to “reassess”, it may still operate within the same flawed
frame.</p>

<h2 id="what-would-be-better">What would be better</h2>

<p>A more useful system would:</p>

<ul>
  <li>Notice when the problem definition has shifted</li>
  <li>Pause and reframe</li>
  <li>Challenge assumptions</li>
  <li>Suggest stepping back before continuing</li>
</ul>

<h2 id="practical-takeaway">Practical takeaway</h2>

<p>AI is excellent at accelerating execution.</p>

<p>But it won’t reliably tell you if you’re solving the wrong problem. You still
have to move the ladder to the right wall.</p>]]></content><author><name>Kyrylo Silin</name><email>silin@kyrylo.org</email></author><summary type="html"><![CDATA[One of my biggest disappointments with AI is this:]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kyrylo.org/assets/images/kyrylo-silin@2x.webp" /><media:content medium="image" url="https://kyrylo.org/assets/images/kyrylo-silin@2x.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Less decoration, more communication</title><link href="https://kyrylo.org/2026/04/08/less-decoration-more-communication.html" rel="alternate" type="text/html" title="Less decoration, more communication" /><published>2026-04-08T00:00:00+08:00</published><updated>2026-04-08T12:10:17+08:00</updated><id>https://kyrylo.org/2026/04/08/less-decoration-more-communication</id><content type="html" xml:base="https://kyrylo.org/2026/04/08/less-decoration-more-communication.html"><![CDATA[<p>I’ve been rethinking why we rely so heavily on icons in interfaces.</p>

<p>While building a mostly text-based UI for <a href="https://telesink.com">Telesink</a>, something became obvious: icons make
things look polished, but they rarely make things clearer.</p>

<p><img src="/assets/images/posts/less-decoration-mode-communication/01.webp" alt="Telesink settings" /></p>

<p>We tend to treat icons as essential. In reality, they’re situational.</p>

<p>They help when:</p>

<ul>
  <li>👍 Multilingual products: not everyone speaks the same language, icons provide
rough affordances</li>
  <li>👍 Space-constrained UIs: when labels don’t fit (mobile, dense tables)</li>
  <li>👍 Repeated actions: once learned, icons are faster to scan</li>
  <li>👍 Visual grouping: they help chunk related actions</li>
  <li>👍 Status &amp; feedback: success, warning, loading are instantly recognizable</li>
</ul>

<p>But most of the time, they’re not necessary:</p>

<ul>
  <li>👎 They have to be learned (many aren’t universal)</li>
  <li>👎 They’re ambiguous without labels</li>
  <li>👎 They slow down new users</li>
  <li>👎 They add visual noise without adding meaning</li>
  <li>👎 They increase design and consistency overhead</li>
  <li>👎 Text is more accessible (screen readers, clarity, localization control)</li>
</ul>

<p>So for Telesink, I chose a mostly text-only interface. Not as a stylistic
choice, but a practical one:</p>

<ul>
  <li>✅ clearer actions</li>
  <li>✅ less guesswork</li>
  <li>✅ faster onboarding</li>
  <li>✅ simpler to build and maintain</li>
</ul>

<h2 id="my-rule-of-thumb">My rule of thumb</h2>

<p>Whenever I’m deciding whether to use an icon or text, I now run it through two
simple questions:</p>

<ul>
  <li>Would a first-time user understand this instantly?</li>
  <li>Does the icon truly add clarity, or is it mostly decoration?</li>
</ul>

<p>If it doesn’t pass both tests with a clear “yes”, I default to text.</p>

<p>This mental filter has dramatically reduced visual clutter and forced me to
focus on actual communication rather than aesthetic habit.</p>

<p>Less decoration, more communication.</p>]]></content><author><name>Kyrylo Silin</name><email>silin@kyrylo.org</email></author><summary type="html"><![CDATA[I’ve been rethinking why we rely so heavily on icons in interfaces.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kyrylo.org/assets/images/kyrylo-silin@2x.webp" /><media:content medium="image" url="https://kyrylo.org/assets/images/kyrylo-silin@2x.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">What 2026 yields (announcing Telesink)</title><link href="https://kyrylo.org/2026/01/01/what-2026-yields-announcing-telesink.html" rel="alternate" type="text/html" title="What 2026 yields (announcing Telesink)" /><published>2026-01-01T00:00:00+08:00</published><updated>2026-01-01T21:58:29+08:00</updated><id>https://kyrylo.org/2026/01/01/what-2026-yields-announcing-telesink</id><content type="html" xml:base="https://kyrylo.org/2026/01/01/what-2026-yields-announcing-telesink.html"><![CDATA[<p>I’m a forward-thinking person. Instead of looking back, I prefer to look
forward. Looking back means celebrating successes, mourning failures,
or maybe even learning from mistakes. Besides, I have a bad memory these days
because LLMs do all the thinking for me. So why not just look ahead and anticipate?
Anticipation is what drives us forward.</p>

<p>2026 is going to be the best year for me since I <a href="/2023/11/08/embarking-on-an-indie-dev-journey.html">embarked on an indie
dev journey</a>. I’ll keep building <a href="https://telebugs.com">Telebugs</a>, which has become quite
mature by now. I’m not done with it (there are still a few big epics ahead),
but overall, the product is rock solid.</p>

<p>The thing I’m most excited about, though, is a little announcement…
Since <a href="/software/2025/12/21/im-back-to-open-source.html">I’m back to open source</a>, I want to make 2026 the year of open source.</p>

<h2 id="announcement">Announcement</h2>

<p>Announcing <a href="https://telesink.com">Telesink</a>! Telesink will be an open-source, real-time
event tracking tool that acts as an informational heartbeat for your business,
so you can see what’s happening <em>right now</em>.</p>

<p>You’ll be able to hook up your projects and watch live events like:</p>

<ul>
  <li>“People are signing up”</li>
  <li>“Payments are flowing”</li>
  <li>“MRR is ticking up”</li>
  <li>“New trials are starting”</li>
  <li>“Users are completing onboarding”</li>
  <li>“Form submissions are coming in”</li>
  <li>“Subscribers are renewing”</li>
  <li>…and many more.</li>
</ul>

<p>Unlike analytical tools that dig into historical data or developer metrics,
Telesink is simple and real-time-first. It’s built for everyone, a straightforward,
all-around dashboard for seeing things happen <em>as they happen</em>.</p>

<p>It’s a cleaner replacement for those read-only bots in <em>Slack</em>, <em>Discord</em>, or
<em>Telegram</em> that handle this kind of monitoring as an afterthought.</p>

<p>I’ve been thinking about this for months, and it grew directly out of my own
need to better understand what’s happening in my <a href="/projects/">projects</a> (beyond just
errors).</p>

<p>It’ll be <strong>open source and free to self-host</strong>, with a paid SaaS offering to
sustain development.</p>

<p>Right now I have nothing but the idea in my head (it’s been simmering there
for months). I don’t have all the answers or solutions yet, and honestly it’s
scary because there are a lot of unknowns, but I’m ready to start.</p>

<p>I’ll be building this in public. <a href="https://x.com/kyrylo">Follow along</a>! I’m looking forward to
having you with me.</p>]]></content><author><name>Kyrylo Silin</name><email>silin@kyrylo.org</email></author><summary type="html"><![CDATA[I’m a forward-thinking person. Instead of looking back, I prefer to look forward. Looking back means celebrating successes, mourning failures, or maybe even learning from mistakes. Besides, I have a bad memory these days because LLMs do all the thinking for me. So why not just look ahead and anticipate? Anticipation is what drives us forward.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kyrylo.org/assets/images/kyrylo-silin@2x.webp" /><media:content medium="image" url="https://kyrylo.org/assets/images/kyrylo-silin@2x.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">How to self-host Bearlytics with Kamal</title><link href="https://kyrylo.org/kamal/2025/12/22/how-to-self-host-bearlytics-with-kamal.html" rel="alternate" type="text/html" title="How to self-host Bearlytics with Kamal" /><published>2025-12-22T00:00:00+08:00</published><updated>2025-12-22T19:25:39+08:00</updated><id>https://kyrylo.org/kamal/2025/12/22/how-to-self-host-bearlytics-with-kamal</id><content type="html" xml:base="https://kyrylo.org/kamal/2025/12/22/how-to-self-host-bearlytics-with-kamal.html"><![CDATA[<p>In this article, I’ll guide you through the process of self-hosting
<a href="https://github.com/HermanMartinus/bearlytics">Bearlytics</a> with Kamal. It’s a
bare-bones alternative to Google Analytics (and even Plausible Analytics - see my
<a href="/kamal/2024/11/15/how-to-self-host-plausible-analytics-with-kamal.html">installation guide with Kamal</a>).</p>

<p>I saw it online and immediately knew it was my kind of jam:</p>

<ul>
  <li>low memory footprint</li>
  <li>runs on SQLite</li>
  <li>simple to use and maintain</li>
  <li>lightweight tracking script</li>
</ul>

<p>It’s designed to be self-hosted, and I love simple self-hosted products (I
<a href="https://telebugs.com">develop them myself</a>).</p>

<p>I come from the Ruby ecosystem and prefer deploying with
<a href="https://kamal-deploy.org/">Kamal</a>. Its configuration file looks almost identical
to <code class="language-plaintext highlighter-rouge">docker-compose.yml</code>, which Bearlytics already ships with, so using Kamal felt
like a natural fit.</p>

<h2 id="requirements">Requirements</h2>

<h3 id="hardware">Hardware</h3>

<p>Bearlytics will run on virtually any VPS or hardware. My running container, with
no traffic, consumes only <strong>56 MiB of RAM</strong>. Impressive!</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CONTAINER ID   NAME                                                            CPU %     MEM USAGE / LIMIT    MEM %     NET I/O           BLOCK I/O         PIDS
0db0cbf3fba1   bearlytics-web-9a7059c5bfe6d403d8bf2de40eaf845956ce69ea         0.02%     55.87MiB / 7.57GiB   0.72%     269kB / 582kB     147kB / 13.8MB    3
</code></pre></div></div>

<p>Go to <a href="https://hetzner.com/cloud">Hetzner Cloud</a> and grab the cheapest plan
(you’ll be totally fine).</p>

<h3 id="subdomain">Subdomain</h3>

<p>To self-host Bearlytics, you’ll need a subdomain, for example
<code class="language-plaintext highlighter-rouge">analytics.example.com</code>.</p>

<p>On Cloudflare, create an <code class="language-plaintext highlighter-rouge">A</code> record pointing to your server’s IP address. This
will allow you to access Bearlytics at
<code class="language-plaintext highlighter-rouge">https://analytics.example.com</code>.</p>

<h2 id="step-by-step-guide">Step-by-step guide</h2>

<p>To understand what we’ll be doing, let’s take a look at the
<a href="https://github.com/HermanMartinus/bearlytics/blob/9a7059c5bfe6d403d8bf2de40eaf845956ce69ea/docker-compose.yaml">docker-compose.yaml</a>
file from the repository and translate it into a Kamal configuration.</p>

<p>Bearlytics has no external dependencies: it’s a single container. All we need
to do is set a few environment variables and we’re good to go.</p>

<h3 id="step-1-clone-the-bearlytics-repo">Step 1: Clone the Bearlytics repo</h3>

<p>Start by cloning the repository:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone <span class="nt">--single-branch</span> https://github.com/HermanMartinus/bearlytics bearlytics
<span class="nb">cd </span>bearlytics
</code></pre></div></div>

<h3 id="step-2-branch-out">Step 2: Branch out</h3>

<p>We don’t want to accidentally push these changes upstream. We’ll be working with
our own configuration values, and it’s unlikely the Bearlytics author would want
this opinionated setup merged.</p>

<p>Create a new Git branch:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git checkout <span class="nt">--b</span> kamal
</code></pre></div></div>

<h3 id="step-3-generate-kamal-configuration">Step 3: Generate Kamal configuration:</h3>

<p>Install Kamal if you don’t already have it (via <a href="https://rubygems.org">RubyGems</a>):</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem <span class="nb">install </span>kamal
</code></pre></div></div>

<p>Then generate the Kamal configuration:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kamal init
</code></pre></div></div>

<p>You should see output similar to this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Created configuration file in config/deploy.yml
Created .kamal/secrets file
Created sample hooks in .kamal/hooks
</code></pre></div></div>

<p>We won’t need hooks, so delete that directory:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">rm</span> <span class="nt">-rf</span> .kamal/hooks
</code></pre></div></div>

<h3 id="step-4-update-gitignore">Step 4: Update .gitignore</h3>

<p>Next, add the generated files to <code class="language-plaintext highlighter-rouge">.gitignore</code> so we don’t accidentally leak
secrets:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># .gitignore
!.kamal
!.kamal/secrets
!.config
!config/deploy.yml
</code></pre></div></div>

<h3 id="step-5-setting-up-kamal-secrets">Step 5: Setting up Kamal secrets</h3>

<p>Bearlytics requires several mandatory environment variables:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">SECRET_KEY</code>: Django secret key</li>
  <li><code class="language-plaintext highlighter-rouge">SALT_SECRET</code>: Secret key used to hash PII</li>
  <li><code class="language-plaintext highlighter-rouge">DEBUG</code>: Set to False in production</li>
  <li><code class="language-plaintext highlighter-rouge">DB_PATH</code>: SQLite database location (default: <code class="language-plaintext highlighter-rouge">/app/db/db.sqlite3</code>)</li>
  <li><code class="language-plaintext highlighter-rouge">ALLOWED_HOSTS</code>: Comma-separated list of allowed hosts</li>
  <li><code class="language-plaintext highlighter-rouge">CSRF_TRUSTED_ORIGINS</code>: Comma-separated list of trusted origins</li>
</ul>

<p>We’ll define most of them in the <code class="language-plaintext highlighter-rouge">config/secrets</code> file generated by Kamal. It
should look like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>DEBUG=$DEBUG
SECRET_KEY=$SECRET_KEY
SALT_SECRET=$SALT_SECRET
DB_PATH=$DB_PATH
CSRF_TRUSTED_ORIGINS=$CSRF_TRUSTED_ORIGINS
</code></pre></div></div>

<p>Now we need to populate these values from the environment. I’ll use a <code class="language-plaintext highlighter-rouge">.env</code>
file and the <code class="language-plaintext highlighter-rouge">dotenv</code> Ruby gem (<code class="language-plaintext highlighter-rouge">gem install dotenv</code>). Kamal also supports
several other approaches, which are covered in the
<a href="https://kamal-deploy.org/docs/commands/secrets/">documentation</a>.</p>

<ol>
  <li>Generate <code class="language-plaintext highlighter-rouge">SECRET_KEY</code> and <code class="language-plaintext highlighter-rouge">SALT_SECRET</code> using any password generator.
<code class="language-plaintext highlighter-rouge">openssl</code> works great.</li>
  <li><code class="language-plaintext highlighter-rouge">DB_PATH</code> refers to the path <em>inside</em> the Bearlytics Docker container.</li>
  <li><code class="language-plaintext highlighter-rouge">CSRF_TRUSTED_ORIGINS</code> should point to your installation URL.</li>
</ol>

<p>Here’s how <code class="language-plaintext highlighter-rouge">.env</code> will look like:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># .env at the bearlytics root directory
SECRET_KEY=$(openssl rand -base64 48)
SALT_SECRET=$(openssl rand -base64 48)
DB_PATH="/app/data/production.sqlite3"
CSRF_TRUSTED_ORIGINS="https://analytics.example.com"
</code></pre></div></div>

<p>That’s it for secrets. Now we just need to tell Kamal where and how to deploy the
app.</p>

<h3 id="step-6-update-kamal-configuration">Step 6: Update Kamal configuration</h3>

<p>We’re almost there. Let’s update the Kamal configuration file. I’ll walk you
through the important sections and then provide the full config at the end.</p>

<p>First, set the service name and image:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">service</span><span class="pi">:</span> <span class="s">bearlytics</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">bearlytics</span>
</code></pre></div></div>

<p>Next, define the servers. We’ll use a single server named <code class="language-plaintext highlighter-rouge">web</code>:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">servers</span><span class="pi">:</span>
  <span class="na">web</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">&lt;your-server-ip&gt;</span>
</code></pre></div></div>

<p>Now configure <a href="https://github.com/basecamp/kamal-proxy">kamal-proxy</a> to enable
SSL. We’ll use the subdomain created earlier.</p>

<p><strong>Important:</strong> Bearlytics runs on port <code class="language-plaintext highlighter-rouge">8000</code>, but kamal-proxy assumes port
<code class="language-plaintext highlighter-rouge">3000</code> by default, so we need to override that.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">proxy</span><span class="pi">:</span>
  <span class="na">ssl</span><span class="pi">:</span> <span class="kc">true</span>
  <span class="na">host</span><span class="pi">:</span> <span class="s">analytics.example.com</span>
  <span class="na">app_port</span><span class="pi">:</span> <span class="m">8000</span>
  <span class="na">healthcheck</span><span class="pi">:</span>
    <span class="na">path</span><span class="pi">:</span> <span class="s">/script.js</span>
</code></pre></div></div>

<p>We also define a health check that hits <code class="language-plaintext highlighter-rouge">/script.js</code>. By default, Kamal pings
<code class="language-plaintext highlighter-rouge">/up</code>, which Bearlytics doesn’t implement. <code class="language-plaintext highlighter-rouge">/script.js</code> is guaranteed to exist
because it’s the tracking script itself.</p>

<p>Next, we’ll build the image locally and use Kamal’s <a href="https://kamal-deploy.org/docs/configuration/docker-registry/">local registry
feature</a> so we
don’t need to push anything to GHCR or Docker Hub:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">registry</span><span class="pi">:</span>
  <span class="na">server</span><span class="pi">:</span> <span class="s">localhost:5555</span>
</code></pre></div></div>

<p>Wire up the environment variables:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">env</span><span class="pi">:</span>
  <span class="na">clear</span><span class="pi">:</span>
    <span class="na">DEBUG</span><span class="pi">:</span> <span class="kc">false</span>
  <span class="na">secret</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">DB_PATH</span>
    <span class="pi">-</span> <span class="s">SALT_SECRET</span>
    <span class="pi">-</span> <span class="s">SECRET_KEY</span>
    <span class="pi">-</span> <span class="s">CSRF_TRUSTED_ORIGINS</span>
</code></pre></div></div>

<p>Now mount a volume for the database so we get persistence and can make backups if
needed:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">volumes</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s2">"</span><span class="s">/root/bearlytics-storage:/app/data"</span>
</code></pre></div></div>

<p>Create the directory on your VPS and adjust permissions:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> /root/bearlytics-storage
<span class="nb">chown</span> <span class="nt">-R</span> 1000:1000 /root/bearlytics-storage
</code></pre></div></div>

<p>Finally, specify the build architecture:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">builder</span><span class="pi">:</span>
  <span class="na">arch</span><span class="pi">:</span> <span class="s">amd64</span>
</code></pre></div></div>

<p>Here’s the full configuration for convenience:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">service</span><span class="pi">:</span> <span class="s">bearlytics</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">bearlytics</span>

<span class="na">servers</span><span class="pi">:</span>
  <span class="na">web</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">&lt;your-server-ip&gt;</span>

<span class="na">proxy</span><span class="pi">:</span>
  <span class="na">ssl</span><span class="pi">:</span> <span class="kc">true</span>
  <span class="na">host</span><span class="pi">:</span> <span class="s">analytics.example.com</span>
  <span class="na">app_port</span><span class="pi">:</span> <span class="m">8000</span>
  <span class="na">healthcheck</span><span class="pi">:</span>
    <span class="na">path</span><span class="pi">:</span> <span class="s">/script.js</span>

<span class="na">registry</span><span class="pi">:</span>
  <span class="na">server</span><span class="pi">:</span> <span class="s">localhost:5555</span>

<span class="na">env</span><span class="pi">:</span>
  <span class="na">clear</span><span class="pi">:</span>
    <span class="na">DEBUG</span><span class="pi">:</span> <span class="kc">false</span>
  <span class="na">secret</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">DB_PATH</span>
    <span class="pi">-</span> <span class="s">SALT_SECRET</span>
    <span class="pi">-</span> <span class="s">SECRET_KEY</span>
    <span class="pi">-</span> <span class="s">CSRF_TRUSTED_ORIGINS</span>

<span class="na">volumes</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s2">"</span><span class="s">/root/bearlytics-storage:/app/data"</span>

<span class="na">builder</span><span class="pi">:</span>
  <span class="na">arch</span><span class="pi">:</span> <span class="s">amd64</span>
</code></pre></div></div>

<p>To verify everything is wired correctly, run:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% kamal config
</code></pre></div></div>

<p>Wonderful! If everything looks good, commit your changes:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git add <span class="nb">.</span>
git commit <span class="nt">-m</span> <span class="s2">"Add Kamal configuration"</span>
</code></pre></div></div>

<h3 id="step-7-deploy-bearlytics">Step 7: Deploy Bearlytics</h3>

<p>I deployed Bearlytics on a server that was already provisioned with Kamal. If
you’re using a fresh server, you’ll probably want to run <code class="language-plaintext highlighter-rouge">kamal setup</code> first.</p>

<p>In my case, the server was already running Kamal-managed containers, so I simply
ran:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% dotenv kamal deploy
</code></pre></div></div>

<p>The deployment should take about a minute.</p>

<p>Once it’s done, visit
<code>https://analytics.example.com</code> and you should see the
Bearlytics sign-up page.</p>

<p>Congratulations, you’ve just self-hosted Bearlyrics with Kamal! I’m
proud of you!</p>]]></content><author><name>Kyrylo Silin</name><email>silin@kyrylo.org</email></author><category term="kamal" /><summary type="html"><![CDATA[In this article, I’ll guide you through the process of self-hosting Bearlytics with Kamal. It’s a bare-bones alternative to Google Analytics (and even Plausible Analytics - see my installation guide with Kamal).]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kyrylo.org/assets/images/kyrylo-silin@2x.webp" /><media:content medium="image" url="https://kyrylo.org/assets/images/kyrylo-silin@2x.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">I’m back to Open Source</title><link href="https://kyrylo.org/software/2025/12/21/im-back-to-open-source.html" rel="alternate" type="text/html" title="I’m back to Open Source" /><published>2025-12-21T00:00:00+08:00</published><updated>2025-12-21T17:57:12+08:00</updated><id>https://kyrylo.org/software/2025/12/21/im-back-to-open-source</id><content type="html" xml:base="https://kyrylo.org/software/2025/12/21/im-back-to-open-source.html"><![CDATA[<p>If you’ve ever worked with Ruby, chances are you’ve used my code. I started my
programming journey by <a href="/programming/ruby/2013/04/12/so-what-is-binding-pry-exactly.html">contributing to open source</a>. Helping others and building software has been my passion from day one. In recent years, I paused publishing
my own projects to focus on commercial software. But I’ve dearly missed the
hacker spirit of open source.</p>

<p>Christmas is special for all Rubyists thanks to the annual Ruby release, so I
decided to tag along and join the Xmas party.</p>

<p>So here’s the announcement: <a href="https://weuserails.com">WeUseRails.com</a> goes open
source! I launched it a year ago, and since then it has received over 150
submissions. It’s a free directory of Ruby on Rails web apps. Released under
AGPL-3.0, it ensures that it’s truly open source.</p>

<p>This is <a href="https://github.com/kyrylo/weuserails.com">my humble contribution</a> to
the ecosystem. Enjoy!</p>]]></content><author><name>Kyrylo Silin</name><email>silin@kyrylo.org</email></author><category term="software" /><summary type="html"><![CDATA[If you’ve ever worked with Ruby, chances are you’ve used my code. I started my programming journey by contributing to open source. Helping others and building software has been my passion from day one. In recent years, I paused publishing my own projects to focus on commercial software. But I’ve dearly missed the hacker spirit of open source.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kyrylo.org/assets/images/kyrylo-silin@2x.webp" /><media:content medium="image" url="https://kyrylo.org/assets/images/kyrylo-silin@2x.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">English is a poor language for programming with AI</title><link href="https://kyrylo.org/software/2025/12/14/english-is-a-poor-language-for-programming-with-ai.html" rel="alternate" type="text/html" title="English is a poor language for programming with AI" /><published>2025-12-14T00:00:00+08:00</published><updated>2025-12-15T06:32:57+08:00</updated><id>https://kyrylo.org/software/2025/12/14/english-is-a-poor-language-for-programming-with-ai</id><content type="html" xml:base="https://kyrylo.org/software/2025/12/14/english-is-a-poor-language-for-programming-with-ai.html"><![CDATA[<p>English and natural language in general is a bad interface for programming with
AI. Not because it is “imperfect”. But because it was never designed for
precision.</p>

<p>Natural languages evolved for persuasion, storytelling, negotiation, and
survival in small tribes. They are optimized for social flexibility and rapid
communication, not exact meaning. That is why they keep failing us when we try
to use them as the primary interface for machines.</p>

<p>The language is full of hidden landmines: ambiguity, implied context, emotional
framing, and undefined terms. Humans constantly misinterpret each other (even
after thousands of years of practice).</p>

<p>The existence of lawyers is not an accident. It is evidence.</p>

<p>If natural language were precise, contracts would not need to be hundreds of
pages long. Courts would not exist to interpret intent. Disputes would not hinge
on what someone “meant”.</p>

<p>Now we take this same fuzzy medium and say: “Hey AI, do exactly what I mean”.</p>

<p>That is not engineering. That is wishful thinking.</p>

<h2 id="examples-of-the-problem">Examples of the problem</h2>

<ul>
  <li>
    <p><em>“Make it faster”.</em><br />
Faster how? CPU? Latency? Cost? Perception?</p>
  </li>
  <li>
    <p><em>“Use a lightweight solution”.</em><br />
Lightweight in memory? Dependencies? Code size?</p>
  </li>
  <li>
    <p><em>“Notify me when it is ready”.</em><br />
Ready by whose definition?</p>
  </li>
</ul>

<p>Humans fill these gaps with intuition and shared context.<br />
Machines cannot reliably do that. And arguably should not be expected to.</p>

<h2 id="real-world-consequences">Real-world consequences</h2>

<p>2025 research drives the point home. Veracode analyzed code from over 100 LLMs
and found that 45% of samples introduced OWASP Top 10 vulnerabilities (see
<a href="https://www.veracode.com/resources/analyst-reports/2025-genai-code-security-report">2025 GenAI Code Security Report</a>).
A Cloud Security Alliance study
<a href="https://cloudsecurityalliance.org/blog/2025/07/09/understanding-security-risks-in-ai-generated-code">reported</a>
that 62% of AI-generated code contains design flaws or known security
vulnerabilities (even with the latest models).</p>

<p>Prompts rarely specify security constraints, error handling, input validation,
or performance bounds. The model guesses. And often guesses wrong.</p>

<p>To be fair, natural language prompting has been a breakthrough. It has
democratized AI, enabling non-programmers to build complex applications quickly
and opened up rapid prototyping in ways that traditional coding never could.
Modern LLMs are remarkably good at resolving ambiguity using conversation
history, context, and probabilistic reasoning. Often better than early skeptics
predicted (see <a href="https://arxiv.org/html/2411.12395v1">research</a>).</p>

<p>Yet these successes are still papering over a fundamental mismatch. Prompt
engineering helps, but it remains an imprecise art, not a science. When outputs
go wrong, we still end up blaming the model for “misunderstanding” us. Rather
than recognizing the root issue. We are asking it to read our minds in a
language built for nuance, not specification.</p>

<p>Jacque Fresco, futurist and founder of the Venus Project, repeatedly argued that
natural language is fundamentally inadequate for operating rational, technical
systems. In his lecture
<a href="https://www.youtube.com/watch?v=ENFMAxol4-Y">“The Inadequacy of Language” (1975)</a>,
he emphasized that words are ambiguous, emotionally charged, and dependent on
personal interpretation. And that truly rational systems must rely on
measurable, objective, and instrument-based forms of communication rather than
symbolic abstraction.</p>

<p>Natural language is symbolic and expressive, not scientific.
It describes impressions and intentions, not verifiable constraints.</p>

<p>Words like “optimize”, “secure”, “simple”, “important”, and “critical” feel
meaningful. But they carry zero measurable content unless explicitly defined.</p>

<p>When we instruct AI in natural language, we are translating fuzziness into code
and hoping it collapses into correctness.</p>

<p>Sometimes it does.<br />
Often it does not.</p>

<p>The future is not just “better prompt engineering”.<br />
It is better interfaces. Ones built on.</p>

<p>Explicit constraints instead of vague adjectives.
Measurable objectives instead of implied intentions.
Formal definitions instead of metaphors.</p>

<p>We are already seeing promising steps in this direction. Structured prompts
using JSON schemas or YAML, formal specification tools like
<a href="https://lamport.azurewebsites.net/tla/tla.html">TLA+</a>
or
<a href="https://alloytools.org/">Alloy</a>,
visual programming environments, and agentic tools like Cursor or Replit Agent
that let users define goals with clearer boundaries and iterative feedback
loops.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Natural language can and should remain a conversational layer.</p>

<p>A high-level UI.
A convenience for exploration and ideation.</p>

<p>But it should never be the source of truth for critical instructions.</p>

<p>You do not build a bridge using poetry.<br />
You do not govern machines using vibes.</p>

<p>And expecting AI to reliably interpret natural language as precise code
is like expecting a compiler to understand sarcasm.</p>]]></content><author><name>Kyrylo Silin</name><email>silin@kyrylo.org</email></author><category term="software" /><summary type="html"><![CDATA[English and natural language in general is a bad interface for programming with AI. Not because it is “imperfect”. But because it was never designed for precision.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kyrylo.org/assets/images/kyrylo-silin@2x.webp" /><media:content medium="image" url="https://kyrylo.org/assets/images/kyrylo-silin@2x.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>