# Runtime Control Features
This guide covers the runtime control features that remain SDK-local after the
CLI runtime cutover.
## What The Client Owns
`ClaudeAgentSDK.Client` now sits above `CliSubprocessCore.ProtocolSession` and
keeps only Claude-specific control behavior:
- initialize handshake shaping
- runtime model switching
- hook callback execution
- permission callback execution
- SDK MCP routing
- Claude message and `stream_event` projection
The client no longer accepts custom transport injection and it no longer owns
subprocess lifecycle directly.
## Runtime Model Switching
```elixir
{:ok, client} =
ClaudeAgentSDK.Client.start_link(%ClaudeAgentSDK.Options{
model: "claude-sonnet-4"
})
:ok = ClaudeAgentSDK.Client.set_model(client, "opus")
```
## Execution Surface Routing
Choose local vs SSH execution with `Options.execution_surface`:
```elixir
opts = %ClaudeAgentSDK.Options{
execution_surface: [
surface_kind: :ssh_exec,
transport_options: [
destination: "claude.example",
user: "sdk",
port: 22
]
]
}
{:ok, client} = ClaudeAgentSDK.Client.start_link(opts)
```
## Error Notes
- Local validation failures are returned before the client starts.
- Control requests preserve typed SDK errors such as `:timeout` and
`{:protocol_session_down, reason}`.
- CLI-declared control errors still come back as provider strings from the
control protocol.