| 1 | # Provider constraints |
| 2 | |
| 3 | Orchestration records intent and state without knowing which provider runs a thread. Provider |
| 4 | protocols, account ownership, permissions, and capabilities belong at the |
| 5 | [adapter boundary](../../packages/provider-core/src/server/ProviderAdapter.ts). Normalize there |
| 6 | instead of spreading provider checks through orchestration and clients. |
| 7 | |
| 8 | A driver kind identifies an integration; an instance identifies one configuration and account |
| 9 | lifecycle. Route work by instance, so two accounts using the same driver do not share mutable |
| 10 | session or catalog state. For a new driver, start with [adding a provider](./adding-a-provider.md). |
| 11 | |
| 12 | ## Process and account isolation |
| 13 | |
| 14 | The `opencode` driver probes the installed version and runs the 1.x or 2.x runtime. OpenCode's MCP |
| 15 | registrations are directory-scoped, while T3's MCP connection is thread-scoped, so threads in one |
| 16 | directory must not share one T3 MCP entry. |
| 17 | |
| 18 | - **1.x** uses one T3-managed chat server per thread, so threads cannot replace each other's |
| 19 | connection. Catalog and text-generation work can share the |
| 20 | [instance-owned helper](../../packages/provider-opencode/src/server/OpenCodeServerOwner.ts), which closes |
| 21 | after an idle period. See the [1.x adapter](../../packages/provider-opencode/src/server/adapter.ts). |
| 22 | - **2.x** serves every directory from one |
| 23 | [server per instance](../../packages/provider-opencode/src/server/v2/OpenCode2Server.ts). Each thread |
| 24 | registers its own `t3-code-<thread>` MCP entry, and session permission rules deny every other |
| 25 | thread's entry. See the [2.x adapter](../../packages/provider-opencode/src/server/v2/adapter.ts). |
| 26 | |
| 27 | External OpenCode servers remain externally owned and can require an external restart to pick up |
| 28 | configuration changes. OpenCode stores "always" approval grants for the whole project. Automatic |
| 29 | full-access replies use `once` so they cannot widen a supervised thread's permissions on a shared |
| 30 | server. On 2.x, a session-wide approval also replies `once` and becomes T3's own rule on that session. |
| 31 | |
| 32 | Pi runs the user's own `pi` install in RPC mode and owns native extension, package, and project |
| 33 | trust discovery. T3 injects only its namespaced MCP bridge, so a Pi session behaves as it does in |
| 34 | the Pi TUI. Pi session files back native resume, rollback, and same-instance thread forks. |
| 35 | Forks use Pi's CLI in the destination directory because RPC session switching retains the source |
| 36 | session's cwd. Provider switches still use portable handoff summaries. |
| 37 | See the [adapter](../../packages/provider-pi/src/server/adapter.ts). |
| 38 | |
| 39 | Antigravity separates account profiles per instance while sharing installed executables across the |
| 40 | environment. It forces file-based credential storage because the native macOS keychain entry would |
| 41 | otherwise be shared across instances. The launch environment removes ambient Google credentials, |
| 42 | so an instance cannot silently use another account or billing project. The agent also resolves |
| 43 | its user-global skill directories under that profile, so the profile links those two directories |
| 44 | back to the user's real `~/.gemini`; MCP servers, hooks, and rules there stay out of the profile. |
| 45 | See [profile isolation](../../apps/server/src/provider/antigravityAuthSupport.ts). |
| 46 | |
| 47 | The [Antigravity installer](../../apps/server/src/provider/AntigravityInstallation.ts) outlives |
| 48 | client connections and provider-instance rebuilds. Releases are immutable, with an atomic pointer |
| 49 | selecting the version for new processes. Running processes hold leases on their version. Updates |
| 50 | and removal must respect those leases instead of replacing executables under a running agent. |
| 51 | |
| 52 | ## Setup must not happen as a health-check side effect |
| 53 | |
| 54 | Opening a provider session can start MCP servers, run hooks, or launch a login browser. |
| 55 | [Grok probes](../../packages/provider-grok/src/server/status.ts) avoid authentication and |
| 56 | session creation for this reason. Antigravity likewise reserves authenticated catalog sessions for |
| 57 | explicit setup or model refresh; background checks use initialization only. |
| 58 | |
| 59 | [Antigravity sign-in](../../apps/server/src/provider/AntigravityAuth.ts) belongs to the initiating |
| 60 | T3 auth session. The client carries the return URL back to the environment because the provider's |
| 61 | loopback listener may be on another machine. Forward only the callback for the owned pending flow; |
| 62 | a successful callback HTTP request is not proof that provider authentication finished. The native |
| 63 | process owns token exchange and storage. |
| 64 | |
| 65 | Managed ChatGPT sign-in for a remote environment can finish on a local primary. The |
| 66 | [primary handoff](../../apps/server/src/provider/CodexChatGptHandoff.ts) uses an ephemeral |
| 67 | credential store and the destination's environment ID. It exchanges and verifies the code before |
| 68 | transferring the issued client registration and tokens. Only the destination persists and refreshes |
| 69 | that session; retaining a primary refresh session would race refresh-token rotation. Without a local |
| 70 | primary, the client uses the remote callback completion flow. |
| 71 | |
| 72 | Antigravity sign-out closes admission to new processes and stops existing processes before clearing account |
| 73 | metadata. Otherwise a helper or resumed session could retain the old account. Cached model lists |
| 74 | do not establish current access, and an authoritative empty catalog must clear the old list. |
| 75 | |
| 76 | Antigravity text-generation helpers deny tool requests, but native hooks and MCP configuration can |
| 77 | run before the prompt. They reject profiles with such configuration before launch. Prompt |
| 78 | instructions and tool denial do not create a native sandbox. |
| 79 | See [helper constraints](../../apps/server/src/textGeneration/AntigravityTextGeneration.ts). |
| 80 | |
| 81 | ## Provider updates run only through the owning installer |
| 82 | |
| 83 | A package manager runs only when the resolved executable's path proves it owns the install. Homebrew |
| 84 | and npm are proven by the real path (symlinks followed): a versioned keg or cask under |
| 85 | `brew --prefix`, or `<prefix>/lib/node_modules/<pkg>/` (Windows: the shim beside `node_modules`). |
| 86 | Native installer layouts and the global directories of pnpm, Bun, Yarn, and Vite+ may match on |
| 87 | either the resolved path or its real target, since those installers place real files or their own |
| 88 | symlinks there. Volta is proven by its `volta-shim` link plus the package's image directory. When |
| 89 | nothing is proven, the provider's own updater (`claude update`, `codex update`, `opencode upgrade`, |
| 90 | `pi update --self`, `grok update`) runs instead, because each one detects its installer itself; |
| 91 | the runner's version check catches an updater that exits 0 without updating. Mise installs stay |
| 92 | manual-only because their version is pinned in mise's config. npm updates pin `--prefix` because the |
| 93 | `npm` on `PATH` can belong to a different Node than the one that owns the provider. Homebrew |
| 94 | compares against `brew info` since casks trail npm by hours; native installs share npm's version |
| 95 | train, so the registry stays authoritative for them. |
| 96 | See the [resolver](../../apps/server/src/provider/providerMaintenance.ts). |
| 97 | |
| 98 | Ownership is cached per instance and re-read immediately before an update runs. The |
| 99 | [runner](../../apps/server/src/provider/providerMaintenanceRunner.ts) refuses when the lock key |
| 100 | changed since the advisory, and reports success only when the refreshed provider is still installed |
| 101 | with a readable, current version. |
| 102 | |
| 103 | ## Protocol traps |
| 104 | |
| 105 | Codex async questions arrive as notifications and are answered with a new user message. There is |
| 106 | no pending RPC response to send. The |
| 107 | [adapter](../../apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts) persists them as |
| 108 | `user_input_request` turn items and runtime requests with `responseCapability: { type: "message" }`. |
| 109 | Their execution nodes do not block the run. Web, desktop, and mobile use their normal question |
| 110 | panels, and requests remain pending after a turn finishes, a provider exits, or the server restarts. |
| 111 | |
| 112 | `runtime-request.respond` reads the persisted request and question item, validates required |
| 113 | answers, and commits the resolution and a user message in one transaction. Repeating the same |
| 114 | command returns its receipt without posting the answer twice. The normal message path starts or |
| 115 | resumes a run, queues behind active work, or steers when the adapter supports it. Blocking questions |
| 116 | retain the provider's live response path. Do not infer that a request has disappeared merely because |
| 117 | it is outside the recent history window. |
| 118 | |
| 119 | Native `/goal` state belongs to the provider and is mirrored on the provider thread. Codex starts |
| 120 | the next goal turn on its own milliseconds after the last one completes, so the |
| 121 | [adapter](../../apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts) keeps the run open |
| 122 | and adds that turn to it. One run can therefore own several native turns. `/goal` commands that |
| 123 | start no Codex turn settle on a provider turn without a native ref, which native rollback must |
| 124 | not count. Claude's SDK mode emits no goal events; its adapter reads goal state from the |
| 125 | synthetic command output and Stop hook feedback in the transcript. |
| 126 | |
| 127 | Capabilities must describe what the provider can actually do. Antigravity can capture workspace |
| 128 | checkpoints but cannot roll back its conversation. The [checkpoint boundary](./overview.md#turn-completion-and-checkpoints) |
| 129 | therefore rejects revert before touching files. Native permission and question option IDs must |
| 130 | also survive normalization; a display label is not necessarily a valid reply. |
| 131 | |
| 132 | ## Attachments and stored history |
| 133 | |
| 134 | Attachments live outside the project workspace. The |
| 135 | [attachment boundary](../../apps/server/src/orchestration-v2/AttachmentClaims.ts) validates and claims |
| 136 | uploads for a thread; adapters choose native input formats for those environment-local files. |
| 137 | A path in the prompt does not grant filesystem access. Keep provider sandbox and approval rules |
| 138 | in force; copying uploads into the project to bypass them changes that boundary. |
| 139 | |
| 140 | File attachments introduced a replay compatibility limit. Image-only clients cannot decode |
| 141 | file-bearing messages, and an image-only server can fail the entire environment's startup when |
| 142 | replaying one such event. Rollouts and downgrades must account for persisted history as well as |
| 143 | current client support. |
| 144 | |
| 145 | ## Provider diagnostics |
| 146 | |
| 147 | Native event logs retain lifecycle events, responses, and failures. Token deltas and duplicate raw |
| 148 | frames are filtered before adapters copy or redact payloads. The filter accepts both legacy native |
| 149 | events and v2 protocol envelopes; decode failures remain visible through diagnostic frames. |
| 150 | |
| 151 | Log payloads have a 64 KiB encoded budget. Large or deeply nested payloads become structural |
| 152 | summaries that retain routing identifiers, methods, status, and error fields. Traversal is bounded |
| 153 | before redaction and serialization, so logging a large response does not require several full |
| 154 | copies. These limits apply to diagnostics; provider event handling is unchanged. |
| 155 | |
| 156 | Codex resumes with metadata-only reads when it needs a thread's identity and update time. Its |
| 157 | initialization capabilities opt out of `turn/diff/updated`: T3 derives diffs from checkpoints. |
| 158 | The logger filters those notifications before traversal when an older provider still sends them. |
| 159 | |
| 160 | Model classification has its own [manifest constraints](./model-manifest.md). Assistant-reference |
| 161 | handling is documented under [citations](./assistant-citations.md). |