# Provider SDK Extensions Guide

Phase 3 keeps an explicit optional layer for provider-native surfaces above
ASM's normalized kernel.

## Kernel Versus Extension Split

Normalized kernel surfaces stay where they already belong:

- `ASM`
- `ASM.Stream`
- `ASM.Result`
- `ASM.ProviderRegistry`

Those APIs continue to own:

- provider selection
- lane selection
- normalized event/result projection
- session/run orchestration

Provider-native extension discovery now lives under
`ASM.Extensions.ProviderSDK`.

Schema ownership follows the same split:

- ASM owns orchestration, provider-option, event, and remote-node envelopes.
- provider-native runtime and protocol schemas stay local to the owning SDK
  repo.
- `NimbleOptions` can still front the public ASM keyword API, but `Zoi` is the
  canonical schema layer underneath new dynamic boundaries.

## Dependency Model

ASM keeps `cli_subprocess_core` required and all provider SDK packages
optional.

- depending only on `:agent_session_manager` gives you the common ASM surface
- adding any provider SDK activates the matching SDK lane and, when one is
  registered, the matching provider-native namespace
- declaring the optional dependency is the only client-app activation step;
  ASM performs discovery and activation automatically

ASM handles those optional dependencies through runtime-checked module loading
and explicit extension/backend boundaries. The kernel never relies on
compile-time warning suppression to reference provider SDK packages.

## Why This Is Separate

`ASM.ProviderRegistry` keeps normalized discovery on its existing surfaces:

- `provider_info/1`: `available_lanes`, `core_capabilities`, `sdk_capabilities`
- `lane_info/2`: `preferred_lane`, `backend`
- `resolve/2`: effective `lane`, `backend`

`ASM.Extensions.ProviderSDK` reports provider-native extension facts:

- explicit optional namespace modules
- provider-native capability inventory
- whether the backing SDK package is loadable locally

That keeps lane discovery and provider-native surface discovery from collapsing
into one API.

## Discovery API

```elixir
alias ASM.Extensions.ProviderSDK

ProviderSDK.extensions()
ProviderSDK.available_extensions()

{:ok, claude_extension} = ProviderSDK.extension(:claude)
{:ok, active_claude_extensions} = ProviderSDK.available_provider_extensions(:claude)
{:ok, codex_extensions} = ProviderSDK.provider_extensions(:codex)
{:ok, codex_native_caps} = ProviderSDK.provider_capabilities(:codex)

report = ProviderSDK.capability_report()
```

Current built-in namespaces:

- `ASM.Extensions.ProviderSDK.Amp`
- `ASM.Extensions.ProviderSDK.Claude`
- `ASM.Extensions.ProviderSDK.Codex`
- `ASM.Extensions.ProviderSDK.Cursor`

These root modules are the namespace anchors for optional provider-native
helpers.

Antigravity is a first-party provider with core and optional SDK runtime lanes,
but it does not currently register an
`ASM.Extensions.ProviderSDK.Antigravity` namespace. Direct SDK-lane execution
uses `AntigravityCliSdk.Runtime.CLI`, and direct SDK ownership remains in the
`antigravity_cli_sdk` package.

`ASM.Extensions.ProviderSDK.Cursor` is backed by `cursor_cli_sdk` when that
dependency is present. Cursor also keeps the common core lane through
`cli_subprocess_core`.

Activation-aware discovery follows a separate rule:

- `extensions/0` is the static registered native-extension catalog
- `provider_extensions/1` is the static native-extension catalog for one
  provider
- `available_extensions/0` reports which of those namespaces are active for the
  currently installed optional deps
- `available_provider_extensions/1` reports the active native-extension subset
  for one provider
- `provider_report/1` and `capability_report/0` always include all ASM
  providers, including Cursor, Amp, and Antigravity, and show whether
  each provider SDK runtime is available plus any active native namespace
  inventory
- `registered_namespaces` and `registered_extensions` keep the static catalog
  visible even when a provider currently composes only through the common
  surface
- Cursor and Amp start with strict derivation helpers. They still have
  explicit namespaces so Cursor mode/MCP/plugin settings and Amp
  permissions/MCP/skills have one provider-native home.
  Antigravity has no derivation helper in this namespace layer yet; its SDK
  lane is available through the runtime kit when installed.

The first argument to each `derive_options/2` helper is checked with strict common ASM preflight. Provider-native
keys in that map are rejected. Native provider data must be passed explicitly in
`native_overrides` or through the provider SDK directly.

Claude now exposes an explicit bridge into the SDK-local control family:

- `ASM.Extensions.ProviderSDK.Claude.derive_options/2`
- `ASM.Extensions.ProviderSDK.Claude.sdk_options/2`
- `ASM.Extensions.ProviderSDK.Claude.sdk_options_for_session/3`
- `ASM.Extensions.ProviderSDK.Claude.start_client/3`
- `ASM.Extensions.ProviderSDK.Claude.start_client_for_session/4`

Those helpers do not redefine Claude control semantics. They only:

- derive `ClaudeAgentSDK.Options` from ASM-style config or session defaults
- preserve ASM execution-surface placement on the resulting SDK options
- keep Claude-native options in a separate `native_overrides` bag
- start `ClaudeAgentSDK.Client` when callers explicitly opt into the SDK-local
  control family

Example:

```elixir
alias ASM.Extensions.ProviderSDK.Claude

asm_opts = [
  provider: :claude,
  cwd: File.cwd!(),
  execution_environment: [permission_mode: :plan],
  model: "sonnet",
  execution_surface: [
    surface_kind: :ssh_exec,
    transport_options: [destination: "buildbox-a", port: 2222]
  ]
]

native_overrides = [
  enable_file_checkpointing: true,
  thinking: %{type: :adaptive}
]

{:ok, client} =
  Claude.start_client(
    asm_opts,
    native_overrides,
    control_request_timeout_ms: 5_000
  )

:ok = ClaudeAgentSDK.Client.set_permission_mode(client, :plan)
```

The normalized ASM APIs stay unchanged. Only the optional extension crosses
into the Claude-native client surface.

Codex now exposes a similarly narrow bridge into the SDK-local app-server
entry path:

- `ASM.Extensions.ProviderSDK.Codex.derive_options/2`
- `ASM.Extensions.ProviderSDK.Codex.codex_options/2`
- `ASM.Extensions.ProviderSDK.Codex.codex_options_for_session/3`
- `ASM.Extensions.ProviderSDK.Codex.thread_options/2`
- `ASM.Extensions.ProviderSDK.Codex.thread_options_for_session/3`
- `ASM.Extensions.ProviderSDK.Codex.connect_app_server/3`
- `ASM.Extensions.ProviderSDK.Codex.connect_app_server_for_session/4`

Those helpers do not re-model app-server, MCP, realtime, or voice as ASM
kernel APIs. They only:

- derive `Codex.Options` from ASM-style config or session defaults
- derive `Codex.Thread.Options` from ASM-style config or session defaults
- preserve ASM execution-surface placement on `Codex.Options`
- keep Codex-native global or thread-only fields in explicit override bags
- start `Codex.AppServer` when callers explicitly opt into the SDK-local
  app-server family

The normalized ASM backend has one promoted Codex app-server path: host dynamic
tools. When a Codex run requests `app_server: true`, `host_tools: [...]`, or
`dynamic_tools: [...]`, `ASM.ProviderBackend.SDK` starts app-server, renders the
tool specs, handles `DynamicToolCallRequested`, and emits ASM host-tool events.
Other Codex app-server APIs still belong to `codex_sdk` or this explicit
extension seam.

Example:

```elixir
alias ASM.Extensions.ProviderSDK.Codex
alias Codex, as: CodexSDK

{:ok, conn} =
  Codex.connect_app_server(
    [
      provider: :codex,
      model: "gpt-5.6-sol",
      reasoning_effort: :max,
      execution_environment: [permission_mode: :default],
      execution_surface: [
        surface_kind: :ssh_exec,
        transport_options: [destination: "codex-host-1"],
        lease_ref: "lease-42"
      ]
    ],
    [model_personality: :pragmatic],
    experimental_api: true
  )

{:ok, thread_opts} =
  Codex.thread_options(
    [
      provider: :codex,
      cwd: "/repo",
      execution_environment: [permission_mode: :default],
      approval_timeout_ms: 45_000,
      output_schema: %{"type" => "object"}
    ],
    transport: {:app_server, conn},
    personality: :pragmatic
  )

{:ok, codex_opts} =
  Codex.codex_options(
    [provider: :codex, model: "gpt-5.6-sol"],
    model_personality: :pragmatic
  )

{:ok, thread} = CodexSDK.start_thread(codex_opts, thread_opts)
```

ASM intentionally does not normalize Codex `:auto` onto `Codex.Thread.Options`
because the current Codex workspace-write auto-edit path dirties repo roots
with a `.codex` artifact. Keep ASM's Codex bridge on `:default` or `:bypass`,
or use `codex_sdk` directly when you explicitly need provider-native
workspace-write behavior.

Cursor exposes the same strict derivation shape for `cursor_cli_sdk`:

```elixir
alias ASM.Extensions.ProviderSDK.Cursor

asm_common = [
  provider: :cursor,
  model: "composer-2.5-fast",
  cwd: File.cwd!(),
  execution_surface: [surface_kind: :local_subprocess]
]

native_overrides = [
  mode: :ask,
  approve_mcps: true,
  plugin_dirs: ["priv/cursor/plugins"]
]

{:ok, cursor_options} =
  Cursor.derive_options(asm_common, native_overrides: native_overrides)
```

The resulting struct is `%CursorCliSdk.Options{}`. The SDK lane uses
`CursorCliSdk.Runtime.CLI`; the core lane remains
`CliSubprocessCore.ProviderProfiles.Cursor`.

Cursor-native settings such as `:mode`, `:sandbox`, `:approve_mcps`, worktree
controls, plugin dirs, and headers belong in `native_overrides`. ASM-derived
fields such as `:model`, `:cwd`, `:execution_surface`, `:timeout_ms`, and
launch auth placement stay in ASM config.

## Optional-Loading Rules

- discovery calls are always available from ASM
- each extension reports `sdk_available?`
- `sdk_available?` only means the backing SDK package is loadable locally
- `provider_capabilities/1` reports only active native capabilities for the
  current dependency set
- richer provider-native APIs still live in the provider SDK repos
- ASM does not re-model those richer APIs in the kernel
- Antigravity reports as an ASM provider in `capability_report/0` with
  `registered_namespaces: []` until a real native-extension namespace lands
- the Claude bridge keeps ASM config and Claude-native config in separate
  arguments on purpose
- local `:core` and local `:sdk` lanes preserve the same normalized
  `execution_surface` contract; `execution_mode: :remote_node`
  remains a separate ASM-only rule
- ASM-derived fields such as `:cwd`, `:execution_environment`, `:model`,
  `:max_turns`, and `:timeout_ms` must stay in ASM config and are rejected
  from `native_overrides`
- the Codex bridge follows the same rule for ASM-derived fields such as
  `:model`, `:reasoning_effort`, `:cwd`, `:approval_timeout_ms`, and
  `:output_schema`
- the Codex bridge is intentionally useful at the app-server entry seam; ASM
  only promotes host dynamic tools into the backend, while the broader
  app-server, MCP, realtime, and voice APIs remain in `codex_sdk`
- the Cursor bridge derives `CursorCliSdk.Options`; Cursor-native settings
  stay in `native_overrides`

## Current Native Capability Inventory

Claude namespace:

- `:control_client`
- `:control_protocol`
- `:hooks`
- `:permission_callbacks`

Codex namespace:

- `:app_server`
- `:mcp`
- `:realtime`
- `:voice`

Amp namespace:

- `:mcp`
- `:permissions`
- `:skills`
- `:threads`

Cursor namespace:

- `:mcp`
- `:plugins`
- `:worktrees`

Antigravity currently has no ProviderSDK namespace capability inventory. Its
core and SDK runtime capability surfaces are reported through
`ASM.ProviderFeatures` and `ASM.ProviderRegistry`, not through this native
extension catalog.

These are capability labels for discovery and documentation only in this
foundation slice. They are not new normalized kernel APIs.
## Session-Control Extensions

Provider SDK extensions may now publish optional callbacks and capability
markers for:

- session history listing
- exact or latest-session resume
- pause
- operator intervention

Those extensions are only valid when the provider runtime can back them with a
real native surface. ASM now keeps the provider-native recovery handles in
session/run state so a caller can choose exact resume before it falls back to
replaying prompts.
