docs/orchestration-v2/provider-capability-system.md

Provider Capability System

V2 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.

Capability Shape

ts
1type ProviderCapabilities = {
2 sessions: SessionCapabilities;
3 threads: ThreadCapabilities;
4 turns: TurnCapabilities;
5 streaming: StreamingCapabilities;
6 tools: ToolCapabilities;
7 approvals: ApprovalCapabilities;
8 planning: PlanningCapabilities;
9 subagents: SubagentCapabilities;
10 context: ContextCapabilities;
11 checkpointing: CheckpointCapabilities;
12 identity: IdentityCapabilities;
13};

Capabilities should be versioned and emitted by each adapter at session start.

Session Capabilities

ts
1type SessionCapabilities = {
2 supportsMultipleProviderThreadsPerSession: boolean;
3 supportsModelSwitchInSession: boolean;
4 supportsProviderSwitchingViaHandoff: boolean;
5 supportsRuntimeModeSwitchInSession: boolean;
6 pendingRequestsSurviveRestart: boolean;
7};

Policy examples:

  • If model switching is unsupported, start a new provider session.
  • If pending requests do not survive restart, mark old requests not_resumable.

Provider-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.

Thread Capabilities

ts
1type ThreadCapabilities = {
2 canCreateEmptyThread: boolean;
3 canReadThreadSnapshot: boolean;
4 canRollbackThread: boolean;
5 canForkThread: boolean;
6 canForkFromTurn: boolean;
7 canForkFromSubagentThread: boolean;
8 exposesNativeThreadId: boolean;
9};

Codex 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.

Turn Capabilities

ts
1type TurnCapabilities = {
2 exposesNativeTurnId: boolean;
3 emitsTurnStarted: boolean;
4 emitsTurnCompleted: boolean;
5 supportsInterrupt: boolean;
6 supportsActiveSteering: boolean;
7 supportsSteeringByInterruptRestart: boolean;
8 supportsQueuedMessages: boolean;
9 terminalStatusQuality: "strong" | "weak" | "none";
10};

If terminalStatusQuality is weak, V2 should use adapter policy to infer terminal state, but still mark correlation strength accordingly.

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.

Streaming Capabilities

ts
1type StreamingCapabilities = {
2 streamsAssistantText: boolean;
3 streamsReasoning: boolean;
4 streamsToolOutput: boolean;
5 streamsPlanText: boolean;
6 emitsMessageCompleted: boolean;
7};

If message completion is not emitted, the normalizer closes assistant messages at root run terminal.

Tool Capabilities

ts
1type ToolCapabilities = {
2 exposesToolItemIds: boolean;
3 emitsToolStarted: boolean;
4 emitsToolCompleted: boolean;
5 emitsToolOutput: boolean;
6 supportsMcpTools: boolean;
7 supportsDynamicToolCallbacks: boolean;
8};

Weak providers may produce only textual tool summaries. The adapter can still create ordered turnItems and execution nodes by scoped ordinal.

Approval Capabilities

ts
1type ApprovalCapabilities = {
2 supportsCommandApproval: boolean;
3 supportsFileReadApproval: boolean;
4 supportsFileChangeApproval: boolean;
5 supportsApplyPatchApproval: boolean;
6 approvalsHaveNativeRequestIds: boolean;
7 approvalCallbacksAreLiveOnly: boolean;
8 approvalsCanOriginateFromSubagents: boolean;
9};

The UI should show pending approvals from any execution node, including subagents, if they are respondable.

Planning Capabilities

ts
1type PlanningCapabilities = {
2 emitsPlanUpdated: boolean;
3 emitsTodoList: boolean;
4 emitsProposedPlan: boolean;
5 supportsStructuredQuestions: boolean;
6 planDeltasHaveItemIds: boolean;
7};

Mapping policy:

  • emitsTodoList: update live run progress.
  • emitsProposedPlan: create accept/implementable plan artifacts.
  • supportsStructuredQuestions: create respondable user-input requests.

If a provider only emits plain assistant text, the app should not pretend it has structured plans.

Subagent Capabilities

ts
1type SubagentCapabilities = {
2 supportsSubagents: boolean;
3 exposesSubagentThreadIds: boolean;
4 emitsSubagentLifecycle: boolean;
5 canWaitForSubagents: boolean;
6 canCloseSubagents: boolean;
7 canForkSubagentThread: boolean;
8};

When 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.

Context Handoff Capabilities

Context handoff controls provider switching and provider-thread reconstruction.

ts
1type ContextCapabilities = {
2 acceptsSystemContext: boolean;
3 acceptsDeveloperContext: boolean;
4 acceptsSyntheticUserContext: boolean;
5 canGenerateSummaries: boolean;
6 canConsumeHandoffSummaries: boolean;
7 supportsDeltaHandoff: boolean;
8 supportsFullThreadHandoff: boolean;
9 maxRecommendedHandoffChars: number | null;
10};

Policy examples:

  • If supportsDeltaHandoff is available, return to a previous provider thread with a summary of only off-provider runs.
  • If delta handoff is unsupported or low quality, create a new provider thread with a full thread summary.
  • If the provider cannot accept explicit context, provider switching should require synthetic user context or be marked unsupported.
  • If no provider can generate summaries, use the app's configured summarization provider or a local summarizer capability.

Checkpoint Capabilities

Checkpointing is primarily app-owned, but provider conversation rollback is provider-dependent.

ts
1type CheckpointCapabilities = {
2 appCanCheckpointFilesystem: boolean;
3 supportsNestedCheckpointScopes: boolean;
4 providerCanRollbackConversation: boolean;
5 providerRollbackReturnsSnapshot: boolean;
6 providerCanReadConversationSnapshot: boolean;
7};

If 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.

Identity Capabilities

ts
1type IdentityCapabilities = {
2 nativeThreadIds: "strong" | "weak" | "none";
3 nativeTurnIds: "strong" | "weak" | "none";
4 nativeItemIds: "strong" | "weak" | "none";
5 nativeRequestIds: "strong" | "weak" | "none";
6};

This controls how the normalizer correlates events:

  • strong: use native id as scoped provider ref.
  • weak: use native id plus ordinal/fingerprint.
  • none: allocate by scoped ordinal.

Degradation Policies

Every feature should declare what happens when capability is missing.

Examples:

text
1interrupt unsupported
2 -> stop session if allowed, otherwise mark unsupported
3
4active steering unsupported
5 -> interrupt active turn and restart the run as a steering replacement attempt
6
7fork unsupported
8 -> synthetic fork from app projection if policy allows
9
10rollback unsupported
11 -> restore filesystem checkpoint, restart provider context, mark provider state divergent
12
13nested checkpoint unsupported
14 -> capture only root-run checkpoint and record child filesystem activity as uncheckpointed
15
16provider switch return unsupported
17 -> create fresh provider thread with full app-thread summary
18
19structured approvals unsupported
20 -> provider runs under configured sandbox policy; no approval UI
21
22plan_updated unsupported
23 -> no live todo UI; rely on assistant messages

The UI should not hide unsupported behavior behind provider-specific errors. It should receive typed capability results.

Adapter Contract

Each provider adapter should expose:

ts
1type ProviderAdapter = {
2 getCapabilities(): ProviderCapabilities;
3 startSession(input): ProviderSession;
4 ensureProviderThread(input): ProviderThread;
5 sendRun(input): ProviderTurnStartResult;
6 steerRun?(input): void;
7 interrupt(input): void;
8 respondToRequest(input): void;
9 readThreadSnapshot?(input): ProviderThreadSnapshot;
10 rollbackThread?(input): ProviderThreadSnapshot | void;
11 forkThread?(input): ProviderThread;
12 streamEvents(): Stream<ProviderAdapterEvent>;
13};

Optional methods are guarded by capabilities. The orchestration layer should not call optional methods without checking capability or going through a policy wrapper.

Raw 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.

Capability-Driven UI

The UI should receive capability-informed affordances:

  • show or hide fork action by fork source.
  • show interrupt as available, destructive fallback, or unavailable.
  • show approvals as respondable or expired.
  • show plan/todo panels only when structured plan artifacts exist.
  • show rollback only when app checkpoint exists, and annotate provider rollback support.

This keeps the product predictable across Codex, Claude, Cursor, OpenCode, and future providers.