Co-authored-by: maria-rcks <maria@kuuro.net> Co-authored-by: Bilal Bakr <62337003+Bil0000@users.noreply.github.com> Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com> Co-authored-by: Vitalii Yehorov <vitalyiegorov@gmail.com> Co-authored-by: Jake Leventhal <jakeleventhal@me.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Alex Southwell <saphid@gmail.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Nicholas Wasmiller <derped@mineperial.com> Co-authored-by: PB <poilmb@gmail.com> Co-authored-by: Exotic <118054752+extoci@users.noreply.github.com> Co-authored-by: Yash Singh <saiansh2525@gmail.com> Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Theo Browne <me@t3.gg> Co-authored-by: Gabriel De Andrade <30420087+gabrielelpidio@users.noreply.github.com> Co-authored-by: Dara Adedeji <76637177+SunkenInTime@users.noreply.github.com> Co-authored-by: scratchyone <scratchywon@gmail.com> Co-authored-by: Dominic Roy <dominic@sdko.org> Co-authored-by: chukfinley <chuk@chuk.dev> Co-authored-by: Primož Ajdišek <bigpod@bigpod.si> Co-authored-by: benthecarman <benthecarman@live.com> Co-authored-by: NaveDanan <nave0712@gmail.com> Co-authored-by: aaditagrawal <103925638+aaditagrawal@users.noreply.github.com> Co-authored-by: Aditya Garud <153842990+yashranaway@users.noreply.github.com> Co-authored-by: Nick Anisimov <n.anisimov.23@gmail.com> Co-authored-by: MacKinley Smith <smithmackinley@gmail.com> Co-authored-by: Yordis Prieto <yordis.prieto@gmail.com> Co-authored-by: t3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com> Co-authored-by: AKolenda <akole779@mtroyal.ca> Co-authored-by: Guillermo Casanova <75276669+Gigioxx@users.noreply.github.com> Co-authored-by: Otavio Salvador <otavio@ossystems.com.br> Co-authored-by: Shirish Pothi <183252392+shirishpothi@users.noreply.github.com> Co-authored-by: Ishaan Kothari <ishaanko.mail@gmail.com> Co-authored-by: Bob Fowler <bob@rjf.ca> Co-authored-by: Anton Bezdenezhnykh <gamer392@yandex.ru> Co-authored-by: ValeraZSD <48602572+ValeraZSD@users.noreply.github.com> Co-authored-by: Ephraim <ephraim39hr14m@gmail.com> Co-authored-by: Ryan Ilano <ryanilano@users.noreply.github.com> Co-authored-by: Alex <me@pixp.cc> Co-authored-by: maco <gosarmarcel7@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Tristan Knight <admin@snappeh.com> Co-authored-by: PR Batch Tester <agent@local.test> Co-authored-by: oliver <97427849+flamboh@users.noreply.github.com> Co-authored-by: kamkm <99585688+Kamkmgamer@users.noreply.github.com> Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
9.3 KiB
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
type ProviderCapabilities = {
sessions: SessionCapabilities;
threads: ThreadCapabilities;
turns: TurnCapabilities;
streaming: StreamingCapabilities;
tools: ToolCapabilities;
approvals: ApprovalCapabilities;
planning: PlanningCapabilities;
subagents: SubagentCapabilities;
context: ContextCapabilities;
checkpointing: CheckpointCapabilities;
identity: IdentityCapabilities;
};
Capabilities should be versioned and emitted by each adapter at session start.
Session Capabilities
type SessionCapabilities = {
supportsMultipleProviderThreadsPerSession: boolean;
supportsModelSwitchInSession: boolean;
supportsProviderSwitchingViaHandoff: boolean;
supportsRuntimeModeSwitchInSession: boolean;
pendingRequestsSurviveRestart: boolean;
};
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
type ThreadCapabilities = {
canCreateEmptyThread: boolean;
canReadThreadSnapshot: boolean;
canRollbackThread: boolean;
canForkThread: boolean;
canForkFromTurn: boolean;
canForkFromSubagentThread: boolean;
exposesNativeThreadId: boolean;
};
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
type TurnCapabilities = {
exposesNativeTurnId: boolean;
emitsTurnStarted: boolean;
emitsTurnCompleted: boolean;
supportsInterrupt: boolean;
supportsActiveSteering: boolean;
supportsSteeringByInterruptRestart: boolean;
supportsQueuedMessages: boolean;
terminalStatusQuality: "strong" | "weak" | "none";
};
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
type StreamingCapabilities = {
streamsAssistantText: boolean;
streamsReasoning: boolean;
streamsToolOutput: boolean;
streamsPlanText: boolean;
emitsMessageCompleted: boolean;
};
If message completion is not emitted, the normalizer closes assistant messages at root run terminal.
Tool Capabilities
type ToolCapabilities = {
exposesToolItemIds: boolean;
emitsToolStarted: boolean;
emitsToolCompleted: boolean;
emitsToolOutput: boolean;
supportsMcpTools: boolean;
supportsDynamicToolCallbacks: boolean;
};
Weak providers may produce only textual tool summaries. The adapter can still create ordered turnItems and execution nodes by scoped ordinal.
Approval Capabilities
type ApprovalCapabilities = {
supportsCommandApproval: boolean;
supportsFileReadApproval: boolean;
supportsFileChangeApproval: boolean;
supportsApplyPatchApproval: boolean;
approvalsHaveNativeRequestIds: boolean;
approvalCallbacksAreLiveOnly: boolean;
approvalsCanOriginateFromSubagents: boolean;
};
The UI should show pending approvals from any execution node, including subagents, if they are respondable.
Planning Capabilities
type PlanningCapabilities = {
emitsPlanUpdated: boolean;
emitsTodoList: boolean;
emitsProposedPlan: boolean;
supportsStructuredQuestions: boolean;
planDeltasHaveItemIds: boolean;
};
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
type SubagentCapabilities = {
supportsSubagents: boolean;
exposesSubagentThreadIds: boolean;
emitsSubagentLifecycle: boolean;
canWaitForSubagents: boolean;
canCloseSubagents: boolean;
canForkSubagentThread: boolean;
};
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.
type ContextCapabilities = {
acceptsSystemContext: boolean;
acceptsDeveloperContext: boolean;
acceptsSyntheticUserContext: boolean;
canGenerateSummaries: boolean;
canConsumeHandoffSummaries: boolean;
supportsDeltaHandoff: boolean;
supportsFullThreadHandoff: boolean;
maxRecommendedHandoffChars: number | null;
};
Policy examples:
- If
supportsDeltaHandoffis 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.
type CheckpointCapabilities = {
appCanCheckpointFilesystem: boolean;
supportsNestedCheckpointScopes: boolean;
providerCanRollbackConversation: boolean;
providerRollbackReturnsSnapshot: boolean;
providerCanReadConversationSnapshot: boolean;
};
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
type IdentityCapabilities = {
nativeThreadIds: "strong" | "weak" | "none";
nativeTurnIds: "strong" | "weak" | "none";
nativeItemIds: "strong" | "weak" | "none";
nativeRequestIds: "strong" | "weak" | "none";
};
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:
interrupt unsupported
-> stop session if allowed, otherwise mark unsupported
active steering unsupported
-> interrupt active turn and restart the run as a steering replacement attempt
fork unsupported
-> synthetic fork from app projection if policy allows
rollback unsupported
-> restore filesystem checkpoint, restart provider context, mark provider state divergent
nested checkpoint unsupported
-> capture only root-run checkpoint and record child filesystem activity as uncheckpointed
provider switch return unsupported
-> create fresh provider thread with full app-thread summary
structured approvals unsupported
-> provider runs under configured sandbox policy; no approval UI
plan_updated unsupported
-> 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:
type ProviderAdapter = {
getCapabilities(): ProviderCapabilities;
startSession(input): ProviderSession;
ensureProviderThread(input): ProviderThread;
sendRun(input): ProviderTurnStartResult;
steerRun?(input): void;
interrupt(input): void;
respondToRequest(input): void;
readThreadSnapshot?(input): ProviderThreadSnapshot;
rollbackThread?(input): ProviderThreadSnapshot | void;
forkThread?(input): ProviderThread;
streamEvents(): Stream<ProviderAdapterEvent>;
};
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.