Metadata-Version: 2.4
Name: spawnllm
Version: 0.11.0
Summary: Delete your subprocess wrappers around claude, codex, and gemini.
Keywords: 
Author: Yasyf Mohamedali
Author-email: Yasyf Mohamedali <yasyfm@gmail.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Typing :: Typed
Requires-Dist: click>=8
Requires-Dist: httpx>=0.27
Requires-Dist: loguru>=0.7
Requires-Dist: pydantic>=2
Requires-Dist: wasmtime>=46
Requires-Dist: zstandard>=0.25.0 ; extra == 'adapter'
Requires-Dist: numpy>=1.26 ; extra == 'adapter'
Requires-Dist: orjson>=3.10 ; extra == 'adapter'
Requires-Dist: apple-fm-sdk>=0.2.1 ; platform_machine == 'arm64' and sys_platform == 'darwin' and extra == 'apple'
Requires-Dist: anyio>=4 ; extra == 'dev'
Requires-Dist: claude-agent-sdk>=0.2 ; extra == 'dev'
Requires-Dist: pytest>=8.0 ; extra == 'dev'
Requires-Dist: ruff>=0.8 ; extra == 'dev'
Requires-Dist: ty>=0.0.44 ; extra == 'dev'
Requires-Dist: zstandard>=0.25.0 ; extra == 'dev'
Requires-Dist: numpy>=1.26 ; extra == 'dev'
Requires-Dist: orjson>=3.10 ; extra == 'dev'
Requires-Dist: openai>=2.43 ; extra == 'dev'
Requires-Dist: anthropic>=0.111 ; extra == 'dev'
Requires-Dist: zstandard>=0.25.0 ; extra == 'mlx'
Requires-Dist: numpy>=1.26 ; extra == 'mlx'
Requires-Dist: orjson>=3.10 ; extra == 'mlx'
Requires-Dist: anyio>=4.4 ; extra == 'mlx'
Requires-Dist: huggingface-hub>=0.25 ; extra == 'mlx'
Requires-Dist: mlx-lm>=0.31.3 ; platform_machine == 'arm64' and sys_platform == 'darwin' and extra == 'mlx'
Requires-Dist: claude-agent-sdk>=0.2 ; extra == 'sdk'
Requires-Python: >=3.13
Project-URL: Homepage, https://github.com/yasyf/spawnllm
Project-URL: Documentation, https://yasyf.github.io/spawnllm/
Project-URL: Repository, https://github.com/yasyf/spawnllm
Project-URL: Issues, https://github.com/yasyf/spawnllm/issues
Project-URL: Changelog, https://github.com/yasyf/spawnllm/blob/main/CHANGELOG.md
Provides-Extra: adapter
Provides-Extra: apple
Provides-Extra: dev
Provides-Extra: mlx
Provides-Extra: sdk
Description-Content-Type: text/markdown

# ![spawnllm](https://github.com/yasyf/spawnllm/raw/main/docs/assets/readme-banner.webp)

**Delete your subprocess wrappers around claude, codex, and gemini.** spawnllm subshells all three CLIs — or drives Claude in-process through the bundled Agent SDK — plus local MLX and Apple's on-device Foundation Models, and returns one Pydantic-validated Response, so the per-model plumbing you hand-rolled goes away.

[![CI](https://github.com/yasyf/spawnllm/actions/workflows/ci.yml/badge.svg)](https://github.com/yasyf/spawnllm/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/spawnllm)](https://pypi.org/project/spawnllm/)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue)](https://github.com/yasyf/spawnllm/blob/main/LICENSE)

## Get started

```bash
uvx spawnllm status
```

<img src="https://github.com/yasyf/spawnllm/raw/main/docs/assets/demo.png" alt="Terminal running 'uvx spawnllm status' — every backend reports ready and auto-selection picks claude" width="700">

Driving with an agent? Paste this:

```text
Run `uv add spawnllm` in this project.
Replace our hand-rolled claude/codex subprocess code with spawnllm's `call_sync`,
or `extract_sync` with a Pydantic response model for structured output.
Verify available backends with `uvx spawnllm status`.
Docs: https://yasyf.github.io/spawnllm/
```

---

## Use cases

### Delete your hand-rolled claude/codex subprocess plumbing

Every small tool grows its own `subprocess.run(["claude", "-p", ...])` — argv quirks, stdin piping, exit-code guesswork — and each copy drifts. One call replaces all of it:

```python
from spawnllm import call_sync

print(call_sync("Reply with just the word: pong"))
```

Prints `pong`. With no `backend=`, spawnllm auto-selects the first installed, authenticated backend — a CLI backend gets the prompt over stdin — and retries transient 529/overloaded/rate-limit failures with capped backoff.

### Get a validated Pydantic object back, not a string to parse

Scraping JSON out of a model's stdout means regexes, code fences, and silent schema drift. `extract_sync` validates instead:

```python
from pydantic import BaseModel

from spawnllm import extract_sync


class Capital(BaseModel):
    country: str
    capital: str


result = extract_sync("What is the capital of France?", Capital)
print(result.capital)  # Paris
```

The backend turns `Capital` into a JSON-schema constraint on the call itself, and a non-conforming reply raises `pydantic.ValidationError` instead of sneaking downstream.

### Keep billing on your subscription, not a stray API key

An `ANTHROPIC_API_KEY` left in your shell silently flips the `claude` CLI from your logged-in plan to per-token API billing. spawnllm strips each provider's key vars from the child environment by default, so every run bills the login:

```python
from spawnllm import call_sync

print(call_sync("Reply with just the word: pong"))
```

Prints `pong`, billed to your Claude plan even with `ANTHROPIC_API_KEY` exported. Pass `api_auth=True` to opt back into key auth. The same guard covers codex (`OPENAI_API_KEY`/`CODEX_API_KEY`) and the Gemini family, in Python, Go, and Rust alike; an explicit `RunSpec.env` entry always wins.

### Call Claude with zero installs

The `sdk` extra adds a backend over the Claude Agent SDK, whose wheel bundles the Claude Code CLI — no separate `claude` install:

```bash
uv add "spawnllm[sdk]"
```

`claude-sdk` registers first in the auto-selection chain and signs in with your existing subscription credentials (keychain login or `CLAUDE_CODE_OAUTH_TOKEN`), so the `call_sync` above works on a machine that has never installed the CLI.

### Run Apple-Silicon MLX models with fused adapters and prompt-cache reuse

Shipping a LoRA-tuned local model means hand-rolling adapter fusion, model caching, and worker-thread lifecycle. The MLX extra owns all three:

```bash
uv add "spawnllm[mlx]"
```

`AdapterFuser.ensure_fused` fuses your compressed adapter into the base model once and caches the result in the Hugging Face hub layout; `MlxEngine` loads it on a dedicated worker thread, precomputes a prompt cache for your shared prefix messages, and batches generation. Wrap the engine in an `MlxBackend` and the same `run_sync` call works.

### Call Apple's on-device model with zero downloads

Even local MLX starts with a multi-gigabyte model fetch. On a Mac with Apple Intelligence, the `apple` extra skips that too: `AppleBackend` generates in-process through Apple's Foundation Models framework, against the model already resident on the device. No credentials, no network, nothing to download:

```bash
uv pip install 'spawnllm[apple]'
```

```python
from spawnllm import AppleBackend, call_sync

print(call_sync("Reply with just the word: pong", backend=AppleBackend()))
```

Auto-selection tries this backend last, after every CLI backend, and only for `model="small"`; an explicit `backend=AppleBackend()` always reaches it. Session and decoding knobs (`use_case`, `guardrails`, `instructions`, `temperature`, sampling) ride in via `RunSpec(provider_configs={"apple": AppleConfig(...)})`. Structured `extract_sync` works too, nested models included, with two Apple-imposed limits: the schema dialect strips JSON Schema `pattern` because Apple's importer rejects it, so a `Field(pattern=...)` constraint goes unenforced during generation and only fails `model_validate` afterward; and recursive models are unsupported. Requires macOS 26+ on Apple Silicon with Apple Intelligence enabled. Installing needs full Xcode 26+: `apple-fm-sdk` ships as an sdist that compiles a Swift dylib, and its build backend rejects the Command Line Tools.

### Call the same backends from Go or Rust

All three languages run the identical engine: argv planning, output parsing, schema strictification, and retry policy live once in a Rust core — linked natively by the Rust crate, embedded as WASM by the Go module and the Python package — pinned by a shared golden-vector suite and released in lockstep.

```bash
go get github.com/yasyf/spawnllm/go   # pure Go, no cgo — the core embeds as WASM
cargo add spawnllm                    # async-first, with a blocking mirror
```

Both expose `Call`/`call` and typed `Extract`/`extract` against your existing CLI logins — see the [Go README](https://github.com/yasyf/spawnllm/tree/main/go) and the [Rust README](https://github.com/yasyf/spawnllm/tree/main/rust/spawnllm). MLX and the Apple backend stay Python-only.

## More in the docs

- **Spec-driven runs** — a literal model id, per-provider flag passthrough, and envelope-aware retry via `RunSpec` — [Running reference](https://yasyf.github.io/spawnllm/reference/#running)
- **Backend selection** — the priority chain, plus `specialty=` routing (`debugging` and `review` go to Codex, `general` to the Claude Agent SDK backend) — [Backends reference](https://yasyf.github.io/spawnllm/reference/#backends)
- **Transport helpers** — `run_cli`, `collect_process`, and `map_concurrent`, the subprocess plumbing shared by every CLI backend — [Transport reference](https://yasyf.github.io/spawnllm/reference/#transport)
- **The CLI** — `spawnllm call`, `status`, and `backends` from any shell — [CLI reference](https://yasyf.github.io/spawnllm/reference/cli/)
- **MLX internals** — the adapter codec, fuser, and runtime patches behind the local engine — [MLX reference](https://yasyf.github.io/spawnllm/reference/#mlx)

Read the [docs](https://yasyf.github.io/spawnllm/) for the full guide and API reference. Licensed under [MIT](https://github.com/yasyf/spawnllm/blob/main/LICENSE).
