Hi, I'm Selçuk. I've been building an open-source tool called Kule, 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.
The short version: 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 ACP) 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.
("Kule" means tower in Turkish. Think control tower.)
The problem I kept running into
Coding agents are good now, good enough that we let them run commands. When an agent runs a command, it runs as you: your files, your ~/.ssh and ~/.aws, your ~/.zshrc, your network, your API keys.
Most agents ask before they run something, but they ask about the command text: "Run npm install?" 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.
I wanted something else: let the agent work freely, somewhere nothing it does is real until I've seen it.
The loop
Kule has one loop, in the CLI and in the desktop app:
make a task → give the agent a prompt → watch → review → commit or roll back.
Here it is without an agent, on a scratch folder:
mkdir -p ~/kule-demo && echo "hello" > ~/kule-demo/notes.md
kule new ~/kule-demo # prints a task id, e.g. 1a2b3c4d
kule exec 1a2b3c4d -- sh -c 'echo "more" >> notes.md' # runs in the copy, in the sandbox
kule status 1a2b3c4d # what changed, and the policy's verdict
kule commit 1a2b3c4d # apply it, keeping a checkpoint
kule undo 1a2b3c4d # put the folder back
And with an agent:
kule run 1a2b3c4d --agent claude-code "Turn notes.md into a checklist"
kule steps 1a2b3c4d # every tool call the agent made, and what it changed
kule commit 1a2b3c4d # or: kule rollback 1a2b3c4d
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.
What's actually between the agent and your stuff
Your folder. 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 kule undo really puts things back. The history of commits and undos is signed and hash-chained.
Your computer. 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. ~/.ssh, ~/.aws, 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".
What gets in. This is the part I care about most. Kule doesn't judge the command; it judges the effects. After a run, it compares the copy with your folder: files created, modified, deleted or moved, git branches moved, hooks added. Then a Cedar policy gives each effect a verdict: pass, ask or deny.
With the default ("balanced") policy:
- ordinary edits to source, notes, documents and data pass;
- manifests and lockfiles, CI files, editor settings, agent instructions (
CLAUDE.md,AGENTS.md,.mcp.json…), git config changes, history rewrites and mass deletes wait for you; - git hooks are denied, because they would run on your next
gitcommand, as you.
So a harmless-looking command that rewrites package.json still waits for you, and a scary-looking rm -rf that only touches the copy changes nothing real. When something waits, Kule tells you which rule stopped it:
policy: waits for approval
package.json · waits for approval (manifests-and-lockfiles)
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.
Your network. 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.
Your secrets. 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.
Your home. A write to ~/.zshrc, ~/.gitconfig or ~/.config lands in the task's home layer with a diff for you to review. It doesn't touch your real home.
Your wallet and your machine. 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.
Not only for code
Kule works on any folder. The review shows what changed inside 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.
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.
The honest part: what it can't do (yet)
I'd rather you hear this from me than find out after an hour of setup:
-
It's a preview (
0.2.0-preview.1). Things will change between previews. - macOS 13+ and Linux x86_64 only. Windows is planned, not started.
-
The macOS app isn't notarized. It's signed ad hoc, so Gatekeeper will complain the first time. Every release file comes with
SHA256SUMS, a build provenance attestation and an SBOM, and the install guide shows how to check them and open the app. - Linux has some requirements. 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.
- The proxy doesn't do HTTP/2 or WebSockets yet. Clients fall back to HTTP/1.1. A few tools on macOS have certificate quirks; the Platforms page lists them all.
- Kule doesn't make the agent right. It makes the agent's mistakes cheap and visible. You still review, and Kule's job is to make that review short and honest.
- It's built by one person. 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.
Try it
-
Download: releases page (universal
.dmg,.deb,.rpm, and the CLI alone as.tar.gz) - Or build from source:
git clone https://github.com/kulehq/kule && cd kule
cargo build --release -p kule -p kule-desktop
- Docs (English and Turkish), with a quick start, a page for every command and a three-minute video tour: kulehq.github.io/kule
- Code: github.com/kulehq/kule · Apache-2.0 · written in Rust
What would help me most
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.
If you find a way through one of the boundaries, please don't post it publicly. Report it privately as SECURITY.md explains. I'll be grateful, and I'll fix it.
And if Kule ends up keeping your folders safe and you'd like to help it keep going, there's GitHub Sponsors. That's completely optional. A star, an issue or a "this worked for me" means a lot too.
Thanks for reading. 🙏
Top comments (3)
Judging effects instead of command text is the right call. The
npm installprompt tells you nothing about what the postinstall does, and the copy-on-write diff does.One question from the other side of this problem. We build Auten, an MCP server that lets an agent click and type on a real screen, so most of what our tools do never shows up as a file change: clicking Send in a mail app, submitting a form, deleting a row in a web app. There is nothing to diff and nothing to roll back.
How does Kule treat the agent's MCP servers? If Claude Code launches a stdio server inside the task sandbox, Seatbelt will probably block the display and accessibility APIs. If the server runs outside it, its side effects skip the review entirely. Is there a policy hook at the tool-call level for that case, or is "anything that isn't a file or a connection" out of scope for now?
What we ended up doing for screen actions: our plugin ships a skill that tells the agent to stop and ask the user before anything irreversible (buy, delete, send, permission dialogs, passwords), and to treat text read off the screen as data, never as instructions. It helps, but it's an instruction to the agent, not a boundary like yours. I'm curious whether you see that layer as yours or as the tool's job.
Thanks, good question. It's right at the edge of what Kule does.
Where MCP servers run: Kule doesn't start them, the agent does. So they run inside the task's sandbox and behind its proxy, with the same rights as the agent. On macOS that sandbox keeps commands away from the window server, Apple events, the clipboard and the privacy-permission service. So, as you guessed, a server like Auten would fail in a Kule task. That's on purpose. There's no "run this server outside" switch either. The agent can't add one itself: inside a task it can read its MCP settings but not change them. A server offered by an extension has to name one exact package version, waits for your approval, and still runs inside the sandbox.
Policy at the tool-call level: No, and that's by design rather than "not yet". Kule sees every tool call through ACP and marks a step for each, so the timeline shows which call changed what. But it doesn't judge them. A tool call is the agent describing what it's about to do, and Kule doesn't trust that description. It judges what actually happened instead: files at commit, connections at the proxy. So a "send" that goes over an HTTP API is covered, but only per host: the policy asks whether the task may reach that host, not what the request says.
The line Kule draws: It supports only effects it can hold back until you decide. A file change waits for commit. A connection waits at the proxy. A click on Send can't wait. There's no copy of it, no diff and no undo, so there's nothing for you to review before it reaches the world. Gating at the tool-call level wouldn't fix that: it would mean trusting either the agent's account of its own action or the tool's, and Kule is built to trust neither. So screen actions stay refused inside a Kule task, and Kule says plainly that they're outside what it protects. The worst outcome would be someone running a screen tool next to Kule and feeling safe because Kule is on.
Whose layer: For screen actions it's the tool's. Only the tool knows that this click is Send and not Cancel. Your skill is a good start. But the agent is also reading the screen, and that's exactly where injected text comes from, so a rule the agent follows can be talked out of. What would make it a boundary: the server itself pauses before an irreversible action and asks the person directly, in its own window or a system dialog. Not through the agent, so the agent can't answer for them. And make it deny-by-default: a short list of actions that may run without asking, rather than a list of dangerous ones.
Thanks for the long answer, that's a clear line and I think it's the right one. Refusing screen tools outright beats offering a gate that looks like protection but can only trust the agent's description of the click.
Your point about the skill is fair and I won't argue it: text on the screen is exactly where an injection would come from, and a rule the agent follows can be talked out of. The one part we do enforce below the agent today is passwords: they're typed locally from the user's machine, so the model never sees the value and can't be talked into pasting it somewhere else. Confirmation before Send is still agent-level for us.
The deny-by-default framing is the useful bit for me. A short allowlist of actions that run without asking is much easier to reason about than trying to recognise every dangerous button, and asking the person directly, outside the agent, is the only version an injected page can't answer for them. Noted, and thanks.