Skip to main content

Ensemblr

Agents write the code, then drive the app.

A desktop orchestrator for the Pi agent harness or the Claude Code CLI you already have installed. I build it, and I use it to build everything else, including this site.

or brew install --cask ensemblr-hq/tap/ensemblr

Platform
macOS (Apple silicon) · Linux (x86-64)
License
Apache-2.0
Agent runtimes
Pi · Claude Code
Languages
English · Russian · Greek

What it is

Ensemblr drives a coding-agent CLI you installed yourself, either the Pi harness or the Claude Code CLI. Either one gets the same chat, with a shared timeline, tool cards, model and thinking pickers, tool-approval prompts, git-backed checkpoints and session branching, and you only need one of them installed. The app ships no agent binary of its own. I left the Agent SDK out on purpose, and that keeps about 260 MB out of the app.

You do not make an account, and nothing syncs off the machine. State lives in a local SQLite database, through the node:sqlite module built into Node 24. Secrets go to the OS keyring (the macOS Keychain, or gnome-keyring or KWallet on Linux) and never to a file or an environment variable. GitHub tokens stay with gh, so there is no second copy to leak. There is no Ensemblr backend in the path, and there is no telemetry.

An Ensemblr workspace: a coding agent mid-turn with its tool calls, the file tree, and the terminal dock running a dev server

A worktree for every workspace

Each stream of work is a workspace: a git worktree with its own branch, working tree, agent sessions, run state and review path. No two agents share a checkout, so several of them can work at once without colliding. A workspace either adopts an existing branch or cuts a new one. Either way, Ensemblr fetches and fast-forwards the base branch first, and retargeting the workspace later leaves its worktree alone.

A workspace can start from an existing branch, a GitHub pull request or a Linear issue, and it sits on a board with five columns: Backlog, In progress, In review, Done and Canceled. Backlog is for work that has no workspace yet, meaning unstarted Linear issues and unassigned open GitHub issues. Drag one to the right and it becomes a workspace. Ensemblr writes nothing back to the tracker. Dismissing an issue only hides it on your machine, and its status is still yours to change.

Agents drive the app

Ensemblr Control lets an agent drive the app it is running inside, and every call it makes goes through a permission check. An agent can spawn conversations, launch harnesses, run terminals, open file and diff tabs, read the workspace diff and leave review comments on it, read and write Linear issues, ask a multiple-choice question and move its own workspace across the board. Pi reaches all of this through an extension that ships with the app. Claude Code, or any harness that speaks MCP, reaches the same operations through an embedded MCP server. Both transports call one set of operations, so they cannot drift apart.

The part I care about most is orchestration. A root agent hands one unit of work to each sub-agent, each in its own tab with its own context, and then blocks until they report back instead of hand-rolling a polling loop. Delegation goes two levels deep at most: a root can hand work to a manager, the manager can hand it on to fresh sub-agents of its own, and those cannot delegate at all. Depth, spawn count and spawn rate all have hard caps.

Those limits are enforced in code, where an agent cannot argue with them. Sub-agents cannot write to Linear, and no agent at any depth can move an issue to a completed or canceled state. Agent work stops at In Review, and a person decides whether it is done. Plan mode keeps an agent on read-only tools until it submits a plan. The check runs on every tool call, and every sub-agent the agent spawns inherits the mode.

Review that ends in GitHub

Review happens on your machine, in one panel with Files, Changes and Checks. It has diffs scoped by source, per-file discard, a live file tree and review comments pinned to specific lines. Agents read those comments, reply to them and resolve them, and they are the same comments a person leaves. When the change is ready, you write the pull request in an inline editor, commit and push, follow each check's status through gh, and merge in two steps. If the work does not survive review, its workspace gets archived instead.

Terminals run underneath all of it, in an xterm.js dock over real PTYs that comes back after a restart. A repository can declare as many named run scripts as it likes in a committed .ensemblr/settings.toml, each with a command and an icon, and one of them is the ⌘R default. Setup and archive scripts run on the same lifecycle. Setup is fingerprinted, so a workspace where nothing has changed skips it.

What it took

Ensemblr is built on Electron 43 and Electron Forge, with Vite and Fuses hardening, and the interface is React 19 in strict TypeScript. TanStack Router and Query handle routing and data, Jotai holds state, and the composer is built on Lexical. The terminal dock is xterm.js over node-pty. Zod checks data at every boundary, and SQLite runs through node:sqlite. Agent control runs on loopback HTTP plus MCP, and the tests are Vitest and electron --test.

The repository holds more than seventy architecture decision records. A codebase this opinionated is only navigable if the reasoning is written down, both for the people reading it and for the agents. The app ships in English, Russian and Greek, and any change that adds a user-facing string has to ship its translations in the same change.

Distribution is the unglamorous half. On macOS that means Developer ID signing, the hardened runtime and Apple notarization with stapling, so the build opens without a Gatekeeper prompt and validates offline. It ships as a Homebrew cask and a signed DMG. On Linux it is a single statically linked AppImage that does not need libfuse2 on the host.

Stack

The full feature tour and the current download are on ensemblr.dev.