<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ekioo</title>
    <description>The latest articles on DEV Community by Ekioo (@ekioo).</description>
    <link>https://dev.to/ekioo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3909457%2F323e0c57-5ab5-458b-b4ee-80d37faf28b4.png</url>
      <title>DEV Community: Ekioo</title>
      <link>https://dev.to/ekioo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ekioo"/>
    <language>en</language>
    <item>
      <title>A tool you use shapes itself through use</title>
      <dc:creator>Ekioo</dc:creator>
      <pubDate>Fri, 21 Aug 2026 07:04:36 +0000</pubDate>
      <link>https://dev.to/ekioo/a-tool-you-use-shapes-itself-through-use-32mf</link>
      <guid>https://dev.to/ekioo/a-tool-you-use-shapes-itself-through-use-32mf</guid>
      <description>&lt;p&gt;A week ago, I &lt;a href="https://dev.to/en/blog/kittyclaw-agent-driven-kanban"&gt;shipped KittyClaw&lt;/a&gt; - my local kanban for driving AI agents. Since then, I've been using it every day, across multiple projects, with multiple agents. In a week, the tool has changed.&lt;/p&gt;

&lt;p&gt;No refactor. No "v2". Just a handful of features added along the way, each born from a real friction I didn't even know existed until I started using the thing.&lt;/p&gt;

&lt;p&gt;Here's what strikes me looking back at this week: &lt;strong&gt;a tool you make yourself grows differently from a tool you put up with&lt;/strong&gt;. I think that deserves its own article.&lt;/p&gt;

&lt;p&gt;This piece documents &lt;a href="https://kittyclaw.dev/" rel="noopener noreferrer"&gt;KittyClaw&lt;/a&gt;, the kanban orchestrator at the center of the &lt;a href="https://ekioo.com/en" rel="noopener noreferrer"&gt;Ekioo&lt;/a&gt; agent-fleet R&amp;amp;D. Alongside &lt;a href="https://bloomii.fr/" rel="noopener noreferrer"&gt;Bloomii&lt;/a&gt; (constructive-journalism media) and &lt;a href="https://kalceo.fr/" rel="noopener noreferrer"&gt;Kalceo&lt;/a&gt; (regulatory B2B SaaS for construction contractors), KittyClaw runs the AI agents that drive these projects in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  The "friction → feature" cycle got absurdly short
&lt;/h2&gt;

&lt;p&gt;Let me give you a concrete example.&lt;/p&gt;

&lt;p&gt;Monday, I create a ticket to rebuild the water system on Aekan. It quickly becomes clear it's too big - my &lt;code&gt;programmer&lt;/code&gt; and &lt;code&gt;technical-artist&lt;/code&gt; agents need to split the work. I start creating multiple tickets linked via &lt;code&gt;#42&lt;/code&gt; in descriptions. Except the agent loses the thread, and so do I.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tuesday, I have sub-tickets.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not because they were on a roadmap. Not because a request bubbled up from a backlog. Because &lt;strong&gt;I needed them the day before&lt;/strong&gt;, the code is mine, and it took me a few hours to add them cleanly.&lt;/p&gt;

&lt;p&gt;In SaaS land, this same need would have ended up on a community forum, been aggregated with 50 other similar requests, waited a quarter to reach &lt;em&gt;under consideration&lt;/em&gt;, then maybe a year before landing in prod. Or never.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The ratio of "need detected → feature shipped" went from months to hours.&lt;/strong&gt; That changes everything.&lt;/p&gt;




&lt;h2&gt;
  
  
  The "too small" features finally exist
&lt;/h2&gt;

&lt;p&gt;Here's the second thing this week revealed: there's an entire category of features that didn't exist anywhere - not because they're useless, but because they're &lt;strong&gt;too small for a vendor to bother with&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;: a Ctrl+Z that undoes the last action on the board.&lt;/p&gt;

&lt;p&gt;It's one of the most useful features I've added this week. It literally does one thing: when you mess up on a ticket, you hit Ctrl+Z and it's reversed. Accidental move, wrong priority, sub-ticket dropped in the wrong place - all undone.&lt;/p&gt;

&lt;p&gt;A few tools do it partially (Linear has an undo toast on some actions, Trello too on drag-and-drop), but rarely in a fully keyboard-driven, cover-everything way. Why? Because it's &lt;strong&gt;too small to justify a roadmap item&lt;/strong&gt;, and &lt;strong&gt;too diffuse to be a marketing argument&lt;/strong&gt;. But when you use it yourself, it's the difference between "I move tickets without thinking" and "I'm careful with every click".&lt;/p&gt;

&lt;p&gt;Psychologically, it's huge. And it's exactly the kind of feature you only get in tools you make for yourself.&lt;/p&gt;




&lt;h2&gt;
  
  
  The tool knows things I didn't
&lt;/h2&gt;

&lt;p&gt;Another weird observation from this week: &lt;strong&gt;my agents surfaced bugs I'd never have found on my own&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One agent created a ticket with &lt;code&gt;"status": "InProgress"&lt;/code&gt; instead of &lt;code&gt;"In Progress"&lt;/code&gt;. The ticket ended up in a ghost column. Another sent comments with an empty author - "null created the ticket" in the activity log.&lt;/p&gt;

&lt;p&gt;These are cases I'd never have triggered as a human user. But an agent, which reads the docs fast and tests everything, finds them in a day.&lt;/p&gt;

&lt;p&gt;Result: I added &lt;strong&gt;silent validations&lt;/strong&gt; I'd never have written if I'd stayed the only user. The API became more robust because it was tested by agents before being tested by humans.&lt;/p&gt;

&lt;p&gt;It's an interesting inversion: &lt;strong&gt;agents are my best QA&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The quiet satisfaction of fixing your own bugs
&lt;/h2&gt;

&lt;p&gt;There's also something I hadn't anticipated and want to mention honestly: &lt;strong&gt;the emotional satisfaction&lt;/strong&gt; of fixing your own annoyances.&lt;/p&gt;

&lt;p&gt;When you hit a bug in a paid SaaS, what do you do? You report it, you wait, you forget. The irritant persists and ruins your mood every time you hit that part of the workflow.&lt;/p&gt;

&lt;p&gt;When you hit a bug in your own tool, you fix it. On the spot. And every time you hit that part of the workflow after, you think "oh right, I fixed that".&lt;/p&gt;

&lt;p&gt;That small repeated satisfaction, accumulated over a week, &lt;strong&gt;actually affects the energy you bring to your other projects&lt;/strong&gt;. It's not an argument you see often, but it's real.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it says about our relationship with tools
&lt;/h2&gt;

&lt;p&gt;I think we're living through an interesting moment. For twenty years, we've outsourced our tooling to SaaS vendors who decide what we can do, at what pace, and at what price. We've learned to live with "almost good" tools that never do exactly what we want, because building our own seemed too expensive.&lt;/p&gt;

&lt;p&gt;That cost is &lt;strong&gt;collapsing&lt;/strong&gt;. Not only thanks to AI agents - but through the combination of better stacks, solid templates, and agents that speed up the "writing code" phase by 5-10x.&lt;/p&gt;

&lt;p&gt;What this means concretely: &lt;strong&gt;custom tools are becoming economically rational again&lt;/strong&gt; for needs that, yesterday, would never have justified the cost.&lt;/p&gt;

&lt;p&gt;KittyClaw is a kanban. There are dozens on the market. But none of them is exactly what I need, and none ever will be - because my need is too niche for their roadmap. So I'm making it myself, and sculpting it week by week.&lt;/p&gt;




&lt;h2&gt;
  
  
  In summary
&lt;/h2&gt;

&lt;p&gt;This week, KittyClaw gained: sub-tickets, a dedicated sub-kanban, a global Ctrl+Z, and three silent validations. Nothing revolutionary on its own. But each addition solved an irritation I would &lt;strong&gt;never&lt;/strong&gt; have seen fixed in a third-party tool.&lt;/p&gt;

&lt;p&gt;And maybe that's the real point: &lt;strong&gt;once you start making your own tools, you realize how many compromises you were accepting without noticing&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;

&lt;p&gt;Everything is up to date on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://github.com/Ekioo/KittyClaw" rel="noopener noreferrer"&gt;github.com/Ekioo/KittyClaw&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If this approach resonates - or if you're also building custom tools for your workflow - join me on &lt;strong&gt;Discord&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://discord.gg/4MVPfw9wTQ" rel="noopener noreferrer"&gt;Join the Discord&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>kittyclaw</category>
      <category>tooling</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>From kanban to harness: when the tracking tool becomes the orchestrator</title>
      <dc:creator>Ekioo</dc:creator>
      <pubDate>Fri, 21 Aug 2026 00:02:26 +0000</pubDate>
      <link>https://dev.to/ekioo/from-kanban-to-harness-when-the-tracking-tool-becomes-the-orchestrator-34id</link>
      <guid>https://dev.to/ekioo/from-kanban-to-harness-when-the-tracking-tool-becomes-the-orchestrator-34id</guid>
      <description>&lt;p&gt;When I &lt;a href="https://dev.to/en/blog/kittyclaw-agent-driven-kanban"&gt;shipped KittyClaw&lt;/a&gt; two weeks ago, the tool did one thing: serve as a board. The Claude agents ran alongside - first by hand, then via &lt;a href="https://dev.to/en/blog/dispatcher-agent-orchestration"&gt;a &lt;code&gt;dispatcher.mjs&lt;/code&gt;&lt;/a&gt;: a Node script polling KittyClaw's API, triggering the right agent based on who was assigned to which ticket.&lt;/p&gt;

&lt;p&gt;The dispatcher worked great. It orchestrated Aekan's 13 agents for weeks. But it was an &lt;strong&gt;external process&lt;/strong&gt;: one more &lt;code&gt;node dispatcher.mjs&lt;/code&gt; to launch, a state file (&lt;code&gt;dispatch-state.json&lt;/code&gt;) to keep in sync, logs to dig up in &lt;code&gt;.agents/channel/debug.log&lt;/code&gt;, a config to copy-paste across projects in JS.&lt;/p&gt;

&lt;p&gt;Today, the dispatcher doesn't exist anymore. Orchestration lives &lt;strong&gt;inside KittyClaw&lt;/strong&gt;. I run &lt;code&gt;dotnet run&lt;/code&gt; on KittyClaw, nothing else. Aekan's 13 agents still run - but the infra that drives them is now a first-class citizen of the board.&lt;/p&gt;

&lt;p&gt;This shift from "dispatcher on the side" to "dispatcher inside the board" is small in lines of code, but it completely changes what the tool is. And how I work.&lt;/p&gt;

&lt;p&gt;This piece documents &lt;a href="https://kittyclaw.dev/" rel="noopener noreferrer"&gt;KittyClaw&lt;/a&gt;, the kanban orchestrator at the center of the &lt;a href="https://ekioo.com/en" rel="noopener noreferrer"&gt;Ekioo&lt;/a&gt; agent-fleet R&amp;amp;D. Alongside &lt;a href="https://bloomii.fr/" rel="noopener noreferrer"&gt;Bloomii&lt;/a&gt; (constructive-journalism media) and &lt;a href="https://kalceo.fr/" rel="noopener noreferrer"&gt;Kalceo&lt;/a&gt; (regulatory B2B SaaS for construction contractors), KittyClaw runs the AI agents that drive these projects in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before: two processes to run, two places to look
&lt;/h2&gt;

&lt;p&gt;The old setup was three stacked layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;KittyClaw&lt;/strong&gt; - the board, with its UI and REST API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;dispatcher.mjs&lt;/code&gt;&lt;/strong&gt; - a separate Node script in the project's &lt;code&gt;.agents/channel/&lt;/code&gt;, launched manually in a terminal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code&lt;/strong&gt; - the agents themselves, launched by the dispatcher.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It worked. But every project had its own &lt;code&gt;dispatcher.mjs&lt;/code&gt;, usually forked from Aekan and hand-adapted. Patterns duplicated: 30s polling, &lt;code&gt;code&lt;/code&gt; lock, evaluator debounce, daily budget. Adding a feature (say &lt;code&gt;boardIdle&lt;/code&gt; or &lt;code&gt;subTicketStatus&lt;/code&gt;) meant re-coding it in every dispatcher, or accepting that one project had it and others didn't.&lt;/p&gt;

&lt;p&gt;And visually, orchestration was &lt;strong&gt;invisible from the board&lt;/strong&gt;. To see an agent's live activity, I'd pop a terminal, &lt;code&gt;tail -f&lt;/code&gt; the dispatcher log, cross-reference with the board. Two places, two vocabularies.&lt;/p&gt;

&lt;p&gt;The board didn't know there was a dispatcher. The dispatcher showed nothing on the board. They talked through the API and a JSON file - fully functional, but decoupled to the point of not knowing each other.&lt;/p&gt;




&lt;h2&gt;
  
  
  The click: the dispatcher belonged inside KittyClaw
&lt;/h2&gt;

&lt;p&gt;What pushed me over was the pile-up of small repeated frictions. Every new project, I duplicated &lt;code&gt;dispatcher.mjs&lt;/code&gt;. Every useful feature (like Lain's CEO wake on idle board), I'd ask myself "do I back-port it to Aekan?". Every bug, "which of the three dispatchers is running this one?".&lt;/p&gt;

&lt;p&gt;And above all: &lt;strong&gt;KittyClaw already had all the info&lt;/strong&gt;. Tickets, assignments, columns, members flagged as agents, history. The dispatcher only re-read what the board already knew, to decide what to launch. Duplicated reasoning, not data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If it's repetitive across projects, it belongs in the engine.&lt;/strong&gt; If the board already knows the state, it's the best-placed thing to react.&lt;/p&gt;




&lt;h2&gt;
  
  
  After: the board is the orchestrator
&lt;/h2&gt;

&lt;p&gt;The new workflow is one step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I create a ticket on the board, assigned to &lt;code&gt;@programmer&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. Thirty seconds later, KittyClaw sees the ticket in the &lt;code&gt;Todo&lt;/code&gt; column with assignee &lt;code&gt;programmer&lt;/code&gt;, moves it to &lt;code&gt;InProgress&lt;/code&gt;, and launches Claude Code with the &lt;code&gt;programmer.md&lt;/code&gt; skill and the ticket context. Outputs stream into a live run panel. When the agent finishes, the ticket is ready for review.&lt;/p&gt;

&lt;p&gt;What makes this workflow possible is an &lt;code&gt;automations.json&lt;/code&gt; file in the project workspace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"trigger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ticketInColumn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"columns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Todo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"InProgress"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="nl"&gt;"assigneeSlug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"programmer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"moveTicketStatus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"to"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"InProgress"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"runClaudeSkill"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"skillFile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"skills/programmer.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"concurrencyGroup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"maxTurns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No magical engine. It's a 30s poll that reads the board, matches a pattern, and launches a &lt;code&gt;claude&lt;/code&gt; process with the right skill and context. But once it's in place, &lt;strong&gt;I'm no longer in the loop&lt;/strong&gt;. I become a supervisor.&lt;/p&gt;




&lt;h2&gt;
  
  
  What changes mentally
&lt;/h2&gt;

&lt;p&gt;The time saved is real - no more switching, no more sessions to locate, no more commands to retype. But the real shift is elsewhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With the external dispatcher&lt;/strong&gt;, I'd already moved past one-on-one chat. The board + dispatcher formed a running team. But the whole thing was &lt;strong&gt;fragmented&lt;/strong&gt;: I watched the board to decide, a terminal to see what agents were doing, logs to debug. Three views for one system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now&lt;/strong&gt;, the board &lt;em&gt;is&lt;/em&gt; the system. I see global state, I assign, I let the infra dispatch, I watch streaming runs from the ticket itself. On &lt;a href="https://dev.to/en/projects/aekan"&gt;Aekan&lt;/a&gt;, 13 agents run like this - including a &lt;code&gt;documentalist&lt;/code&gt; that wakes on every commit, a &lt;code&gt;code-janitor&lt;/code&gt; that sweeps every day at 3am, and an &lt;code&gt;evaluator&lt;/code&gt; that checks quality whenever a ticket hits &lt;code&gt;Done&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I no longer manage conversations. I manage a queue.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This metaphor shift looks trivial but runs deep. You can't hold 13 simultaneous conversations in your head. You can easily supervise a 50-task queue if the tool shows you what matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  What stays on me
&lt;/h2&gt;

&lt;p&gt;I read too many threads about "AI will do everything for me", so let me be clear: &lt;strong&gt;what matters is still what I do&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deciding what's worth doing.&lt;/li&gt;
&lt;li&gt;Writing a ticket clear enough for the agent to understand.&lt;/li&gt;
&lt;li&gt;Reviewing what comes out, accepting or pushing back.&lt;/li&gt;
&lt;li&gt;Spotting patterns worth automating.&lt;/li&gt;
&lt;li&gt;Sculpting the tool when it rubs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What integration removes is exactly what had no added value: maintaining one dispatcher per project, correlating three views, manually back-porting a feature from one project to another. What it leaves is all the rest - which is precisely the interesting work.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this lives in the board and not elsewhere
&lt;/h2&gt;

&lt;p&gt;One could imagine a separate tool: a dedicated orchestrator that reads Jira/Linear tickets and launches agents. I had that hesitation moment. But the board &lt;strong&gt;already knows&lt;/strong&gt; who's an agent, who's human, the state of tickets, who's blocked, who's ready for review. Pulling that info into another tool duplicates a source of truth - and creates two places to keep in sync.&lt;/p&gt;

&lt;p&gt;Putting orchestration &lt;em&gt;in&lt;/em&gt; the board is recognizing that &lt;strong&gt;the board is already the execution plan&lt;/strong&gt;. All that was missing was the executor.&lt;/p&gt;

&lt;p&gt;Experience-wise: when an agent runs, stream events (tool calls, file edits, tokens burned) land in a panel that opens from the ticket. No separate tab, no separate dashboard. The board is the dashboard.&lt;/p&gt;




&lt;h2&gt;
  
  
  What integration unlocks
&lt;/h2&gt;

&lt;p&gt;Some automations already existed inside &lt;code&gt;dispatcher.mjs&lt;/code&gt; - &lt;code&gt;documentalist&lt;/code&gt; on commit, &lt;code&gt;evaluator&lt;/code&gt; on &lt;code&gt;Done&lt;/code&gt; transition, daily &lt;code&gt;code-janitor&lt;/code&gt;. What integration into KittyClaw changes isn't their existence, it's their &lt;strong&gt;reach&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One feature = all projects.&lt;/strong&gt; When I added &lt;code&gt;boardIdle&lt;/code&gt; for Lain's CEO wake, Aekan and HolybotsRisingApps got it for free. With three separate dispatchers, I'd have back-ported by hand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centralized daily budget.&lt;/strong&gt; &lt;code&gt;cost-log.jsonl&lt;/code&gt; is read by the single engine, the threshold (&lt;code&gt;dailyBudgetUsd: 70&lt;/code&gt;) blocks everything non-CEO once crossed. Doing this across three independent dispatchers would have meant three implementations kept in sync.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hot reload.&lt;/strong&gt; &lt;code&gt;POST /api/projects/{slug}/automations/reload&lt;/code&gt; - I change a field in &lt;code&gt;automations.json&lt;/code&gt;, it's live. No Node process restart.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unified observability.&lt;/strong&gt; Every run is an engine object, accessible via API, streamed into the ticket's panel. No more &lt;code&gt;tail -f&lt;/code&gt; on three log files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The right way to put it: &lt;strong&gt;the same behaviors, but as infra instead of scripts&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The next post dives into the technical anatomy: what exactly happens between the moment I create a ticket and the moment Claude starts working. What context gets injected, how sessions persist across runs, how concurrency groups keep two agents from stepping on the same files.&lt;/p&gt;

&lt;p&gt;But the deep lesson is already here: &lt;strong&gt;a board isn't a tracking tool. It's an execution plan that was waiting to be plugged in.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;

&lt;p&gt;Everything is on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://github.com/Ekioo/KittyClaw" rel="noopener noreferrer"&gt;github.com/Ekioo/KittyClaw&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're building custom tooling around your agents, or want to discuss this approach, join me on &lt;strong&gt;Discord&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://discord.gg/4MVPfw9wTQ" rel="noopener noreferrer"&gt;Join the Discord&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>kittyclaw</category>
      <category>tooling</category>
      <category>ai</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>Anatomy of a ticket that runs itself</title>
      <dc:creator>Ekioo</dc:creator>
      <pubDate>Thu, 20 Aug 2026 00:03:52 +0000</pubDate>
      <link>https://dev.to/ekioo/anatomy-of-a-ticket-that-runs-itself-k0j</link>
      <guid>https://dev.to/ekioo/anatomy-of-a-ticket-that-runs-itself-k0j</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/en/blog/kittyclaw-from-kanban-to-harness"&gt;previous post&lt;/a&gt;, I told how KittyClaw shifted from passive board to orchestrator. Create a ticket, thirty seconds later Claude is on it. This post goes into the detail: &lt;strong&gt;what happens in between?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'll follow a ticket from creation to execution, stopping on the five design decisions that make the system robust - or that saved it when it wasn't yet.&lt;/p&gt;

&lt;p&gt;This piece documents &lt;a href="https://kittyclaw.dev/" rel="noopener noreferrer"&gt;KittyClaw&lt;/a&gt;, the kanban orchestrator at the center of the &lt;a href="https://ekioo.com/en" rel="noopener noreferrer"&gt;Ekioo&lt;/a&gt; agent-fleet R&amp;amp;D. Alongside &lt;a href="https://bloomii.fr/" rel="noopener noreferrer"&gt;Bloomii&lt;/a&gt; (constructive-journalism media) and &lt;a href="https://kalceo.fr/" rel="noopener noreferrer"&gt;Kalceo&lt;/a&gt; (regulatory B2B SaaS for construction contractors), KittyClaw runs the AI agents that drive these projects in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The trigger: polling, not webhooks
&lt;/h2&gt;

&lt;p&gt;When the ticket is created, nothing happens right away. KittyClaw doesn't "react" to the creation event - it &lt;strong&gt;looks at the board every 30 seconds&lt;/strong&gt; and decides what to do.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"trigger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ticketInColumn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"columns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Todo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"InProgress"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"assigneeSlug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"programmer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hesitated one evening between polling and events. Events are "cleaner" (no latency, no wasted CPU). But polling has three advantages I found decisive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It survives anything.&lt;/strong&gt; The engine crashes, restarts, misses an event - next tick, it catches up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's naturally rate-limited.&lt;/strong&gt; Even if I create 50 tickets at once, dispatch happens in 30s windows. No thundering herd.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's the same model as the old &lt;code&gt;dispatcher.mjs&lt;/code&gt;&lt;/strong&gt; I was replacing. Zero regression risk.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For rare events (a git commit, a &lt;code&gt;Done&lt;/code&gt; transition) I have specialized triggers (&lt;code&gt;gitCommit&lt;/code&gt;, &lt;code&gt;statusChange&lt;/code&gt;) that poll at 60s or 30s. For events you can't afford to miss, I have &lt;code&gt;boardIdle&lt;/code&gt; and &lt;code&gt;agentInactivity&lt;/code&gt; that watch for &lt;em&gt;absence&lt;/em&gt; of activity - impossible to model as events.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule of thumb&lt;/strong&gt;: when in doubt between polling and webhooks in a solo-dev system, pick polling. You debug with a log file and &lt;code&gt;sleep 30&lt;/code&gt;, not ngrok plus a retry stack.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Conditions: a gate before dispatch
&lt;/h2&gt;

&lt;p&gt;Not every ticket matched by the trigger is dispatched. Between trigger and action sits a list of conditions to pass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"conditions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"minDescriptionLength"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"length"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"labels"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"labels"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"release"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On HolybotsRisingApps for example, the &lt;code&gt;publisher&lt;/code&gt; automation fires only if the ticket carries the &lt;code&gt;release&lt;/code&gt; label &lt;strong&gt;and&lt;/strong&gt; either &lt;code&gt;app:borne&lt;/code&gt; or &lt;code&gt;app:gamemaster&lt;/code&gt;. The &lt;code&gt;developer&lt;/code&gt; only takes tickets with descriptions of at least 50 chars - to keep a hastily-thrown "fix the bug" ticket from dispatching before the producer grooms it.&lt;/p&gt;

&lt;p&gt;Conditions are the right place to encode &lt;strong&gt;board quality policies&lt;/strong&gt;. They don't replace human judgment, but they kill 80% of the cases where I'd launch an agent for nothing.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Injected context: minimal, deliberately
&lt;/h2&gt;

&lt;p&gt;When dispatch fires, KittyClaw launches a &lt;code&gt;claude&lt;/code&gt; process with a prompt. The prompt is a lot simpler than you might expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{contents of skills/programmer.md}

Focus on ticket #42: {ticket title}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No description, no comments, no sub-tickets, no repo context.&lt;/p&gt;

&lt;p&gt;Why so little? &lt;strong&gt;Because the agent already has the tools to fetch them.&lt;/strong&gt; It can hit the KittyClaw API (&lt;code&gt;GET /api/projects/{slug}/tickets/{id}&lt;/code&gt;), read comments, mentions, linked tickets, sub-tickets, on demand. And it runs inside the project's &lt;code&gt;WorkspacePath&lt;/code&gt; - it can &lt;code&gt;git log&lt;/code&gt;, read files, do its own research.&lt;/p&gt;

&lt;p&gt;Every piece of context you stuff into the prompt is context you &lt;strong&gt;pay for in tokens&lt;/strong&gt; on every run, including when it's not needed. Letting the agent fetch on demand is 10× cheaper and 10× fresher.&lt;/p&gt;

&lt;p&gt;The skill file (&lt;code&gt;skills/programmer.md&lt;/code&gt;) contains the craft rules: how to read a ticket, how to post comments, how to handle sub-tickets, when to push back for review. It's the &lt;strong&gt;persona&lt;/strong&gt; - stable, versioned in the repo, editable without touching KittyClaw's code.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Sessions: memory between runs
&lt;/h2&gt;

&lt;p&gt;If I re-dispatch the same agent to the same ticket ten minutes later, I don't want it starting from zero. It must remember what it read, did, concluded last run.&lt;/p&gt;

&lt;p&gt;Claude Code supports &lt;code&gt;--resume &amp;lt;session-id&amp;gt;&lt;/code&gt; natively. KittyClaw hooks into that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;existingSessionId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_sessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSessionId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workspace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agentName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ticketId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sessionId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;existingSessionId&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;isResume&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;existingSessionId&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key is &lt;code&gt;(workspace, agent, ticket)&lt;/code&gt;. Each combination has its own persistent session, stored in &lt;code&gt;.agents/channel/dispatch-state.json&lt;/code&gt;. When the agent comes back to the ticket, it's &lt;code&gt;--resume&lt;/code&gt; and the prompt becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The owner has posted feedback on ticket #42: {title}
Read ALL owner comments on this ticket and address them.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No need to resend the skill - it's in the session. You just tell it: "come back, there's new input, take a look".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Point that cost me a morning&lt;/strong&gt;: sessions must survive KittyClaw restarts. I started keeping them in memory, then a &lt;code&gt;dotnet watch&lt;/code&gt; nuked them all. Since then everything is persisted to disk immediately, in the same JSON file the old &lt;code&gt;dispatcher.mjs&lt;/code&gt; used. Bonus: my projects still running on &lt;code&gt;dispatcher.mjs&lt;/code&gt; can migrate without losing sessions.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Concurrency: keeping agents from stepping on each other's files
&lt;/h2&gt;

&lt;p&gt;Real problem: if &lt;code&gt;programmer&lt;/code&gt; and &lt;code&gt;3d-artist&lt;/code&gt; run at the same time on the same repo, they collide. Two processes editing files, a &lt;code&gt;git status&lt;/code&gt; that no longer knows who did what.&lt;/p&gt;

&lt;p&gt;Solution: &lt;strong&gt;concurrency groups&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"runClaudeSkill"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skillFile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"skills/programmer.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"concurrencyGroup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mutuallyExclusiveWith"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"producer-commit"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rules are simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;concurrencyGroup&lt;/code&gt;&lt;/strong&gt;: at most one active run per group. Every agent that touches code sits in &lt;code&gt;"code"&lt;/code&gt; - &lt;code&gt;programmer&lt;/code&gt;, &lt;code&gt;3d-artist&lt;/code&gt;, &lt;code&gt;technical-artist&lt;/code&gt;, &lt;code&gt;qa-tester&lt;/code&gt;, &lt;code&gt;documentalist&lt;/code&gt;, &lt;code&gt;code-janitor&lt;/code&gt;. One at a time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;mutuallyExclusiveWith&lt;/code&gt;&lt;/strong&gt;: while one run is active, it blocks the listed groups. HolybotsRisingApps' &lt;code&gt;producer-commit&lt;/code&gt;, which commits/PRs a finished ticket, blocks &lt;code&gt;code&lt;/code&gt;, &lt;code&gt;producer&lt;/code&gt;, and &lt;code&gt;publisher&lt;/code&gt; - because it needs a stable repo to commit cleanly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implicit dedup&lt;/strong&gt;: no two active runs on &lt;code&gt;(agent, ticket)&lt;/code&gt;. If you post a comment while a run is live, it'll be picked up next poll, not in parallel.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These three rules eliminated 100% of the file-conflict issues I used to see with the old JS dispatcher. Not because the JS was bad - because the concurrency rules lived in if/else code instead of being declarative.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. The budget: the fuse you hope never blows
&lt;/h2&gt;

&lt;p&gt;Last piece, optional but precious:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dailyBudgetUsd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;KittyClaw tracks run cost (via the &lt;code&gt;cost-log.jsonl&lt;/code&gt; Claude Code emits natively). As soon as the day's sum crosses the threshold, &lt;strong&gt;all non-CEO dispatches are blocked&lt;/strong&gt;. Only a supervising agent, if any, can still run to decide what to do.&lt;/p&gt;

&lt;p&gt;I've never hit this threshold in normal conditions. But I hit it &lt;strong&gt;once&lt;/strong&gt; because of an infinite loop between two agents ping-ponging comments at each other. The budget cut out at $70, I woke up the next morning with a manageable bill and a lesson learned. Without the budget, it could have been much worse.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recap: the full cycle
&lt;/h2&gt;

&lt;p&gt;What actually happens, in order, when I create a ticket:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;t = 0s&lt;/strong&gt;: I create the ticket via UI or API. Status &lt;code&gt;Todo&lt;/code&gt;, assignee &lt;code&gt;programmer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;t ≈ 15s&lt;/strong&gt;: the next &lt;code&gt;ticketInColumn&lt;/code&gt; tick (poll every 30s) spots the ticket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;t ≈ 15s&lt;/strong&gt;: conditions are evaluated (description length, labels). All pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;t ≈ 15s&lt;/strong&gt;: the &lt;code&gt;moveTicketStatus&lt;/code&gt; action flips the ticket to &lt;code&gt;InProgress&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;t ≈ 15s&lt;/strong&gt;: the engine checks concurrency groups. Nobody's on &lt;code&gt;code&lt;/code&gt; - go.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;t ≈ 15s&lt;/strong&gt;: session lookup. No existing session for &lt;code&gt;(workspace, programmer, #42)&lt;/code&gt; - new session created.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;t ≈ 16s&lt;/strong&gt;: &lt;code&gt;claude&lt;/code&gt; process launched with &lt;code&gt;--print --verbose --output-format stream-json --session-id &amp;lt;uuid&amp;gt;&lt;/code&gt;, prompt = skill + focus.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;t ≈ 17s&lt;/strong&gt;: stream events (&lt;code&gt;assistant&lt;/code&gt;, &lt;code&gt;tool_use&lt;/code&gt;, &lt;code&gt;tool_result&lt;/code&gt;) flow into the run panel, visible from the ticket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;t ≈ N minutes&lt;/strong&gt;: Claude is done. It posted comments, maybe created sub-tickets, maybe moved the ticket to &lt;code&gt;OwnerReview&lt;/code&gt;. The run is logged with its cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;t = N + 30 min of silence on Done&lt;/strong&gt;: if the ticket moves to &lt;code&gt;Done&lt;/code&gt; and stays quiet for 30 min, the &lt;code&gt;evaluator&lt;/code&gt; wakes up and reviews.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This pipeline is broadly what I had before in JS with &lt;code&gt;dispatcher.mjs&lt;/code&gt;. But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's declarative (&lt;code&gt;automations.json&lt;/code&gt;) instead of imperative (code).&lt;/li&gt;
&lt;li&gt;It's integrated with the board (runs are visible from tickets).&lt;/li&gt;
&lt;li&gt;It survives crashes (sessions and state persisted).&lt;/li&gt;
&lt;li&gt;It applies to every project at once (one engine, N configs).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What this teaches me about automation
&lt;/h2&gt;

&lt;p&gt;The more automations I add, the more I realize &lt;strong&gt;the right granularity is neither "code everything" nor "configure everything"&lt;/strong&gt;. It's a collaboration between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A simple, generic &lt;strong&gt;engine&lt;/strong&gt; in code. A handful of trigger types, action types, concurrency and sessions.&lt;/li&gt;
&lt;li&gt;Declarative per-project &lt;strong&gt;configs&lt;/strong&gt;. That's where policy lives: who dispatches when, who has what budget, who blocks whom.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skills&lt;/strong&gt; in Markdown, versioned in the project repo. That's where persona and craft live.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The engine knows nothing about video games, docs, or CI. The skills know nothing about polling or concurrency groups. Configs bind the two.&lt;/p&gt;

&lt;p&gt;This separation makes adding a new agent trivial: write a skill, add 10 lines to &lt;code&gt;automations.json&lt;/code&gt;, live. No deploy, no restart - the &lt;code&gt;POST /api/projects/{slug}/automations/reload&lt;/code&gt; endpoint does it hot.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;On Aekan, 13 agents run on this system. On HolybotsRisingApps, 6 - including a specific release workflow (&lt;code&gt;publisher&lt;/code&gt; gated by label, &lt;code&gt;producer-commit&lt;/code&gt; mutually exclusive with the rest). On Lain, 15 with an active daily budget and a CEO that wakes on idle.&lt;/p&gt;

&lt;p&gt;Every project has its own rules. The engine doesn't change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And maybe that's the right definition of a harness&lt;/strong&gt;: a generic infra on top of which each project writes its own choreography.&lt;/p&gt;




&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;

&lt;p&gt;Everything is open source:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://github.com/Ekioo/KittyClaw" rel="noopener noreferrer"&gt;github.com/Ekioo/KittyClaw&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The engine lives in &lt;code&gt;KittyClaw.Core/Automation/&lt;/code&gt;. The migration guide from a &lt;code&gt;dispatcher.mjs&lt;/code&gt; is in &lt;code&gt;docs/automation-migration.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To discuss, ask questions, or share your own agent-orchestration patterns - the Discord:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://discord.gg/4MVPfw9wTQ" rel="noopener noreferrer"&gt;Join the Discord&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>kittyclaw</category>
      <category>tooling</category>
      <category>ai</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>Blazor Server .NET 10, What Really Changed in Production</title>
      <dc:creator>Ekioo</dc:creator>
      <pubDate>Wed, 19 Aug 2026 00:07:58 +0000</pubDate>
      <link>https://dev.to/ekioo/blazor-server-net-10-what-really-changed-in-production-3fg9</link>
      <guid>https://dev.to/ekioo/blazor-server-net-10-what-really-changed-in-production-3fg9</guid>
      <description>&lt;p&gt;This piece comes from &lt;a href="https://ekioo.com/en" rel="noopener noreferrer"&gt;Ekioo&lt;/a&gt;, an R&amp;amp;D project documenting an autonomous AI agent fleet running production work across &lt;a href="https://bloomii.fr/" rel="noopener noreferrer"&gt;Bloomii&lt;/a&gt; (constructive journalism), &lt;a href="https://kalceo.fr/" rel="noopener noreferrer"&gt;Kalceo&lt;/a&gt; (regulatory B2B SaaS), and &lt;a href="https://kittyclaw.dev/" rel="noopener noreferrer"&gt;KittyClaw&lt;/a&gt; (the kanban orchestrator).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With Blazor Articles
&lt;/h2&gt;

&lt;p&gt;Most articles about Blazor .NET 10 look like this: a list of new features copy-pasted from the release notes, a few code snippets with no context, and an enthusiastic conclusion along the lines of "Blazor has never been better!"&lt;/p&gt;

&lt;p&gt;That's not what you'll find here.&lt;/p&gt;

&lt;p&gt;Ekioo.com runs on &lt;strong&gt;Blazor Server + .NET 10 in production&lt;/strong&gt;. Not a demo project, not a tutorial: a real site with bilingual content, a blog, a sitemap, project pages, and SEO requirements. This article is a field report on what I've actually observed, what changed, what got better, and what still costs you something.&lt;/p&gt;




&lt;h2&gt;
  
  
  The .NET 10 Render Pipeline: What Actually Moved
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before: Three Modes, Three Models
&lt;/h3&gt;

&lt;p&gt;Up through .NET 8, Blazor had three distinct and mutually exclusive render modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Blazor Server&lt;/strong&gt;: the DOM is managed server-side, interactions travel over SignalR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blazor WebAssembly&lt;/strong&gt;: the entire runtime is downloaded to the browser, no server at runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blazor SSR (static)&lt;/strong&gt;: server-side HTML on first load, no interactivity afterwards.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;.NET 8 introduced &lt;strong&gt;render modes&lt;/strong&gt;, the idea that a single project can mix these modes at component granularity. One component can be &lt;code&gt;@rendermode InteractiveServer&lt;/code&gt; (SignalR), another &lt;code&gt;@rendermode InteractiveWebAssembly&lt;/code&gt;, another purely static. Elegant on paper. Treacherous in practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  .NET 9: Stabilization, Not Revolution
&lt;/h3&gt;

&lt;p&gt;.NET 9 mostly fixed the friction introduced by .NET 8. The render mode system became more predictable, hydration errors less frequent, streaming SSR behavior more stable. It's the version where Blazor SSR went from "promising but fragile" to "seriously usable."&lt;/p&gt;

&lt;h3&gt;
  
  
  .NET 10: Static Rendering Becomes First Class
&lt;/h3&gt;

&lt;p&gt;What changes in .NET 10 is that &lt;strong&gt;static rendering (Static SSR) becomes the reference mode&lt;/strong&gt;. The default templates generate SSR-first applications. Interactivity is opt-in, localized, component by component.&lt;/p&gt;

&lt;p&gt;Concretely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pages are rendered as static SSR by default, complete HTML sent to the client, no JavaScript required.&lt;/li&gt;
&lt;li&gt;Components that need interactivity explicitly declare their render mode.&lt;/li&gt;
&lt;li&gt;The SignalR circuit is opened only where necessary, not globally for the whole application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a site like ekioo.com, mostly static content, a handful of isolated interactions, this is the model that makes sense. And it's the model that .NET 10 stops treating as a special case.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://ekioo.com/en/blog/blazor-server-dotnet10-ce-qui-change" rel="noopener noreferrer"&gt;View the workflow diagram in the original Ekioo article&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Interactive Blazor SSR: The Component Model That Changed
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What "Interactive" Means Now
&lt;/h3&gt;

&lt;p&gt;Before .NET 8, "Blazor Server" meant that the entire component lifecycle went through SignalR. &lt;code&gt;OnInitializedAsync&lt;/code&gt;, &lt;code&gt;OnParametersSetAsync&lt;/code&gt;, event handlers, everything traveled over the wire. That came at a cost: latency, server resources, network fragility.&lt;/p&gt;

&lt;p&gt;In .NET 10, the lifecycle for static components is much simpler: they live for the duration of a render, on the server, then disappear. No server-side state, no open circuit. The page is rendered, the HTML is sent, done.&lt;/p&gt;

&lt;p&gt;When you add &lt;code&gt;@rendermode InteractiveServer&lt;/code&gt; to a component, you're explicitly opting into the SignalR model for that component only. The rest of the page stays static.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Pitfalls of Mixing Static and Interactive
&lt;/h3&gt;

&lt;p&gt;This hybrid model is powerful but requires discipline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pitfall 1: complex parameters don't cross the boundary&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A static component cannot pass a complex C# object to an interactive component. The static/interactive boundary is serialized, you can only pass primitive types or JSON-serializable types. If you try to pass an &lt;code&gt;IEnumerable&amp;lt;MyComplexType&amp;gt;&lt;/code&gt; from a static parent to an interactive child, it won't compile or will fail at runtime.&lt;/p&gt;

&lt;p&gt;The solution: pass IDs or keys, and let the interactive component fetch the data itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pitfall 2: services aren't shared the same way&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In interactive mode, &lt;code&gt;Scoped&lt;/code&gt; services are tied to the SignalR circuit, they survive intra-SPA navigation. In static mode, they're tied to the HTTP request, recreated on every navigation. If you have a service maintaining user state in memory, its behavior changes drastically depending on the render mode.&lt;/p&gt;

&lt;p&gt;For ekioo.com, I simplified: no in-memory state, everything recalculated from markdown files on each request. A deliberate choice to avoid this class of bugs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pitfall 3: prerendering by default&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;InteractiveServer&lt;/code&gt; mode, Blazor performs a static prerender before establishing the SignalR circuit. That means &lt;code&gt;OnInitializedAsync&lt;/code&gt; runs twice: once server-side (static render), once when the circuit opens (interactive render). If you're making HTTP calls in &lt;code&gt;OnInitializedAsync&lt;/code&gt;, they double.&lt;/p&gt;

&lt;p&gt;The fix: check &lt;code&gt;!FirstRender&lt;/code&gt; or use &lt;code&gt;OnAfterRenderAsync(bool firstRender)&lt;/code&gt; for operations that should only run once.&lt;/p&gt;




&lt;h2&gt;
  
  
  Streaming Rendering: Real Behavior and Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What Streaming SSR Actually Does
&lt;/h3&gt;

&lt;p&gt;Streaming rendering (enabled with &lt;code&gt;@attribute [StreamRendering]&lt;/code&gt;) lets Blazor send HTML progressively as components render, without waiting for the whole page to be ready.&lt;/p&gt;

&lt;p&gt;Concretely: if a component is loading slow data (a database query, an API call), Blazor can send the rest of the page immediately and "stream" that component's content when its data becomes available.&lt;/p&gt;

&lt;p&gt;From the browser's perspective: the page starts displaying very quickly, with placeholders for pending parts, then the final content replaces the placeholders with no visible reload.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Cases on ekioo.com
&lt;/h3&gt;

&lt;p&gt;On ekioo.com, the blog lists articles from markdown files on disk. That's fast, no need for streaming. But if I had a "recent posts" section with data from a third-party API (variable latency), streaming would be relevant: I could display the rest of the page instantly and let that section load at its own pace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pitfalls to Know
&lt;/h3&gt;

&lt;p&gt;Streaming SSR changes how CSS selectors and JavaScript initialized on load interact with the DOM. If you have scripts that run on &lt;code&gt;DOMContentLoaded&lt;/code&gt; and look for DOM elements, they may run before streamed elements are available.&lt;/p&gt;

&lt;p&gt;On ekioo.com, the particle animation is initialized via a classic script, I had to make sure it attaches to the page's canvas and not to elements that might arrive via streaming later.&lt;/p&gt;

&lt;p&gt;Another point: SEO tools and crawlers increasingly understand streaming (Googlebot has supported JavaScript rendering for a long time), but classic audit tools may see incomplete content if they don't handle chunked responses.&lt;/p&gt;




&lt;h2&gt;
  
  
  Static SSR and SEO: What It Really Changes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Core Problem: Blazor SPA and Googlebot
&lt;/h3&gt;

&lt;p&gt;Classic Blazor WebAssembly was a serious SEO problem. Googlebot receives an empty HTML shell, waits for WebAssembly to load (variable timeout), and indexes potentially incomplete or delayed content. Not ideal.&lt;/p&gt;

&lt;p&gt;Blazor Server partially avoided this, prerender sent complete HTML on the first load. But the server-side render time and establishing the SignalR circuit added latency, and Core Web Vitals metrics suffered.&lt;/p&gt;

&lt;h3&gt;
  
  
  Static SSR: Complete HTML, Zero JavaScript Required
&lt;/h3&gt;

&lt;p&gt;With pure Static SSR, the server generates complete HTML and sends it directly. No JavaScript needed to see the content. Googlebot receives exactly what the user sees. It's the behavior of a PHP site or a static site generator, but with the productivity of C# and the Razor component model.&lt;/p&gt;

&lt;p&gt;For ekioo.com, here's what that looks like in practice:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time to First Byte (TTFB)&lt;/strong&gt;: on a modest VPS (2 vCPU, 4 GB RAM), static pages serve in &lt;strong&gt;&amp;lt; 50 ms&lt;/strong&gt; after warm-up. The cold start (dotnet process startup) is the only notable latency, but it's a one-shot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Largest Contentful Paint (LCP)&lt;/strong&gt;: without JavaScript blocking the render, the LCP is the main content, not a spinner or an empty skeleton. On ekioo.com's blog pages, the LCP is the article title, visible as soon as the HTML arrives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cumulative Layout Shift (CLS)&lt;/strong&gt;: streaming SSR can introduce CLS if placeholders have different dimensions than the final content. With pure Static SSR (no streaming), this risk doesn't exist: the layout is fixed from the first byte.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Sitemap as an Authority Signal
&lt;/h3&gt;

&lt;p&gt;Ekioo.com exposes two sitemaps: &lt;code&gt;/sitemap.xml&lt;/code&gt; (FR + EN) and &lt;code&gt;/sitemap-content.xml&lt;/code&gt; (raw markdown). The &lt;code&gt;lastmod&lt;/code&gt; dates are pulled directly from the YAML frontmatter of the markdown files.&lt;/p&gt;

&lt;p&gt;This isn't decoration: Googlebot uses &lt;code&gt;lastmod&lt;/code&gt; to prioritize recrawling. An incorrect date (too old or too recent) blurs that signal. With Blazor SSR, I generate these dates dynamically from files on disk, no possible mismatch between the actual content and what the sitemap declares.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Concrete Example: Bilingual Navigation on ekioo.com
&lt;/h2&gt;

&lt;p&gt;A case that illustrates the SSR model's constraints well:&lt;/p&gt;

&lt;p&gt;Ekioo.com is bilingual (FR/EN). Language detection happens only from the URL, no cookie, no &lt;code&gt;Accept-Language&lt;/code&gt; header, no automatic redirect. A deliberate SEO decision: Google recommends distinct URLs per language, not redirects based on geolocation.&lt;/p&gt;

&lt;p&gt;Each Razor page declares two routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@page&lt;/span&gt; &lt;span class="s"&gt;"/blog/{Slug}"&lt;/span&gt;
&lt;span class="n"&gt;@page&lt;/span&gt; &lt;span class="s"&gt;"/en/blog/{Slug}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a cascading parameter passes the language to all child components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;CascadingParameter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Lang&lt;/span&gt; &lt;span class="n"&gt;Lang&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern works perfectly with Static SSR. There's no navigation state to maintain between pages, each URL is an independent request, served by the right component with the right language.&lt;/p&gt;

&lt;p&gt;With classic Blazor Server (global interactive mode), this type of navigation could create inconsistencies: the language could "stick" from the previous page if the SignalR circuit remained open and the state wasn't properly reset. With pure SSR, this problem doesn't exist structurally.&lt;/p&gt;




&lt;h2&gt;
  
  
  Verdict: Who Should Use .NET 10 + Blazor SSR in 2026
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;It's the right choice if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're building a mostly static site (blog, portfolio, documentation, corporate site) with localized interactivity (contact form, search component, shopping cart).&lt;/li&gt;
&lt;li&gt;You work in C# and want to avoid context-switching between a .NET backend and a React/Vue/Angular frontend.&lt;/li&gt;
&lt;li&gt;SEO is critical, you want crawlers to see exactly what users see, without depending on JavaScript rendering.&lt;/li&gt;
&lt;li&gt;You're targeting solid Core Web Vitals performance without a CDN and without complex infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;It's not the right choice if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're building a highly interactive application (real-time dashboard, collaborative editor, tool with lots of local state), in that case, a React SPA with an API backend will be more natural.&lt;/li&gt;
&lt;li&gt;Your team is primarily JavaScript, the learning cost of Blazor is non-trivial.&lt;/li&gt;
&lt;li&gt;You need a very large number of simultaneous stateful interactive connections, SignalR circuits have a memory cost per connection that static SSR doesn't.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The ekioo.com case&lt;/strong&gt; is representative: static content, bilingual, SEO-critical, solo C# team. Blazor SSR .NET 10 is the natural fit. Productivity is excellent, I built the site in two days with Claude, and the Razor component model is expressive enough to cover all the requirements.&lt;/p&gt;

&lt;p&gt;What convinced me wasn't the novelty, it's the coherence. .NET 10 has made the SSR-first model predictable and opinionated. It's no longer an experimental mode you hack together to look like Next.js. It's a clear architectural stance: static rendering by default, interactivity by exception, performance without compromise.&lt;/p&gt;

&lt;p&gt;It's a choice I wish I could have made three years ago.&lt;/p&gt;

</description>
      <category>blazor</category>
      <category>dotnet</category>
      <category>ssr</category>
      <category>seo</category>
    </item>
    <item>
      <title>KittyClaw D+7: What We Learned Letting Agents Run a Real Kanban</title>
      <dc:creator>Ekioo</dc:creator>
      <pubDate>Tue, 11 Aug 2026 07:04:19 +0000</pubDate>
      <link>https://dev.to/ekioo/kittyclaw-d7-what-we-learned-letting-agents-run-a-real-kanban-3cj7</link>
      <guid>https://dev.to/ekioo/kittyclaw-d7-what-we-learned-letting-agents-run-a-real-kanban-3cj7</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvwoqptlyeey1hrv6cxfy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvwoqptlyeey1hrv6cxfy.webp" alt="KittyClaw D+7 — real metrics" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Monday morning. I open KittyClaw and the board is already moving: three agents running, one ticket that just hit Done, another that just got blocked by the content-writer. I triggered nothing. That's what changed.&lt;/p&gt;

&lt;p&gt;Here's one week in production. Not a demo. Not an architecture article — the &lt;a href="https://dev.to/en/blog/kittyclaw-from-kanban-to-harness"&gt;previous&lt;/a&gt; &lt;a href="https://dev.to/en/blog/kittyclaw-ticket-anatomy"&gt;ones&lt;/a&gt; cover that. What follows is what actually happened, numbers included.&lt;/p&gt;

&lt;p&gt;This piece documents &lt;a href="https://kittyclaw.dev/" rel="noopener noreferrer"&gt;KittyClaw&lt;/a&gt;, the kanban orchestrator at the center of the &lt;a href="https://ekioo.com/en" rel="noopener noreferrer"&gt;Ekioo&lt;/a&gt; agent-fleet R&amp;amp;D. Alongside &lt;a href="https://bloomii.fr/" rel="noopener noreferrer"&gt;Bloomii&lt;/a&gt; (constructive-journalism media) and &lt;a href="https://kalceo.fr/" rel="noopener noreferrer"&gt;Kalceo&lt;/a&gt; (regulatory B2B SaaS for construction contractors), KittyClaw runs the AI agents that drive these projects in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Numbers
&lt;/h2&gt;

&lt;p&gt;Since KittyClaw started running the ekioo.com project, the board holds &lt;strong&gt;20 tickets&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;17 Done&lt;/strong&gt; (85%)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2 Todo&lt;/strong&gt; (waiting)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1 InProgress&lt;/strong&gt; (this one)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Breakdown by assignee:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Tickets&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;content-writer&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;45%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lain (direct)&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;programmer&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;owner&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;65% of tickets were handled by agents without direct intervention. I create the ticket, assign it, KittyClaw handles the rest within 30 seconds.&lt;/p&gt;

&lt;p&gt;But that number only tells part of the story. What the table doesn't show: each ticket generates &lt;strong&gt;up to 6 cascading agent runs&lt;/strong&gt;. A typical blog article sequentially triggers: content-writer, qa-tester, fact-checker, committer, evaluator — and sometimes a groomer upstream. 20 tickets = well over 60 agent runs.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Worked Well
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;assignee-dispatch&lt;/code&gt; automation
&lt;/h3&gt;

&lt;p&gt;This is the board's simplest automation, and by far its most valuable: a ticket moves to Todo with an assignee → 30 seconds later, the agent is running. No clicking, no terminal to open, no command to type. The friction between "I have an idea" and "an agent is working on it" dropped to zero.&lt;/p&gt;

&lt;p&gt;Over the course of a week, that's the clearest habit shift: I note a task, assign it, and forget it. By evening, it's often Done.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;qa-on-review&lt;/code&gt; pipeline
&lt;/h3&gt;

&lt;p&gt;Every ticket that reaches Review automatically triggers the qa-tester. For content tickets, the fact-checker follows immediately. In practice, this caught several issues before they hit production: broken links, missing images, pages that didn't render correctly.&lt;/p&gt;

&lt;p&gt;The automatic "someone always checks before merging" behavior is exactly what I was after. It costs a few minutes of compute, but it replaces a manual review habit I would have often skipped.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;owner-feedback&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;When I comment on a blocked ticket, the agent resumes automatically. No manual restart needed. This is an automation I wouldn't have anticipated before needing it — and since it's been running, it's saved me several back-and-forth cycles.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Surprised Us (Not Always in a Good Way)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The content-writer blocks too often
&lt;/h3&gt;

&lt;p&gt;Block rate on content tickets: &lt;strong&gt;~33%&lt;/strong&gt;. One article in three generates a Q&amp;amp;A phase before writing begins. In some cases that's legitimate — the ticket was underspecified. In others, the agent asks for clarification on things it could have inferred from context.&lt;/p&gt;

&lt;p&gt;This isn't a bug, it's a calibration issue. The content-writer SKILL may be too conservative on this point. Something to revisit.&lt;/p&gt;

&lt;h3&gt;
  
  
  The evaluator always plateaus at 0.5
&lt;/h3&gt;

&lt;p&gt;The evaluator scores every completed ticket. Across the 13 evaluated tickets, &lt;code&gt;deliveryQuality&lt;/code&gt; is &lt;strong&gt;0.5&lt;/strong&gt; in 11 out of 13 cases. The two exceptions (0.0) correspond to lain tickets with no comments, likely outside the rubric's scope. For every agent-handled ticket, the plateau is absolute: nobody clears the bar. Either the rubric is miscalibrated, or all agents share a common issue I haven't identified yet.&lt;/p&gt;

&lt;p&gt;The symptom is clear; the diagnosis is not. That's a next-week topic.&lt;/p&gt;

&lt;h3&gt;
  
  
  The groomer stopped after 2 tickets
&lt;/h3&gt;

&lt;p&gt;The groomer is supposed to enrich tickets in Backlog before they get assigned. It ran on tickets #22 and #23, then nothing. Reason: subsequent tickets were created directly in Todo, skipping the Backlog column. The automation only fires when a ticket passes through Backlog with the assignee set to "groomer."&lt;/p&gt;

&lt;p&gt;Result: 80% of tickets reach the dispatcher without enrichment. Some were well-specified anyway, but it's a pipeline gap I hadn't anticipated.&lt;/p&gt;

&lt;h3&gt;
  
  
  The noise from the lain sweep
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;lain&lt;/code&gt; agent runs every hour to review the board — that's the &lt;code&gt;ceo-board-review&lt;/code&gt; automation. Over a week, that's dozens of runs, most of which observe nothing actionable. The debug log is cluttered with lain sessions pointing to ticket &lt;code&gt;#&lt;/code&gt; (empty). &lt;/p&gt;

&lt;p&gt;It's useful for catching stuck situations. But a one-hour interval generates a lot of noise for very little signal when the board is healthy.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We'd Do Differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Force the Backlog passage.&lt;/strong&gt; Not to slow down the flow, but to give the groomer time to work. An automation that detects Todo tickets that skipped Backlog and either requeues them or flags them would prevent underspecified tickets from reaching dispatch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Calibrate the evaluator.&lt;/strong&gt; A 0.5 floor for everyone provides no useful signal. The rubric needs refinement, or the evaluator needs more context to discriminate between different levels of quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Increase the lain sweep interval.&lt;/strong&gt; Moving to 4h or even 8h would cut the noise without losing the ability to detect stuck situations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update the content-writer SKILL&lt;/strong&gt; to reduce the block rate on well-specified tickets. The heuristic "if the ticket has full structure, skip clarifying questions" is in memory, but not yet in the SKILL file. It should be.&lt;/p&gt;




&lt;h2&gt;
  
  
  One Week Is Short
&lt;/h2&gt;

&lt;p&gt;What stands out most after seven days: the tool works, but the calibrations are far from stable. That's the exact opposite of adopting a SaaS product — where you inherit calibrations from a product team. Here, you discover them yourself, through real friction.&lt;/p&gt;

&lt;p&gt;The advantage: every friction point is directly actionable. The groomer isn't running enough? Change a rule in &lt;code&gt;automations.json&lt;/code&gt;. The content-writer blocks too often? Adjust the SKILL. No product forum tickets. No "under consideration." Just code you control.&lt;/p&gt;

&lt;p&gt;That's why I built this tool instead of using an existing one. Not for the features — for the feedback loop.&lt;/p&gt;

</description>
      <category>kittyclaw</category>
      <category>tooling</category>
      <category>ai</category>
      <category>retex</category>
    </item>
    <item>
      <title>Why I'm Building Bloomii: Technology in Service of People and Nature</title>
      <dc:creator>Ekioo</dc:creator>
      <pubDate>Sun, 09 Aug 2026 07:02:35 +0000</pubDate>
      <link>https://dev.to/ekioo/why-im-building-bloomii-technology-in-service-of-people-and-nature-133g</link>
      <guid>https://dev.to/ekioo/why-im-building-bloomii-technology-in-service-of-people-and-nature-133g</guid>
      <description>&lt;p&gt;Imagine an ordinary dinner. One person talks about melting ice caps as if the world will end tonight. Another is convinced AI will erase her job within two years. A third has simply stopped following the news: "it's all going to hell anyway." Three people, three different apocalypses, same result: no one knows what to do with any of it. This fictional scene condenses the reactions that shaped my thinking.&lt;/p&gt;

&lt;p&gt;I went home and looked for proof that they were wrong.&lt;/p&gt;

&lt;p&gt;Not to deny the problems. But I had an intuition: somewhere, people had found answers. Not utopias. Things that work, documented, verifiable.&lt;/p&gt;

&lt;p&gt;I found enough material to convince myself the intuition was right. To me, the problem wasn't the absence of solutions, but their lack of visibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anxiety as a Byproduct of Information
&lt;/h2&gt;

&lt;p&gt;A large randomized experiment on headlines published by Upworthy found that negative words increased click-through rates. Another study, cross-sectional and conducted with an Italian sample, observed that climate anxiety could be associated with more pro-environmental behavior while also being indirectly associated with eco-paralysis when self-efficacy decreased.&lt;/p&gt;

&lt;p&gt;These findings do not explain our entire relationship with information, nor do they prove that a media narrative causes paralysis. They nevertheless reinforce my conviction that a gap can remain between perceiving a problem and knowing how to act.&lt;/p&gt;

&lt;p&gt;Bloomii is an attempt to fill that gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Choice to Document
&lt;/h2&gt;

&lt;p&gt;The idea isn't to manufacture optimism. Not to find "good news" to offset the bad. It's more precise than that: finding proof of what works in domains undergoing deep change, such as ecology, technology, ways of living, health, and economics.&lt;/p&gt;

&lt;p&gt;Regenerative farming practices. Energy cooperatives. Municipal mobility policies. These are subjects whose results, sources, and limits I want to examine without assuming their effectiveness in advance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn5ua0cgdfxgnodbkhqeg.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn5ua0cgdfxgnodbkhqeg.webp" alt="Bloomii articles page, with documented solutions organized by theme" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My editorial charter sets demanding criteria: do not present an isolated case as a universal solution, trace claims back to studies and official reports whenever possible, and seek out limitations as carefully as results.&lt;/p&gt;

&lt;p&gt;I apply this filter the way I validate production code: if it's not verifiable, it doesn't ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rigor as Conviction
&lt;/h2&gt;

&lt;p&gt;As a developer, I've spent years building systems where approximations are expensive. In my experience, an assertion without proof in a test, a poorly documented dependency, or an untreated edge case always comes back. Rigor isn't a constraint in this profession. It's what distinguishes something that holds from something that collapses.&lt;/p&gt;

&lt;p&gt;I apply the same logic to solutions journalism. Just because we want positive outcomes doesn't mean we can afford to be imprecise. On the contrary: if the goal is to document convincing proof, it needs to withstand critical examination. An article that oversells a result or omits its constraints does more harm than an honest, nuanced one: it feeds skepticism rather than dissolving it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmfdh4pqab6g9vlkdj97z.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmfdh4pqab6g9vlkdj97z.webp" alt="Bloomii's About page, with the editorial charter and the publication's commitments" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This methodological transfer between development and writing is central to the project. No shortcuts, no wishful thinking: building something that holds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Now
&lt;/h2&gt;

&lt;p&gt;I could have waited for a journalist to do it. In my search, I did not find a French-language publication that combined exactly what I wanted: documented solutions, a nonpartisan approach, and a strong standard for sourcing.&lt;/p&gt;

&lt;p&gt;That observation raised an uncomfortable question for me: since I had the technical skills to build the information tool I was looking for, why wait for someone else to do it?&lt;/p&gt;

&lt;p&gt;This isn't a heroic posture. It's a practical observation. We build the tools we need. Sometimes, the missing tool is a media outlet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Technology Makes Possible
&lt;/h2&gt;

&lt;p&gt;Bloomii isn't a journalism project made possible &lt;em&gt;despite&lt;/em&gt; technology: it's made possible &lt;em&gt;by&lt;/em&gt; it. In my own practice, technology helps me manage a demanding editorial pipeline alone, publish consistently, and evolve the content structure.&lt;/p&gt;

&lt;p&gt;Technology doesn't solve human problems on its own. It doesn't replace editorial judgment, field investigation, or primary sources. In my work on Bloomii, however, it reduces some repetitive tasks and helps me maintain this editorial pipeline on my own. I see it as support for my method, not as a substitute for a newsroom.&lt;/p&gt;

&lt;p&gt;That's technology in service of people: not the pretension of automating what matters most, but the amplification of the capabilities of those who choose to use it for something useful.&lt;/p&gt;




&lt;p&gt;Bloomii is a work in progress. The bet: documenting that it works somewhere is more useful than repeating that everything is going wrong everywhere.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;For Bloomii, I use &lt;a href="https://kittyclaw.dev" rel="noopener noreferrer"&gt;KittyClaw&lt;/a&gt;, a self-hosted kanban that orchestrates AI agents, to assist with selected steps in my editorial workflow. I remain responsible for editorial decisions and publication.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>bloomii</category>
      <category>ai</category>
      <category>philosophy</category>
      <category>behindthescenes</category>
    </item>
    <item>
      <title>VizMail: Building a Complete Email API with AI Agents</title>
      <dc:creator>Ekioo</dc:creator>
      <pubDate>Fri, 07 Aug 2026 07:02:57 +0000</pubDate>
      <link>https://dev.to/ekioo/vizmail-building-a-complete-email-api-with-ai-agents-3b3p</link>
      <guid>https://dev.to/ekioo/vizmail-building-a-complete-email-api-with-ai-agents-3b3p</guid>
      <description>&lt;p&gt;This piece documents VizMail, a local email client developed within the &lt;a href="https://ekioo.com/en" rel="noopener noreferrer"&gt;Ekioo&lt;/a&gt; agent-fleet R&amp;amp;D. Alongside &lt;a href="https://bloomii.fr/" rel="noopener noreferrer"&gt;Bloomii&lt;/a&gt; (constructive-journalism media) and &lt;a href="https://kalceo.fr/" rel="noopener noreferrer"&gt;Kalceo&lt;/a&gt; (regulatory B2B SaaS for construction contractors), VizMail runs on &lt;a href="https://kittyclaw.dev/" rel="noopener noreferrer"&gt;KittyClaw&lt;/a&gt;, the kanban orchestrator for AI agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  The starting problem
&lt;/h2&gt;

&lt;p&gt;VizMail is a MAUI .NET desktop app built for one specific purpose: classifying emails semantically so you don't have to sort them manually. The original idea has nothing to do with exposing an API, it's about eliminating inbox chores.&lt;/p&gt;

&lt;p&gt;The HTTP API came later, as a natural extension: if an AI agent can read and act on emails directly, why keep a human in the loop for repetitive actions? The data, inbox, threads, labels, attachments, lives inside a local .NET process. For an agent to access it, an API surface is required.&lt;/p&gt;

&lt;p&gt;The question wasn't &lt;em&gt;whether&lt;/em&gt; to build this API, but &lt;em&gt;how&lt;/em&gt; to build it without spending three weeks on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The method: one ticket, one feature
&lt;/h2&gt;

&lt;p&gt;Every feature was broken down into an atomic ticket in &lt;a href="https://dev.to/projects/kittyclaw"&gt;KittyClaw&lt;/a&gt;. One ticket = one feature = one endpoint or one fix. No catch-all tickets, no "global refactoring."&lt;/p&gt;

&lt;p&gt;The workflow for each ticket:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Programmer&lt;/strong&gt; picks up the ticket, writes the C# code in &lt;code&gt;LocalHttpServer&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;QA-tester&lt;/strong&gt; runs the existing tests, writes new ones, checks for regressions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Committer&lt;/strong&gt; commits and opens the PR&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluator&lt;/strong&gt; scores the deliverable quality&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;KittyClaw orchestrates this chain automatically. When the programmer finishes, the qa-tester is triggered. When tests pass, the committer takes over. Zero manual coordination.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://ekioo.com/en/blog/vizmail-api-retex" rel="noopener noreferrer"&gt;View the workflow diagram in the original Ekioo article&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;p&gt;Thirty-four tickets delivered. Six hundred and forty-two tests. Zero regressions detected after merge.&lt;/p&gt;

&lt;p&gt;What stands out is the steady progression. From the initial scaffolding (ticket #1) to batch operations (ticket #34, &lt;code&gt;mark_read&lt;/code&gt;/&lt;code&gt;mark_unread&lt;/code&gt; in bulk), each iteration laid a brick without breaking the previous ones. The qa-tester replayed the full test suite on every PR, that's not a principle, it's what the history shows.&lt;/p&gt;

&lt;p&gt;Average time from ticket open to PR? Under an hour for standard features. Bug fixes (tickets #7, #25) sometimes took longer, but because of diagnosis, not implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The patterns that emerged
&lt;/h2&gt;

&lt;p&gt;A few conventions stabilized over the course of the tickets, not by upfront design, but by necessity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pagination with &lt;code&gt;before=&lt;/code&gt;&lt;/strong&gt;. Unbounded email listings can blow an agent's context. The &lt;code&gt;before={timestamp}&lt;/code&gt; parameter allows paginating backward in time with no server-side state. Simple and predictable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Batch max 500&lt;/strong&gt;. Batch endpoints (&lt;code&gt;/batch/mark_processed&lt;/code&gt;, &lt;code&gt;/batch/mark_read&lt;/code&gt;) accept at most 500 IDs per call. An arbitrary limit at first, it became convention, large enough to be useful, small enough to stay traceable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MailState vs SQLite&lt;/strong&gt;. &lt;code&gt;MailState&lt;/code&gt; is the in-memory view of emails synced from IMAP, fast, volatile. SQLite holds local metadata (labels, processed, starred). An endpoint modifying a label writes to both. An endpoint reading a list queries &lt;code&gt;MailState&lt;/code&gt;. This separation held from ticket #4 to ticket #34.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Label idempotency&lt;/strong&gt;. Adding an already-present label returns no error. Removing an absent label doesn't either. Agents call without tracking current state, the API absorbs duplicates.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this changes
&lt;/h2&gt;

&lt;p&gt;The programmer didn't "code" VizMail. It executed precise specs in a controlled environment. The real intellectual work was in the ticket descriptions, the angle, the edge cases, the expected behavior. Once the spec was clear, the implementation was mechanical.&lt;/p&gt;

&lt;p&gt;This isn't a criticism, it's an observation. The intellectual work shifted toward ticket design. Automated QA caught regressions that fast implementation would have otherwise missed. And the committer kept the git history clean without any human involvement.&lt;/p&gt;

&lt;p&gt;Thirty-four features in a matter of days, with solid test coverage. The method works when the specs are honest.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;VizMail is a desktop email management app built by Ekioo. &lt;a href="https://dev.to/about"&gt;Discover the Ekioo method →&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>vizmail</category>
      <category>api</category>
      <category>method</category>
    </item>
    <item>
      <title>The SaaS Paradox in 2026: Never Easier, Never Harder</title>
      <dc:creator>Ekioo</dc:creator>
      <pubDate>Wed, 05 Aug 2026 07:02:24 +0000</pubDate>
      <link>https://dev.to/ekioo/the-saas-paradox-in-2026-never-easier-never-harder-2gp</link>
      <guid>https://dev.to/ekioo/the-saas-paradox-in-2026-never-easier-never-harder-2gp</guid>
      <description>&lt;p&gt;Here is my thesis: as AI tools reduce the effort required to produce software, the difficulty shifts from building to choosing the right problem and reaching a market. This is a reading drawn from my own practice, not a universal law of the industry.&lt;/p&gt;

&lt;p&gt;I wrote this piece as part of my work at &lt;a href="https://ekioo.com/en" rel="noopener noreferrer"&gt;Ekioo&lt;/a&gt;. It is a personal reflection shaped by the products I build and the choices I face while building them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Commoditization of Software Infrastructure
&lt;/h2&gt;

&lt;p&gt;In my work, I use AI to accelerate tasks such as authentication, payments, database migrations, API documentation, and CI/CD pipelines. The gain varies with the context, technical debt, and the level of oversight required.&lt;/p&gt;

&lt;p&gt;That acceleration makes it possible to explore an idea faster. It guarantees neither product stability, security, nor adoption. Those qualities still require judgment, testing, and time.&lt;/p&gt;

&lt;p&gt;My intuition is that this technical ease intensifies competition: when more people can test an idea, the mere existence of a feature becomes a less durable differentiator.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Doesn't Commoditize
&lt;/h2&gt;

&lt;p&gt;What seems to resist commoditization is a deep understanding of a specific problem, a trusted relationship with users, and the ability to make coherent product decisions. These are assets built over time.&lt;/p&gt;

&lt;p&gt;I draw one working rule from this: distribution and trust deserve as much attention as technology. Copying a feature does not automatically reproduce an audience, a reputation, or a customer relationship.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Token Vendor Observation
&lt;/h2&gt;

&lt;p&gt;Another question guides my analysis: in an ecosystem built on usage-priced models, how much value goes to infrastructure providers, and how much remains with the products using them? The answer depends on costs, pricing, and each product's ability to create value of its own.&lt;/p&gt;

&lt;p&gt;I therefore prefer to treat the "token vendor" idea as an economic hypothesis to test case by case, not as an automatic victory for providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Practical Implications
&lt;/h2&gt;

&lt;p&gt;Before building, I ask two questions: "Does anyone want to pay for this?" and "Why choose this product over another solution?"&lt;/p&gt;

&lt;p&gt;In my practice, clarifying these answers early avoids confusing building speed with market validation. AI can accelerate a technical experiment; by itself, it does not provide evidence of demand.&lt;/p&gt;

&lt;p&gt;My conclusion is deliberately cautious: technical speed remains useful, but it is not enough. I look for a more durable advantage in understanding the problem, building strong user relationships, and learning faster than a simple cycle of feature copying.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>philosophie</category>
      <category>coulisses</category>
    </item>
    <item>
      <title>KittyClaw dashboard: how I produce and leverage my KPIs</title>
      <dc:creator>Ekioo</dc:creator>
      <pubDate>Tue, 04 Aug 2026 00:02:05 +0000</pubDate>
      <link>https://dev.to/ekioo/kittyclaw-dashboard-how-i-produce-and-leverage-my-kpis-21bi</link>
      <guid>https://dev.to/ekioo/kittyclaw-dashboard-how-i-produce-and-leverage-my-kpis-21bi</guid>
      <description>&lt;p&gt;A dashboard has two hidden costs: creating it and maintaining it. Most tools solve the first (an assistant generates the SQL queries), then abandon the second (data goes stale, no one refreshes it). KittyClaw takes a different approach, and it fits in three sentences:&lt;/p&gt;

&lt;p&gt;The LLM writes the tile &lt;strong&gt;once&lt;/strong&gt;, via chat. The refresh runs &lt;strong&gt;periodically with zero LLM tokens&lt;/strong&gt;, via a shell or PowerShell script. And the generated &lt;code&gt;output.md&lt;/code&gt; serves &lt;strong&gt;both the human and the agents&lt;/strong&gt; watching the project.&lt;/p&gt;

&lt;p&gt;This triple economy (minimal upfront cost, zero-cost operation, closed loop) is what I want to describe here.&lt;/p&gt;

&lt;p&gt;This piece documents &lt;a href="https://kittyclaw.dev/" rel="noopener noreferrer"&gt;KittyClaw&lt;/a&gt;, the kanban orchestrator at the center of the &lt;a href="https://ekioo.com/en" rel="noopener noreferrer"&gt;Ekioo&lt;/a&gt; agent-fleet R&amp;amp;D. Alongside &lt;a href="https://bloomii.fr/" rel="noopener noreferrer"&gt;Bloomii&lt;/a&gt; (constructive-journalism media) and &lt;a href="https://kalceo.fr/" rel="noopener noreferrer"&gt;Kalceo&lt;/a&gt; (regulatory B2B SaaS for construction contractors), KittyClaw runs the AI agents that drive these projects in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  The &lt;code&gt;.dashboard/&amp;lt;slug&amp;gt;/&lt;/code&gt; primitive
&lt;/h2&gt;

&lt;p&gt;Each tile is a folder in the repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.dashboard/
  monthly-revenue/
    tile.yaml        ← template, refresh, prompt, title
    script.ps1       ← collects data (optional)
    output.json      ← data rendered by the dashboard
  health/
    tile.yaml
    script.ps1       ← collects project metrics
    data.json        ← produced by script.ps1
    output.md        ← generated by LLM from data.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;tile.yaml&lt;/code&gt; declares three things:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kpi&lt;/span&gt;          &lt;span class="c1"&gt;# one of: kpi, kpi-grid, donut, bar-chart,&lt;/span&gt;
                       &lt;span class="c1"&gt;#         sparkline, progress, heatmap,&lt;/span&gt;
                       &lt;span class="c1"&gt;#         status-grid, gauge, leaderboard,&lt;/span&gt;
                       &lt;span class="c1"&gt;#         table, timeline, markdown, mermaid, image&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Monthly&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;revenue"&lt;/span&gt;
&lt;span class="na"&gt;refresh&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;86400&lt;/span&gt;         &lt;span class="c1"&gt;# in seconds. 0 = never auto-refresh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a refresh fires:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;KittyClaw runs &lt;code&gt;script.*&lt;/code&gt; if present → produces &lt;code&gt;data.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If the tile has a &lt;code&gt;prompt&lt;/code&gt;, KittyClaw calls the LLM with &lt;code&gt;data.json&lt;/code&gt; as context → writes &lt;code&gt;output.json&lt;/code&gt; or &lt;code&gt;output.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If the tile has no prompt, &lt;code&gt;output.*&lt;/code&gt; is read directly as-is&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The second step is the only one that consumes tokens, and only on &lt;code&gt;markdown&lt;/code&gt; tiles that synthesize raw data. &lt;code&gt;kpi&lt;/code&gt;, &lt;code&gt;bar-chart&lt;/code&gt;, &lt;code&gt;sparkline&lt;/code&gt;, and similar tiles render &lt;code&gt;output.json&lt;/code&gt; server-side: zero LLM at runtime.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tile gallery: the BrewCo project
&lt;/h2&gt;

&lt;p&gt;To illustrate the range of templates without screenshotting a real production project (real numbers stay private), I created &lt;strong&gt;BrewCo&lt;/strong&gt;: a fictional chain of 12 coffee shops. Eleven tiles, seven different templates, all fed by realistic JSON.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5lgw3yny4d950o9kztg3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5lgw3yny4d950o9kztg3.webp" alt="BrewCo dashboard: 11 tiles, 7 different templates" width="799" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First row: quick scan.&lt;/strong&gt; Four &lt;code&gt;kpi&lt;/code&gt; tiles: monthly revenue, orders/day, average ticket, NPS. Each is an &lt;code&gt;output.json&lt;/code&gt; with five fields (&lt;code&gt;value&lt;/code&gt;, &lt;code&gt;label&lt;/code&gt;, &lt;code&gt;unit&lt;/code&gt;, &lt;code&gt;delta&lt;/code&gt;, &lt;code&gt;trend&lt;/code&gt;). Delta and trend arrow are rendered automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second row: analytical reading.&lt;/strong&gt; A &lt;code&gt;donut&lt;/code&gt; (channel breakdown: 58% dine-in, 31% takeaway, 11% delivery), a &lt;code&gt;bar-chart&lt;/code&gt; (sales by day of the week, Saturday at 612 stands out immediately), a &lt;code&gt;sparkline&lt;/code&gt; (revenue trend over 12 weeks), and a GitHub Calendar-style &lt;code&gt;heatmap&lt;/code&gt; showing the last 90 days of activity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third row: state and context.&lt;/strong&gt; A &lt;code&gt;status-grid&lt;/code&gt; (6 systems with ok/warn/err status, one store's Wi-Fi is in warn), &lt;code&gt;progress&lt;/code&gt; bars for Q2 objectives, and a &lt;code&gt;markdown&lt;/code&gt; tile for the weekly management note.&lt;/p&gt;

&lt;p&gt;Each template has its own JSON schema, documented in the interface: the tile creation AI chat automatically injects the correct schema into its prompt.&lt;/p&gt;




&lt;h2&gt;
  
  
  The closed loop: Lain reads &lt;code&gt;output.md&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;What makes this primitive particularly powerful is that the &lt;code&gt;output.md&lt;/code&gt; files from analytical tiles naturally become inputs for other agents.&lt;/p&gt;

&lt;p&gt;On the ekioo project, the &lt;code&gt;health&lt;/code&gt; tile produces a daily &lt;code&gt;output.md&lt;/code&gt; that cross-references ticket metrics (velocity, blocks, stale tickets), Umami traffic data, and Search Console SEO signals. This file is read by &lt;strong&gt;Lain&lt;/strong&gt;, the CEO agent that reviews the board every hour:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .dashboard/health/tile.yaml&lt;/span&gt;
&lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;markdown&lt;/span&gt;
&lt;span class="na"&gt;refresh&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;86400&lt;/span&gt;
&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Read&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;JSON&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;at&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;.dashboard/health/data.json,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;which&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;contains&lt;/span&gt;
  &lt;span class="s"&gt;ticket&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;metrics,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;roadmap&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;goals,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;optionally&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;umami&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(traffic)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;
  &lt;span class="s"&gt;gsc&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(SEO)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;data.&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Cross-reference&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;all&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;signals.&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Format:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Diagnostic&lt;/span&gt;
  &lt;span class="s"&gt;(3&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sentences),&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Risks&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(2&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bullets),&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Action&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;week&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(1&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bullet).&lt;/span&gt;
  &lt;span class="s"&gt;Be&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;opinionated.&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Output&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ONLY&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;raw&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;markdown.'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lain doesn't poll the KittyClaw API 30 times to reconstruct a global state. It reads &lt;code&gt;health/output.md&lt;/code&gt;, a document already synthesized, up to date, in natural language, and decides from there whether a ticket needs escalating or an SEO opportunity deserves immediate action. A single LLM call consumes an already-processed context.&lt;/p&gt;

&lt;p&gt;It's the same logic as caching in a backend architecture: compute once, serve N times.&lt;/p&gt;




&lt;h2&gt;
  
  
  The dashboard isn't just for tech projects
&lt;/h2&gt;

&lt;p&gt;BrewCo isn't a dev project. It's a coffee chain. And the tiles work exactly the same way: a PowerShell script reads a CSV export from the POS system, produces &lt;code&gt;data.json&lt;/code&gt;, and the dashboard shows the weekend's KPIs.&lt;/p&gt;

&lt;p&gt;The primitive (&lt;code&gt;tile.yaml&lt;/code&gt; + &lt;code&gt;script.*&lt;/code&gt; + &lt;code&gt;output.*&lt;/code&gt;) is generic enough to monitor business metrics, marketing campaigns, or physical operations. The only constraint is that the data be readable by a script. If it is, the dashboard can display it.&lt;/p&gt;

&lt;p&gt;This is deliberate. KittyClaw is a project management and agent orchestration tool, not a BI SaaS. The dashboard is an observability layer, not an end in itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  Observability between human and agents
&lt;/h2&gt;

&lt;p&gt;What interests me about this architecture is that it solves a coordination problem that agentic systems create: humans and agents need to see the same reality, but at different granularities.&lt;/p&gt;

&lt;p&gt;The human wants a high-level view: a few numbers, a trend, an alert. The agent wants a structured input it can reason over. The &lt;code&gt;health&lt;/code&gt; tile's &lt;code&gt;output.md&lt;/code&gt; is both at once: readable by a human in 30 seconds, parseable by an agent in a single call.&lt;/p&gt;

&lt;p&gt;This isn't an infrastructure revolution. It's a text file regenerated every night. But it's exactly the right abstraction for the layer between the human who steers and the agents who execute.&lt;/p&gt;

</description>
      <category>kittyclaw</category>
      <category>ai</category>
      <category>tooling</category>
      <category>coulisses</category>
    </item>
    <item>
      <title>Cadence Over Volume — Orchestrating Multiple Projects with AI Agents</title>
      <dc:creator>Ekioo</dc:creator>
      <pubDate>Mon, 03 Aug 2026 00:02:02 +0000</pubDate>
      <link>https://dev.to/ekioo/cadence-over-volume-orchestrating-multiple-projects-with-ai-agents-6op</link>
      <guid>https://dev.to/ekioo/cadence-over-volume-orchestrating-multiple-projects-with-ai-agents-6op</guid>
      <description>&lt;p&gt;Running multiple projects in parallel with AI agents produces a counter-intuitive effect: without a compounding infrastructure, every run starts from zero. Memory stays local to the project, discovered patterns don't migrate, and cadence depends entirely on human availability. What follows describes the infrastructure that solves this problem — and why regularity is its primary condition.&lt;/p&gt;

&lt;p&gt;Three live projects feed this piece: &lt;a href="https://bloomii.fr/" rel="noopener noreferrer"&gt;Bloomii&lt;/a&gt;, a constructive-journalism media outlet covering social and environmental alternatives; &lt;a href="https://kalceo.fr/" rel="noopener noreferrer"&gt;Kalceo&lt;/a&gt;, a regulatory B2B SaaS for French construction contractors; and &lt;a href="https://ekioo.com/en" rel="noopener noreferrer"&gt;Ekioo&lt;/a&gt;, the agent-fleet R&amp;amp;D project behind this article. All three run on &lt;a href="https://kittyclaw.dev/" rel="noopener noreferrer"&gt;KittyClaw&lt;/a&gt;, a kanban orchestrator for AI agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anatomy of an Orchestrated Project
&lt;/h2&gt;

&lt;p&gt;Each project hosts a &lt;code&gt;.agents/&lt;/code&gt; directory with four elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.agents/
├── preamble.md          # context injected into every agent run
├── automations.json     # trigger pipelines
├── {agent}/
│   ├── SKILL.md         # stable domain instructions
│   └── memory.md        # accumulated learnings, run after run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;preamble.md&lt;/code&gt;&lt;/strong&gt; is the coherence vector. It contains shared rules — git workflow, commit conventions, API access — injected into every agent's context at startup. What lives in &lt;code&gt;preamble.md&lt;/code&gt; doesn't need to be repeated in every &lt;code&gt;SKILL.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;automations.json&lt;/code&gt;&lt;/strong&gt; defines the pipelines. The core automation, &lt;code&gt;assignee-dispatch&lt;/code&gt;, runs three actions as soon as a ticket moves to Todo with an assignee:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assignee-dispatch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"trigger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ticketInColumn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"columns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Todo"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"conditions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assignedTo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"slugs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"programmer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"content-writer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"qa-tester"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ticketCountInColumn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"columns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"InProgress"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sameAssignee"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"=="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"moveTicketStatus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"to"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"InProgress"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"runAgent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{assignee}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"claude-sonnet-4-6"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"commitAgentMemory"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{assignee}"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ticketCountInColumn == 0&lt;/code&gt; condition prevents uncontrolled parallelism: an agent won't start a new ticket while it has one in progress. The final action, &lt;code&gt;commitAgentMemory&lt;/code&gt;, automatically persists the agent's memory after every run — this is the key to compounding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory as a Progression Tool
&lt;/h2&gt;

&lt;p&gt;Where most AI agent setups remain stateless, &lt;code&gt;memory.md&lt;/code&gt; introduces structured persistent state. Each agent maintains a file of lessons with a &lt;code&gt;[+N]&lt;/code&gt; counter that tracks how many times each lesson has been reapplied:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## API / tooling [+5]&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; curl on Windows mangles UTF-8 in JSON bodies.
  Use python3 urllib.request instead. [+2]
&lt;span class="p"&gt;-&lt;/span&gt; PR creation: gh not available;
  az repos pr create has permission errors. [+2]

&lt;span class="gu"&gt;## SVG inline in Markdown [+3]&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; NEVER use &lt;span class="nt"&gt;&amp;lt;marker&amp;gt;&lt;/span&gt; + marker-end="url(#id)" in inline SVGs. [+3]
&lt;span class="p"&gt;-&lt;/span&gt; Draw arrowheads as explicit &lt;span class="nt"&gt;&amp;lt;polygon&amp;gt;&lt;/span&gt; elements.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The counter serves two functions: it shows which lessons have value (they recur), and it guides consolidation — when an entry reaches &lt;code&gt;[5+]&lt;/code&gt;, it migrates to &lt;code&gt;SKILL.md&lt;/code&gt; as a stable rule. Entries at &lt;code&gt;[0]&lt;/code&gt; are removed.&lt;/p&gt;

&lt;p&gt;This distinction between &lt;code&gt;SKILL.md&lt;/code&gt; (stable rules, rarely modified) and &lt;code&gt;memory.md&lt;/code&gt; (active learnings, updated each run) is structural. It prevents memory from becoming a catch-all and enforces discipline about what is worth retaining.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-Project Compounding
&lt;/h2&gt;

&lt;p&gt;Agents learn from their mistakes — once an agent hits a problem, &lt;code&gt;memory.md&lt;/code&gt; ensures it never hits the same one again. But the real step change happens when projects start sharing entire capabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shared image generation
&lt;/h3&gt;

&lt;p&gt;A content-writer on Bloomii needs an illustration. A content-writer on Ekioo needs a cover. Neither knows how gpt-image-2 works, neither manages an API key, neither worries about rate limiting.&lt;/p&gt;

&lt;p&gt;A single processor runs on a 10-minute cron in the central Workspace. Agents from any project submit a request via &lt;code&gt;image-enqueue.mjs&lt;/code&gt;, get an ID back, and move on. The processor manages the queue, pauses when the quota is hit, resumes automatically. The agent gets notified when the image is ready.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node C:/IA/Workspace/.agents/tools/image-enqueue.mjs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--prompt&lt;/span&gt; &lt;span class="s2"&gt;"..."&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; path/to/cover.webp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--project&lt;/span&gt; bloomii &lt;span class="nt"&gt;--ticket&lt;/span&gt; 189
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One tool, one processor, N consumer projects. Adding a sixth project requires zero configuration — just a call to &lt;code&gt;image-enqueue.mjs&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bloomii ──┐
Ekioo ────┼── image request ──&amp;gt; image-queue.json
Kalceo ───┘                     cron: every 10 minutes
                                rate limits handled centrally
                                           |
                                           v
                                     gpt-image-2 ──&amp;gt; .webp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Centralized mailbox — automatic dispatch
&lt;/h3&gt;

&lt;p&gt;All projects share a single mailbox, exposed through VizMail (a local HTTP server with 40+ REST endpoints). When an email arrives — a Kalceo client reply, Bloomii reader feedback, an Ekioo contact form — it gets automatically dispatched to the relevant project as a KittyClaw ticket. The appropriate agent picks it up with no human intervention.&lt;/p&gt;

&lt;p&gt;The result: one mailbox to monitor, zero manual sorting, and every project receives its relevant emails as actionable tasks for agents.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IMAP mailbox
     |
     v
VizMail: classify + dispatch
     |
     +──&amp;gt; Kalceo ticket  (client reply)
     +──&amp;gt; Bloomii ticket (reader feedback)
     └──&amp;gt; Ekioo ticket   (contact form)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Centralized X branding
&lt;/h3&gt;

&lt;p&gt;Four projects feed a single X account (&lt;code&gt;@LainAgent_AI&lt;/code&gt;). Each project has its own publishing tool (&lt;code&gt;x-post-tweet.mjs&lt;/code&gt;), but the editorial strategy lives in a single file — &lt;code&gt;x-strategy.md&lt;/code&gt; — in the central Workspace: content pillars, voice, engagement rules.&lt;/p&gt;

&lt;p&gt;A community-manager drafts, lain approves. No audience fragmentation across four ghost accounts. A single account building authority, fed by four different content sources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ekioo content ──┐
Kalceo content ─┼──&amp;gt; x-strategy.md ──&amp;gt; review and approval ──&amp;gt; @LainAgent_AI
Bloomii content ┘

One strategy, one voice, one account.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Hub-and-spoke knowledge base
&lt;/h3&gt;

&lt;p&gt;The central Workspace provides shared infrastructure: API documentation (KittyClaw, Brevo, Chrome CDP), centralized credentials, CLI tools. Each project maintains its own domain knowledge base — BTP regulation and artisan pain points for Kalceo, editorial research packs and distribution guides for Bloomii.&lt;/p&gt;

&lt;p&gt;A Kalceo agent sending a newsletter and a Bloomii agent scheduling a LinkedIn post use the same Brevo token, the same documentation, the same call patterns — without knowing the other exists. Infrastructure knowledge is written once, consumed everywhere.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Central Workspace
        API docs · credentials · shared tools
              /            |            \
             v             v             v
          Kalceo         Bloomii        Ekioo
       BTP knowledge   editorial data   agent R&amp;amp;D

Shared infrastructure is written once. Domain knowledge stays local.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Cadence as a Health Signal
&lt;/h2&gt;

&lt;p&gt;Thirty to fifty tickets processed per week across all projects. That number isn't a productivity target — it's a system health indicator.&lt;/p&gt;

&lt;p&gt;When a project's Todo column accumulates without draining, the signal is clear: the backlog is poorly prioritized, or the project lacks agents suited to the queued tasks. The weekly cadence makes this imbalance immediately visible.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;evaluator&lt;/code&gt; agent calculates scores after each closed ticket: first-pass success rate, feedback compliance, delivery quality. These metrics measure whether compounding is actually working — a rising first-pass success rate means agents are improving run after run. A stagnating rate signals a memory that's no longer being fed.&lt;/p&gt;

&lt;p&gt;An agent that doesn't run regularly doesn't accumulate usable memory: it resets each time, repeats the same mistakes, asks the same clarifying questions. Regularity isn't a publishing discipline — it's the condition for the system to learn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The infrastructure fits in four files per agent: &lt;code&gt;preamble.md&lt;/code&gt;, &lt;code&gt;automations.json&lt;/code&gt;, &lt;code&gt;SKILL.md&lt;/code&gt;, &lt;code&gt;memory.md&lt;/code&gt;. What makes it effective is feeding discipline: commit memory after every run (the &lt;code&gt;commitAgentMemory&lt;/code&gt; action handles this automatically), remove entries at &lt;code&gt;[0]&lt;/code&gt;, promote lessons at &lt;code&gt;[5+]&lt;/code&gt; to &lt;code&gt;SKILL.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The only rule that matters: never miss two weeks in a row. One quiet week doesn't break the cadence. Two weeks break agent memory, project coherence, and the SEO signal.&lt;/p&gt;

&lt;p&gt;Consistency first. Volume second.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>kittyclaw</category>
      <category>tooling</category>
      <category>behindthescenes</category>
    </item>
    <item>
      <title>Nobody Searches, Everyone Scrolls</title>
      <dc:creator>Ekioo</dc:creator>
      <pubDate>Sun, 02 Aug 2026 00:00:57 +0000</pubDate>
      <link>https://dev.to/ekioo/nobody-searches-everyone-scrolls-3j6h</link>
      <guid>https://dev.to/ekioo/nobody-searches-everyone-scrolls-3j6h</guid>
      <description>&lt;p&gt;Eighteen months ago, the bottleneck for a solo builder was dev. Spinning up an API, wiring a UI, building a data ingestion pipeline — all of that took weeks. Today, with the right agents in place, it takes days. Sometimes hours for the simple stuff.&lt;/p&gt;

&lt;p&gt;Dev is solved. Not perfect, not free — but solved.&lt;/p&gt;

&lt;p&gt;The problem now is that distributing what you've built has become the real wall. And it's a wall that AI agents don't demolish with a single prompt. You have to find eyeballs. Earn their attention. Without paying to buy it.&lt;/p&gt;

&lt;p&gt;This article documents where I stand on two projects — Bloomii and Kalceo — with real numbers, misreadings, and a twist I didn't see coming: why the clean, well-sourced content chains we built for SEO may be the exact asset for the next layer of search.&lt;/p&gt;

&lt;p&gt;This piece comes from &lt;a href="https://ekioo.com/en" rel="noopener noreferrer"&gt;Ekioo&lt;/a&gt;, an R&amp;amp;D project documenting an autonomous AI agent fleet running production work across &lt;a href="https://bloomii.fr/" rel="noopener noreferrer"&gt;Bloomii&lt;/a&gt; (constructive journalism), &lt;a href="https://kalceo.fr/" rel="noopener noreferrer"&gt;Kalceo&lt;/a&gt; (regulatory B2B SaaS), and &lt;a href="https://kittyclaw.dev/" rel="noopener noreferrer"&gt;KittyClaw&lt;/a&gt; (the kanban orchestrator).&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Real Wall
&lt;/h2&gt;

&lt;p&gt;Solo dev in 2026 looks like this: you have the firepower of a small team, without the headcount costs. Agents run the plumbing. You validate, arbitrate, direct.&lt;/p&gt;

&lt;p&gt;Result: you can launch products at a pace that would have seemed absurd two years ago. Bloomii — a media outlet covering the living world and regenerative economics — runs with a fully agent-orchestrated content pipeline. Kalceo — a B2B SaaS for tradespeople in the construction industry — generated 108 pieces of content (70 articles, 13 of them scheduled for publication + 38 reference sheets) in three weeks, by a single person, with automated quality gates that filter errors before they ever surface.&lt;/p&gt;

&lt;p&gt;The problem is that this production capacity doesn't answer the fundamental question: &lt;em&gt;who's going to read it?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Organic marketing — SEO, social, engagement, visibility — demands patience that agents don't really accelerate. Google indexes at its own pace. Social media algorithms reward longevity, not density. And I refuse to pay for ads before I've earned the right to know what people actually want. Ads optimize what you already know how to measure; organic extracts signal about what resonates. For a solo operator in the market-learning phase, organic is the right call — even if it's slower.&lt;/p&gt;

&lt;p&gt;So we build. We publish. And we watch the numbers.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Industrialization
&lt;/h2&gt;

&lt;p&gt;Before sharing the numbers, a few words on the machine that produces them — because the cadence isn't anecdotal, it's the demonstration of what is now possible.&lt;/p&gt;

&lt;p&gt;Bloomii publishes articles and briefs on the living world: biodiversity, regenerative economics, human initiatives that actually hold up. 44 articles and 61 briefs published — 105 pieces of content in total — pipeline fully orchestrated via KittyClaw (the in-house agentic kanban), with specialized agents for research, writing, translation, and illustration.&lt;/p&gt;

&lt;p&gt;Kalceo targets construction tradespeople: sole-trader income ceilings, professional liability insurance, payment deadlines, unpaid invoices. 108 pieces of content generated in three weeks (57 live articles + 13 scheduled + 38 reference sheets). Titles with surgical precision like "Sole Trader Income Cap BTP 2026: Revenue Thresholds, VAT, and What to Do If You Exceed Them" — not for aesthetics, but because that's exactly what a tradesperson types into Google at 10pm from their job site.&lt;/p&gt;

&lt;p&gt;Behind these two projects, three specialized factories:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;image-factory&lt;/strong&gt; — AI image generation via pipeline (Chrome CDP), with a 20/day quota, rate-limit awareness, and an &lt;code&gt;illustrator&lt;/code&gt; agent that handles rejections and visual iterations. We don't publish an unvalidated image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;video-factory&lt;/strong&gt; — cinematic documentary videos (AI stills + Ken Burns motion), ~10 specialized roles, for subjects that deserve richer treatment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;remotion-factory&lt;/strong&gt; — the most technical. "Code / UI / data" videos rendered in Remotion + ElevenLabs TTS (&lt;code&gt;eleven_v3&lt;/code&gt;). A &lt;code&gt;design-reviewer&lt;/code&gt; agent validates keyframes &lt;em&gt;before&lt;/em&gt; the owner ever sees them. Ticket #168: 9 audio iterations + 5 visual rejections before locking down the right TTS settings. This isn't a proof of concept. It's production-grade — with reusable CLI tools, managed quotas, and automatic recovery of blocked tickets.&lt;/p&gt;

&lt;p&gt;The classic trap with an AI pipeline is wanting to validate everything by hand — and turning your cadence into a bottleneck. I go the other way: fact-checkers that block factually wrong drafts, keyframe extraction that flags visual defects before rendering, design-reviewers that reject bad TTS or layouts before I ever see them. The human arbitrates the result, not every step.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Verdict in Numbers
&lt;/h2&gt;

&lt;p&gt;Here are the real numbers, as of June 2, 2026.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kalceo&lt;/strong&gt; — launched ~May 11, 2026, 22 days in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;150 unique visitors over 30 days, 322 page views, 79.4% bounce rate&lt;/li&gt;
&lt;li&gt;SEO over 28 days: 1,151 impressions, 15 clicks, 1.3% CTR, average position 10.8&lt;/li&gt;
&lt;li&gt;108 pieces generated (57 live articles + 13 scheduled + 38 live reference sheets)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Bloomii&lt;/strong&gt; — active for several months, 105 pieces published (44 articles + 61 briefs):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;153 unique visitors over 30 days, 357 page views, 70.7% bounce rate&lt;/li&gt;
&lt;li&gt;SEO over 28 days: 374 impressions, 19 clicks, 5.08% CTR, average position 10.1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The diagnosis is clear: the content is there, the SEO work is done, rankings are being acquired — and traffic plateaus below 200 unique visitors per month on each project.&lt;/p&gt;

&lt;p&gt;This isn't failure. It's how organic SEO works on new domains. Kalceo's Google impressions (1,151 in 28 days) on a site launched 22 days ago are actually an encouraging signal. But it's not the visibility scale a project needs to generate engagement or quickly validate a market hypothesis.&lt;/p&gt;

&lt;p&gt;SEO is a 12–18 month strategy. The problem is we need signal &lt;em&gt;now&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;108 pieces generated in 3 weeks for Kalceo alone. 150 unique visitors in 30 days. Content is not the problem.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  4. The Shorts Pivot
&lt;/h2&gt;

&lt;p&gt;If the site isn't capturing eyeballs, where are they?&lt;/p&gt;

&lt;p&gt;The answer everyone knows but won't admit: they're scrolling. YouTube Shorts, TikTok, Instagram Reels. The collective attention window has fragmented into 60-second vertical formats.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A single short gets 1,540 views in a few days. Bloomii's entire site gets 153 unique visitors in 30 days. The short beats an entire month of site traffic by 10×.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the number I wished I didn't have to write. But it's there in the dashboards: Bloomii launched its YouTube Shorts channel on May 27, 2026. Results through June 1 — 6 days after launch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;7 shorts published&lt;/li&gt;
&lt;li&gt;2,673 total views&lt;/li&gt;
&lt;li&gt;Top short: &lt;em&gt;The otter saving the climate (and she doesn't know it)&lt;/em&gt; — 1,540 views, 22 likes&lt;/li&gt;
&lt;li&gt;12 subscribers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk7x3zxiutio1ouv4mlk3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk7x3zxiutio1ouv4mlk3.webp" alt="A solo creator at night, smartphone on a tripod filming a short — flat analytics visible on the desktop screen behind" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"Nobody searches, everybody scrolls." This isn't a pessimistic observation about digital culture. It's a market constraint to integrate like any other data point.&lt;/p&gt;

&lt;p&gt;The next question is inevitable: how do you produce short-form video worth watching, without sacrificing quality to cadence?&lt;/p&gt;

&lt;p&gt;AI slop looks like this: mass-generated videos where hands have six fingers, logos morph mid-shot, synthetic voices recite text nobody proofread. The algorithms temporarily favor this volume, until engagement signals collapse — and creators who bet on pure quantity end up with penalized channels.&lt;/p&gt;

&lt;p&gt;My defense isn't validating every step by hand — that would kill the cadence. It's stacking automated controls: fact-checkers that block factually wrong drafts, keyframe extraction that flags visual defects before rendering, design-reviewers that reject failed layouts. I review the result, not the process. The difference shows: &lt;em&gt;What Grows Where the Post Office, the Bank, and the School Have Closed&lt;/em&gt; at 919 views in a few days is content that resonates because it's precise and true, not because it was optimized for a clickbait thumbnail.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. The GEO Reversal
&lt;/h2&gt;

&lt;p&gt;Here's where it gets interesting. And counterintuitive.&lt;/p&gt;

&lt;p&gt;We've heard a lot lately that SEO is dead. That LLMs will absorb all queries and stop sending traffic to websites. That's a partially correct reading — and a completely incomplete one.&lt;/p&gt;

&lt;p&gt;Search isn't dead. It has moved.&lt;/p&gt;

&lt;p&gt;Traditional search engines (Google, Bing) now fuel the responses of ChatGPT, Perplexity, Claude, Gemini. These LLMs do web searches under the hood and cite their sources. They don't replace search — they &lt;em&gt;are&lt;/em&gt; the new search, with a synthesis layer on top.&lt;/p&gt;

&lt;p&gt;And there lies the reversal: &lt;strong&gt;what performs well in this new search is exactly the content we built.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The principle is called GEO — &lt;em&gt;Generative Engine Optimization&lt;/em&gt;. It's about optimizing not for SERP rankings, but for &lt;em&gt;citation&lt;/em&gt; by LLMs. And the criteria differ from classical SEO:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Factual precision&lt;/strong&gt;: LLMs favor sourced, quantified, dated content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear structure&lt;/strong&gt;: LLM responses extract passages; titles and subheadings serve as anchors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Topical authority&lt;/strong&gt;: covering a domain in depth (Kalceo covers the entire sole-trader construction ecosystem) rather than publishing isolated articles.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Look at a Kalceo title: "Sole Trader Income Cap BTP 2026: Revenue Thresholds, VAT, and What to Do If You Exceed Them". That title wasn't written to look good — it was written to match exactly the question a tradesperson asks ChatGPT. And when ChatGPT searches for the answer, it cites its sources.&lt;/p&gt;

&lt;p&gt;There's already a signal in the dashboards: among Kalceo's traffic referrers, &lt;code&gt;chatgpt.com&lt;/code&gt; appears. Two visits. Not statistically significant — but it's the first month, and the domain is 22 days old. It's a validation signal, not a projection.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The clean, well-sourced chains we built for classical SEO are the exact asset for the search that's coming.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fchhmnufkns6cpa1s1lus.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fchhmnufkns6cpa1s1lus.webp" alt="A human query, four distinct AI nodes, concrete source documents, a synthesized response — the GEO flow" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The difference from classical SEO? Timelines. SEO works on 12–18 months. With GEO, we don't know yet — LLMs update their web grounding continuously or on short cycles (Perplexity in real-time, ChatGPT and Claude via indexing partnerships). The citation acquisition window may be shorter.&lt;/p&gt;

&lt;p&gt;I won't go deeper here. This subject deserves a dedicated article — concrete tactics, markup structure, schema.org, measurement methodology, examples of LLM-cited content. I'm working on it. What I wanted to establish here is the framework: search isn't dead, the layer above search is changing, and the assets we've built are well-positioned for it.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. While Others Make Slop
&lt;/h2&gt;

&lt;p&gt;The macro context is this: content generation tools are accessible to everyone. Anyone can publish 500 articles in a week. Many do. The result is an inflation of poor content that pushes Google to refine its filters, and drowns quality signals in noise.&lt;/p&gt;

&lt;p&gt;What survives in this environment is content that costs something to produce — not in machine time, but in quality gates, embedded domain expertise, deliberate editorial choices.&lt;/p&gt;

&lt;p&gt;The construction tradespeople reading Kalceo don't need to know how the articles were produced. They need the answer to their question to be accurate, sourced, and current. That's what we deliver. The pipeline behind it is an implementation detail.&lt;/p&gt;

&lt;p&gt;The same goes for Bloomii readers — they don't see the agents behind the articles on otters and citizens' assemblies. They see well-documented content on topics that matter to them.&lt;/p&gt;

&lt;p&gt;Distribution remains a wall. Shorts are a partial short-term answer. SEO is a medium-term answer. And GEO may be the long-term strategic answer — the one we're already positioned for without having planned it.&lt;/p&gt;

&lt;p&gt;This isn't a success story. It's a documented work in progress. But while others make AI slop for an SEO that's degrading, we're building the asset that monetizes on the next layer.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If these questions — distribution, scaling production, GEO — are on your mind as much as they're on mine, &lt;a href="https://dev.to/contact"&gt;write to me&lt;/a&gt;. I like exchanging notes with people who are actually working on them.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>coulisses</category>
      <category>distribution</category>
      <category>geo</category>
    </item>
    <item>
      <title>Kalceo: Building a B2B SaaS with AI Agents</title>
      <dc:creator>Ekioo</dc:creator>
      <pubDate>Sat, 01 Aug 2026 13:59:42 +0000</pubDate>
      <link>https://dev.to/ekioo/kalceo-building-a-b2b-saas-with-ai-agents-30nl</link>
      <guid>https://dev.to/ekioo/kalceo-building-a-b2b-saas-with-ai-agents-30nl</guid>
      <description>&lt;p&gt;This piece documents &lt;a href="https://kalceo.fr/" rel="noopener noreferrer"&gt;Kalceo&lt;/a&gt;, one of the projects in the &lt;a href="https://ekioo.com/en" rel="noopener noreferrer"&gt;Ekioo&lt;/a&gt; agent-fleet R&amp;amp;D. Alongside &lt;a href="https://bloomii.fr/" rel="noopener noreferrer"&gt;Bloomii&lt;/a&gt; (constructive-journalism media) and &lt;a href="https://kittyclaw.dev/" rel="noopener noreferrer"&gt;KittyClaw&lt;/a&gt; (the kanban orchestrator running all the agents), Ekioo documents how an autonomous AI agent fleet operates in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem We Chose
&lt;/h2&gt;

&lt;p&gt;The construction industry still runs on emails and Excel spreadsheets. Craftsmen and small construction companies spend hours writing quotes by hand, chasing clients, and tracking payments. Tools exist — but they're either too complex or designed for structured mid-sized companies, not the solo tiler or plasterer.&lt;/p&gt;

&lt;p&gt;Kalceo starts from a simple observation: a quote should take 10 minutes, not two hours.&lt;/p&gt;

&lt;p&gt;The MVP scope is deliberately narrow — no accounting module, no bank integration, no e-signature in the first version. Just: create a quote, convert it to an invoice, PDF it, email it to the client. The bare minimum to be useful from day one.&lt;/p&gt;




&lt;h2&gt;
  
  
  From Ticket to Production
&lt;/h2&gt;

&lt;p&gt;Kalceo was built with the same infrastructure as Ekioo.com itself: &lt;strong&gt;KittyClaw&lt;/strong&gt; as the orchestrating kanban, and a fleet of specialized Claude agents taking tickets one by one.&lt;/p&gt;

&lt;p&gt;Scoping the MVP started with a raw feature list, then refined into atomic tickets. Each ticket has a precise scope: "Create the quote input form", "Generate PDF from HTML template", "Send the quote by email via Brevo". Nothing vague, nothing cross-cutting. Agents work well with clear boundaries.&lt;/p&gt;

&lt;p&gt;The workflow for each feature looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;KittyClaw ticket in Todo
          |
          v
Assignee selected
          |
          v
Dispatcher launches the matching agent
(programmer, QA tester, or content writer)
          |
          v
Pull request and review
          |
          v
Owner merge ──&amp;gt; Production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each agent works in its own isolated &lt;strong&gt;git worktree&lt;/strong&gt;. It creates a branch, codes the feature, commits, opens a PR. The owner (me) reviews and merges. When the agent finishes, the ticket moves to Review in KittyClaw.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Agents Built
&lt;/h2&gt;

&lt;p&gt;Over a few weeks, the fleet produced the core of the MVP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authentication&lt;/strong&gt; — sign-up, login, account management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service catalog&lt;/strong&gt; — each company creates their own reusable quote line items&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quote creation&lt;/strong&gt; — adding line items, calculating totals (pre-tax/VAT/all-in), statuses (draft, finalized, sent, accepted, declined)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDF export&lt;/strong&gt; via browser print (server-generated print-ready HTML page)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DOCX export&lt;/strong&gt; for editing in Word&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;programmer&lt;/code&gt; agent handled most of the backend features (JavaScript, Cloudflare Workers + D1). The &lt;code&gt;qa-tester&lt;/code&gt; wrote integration tests and caught several bugs in total calculations — particularly rounding issues on lines with decimal quantities.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Worked, What We Had to Fix
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What worked well:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Agents are excellent on well-bounded tasks. Give a ticket a clear scope ("generate a PDF from the Quote Razor component"), and it's often a single round-trip. The agent reads the existing code, proposes an implementation consistent with what's already there, and produces something testable.&lt;/p&gt;

&lt;p&gt;The isolated worktree structure also simplified parallelization: two or three features could advance simultaneously without interference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we had to fix:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Agents sometimes miss side effects — especially in complex business logic. The totals calculation is the clearest example: the first pass handled rounding correctly for whole-number quantities, but there was a subtle bug on decimal quantities (e.g. 1.5 units × unit price) that produced an incorrect pre-tax total by a few cents. The &lt;code&gt;qa-tester&lt;/code&gt; caught it, the &lt;code&gt;programmer&lt;/code&gt; fixed it — but it illustrates why human review remains essential.&lt;/p&gt;

&lt;p&gt;There were also UI regressions between features. One agent adds a component, another modifies the layout, and CSS breaks in a corner neither tested. We introduced a simple rule: every PR must include a screenshot of the affected page before going into Review.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Kalceo Stands Today
&lt;/h2&gt;

&lt;p&gt;The MVP is &lt;strong&gt;live&lt;/strong&gt; at &lt;a href="https://kalceo.fr" rel="noopener noreferrer"&gt;kalceo.fr&lt;/a&gt;. A Facebook campaign has been running for a few weeks, targeting craftsmen and micro-businesses in the French construction industry.&lt;/p&gt;

&lt;p&gt;Early feedback confirms the problem: users who sign up are indeed solo or micro-enterprises in construction who still manage their quotes in Excel or Word. The main friction they report is importing their existing catalog — a clearly identified improvement for V2.&lt;/p&gt;

&lt;p&gt;The rest of the backlog centers on two axes: &lt;strong&gt;automatic client follow-up&lt;/strong&gt; (sending a reminder email X days after sending a quote with no response) and &lt;strong&gt;accounting export&lt;/strong&gt; (export in formats compatible with popular French accounting software).&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Changes
&lt;/h2&gt;

&lt;p&gt;Kalceo wouldn't have existed as a solo project — not in this timeframe. Not because the features are impossible to code, but because the sum of background tasks (auth, data models, PDF generation, emails, tests, deployment) would have taken several months part-time.&lt;/p&gt;

&lt;p&gt;With the fleet, it works differently: you spend time scoping tickets, reviewing PRs, deciding what's in the MVP and what isn't. The background work is delegated. That doesn't mean "error-free" — but it means you can iterate fast, fix fast, and stay focused on the product rather than the code.&lt;/p&gt;

&lt;p&gt;That's the real shift: &lt;strong&gt;the acceleration doesn't come from agents coding faster, but from you deciding faster&lt;/strong&gt; because execution friction has disappeared.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>saas</category>
      <category>kittyclaw</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
