<?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: Selçuk GÜRAL</title>
    <description>The latest articles on DEV Community by Selçuk GÜRAL (@selcukgural).</description>
    <link>https://dev.to/selcukgural</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%2F2504265%2Fefaad0e8-8552-42d4-b02d-6edf59361594.png</url>
      <title>DEV Community: Selçuk GÜRAL</title>
      <link>https://dev.to/selcukgural</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/selcukgural"/>
    <language>en</language>
    <item>
      <title>What is Lonca? Version control for AI coding agents (Lonca series, part 0)</title>
      <dc:creator>Selçuk GÜRAL</dc:creator>
      <pubDate>Sat, 14 Nov 2026 17:26:23 +0000</pubDate>
      <link>https://dev.to/selcukgural/what-is-lonca-version-control-for-ai-coding-agents-lonca-series-part-0-3fc2</link>
      <guid>https://dev.to/selcukgural/what-is-lonca-version-control-for-ai-coding-agents-lonca-series-part-0-3fc2</guid>
      <description>&lt;p&gt;For a while now I have been working on Lonca, a version control system for AI agents. This post starts a series about it. Later posts will take its parts one at a time; this first one only gives the overall picture: what Lonca is, which problem it tries to solve, and how much of that it does today, in version 0.2.3.&lt;/p&gt;

&lt;p&gt;I will try not to oversell it. I will write down what works, and also what does not exist yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lonca in short: version control for AI agents
&lt;/h2&gt;

&lt;p&gt;Lonca is a version control system for repositories where several AI agents and a few humans work at the same time. It is written in Rust and it is open source (Apache-2.0).&lt;/p&gt;

&lt;p&gt;It does not try to replace Git. The content stays plain Git: you can push to GitHub, and your Git tools keep working. What changes is everything around it: who may do what, how work done at the same time is merged, and how every operation is recorded and checked.&lt;/p&gt;

&lt;p&gt;The name comes from the craft guilds of the Ottoman bazaar, the &lt;em&gt;lonca&lt;/em&gt;: written rules, masters, journeymen and apprentices, and an inspection before goods reach the market. That is Lonca's model too.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem it solves
&lt;/h2&gt;

&lt;p&gt;Most days I run several coding agents on the same repository. The agents do their work well; the problem is the tooling around them. Git and platforms like GitHub assume that every writer is a person. With agents, that assumption breaks in three places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Permissions.&lt;/strong&gt; An agent works with my token, shows up in the history under my name, and has every right I have. Even when its task is to fix one function, it can force-push, delete a branch or change the CI files. Nothing stands in between.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency.&lt;/strong&gt; Two agents working on the same file only find out about each other at merge time. Each is green on its own; together they are red.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accountability.&lt;/strong&gt; When something goes wrong, I can't answer the two questions that matter: &lt;em&gt;what was this agent allowed to do, and did it do only that?&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lonca exists to answer these three.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;The core idea is simple: agents never write to the repository directly, they &lt;em&gt;propose&lt;/em&gt;. A single process on the machine, the Lonca daemon, is the repository's only writer. It decides every request, signs the decision, appends it to an operation log kept inside the repository (&lt;code&gt;refs/lonca/oplog&lt;/code&gt;), and only then applies 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%2Fbesuaw6ynkmsx49r79i3.png" 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%2Fbesuaw6ynkmsx49r79i3.png" alt="Diagram of Lonca, version control for AI agents: an agent proposes and a human approves; the lonca daemon, the only writer, decides with decide(), signs, appends to the log and only then applies it to the Git repository; a sandboxed verifier runs build and test; after git push, lonca-audit replays every decision on the remote." width="800" height="352"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Agents propose; the daemon, the only writer, decides, signs and records.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Four parts around the daemon
&lt;/h3&gt;

&lt;p&gt;Four parts are built around that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capabilities, not accounts.&lt;/strong&gt; Every agent works in a session, and the session gets narrow rights that expire: which paths it may touch, which operations it may run, size limits. Rights can only narrow when they are handed to sub-agents. Revoking a session revokes everything derived from it. Unknown operations, scopes and paths are refused.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Symbol leases and a structural merge queue.&lt;/strong&gt; Agents lease the functions and types they edit, so several agents can work in parallel on different functions of the same file. When an agent proposes a change, it declares what it changed; Lonca computes what really changed with tree-sitter and refuses the proposal if the two don't match. The merge queue merges changes symbol by symbol and runs the build and tests in a sandbox with no network access; trunk moves only after that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Taint.&lt;/strong&gt; The tool the agent runs in (the harness; today that is Claude Code) reports every tool call to the daemon, and a proxy sees outbound connections. A session that reads outside content (a web page, an issue, another MCP server) counts as tainted: its changes need a human's approval, and it may not touch build scripts, CI or lock files at all. This is the structural answer to prompt injection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A log anyone can replay.&lt;/strong&gt; Every operation, whether allowed, denied or sent to a human, is in the log, and the log travels with the repository on &lt;code&gt;git push&lt;/code&gt;. The log is append-only; an undo (&lt;code&gt;lonca undo&lt;/code&gt;) is a new, compensating operation. The independent auditor, &lt;code&gt;lonca-audit&lt;/code&gt;, replays every decision from the start, recomputes every merge, and catches a forged entry even when it is signed with the daemon's own key.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Human signatures, and no AI where integrity depends on it
&lt;/h3&gt;

&lt;p&gt;Humans sign their own operations with their own keys. The key can live in the Mac's Secure Enclave or on a FIDO security key; then every approval needs Touch ID or a touch. An agent running as your user can't approve on your behalf.&lt;/p&gt;

&lt;p&gt;There is no AI where integrity depends on it. The decision function &lt;code&gt;decide()&lt;/code&gt;, the daemon and the auditor contain no AI, no network and no randomness. &lt;code&gt;decide()&lt;/code&gt; is a pure function, and all of its inputs, time included, are in the log entry. Build and test results are recorded as evidence and never recomputed later. AI appears only as an optional advisor that helps a human during review, and it decides nothing.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where it stands today
&lt;/h2&gt;

&lt;p&gt;The current version is 0.2.3. Every phase of the design is implemented and tested. A log written by any 0.x release stays readable and auditable by every later release.&lt;/p&gt;
&lt;h3&gt;
  
  
  What it does today
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full support for Claude Code.&lt;/strong&gt; I build Lonca against Claude Code and try it live before every release. The hooks and Lonca's MCP tools are ready as soon as a session starts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Symbol-level work in 13 languages.&lt;/strong&gt; Rust, Python, TypeScript/TSX, JavaScript, C#, Go, Java, Kotlin, C, C++, Ruby, PHP and Swift. Markdown, TOML, JSON and YAML files are split into sections; other files are leased whole.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;macOS and Linux.&lt;/strong&gt; Installed with Homebrew, &lt;code&gt;install.sh&lt;/code&gt; or &lt;code&gt;cargo install&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests.&lt;/strong&gt; More than 1,800 tests, real sandbox and socket tests included. 45 acceptance criteria, run with the real binaries and real daemon processes, must pass before every release. Eight fuzz targets must also be green before every release.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Three levels of trust
&lt;/h3&gt;

&lt;p&gt;How far you can trust what you see depends on how you run the agents. There are three levels:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;How agents run&lt;/th&gt;
&lt;th&gt;Going around the store&lt;/th&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cooperative&lt;/td&gt;
&lt;td&gt;As your user&lt;/td&gt;
&lt;td&gt;Not prevented, but detected (hash chain, auditor). Every change needs a human's approval.&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardened&lt;/td&gt;
&lt;td&gt;As your user; Claude Code's hooks and sandbox are locked&lt;/td&gt;
&lt;td&gt;Prevented: the daemon is a separate system user&lt;/td&gt;
&lt;td&gt;An install script run as root&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Isolated&lt;/td&gt;
&lt;td&gt;Inside a container or an OS sandbox&lt;/td&gt;
&lt;td&gt;Prevented&lt;/td&gt;
&lt;td&gt;Your own container runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  What doesn't exist yet, or is limited
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One machine.&lt;/strong&gt; 0.x is for one developer with many agents. A team server is designed, not built.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One real user.&lt;/strong&gt; The only real project running on Lonca today is &lt;a href="https://github.com/selcukgural/imece" rel="noopener noreferrer"&gt;imece&lt;/a&gt;, and that is my project too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Other harnesses.&lt;/strong&gt; Codex, Gemini CLI and OpenCode have profiles and run isolated under &lt;code&gt;lonca run&lt;/code&gt;, but their hooks are not wired up yet. IDE agents are planned; any client that speaks MCP can use &lt;code&gt;lonca mcp&lt;/code&gt; today.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The verifier's limits.&lt;/strong&gt; Resource limits hold for each process; a build or test command's total disk, memory and (on macOS) process count are not bounded yet. So only verify commands you would run yourself. On Linux the sandbox needs &lt;code&gt;bubblewrap&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signature changes.&lt;/strong&gt; When a function's signature changes, Lonca notices and asks for it to be declared, but it does not analyze the callers. Broken calls are caught by the tests in the merge queue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redaction is logical.&lt;/strong&gt; When a leaked secret is redacted, the daemon no longer hands out its content, but the object is not yet purged from Git history.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paths not yet exercised.&lt;/strong&gt; Signing with a physical FIDO key has not been tried by the project yet. The AI advisor has only been tested against a fake API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full, current list is on Lonca's site, under &lt;a href="https://selcukgural.github.io/lonca/docs/guide#limitations" rel="noopener noreferrer"&gt;Limitations&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Who it is for, and how to try it
&lt;/h2&gt;

&lt;p&gt;Right now Lonca is version control for AI agents on one developer's machine. It is mostly for developers who run several coding agents on the same repository on their own machine and want to keep what those agents do under control. If you work with a single agent now and then, you probably don't need it; Git and a good code review are enough.&lt;/p&gt;

&lt;p&gt;Install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap selcukgural/lonca &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; brew install lonca
&lt;span class="c"&gt;# or: curl -fsSL https://selcukgural.github.io/lonca/install.sh | sh&lt;/span&gt;
&lt;span class="c"&gt;# or: cargo install --locked lonca lonca-audit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lonca daemon &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;                       &lt;span class="c"&gt;# the gatekeeper&lt;/span&gt;
lonca human keygen ayse              &lt;span class="c"&gt;# or --secure-enclave, --security-key&lt;/span&gt;
lonca init demo --import ~/src/myproject
lonca session start --repo demo --agent claude --paths &lt;span class="s1"&gt;'src/**'&lt;/span&gt; \
        --task T-142 --ttl 2h --workspace ~/ws/claude-1 --harness claude-code
lonca doctor                         &lt;span class="c"&gt;# what a first run trips over&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/ws/claude-1 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From here the agent leases the symbols it edits and makes proposals. You see what is waiting with &lt;code&gt;lonca pending&lt;/code&gt;, approve with &lt;code&gt;lonca approve&lt;/code&gt;, undo with &lt;code&gt;lonca undo&lt;/code&gt; when needed, and audit the log with &lt;code&gt;lonca-audit verify&lt;/code&gt;. The step-by-step walk-through is in the &lt;a href="https://selcukgural.github.io/lonca/docs/guide/quick-start" rel="noopener noreferrer"&gt;quick start guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What comes next in the series
&lt;/h2&gt;

&lt;p&gt;In the next posts I plan to explain each part on its own, together with why it was built that way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;One writer and a pure &lt;code&gt;decide()&lt;/code&gt;: why there is no AI, network or randomness in the decision&lt;/li&gt;
&lt;li&gt;Symbol leases and the structural merge: agents working in parallel on one file&lt;/li&gt;
&lt;li&gt;Taint: content read from outside, and prompt injection&lt;/li&gt;
&lt;li&gt;The operation log and &lt;code&gt;lonca-audit&lt;/code&gt;: how you prove a decision afterwards&lt;/li&gt;
&lt;li&gt;Human signatures: the Secure Enclave and FIDO keys&lt;/li&gt;
&lt;li&gt;Lonca with Claude Code: a real working day&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The order and the topics may change along the way. Every post of the series will be collected under the &lt;a href="https://selcukgural.com/category/lonca/" rel="noopener noreferrer"&gt;Lonca&lt;/a&gt; category.&lt;/p&gt;

&lt;p&gt;One last note: I built Lonca with heavy help from Claude Code. The design and every decision are mine, and each one is written down with its reasons in &lt;a href="https://github.com/selcukgural/lonca/blob/main/docs/decisions.md" rel="noopener noreferrer"&gt;docs/decisions.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The code is &lt;a href="https://github.com/selcukgural/lonca" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;, the documentation is on &lt;a href="https://selcukgural.github.io/lonca/" rel="noopener noreferrer"&gt;Lonca's site&lt;/a&gt;. Questions and ideas go to &lt;a href="https://github.com/selcukgural/lonca/discussions" rel="noopener noreferrer"&gt;Discussions&lt;/a&gt;, bugs to issues.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was first published on &lt;a href="https://selcukgural.com/2026/11/12/what-is-lonca-version-control-for-ai-agents/" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ai</category>
      <category>git</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Kule, screen by screen: an AI agent's task from prompt to undo</title>
      <dc:creator>Selçuk GÜRAL</dc:creator>
      <pubDate>Sun, 01 Nov 2026 16:16:48 +0000</pubDate>
      <link>https://dev.to/selcukgural/kule-screen-by-screen-an-ai-agents-task-from-prompt-to-undo-klc</link>
      <guid>https://dev.to/selcukgural/kule-screen-by-screen-an-ai-agents-task-from-prompt-to-undo-klc</guid>
      <description>&lt;p&gt;Hi again. My &lt;a href="https://dev.to/selcukgural/i-built-kule-ai-agents-work-on-a-copy-of-your-folder-and-you-decide-what-comes-back-1ik2"&gt;first post about Kule&lt;/a&gt; was all CLI commands, and I kept wanting to show you the window instead. So this one is screenshots: one small task, from the prompt to the undo, with nothing cut out. That includes the two places where Kule stopped &lt;em&gt;me&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Kule in one line: AI agents work on a copy of your folder, inside a sandbox, and nothing reaches the real folder until you review it and commit it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I took these screenshots
&lt;/h2&gt;

&lt;p&gt;I'll say this first, so nothing below looks staged:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;This is Kule's real window&lt;/strong&gt; (&lt;code&gt;0.2.0-preview.20&lt;/code&gt;) on Linux, running in a throwaway container on a virtual display. On macOS the window has its native title bar; the rest looks the same.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The project is a tiny Node CLI&lt;/strong&gt; I made for this post, &lt;code&gt;tiny-weather&lt;/code&gt;: four files and one git commit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The agent is not a model.&lt;/strong&gt; It's &lt;code&gt;kule-test-agent&lt;/code&gt;, the scripted agent Kule's own tests use. It speaks the same protocol (ACP) as Claude Code or Codex, but it runs a list of steps I wrote. That way one run could show a question, an approval and a refusal, and I didn't have to put a real agent's login into a throwaway container. Everything around the agent is real: the copy, the sandbox, the proxy, the policy, the review, the commit and the undo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The script asked the "agent" to add a &lt;code&gt;--units&lt;/code&gt; option, write a test and a README section, bump the version and require Node 20 in &lt;code&gt;package.json&lt;/code&gt;, add a git pre-commit hook that runs the tests, and then check that the forecast API answers. That's roughly what an eager agent might do if you asked it to "add Fahrenheit support and make sure the tests run before every commit".&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The agent at work, and a question
&lt;/h2&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%2Fi1uts5gdqyif02e2l87p.png" 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%2Fi1uts5gdqyif02e2l87p.png" alt="Kule's window while the agent works: the task under &amp;quot;Needs you&amp;quot;, tool calls in the Agent panel, and a card asking whether the agent may connect to api.open-meteo.com:443" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your tasks are on the left. While the agent works, the bottom panel (Live) shows each tool call on one line, the files it changed after the arrow, and its output under it. The bar at the bottom shows what the run is using: CPU, memory, disk and processes.&lt;/p&gt;

&lt;p&gt;Then the agent tried to reach &lt;code&gt;api.open-meteo.com&lt;/code&gt;. Package registries and the agent's own model API go through on their own; any other host waits for a person. So the connection is held, a card shows up, and the task moves under &lt;strong&gt;Needs you&lt;/strong&gt;. The card tells you what approving means (a tunnel: Kule sees the host and port, not what's sent over it), which rule held it, and what happens if nobody answers: after 15 minutes the connection is refused and the agent carries on without it.&lt;/p&gt;

&lt;p&gt;I pressed &lt;strong&gt;Deny&lt;/strong&gt;. The agent's &lt;code&gt;curl&lt;/code&gt; got a 403 from the proxy, and the run finished.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Review: what changed, and what the policy says
&lt;/h2&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%2F5df1y49meibjajfcaeug.png" 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%2F5df1y49meibjajfcaeug.png" alt="The Review tab: &amp;quot;Denied by the policy&amp;quot; at the top (package.json needs approval, the git hook is denied), then the modified files with the functions, variables, JSON keys and Markdown sections that changed" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the screen I care about most. At the top is the ruling, and the rule behind each part of it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;package.json&lt;/code&gt; &lt;strong&gt;needs approval&lt;/strong&gt; (&lt;code&gt;manifests-and-lockfiles&lt;/code&gt;). It decides what gets installed and run, so a person looks at it.&lt;/li&gt;
&lt;li&gt;The new git hook is &lt;strong&gt;denied&lt;/strong&gt; (&lt;code&gt;git-hooks&lt;/code&gt;). A hook runs on your next &lt;code&gt;git commit&lt;/code&gt;, as you, so an agent doesn't get to add one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below the ruling, each file shows what changed &lt;em&gt;inside&lt;/em&gt; it, by name: &lt;code&gt;function format&lt;/code&gt;, &lt;code&gt;variable SIGN&lt;/code&gt;, &lt;code&gt;key engines&lt;/code&gt;, &lt;code&gt;section Units&lt;/code&gt;. You can read what happened before you open a single file. Click a file to see it in Preview, as it is in the copy.&lt;/p&gt;

&lt;p&gt;Something I want to be upfront about, because my own docs get it slightly wrong: &lt;strong&gt;while a denied change is in the pile, nothing gets committed.&lt;/strong&gt; The window offers only &lt;strong&gt;Roll back&lt;/strong&gt;, and &lt;code&gt;kule commit --approve&lt;/code&gt; refuses too. Parts of the docs say denied changes are "held back" on a commit, which suggests the rest would go through. What Kule actually does is the stricter thing, but the docs, and a hint the CLI prints, should say so. That fix goes into the next preview.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Timeline: every step
&lt;/h2&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%2Frmpsa0elvn1s36dmg1ye.png" 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%2Frmpsa0elvn1s36dmg1ye.png" alt="The Timeline tab: &amp;quot;Before the first step&amp;quot;, then steps 1 to 6, each with the files it changed and the actions &amp;quot;Go back here&amp;quot; and &amp;quot;Fork from here&amp;quot;" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every tool call that changed something is a step. &lt;strong&gt;Go back here&lt;/strong&gt; puts the copy back the way it was after that step. &lt;strong&gt;Fork from here&lt;/strong&gt; starts a new task from that point, so you can try another direction without losing this one.&lt;/p&gt;

&lt;p&gt;Going back to a step doesn't touch &lt;code&gt;.git&lt;/code&gt;, though, so for the hook I used a terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. A terminal in the same sandbox
&lt;/h2&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%2Fp8u4yh5ud1c80d7gg2y6.png" 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%2Fp8u4yh5ud1c80d7gg2y6.png" alt="A terminal tab marked &amp;quot;in the sandbox&amp;quot;: ls shows the project, while ls ~/.ssh, ls of the projects folder, cat of another project's .env and curl to example.com are all refused" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The bottom panel also has terminals, and they run in the task's sandbox, just like the agent. Here &lt;code&gt;ls&lt;/code&gt; shows the copy. The home folder's &lt;code&gt;~/.ssh&lt;/code&gt; (I put a dummy key there), the folder with other projects, and another project's &lt;code&gt;.env&lt;/code&gt; are all refused. &lt;code&gt;curl&lt;/code&gt; to a host the policy doesn't let through gets the same 403. &lt;code&gt;git status&lt;/code&gt; shows the agent's changes, because the copy is a full working copy, &lt;code&gt;.git&lt;/code&gt; included.&lt;/p&gt;

&lt;p&gt;From here I removed the hook with &lt;code&gt;rm .git/hooks/pre-commit&lt;/code&gt;. That's a change in the copy too, so Review judged it again.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Approve and commit
&lt;/h2&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%2F80ijttm58siual6hwbva.png" 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%2F80ijttm58siual6hwbva.png" alt="Review now says &amp;quot;Waits for your approval&amp;quot;: only package.json is held, and the button reads &amp;quot;Approve and commit&amp;quot;" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the hook gone, the ruling is &lt;strong&gt;Waits for your approval&lt;/strong&gt; and the button says &lt;strong&gt;Approve and commit&lt;/strong&gt;. If you tick &lt;strong&gt;Don't ask again in this folder&lt;/strong&gt;, Kule first shows what that would have let through, then remembers it for this folder.&lt;/p&gt;

&lt;p&gt;My first click didn't work:&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%2F18qi9jmes8pzwtyqf4b1.png" 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%2F18qi9jmes8pzwtyqf4b1.png" alt="A red notice: &amp;quot;a terminal is open on this task's copy (in Kule's app, or kule shell); close it first, then commit&amp;quot;" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I still had the terminal open on the copy, so Kule wouldn't commit. Fair enough. I closed the terminal and clicked again.&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%2F7p3lfj5yhr0tspzuyi0w.png" 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%2F7p3lfj5yhr0tspzuyi0w.png" alt="The Timeline after the commit: &amp;quot;Commit 1&amp;quot; with README.md, package.json, src/cli.js, src/format.js and the new test, plus an Undo button" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Commit 1 shows up in the Timeline with what it applied. The real folder now has the changes, and no hook.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Undo
&lt;/h2&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%2Fbod62qma2q01y9h6e7z0.png" 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%2Fbod62qma2q01y9h6e7z0.png" alt="The Timeline after undo: &amp;quot;Commit 1 · undone&amp;quot; struck through, and a notice &amp;quot;Undid commit 1&amp;quot;" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Undo takes two clicks: the first one turns red and asks again. It puts back what the commit replaced, from the checkpoint Kule kept. Afterwards I checked the real folder: &lt;code&gt;git status&lt;/code&gt; was clean and &lt;code&gt;package.json&lt;/code&gt; was back at &lt;code&gt;0.1.0&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What these screenshots don't show
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A real model.&lt;/strong&gt; With Claude Code, Codex or Gemini you'd also see the agent's own messages and thinking, and what the run cost in the task header (the header here says "nothing spent yet").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refusals listed on Linux.&lt;/strong&gt; Linux doesn't tell Kule what the sandbox refused; the command just gets a permission error, and the run ends with a line that says so. macOS reports them, and Kule lists them in Live and the Timeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows.&lt;/strong&gt; Windows 11 has had release files for a few previews now, but I've only tested it in an ARM64 virtual machine, not on a real x64 PC yet. It's the least tested of the three.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A finished product.&lt;/strong&gt; It's still a preview, and screens will keep changing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What changed since my last post
&lt;/h2&gt;

&lt;p&gt;I last wrote about &lt;code&gt;0.2.0-preview.14&lt;/code&gt;; the latest is &lt;code&gt;0.2.0-preview.20&lt;/code&gt;. The biggest changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A task's commands read only what's granted:&lt;/strong&gt; the copy, the system, your toolchains, and folders you add with &lt;code&gt;kule tools add&lt;/code&gt;. The rest of your home simply isn't there for them. That's what you see in the terminal screenshot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows 11 builds&lt;/strong&gt;, with the limits above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;19 security advisories published so far&lt;/strong&gt;, each one after its fix had shipped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mutation testing (cargo-mutants) runs in CI&lt;/strong&gt; on every change, on macOS and Linux, on top of the tests that run on real processes and the real filesystem.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it, and tell me what got in your way
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Download:&lt;/strong&gt; &lt;a href="https://github.com/kulehq/kule/releases" rel="noopener noreferrer"&gt;releases page&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs&lt;/strong&gt; (English and Turkish), with a page for every screen above: &lt;a href="https://kulehq.github.io/kule/" rel="noopener noreferrer"&gt;kulehq.github.io/kule&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing guide&lt;/strong&gt;, if you'd like to help: &lt;a href="https://kulehq.github.io/kule/docs/testing" rel="noopener noreferrer"&gt;kulehq.github.io/kule/docs/testing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/kulehq/kule" rel="noopener noreferrer"&gt;github.com/kulehq/kule&lt;/a&gt; · Apache-2.0 · Rust&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a screen confused you, or a card asked too often, tell me here or in an issue. If you find a way past one of the boundaries, please report it privately as &lt;a href="https://github.com/kulehq/kule/blob/main/SECURITY.md" rel="noopener noreferrer"&gt;SECURITY.md&lt;/a&gt; explains, not in public.&lt;/p&gt;

&lt;p&gt;If this kind of walk-through is useful, I'd like the next one to be about the policy: how Kule decides what passes, what waits and what's denied, and how to write your own rules. Tell me which screen you'd like to see up close.&lt;/p&gt;

&lt;p&gt;Thanks for reading. 🙏&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>security</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Kule needs testers: run AI agents on a copy of your folder, then tell me what broke</title>
      <dc:creator>Selçuk GÜRAL</dc:creator>
      <pubDate>Mon, 26 Oct 2026 20:09:26 +0000</pubDate>
      <link>https://dev.to/selcukgural/kule-needs-testers-run-ai-agents-on-a-copy-of-your-folder-then-tell-me-what-broke-5ga8</link>
      <guid>https://dev.to/selcukgural/kule-needs-testers-run-ai-agents-on-a-copy-of-your-folder-then-tell-me-what-broke-5ga8</guid>
      <description>&lt;p&gt;Last week I wrote about &lt;a href="https://dev.to/selcukgural/i-built-kule-ai-agents-work-on-a-copy-of-your-folder-and-you-decide-what-comes-back-1ik2"&gt;Kule&lt;/a&gt;, the open-source tool I'm building. Your comments turned into real changes, so this time I'd like to ask for something more specific: &lt;strong&gt;try Kule on your own computer, and tell me what happened.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Kule in one paragraph
&lt;/h2&gt;

&lt;p&gt;Kule is a control room for AI agents that work on your folders. You point an agent (Claude Code, Codex, Gemini, or any agent that speaks &lt;a href="https://agentclientprotocol.com" rel="noopener noreferrer"&gt;ACP&lt;/a&gt;) at a folder. Kule gives it a copy-on-write copy of that folder inside an OS sandbox, looks at what every command really changed, and lets nothing reach the real folder until a policy, or you, says so. Every commit can be undone. It has a CLI and a desktop app, it's written in Rust, and it's Apache-2.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed since the first post
&lt;/h2&gt;

&lt;p&gt;That post covered &lt;code&gt;0.2.0-preview.1&lt;/code&gt;. The latest is &lt;code&gt;0.2.0-preview.14&lt;/code&gt;. Some of the bigger changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The default policy is an allow-list now.&lt;/strong&gt; Files of a kind known not to run by themselves, or of a kind your project already tracks, pass on their own. Everything else waits for you, scripts and hidden files included.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing an agent writes reaches your home.&lt;/strong&gt; Agents keep their settings, history and transcripts in a home of their own inside Kule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A task's commands can't read your other projects&lt;/strong&gt;, or where apps keep their settings, sign-ins and data, beyond what toolchains and shells need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your terminals run in the task's sandbox too&lt;/strong&gt;, not just the agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kule tells you what it cut, ended or refused&lt;/strong&gt;, where you're working: in the command's output, the terminal, and the app's Live view.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Releases are built on pinned images&lt;/strong&gt; and can't be changed once they're published. Each one comes with checksums, a build provenance attestation and an SBOM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;15 security advisories are published so far.&lt;/strong&gt; Each one went out after its fix had shipped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of these came straight from comments on the first post:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An approval card now says whether Kule looks inside the connection you're approving (&lt;a href="https://github.com/kulehq/kule/issues/27" rel="noopener noreferrer"&gt;#27&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;I measured which tools send Encrypted Client Hello before deciding how Kule treats it (&lt;a href="https://github.com/kulehq/kule/issues/34" rel="noopener noreferrer"&gt;#34&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Holding a &lt;code&gt;git push&lt;/code&gt; apart from fetches is next (&lt;a href="https://github.com/kulehq/kule/issues/50" rel="noopener noreferrer"&gt;#50&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you to everyone who asked those questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm asking for
&lt;/h2&gt;

&lt;p&gt;I wrote a short &lt;a href="https://kulehq.github.io/kule/docs/testing" rel="noopener noreferrer"&gt;testing guide&lt;/a&gt; with six scenarios. Each takes 5 to 20 minutes, and you can pick any of them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install and check.&lt;/strong&gt; Does the install guide hold up on your machine?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The loop, without an agent.&lt;/strong&gt; Make a task, change the copy, review, commit, undo. No network or agent needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The app, with an agent.&lt;/strong&gt; Your first task in the desktop app, from prompt to commit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your own work.&lt;/strong&gt; Ask an agent for something you'd ask it anyway, and see whether Kule gets in the way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your own setup.&lt;/strong&gt; This is where I need help most (more on that below).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The boundaries.&lt;/strong&gt; Try to get past them, and report what you find privately.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At the end of each one, there's only one question: &lt;strong&gt;what happened that you didn't expect?&lt;/strong&gt; A confusing step, a policy that asks too often, a tool that breaks inside the sandbox, a slow copy: all of it is useful. "Everything worked" is useful too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setups I can't test myself
&lt;/h3&gt;

&lt;p&gt;Kule is tested on a handful of machines. If yours is one of these, a short report is worth a lot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a Linux distribution other than Ubuntu, or Ubuntu 24.04 with its user namespace restriction;&lt;/li&gt;
&lt;li&gt;a file system other than APFS, Btrfs or XFS;&lt;/li&gt;
&lt;li&gt;a large repository or a monorepo;&lt;/li&gt;
&lt;li&gt;an Intel Mac;&lt;/li&gt;
&lt;li&gt;an ACP agent other than Claude Code, Codex and Gemini.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Before you start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;macOS 13+ and Linux x86_64 only.&lt;/strong&gt; Windows support is in progress, but it's not ready for testing yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The macOS app isn't notarized.&lt;/strong&gt; It's signed ad hoc, so macOS will refuse to open it the first time. The &lt;a href="https://kulehq.github.io/kule/docs/getting-started/install" rel="noopener noreferrer"&gt;install guide&lt;/a&gt; shows how to check the download and open the app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start with a test folder&lt;/strong&gt;, or with a project whose work is all committed and pushed. Kule doesn't touch your folder until you commit, but it's still a preview.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agents use your own accounts&lt;/strong&gt;, so a run spends your own quota or API credit, the same as it would without Kule.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to report
&lt;/h2&gt;

&lt;p&gt;Open an issue at &lt;a href="https://github.com/kulehq/kule/issues/new/choose" rel="noopener noreferrer"&gt;github.com/kulehq/kule/issues/new/choose&lt;/a&gt; and pick one of the forms: &lt;strong&gt;Bug report&lt;/strong&gt;, &lt;strong&gt;Test report&lt;/strong&gt; or &lt;strong&gt;Idea or change&lt;/strong&gt;. Questions are welcome in &lt;a href="https://github.com/kulehq/kule/discussions" rel="noopener noreferrer"&gt;Discussions&lt;/a&gt;, or right here in the comments.&lt;/p&gt;

&lt;p&gt;Issues are public, so please leave out API keys, tokens, private paths and anything else you wouldn't publish. The forms remind you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you find a way past one of Kule's boundaries, please don't post it publicly.&lt;/strong&gt; Report it privately, as &lt;a href="https://github.com/kulehq/kule/blob/main/SECURITY.md" rel="noopener noreferrer"&gt;SECURITY.md&lt;/a&gt; explains. The fix is made in private and released first, and the advisory credits you unless you'd rather it didn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens next
&lt;/h2&gt;

&lt;p&gt;I read every report. A fix ships in the next preview, and its issue says which one. The &lt;a href="https://github.com/kulehq/kule/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;CHANGELOG&lt;/a&gt; lists what each preview changed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Testing guide:&lt;/strong&gt; &lt;a href="https://kulehq.github.io/kule/docs/testing" rel="noopener noreferrer"&gt;kulehq.github.io/kule/docs/testing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Download:&lt;/strong&gt; &lt;a href="https://github.com/kulehq/kule/releases" rel="noopener noreferrer"&gt;releases page&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/kulehq/kule" rel="noopener noreferrer"&gt;github.com/kulehq/kule&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading, and thanks in advance to anyone who gives it 15 minutes. 🙏&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>security</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I built Kule: AI agents work on a copy of your folder, and you decide what comes back</title>
      <dc:creator>Selçuk GÜRAL</dc:creator>
      <pubDate>Wed, 21 Oct 2026 09:10:54 +0000</pubDate>
      <link>https://dev.to/selcukgural/i-built-kule-ai-agents-work-on-a-copy-of-your-folder-and-you-decide-what-comes-back-1ik2</link>
      <guid>https://dev.to/selcukgural/i-built-kule-ai-agents-work-on-a-copy-of-your-folder-and-you-decide-what-comes-back-1ik2</guid>
      <description>&lt;p&gt;Hi, I'm Selçuk. I've been building an open-source tool called &lt;strong&gt;Kule&lt;/strong&gt;, and its first preview release is out. I'd like to tell you what it does, why I built it, and what it can't do yet.&lt;/p&gt;

&lt;p&gt;The short version: &lt;strong&gt;Kule is a control room for AI agents that work on your folders.&lt;/strong&gt; You point an agent (Claude Code, Codex, Gemini, or any agent that speaks &lt;a href="https://agentclientprotocol.com" rel="noopener noreferrer"&gt;ACP&lt;/a&gt;) at a folder. Kule gives it a copy-on-write copy of that folder inside a sandbox, looks at what every command really changed, and lets nothing reach the real folder until a policy, or you, says so. Every commit can be undone.&lt;/p&gt;

&lt;p&gt;("Kule" means &lt;em&gt;tower&lt;/em&gt; in Turkish. Think control tower.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem I kept running into
&lt;/h2&gt;

&lt;p&gt;Coding agents are good now, good enough that we let them run commands. When an agent runs a command, it runs as &lt;em&gt;you&lt;/em&gt;: your files, your &lt;code&gt;~/.ssh&lt;/code&gt; and &lt;code&gt;~/.aws&lt;/code&gt;, your &lt;code&gt;~/.zshrc&lt;/code&gt;, your network, your API keys.&lt;/p&gt;

&lt;p&gt;Most agents ask before they run something, but they ask about the &lt;strong&gt;command text&lt;/strong&gt;: "Run &lt;code&gt;npm install&lt;/code&gt;?" You can't tell from that text what a postinstall script, a Python one-liner or a build script will actually touch. So after the twentieth prompt you start clicking "yes" without reading, or you switch on the mode that stops asking. And if a file the agent reads contains a prompt injection, the agent is on the attacker's side, and all that's left is what you approve.&lt;/p&gt;

&lt;p&gt;I wanted something else: &lt;strong&gt;let the agent work freely, somewhere nothing it does is real until I've seen it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop
&lt;/h2&gt;

&lt;p&gt;Kule has one loop, in the CLI and in the desktop app:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;make a task → give the agent a prompt → watch → review → commit or roll back.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here it is without an agent, on a scratch folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/kule-demo &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"hello"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/kule-demo/notes.md

kule new ~/kule-demo                                    &lt;span class="c"&gt;# prints a task id, e.g. 1a2b3c4d&lt;/span&gt;
kule &lt;span class="nb"&gt;exec &lt;/span&gt;1a2b3c4d &lt;span class="nt"&gt;--&lt;/span&gt; sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'echo "more" &amp;gt;&amp;gt; notes.md'&lt;/span&gt;  &lt;span class="c"&gt;# runs in the copy, in the sandbox&lt;/span&gt;
kule status 1a2b3c4d                                    &lt;span class="c"&gt;# what changed, and the policy's verdict&lt;/span&gt;
kule commit 1a2b3c4d                                    &lt;span class="c"&gt;# apply it, keeping a checkpoint&lt;/span&gt;
kule undo 1a2b3c4d                                      &lt;span class="c"&gt;# put the folder back&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with an agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kule run 1a2b3c4d &lt;span class="nt"&gt;--agent&lt;/span&gt; claude-code &lt;span class="s2"&gt;"Turn notes.md into a checklist"&lt;/span&gt;
kule steps 1a2b3c4d     &lt;span class="c"&gt;# every tool call the agent made, and what it changed&lt;/span&gt;
kule commit 1a2b3c4d    &lt;span class="c"&gt;# or: kule rollback 1a2b3c4d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The desktop app (Tauri) does the same thing with a live stream, an approval inbox, a review screen and a timeline. A small daemon keeps runs going when the window is closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's actually between the agent and your stuff
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Your folder.&lt;/strong&gt; The agent works in a copy-on-write copy: an APFS clone on macOS, a reflink on Btrfs/XFS. Its changes reach the real folder only through a commit, and every commit keeps a checkpoint, so &lt;code&gt;kule undo&lt;/code&gt; really puts things back. The history of commits and undos is signed and hash-chained.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your computer.&lt;/strong&gt; Every command runs in the OS sandbox: Seatbelt on macOS, Landlock + seccomp + namespaces on Linux. It can write only to the copy and its own temp folders. &lt;code&gt;~/.ssh&lt;/code&gt;, &lt;code&gt;~/.aws&lt;/code&gt;, your Documents and the projects next to the folder are hidden. If the sandbox can't be set up, the command doesn't run. Kule never quietly falls back to "no sandbox".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What gets in.&lt;/strong&gt; This is the part I care about most. Kule doesn't judge the command; it judges the &lt;strong&gt;effects&lt;/strong&gt;. After a run, it compares the copy with your folder: files created, modified, deleted or moved, git branches moved, hooks added. Then a &lt;a href="https://www.cedarpolicy.com" rel="noopener noreferrer"&gt;Cedar&lt;/a&gt; policy gives each effect a verdict: &lt;em&gt;pass&lt;/em&gt;, &lt;em&gt;ask&lt;/em&gt; or &lt;em&gt;deny&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;With the default ("balanced") policy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ordinary edits to source, notes, documents and data &lt;strong&gt;pass&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;manifests and lockfiles, CI files, editor settings, agent instructions (&lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;AGENTS.md&lt;/code&gt;, &lt;code&gt;.mcp.json&lt;/code&gt;…), git config changes, history rewrites and mass deletes &lt;strong&gt;wait for you&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;git hooks are &lt;strong&gt;denied&lt;/strong&gt;, because they would run on your next &lt;code&gt;git&lt;/code&gt; command, as you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So a harmless-looking command that rewrites &lt;code&gt;package.json&lt;/code&gt; still waits for you, and a scary-looking &lt;code&gt;rm -rf&lt;/code&gt; that only touches the copy changes nothing real. When something waits, Kule tells you which rule stopped it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;policy: waits for approval
  package.json · waits for approval (manifests-and-lockfiles)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's also a "careful" policy where every change waits. You can write your own, and a repository can add refusals of its own, but never loosen yours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your network.&lt;/strong&gt; Every connection goes through the task's proxy. Package registries pass, the agent reaches its own model API while it runs, and anything else waits for your answer. Every connection is logged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your secrets.&lt;/strong&gt; The agent sees a placeholder, never the key. The proxy puts the real value into requests going to the hosts you named, and nowhere else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your home.&lt;/strong&gt; A write to &lt;code&gt;~/.zshrc&lt;/code&gt;, &lt;code&gt;~/.gitconfig&lt;/code&gt; or &lt;code&gt;~/.config&lt;/code&gt; lands in the task's &lt;em&gt;home layer&lt;/em&gt; with a diff for you to review. It doesn't touch your real home.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your wallet and your machine.&lt;/strong&gt; Cost budgets per task, per group and per day (with a notice at 80%), CPU/memory/disk/time limits per run, and a Stop button.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not only for code
&lt;/h2&gt;

&lt;p&gt;Kule works on any folder. The review shows what changed &lt;em&gt;inside&lt;/em&gt; a file (functions and methods, Markdown sections, JSON/YAML keys), before-and-after pictures, and previews for CSV, Excel and Word. If you let an agent tidy your notes, rename photos or clean up a data folder, the same loop applies.&lt;/p&gt;

&lt;p&gt;Two tasks can work on the same folder. A file both of them changed merges method by method, and only the parts that were changed twice conflict.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest part: what it can't do (yet)
&lt;/h2&gt;

&lt;p&gt;I'd rather you hear this from me than find out after an hour of setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It's a preview&lt;/strong&gt; (&lt;code&gt;0.2.0-preview.1&lt;/code&gt;). Things will change between previews.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;macOS 13+ and Linux x86_64 only.&lt;/strong&gt; Windows is planned, not started.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The macOS app isn't notarized.&lt;/strong&gt; It's signed ad hoc, so Gatekeeper will complain the first time. Every release file comes with &lt;code&gt;SHA256SUMS&lt;/code&gt;, a build provenance attestation and an SBOM, and the &lt;a href="https://kulehq.github.io/kule/docs/getting-started/install" rel="noopener noreferrer"&gt;install guide&lt;/a&gt; shows how to check them and open the app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linux has some requirements.&lt;/strong&gt; On ext4 the copy is a full copy, not a clone. The sandbox needs Landlock (Linux 6.2+, 6.7+ for network rules) and unprivileged user namespaces, which Ubuntu 24.04 restricts by default. Kule tells you what's missing and won't run an agent with weaker isolation unless you choose that knowingly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The proxy doesn't do HTTP/2 or WebSockets yet.&lt;/strong&gt; Clients fall back to HTTP/1.1. A few tools on macOS have certificate quirks; the &lt;a href="https://kulehq.github.io/kule/docs/platforms" rel="noopener noreferrer"&gt;Platforms page&lt;/a&gt; lists them all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kule doesn't make the agent right.&lt;/strong&gt; It makes the agent's mistakes cheap and visible. You still review, and Kule's job is to make that review short and honest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's built by one person.&lt;/strong&gt; I take security seriously (every boundary has a test that fails without it, on real processes and the real filesystem, no mocks), but one person can still miss things. That's part of why it's open.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Download:&lt;/strong&gt; &lt;a href="https://github.com/kulehq/kule/releases" rel="noopener noreferrer"&gt;releases page&lt;/a&gt; (universal &lt;code&gt;.dmg&lt;/code&gt;, &lt;code&gt;.deb&lt;/code&gt;, &lt;code&gt;.rpm&lt;/code&gt;, and the CLI alone as &lt;code&gt;.tar.gz&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Or build from source:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  git clone https://github.com/kulehq/kule &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;kule
  cargo build &lt;span class="nt"&gt;--release&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; kule &lt;span class="nt"&gt;-p&lt;/span&gt; kule-desktop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docs&lt;/strong&gt; (English and Turkish), with a quick start, a page for every command and a three-minute video tour: &lt;a href="https://kulehq.github.io/kule/" rel="noopener noreferrer"&gt;kulehq.github.io/kule&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/kulehq/kule" rel="noopener noreferrer"&gt;github.com/kulehq/kule&lt;/a&gt; · Apache-2.0 · written in Rust&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What would help me most
&lt;/h2&gt;

&lt;p&gt;Try it on a real folder and tell me where it got in your way. A policy that asks too often, a confusing screen, a tool that breaks inside the sandbox: that's exactly the feedback I'm looking for. Open an issue, or just reply here.&lt;/p&gt;

&lt;p&gt;If you find a way through one of the boundaries, please &lt;strong&gt;don't&lt;/strong&gt; post it publicly. Report it privately as &lt;a href="https://github.com/kulehq/kule/blob/main/SECURITY.md" rel="noopener noreferrer"&gt;SECURITY.md&lt;/a&gt; explains. I'll be grateful, and I'll fix it.&lt;/p&gt;

&lt;p&gt;And if Kule ends up keeping your folders safe and you'd like to help it keep going, there's &lt;a href="https://github.com/sponsors/selcukgural" rel="noopener noreferrer"&gt;GitHub Sponsors&lt;/a&gt;. That's completely optional. A star, an issue or a "this worked for me" means a lot too.&lt;/p&gt;

&lt;p&gt;Thanks for reading. 🙏&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rust</category>
      <category>opensource</category>
      <category>security</category>
    </item>
    <item>
      <title>Stop running your whole .NET test suite on every pull request</title>
      <dc:creator>Selçuk GÜRAL</dc:creator>
      <pubDate>Fri, 16 Oct 2026 18:30:36 +0000</pubDate>
      <link>https://dev.to/selcukgural/stop-running-your-whole-net-test-suite-on-every-pull-request-5606</link>
      <guid>https://dev.to/selcukgural/stop-running-your-whole-net-test-suite-on-every-pull-request-5606</guid>
      <description>&lt;p&gt;Every .NET codebase I have worked on hits the same wall sooner or later. The test suite grows, CI goes from three minutes to twenty, and every pull request pays the full price, even a one-line rename deep inside a helper class.&lt;/p&gt;

&lt;p&gt;Splitting the suite or buying bigger runners helps for a while. But most of those tests could never have noticed the change in the first place. They run anyway because nothing tells CI which ones matter.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;TestDetta&lt;/strong&gt;: a .NET tool and GitHub Action that reads your diff and runs only the test classes that can observe it. It follows one rule throughout: &lt;strong&gt;when it cannot be sure, it runs more tests, never fewer.&lt;/strong&gt; A missed test is a bug. An extra test only costs CI time.&lt;/p&gt;

&lt;p&gt;This post covers how it works, how to try it in a few minutes, and how to check that it holds up on your own code before you trust it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A real example
&lt;/h2&gt;

&lt;p&gt;Here is a real change to &lt;a href="https://github.com/open-telemetry/opentelemetry-dotnet" rel="noopener noreferrer"&gt;OpenTelemetry .NET&lt;/a&gt;: a local variable renamed inside one method. It changes no behaviour, but a normal pipeline still runs all 30 test projects and 274 test classes.&lt;/p&gt;

&lt;p&gt;TestDetta works it out like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Projects:&lt;/strong&gt; 2 of 30 test projects are affected. The other 28 are not even built.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Symbols:&lt;/strong&gt; 1 member changed, &lt;code&gt;WarnIfConfigFileEnvVarDiffers&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coverage map:&lt;/strong&gt; 8 test classes ran that method on &lt;code&gt;main&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; 8 of 274 classes run, 266 are skipped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those 8 classes are exactly the ones that fail when that method is broken. Recorded test time went from &lt;strong&gt;5 m 59 s to 22.4 s&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it in two minutes
&lt;/h2&gt;

&lt;p&gt;TestDetta is a .NET tool (it runs on the .NET 10 SDK; your projects can target older frameworks):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; TestDetta
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ask what your branch affects, and why:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;td affected &lt;span class="nt"&gt;--base&lt;/span&gt; origin/main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the &lt;code&gt;dotnet test&lt;/code&gt; commands it would run, without running anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;td &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--base&lt;/span&gt; origin/main &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And run only the affected tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;td &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--base&lt;/span&gt; origin/main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output 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;$ td test --base origin/main
TestDetta 0.2.2 · 1 changed file · 2 affected test projects
Tests to run
┌──────────────────┬──────────┬─────────┬──────────────────────────────────────────────────────────┐
│ Test project     │ Run      │ Classes │ Why                                                      │
├──────────────────┼──────────┼─────────┼──────────────────────────────────────────────────────────┤
│ tests/Shop.Tests │ filtered │  3 / 41 │ 3 of 41 test class(es) run or reference the changed code │
│ tests/Api.Tests  │ skipped  │  0 / 12 │ no test class runs or references the changed code        │
└──────────────────┴──────────┴─────────┴──────────────────────────────────────────────────────────┘
  tests/Shop.Tests → 3 classes
    Shop.Tests.CartTests
    Shop.Tests.CheckoutTests
    Shop.Tests.OrderTests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without a coverage map, TestDetta decides by tracing names through your source. For sharper selection, record a map once on your default branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;td record
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs every test with coverage and writes one map per test project to &lt;code&gt;.testdetta/coverage&lt;/code&gt; (add &lt;code&gt;.testdetta/&lt;/code&gt; to your &lt;code&gt;.gitignore&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Need machine-readable output? &lt;code&gt;td affected --format json&lt;/code&gt; gives you the selection for your own scripts, &lt;code&gt;--format paths&lt;/code&gt; lists the test projects, and &lt;code&gt;--format commands&lt;/code&gt; prints the &lt;code&gt;dotnet test&lt;/code&gt; commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it decides
&lt;/h2&gt;

&lt;p&gt;Each step narrows the selection only when it has evidence. Otherwise it falls back to something wider.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Projects.&lt;/strong&gt; Changed files are mapped to their projects and followed through &lt;code&gt;ProjectReference&lt;/code&gt;s. Build-wide files such as &lt;code&gt;Directory.Build.props&lt;/code&gt;, &lt;code&gt;global.json&lt;/code&gt; or &lt;code&gt;.editorconfig&lt;/code&gt; affect every project below them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Symbols.&lt;/strong&gt; For C#, Visual Basic and F# files, the old and new syntax trees are compared (Roslyn for C# and VB). Comment and whitespace edits select nothing. Otherwise TestDetta knows exactly which types and members changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coverage.&lt;/strong&gt; A map of which test class ran which method, recorded at the base commit, gives the test classes for changed methods, properties and constructors directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Names.&lt;/strong&gt; What coverage cannot see, such as new code, fields and constants, is traced through the source by name until it reaches test classes or code the map knows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallbacks.&lt;/strong&gt; When a change cannot be traced, the whole test project runs. That covers non-code files the build uses (&lt;code&gt;appsettings.json&lt;/code&gt;, &lt;code&gt;.csproj&lt;/code&gt;, &lt;code&gt;.resx&lt;/code&gt;), global usings, assembly attributes, and types a framework discovers through reflection (controllers, message handlers, EF configurations).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is one more safety net: if a filter matches no test at all, TestDetta treats that as its own bug and runs the project again without the filter.&lt;/p&gt;

&lt;p&gt;A few details took a lot of work to get right:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Maps don't have to be fresh.&lt;/strong&gt; The best map is one recorded at your branch's merge base. A map up to 50 commits older still works: TestDetta also selects the classes that ran anything changed in between that could reach your change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source generators are handled.&lt;/strong&gt; Generated code is read from the PDBs during recording. When a change reaches a generator in your repository, TestDetta builds the test projects that use it and compares what is generated, framework by framework.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;#if&lt;/code&gt; code is not missed.&lt;/strong&gt; C# files are parsed with each target framework's real preprocessor symbols, and with every symbol their &lt;code&gt;#if&lt;/code&gt;s name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your test configuration is respected.&lt;/strong&gt; Your own &lt;code&gt;--filter&lt;/code&gt;, run settings and runner arguments are combined with TestDetta's class filter, so it only ever narrows your runs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  In GitHub Actions
&lt;/h2&gt;

&lt;p&gt;The usual setup records a map on every push to &lt;code&gt;main&lt;/code&gt; and runs only the affected tests on pull requests:&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;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
  &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt; &lt;span class="c1"&gt;# lets pull requests find the newest coverage map in the Actions cache&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v7&lt;/span&gt; &lt;span class="c1"&gt;# pin to a commit SHA&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="c1"&gt;# TestDetta compares with the merge base&lt;/span&gt;
          &lt;span class="na"&gt;persist-credentials&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-dotnet@v6&lt;/span&gt; &lt;span class="c1"&gt;# pin to a commit SHA&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;global-json-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;global.json&lt;/span&gt;

      &lt;span class="c1"&gt;# Full test run of main, which also records the coverage map&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name == 'push'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TestDetta/action@a2f55a8252840a367f2f7a9ebd6733de3ebfb2f5&lt;/span&gt; &lt;span class="c1"&gt;# v0.2.2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;record&lt;/span&gt;

      &lt;span class="c1"&gt;# Pull requests run only what the change can affect&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name == 'pull_request'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TestDetta/action@a2f55a8252840a367f2f7a9ebd6733de3ebfb2f5&lt;/span&gt; &lt;span class="c1"&gt;# v0.2.2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
          &lt;span class="na"&gt;dotnet-test-args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;--logger trx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Maps live in the Actions cache, keyed by commit, and pull requests read the map of their merge base. Every run writes a job summary explaining what ran and why.&lt;/p&gt;

&lt;p&gt;Not on GitHub? GitLab CI, Azure DevOps, Jenkins and Bitbucket use the same two commands (&lt;code&gt;td record&lt;/code&gt; and &lt;code&gt;td test&lt;/code&gt;) with a folder of per-commit maps on a shared disk or object storage (&lt;code&gt;--maps-dir&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't trust it, check it
&lt;/h2&gt;

&lt;p&gt;A tool that decides which tests to skip has to earn your trust on your own codebase, not on a marketing page. TestDetta ships two ways to check it, and neither needs a license:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shadow mode&lt;/strong&gt; (&lt;code&gt;td test --shadow&lt;/code&gt;, or &lt;code&gt;mode: shadow&lt;/code&gt; in the action) keeps running your full suite and compares the result with what the selection would have run. When a test fails outside the selection, it reruns that class to tell a real miss from a flaky or pre-existing failure. Run it for a couple of weeks of normal pull requests and you have evidence from your own code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit&lt;/strong&gt; (&lt;code&gt;td audit&lt;/code&gt;, or &lt;code&gt;mode: audit&lt;/code&gt;) doesn't wait for real failures. It breaks your code on purpose, one member at a time with IL mutants in a copy of the working tree, runs every test that can see each change, and reports any failing class the selection left out. It also reports the 95% upper bound of the miss rate. Your sources and build output are only read.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it is tested
&lt;/h2&gt;

&lt;p&gt;I run the same check at scale on &lt;strong&gt;53 open-source .NET repositories&lt;/strong&gt;, including Polly, Newtonsoft.Json, AutoMapper, Serilog, Markdig, Refit, Autofac, NodaTime, FastEndpoints, FluentValidation and OpenTelemetry. Members are changed one at a time, and every test class that fails must be one TestDetta selected.&lt;/p&gt;

&lt;p&gt;From the latest round (2026-10-13 to 2026-10-15):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;3,071&lt;/strong&gt; changes broke at least one test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5&lt;/strong&gt; misses, each a documented limitation (more on those below). Earlier real misses were fixed, each with a regression test that fails without the fix.&lt;/li&gt;
&lt;li&gt;The median repository ran &lt;strong&gt;35%&lt;/strong&gt; of its test classes per change. In 20 of the 53 repositories it was a quarter or less, and Polly got down to 3%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To be upfront: some repositories gain much less. Mapperly ran 97% and AutoMapper 86%, because nearly every test there goes through the same central code paths. If most of your tests touch everything, test impact analysis can't save much, and the evaluation shows that honestly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;TestDetta is static and coverage-based analysis, not magic. Here is what it can't fully see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code that runs once per process&lt;/strong&gt; (static constructors, lazy singletons, EF model building) is credited to the first test class that triggers it. TestDetta traces changes to such code by name instead, but a cache you wrote by hand (a &lt;code&gt;Dictionary&lt;/code&gt; checked with &lt;code&gt;TryGetValue&lt;/code&gt;) can still hide its users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reflection and dynamic loading&lt;/strong&gt; are only partly visible. Types a framework discovers this way run their whole test project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;F# signature files&lt;/strong&gt; (&lt;code&gt;.fsi&lt;/code&gt;) are not analysed, so a change to one runs its project's dependents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests skipped while recording&lt;/strong&gt; have no coverage, for example a missing Docker daemon. Record where your tests actually run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full list is on the &lt;a href="https://testdetta.com/docs/limitations/" rel="noopener noreferrer"&gt;Limitations page&lt;/a&gt;. My recommendation: keep running the full suite on your default branch. Recording does that anyway, so anything a selection could ever hide is still caught before release.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test frameworks:&lt;/strong&gt; xUnit v2, xUnit v3, NUnit, MSTest and TUnit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runners:&lt;/strong&gt; VSTest and Microsoft.Testing.Platform (read from &lt;code&gt;global.json&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Languages:&lt;/strong&gt; C#, Visual Basic and F#&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI:&lt;/strong&gt; GitHub Actions, GitLab CI, Azure DevOps, Jenkins, Bitbucket and on-premises setups&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Privacy and licensing
&lt;/h2&gt;

&lt;p&gt;None of your code leaves your CI. Analysis, coverage maps and test results stay on your machines. The only network call is an optional license renewal, at most once a day, that sends the license and nothing else.&lt;/p&gt;

&lt;p&gt;TestDetta is &lt;strong&gt;free for public repositories on GitHub Actions and GitLab CI&lt;/strong&gt;, with every feature and no sign-up. It detects public repositories from the CI itself. Private repositories need a paid license, with a 30-day free trial that needs no card. A license problem never fails your build: without a valid license, every test simply runs, as it did before TestDetta.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Website and docs: &lt;a href="https://testdetta.com" rel="noopener noreferrer"&gt;testdetta.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;How it decides, step by step: &lt;a href="https://testdetta.com/docs/how-it-works/" rel="noopener noreferrer"&gt;testdetta.com/docs/how-it-works&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Validate it on your repository: &lt;a href="https://testdetta.com/docs/validate/" rel="noopener noreferrer"&gt;testdetta.com/docs/validate&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Action: &lt;a href="https://github.com/TestDetta/action" rel="noopener noreferrer"&gt;github.com/TestDetta/action&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NuGet: &lt;a href="https://www.nuget.org/packages/TestDetta" rel="noopener noreferrer"&gt;nuget.org/packages/TestDetta&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you try it, I would love to hear how it goes, especially if it &lt;strong&gt;misses&lt;/strong&gt; a test. A miss is the one bug I care about most. Leave a comment here or write to &lt;a href="mailto:support@testdetta.com"&gt;support@testdetta.com&lt;/a&gt;, and I'll dig in.&lt;/p&gt;

&lt;p&gt;How does your team deal with a slow .NET test suite today? I'm curious what has worked for you.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>testing</category>
      <category>devops</category>
    </item>
    <item>
      <title>Anka: Designing a Minimal HTTP Server for Native AOT, Its Architecture, and Intentional Constraints</title>
      <dc:creator>Selçuk GÜRAL</dc:creator>
      <pubDate>Fri, 01 May 2026 16:48:33 +0000</pubDate>
      <link>https://dev.to/selcukgural/anka-designing-a-minimal-http-server-for-native-aot-its-architecture-and-intentional-constraints-284o</link>
      <guid>https://dev.to/selcukgural/anka-designing-a-minimal-http-server-for-native-aot-its-architecture-and-intentional-constraints-284o</guid>
      <description>&lt;p&gt;In modern backend systems, being “fast” alone is no longer enough. How quickly your service responds to the first request, how little memory it consumes at startup, and how predictably it behaves are now just as important as raw throughput. Especially in serverless, short-lived container, edge worker, and bursty internal service scenarios, the real problem is often not steady-state throughput, but cold start performance, startup allocations, and operational simplicity.&lt;/p&gt;

&lt;p&gt;Anka approaches this problem from the opposite direction of the traditional ASP.NET Core model: no middleware pipeline, no built-in routing, no TLS, no HTTP/2. Instead, it provides a Native AOT-friendly, HTTP/1.x-focused, allocation-conscious HTTP server core built on .NET 8+. The goal is straightforward: once the process starts, begin listening on the socket within milliseconds and keep the connection/request lifecycle as deterministic as possible.&lt;/p&gt;




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

&lt;p&gt;The traditional .NET web stack is extremely powerful, but that power comes with a cost. Middleware chains, reflection-heavy surfaces, dynamic extensibility, broad protocol support, and JIT warmup all introduce significant overhead on the cold-start path. If your actual requirement is simply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Open a socket, parse HTTP/1.1, execute a handler, and write the response,”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;then the general-purpose framework model can become unnecessarily expensive.&lt;/p&gt;

&lt;p&gt;The core question Anka focuses on is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If we do not need a full-featured web framework, but only a fast-starting, low-memory, Native AOT-compatible HTTP listener, what should the minimum architecture look like?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer becomes visible in the performance characteristics as well. In measured environments, Anka reaches a time-to-ready of approximately 2.3 ms, startup allocations around 124.5 KB, and a steady-state RSS of roughly 15 MB. Kestrel, by comparison, offers a much broader feature set, but carries a heavier cold-start and memory profile. Interestingly, throughput differences are not dramatic; Anka’s real advantage is not raw requests-per-second, but startup cost and footprint.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture: Minimal Layers, Maximum Control
&lt;/h2&gt;

&lt;p&gt;Anka’s architecture is intentionally narrow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Server.StartAsync()
-&amp;gt; parallel accept loops
-&amp;gt; Connection.RunAsync() (one task per TCP connection)
-&amp;gt; SocketReceiver.ReceiveAsync()
-&amp;gt; HttpParser
-&amp;gt; RequestHandler(request, response, ct)
-&amp;gt; HttpResponseWriter.WriteAsync()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important aspect of this flow is that there is very little abstraction. Request processing starts directly at the socket layer and ends directly at the socket layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Server: The Acceptance Layer
&lt;/h2&gt;

&lt;p&gt;The server opens the listening socket, configures the backlog, and runs multiple parallel accept loops instead of a single accept loop. This may sound like a small detail, but under burst traffic it prevents a single accept loop from serializing connection acceptance.&lt;/p&gt;

&lt;p&gt;Additionally, the minimum thread pool size is increased to reduce thread injection latency during initial load. This may look like benchmark tuning, but in reality it is an operational decision intended to make cold-path behavior more deterministic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Connection: The Real Unit of Execution
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;Connection&lt;/code&gt; task is created for each TCP connection. This object:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rents a pooled receive buffer,&lt;/li&gt;
&lt;li&gt;acquires a pooled &lt;code&gt;HttpRequest&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;creates a connection-scoped &lt;code&gt;HttpResponseWriter&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;reads and parses data from the socket,&lt;/li&gt;
&lt;li&gt;invokes the request handler,&lt;/li&gt;
&lt;li&gt;and if keep-alive is enabled, continues with the next request using the same resources.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The critical idea here is resource ownership at the connection level rather than the request level. This minimizes repetitive buffer rental and return operations across keep-alive requests.&lt;/p&gt;




&lt;h2&gt;
  
  
  SocketReceiver: Async I/O with Allocation Discipline
&lt;/h2&gt;

&lt;p&gt;Instead of using the more convenient &lt;code&gt;Socket.ReceiveAsync(Memory)&lt;/code&gt; APIs, Anka uses &lt;code&gt;SocketAsyncEventArgs&lt;/code&gt; together with &lt;code&gt;IValueTaskSource&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is not micro-optimization for its own sake. The goal is to avoid state machine overhead and additional allocations when socket reads complete synchronously.&lt;/p&gt;

&lt;p&gt;In loopback or LAN scenarios, data is often already available in the kernel buffer. In those cases, the receive operation completes synchronously and the system proceeds with almost direct function-call overhead. If data is not available, the OS I/O completion thread finishes the operation, while continuations are posted back to the thread pool using &lt;code&gt;RunContinuationsAsynchronously = true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The result: I/O threads are not blocked by request-processing work.&lt;/p&gt;




&lt;h2&gt;
  
  
  HttpParser: Single-Pass Parsing
&lt;/h2&gt;

&lt;p&gt;The parser’s real value is not merely that it parses HTTP, but that it does so in a single pass using pooled buffers.&lt;/p&gt;

&lt;p&gt;It performs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;request line parsing,&lt;/li&gt;
&lt;li&gt;path/query separation,&lt;/li&gt;
&lt;li&gt;lowercase normalization of header names during ingestion,&lt;/li&gt;
&lt;li&gt;inline &lt;code&gt;Content-Length&lt;/code&gt; tracking,&lt;/li&gt;
&lt;li&gt;request body reading when necessary,&lt;/li&gt;
&lt;li&gt;chunked request body decoding and reassembly into &lt;code&gt;req.Body&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;keep-alive decision calculation during parsing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach provides two important benefits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No second header scan is required.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;HttpRequest&lt;/code&gt; passed to the handler is already in a usable, near zero-copy state.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  HttpResponseWriter: Not a Framework, Just Wire Format
&lt;/h2&gt;

&lt;p&gt;The response layer follows the same philosophy. &lt;code&gt;HttpResponseWriter&lt;/code&gt; constructs the HTTP response header block inside a pooled buffer, inlines small bodies into the same buffer whenever possible, and writes directly through &lt;code&gt;Socket.SendAsync()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Another important design choice here is the absence of chunked response encoding. All responses use &lt;code&gt;Content-Length&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This constraint simplifies the response pipeline significantly and narrows the execution path for AOT and low-allocation goals.&lt;/p&gt;




&lt;h2&gt;
  
  
  Core Design Decisions
&lt;/h2&gt;

&lt;h2&gt;
  
  
  A Single RequestHandler Delegate
&lt;/h2&gt;

&lt;p&gt;Anka has no middleware chain. There is only one &lt;code&gt;RequestHandler&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is a deliberate form of radical simplification.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;shorter call chains,&lt;/li&gt;
&lt;li&gt;fewer abstraction layers,&lt;/li&gt;
&lt;li&gt;lower dispatch overhead,&lt;/li&gt;
&lt;li&gt;a cleaner surface for Native AOT.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Costs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;cross-cutting concerns must be implemented manually,&lt;/li&gt;
&lt;li&gt;routing, authentication, logging, and metrics are not framework-managed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not an “incomplete framework”; it is a primitive-oriented design.&lt;/p&gt;




&lt;h2&gt;
  
  
  No Built-in Routing
&lt;/h2&gt;

&lt;p&gt;Path dispatching is left entirely to user code. A simple &lt;code&gt;switch&lt;/code&gt; statement is considered sufficient for many scenarios.&lt;/p&gt;

&lt;p&gt;At first glance this may appear primitive, but routing costs are not limited to path matching itself. Routing systems also bring metadata graphs, endpoint conventions, and large API surfaces. Anka rejects that cost upfront.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lowercasing Header Names During Ingestion
&lt;/h2&gt;

&lt;p&gt;Instead of performing case-insensitive lookups repeatedly, header names are normalized during parsing. This allows lookups to use straightforward &lt;code&gt;SequenceEqual&lt;/code&gt; comparisons.&lt;/p&gt;

&lt;p&gt;It may seem like a small design detail, but on the hot path it reduces both branching and processing overhead.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reusing HttpRequest and Buffers Across Connections
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;HttpRequest.ResetForReuse()&lt;/code&gt; approach avoids discarding request objects after every request. The same object is reused throughout the connection lifetime.&lt;/p&gt;

&lt;p&gt;This becomes especially effective under keep-alive workloads, because steady-state allocation reductions often come not from huge optimizations, but from many small, systematic reuse decisions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Small CAS-Based Request Pool
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;HttpRequestPool&lt;/code&gt; is lock-free and intentionally small.&lt;/p&gt;

&lt;p&gt;The goal is not theoretical “infinite pooling,” but a practical, cache-friendly, low-overhead reuse mechanism. If all slots are occupied, objects are simply discarded.&lt;/p&gt;

&lt;p&gt;This is another important engineering decision: trying to pool everything can sometimes become more expensive than not pooling at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  Trade-offs: What This Architecture Gains — and Intentionally Gives Up
&lt;/h2&gt;

&lt;p&gt;One of Anka’s strengths is that it clearly communicates what it is not.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;th&gt;Benefit&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No middleware&lt;/td&gt;
&lt;td&gt;Shorter hot path, fewer abstractions&lt;/td&gt;
&lt;td&gt;Developers handle cross-cutting concerns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No built-in routing&lt;/td&gt;
&lt;td&gt;Lower startup cost, simpler model&lt;/td&gt;
&lt;td&gt;Reduced ergonomics in large APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP/1.x only&lt;/td&gt;
&lt;td&gt;Narrower execution path, simpler AOT surface&lt;/td&gt;
&lt;td&gt;No modern protocol features&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No built-in TLS&lt;/td&gt;
&lt;td&gt;Keeps the core minimal&lt;/td&gt;
&lt;td&gt;Reverse proxy becomes mandatory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No chunked responses&lt;/td&gt;
&lt;td&gt;Simpler response writer&lt;/td&gt;
&lt;td&gt;Limited streaming ergonomics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raw socket approach&lt;/td&gt;
&lt;td&gt;Full control over allocations and behavior&lt;/td&gt;
&lt;td&gt;Higher maintenance burden&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The most important trade-off is this:&lt;/p&gt;

&lt;p&gt;Anka does not exist to replace Kestrel. It exists to occupy a different optimization point.&lt;/p&gt;

&lt;p&gt;If you need HTTP/2, TLS termination, middleware ecosystems, advanced hosting integrations, or rich observability plugins, then Kestrel is probably still the correct answer.&lt;/p&gt;

&lt;p&gt;But if your priority is low footprint, rapid readiness, and a highly controlled request path, Anka becomes compelling.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Strongest Part of This Design: Predictability, Not Performance
&lt;/h2&gt;

&lt;p&gt;In my opinion, the real value here is not just “0 B allocations” or “2.3 ms startup.”&lt;/p&gt;

&lt;p&gt;The real value is how easy it becomes to mentally model the system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one task per connection,&lt;/li&gt;
&lt;li&gt;connection-scoped buffers,&lt;/li&gt;
&lt;li&gt;parse → handle → write pipeline,&lt;/li&gt;
&lt;li&gt;very few failure states,&lt;/li&gt;
&lt;li&gt;limited protocol surface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Architecturally, systems like this become valuable not because of benchmark presentations, but because of operational clarity. You know exactly what the system does, what it does not do, where behavior changes under load, and where costs are paid.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example Use Cases
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Serverless or Short-Lived Container APIs
&lt;/h2&gt;

&lt;p&gt;If cold start is critical and the endpoint surface is small, Anka’s minimal startup overhead becomes a major advantage.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sidecar / Internal Control Plane Endpoints
&lt;/h2&gt;

&lt;p&gt;For health checks, readiness probes, metrics gateways, or simple control APIs, a full framework is often unnecessary.&lt;/p&gt;




&lt;h2&gt;
  
  
  Edge Compute and Appliance Deployments
&lt;/h2&gt;

&lt;p&gt;Low RSS and Native AOT compatibility matter significantly in resource-constrained environments.&lt;/p&gt;




&lt;h2&gt;
  
  
  Benchmarking and Protocol Research
&lt;/h2&gt;

&lt;p&gt;Anka provides a clean environment for teams that want to observe HTTP pipeline behavior without framework noise.&lt;/p&gt;




&lt;h2&gt;
  
  
  Narrowly Scoped, High-Control Services
&lt;/h2&gt;

&lt;p&gt;It is well suited for single-purpose webhook receivers, internal callback endpoints, or lightweight ingestion services.&lt;/p&gt;




&lt;h2&gt;
  
  
  When I Would Not Use It
&lt;/h2&gt;

&lt;p&gt;Personally, I would avoid this architecture for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;large general-purpose product APIs developed by big teams,&lt;/li&gt;
&lt;li&gt;applications requiring rich middleware ecosystems,&lt;/li&gt;
&lt;li&gt;systems where streaming, TLS, or HTTP/2 are first-class requirements,&lt;/li&gt;
&lt;li&gt;organizations where framework integrations matter more than low-level control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because what you gain here is not “freedom.”&lt;/p&gt;

&lt;p&gt;It is control.&lt;/p&gt;

&lt;p&gt;And control always comes with increased engineering responsibility.&lt;/p&gt;




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

&lt;p&gt;The most accurate technical summary of Anka is this:&lt;/p&gt;

&lt;p&gt;It is not an HTTP server that gains performance by adding optimizations on top of features; it gains simplicity and predictability by deliberately reducing scope.&lt;/p&gt;

&lt;p&gt;Native AOT compatibility, low startup allocations, connection-scoped resource management, single-pass parsing, and direct socket writing all serve the same goal:&lt;/p&gt;

&lt;p&gt;a small, fast-starting, understandable request pipeline.&lt;/p&gt;

&lt;p&gt;This approach is not for everyone — and it is not trying to be.&lt;/p&gt;

&lt;p&gt;From an experienced backend engineering perspective, its most mature characteristic is precisely this: it defines the problem narrowly, optimizes aggressively for that problem, and consciously leaves everything else outside the boundary.&lt;/p&gt;

&lt;p&gt;And often, that is a far more valuable engineering decision than building a system that tries to do everything.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Originally published in Turkish on my personal blog:&lt;br&gt;&lt;br&gt;
&lt;a href="https://selcukgural.com/2026/04/17/anka-native-aot-icin-minimal-http-sunucusu-tasarlamak-ankanin-mimarisi-ve-bilincli-kisitlari/" rel="noopener noreferrer"&gt;https://selcukgural.com/2026/04/17/anka-native-aot-icin-minimal-http-sunucusu-tasarlamak-ankanin-mimarisi-ve-bilincli-kisitlari/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>anka</category>
      <category>kestrel</category>
      <category>httpframework</category>
      <category>aot</category>
    </item>
  </channel>
</rss>
