<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Rust on Electric Nexus</title><link>https://arkoniak.com/tags/rust/</link><description>Recent content in Rust on Electric Nexus</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Tue, 20 Oct 2026 14:20:00 +0300</lastBuildDate><atom:link href="https://arkoniak.com/tags/rust/index.xml" rel="self" type="application/rss+xml"/><item><title>Exerakt: Reading Matrix the Way Element Shows It</title><link>https://arkoniak.com/posts/exerakt-reading/</link><pubDate>Tue, 20 Oct 2026 14:20:00 +0300</pubDate><guid>https://arkoniak.com/posts/exerakt-reading/</guid><description>The second milestone of Exerakt: a client that reads Matrix messages the way Element shows them. The interesting problems were not in drawing but in other people&amp;rsquo;s code, and every feature was checked side by side with Element.</description><content:encoded><![CDATA[<p>How Exerakt is written is up to me. But there is one part I do not choose: the Matrix server on the
other side. There are many servers, and I need two of them. My own server runs Conduit, and the
server at work runs Synapse. Drawing turned out to be the easy part, mostly because I had given up
the heavy frameworks. The interesting problems came from other people&rsquo;s code: the Matrix SDK (<code>matrix-sdk</code>),
and servers that do not behave the same way.</p>
<h2 id="element-as-the-reference">Element as the reference</h2>
<p>For the look and the features I like Element a lot, so it is no surprise that I chose it as the
reference for where the project should go. This also answered a question the pipeline cannot answer
by itself. Jev and Claude check whether a card is done, but nobody checks where the whole project is
going. For now I want to reproduce the look and feel of Element, so every feature of Exerakt is
finished by comparing Exerakt and Element side by side: the same account, the same room, the two
windows next to each other.</p>
<h2 id="what-was-built">What was built</h2>
<p>The second milestone was a client that reads Matrix the way Element shows it. It took six steps:</p>
<ol>
<li>Turning the HTML of formatted messages into the client&rsquo;s own representation.</li>
<li>Drawing it: quotes, code, lists, headings, tables, links, mentions, and spoilers, with selection
and copy working across all of them.</li>
<li>Showing replies, edits, reactions, deleted messages, and messages that could not be decrypted,
with the same wording as Element.</li>
<li>Read receipts and the line that marks where the user stopped reading.</li>
<li>Pictures and colour emoji.</li>
<li>Local time, a status line for the connection, and a final check of the whole milestone against
Element.</li>
</ol>
<h2 id="side-by-side">Side by side</h2>
<p>A card is checked by its tests, but nothing tests how the whole client looks. So every step ended
with a comparison, and since the process is automatic, Claude did it as well:</p>
<ol>
<li>A local Conduit server ran in Docker. A small program created rooms on it and filled them with
the kinds of messages the step was about: formatted text, replies and reactions, pictures.</li>
<li>Claude started both clients, logged in to the same account on this server, on a separate
workspace of an invisible (headless) Hyprland output, so nothing appeared on my screen. It
captured Exerakt with <code>grim</code>, a screenshot tool for Wayland, and Element through its developer
tools.</li>
<li>Claude put the two screenshots side by side and compared them. What could be fixed within the
step was fixed there; everything else became a task for a later step.</li>
</ol>
<p>Below are three of these comparisons from the second milestone, Exerakt on the left and Element on
the right. They are not final screenshots: they show the clients the way Claude saw them at the
moment of each check. Two of the differences in them, local time and colour emoji, were done later
within the same milestone; the others are still ahead.</p>
<p><img alt="Formatted messages in Exerakt and Element" loading="lazy" src="/images/exerakt/m2-formatting.png"></p>
<p>Works:</p>
<ul>
<li>bold, italic, inline code, and strikethrough;</li>
<li>code blocks, numbered and nested lists, headings, quotes, tables, and rules;</li>
<li>coloured text, spoilers, links, and mentions, all in the same places as in Element.</li>
</ul>
<p>Not yet:</p>
<ul>
<li>syntax highlighting and line numbers in code blocks;</li>
<li>dimmer quotes, and a mention shown as the user&rsquo;s name with an avatar;</li>
<li>local time: at that moment Exerakt showed UTC, local time came later in the same milestone.</li>
</ul>
<p><img alt="Replies, edits, and reactions in Exerakt and Element" loading="lazy" src="/images/exerakt/m2-relations.png"></p>
<p>Works:</p>
<ul>
<li>the replied-to message above the reply;</li>
<li>the edited mark;</li>
<li>reactions with their counts under the message;</li>
<li>messages grouped by sender the same way.</li>
</ul>
<p>Not yet:</p>
<ul>
<li>colour emoji, which came later in the milestone;</li>
<li>the replied-to sender with an avatar and a coloured bar;</li>
<li>icons before deleted and undecryptable messages.</li>
</ul>
<p><img alt="The read marker in Exerakt and Element" loading="lazy" src="/images/exerakt/m2-read-marker.png"></p>
<p>Works:</p>
<ul>
<li>the read marker line after the same message;</li>
<li>the same times;</li>
<li>the badge of the other user who read the room, on the same message.</li>
</ul>
<p>Not yet: avatars and coloured sender names, here and everywhere else.</p>
<h2 id="other-peoples-code">Other people&rsquo;s code</h2>
<p>As I said at the start, drawing the messages itself went fairly smoothly. The problems came from two
other places: the servers, and one of the libraries.</p>
<h3 id="the-servers">The servers</h3>
<p>Matrix servers that speak the same protocol do not always send the same things. But first, a few
technical details about two features where this showed up.</p>
<p>The first one is read receipts. When you read a message, your client tells the server, and the
clients of other people in the room show it, usually as small avatars next to the last message each
person has read. The second one is the read marker: a line in a room that shows where you stopped
reading last time, so that when you come back you see where the new messages begin. The server keeps
its position, so the line is in the same place on all your devices.</p>
<p>A Matrix client gets news from the server through one long-lived connection, and both of these
should come through it. With Synapse they did. With Conduit they did not come at all, so Exerakt
never learned where the line was or who had read what. I found this only because Claude checked
the feature against both local servers. Exerakt now asks the server for the line separately every
time a room opens, which Conduit does answer. Other people&rsquo;s read receipts on Conduit are still
missing.</p>
<h3 id="the-emoji">The emoji</h3>
<p>The library problem was with emoji. They came out in black and white. To draw a character, <code>parley</code>,
the text layout library, looks for a font that has it, and it looked through the regular fonts
first. Some of them have black-and-white emoji, so they were picked before the colour emoji font.
The fix was to find the emoji in the text in Exerakt itself and send them to the colour emoji font
directly. As a side effect, the first emoji the program shows now takes 0.7 milliseconds instead of
97 (yes, almost 140 times faster), because the slow part had been the search through the system&rsquo;s list of fonts.</p>
<h2 id="additional-lessons">Additional lessons</h2>
<p>It turned out that Bonsai is not the only one who does not know everything. When you reply to a
message in Matrix, the client also puts a copy of the original message into the reply, for older
clients that cannot show replies. A modern client has to remove that copy before showing the reply.
In the first step of the milestone Claude cut a card asking Bonsai to do exactly that, and Bonsai
did it. Only then it turned out that <code>matrix-sdk-ui</code>, the part of the Matrix SDK that builds the list
of messages, already removes the copy, so the card was withdrawn.</p>
<p>The mistake was Claude&rsquo;s, the architect&rsquo;s, like the ones in <a href="/posts/exerakt-pipeline/">the first run</a>,
and it led to a new rule for writing cards: before cutting a card, read what the dependency already
does with the data.</p>
<p>The next post is about the third milestone, writing messages, and about how the plan for the
pipeline met reality.</p>
]]></content:encoded></item><item><title>Exerakt: Why Rust, Wayland and a UI of Its Own</title><link>https://arkoniak.com/posts/exerakt-skeleton/</link><pubDate>Tue, 20 Oct 2026 13:30:00 +0300</pubDate><guid>https://arkoniak.com/posts/exerakt-skeleton/</guid><description>Why Exerakt is written for Wayland only and draws its own interface instead of using a cross-platform toolkit, how this choice was checked in the first milestone, and how the result compares with Element.</description><content:encoded><![CDATA[<p>When I was choosing what to build (<a href="/posts/exerakt-why/">the first post</a>), I said that I wanted a
Rust implementation for Wayland. With Rust everything is more or less clear: a fast language, good
memory checks, and a compiler that does not let you make too many mistakes. We saw this in the
<a href="/posts/exerakt-jev-calibration/">calibration of Jev</a>: almost everything that passed the mechanical
checks turned out to be right and did not need to be redone.</p>
<h2 id="why-wayland-only">Why Wayland only</h2>
<p>With Wayland the reader may ask: why this way? Why not go the standard route, choose a good,
time-tested cross-platform library and use it?</p>
<p>My answer has two parts. The first one is philosophical. I believe that with LLMs it is possible
(or will be in the near future) to build specialised solutions efficiently. Do you want it for macOS
or Windows? You create a project like mine, feed it the code of Exerakt, and after some time you
have your own version of the application, built and tuned for your environment, in the form you
need. The word &ldquo;soft&rdquo; in software stops being just an adjective and becomes a literal description of
what you do: you change the code to fit the problem, instead of changing the problem to fit the
&ldquo;right&rdquo; framework.</p>
<p>The second part is technical. A cross-platform library is not only a convenient and free way to get
an application that runs everywhere. It is also an extra layer of abstraction that makes you write
code not the way you want. It keeps you from using native things the way they were meant to be
used. It brings its own complex bugs that often need non-trivial workarounds, and it makes you give
up features simply because the framework does not support them yet.</p>
<p>So I chose to specialise for one protocol. And the other decisions went the same way. Wherever I
could, I avoided big ready-made solutions. They are good, great, time-tested (no sarcasm), but they
would make me bend the code the way the framework needs, not the way I want. I did not want to run, every time, into a feature that &ldquo;cannot be done the way you want because of
this limitation of the framework&rdquo; and then kick myself, thinking &ldquo;why did I make this choice?
Nobody forced me to use Qt/GTK/your favourite framework&rdquo;.</p>
<h2 id="drawing-everything-myself">Drawing everything myself</h2>
<p>The client draws its whole interface by itself, and this gives simple answers to problems that are
hard with a toolkit. For example, selecting text across many messages, the problem I had with
Fractal: in Exerakt the whole timeline is one text document, so a selection simply goes across
messages. HTML is laid out my own way, and themes have no limits beyond what I write.</p>
<p>The stack is small: <code>winit</code> for the window and input, <code>softbuffer</code> to put pixels on the screen,
<code>tiny-skia</code> to draw, and <code>parley</code> to lay out text. Everything is drawn on the CPU first, and the GPU
comes in only if the CPU turns out to be too slow.</p>
<p>These crates are young, so before going further I wrote down the criteria the first milestone had
to check. If one of them failed, one crate would be replaced and the others would stay:</p>
<ul>
<li>selecting text across several messages and copying it;</li>
<li>smooth scrolling of a timeline of 2000 messages at 1440p, on the CPU.</li>
</ul>
<h2 id="the-first-milestone">The first milestone</h2>
<p>The first milestone was a client that can read: log in, restore the session, show the rooms and
their history, and send a plain message. It took five steps:</p>
<ol>
<li>The client&rsquo;s model: the data the screen shows, such as rooms and their messages.</li>
<li>The layer that talks to Matrix: login, the session, and sync through <code>matrix-sdk</code>.</li>
<li>The window, drawing, and text.</li>
<li>The views: the room list, the timeline, and the message field.</li>
<li>Connecting all of it into a working client and testing it end to end against local Matrix
servers (Conduit and Synapse).</li>
</ol>
<p>Selection across messages works. A frame of the timeline with real data at 1440p is drawn in 3 to 4
milliseconds, less than a quarter of the time a frame has at 60 Hz. So the stack stays.</p>
<h2 id="against-element">Against Element</h2>
<p>Then I compared it with Element Desktop on the same account with 14 rooms, both with a stored
session:</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>Exerakt</th>
					<th>Element Desktop</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Start to the room list on screen</td>
					<td>about 0.25 s</td>
					<td>about 2 s</td>
			</tr>
			<tr>
					<td>Memory after opening a room</td>
					<td>69 MiB</td>
					<td>about 700 MiB</td>
			</tr>
	</tbody>
</table>
<p>Of course the client is still very far from its final shape, and it does not have a huge number of
features that can grow its size and slow its start. Even so, the numbers are very encouraging, and
they support my confidence that I should keep going in this direction.</p>
<p>The next post is about the second milestone: reading Matrix messages the way Element shows them.</p>
]]></content:encoded></item><item><title>Exerakt: What Bonsai Does With Frozen Tests</title><link>https://arkoniak.com/posts/exerakt-bonsai/</link><pubDate>Tue, 20 Oct 2026 12:00:00 +0300</pubDate><guid>https://arkoniak.com/posts/exerakt-bonsai/</guid><description>How the local Bonsai model did as the executor over 28 cards: the logic was almost always right, and almost every failure was code that did not compile. And the mistake I made in how I ran it.</description><content:encoded><![CDATA[<p>When I set up the pipeline, I was worried that Bonsai would write poor code. An independent review
said it plans well and codes poorly in agentic tasks, so the whole scheme was built to compensate:
fixed signatures, frozen tests, and a task small enough to fit in one or two files.</p>
<p>In hindsight I can see that I made a big mistake here. I ran Bonsai not as an agent but purely as a
text generator: one request per attempt, no tools, the answer is the full content of the files it
changes. Now the mistake looks obvious, but in the first iteration I did not give this difference
much weight. The numbers below are about that setup.</p>
<h2 id="the-numbers">The numbers</h2>
<p>By now Bonsai has gone through 28 cards in 34 runs. All 28 were accepted in the end, 12 of them on
the first attempt of the first run. Six cards went back to Claude at least once.</p>
<p>More interesting is where the failed attempts failed. Every attempt goes through the same checks in
the same order: the files it touched, compilation and clippy, the project&rsquo;s rules, the tests, and
Jev. Over all runs:</p>
<ul>
<li>27 failed attempts stopped at compilation and clippy;</li>
<li>4 at formatting;</li>
<li>3 at Jev;</li>
<li>none at the tests.</li>
</ul>
<p>When Bonsai&rsquo;s code compiled, the logic was almost always right.</p>
<h2 id="what-went-wrong">What went wrong</h2>
<p>About two thirds of the compilation failures were code that does not compile at all. The most
typical one is a method that does not exist, like this check of an id&rsquo;s first character:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="n">input</span><span class="p">.</span><span class="n">first</span><span class="p">()</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="nb">Some</span><span class="p">(</span><span class="sc">&#39;@&#39;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span></code></pre></div><p>Slices have <code>first()</code>, strings do not. The same happened with <code>is_ascii_digits</code> and with a
<code>normalized</code> method on one of our own types. The rest were the places where Rust is hard: moving a value out from behind a reference, a temporary that does not live long enough,
a function that returns a value where <code>Ok(value)</code> is expected. The remaining third were clippy&rsquo;s
style remarks, such as two nested <code>if</code>s that can be one.</p>
<p>Claude and Jev classify every failure, and 30 of 34 were classified as trivial: a typo, a wrong
method name, a missing import.</p>
<p>The most expensive failure was of a different kind, and it was in the previous post. A card asked
for date calculations and named the algorithm without writing it down. Bonsai spent its whole
budget twice trying to derive the formulas and did not produce any code. Where it had to know
something rather than write something, it had no way to find out.</p>
<p>My conclusion after all these runs was this: Bonsai has no problem with how to write code, but there
is a lot it does not know. And
in the setup I chose it had no way to check: it could not look up a signature in a dependency or run
the compiler before answering. It learned about a missing method only from the next attempt&rsquo;s
error list.</p>
<h2 id="what-changed">What changed</h2>
<p>Bonsai&rsquo;s prompt did not change: it is the same as on the first run. What changed were the cards.
After the first step Claude got two rules for writing them: a known algorithm is written out, not
named, and every signature is checked against the project&rsquo;s rules before the card is cut. In the
runs since 19 October the first attempt compiles in 6 of 9 runs, against 7 of 25 before. Maybe the
better written cards are the reason, but the cards themselves are different and the numbers are
small, so I cannot say that for sure.</p>
<p>The orchestrator also runs <code>clippy --fix</code> before the clippy check now, so clippy&rsquo;s style remarks
that it can fix by itself would no longer cost an attempt. Since it was added, it has not had
anything to fix.</p>
<p>All of this cost about four hours of Bonsai&rsquo;s time for 28 cards, most of it spent on reasoning
tokens.</p>
<p>Running Bonsai as an agent, with tools and the compiler inside the loop, is still ahead. The next
post is about Jev and how much its answers can be trusted.</p>
]]></content:encoded></item><item><title>Exerakt: How the Models Work Together</title><link>https://arkoniak.com/posts/exerakt-pipeline/</link><pubDate>Sun, 18 Oct 2026 20:00:00 +0300</pubDate><guid>https://arkoniak.com/posts/exerakt-pipeline/</guid><description>How Claude, Jev and a local Bonsai model are put together into one pipeline, and what its first run showed: most failures that looked like the executor&amp;rsquo;s were mine.</description><content:encoded><![CDATA[<p>Once I knew what to build and with what, the next question was how to organise it. The idea of
Claude, Jev and Bonsai working together is clear on paper, but how does it work in practice? What is
called when and in which order, who depends on whom, and who checks whom?</p>
<h2 id="the-plan">The plan</h2>
<p>The work goes from the top down, in three levels. The whole client is split into milestones, each
ending in something that runs end to end: first a client that can only read, then one that can
write, then accounts, threads, and so on. When a milestone starts, Claude splits it into steps, and
when a step starts, Claude splits it into cards. Detail is added only one level ahead: only the
milestone we are working on is split into steps, and only the current step is split into cards.
This is called rolling wave planning. Every step ends with a review, and the review may change the
plan for the next steps, so there is no detailed plan for work that the earlier work can still
change. But there is always a more or less clear picture of what lies ahead and what is still left
to do. The diagram below shows the whole cycle.</p>
<pre class="mermaid">flowchart TD
    M[&#34;Milestone&lt;br/&gt;(e.g. a client that can write)&#34;] --&gt;|Claude splits the current milestone| S[Steps]
    S --&gt;|Claude splits the current step| C[&#34;Cards: signatures,&lt;br/&gt;frozen tests, task&#34;]
    C --&gt; L[[&#34;Card loop&lt;br/&gt;(Bonsai writes, checks, Jev decides)&#34;]]
    L --&gt; R[Step review by Claude]
    R --&gt;|next step| S
    R -.-&gt;|may change the plan| M</pre><p>The unit of work is a card: one small task for the executor (normally Bonsai), usually a few functions in one file.
Before a card goes anywhere, Claude has already written everything around it: the types and
function signatures with empty bodies, the module&rsquo;s description, and the tests. The tests are
frozen. Bonsai sees the task, the files it may change and the tests, and fills in the bodies. If a
test file changes by a single byte, the attempt does not count, so the model cannot pass a test by
rewriting it. For a model working on its own this is the most important guard.</p>
<p>The cards are run by an orchestrator, a separate program that Claude wrote. For each
card it asks Bonsai for code and then checks the result, cheapest checks first:</p>
<ol>
<li>Did the model touch only the files it was allowed to touch?</li>
<li>Does it compile, and does clippy have nothing to say?</li>
<li>Does the code break any of the project&rsquo;s mechanical rules, such as which crate may depend on
which?</li>
<li>Do the tests pass?</li>
<li>Only then Jev: questions that a machine cannot check, like &ldquo;does the code do what the task
describes&rdquo; or &ldquo;does every function belong to the module&rsquo;s stated responsibility&rdquo;.</li>
</ol>
<p>The first four checks cost nothing and are exact, so Jev only sees code that already
works. When a check fails, the errors go back to Bonsai for another attempt. Jev also looks at
the failure and decides whether it is worth retrying (a typo, a wrong method name) or whether the
card should go back to Claude (the task itself looks wrong, or the model is going in circles).
After three failed attempts the card goes back to Claude anyway. The second diagram shows one
card&rsquo;s way through this loop.</p>
<pre class="mermaid">flowchart TD
    B[Bonsai writes the code] --&gt; G1{&#34;Only the allowed&lt;br/&gt;files changed?&#34;}
    G1 --&gt;|yes| G2{&#34;Compiles,&lt;br/&gt;clippy clean?&#34;}
    G2 --&gt;|yes| G3{&#34;Project rules&lt;br/&gt;hold?&#34;}
    G3 --&gt;|yes| G4{&#34;Tests pass?&#34;}
    G4 --&gt;|yes| J{&#34;Jev: does the code&lt;br/&gt;do what the task says?&#34;}
    J --&gt;|yes| A[Accepted, merged into the step]
    J -.-&gt;|not sure| JD[Claude as judge]
    JD -.-&gt; A
    G1 &amp; G2 &amp; G3 &amp; G4 &amp; J --&gt;|no| F{&#34;Jev: what kind&lt;br/&gt;of failure?&#34;}
    F --&gt;|&#34;typo, local mistake:&lt;br/&gt;errors go back&#34;| B
    F --&gt;|&#34;card looks wrong,&lt;br/&gt;model goes in circles,&lt;br/&gt;attempts used up&#34;| E[Back to Claude]</pre><p>There is one more participant for the start. When I searched for how Jev is used, I found the
recommendation to calibrate it on your own tasks before relying on it fully. So Claude was added as
a judge: every question Jev answers is also put to Claude, and while this calibration is on,
Claude&rsquo;s answer decides (the dashed arrows in the second diagram). On the first step Jev cost about 300
times less than Claude for the same questions, which is the reason to want it to take over.</p>
<p>Bonsai (<a href="https://prismml.com/news/bonsai-2-27b">Bonsai 2 27B</a> by PrismML) was chosen for a
simple reason: on my machine, with one 12 GB graphics card, it had a reasonable balance between
speed and quality, about 56 tokens per second with 128K tokens of context. A better candidate
could probably be found, but at that moment it was a model that definitely worked, had a good
context size, and I did not want to spend time searching for another one.</p>
<p>An independent review of Bonsai said that it plans well and codes poorly in agentic tasks. Fixed
signatures and frozen tests were meant to compensate for exactly that: the model does not have to
design anything, only to fill in bodies until the tests pass.</p>
<h2 id="the-first-run">The first run</h2>
<p>The first step was the client&rsquo;s model: pure view state, room list, timeline, dates, previews. Six
cards, 75 frozen tests. One card of six passed on the first attempt, one more on the second, and four
came back to Claude.</p>
<p>When I looked at why, only one of the four was Bonsai&rsquo;s failure: it converged on the answer,
but ran out of attempts on the way. The other three were not its fault:</p>
<ul>
<li>The orchestrator itself had a bug. Bonsai&rsquo;s code had passed every check, and the last thing left
was to ask Claude, as the judge, the same questions Jev had answered. That request failed: the
orchestrator named each question after the project rule it checks, with names like <code>R:S4</code>, and
the colon is not allowed in the format Claude&rsquo;s interface expects for these answers. So a correct
card came back as a failure.</li>
<li>One card asked for a function that returns <code>bool</code> when an operation fails. The project&rsquo;s own rules
say that failures are typed errors. Bonsai followed the card, and Jev correctly rejected the
code for breaking the rules.</li>
<li>One card asked for date calculations and said &ldquo;use Hinnant&rsquo;s algorithm&rdquo;, without writing the
algorithm down. Bonsai spent its whole token budget twice trying to derive it and did not produce
a single line of code.</li>
</ul>
<p>So in this scheme the architect&rsquo;s mistakes look like the executor&rsquo;s mistakes. A badly written card
shows up as a failed attempt, and from the outside it is hard to tell which side is wrong.</p>
<h2 id="what-changed">What changed</h2>
<p>The fixes went to where the problems were. The orchestrator got its bug fixed and a check that
catches it before a run. The card with <code>bool</code> returns a typed error now. The date card got the
formulas themselves, checked against Python&rsquo;s <code>datetime</code> for every year from 1 to 9999. And the
process for writing cards got two new rules: a known algorithm is written out, not named, and
every signature is checked against the project&rsquo;s rules before the card is cut.</p>
<p>The more general lesson is that the pipeline has to tell these failures apart. That is why Jev
classifies every failure, and why &ldquo;the card itself looks wrong&rdquo; goes straight back to Claude
instead of costing Bonsai more attempts.</p>
<p>What Bonsai itself does with the cards it gets is the subject of the next post.</p>
]]></content:encoded></item><item><title>Exerakt: Why I Let Models Write My Matrix Client</title><link>https://arkoniak.com/posts/exerakt-why/</link><pubDate>Sun, 18 Oct 2026 10:00:00 +0300</pubDate><guid>https://arkoniak.com/posts/exerakt-why/</guid><description>Why I started Exerakt: to see how far models can get writing a real program on their own, in a structured process and without a big bill, and to get a Matrix desktop client I actually want to use.</description><content:encoded><![CDATA[<h2 id="the-experiment">The experiment</h2>
<p>I like trying new things. At work I use AI all the time, but as an assistant: I am in the loop for
every feature. I agree with the agent on what it is going to write, correct it as it goes, and keep
its work in line with the bigger picture of the project. This works very well. What I had never
tried is the opposite: let the model work on its own from start to finish, choose the solutions,
find its own mistakes and improve its own code.</p>
<p>At the same time I wanted to use my own experience. A model works better when its work is
structured: there is a problem statement, a breakdown into tasks, steps that end with tests and a
review of the design. Not &ldquo;make it good&rdquo;, but a process that looks like the classic way of building
software, the one I know works. There is nothing new here. It is only a formalisation of what
already exists, written down precisely enough to be run without me.</p>
<p>And I did not want to spend thousands of dollars on it. So the third thing I wanted to see is how
the models work together when each one does the job that fits its price:</p>
<ul>
<li>Claude is the organiser and planner: it designs the interfaces, writes the tests and splits the
work into small tasks.</li>
<li>Jev, a small and very cheap decision model, is the orchestrator: it checks the result against the
task and decides what happens next.</li>
<li><a href="https://prismml.com/news/bonsai-2-27b">Bonsai</a>, a 27B model running locally on my GPU, does all
the grunt work.</li>
</ul>
<p>I had heard about this kind of setup, but I had never used it in practice.</p>
<h2 id="the-project">The project</h2>
<p>Then the question was what to build. I did not want another to-do application or another proof of
concept. I have made too many pet projects like that, and they led nowhere. This time it had to be
something I really need and will use every day.</p>
<p>I chose a desktop client for <a href="https://matrix.org/">Matrix</a>. Matrix is an open, federated
chat protocol: anyone can run a server (I run my own), servers talk to each other the way email
servers do, rooms can be end-to-end encrypted, and any client works with any server.</p>
<p>This may look like a strange choice, because there are already many Matrix desktop clients. But each
of them has something that does not work for me:</p>
<ul>
<li>Element is the closest to what I need, but it runs only on Electron, which is heavy, and on my
Hyprland desktop the left Alt key opens its menu, while I switch keyboard layouts with left Alt.</li>
<li>FluffyChat is poor in features: it turns message HTML into its own widgets, so formatting comes
out badly, and it has no threads.</li>
<li>With the others there were other unpleasant things: no custom themes in Nheko, no text selection
with the mouse in Fractal.</li>
</ul>
<p>These are all good clients, and I have no big complaints about them. I could live with their
shortcomings or send pull requests. The closest to my wishes was
<a href="https://github.com/surakin/tesseract">Tesseract</a>, but it develops fast enough that keeping a fork
would be expensive, and I did not want to carry its Windows and macOS code.</p>
<p>So two things came together: the wish to try a process where models write the code, and the
wish to have a good client made for my needs, one I can change in any direction without depending on
the plans, means or taste of its maintainers. That is how Exerakt was born: a Matrix desktop client
for Wayland, written in Rust.</p>
<p>How it is going is the subject of the next posts.</p>
]]></content:encoded></item></channel></rss>