DEV Community

Cover image for downbeat: stop copy-pasting between your AI terminals
Nazarii Ahapevych
Nazarii Ahapevych

Posted on

downbeat: stop copy-pasting between your AI terminals

Context

I run more than one AI coding session at a time (Claude Code, in my case). One is usually the planner: it holds the architecture, decides what to do, splits the work. The others are executors, and each takes a chunk and grinds through it.

The problem is that these sessions cannot talk to each other. Every terminal is its own island, so I became the message bus. I copied a plan out of the planning session and pasted it into an executor. I copied the result back. I re-explained context that one session had and another needed. Every handoff went through me, by hand, one clipboard trip at a time.

TL;DR: downbeat is a small, local, human-in-the-loop message bus that lets parallel AI coding sessions on one machine hand tasks to each other and read the replies back. No cloud, no account, no network. It is on PyPI now: uv tool install downbeat.

I was the message bus, and it fell apart at four sessions

Two sessions and a clipboard works fine. Four sessions is chaos. I lost track of which executor had which version of the plan, I pasted stale context, and I spent more attention shuttling text between terminals than on the actual work.

I wanted the sessions to pass messages directly, on my machine, with me still in the loop but no longer doing the typing. No cloud service, nothing leaving the terminal.

What downbeat is

downbeat is a local, filesystem-backed message broker for AI coding sessions running on the same machine. A CLI, a terminal UI, and a bundled agent skill. No server, no account, no network.

The model is simple:

  • Each session registers as a named peer (a "parent" planner, a "child" executor, whatever role you give it).
  • One session sends a message to another: a task, a plan, a chunk of context, a result.
  • The recipient sees that message surfaced at the start of its next turn, through a hook. It just appears.
  • Replies flow back the same way, so a child can report "done" to its parent without you touching the clipboard.

The part I care about most: nothing auto-executes. Every watcher notifies you, nothing runs on the parent side on its own, and a child only acts because you told it to at registration time. Human-in-the-loop is the default you would have to work to turn off.

Underneath, a message is a JSON file in the recipient's inbox directory. That is the whole transport. It turns out you can build a surprisingly capable little queue out of a directory and a hook.

Install

The whole runtime installs in two commands:

uv tool install downbeat     # or: pipx install downbeat
downbeat init                # one command installs the WHOLE runtime
Enter fullscreen mode Exit fullscreen mode

downbeat init is the single source of truth for the setup. It bootstraps the data dirs, installs the agent skill, drops the bundled hooks and slash commands, and registers the hooks in your settings idempotently (backed up, atomic, and it never clobbers hooks you already have). It is safe to re-run.

Then the loop itself:

downbeat register parent --role parent
downbeat register child  --role child
downbeat send child "task" "do the thing"
downbeat inbox --peer child
downbeat reply <msg_id> "done"
downbeat tui                 # full management UI
Enter fullscreen mode Exit fullscreen mode

If you want to see it before installing anything, the repo has a five-command walkthrough in examples/parent-child-handoff/ with a recorded demo.

Why I built it this way

Two constraints shaped it.

It had to be local and dependency-free. My sessions already run on one laptop. Reaching for Kafka or a hosted queue to pass a sentence between two terminals is absurd. The filesystem is already there, it is already durable, and a rename is already atomic.

It had to be reliable enough to trust. Early on it was fire-and-forget, and it quietly lost messages: a file got marked handled the instant it was read, even if the session never acted on it. So it grew a proper delivery state machine (delivered, then acknowledged, with anything unconfirmed re-queued instead of dropped), a TUI to watch the traffic, session identity that survives a /clear, and an event-driven file watcher so it reacts instantly instead of polling.

None of that was planned up front. It started as a hack to stop copy-pasting, and each rough edge I hit in daily use turned into the next piece.

Who it might help

If you drive more than one AI coding session at once, or you have ever found yourself relaying instructions between terminals by hand, this is for that. It is deliberately small and single-machine. It does one thing: let sessions on your box hand work to each other and report back, with you still holding the wheel.

I am open-sourcing it because it became genuinely useful to me and it might help someone with the same workflow. It is early and opinionated. Issues, ideas, and "why on earth did you do it this way" questions are all welcome.

What's next

The interesting parts to build were the ones I did not expect. Over the next few posts I want to dig into a handful of them:

  • the two-phase delivery that stopped the silent message loss, and the backlog it exposed
  • an event-driven file watcher I had already written months earlier and completely forgotten, until two separate agents each reinvented polling next to it
  • giving a process a stable identity that survives /clear

If any of that sounds familiar from your own tools, I would like to hear how you solved it.

Top comments (0)