Raw capture β raw/traycer.md
url: https://github.com/traycerai/traycer title: Traycer β Open-Source AI Agent Orchestration Platform author: Traycer date_fetched: 2026-07-18 date_published: 2025
Full-code analysis of the open-source portion of Traycer, an AI orchestration desktop application that wraps multiple coding agents under a unified interface with real-time collaboration, agent-to-agent communication, and multi-provider support.
Monorepo using Bun workspaces + Nx. ~170K lines of TypeScript (460K gui-app, 72K protocol, 45K desktop, 35K CLI). Strict typing: no any, no optionals, explicit union types.
| Path | Package | Lines | Role |
|---|---|---|---|
protocol/ |
@traycer/protocol |
72K | Versioned clientβhost wire contract |
clients/traycer-cli/ |
@traycer-clients/traycer-cli |
35K | Provision host, auth, agent/worktree commands |
clients/shared/ |
@traycer-clients/shared |
β | Transport (WS/RPC), auth (PKCE/bearer), formatting |
clients/gui-app/ |
@traycer-clients/gui-app |
461K | React + Vite renderer |
clients/desktop/ |
@traycer-clients/desktop |
45K | Electron shell around gui-app |
The actual agent runtime ("host") is not in this repo. It's a separately shipped, signed binary (Go-based) provisioned by the CLI from GitHub Releases. The CLI verifies it against a trust key committed in clients/traycer-cli/src/config.ts. This split between open-source client+protocol and closed-source host is the fundamental architectural boundary.
The protocol layer (protocol/src/framework/) is the most sophisticated part of this codebase. It implements a per-method {major, minor} schema versioning system negotiated at handshake β distinct from npm semver.
Key files:
- protocol/src/framework/versioned-rpc.ts β Core registry: defineRpcContract(), defineUpgradePath(), defineDowngradePath(), validateVersionedRpcRegistry()
- protocol/src/framework/versioned-rpc-types.ts β Type system: RpcContract, MethodVersionRegistry, UpgradePath, DowngradeResult, error code vocabulary
- protocol/src/framework/json-schema-fingerprint.ts β Structural diff detection: converts Zod schemas to normalized fingerprints (object/enum/anyOf/array), detects additivity violations and breaking changes
- protocol/src/framework/surface-compat.ts β Clientβhost handshake: compares method-name sets to negotiate compatible version
- protocol/src/framework/versioned-stream-rpc.ts β Streaming counterpart for subscriptions (chat, terminal, notifications)
How the versioning works:
MethodVersionRegistry keyed by major version numberlatestMinor tracks the highest installed version; versions holds each minorupgradeFromPreviousVersion (request + response transforms)downgradePathsFromLatest β direct bridges to older majorsRuntime negotiation: The client sends its method-name set; the host responds with the versions it supports; they agree on the highest mutually-compatible version per method. The "released floor" (RELEASED_FLOOR_METHOD_NAMES in protocol/src/host/released-floor.ts) defines ~118 method names every peer must support. Non-floor methods can declare degrade strategies: { kind: "unsupported" } (caller gets per-call upgrade guidance) or { kind: "fallback" } (mapped to a floor method with adaptRequest/adaptResponse transforms).
The system abstracts coding agents as "harnesses." Each harness has:
- A unique harnessId (e.g., claude, codex, cursor, opencode, traycer, openrouter, grok, qwen, kiro, droid, kimi, copilot, kilocode, amp, devin, pi)
- A UserMessageAnchor schema that resolves how to identify a specific message in that agent's SDK (Claude uses sessionId + claudeMessageUuid, Codex uses sessionId + codexTurnId + codexUserMessageId, Cursor uses sessionId + cursorRunId, etc. β see agent-runtime.ts lines 802-919)
- Two surface types: GUI (chat-based, with streaming events) and TUI (terminal-based, record-activity and turn-ended fire-and-forget)
- Provider profiles (ambient CLI login vs. Traycer-managed subscriptions)
protocol/src/host/agent/gui/agent-runtime.ts defines ~50 discriminated event types in a RuntimeEvent union. These form the normalized event stream that all harnesses emit into:
text.delta, text.completed β response streamingreasoning.delta, reasoning.completed β reasoning tracestool_call.started, .completed, .errored, .progress β tool call lifecycleapproval.requested, approval.resolved β permission gatestodo.updated β task trackingplan.delta, plan.updated, plan.completed β plan documentscompaction.started, .completed, .errored β context summarizationsubagent.started, .progress, .completed β child agent lifecycleworkflow.started, .progress, .completed β workflow orchestrationfile_change.started, .completed β file edits with content-addressed snapshot refscommand.started, .completed β shell command trackingsession.created, session.resumed β session lifecycleturn.started, .completed, .stopped, .interrupted β turn lifecycleusage.updated β interim token usage (per-adapter: Claude uses BetaUsage, Codex uses thread/tokenUsage/updated notification, Cursor has no contextWindow source)provider_notice.upsert β durable notices from providers (model reroutes, safety verification)steer.submitted β message injection from agent-to-agentThe agent-runtime-accumulator.ts converts these runtime events into persisted ContentBlock types, managing ownership (parentBlockId for nested subagents) and terminal status mapping.
agent.sendMessage β fire-and-forget message handoff between agentsagent.create β mint child agents (with profile selection: inherit_sender, explicit profile, ambient, or last_used)agent.list β enumerate all agents in an epic's Y.Docagent.configure β atomically switch provider/profile/model for an existing agentagent.stop β halt an agent and optionally its delegated subtreeThe provider system (protocol/src/host/provider-schemas.ts, protocol/src/host/registry.ts lines 722-1920) manages:
profileId parameter on providers.startLogin@1.1)host.getRateLimitUsageUses Yjs (CRDT) via @tiptap/y-tiptap for collaborative text editing. The Y.Doc is the shared document for epics and chats. The yjs-utils package (protocol/utils/yjs-utils/) provides typed wrappers.
protocol/src/persistence/ defines record registries for chat events, messages, content blocks, senders, checkpoint manifests, and room metadata β all versioned with the same record framework as RPC.
This is the single most impressive engineering decision. Every RPC contract has explicit upgrade/downgrade transforms with runtime-verified compatibility rules (additivity within minors, breaking change requirements for majors). The version registry is fully typed β defineVersionedRpcRegistry() validates structural invariants at module load. This is not just "we use Zod" β it's a complete versioned API lifecycle system with compile-time and runtime enforcement.
The downgrade paths are particularly telling: agentCreateDowngradeV20ToV10 returns DOWNGRADE_UNSUPPORTED for ambient and last_used profile selections because the frozen v1.0 wire doesn't have those concepts β and the error message tells the caller to choose a specific profile or upgrade the host. This is UX-engineering at the protocol level.
fileChangeCompletedEventSchema carries beforeHash and afterHash (content-addressed snapshot refs) rather than shipping the decoded before/after content over the wire. snapshots.readSnapshotDiff fetches diffs on demand. This keeps the event stream lightweight while enabling on-demand diff viewing.
Each harness has its own discriminator in the userMessageAnchorResolvedEventSchema union, with harness-specific data (Claude: claudeMessageUuid, Codex: codexTurnId + codexUserMessageId, OpenCode: opencodeUserMessageId, etc.). This means the system can precisely identify which message an agent sent, regardless of the agent's own internal identifiers β solving the cross-agent message-linking problem at the protocol level.
The RELEASED_FLOOR_METHOD_NAMES list (118 methods) defines the minimum method-name set that all peers must support. New methods are added outside the floor with degrade: { kind: "unsupported" }, allowing graceful degradation without breaking the handshake. This is the protocol-level equivalent of feature flags β new capabilities can ship without requiring immediate host updates.
The host binary ships independently from the client/protocol. The version-negotiated RPC means any host version can talk to any client version within compatible major ranges. The CLI provisions the host from GitHub Releases, verifying it against a trust key committed in the open-source code. This physical separation between "protocol + client" (open) and "runtime" (proprietary) is a business-model architecture decision encoded directly in the build system.
The entire protocol framework is designed so that the host can be updated independently of all clients, and vice versa. Every schema change carries explicit transforms in both directions. This is expensive to maintain (the registry file alone is 4,100+ lines of version definitions) but enables the open-source client and closed-source host to ship asynchronously.
A single worktree.listAllForHost method has 5 versions (v1.0 through v1.4) with 4 upgrade paths and a 14-version chain for the client. The version registry is complex and the build-time validation (validateVersionedRpcRegistry) runs through every contract for every module load. New contributors face a steep learning curve.
New features that would naturally be a new method (like worktree.readScriptsAtRef or terminal.defaultCwd) are folded onto existing methods to avoid a fatal equal-set handshake mismatch against already-shipped hosts. The comment on worktreeListBindingsForEpicV11 (line 1931-1936) explicitly states: "a new method name fatally fails the equal-set handshake against an already-shipped host." This pushes feature surface growth into parameter bloat rather than new endpoints β a notable constraint that shapes the entire API design.
The harness system supports 17+ coding agents with per-harness adapters, each normalizing its SDK-specific events into the shared RuntimeEvent stream. The system is designed to add new agents by adding a new discriminator to the union β the rest of the system remains harness-agnostic.
any, no optional params (?:), explicit T | undefined unions, no ReturnType<> inference of others' return typesadversarial-detect-shells.test.ts, adversarial-hostile-config.test.ts, adversarial-store-fuzz.test.ts)released-baseline-compat.test.ts, released-surface-compat.test.ts β tests that the released floor is never accidentally changedThe system supports these agent harnesses (from agent-runtime.ts anchor schemas and host/registry.ts):
claude) β session-based with BetaUsage token trackingcodex) β OpenAI's coding agent, thread-basedcursor) β Turn-based, no public-API contextWindowopencode) β Open-source agent, message-id-basedtraycer) β Native inference, uses OpenCode-style sessionopenrouter) β Model aggregator, OpenCode-style sessiongrok) β xAI agent, ACP sessionqwen) β Alibaba agent, ACP sessionkiro) β ACP sessiondroid) β Uses @factory/droid-sdk exec sessionkimi) β Moonshot AI agent, ACP sessioncopilot) β GitHub, ACP sessionkilocode) β ACP sessionamp) β Thread-based, execute with options.continuedevin) β ACP sessionpi) β Session-basedPlus TUI-only harnesses (listed separately).
The ACP (Agent Communication Protocol) is used as the common interface for many harnesses β the host's adapter spawns the agent process with --acp and communicates over stdio.