A multi-agent coding environment. ace drives the coding-agent CLIs you already have installed (Claude Code, Codex, OpenCode, Cursor, Antigravity, Pi) and gives them one interface on desktop, in the browser and on your phone.
Status: early development. The local daemon can store events and serve snapshots and replay over WebSocket. Provider execution and clients are still to come.
- Daemon runs on the machine that has your code and your logged-in agent CLIs. It owns every agent process, the event log, git worktrees and terminals.
- Clients (desktop app, web app, mobile app) are views over the daemon's event log. They can connect locally or from another device.
- Protocol: every provider is translated into one canonical model. Agents form a tree, status is derived from the whole tree, and tool calls, approvals and background tasks are typed. See docs/adr and docs/research/providers.
Each agent CLI owns its credentials. ace can start its login or hand a pasted API key directly to its supported credential mechanism over stdin; ace never saves the key. See the authentication policy.
| Path | Purpose |
|---|---|
apps/relay |
Self-hosted WebSocket relay with encrypted outbound host/client streams |
packages/secure-channel |
Portable Noise XX handshake, ordered transport and static identity |
apps/daemon |
Local SQLite event store and authenticated WebSocket server |
apps/ios |
Native SwiftUI/JavaScriptCore spike; Phase 1 held by ADR 0074 |
apps/web |
React web client; the same bundle runs in the desktop app |
apps/desktop |
Electron shell: daemon supervision, notifications, in-app browser |
packages/projection |
Pure shared event folds for thread and sidebar views |
packages/git |
Local Git worktrees, checkpoints, diffs and safe restore |
packages/orchestrator |
Multi-provider fan-out, races, pipelines and coordinator lanes |
packages/review |
Local diff comments, persistent anchors and structured agent fixes |
packages/protocol |
Canonical protocol: Zod schemas and types, no runtime logic |
packages/agent-registry |
Official ACP catalog, approved local installations and source profiles |
packages/provider-kit |
Supervised provider processes, JSON-RPC, SSE and local CLI discovery |
packages/workspace |
Workspace file listing, reads, search and change subscriptions |
packages/files |
Streamed transfers, atomic workspace mutations, trash and artifacts |
tools/recorder |
Records raw provider sessions as fixtures for adapter contract tests |
tools/dev |
The development process runner behind bun run dev |
docs/adr |
Architecture decision records |
docs/research |
Primary-source research behind the decisions |
The agent browser and computer tool contract documents scoped MCP calls, navigation waits and control handback.
The source-control draft workflow documents editable commit and PR text generated through the user's own CLI, source revisions and cancellation.
The headless CLI exposes thread and delegated-task control.
Agents can publish HTML visualizations through dedicated MCP tools. Pages replay with the thread and run in an offline sandbox. Claude Code and Codex sessions receive the bundled visualize skill.
The generated protocol reference covers WebSocket messages, canonical events and built-in MCP tools. Regenerate it with bun run docs:protocol.
- Bun 1.4.4+ (package manager, script runner and the daemon runtime) and Node 26+ (release builds and tooling).
- macOS: the Xcode Command Line Tools (
xcode-select --install) for git,ccandswiftc(native addons and the screen helper). Full Xcode only for the iOS Simulator. - Optional: the Android SDK (
ANDROID_HOME) for emulators; Rust (cargo) to build the Windows or Linux screen helper: Rust 1.87+ for Linux (rust-versioninnative/screen-helper-linux/Cargo.toml;rustup update stable), 1.82+ for Windows. - The provider CLIs you want to use, installed and logged in on their own (
claude,codex,opencode,agent, …). the CLI owns credential storage.
bun install
bun run check # merge gate only: includes testsEvery development command runs its processes side by side with prefixed logs (daemon │,
web │, desktop │). Ctrl-C stops all of them; a second Ctrl-C kills them.
| Command | What runs |
|---|---|
bun run dev |
The daemon in watch mode, the web Vite server, and Electron on the Vite URL (renderer HMR; main restarts) |
bun run dev:web |
The daemon and the web app in your browser; prints a #token= link for the dev daemon |
bun run dev:fake |
The web app against the in-page fake daemon. No daemon, no providers |
bun run dev:desktop:fake |
Electron against the fake daemon |
bun run daemon |
Only the daemon, in watch mode |
bun run doctor |
The daemon's doctor checks against the dev data dir |
bun run desktop:build |
An unsigned (ad-hoc signed) local app for this machine: macOS .app and dmg in apps/desktop/dist/release |
bun run desktop:package |
The full platform package (see Packaging) |
bun run desktop:e2e |
Playwright-for-Electron smoke test against the fake daemon (ACE_E2E_ELECTRON=1) |
bun run release:doctor |
What this Mac has set up for a release: Developer ID, notarization, GitHub CLI (releasing) |
bun run release:build <v> |
A release for this Mac's architecture, signed, notarized and verified, staged under dist/releases/v<v> |
bun run release:publish <v> |
The staged release as a GitHub release, which installed apps update from |
bun run smoke:real |
Headless Chromium tour against a protected copy of owner data; options and fixture mode |
Dev data is isolated and persistent. The dev daemon uses ACE_HOME=~/.ace/dev on port 4343,
and Electron keeps its window state, sessions, settings and logs in ~/.ace/dev-electron. Every
checkout and worktree shares this home, so dev threads and settings survive across branches,
and none of it touches ~/.ace or an installed app. Set ACE_DEV_SEED=1 to copy the fake
daemon's demo threads into an empty dev store before the daemon starts
(ACE_DEV_SEED=1 bun run dev). Reset everything with rm -rf ~/.ace/dev ~/.ace/dev-electron;
set ACE_DEV_DIR for a throwaway home elsewhere.
Pointing the app at another daemon. bun run dev attaches to the dev daemon and never
spawns one. The packaged app reuses a daemon already serving ACE_HOME (the CLI, or the login
service from ace service install) and only starts its bundled daemon when none answers. To use
a daemon on another machine, start the app with ACE_DAEMON_URL=wss://host:port/ and
ACE_DAEMON_TOKEN=<paired device token> (or ACE_DAEMON_TOKEN_FILE) and
ACE_DAEMON_DEVICE_ID=<paired device ID>; use the credentials issued when pairing this
device, rather than the host token printed by ace token. In the browser build, enter the
address, paired-device token and paired device ID on the connection screen.
Terminals and native calls. The daemon runs on Bun: terminals use Bun's own PTY
(Bun.spawn({ terminal })) and C or Win32 calls go through bun:ffi, so the daemon ships no
node-pty or koffi. node-pty stays a development dependency of @ace/terminal, loaded only when
tests open a terminal on Node.
- Electron starts as plain Node (
Cannot read properties of undefined (reading 'setPath')): your shell exportsELECTRON_RUN_AS_NODE=1(some Electron-based terminals and tools do). The dev scripts strip it; unset it when you launch Electron yourself. - Port 4343 or 5173 is busy: set
ACE_DEV_DAEMON_PORT/ACE_DEV_WEB_PORT. - "Cannot acquire daemon lock": another dev daemon owns
~/.ace/dev(perhaps from another worktree). Stop it, or useACE_DEV_DIR=/tmp/ace-dev-2. - The desktop window shows the connection screen: the daemon had not published
~/.ace/dev/daemon-endpointwithin 15 seconds. The window reloads by itself once the daemon is up; check thedaemon │lines meanwhile. - Screen helper skipped during a build: the Swift (macOS) or Rust (Windows, Linux) toolchain is missing. The app works without it; screen and computer use stay off. On Linux a Rust older than 1.87 fails the desktop build instead of skipping the helper; update Rust and build again.
bun run typecheck uses incremental declaration builds and checks the original
package programs, root test tooling, and browser-only client runtime. One native
compiler runs at a time, with at most two project builders and two CPU threads.
bun run --filter @ace/protocol typecheck checks a workspace and its dependencies.
Use bun run typecheck:clean to remove the caches. See
incremental typechecking for the design and cache rules.
CI also runs bun run typecheck:full, checking every original program from source
with fresh build info as an independent safety net for declaration inference.
Tests run in separate unit and process projects with bounded concurrency. They run on Bun,
the daemon's runtime; the web and client-react jsdom projects and a few Node-only suites follow
in a second pass on Node (node on PATH). See
process test reliability for the real-I/O suite
inventory, shared fixtures and validation record. The owner requires tests to
run only at merge; development verification uses the permitted static checks.
The daemon prints its URL and token-file path. See startup and degraded services for readiness and service diagnostics. Remote access is on by default and serves the local network; a saved choice wins. See remote access for LAN/Tailscale pairing, device scopes and the ace CLI. See store and sync for configuration, the development creator, the command port, and replay behavior.
Local generic ACP launch and registry wire APIs are described in agent registry. Arbitrary agents retain unknown authentication and limited visibility; compatibility profiles are source-based until separately approved recordings establish behavior.
Remote access relay setup and APIs are described in encrypted relay.
apps/desktop is the Electron shell (ADR 0054). bun run desktop:package builds one
architecture, the host's, for the platform it runs on. --arch arm64|x64 may name it and must
match the host: native code is built for the host, so release CI runs once per architecture on a
matching runner. The build stops, listing each problem, if a required native piece is missing or
any staged binary is built for another architecture.
- macOS: dmg and zip (the zip is what Squirrel.Mac updates from). Signed with the
Developer ID in the keychain (
CSC_NAMEpicks one;CSC_LINKin CI) and notarized with whichever notarytool credentials are set:APPLE_KEYCHAIN_PROFILE,APPLE_API_KEYwithAPPLE_API_KEY_IDandAPPLE_API_ISSUER, orAPPLE_IDwithAPPLE_APP_SPECIFIC_PASSWORDandAPPLE_TEAM_ID.--unsignedbuilds the same package ad-hoc signed. The screen helper ships asContents/Helpers/AceScreenHelper.app(dev.ace.screen-helper), signed bynative/screen-helper/build.shand left untouched by the app's signing; its manifest isContents/Resources/screen-helper-manifest.json. A full package signs the helper with the Developer ID Application identity named byACE_SCREEN_SIGN_IDENTITY(orCSC_NAME) and a secure timestamp, and fails without one. That identity must already be in a keychain on the search list when the package command starts: the helper is signed during the build, before electron-builder importsCSC_LINKinto its own temporary keychain. In CI, import the certificate first (security create-keychain,security import … -T /usr/bin/codesign,security set-key-partition-list,security list-keychains -s), then runbun run desktop:package --full. - Windows: NSIS, with an "Open in ace" folder entry in Explorer. Windows runs no local daemon yet; it connects to a remote one.
- Linux: AppImage, deb and tar.gz (ADR 0093). Launch at
login writes
~/.config/autostart/dev.ace.app.desktop. An AppImage or extracted tar.gz installs nothing, so its first launch addsdev.ace.app.desktopand the app icon under~/.local/share(the app list andace://links), and the sandbox setup it offers installs/etc/apparmor.d/ace-desktop-user-<uid>andace-chromium. Deleting the AppImage or the folder removes none of these, nor theacecommand in~/.ace/binor ace's data in~/.ace. To remove them: turn off Launch at login first (or delete the autostart entry), delete~/.local/share/applications/dev.ace.app.desktopand~/.local/share/icons/hicolor/*/apps/ace-desktop.png, and unload each profile withsudo apparmor_parser -R /etc/apparmor.d/<name>before deleting its file (keepace-chromiumwhile a standalone daemon or the deb uses ace's Chrome). Delete~/.aceonly to erase ace's data.
Each package carries the daemon bundle from tools/release (Resources/daemon), the pinned Bun
runtime it runs on (Resources/runtime, the same one as the standalone archive), the staged
runtime packages, rg from @vscode/ripgrep, and the platform's screen
helper. Electron's runAsNode fuse is off. A full package also carries the update feed
(app-update.yml), and electron-builder writes latest*.yml beside it: the app updates itself
from GitHub releases (ADR 0091), and the bundled
ace update hands updates to the app. ACE_RELEASE_PUBLIC_KEY_FILE embeds the release
authority's Ed25519 key in the bundled daemon for standalone installs (ADR 0041).