1# Provider Capability System
2
3V2 must not assume every provider can do what Codex can do. Provider behavior should be expressed through capabilities and policies, not provider-name conditionals spread across orchestration.
4
5## Capability Shape
6
7```ts
8type ProviderCapabilities = {
9 sessions: SessionCapabilities;
10 threads: ThreadCapabilities;
11 turns: TurnCapabilities;
12 streaming: StreamingCapabilities;
13 tools: ToolCapabilities;
14 approvals: ApprovalCapabilities;
15 planning: PlanningCapabilities;
16 subagents: SubagentCapabilities;
17 context: ContextCapabilities;
18 checkpointing: CheckpointCapabilities;
19 identity: IdentityCapabilities;
20};
21```
22
23Capabilities should be versioned and emitted by each adapter at session start.
24
25## Session Capabilities
26
27```ts
28type SessionCapabilities = {
29 supportsMultipleProviderThreadsPerSession: boolean;
30 supportsModelSwitchInSession: boolean;
31 supportsProviderSwitchingViaHandoff: boolean;
32 supportsRuntimeModeSwitchInSession: boolean;
33 pendingRequestsSurviveRestart: boolean;
34};
35```
36
37Policy examples:
38
39- If model switching is unsupported, start a new provider session.
40- If pending requests do not survive restart, mark old requests `not_resumable`.
41
42Provider-thread resumption is not a capability. It is a required adapter primitive. Adapters must be able to start from a stored provider cursor/session/thread handle or return a runtime resume failure.
43
44## Thread Capabilities
45
46```ts
47type ThreadCapabilities = {
48 canCreateEmptyThread: boolean;
49 canReadThreadSnapshot: boolean;
50 canRollbackThread: boolean;
51 canForkThread: boolean;
52 canForkFromTurn: boolean;
53 canForkFromSubagentThread: boolean;
54 exposesNativeThreadId: boolean;
55};
56```
57
58Codex can expose strong thread ids and rollback snapshots. Claude may support native forking through its own model/session primitives. Other providers may only support synthetic app forks.
59
60## Turn Capabilities
61
62```ts
63type TurnCapabilities = {
64 exposesNativeTurnId: boolean;
65 emitsTurnStarted: boolean;
66 emitsTurnCompleted: boolean;
67 supportsInterrupt: boolean;
68 supportsActiveSteering: boolean;
69 supportsSteeringByInterruptRestart: boolean;
70 supportsQueuedMessages: boolean;
71 terminalStatusQuality: "strong" | "weak" | "none";
72};
73```
74
75If `terminalStatusQuality` is weak, V2 should use adapter policy to infer terminal state, but still mark correlation strength accordingly.
76
77`supportsActiveSteering` means the provider can modify an in-flight turn directly. `supportsSteeringByInterruptRestart` means V2 can implement app-level steering by interrupting the active turn and starting a replacement attempt. Most providers should support the latter if they support interruption and normal follow-up turns.
78
79## Streaming Capabilities
80
81```ts
82type StreamingCapabilities = {
83 streamsAssistantText: boolean;
84 streamsReasoning: boolean;
85 streamsToolOutput: boolean;
86 streamsPlanText: boolean;
87 emitsMessageCompleted: boolean;
88};
89```
90
91If message completion is not emitted, the normalizer closes assistant messages at root run terminal.
92
93## Tool Capabilities
94
95```ts
96type ToolCapabilities = {
97 exposesToolItemIds: boolean;
98 emitsToolStarted: boolean;
99 emitsToolCompleted: boolean;
100 emitsToolOutput: boolean;
101 supportsMcpTools: boolean;
102 supportsDynamicToolCallbacks: boolean;
103};
104```
105
106Weak providers may produce only textual tool summaries. The adapter can still create ordered `turnItems` and execution nodes by scoped ordinal.
107
108## Approval Capabilities
109
110```ts
111type ApprovalCapabilities = {
112 supportsCommandApproval: boolean;
113 supportsFileReadApproval: boolean;
114 supportsFileChangeApproval: boolean;
115 supportsApplyPatchApproval: boolean;
116 approvalsHaveNativeRequestIds: boolean;
117 approvalCallbacksAreLiveOnly: boolean;
118 approvalsCanOriginateFromSubagents: boolean;
119};
120```
121
122The UI should show pending approvals from any execution node, including subagents, if they are respondable.
123
124## Planning Capabilities
125
126```ts
127type PlanningCapabilities = {
128 emitsPlanUpdated: boolean;
129 emitsTodoList: boolean;
130 emitsProposedPlan: boolean;
131 supportsStructuredQuestions: boolean;
132 planDeltasHaveItemIds: boolean;
133};
134```
135
136Mapping policy:
137
138- `emitsTodoList`: update live run progress.
139- `emitsProposedPlan`: create accept/implementable plan artifacts.
140- `supportsStructuredQuestions`: create respondable user-input requests.
141
142If a provider only emits plain assistant text, the app should not pretend it has structured plans.
143
144## Subagent Capabilities
145
146```ts
147type SubagentCapabilities = {
148 supportsSubagents: boolean;
149 exposesSubagentThreadIds: boolean;
150 emitsSubagentLifecycle: boolean;
151 canWaitForSubagents: boolean;
152 canCloseSubagents: boolean;
153 canForkSubagentThread: boolean;
154};
155```
156
157When `exposesSubagentThreadIds` is true, subagent provider threads become addressable `ProviderThread` records. When false, subagents can still be shown as nested execution nodes if the provider exposes enough lifecycle information.
158
159## Context Handoff Capabilities
160
161Context handoff controls provider switching and provider-thread reconstruction.
162
163```ts
164type ContextCapabilities = {
165 acceptsSystemContext: boolean;
166 acceptsDeveloperContext: boolean;
167 acceptsSyntheticUserContext: boolean;
168 canGenerateSummaries: boolean;
169 canConsumeHandoffSummaries: boolean;
170 supportsDeltaHandoff: boolean;
171 supportsFullThreadHandoff: boolean;
172 maxRecommendedHandoffChars: number | null;
173};
174```
175
176Policy examples:
177
178- If `supportsDeltaHandoff` is available, return to a previous provider thread with a summary of only off-provider runs.
179- If delta handoff is unsupported or low quality, create a new provider thread with a full thread summary.
180- If the provider cannot accept explicit context, provider switching should require synthetic user context or be marked unsupported.
181- If no provider can generate summaries, use the app's configured summarization provider or a local summarizer capability.
182
183## Checkpoint Capabilities
184
185Checkpointing is primarily app-owned, but provider conversation rollback is provider-dependent.
186
187```ts
188type CheckpointCapabilities = {
189 appCanCheckpointFilesystem: boolean;
190 supportsNestedCheckpointScopes: boolean;
191 providerCanRollbackConversation: boolean;
192 providerRollbackReturnsSnapshot: boolean;
193 providerCanReadConversationSnapshot: boolean;
194};
195```
196
197If provider rollback is unsupported, filesystem rollback can still happen, but provider conversation state must be restarted or marked divergent. Nested checkpoint scopes are app-owned; providers only affect whether their nested provider conversations can be rolled back to match restored filesystem state.
198
199## Identity Capabilities
200
201```ts
202type IdentityCapabilities = {
203 nativeThreadIds: "strong" | "weak" | "none";
204 nativeTurnIds: "strong" | "weak" | "none";
205 nativeItemIds: "strong" | "weak" | "none";
206 nativeRequestIds: "strong" | "weak" | "none";
207};
208```
209
210This controls how the normalizer correlates events:
211
212- `strong`: use native id as scoped provider ref.
213- `weak`: use native id plus ordinal/fingerprint.
214- `none`: allocate by scoped ordinal.
215
216## Degradation Policies
217
218Every feature should declare what happens when capability is missing.
219
220Examples:
221
222```text
223interrupt unsupported
224 -> stop session if allowed, otherwise mark unsupported
225
226active steering unsupported
227 -> interrupt active turn and restart the run as a steering replacement attempt
228
229fork unsupported
230 -> synthetic fork from app projection if policy allows
231
232rollback unsupported
233 -> restore filesystem checkpoint, restart provider context, mark provider state divergent
234
235nested checkpoint unsupported
236 -> capture only root-run checkpoint and record child filesystem activity as uncheckpointed
237
238provider switch return unsupported
239 -> create fresh provider thread with full app-thread summary
240
241structured approvals unsupported
242 -> provider runs under configured sandbox policy; no approval UI
243
244plan_updated unsupported
245 -> no live todo UI; rely on assistant messages
246```
247
248The UI should not hide unsupported behavior behind provider-specific errors. It should receive typed capability results.
249
250## Adapter Contract
251
252Each provider adapter should expose:
253
254```ts
255type ProviderAdapter = {
256 getCapabilities(): ProviderCapabilities;
257 startSession(input): ProviderSession;
258 ensureProviderThread(input): ProviderThread;
259 sendRun(input): ProviderTurnStartResult;
260 steerRun?(input): void;
261 interrupt(input): void;
262 respondToRequest(input): void;
263 readThreadSnapshot?(input): ProviderThreadSnapshot;
264 rollbackThread?(input): ProviderThreadSnapshot | void;
265 forkThread?(input): ProviderThread;
266 streamEvents(): Stream<ProviderAdapterEvent>;
267};
268```
269
270Optional methods are guarded by capabilities. The orchestration layer should not call optional methods without checking capability or going through a policy wrapper.
271
272Raw provider frames should be logged by the provider transport/runtime as bounded diagnostics. Adapter streams should expose normalized provider events that the V2 normalizer can turn into app orchestration events.
273
274## Capability-Driven UI
275
276The UI should receive capability-informed affordances:
277
278- show or hide fork action by fork source.
279- show interrupt as available, destructive fallback, or unavailable.
280- show approvals as respondable or expired.
281- show plan/todo panels only when structured plan artifacts exist.
282- show rollback only when app checkpoint exists, and annotate provider rollback support.
283
284This keeps the product predictable across Codex, Claude, Cursor, OpenCode, and future providers.