authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-10-08 09:16:13-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-08 16:16:13+00:00
loga65b43892f14bd6c1280296a37a11bd5234be00d
treed733fe5929803410a4119b0d56fee70e31ab7c07
parent9a3070bcf023c3c025633659addeef4cf628133a
signature Signed by PGP key B5690EEEBB952194

docs(internals): add a checklist for adding a provider (#17229)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

3 files changed, 119 insertions(+), 1 deletions(-)

docs/README.md+1
......@@ -38,6 +38,7 @@ source alone does not explain. Most code changes do not need an internal documen
3838- [Glossary](./internals/glossary.md)
3939- [Connection runtime](./internals/connection-runtime.md)
4040- [Providers](./internals/providers.md)
41- [Adding a provider](./internals/adding-a-provider.md)
4142- [Pull request file revisions](./internals/pull-request-file-revisions.md)
4243- [Model classification](./internals/model-manifest.md)
4344- [Remote environments](./internals/remote.md)
docs/internals/adding-a-provider.md created+117
......@@ -0,0 +1,117 @@
1# Adding a provider
2
3A provider integration is judged by whether every T3 feature behaves honestly on it, not by
4whether a turn runs. This page lists the decisions and evidence a new driver needs. The
5[provider constraints](./providers.md) and the
6[capability system](../orchestration-v2/provider-capability-system.md) explain why these rules exist.
7
8## Choose the integration shape
9
10- **ACP agents** start as [ACP Registry](../user/providers-acp.md) entries, which follow the ACP
11 spec with no per-agent handling. An agent gets a dedicated driver only when it needs behavior the
12 spec does not cover, and then it is a small flavor over the
13 [shared ACP adapter](../../apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts), like Grok
14 and Antigravity. Never add agent-id checks to the generic registry adapter.
15- **Other protocols** get a native adapter that implements
16 [`ProviderAdapterV2`](../../apps/server/src/orchestration-v2/ProviderAdapter.ts), like Codex,
17 Claude, Cursor, OpenCode, Pi, and Muse.
18
19Provider-specific behavior stays in the adapter and driver. Orchestration and clients read
20capabilities, never the driver kind.
21
22## Report only what the provider does
23
24- **Capabilities.** Set every flag in `OrchestrationV2ProviderCapabilities` to what the provider
25 really does. Turn off fork, rollback, steering, or subagent support it lacks; orchestration then
26 falls back, for example to portable context handoff for forks. A capability left on that fails
27 at runtime is a bug.
28- **Permission modes.** Offer only modes the provider enforces natively, through
29 `supportedRuntimeModes` in the provider presentation ([Grok](../../apps/server/src/provider/GrokProvider.ts)
30 and [Pi](../../apps/server/src/provider/PiProvider.ts) are examples). Do not imitate a missing
31 mode by answering approvals in T3: T3's check is weaker than the agent's own enforcement. The
32 server runs an unoffered stored mode as Supervised
33 ([`RuntimePolicy.ts`](../../apps/server/src/orchestration-v2/RuntimePolicy.ts)).
34- **Approval and question options.** Pass the provider's own option IDs through unchanged. Every
35 request needs a way to decline that the provider honors.
36- **Interaction modes.** Hide the plan toggle (`showInteractionModeToggle: false`) unless the
37 provider's plan output becomes T3's proposed-plan card.
38
39## Process, account, and setup boundaries
40
41- **Instances.** Session state, catalogs, and credentials belong to a provider instance, not the
42 driver. Remove ambient credentials the instance did not configure, so two instances cannot
43 silently share an account or billing.
44- **Status checks.** Background status and model refreshes must not open a session that can start
45 MCP servers, run hooks, or launch a login. Keep heavier probes behind an explicit refresh.
46- **T3 MCP tools.** Inject the thread's MCP server so agents can use T3's tools, and make a turn
47 survive when that server is unreachable.
48- **Updates.** Run an update only through the installer that provably owns the binary; otherwise
49 leave it manual. See [`providerMaintenance.ts`](../../apps/server/src/provider/providerMaintenance.ts).
50
51## Tests
52
53Prove adapter behavior with replay fixtures: a provider transcript replayed through the real
54orchestrator, adapter, and projections. See the [testing strategy](../orchestration-v2/testing-strategy.md).
55
56- Record transcripts from the real provider with a recorder script in
57 [`apps/server/scripts`](../../apps/server/scripts) (`record-*-replay-fixture.ts`). A new protocol
58 usually needs its own recorder.
59- Cover the shared scenarios under
60 [`testkit/fixtures`](../../apps/server/src/orchestration-v2/testkit/fixtures) that the provider
61 supports: `simple`, `multi_turn`, `queued_turn`, `turn_interrupt`, `message_steering`,
62 `provider_thread_resume`, and the `tool_call_*` approval cases. Add provider-specific fixtures
63 for behavior the shared ones do not reach.
64- Unit tests are for pure logic and for failures a real provider cannot produce on demand. A test
65 that drives the adapter with hand-written frames is a guess about the protocol; live testing
66 regularly finds what those guesses missed.
67- A gated live test against the real binary is welcome, but it does not replace replays, because
68 CI skips it.
69
70## Traps several providers hit
71
72Each of these broke more than one adapter. A replay fixture for each case your provider can
73produce catches them.
74
75- **Settings applied before the provider advertises them.** Agents often list config options after
76 the session opens, in a later update, or only once a model runs (an effort option that exists
77 only for models with effort). An agent can accept a write before then and change nothing. Wait
78 for the option to be advertised before writing it, and treat a stored option the live session
79 does not offer as skipped, not as a failure.
80- **"Default" is a sentinel, not a model.** A default-model selection must switch the session back
81 after a named model ran, including in a resumed session. Never send the sentinel to the
82 provider.
83- **Stop sent before the turn started.** A cancel that reaches the provider before it registered
84 the prompt can be ignored while the turn runs to completion, or can leave a session the provider
85 never saved. Find the provider's own turn-started signal, and test a Stop pressed right after
86 sending.
87- **Work the provider starts on its own.** Background commands, subagents, workflows, and goals can
88 finish or start a turn after T3's turn settled. Offer a continuation through
89 [`ProviderContinuationRequests`](../../apps/server/src/orchestration-v2/ProviderContinuationRequests.ts)
90 so the parent wakes, and report `hasPendingBackgroundWork` so the session is not released as
91 idle. Dropping it, or killing the session, loses the result.
92- **Subagents outlive the run that launched them.** A child can report after its parent's turn
93 settled, or resume during a later run. Route its updates by the subagent, not by whichever run
94 is currently active.
95- **A failed or stopped turn leaves nothing open.** Settle the turn's tool calls, subagents, and
96 pending approvals and questions. Each terminal path needs its own replay: completion, Stop,
97 provider failure, and process exit.
98- **Usage limits are not generic failures.** Classify quota and rate-limit errors as `usage_limit`
99 with a reset time when the provider gives one, so the thread shows Limited and can resume.
100- **Paths and environment.** Test a symlinked workspace, such as macOS `/tmp`; an agent that
101 compares canonical paths fails every turn there. Drop inherited credentials the instance did not
102 configure. Report unknown sign-in as unknown rather than as signed in.
103
104## Where a driver plugs in
105
106- **Contracts:** settings schema and patch, default model, and display name in
107 [`packages/contracts`](../../packages/contracts/src). New providers are off by default.
108- **Server:** the driver in [`provider/Drivers`](../../apps/server/src/provider/Drivers) and its entry
109 in [`builtInDrivers.ts`](../../apps/server/src/provider/builtInDrivers.ts). Also its position in
110 the [status order](../../apps/server/src/provider/providerStatusCache.ts), a compatibility policy
111 in [`model-manifest.json`](../../apps/server/src/provider/model-manifest.json) (bump `updatedAt`;
112 see [model manifest](./model-manifest.md)), and text generation for titles and commit messages.
113- **Clients:** web settings metadata and badge, provider icons on web and mobile, and settings
114 search terms.
115- **Docs:** a `docs/user/providers-<name>.md` guide in the product's voice, a row in the
116 [install](../user/install.md#providers) table, and any provider difference that changes the
117 [permission modes](../user/permission-modes.md) page.
docs/internals/providers.md+1-1
......@@ -7,7 +7,7 @@ instead of spreading provider checks through orchestration and clients.
77
88A driver kind identifies an integration; an instance identifies one configuration and account
99lifecycle. Route work by instance, so two accounts using the same driver do not share mutable
10session or catalog state.
10session or catalog state. For a new driver, start with [adding a provider](./adding-a-provider.md).
1111
1212## Process and account isolation
1313