Eduardo Goes

ekky

A daemon that runs my coding tasks in the background, one git worktree each, and leaves me branches to review.

status
active
stack
Go / Svelte 5 / Claude Code / systemd
topics
software-engineering / ai

ekky is the harness I use to hand coding tasks to an agent without watching it work. It takes a list of task records, runs a headless Claude Code session on each one in its own git worktree, and leaves behind a local branch and a short result card. Nothing pings me, and nothing leaves my machine until I say so. The goal is to sit down and find five branches waiting for review instead of five terminals waiting for me.

Why I built it, and what I gave up on along the way, is in the post (in Portuguese). This page is the other half: what it is and how it’s put together.

From a direction to a branch

flowchart LR
	accTitle: The ekky pipeline
	accDescr: A decided direction, by me; decisions and tasks, by me with Claude; the drain, by ekky, one worktree per task; review and promote, by me.
	dir["A decided direction<span class='who'>me</span>"]:::eu
	cut["Decisions + tasks<span class='who'>me + Claude</span>"]:::par
	run["The drain<span class='who'>ekky, one worktree per task</span>"]
	rev["Review, then promote<span class='who'>me</span>"]:::eu
	dir --> cut --> run --> rev

Everything starts with a direction I’ve already decided on: an RFC, a design doc, a page of notes. ekky:stage grills me on it, hunting for every place a headless agent would otherwise have to guess, then writes it down with two sections at the end: ## Decisions and ## Tasks, one task per future PR. ekky:decompose then turns those sections into one task.md per task: what done looks like, the decisions already made, and the one thing the agent must not do. A task that builds on another says so with after:.

ekky tackle queues the tasks and starts the drain, which runs the ready ones a few at a time. Later, ekky:review shows me the result card and the diff of the next finished task, and I accept it or fix it by hand. ekky:promote renames the branches, pushes them and opens the PRs. It’s the only step that touches anything outside my machine.

How it’s put together

The dispatcher is a single Go binary, standard library only. The drain is a oneshot systemd user service that loops until nothing is ready. Each attempt is a claude process in its own process group, with the task record on stdin, a system prompt that makes it write its own spec before touching code, a 90-minute wall clock and a dollar budget.

There’s no database. State is a directory with one folder per task: task.md, which only I edit, a status file holding one word, the agent’s result.json and its log. Every command rebuilds its view from those files on every call, so when something breaks I can find out what happened with cat.

The agent owns nothing but its commits. It can’t push: it has no GitHub credentials, its push URL points at /nonexistent, a pre-push hook sits behind that, and it runs without MCP servers. It doesn’t ask questions either. When the record leaves something open, it picks the option it would bet on, builds it, and writes down what it rejected and why. It may only stop and ask when the answer would throw away more than half the work. A failed attempt is never resumed: the next one starts cold, with the tail of the previous log appended to the record.

I sit in three places: the decisions before the cut, the review of each diff, and the promote. A fix I make by hand moves only its own branch, and review rebases the next task onto it when I open that one, one hop at a time. A read-only web view on localhost shows the backlog grouped by who each task is waiting on: me, the machine, or nobody.

An example

This blog’s English/Portuguese split went through ekky. One RFC became five chained tasks, ekky/i18n-01 through ekky/i18n-05, each one cut from the previous one’s branch. The agent’s side took about 26 minutes across all five. My side was reading the diffs.

What’s rough

  • It assumes Linux, systemd, the Claude Code CLI and the way I happen to work. The code is published to be read, not installed, and there’s no license.
  • Dependencies are chains, not graphs. A task can wait on exactly one other task.
  • If a review comment changes the RFC itself and invalidates branches that already exist, there’s no rework path. I’ll build one when that actually happens.
  • The agent only gets the built-in tools: no LSP, no browser. On UI work, checking that the thing looks right is still my job.
  • Hand fixes get logged in the task record, but turning them into better guidelines for the next run is something I still do by hand, when I remember to.
© 2026