A small, fast, open-source home for AI coding agents.
One place for your folders, your terminal, and the right account, per project.
Download Β Β·Β Features Β Β·Β Remote access Β Β·Β Architecture Β Β·Β Development
Each work folder, with all its repos, gets a terminal, an agent, and the right account. Switch between them like Slack workspaces.
The daemon owns the terminals, so your agents keep running when you close the window. The desktop app is just a viewport onto them.
Grab the latest build for your platform from the releases page:
- macOS: the universal
.dmg(Apple Silicon and Intel) - Windows: the
-setup.exeinstaller (or the.msi) - Linux: the
.AppImage(or the.deb/.rpmpackages)
Note: macOS builds are not notarized. On first launch, open System Settings > Privacy & Security and click "Open Anyway" (or right-click Soromi and choose Open). Soon the builds will be signed with an Apple developer account.
- A workspace for every project. Point Soromi at a folder and it becomes a workspace with its own terminal. Jump between projects like switching Slack workspaces.
- Terminals that stay alive. Close the window and your agents keep working. Reopen and pick up exactly where you left off.
- Multiple tabs per workspace. Run several agents side by side in one project, name them, and they come back after a restart.
- Keep your accounts separate. Give each agent its own login (work, personal, client) so they never mix.
- Work on just the folders you choose. Select the repos or folders that matter and the agent stays focused on them.
- Browse files without leaving. A file tree and preview for quick reference, where a right click (a long press on the phone) copies a path or moves a file or folder to the Trash after asking; your real editor stays your editor.
- Skills at a click. See your agent's commands and skills in a sidebar and drop one into the terminal.
- Know when it needs you. A sound and a notification when an agent asks for permission or finishes, so you can step away. Mute any workspace you want.
- Stay awake while it works. Optionally keep your machine from sleeping until the agent is done.
- Know when there's a new version. Soromi checks for newer releases and shows a quiet banner with a link to download. Nothing installs behind your back.
- Shareable setup. Export a workspace to a small file anyone can import, or start fresh with no file at all.
- Built to be reached from anywhere. Pair a device over an end-to-end-encrypted relay; the phone app that connects through it is in development. See Remote access.
- One app, nothing to wire up. Everything runs from a single desktop app.
The daemon owns the terminals, so any viewport that speaks the protocol can attach, not only the desktop app. A paired device gets the same live session: watch the output stream, type into it, run a skill, and answer permission prompts with a tap.
Status: the phone client is a native mobile app (
apps/mobile), in development and not released yet. Today it can be run from source with Expo: it pairs, lists sessions live, and follows and continues a chat. The earlier browser client has been removed.Push notifications need credentials of your own, which are not in this repository: a Firebase project for Android and an Apple push key for iOS. See
apps/mobile/README.md. Without them the app works and notifies only while it is open.
The daemon also runs on its own on a Linux server, with no desktop. One line in an SSH session installs it, runs it as a service and shows the QR code to pair the phone:
curl -fsSL https://raw.githubusercontent.com/soromi/soromi/main/scripts/install.sh | shSee docs/server.md.
Control is exclusive and hands off cleanly. One device drives the terminals at a time: it types and its window decides their size. Every other device keeps watching live, read-only, and takes over with one explicit action, so two devices never fight over the same terminal.
Remote access is off until you pair a device, and even then the link is built so nothing in the middle can read your session:
- π End-to-end encrypted. Frames on the relay link are sealed with XChaCha20-Poly1305. The relay is a content-blind pipe that only ever forwards ciphertext, so it never sees your terminal, keystrokes, or output. Every connection agrees on fresh keys, one per direction, and frames are numbered, so a recorded frame cannot be played back later or turned around on its sender.
- πͺ No open ports. The daemon dials out to the relay; it never listens for inbound connections. Nothing on your machine is exposed to the internet, so there's no port to scan or firewall to open.
- π A locked local port. The desktop app and the
soromicommands reach the daemon onlocalhostwith a token only your user can read, so another account on the machine, or a web page open in your browser, can't drive it. - ποΈ Relay access key never leaves the daemon. The relay is gated by an access key so randoms
can't abuse it, but that key lives only in the daemon (config /
SOROMI_RELAY_ACCESS_KEY/ Settings) and is sent as a WebSocket header when dialing, never in a URL. Only the daemon can create a room; a paired device only ever joins an existing one with the room id. So the key never rides along in the pairing code or in logs, and a leaked code can't expose it. - π± Per-device keys, revocable. Pairing a phone (via QR) mints that device its own relay room and end-to-end key. Each paired device shows a live connection indicator, and you can revoke any of them at any time from the paired-devices list. A room holds one daemon and one device: even if a room id leaked, an attacker could at worst take the device's place (a nuisance you end by revoking the device) and still see nothing, because the session is end-to-end encrypted.
- π No downgrade. An invalid or missing key is refused rather than silently falling back to plaintext. Plaintext relay exists only for local development.
- βοΈ Close to you, on Cloudflare. The relay is a small Cloudflare Worker
(
packages/relay): each room runs near the daemon that opened it, and both ends connect through their nearest Cloudflare location. Deploy it to your own account and point the relay URL at it from Settings (or~/.soromi/config.json), no rebuild required, with your own access key on the Worker and in Settings.
I built Soromi because I was tired of juggling a separate editor window for every project and wrestling my agents into the right context. Setup was manual and fiddly, and I kept worrying about mixing up accounts that were meant for different things.
I just wanted one place: the folders, the skills, and the terminal together, so I could jump between projects, get a nudge when an agent needs me, keep the machine awake while it works, and eventually glance at what it is doing from my phone.
The tools I tried were often heavier than I wanted or did far more than I needed, and left me more confused than productive. So Soromi stays small and gets out of the way.
A provider is a coding-agent CLI Soromi can run, isolate per account, and listen to. Adding one
is a small entry in the provider registry (crates/daemon/src/config.rs).
β full support Β Β·Β βοΈ partial, needs a trusted hook Β Β·Β β not applicable
Notes:
- Account isolation points each account at its own config directory, so multiple logins
(work, personal, client) stay separate. Accounts are referenced by name; no secrets are stored
in
soromi.space.json. - Event cues are driven by the agent's own hook events, not terminal parsing, so they are robust across CLI versions. Sounds play with no OS permission; native notifications may prompt for permission once.
Soromi is one idea repeated everywhere: the daemon is the product, and every UI is just a viewport onto it. The daemon owns all state (PTYs, workspaces, accounts, the file tree, notifications); viewports are stateless and interchangeable. The same protocol runs over a trusted local socket or an end-to-end-encrypted relay, so a phone and the desktop app speak to the daemon the same way, just over a different transport.
VIEWPORTS (stateless)
βββββββββββββββββ βββββββββββββββββ
β Desktop GUI β β Mobile app β render from messages, send input
β (Electron) β β (planned) β desktop: React + Zustand + xterm.js
βββββββββ¬ββββββββ βββββββββ¬ββββββββ
β local WS β relay WS (E2EE)
β (localhost) β XChaCha20-Poly1305
β β
β βββββββββ΄ββββββββ
β β Relay β content-blind pipe
β β β (forwards ciphertext)
β βββββββββ¬ββββββββ
β β dial-out, no open ports
βββββββββ΄ββββββββββββββββββββββ΄ββββββββββ
β DAEMON (Rust) β sole state authority
β PTYs Β· workspaces Β· accounts β one WebSocket protocol
β file tree Β· status Β· hooks β (single source of truth)
βββββββββββββββββββββ¬ββββββββββββββββββββ
β spawn + isolate + listen
βββββββββββββββββββββ΄ββββββββββββββββββββ
β PROVIDERS (agent CLIs) β one folder + one registry
β Claude Code Β· Codex Β· Grok Β· β¦ β entry per provider
βββββββββββββββββββββββββββββββββββββββββ
Principles that hold the design together:
- The daemon is the sole state authority. Viewports never hold authoritative state; they render what the daemon sends and forward input and resize. Any viewport can drop and reattach, and terminals are redrawn as they stand, scrollback included.
- Local and remote are the same client. The transport is swappable (local WS vs. E2EE relay); everything above it is identical, so a feature works everywhere the moment it works once.
- The protocol is defined once. The wire types live in Rust (
crates/protocol) and the TypeScript types are generated from them, so the two sides can never drift. - Providers are pluggable. All provider-specific logic (launch flags, login checks, event
hooks, resume, skills, usage) sits behind a single
Providertrait, so adding an agent is a new folder plus one registry entry.
- The daemon (Rust) owns every PTY, resolves accounts into launch environments, watches agent status, installs the agent event hooks, checks for newer releases, and speaks a small WebSocket protocol.
- The GUI (React + Zustand + xterm.js) is a pure viewport: it renders from protocol messages and sends input and resize. It holds no state authority of its own.
- The protocol is defined once in Rust (
crates/protocol) and the TypeScript types are generated from it with ts-rs, so the two never drift. - The desktop app (Electron) renders the GUI and spawns the daemon as a child process, connecting over a local socket. The transport is kept deliberately simple so the same viewport can run remotely.
crates/
protocol/ Rust: the wire protocol (serde types), the single source of truth
daemon/ Rust: PTY sessions, account resolution, status, agent hooks, WS server
packages/
protocol/ TypeScript types generated from crates/protocol (do not edit by hand)
core/ What every client shares, no UI framework: transports (local WS, E2EE relay),
the store that mirrors the daemon, the pairing code
client/ The desktop's React bindings for core, and the xterm terminal
relay/ Content-blind WebSocket relay that pairs a daemon with a remote viewport
gui/ React + Zustand + xterm.js viewport (runs in the desktop app's webview)
apps/
desktop/ Electron app that spawns the daemon and renders the gui
mobile/ Expo (React Native) phone app, on core
Each package and crate has a README describing its boundaries.
Requires Node 22+, pnpm 10, and a stable Rust toolchain.
pnpm install # install workspace deps
pnpm build # build every package + the desktop bundle (turbo)
pnpm typecheck # type-check the TypeScript packages
pnpm check # format + lint with biome (pnpm check:fix to fix)
pnpm test # run the vitest suite
pnpm gen:protocol # regenerate the TypeScript protocol types from the Rust crateThe Rust crates have their own gate:
cargo fmt --check
cargo clippy --all-targets
cargo testRun it:
pnpm dev:desktop # the full app (Electron: builds the daemon + gui, then launches)Or iterate on the two processes separately:
pnpm dev:daemon # the Rust daemon (serves the local WebSocket)
pnpm dev # the GUI dev server against that daemonProject rules and conventions live in CONTRIBUTING.md.
MIT, see LICENSE.