Architecture
Condr splits the runtime and the interface into two processes. The Server hosts terminals, Agents and the Session; the window is just one client connected to the Server. This page is for developers who plan to read or change Condr’s code.
Process model
Section titled “Process model”- One Server per machine: the Server always listens on a private local socket (a named pipe on Windows). With
[server] listenconfigured, the Server opens one extra TCP listening port; with Peer-to-peer turned on, it also accepts P2P connections. Every entry point reaches the same Session. - The Server and the command line are one binary:
condr server …manages the Server, and every other subcommand connects to the Server as a client. - The window runs as a separate client: on startup
condr-guifirst probes the local Server. If nothing answers, it starts the Server as a background process, through a small Supervisor process that starts it again if it crashes. Closing the window only disconnects; the Server, terminals and Agents keep running. - Single window instance: on startup the window locks
condr.lockin the runtime directory. If the lock is already held, the new instance exits at once.
Crates
Section titled “Crates”| Crate | Builds | Responsibilities |
|---|---|---|
condr-core |
Library | Domain model, protocol, PTY and terminal emulation, Agent detection, Git. No GUI dependencies, so it can be tested without a display |
condr-server |
condr |
The Server process and the command line. Manages the Session, the terminal runtime, persisted state and every connection type |
condr-gui |
condr-gui |
The window. Renders with GPUI Kit, and reuses condr-server’s client connection code as a library |
Terminal frames
Section titled “Terminal frames”- The Server creates the PTY through
portable-pty. On Windows it uses ConPTY, Microsoft’s own from Windows Terminal, which Condr ships besidecondr.exebecause the one Windows has keeps a terminal’s colour and status answers from programs, and each terminal’s processes go into their own Job Object, so closing a Pane ends them together. - PTY output goes to
libghostty-vt, Ghostty’s terminal core, which parses it. - While parsing, it hands the Server what is not screen content: OSC 7 updates the current working directory, OSC 7501 carries Program Status Protocol reports, and OSC 777 carries Agent events.
- The Server coalesces wakeups at 60 Hz and reads again only the rows
libghostty-vtmarks changed: a full-screen change produces a complete frame, a partial change produces only the changed cells. - The Server builds each client’s frame against the frame that client already has, and sends it over the protocol.
- The window draws the terminal with a custom GPUI element and caches the shaping results of unchanged content.
Reliable events and visual frames
Section titled “Reliable events and visual frames”- Reliable events: layout changes, lifecycle, Agent state and Git changes are sent in order with a replay cursor, so none is lost.
- Visual frames: terminal frames travel on a separate channel. Each client gets only one droppable frame slot, and reliable messages always go first. When the slot is full, the Server records only which Panes need a refresh; once the slot frees up, it builds a new frame straight from the latest terminal state and never replays stale intermediate frames.
- Reconnecting: after connecting, the window first requests a Bootstrap (the full structure snapshot plus each Pane’s current frame), and subscribes to live events once that is in sync. If it detects a gap in the event sequence, the window requests one new Bootstrap.
Agent state
Section titled “Agent state”- An Agent that speaks the Program Status Protocol asks the terminal with
OSC 7501 ; ?; the Server answers before the terminal emulator’s own reply to the device-attributes query the Agent sends behind it, and the Agent then reports every state change as an OSC 7501 sequence on its own PTY. - For the other Agents,
condr agent hooks installwrites the hooks into the Agent’s own configuration. When the Agent fires a hook, it runscondr agent-hook <agent> <event>, which reads the JSON the Agent passes and writes it back to the current terminal as an OSC 777 sequence. - The Server intercepts both in the PTY output and updates that Pane’s Agent state to Unknown, Idle, Working, Blocked, Done or Error. Once an Agent has reported over OSC 7501, its hooks only supply the session ID a restart resumes with. Blocked and Error carry a
detailfield: what the Agent waits for, or why it failed. - If the Agent has never reported, its state stays Unknown. Condr does not infer Agent state from screen text.
Done is the Agent’s own state after a finished turn. The Server adds whether anyone looked, one bit for every window and the phone: a Done that arrived while its Pane was focused, or whose Pane someone has opened since, shows as Idle everywhere, and stays so after a restart of the GUI or the app.
Connection types
Section titled “Connection types”| Type | Implementation |
|---|---|
| Local | Unix domain sockets or Windows named pipes from interprocess |
| TCP | Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s implemented with snow; both sides authenticate with static keys, and a new Device pairs with a one-time invite |
| SSH | Runs the system ssh -T to execute condr server bridge on the remote Device, forwarding the remote private socket to this machine. Authentication and transport encryption are left entirely to OpenSSH |
| Peer-to-peer | QUIC connections from iroh, dialled by Device key with NAT hole punching; when a direct connection fails, traffic is relayed through relay.condr.dev |
condr --device <name> connects directly to a Device saved in [[client.servers]], without going through the local Server.
Protocol
Section titled “Protocol”- Encoding: frames are Protocol Buffers, delimited by a varint length prefix, at most 2 MiB each. The definitions are in
proto/condr/v1/and compiled bycondr-core’sbuild.rs. Generated types are used only insideprotocol::pband are converted to domain types at the module boundary. - Handshake: the client sends a
ClientHandshake, and the Server answers with aWelcome(which carries a refusal reason when the handshake fails). The handshake messages’ Protobuf field numbers never change, and both sides report their build version during the handshake. - Compatibility: on connecting, each side states the oldest peer protocol version it accepts. Fields and oneof members may only be added, never removed; an unknown enum value is treated as the default documented in the
.protofile; an unknown oneof member causes the enclosing message to be dropped, without disconnecting. When the two builds differ, the window only shows a warning mark.
Persistence
Section titled “Persistence”| File | Format | Contents |
|---|---|---|
condr-server-<id>.snapshot |
Protocol Buffers | The Session structure: Workspaces, Tabs, the Pane layout and its split ratios, working directories, Agent kind and session ID for resuming, and worktree associations. No terminal scrollback |
condr-gui.state |
Protocol Buffers | Window position, sidebar widths, and what each Device and Workspace is showing. Read only by the window; the Server never touches it |
config.toml |
TOML | The settings file shared by the window, the Server and the command line. Every read and write goes through condr_core::{read_config_value, update_config_values}, behind a cross-process file lock that keeps the existing formatting |
Main dependencies
Section titled “Main dependencies”| Dependency | Role |
|---|---|
gpui-kit |
GPUI, UI components, the Dock layout and assets |
libghostty-vt |
Ghostty’s terminal core: terminal emulation, VT sequence parsing and kitty keyboard encoding. Built from source with Zig |
portable-pty |
Cross-platform PTYs (Unix pty and Windows ConPTY) |
gix |
Every Git query, plus creating and removing worktrees. Never calls the git command and does not link libgit2 |
snow |
The Noise handshake for TCP connections |
iroh |
P2P QUIC transport and hole punching |
syntect and two-face |
Syntax highlighting in the Preview and Diff views |
tracing |
Structured logging |
tokio |
The async runtime in the Server and core. The window uses GPUI’s own executor |
Where to start reading
Section titled “Where to start reading”| Area | Entry points |
|---|---|
| Terminal rendering | crates/condr-gui/src/terminal_element.rs, crates/condr-server/src/server/terminal_stream.rs, crates/condr-server/src/client_writer.rs |
| Terminal input | crates/condr-gui/src/app/terminal_input.rs, crates/condr-core/src/terminal/input.rs |
| Protocol | proto/condr/v1/, crates/condr-core/src/protocol.rs, crates/condr-server/src/server/client.rs |
| Agent detection | crates/condr-core/src/agent/, crates/condr-core/src/terminal/osc.rs, crates/condr-server/src/server/agents.rs |
| Git and worktrees | crates/condr-core/src/git.rs, crates/condr-server/src/server/workspace_git.rs |
| Settings | crates/condr-gui/src/app/settings.rs, crates/condr-gui/src/app/config.rs |
docs/code-organization.md lists every module’s entry point.
Build and test
Section titled “Build and test”cargo build # build everythingcargo run -p condr-gui # run the window; needs a displaycargo test --workspace --features condr-gui/test-supportcargo clippy --workspace --all-targets --features condr-gui/test-support -- -D warningscargo fmt --all- Zig is required:
libghostty-vtis compiled from source with the exact Zig versioncrates/condr-ghostty-vt/ghostty.tomlnames (zig = "…"); install it with mise. - Build the GUI on its target OS: GPUI does not cross-compile, so the window must be built natively on the target system.
condr-coreandcondr-serverbuild and test without a display. - Optimized dev builds: the root
Cargo.tomlturns on optimization for GPUI, text shaping, terminal emulation and Condr’s hot paths, socargo run -p condr-guikeeps a usable frame rate in a dev build.
See CONTRIBUTING.md for how to contribute.
Design decisions
Section titled “Design decisions”Major architecture decisions are recorded in the ADR directory. Read the relevant record before changing a module.