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