1# Architecture
2
3T3 Code keeps execution in the environment that owns the workspace. Web, desktop, and mobile
4clients control it over authenticated RPC. A remote client must never substitute its own filesystem,
5provider credentials, or machine state for the environment's. The desktop app bundles a server,
6but its renderer follows the same boundary.
7
8## Ownership boundaries
9
10Provider processes, terminals, Git, and project files belong to the server. Shared connection and
11domain state belongs in `packages/client-runtime`; clients supply platform services and UI.
12Keeping that logic shared prevents reconnect and multi-environment behavior from diverging between
13web and mobile. See [connection runtime](./connection-runtime.md) and
14[remote environments](./remote.md).
15
16The [RPC contract](../../packages/contracts/src/rpc.ts) is the boundary between independently
17versioned clients and servers. Subscriptions send the state a client needs, so a client viewing one
18thread does not pay for every thread's history. Authentication of a socket does not authorize every
19method on it. See [environment auth](./environment-auth.md).
20
21### Pull request linking compatibility
22
23Web, desktop, mobile, and environments upgrade independently. Negotiate linking through the
24environment descriptor, never through a client version or an assumed coordinated release:
25
26| Environment capability | Client behavior |
27| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
28| `threadPullRequests: true` | Use persisted `pullRequests[]`, multi-link commands, stack UI, and reverse thread lookup. |
29| Only `threadPullRequestLinking: true` | Use `linkedPullRequest` and the existing `thread.meta.update` single-link operation. Do not call multi-link RPCs. |
30| Neither flag | Hide linking actions; existing branch-discovered PR display remains available. |
31
32New environments continue advertising the legacy flag, accepting legacy metadata commands, and
33emitting the derived `linkedPullRequest` field for older clients. That hostless field includes only
34links in the thread project's own repository; cross-host and cross-repository links require the
35multi-link protocol. New clients accept snapshots that
36omit `pullRequests`. Retain the legacy wire fields, projection column, and replay support; this feature
37does not schedule their removal. Missing new capabilities must also override cached multi-link data
38after an environment downgrade.
39
40Provider-specific behavior belongs behind an adapter. Orchestration works with normalized commands
41and events, so adding a provider should not require branches throughout the domain or clients.
42See [provider constraints](./providers.md).
43
44## Settings ownership
45
46Client preferences stay in the current client; environment defaults and project overrides stay
47on their owning server. The web and desktop settings target is URL state, resolved against current
48connections and project membership. An unavailable target must not fall back to another environment.
49**All environments** is an explicit bulk edit of connected, loaded servers, not a durable global
50default or a promise to synchronize offline or future environments. Project-group targets similarly
51select known environment-local checkouts; the group itself does not store inherited defaults.
52
53## Durable intent and side effects
54
55The event log is the source of truth for orchestration state. The
56[v2 orchestrator](../../apps/server/src/orchestration-v2/Orchestrator.ts) serializes commands and
57decides events without performing provider or filesystem work.
58[EventSink](../../apps/server/src/orchestration-v2/EventSink.ts) commits events, persisted projections,
59the accepted command receipt, and outbox effects in one database transaction. Subscribers receive
60events after that commit. This keeps command retries idempotent and prevents a persisted projection
61from getting ahead of the event log.
62
63The [effect worker](../../apps/server/src/orchestration-v2/EffectWorker.ts) performs side effects
64after intent has been recorded, then feeds results back into orchestration. A command acknowledgement
65therefore means the intent committed, not that the provider, checkpoint, or other follow-up work
66finished. Keep external I/O out of command decisions and the database transaction. Effects tied to
67a lost provider process cannot simply replay; recovery retires them before admitting new work.
68
69Persisted events must remain decodable on replay. Changing a schema affects old environments at
70startup as well as live RPC traffic. Compatibility work must account for stored history, not just
71what the newest client sends.
72
73## Turn completion and checkpoints
74
75A provider turn ending and its follow-up work settling are separate milestones. Orchestration
76records provider turn and run state independently from
77[run finalization](../../apps/server/src/orchestration-v2/RunFinalizationService.ts). A late
78checkpoint or diff must not extend the recorded provider duration or keep the client showing
79provider work as active. PR discovery after completion also checks that the checkout still matches
80the thread's non-default branch and that a newer run is not active.
81
82[Checkpoints](../../apps/server/src/checkpointing/CheckpointStore.ts) use hidden Git refs to
83capture workspace state without adding commits to the user's branch. A revert must coordinate
84workspace state with the provider conversation. A provider that cannot roll back its conversation
85must reject that operation before changing the filesystem.
86
87Thread settlement is server-owned. The
88[settlement service](../../apps/server/src/orchestration-v2/ThreadSettlementService.ts) evaluates PR
89and inactivity settings without a connected client. Merge notifications invalidate cached PR state
90and trigger a check. A merge outside T3, such as an agent running `gh pr merge`, sends no
91notification, so the [PR sync reactor](../../apps/server/src/orchestration-v2/PullRequestSyncReactor.ts)
92re-reads a thread's open links when a run that ran a merge or close command ends. The guarded
93`thread.auto-settle` command rejects newer activity, explicit settlement overrides, and live or
94blocked work. It records the activity timestamp for stable
95sorting and detaches idle provider sessions. Clients render the persisted result; they do not
96derive settlement from their own clocks or PR caches.
97
98## Waiting for asynchronous work
99
100Tests use [drainable workers](../../packages/shared/src/DrainableWorker.ts) to wait until both the
101queue and its current item have finished. An empty queue alone does not prove the worker is idle.
102
103V2 tests also drain the effect worker or await a specific persisted event or receipt. Test signals
104are separate from the durable command receipts that make dispatch idempotent. Production behavior
105must use persisted state and events, not test instrumentation or assumptions about elapsed time.
106
107The Electron shell acquires `DesktopPreReadyPlatform.layer` synchronously before asynchronous
108services. On Linux this sets the desktop-entry identity and global-shortcut portal flags before
109Chromium initializes its portal connection. Setting the identity later in `DesktopAppIdentity`
110is too late: Chromium caches the first registration, including failures. The identity must match
111the installed entry managed by `DesktopLinuxUrlHandler`. Pre-ready setup also refreshes that entry's
112`Exec` path before portal registration: AppImage updates can remove the previous executable, which
113makes the old entry invalid even though its filename is correct. The later URL handler avoids
114rewriting an identical entry while the portal may be reading it. On Wayland, Electron's synchronous
115shortcut-registration result only confirms submission; it does not confirm desktop consent or
116an active binding.
117
118Native modules never load in the Electron main process on the startup path, and the two the
119snapshot feature keeps are isolated: `@crowecawcaw/xa11y` runs only in forked Node-mode children
120(`SnapShotAccessibilityWorker`, `RegionSnapShotWorker`) and a worker thread, and `ffi-rs` loads
121lazily inside `WindowsForeground.ts` for a handful of Win32 calls. macOS window lookup shells out
122to `osascript` instead of a native addon. A crash or stall in any of these must not take the app
123down, so new native capability goes in a child with a deadline, not an `import` in main.
124
125See the [glossary](./glossary.md) for shared terms and the
126[development runbook](../operations/development.md) for setup and checks.