# Models, Modes, And Slash Commands `pydantic-acp` exposes a small ACP control plane on top of normal prompts. These controls exist to keep session state explicit and inspectable from the client UI. ## Slash Commands The adapter exposes a small fixed command set, dynamic mode commands, and optional host-defined commands. ### Fixed commands | Command | Purpose | |---|---| | `/model` | Show the current model | | `/model ` | Set the current model | | `/thinking` | Show the current thinking effort | | `/thinking ` | Set the current thinking effort | | `/tools` | List visible tools on the active agent | | `/hooks` | List registered visible hook callbacks | | `/mcp-servers` | List MCP servers derived from toolsets and session metadata | ### Dynamic mode commands Mode commands are registered from the current session’s available modes. If your session exposes: - `review` - `execute` then ACP publishes: - `/review` - `/execute` The adapter no longer hardcodes `ask`, `plan`, and `agent` as global commands. They are only published when those modes actually exist. Mode ids must remain compatible with slash-command addressing: - they cannot be empty - they cannot contain whitespace - they cannot collide with reserved commands such as `model`, `thinking`, `tools`, `hooks`, or `mcp-servers` - they should stay specific enough that the command still reads clearly in the UI ### Custom Commands Configure `AdapterConfig(slash_command_provider=...)` to add host-owned commands: ```python from acp.schema import AvailableCommand from pydantic_acp import SlashCommandResult, StaticSlashCommand, StaticSlashCommandProvider provider = StaticSlashCommandProvider( commands=[ StaticSlashCommand( command=AvailableCommand(name="diagnose", description="Run host diagnostics."), handler=lambda request: SlashCommandResult(text="Diagnostics queued."), ) ] ) ``` Custom handlers receive `SlashCommandRequest` with the parsed command name, optional raw argument string, session, and active agent. Returning `None` or `SlashCommandResult(handled=False)` falls through to normal model execution. `SlashCommandResult.refresh_session_surface` defaults to `True` so commands that mutate visible state refresh commands, config, mode, plan, and session metadata after they run. ## Mode Changes Update ACP State Mode commands do more than print text. When a mode changes, the adapter updates: - current mode state - ACP config options when the mode is mirrored as a config option - plan state visibility - available commands - session metadata This is why `/plan` or `/agent` affects the UI surface as well as the next prompt. ## Model Selection Model selection can be provided by either: - built-in `available_models` - a `SessionModelsProvider` If model selection is enabled, the adapter also mirrors it into ACP config options unless that behavior is disabled or the provider owns it already. Example built-in model config: ```python from pydantic_acp import AdapterConfig, AdapterModel config = AdapterConfig( allow_model_selection=True, available_models=[ AdapterModel( model_id="fast", name="Fast", description="Lower latency.", override="openai:gpt-5-mini", ), AdapterModel( model_id="smart", name="Smart", description="More capable model.", override="openai:gpt-5", ), ], ) ``` ## Thinking Effort `ThinkingBridge` exposes a session-local ACP config option named `thinking`. Supported values: - `default` - `off` - `minimal` - `low` - `medium` - `high` - `xhigh` Example: ```python from pydantic_acp import AdapterConfig, ThinkingBridge config = AdapterConfig(capability_bridges=[ThinkingBridge()]) ``` From the UI: ```text /thinking high ``` The bridge uses Pydantic AI’s native `Thinking` capability to generate model settings rather than inventing provider-specific request payloads itself. ## Mode-aware Tool Surfaces The common pattern is: - `ask`: read-only, inspection-focused - `plan`: inspect and draft ACP plan state - `agent`: full tool surface plus plan progress tools This is usually implemented with `PrepareToolsBridge`. ```python from pydantic_acp import PrepareToolsBridge, PrepareToolsMode from pydantic_ai.tools import RunContext, ToolDefinition def ask_tools( ctx: RunContext[None], tool_defs: list[ToolDefinition], ) -> list[ToolDefinition]: del ctx return [tool_def for tool_def in tool_defs if not tool_def.name.startswith("write_")] prepare_bridge = PrepareToolsBridge( default_mode_id="ask", modes=[ PrepareToolsMode( id="ask", name="Ask", description="Read-only repo inspection.", prepare_func=ask_tools, ), PrepareToolsMode( id="plan", name="Plan", description="Draft ACP plan state.", prepare_func=ask_tools, plan_mode=True, ), ], ) ``` ## What `/tools` Actually Lists `/tools` lists currently registered visible tools on the active agent. Important detail: - internal ACP tools such as `acp_get_plan` are intentionally hidden from `/tools` - the list reflects the agent after mode-aware prepare-tools filtering That makes `/tools` a good debugging surface for “why can the model see this tool right now?” ## What `/mcp-servers` Actually Lists The MCP server listing is assembled from: - active agent toolsets - session MCP server payloads - bridge-contributed MCP metadata It is primarily intended as a client-visible observability surface, not as the source of truth for server wiring. If client-provided `session/new.mcpServers` should become runnable model tools, configure `SessionMcpBridge` and build the agent through `AgentBridgeBuilder`: ```python from pydantic_acp import AdapterConfig, SessionMcpBridge config = AdapterConfig( capability_bridges=[ SessionMcpBridge(include_instructions=True), ], ) ``` Without that bridge, session MCP payloads are retained for resume/listing and shown by `/mcp-servers`, but the adapter does not connect to those servers. ## Common Failure Modes - `/thinking` does not appear unless a `ThinkingBridge()` is configured - `/model` only appears when model state is actually available - mode commands are not global; if a mode is not present in current session state, its slash command is not published - mode ids like `model` or `thinking` are rejected because they would collide with reserved slash commands