Zedra is a mobile code editor that controls desktop AI coding agents through a peer-to-peer QUIC tunnel. No port forwarding, no VPN, no cloud relay. Both sides initiate outbound connections to establish a bidirectional control channel. The architecture exposes a pattern worth studying: how to synchronize file system state, terminal output, and git status across a mobile client and a desktop agent session when the network link can drop at any moment.
Built on Rust with GPUI (the rendering engine from Zed editor), Zedra runs on iOS and Android. The desktop CLI works on Mac, Linux, and Windows. After four months of development, it is now in beta.
Why Outbound-Only QUIC Matters
Most remote coding setups require either:
- Inbound firewall rules and port forwarding (SSH, VNC)
- A cloud relay server (VS Code Remote, Tailscale)
- A VPN tunnel (WireGuard, OpenVPN)
Zedra avoids all three by having both the mobile client and desktop CLI initiate outbound connections to a lightweight rendezvous server. Once the peers discover each other, they establish a direct QUIC/UDP tunnel. The rendezvous server only brokers the initial handshake. After that, control messages and file diffs flow peer-to-peer.
This works on home networks behind NAT because both sides punch through with outbound UDP packets. No router configuration required.
Architecture: Control Flow and State Sync
The mobile app sends commands (file edits, terminal input, git operations) over the QUIC stream. The desktop CLI executes them and streams back:
- File system diffs (incremental, not full snapshots)
- Terminal output (line-buffered, with ANSI escape codes)
- Git status (branch, uncommitted changes, merge conflicts)
The challenge is keeping the mobile UI consistent when the agent modifies files outside the mobile session. For example:
- Agent writes a new file while mobile client is viewing another file
- Agent runs
git commitwhile mobile client is editing a file - Agent crashes or hangs, leaving the mobile client waiting
Zedra handles this with a versioned state model. Each file edit or agent action increments a version counter. The mobile client requests a full state snapshot if it detects a gap in the version sequence (indicating missed updates during a network drop).
State Synchronization Table
| Event | Mobile Action | Desktop Action | Conflict Resolution |
|---|---|---|---|
| Mobile edits file | Send diff over QUIC | Apply diff, increment version | Desktop rejects if version mismatch |
| Agent modifies file | Request state snapshot | Send full file + version | Mobile overwrites local buffer |
| Network drop | Queue edits locally | Continue agent execution | Replay queue on reconnect, reject stale edits |
| Agent hangs | Timeout after 30s, show warning | No change | Mobile can kill agent process via control message |
| Git merge conflict | Show conflict markers in editor | Pause agent, wait for resolution | Mobile sends resolved file, agent resumes |
Code Snippet: QUIC Stream Multiplexing
Zedra uses separate QUIC streams for control messages, file diffs, and terminal output. This prevents a long-running terminal command from blocking file edits.
// Simplified example of stream multiplexing
async fn handle_connection(conn: quinn::Connection) {
let (mut control_tx, mut control_rx) = conn.open_bi().await?;
let (mut file_tx, mut file_rx) = conn.open_bi().await?;
let (mut term_tx, mut term_rx) = conn.open_bi().await?;
tokio::spawn(async move {
while let Some(msg) = control_rx.read_to_end(1024).await? {
match parse_control_message(&msg) {
ControlMessage::EditFile { path, diff } => {
apply_diff(&path, &diff).await?;
file_tx.write_all(&serialize_ack()).await?;
}
ControlMessage::RunCommand { cmd } => {
let output = execute_command(&cmd).await?;
term_tx.write_all(&output).await?;
}
ControlMessage::KillAgent => {
terminate_agent_process();
}
}
}
});
}
The mobile client opens three bidirectional streams on connection. Control messages (edit, run, kill) go over control_tx. File diffs go over file_tx. Terminal output streams over term_tx. If the terminal stream stalls, the control stream remains responsive.
Reconnection and Session Persistence
Mobile networks switch between WiFi and cellular frequently. Zedra handles this by:
- Detecting connection loss (QUIC idle timeout, typically 10 seconds)
- Buffering unsent edits in memory (up to 100 KB)
- Reconnecting to the rendezvous server
- Re-establishing the QUIC tunnel
- Sending a state sync request with the last known version counter
- Replaying buffered edits if the desktop accepts the version
If the desktop has moved ahead (agent made changes during the disconnect), the mobile client discards its buffer and pulls the latest state. This prevents divergent edits from corrupting the file system.
Session persistence is handled by a session ID generated on first connection. Both peers store this ID. On reconnect, they include it in the rendezvous handshake. The desktop CLI keeps the agent process alive during disconnects (configurable timeout, default 5 minutes).
Observability Gaps
Zedra does not yet expose:
- Agent execution metrics (CPU, memory, token usage)
- Network latency histograms (QUIC RTT, packet loss)
- State sync conflict rates (how often mobile edits are rejected)
These would be useful for debugging performance issues and tuning the reconnection logic. The current approach is to log errors locally and rely on user reports.
Security Boundaries
The rendezvous server sees peer IP addresses and session IDs, but not the content of control messages or file diffs. The QUIC tunnel uses TLS 1.3 with mutual authentication (both peers verify each other’s certificates).
The desktop CLI runs with the same permissions as the user who launched it. If an agent has file system access, the mobile client inherits that access. There is no sandboxing or permission model beyond the OS-level user account.
This means:
- A compromised mobile device can delete files on the desktop
- A malicious agent can exfiltrate data over the QUIC tunnel
- The rendezvous server can perform a denial-of-service attack by refusing to broker connections
The threat model assumes the mobile device and desktop are both trusted, and the rendezvous server is operated by the user or a trusted party.
Deployment Shape
Zedra requires:
- Mobile app (iOS or Android)
- Desktop CLI (installed via Homebrew, apt, or binary download)
- Rendezvous server (optional, defaults to a public instance)
The desktop CLI runs as a background process. The mobile app connects on demand. The rendezvous server is a single Go binary that listens on UDP port 4433.
For production use, you would:
- Run your own rendezvous server (source code available)
- Configure the mobile app to use your server’s IP
- Set up monitoring for the rendezvous server (uptime, connection rate)
- Rotate TLS certificates periodically (Zedra does not handle this automatically)
Likely Failure Modes
| Failure | Symptom | Mitigation |
|---|---|---|
| Rendezvous server down | Mobile app cannot connect | Run multiple rendezvous servers, configure fallback IPs |
| QUIC tunnel blocked by firewall | Connection succeeds but no data flows | Fall back to TCP-based tunnel (not yet implemented) |
| Agent process crashes | Mobile app shows stale state | Desktop CLI detects crash, sends error message, mobile app shows notification |
| Mobile app killed by OS | Desktop CLI waits for reconnect | Reduce session timeout to 2 minutes, or add push notification to wake app |
| File system race condition | Mobile edit conflicts with agent edit | Desktop rejects edit, mobile pulls latest state and retries |
Technical Verdict
Use Zedra when:
- You need to control coding agents from a mobile device without cloud dependencies
- You trust both the mobile device and desktop (no sandboxing or permission model)
- You can run your own rendezvous server for production use
- You are comfortable with Rust-based tooling and GPUI rendering
Avoid Zedra when:
- You need fine-grained permission controls (file access, network access)
- You require observability into agent execution and network performance
- You need guaranteed message delivery (QUIC can drop packets under high loss)
- You need to support multiple concurrent mobile clients controlling the same desktop session
The peer-to-peer QUIC architecture is the most interesting part. It solves the NAT traversal problem without requiring cloud infrastructure. The state synchronization model is simple (version counters and full snapshots) but may not scale to large codebases or high-frequency edits. The lack of observability and sandboxing limits production use, but for personal projects and side hustles, Zedra offers a clean alternative to SSH or cloud-based remote editors.