Running
run.run()
Execute a RunSpec asynchronously, retrying transient failures with backoff.
Usage
run.run(
spec,
*,
backend=None,
)Each attempt runs through backend.aexecute; the core’s retry_decision op classifies a Response.error (a 529, overloaded, rate-limit, or 5xx) and returns the backoff to sleep before another attempt, up to spec.max_attempts. The final Response — success or last failure — is returned without raising; every operational failure (nonzero exit, error envelope, timeout, validation) lives in resp.error.
Parameters
spec: RunSpec-
The configured run to execute.
backend: LlmBackend | None = None- The backend to run on; defaults to select_backend().
Returns
run.run_sync()
Execute a RunSpec synchronously, retrying transient failures with backoff.
Usage
run.run_sync(spec, *, backend=None)The synchronous companion to run: each attempt runs through backend.execute, the core decides retry and backoff, and the last Response is returned without raising.
Parameters
spec: RunSpec-
The configured run to execute.
backend: LlmBackend | None = None- The backend to run on; defaults to select_backend().
Returns
RunSpec
A single configured run, translated per backend at execution time.
Usage
RunSpec(
prompt,
model,
response_model=None,
schema=None,
agent=False,
isolated=True,
cwd=None,
env=None,
api_auth=False,
timeout=180,
max_attempts=5,
provider_configs=dict()
)Common fields are interpreted by every backend; provider_configs carries optional per-provider flag passthrough that only the matching backend reads. model is a literal provider model id (opus, sonnet, …) passed straight through with no tier mapping. isolated (default True) runs the backend against a fresh, host-free config home so a spawned CLI ignores ambient settings, MCP servers, and hooks. api_auth (default False) strips the provider’s API-key environment variables from the child process so the CLI bills the logged-in subscription; True inherits the environment untouched. Structured output comes from either a response_model (validated to a model) or a raw schema (a JSON-Schema dict or pre-serialized string, passed to the provider verbatim, with nothing to validate); setting both raises ValueError.
Parameter Attributes
prompt: strmodel: strresponse_model: type[BaseModel] | None = Noneschema: dict[str, object] | str | None = Noneagent: bool = Falseisolated: bool = Truecwd: str | None = Noneenv: dict[str, str] | None = Noneapi_auth: bool = Falsetimeout: int = 180max_attempts: int = 5provider_configs: dict[ProviderName, ProviderConfig] = dict()
Example
>>> RunSpec(prompt="ping", model="opus")Methods
| Name | Description |
|---|---|
| config_for() |
Return the first provider config that is an instance of kind, or None.
|
config_for()
Return the first provider config that is an instance of kind, or None.
Usage
config_for(kind)Response
A backend’s fully-resolved outcome: the spec, the raw output, and exactly one of result/error.
Usage
Response(spec, output, result=None, error=None, discarded_attempts=())A backend runs the process, reads its output wherever the provider writes it, detects failure, and validates — then hands back one Response. spec and output are always present (the raw bytes live in output.raw even on failure); exactly one of result/error is set. Every failure — a nonzero exit, an error envelope, a timeout, or a validation error — routes through error, never a raise from run. discarded_attempts carries the transient failures run retried away before this one, empty when there were none.
Parameter Attributes
spec: RunSpecoutput: Outputresult: Result | None = Noneerror: Error | None = Nonediscarded_attempts: tuple[DiscardedAttempt, …] = ()
Example
>>> Response(spec=spec, output=Output(raw="hi"), result=Result(raw="hi"))Result
A successful run: the extracted final text and the optional validated model.
Usage
Result(raw, parsed=None)raw is the extracted final text; parsed is the validated model, set only when the RunSpec carried a response_model.
Parameter Attributes
raw: strparsed: BaseModel | None = None
Example
>>> Result(raw="hello", parsed=None)Output
The full unparsed transport stream, present on success and failure alike.
Usage
Output(raw)raw is the complete output the provider wrote — the claude --output-format json event stream, the codex -o file, or plain stdout — before any extraction.
Parameter Attributes
raw: str
Example
>>> Output(raw='[{"type": "system"}, {"type": "result", "result": "hi"}]')Error
A failed run: a human-readable message plus the underlying exception.
Usage
Error(msg, ex)ex preserves the real exception so a caller can re-raise it unchanged — a BackendCallError for a nonzero exit or error envelope, a normalized timeout error, or a pydantic.ValidationError for a non-conforming model.
Parameter Attributes
msg: strex: Exception
Example
>>> Error(msg="codex exited 127: codex: not found", ex=RuntimeError("..."))DiscardedAttempt
A transient failure the retry loop threw away, summarized for spend accounting.
Usage
DiscardedAttempt(attempt, error, cost_usd, usage, raw_bytes)run/run_sync return only the final attempt’s Response, so a caller tracking spend never sees the cost of the retries that preceded it. Each discarded attempt is summarized here: cost_usd and usage when the attempt’s output carried parseable accounting, else None; raw_bytes is always the UTF-8 byte length of the discarded output. attempt is the zero-based index and error the discarded exception’s class name.
Parameter Attributes
attempt: interror: strcost_usd: float | Noneusage: dict[str, object] | Noneraw_bytes: int
Example
>>> DiscardedAttempt(attempt=0, error="BackendCallError", cost_usd=0.02, usage=None, raw_bytes=57)AppleConfig
Apple Foundation Models knobs applied only by the Apple backend.
Usage
AppleConfig(
use_case="general",
guardrails="default",
instructions=None,
temperature=None,
maximum_response_tokens=None,
sampling=None,
sampling_top=None,
sampling_probability_threshold=None,
sampling_seed=None
)use_case and guardrails select the SystemLanguageModelUseCase and SystemLanguageModelGuardrails the spawnllm-apple sidecar builds its session with. The sampling knobs are flat rather than a nested mode because Apple exposes SamplingMode as a factory (greedy() / random()) that no serializable value can carry: sampling picks the factory and sampling_top, sampling_probability_threshold, and sampling_seed are the arguments random takes. None everywhere leaves the framework default.
Parameter Attributes
use_case: Literal["general", "content_tagging"] = "general"guardrails: Literal["default", "permissive_content_transformations"] = "default"instructions: str | None = Nonetemperature: float | None = Nonemaximum_response_tokens: int | None = Nonesampling: Literal["greedy", "random"] | None = Nonesampling_top: int | None = Nonesampling_probability_threshold: float | None = Nonesampling_seed: int | None = None
Example
>>> AppleConfig(use_case="content_tagging", sampling="random", sampling_top=20)Raises
ValueError-
When a
random-only argument is set withoutsampling="random", a combination the framework would silently discard; whensampling_topandsampling_probability_thresholdare set together, a pair the framework rejects; or whensampling_seedis negative, which the framework’sUInt64cannot carry.
ClaudeConfig
Claude CLI flag passthrough applied only by the Claude backend.
Usage
ClaudeConfig(
permission_mode=None,
mcp_config=None,
strict_mcp=False,
append_system_prompt=None,
system_prompt=None,
settings=None,
disallowed_tools=(),
max_turns=None,
max_budget_usd=None,
tools=None,
disable_slash_commands=False,
output_format=None,
verbose=False
)Fields map one-to-one onto claude flags: the agent/system-prompt knobs (permission_mode, mcp_config, strict_mcp, append_system_prompt, system_prompt, settings, disallowed_tools) and orthogonal extras (max_turns, max_budget_usd, tools, disable_slash_commands, output_format, verbose). tools selects the built-in toolset: None keeps the CLI default, () disables every built-in tool, and names restrict the session to those tools.
Parameter Attributes
permission_mode: str | None = Nonemcp_config: str | None = Nonestrict_mcp: bool = Falseappend_system_prompt: str | None = Nonesystem_prompt: str | None = Nonesettings: str | None = Nonedisallowed_tools: tuple[str, …] = ()max_turns: int | None = Nonemax_budget_usd: float | None = Nonetools: tuple[str, …] | None = Nonedisable_slash_commands: bool = Falseoutput_format: str | None = Noneverbose: bool = False
Example
>>> ClaudeConfig(permission_mode="bypassPermissions", strict_mcp=True)
>>> ClaudeConfig(tools=()) # bare session: no built-in toolsCodexConfig
Codex CLI knobs applied only by the Codex backend.
Usage
CodexConfig(
sandbox=None,
enable_hooks=False,
enable_mcp=False,
service_tier="fast",
developer_instructions=None
)service_tier (default "fast") is emitted as -c service_tier=<value> on every invocation, isolated or not: isolated runs pass --ignore-user-config, which drops a service_tier pin in the user’s ~/.codex/config.toml, and the standard tier turns long prompts into multi-minute runs. Set it to None to drop the flag; an isolated run still passes --ignore-user-config, so a user-level tier pin applies only with isolated=False. developer_instructions injects the system-prompt layer via -c developer_instructions=<value>, serialized as a TOML string so any text — multi-line, or TOML-ambiguous words like true — arrives as a string.
Parameter Attributes
sandbox: str | None = Noneenable_hooks: bool = Falseenable_mcp: bool = Falseservice_tier: str | None = "fast"developer_instructions: str | None = None
Example
>>> CodexConfig(sandbox="read-only", enable_mcp=True)GeminiConfig
Gemini CLI knobs applied only by the Gemini and Antigravity backends.
Usage
GeminiConfig(approval_mode=None, extensions=None)Parameter Attributes
approval_mode: str | None = Noneextensions: tuple[str, …] | None = None
Example
>>> GeminiConfig(approval_mode="auto", extensions=("search",))