Skip to content

Repository files navigation

claude-pool

claude-pool is a zero-dependency Python utility for running the Claude Code CLI through warm persistent workers.

It keeps Claude Code worker processes ready, sends prompts through either the default stream-json print-mode backend or the TUI pty backend, and returns structured result objects. A plain ask() uses a fresh worker context for each request. Multi-turn conversations are only kept when you explicitly open a Session. The optional Unix-socket daemon speaks NDJSON so programs in any language can send prompt requests.

claude-pool is not affiliated with Anthropic.

Install

pip install claude-pool

You can also vendor it by copying claude_pool.py into your project. The runtime uses only the Python standard library, supports Python 3.10+, and is intended for POSIX platforms.

Quickstart

fromclaude_poolimportClaudePoolwithClaudePool() aspool:
result=pool.ask_sync("Reply with exactly: OK")
print(result.text)

Async Usage

importasynciofromclaude_poolimportClaudePoolasyncdefmain() ->None:
asyncwithClaudePool(warm=1, max_workers=4) aspool:
result=awaitpool.ask("Summarize what a warm worker pool does.")
print(result.text)
asyncwithpool.session() assession:
first=awaitsession.send("Remember the number 12.")
second=awaitsession.send("What number did I ask you to remember?")
print(first.session_id, second.session_id)
print(second.text)
asyncio.run(main())

Async and sync methods are mutually exclusive per ClaudePool instance. Create separate pools if one part of a program uses async code and another uses sync code.

Sync Usage

fromclaude_poolimportClaudePoolpool=ClaudePool()
try:
result=pool.ask_sync("Reply with one sentence.")
print(result.text)
withpool.session_sync() assession:
first=session.send("Remember the word: river.")
second=session.send("What word did I ask you to remember?")
print(first.session_id, second.session_id)
print(second.text)
finally:
pool.close()

CLI Daemon

Start a daemon:

claude-pool serve --socket /tmp/claude-pool.sock --warm 1 --max-workers 4

Ask through the daemon:

claude-pool ask "Reply with exactly: OK" --socket /tmp/claude-pool.sock

Check status:

claude-pool status --socket /tmp/claude-pool.sock

Check the local Claude Code setup. This makes one real Claude request:

claude-pool doctor

Use claude-pool doctor --backend both to check both backends. That makes two real Claude requests.

See examples/shell.md for a two-daemon multi-profile pattern and a systemd --user unit sketch.

Backends

Choose a backend with ClaudePool(backend=...) or claude-pool serve --backend.

BackendUse whenTradeoff
stream-jsonYou want structured metadata including usage, cost, duration, and rate-limit events.Uses Claude Code print mode (claude -p). This is the default.
tuiYou need to avoid print mode and drive plain claude in a pty.Text-first result metadata, best-effort usage extraction, and heavier worker startup.

Startup Tuning

Two constructor arguments control worker cold starts. Both default to the original behavior.

ArgumentDefaultMeaning
tui_ready_timeout30.0Seconds a TUI worker may take to signal readiness through its SessionStart hook before its spawn fails with WorkerStartError.
spawn_concurrencyNoneMaximum simultaneous worker cold starts across the pool. None leaves spawning unbounded.

On slow hosts such as single-board computers, several TUI workers cold-starting at the same time can push each one past the 30-second readiness deadline, so a process restart fails with WorkerStartError even though each worker would have started fine on its own. Raising tui_ready_timeout and capping spawn_concurrency keeps those restarts calm:

pool=ClaudePool(backend="tui", warm=2, tui_ready_timeout=75.0, spawn_concurrency=2)

The daemon accepts the same knobs:

claude-pool serve --backend tui --warm 2 --tui-ready-timeout 75 --spawn-concurrency 2

Authentication Failures

Authentication failures fail fast with WorkerAuthError. Headless deployments should prefer CLAUDE_CODE_OAUTH_TOKEN via the env= parameter.

How It Works

start()
|
v
spawn warm workers
|
v
ask() checks out one worker -> sends one prompt -> reads result
|
v
retire consumed worker
|
v
replenisher spawns a replacement warm worker

Plain ask() always consumes and retires its worker so the next plain request gets a fresh context. Session keeps one checked-out worker for the context manager lifetime, so the Claude CLI process carries conversation state across turns.

How this relates to claude -p

The default stream-json backend is Claude Code print mode kept warm:

claude -p --input-format stream-json --output-format stream-json --verbose

It uses the same CLI flags, same local Claude Code login, same account, and same limits as running claude -p yourself. The difference is process lifecycle: claude-pool starts workers ahead of time and reuses a checked-out process for exactly one plain ask() or for the lifetime of an explicit Session.

The tui backend is the shipped no--p path. It starts plain claude in a pty, registers Claude Code hooks, and reads completed turn text from the Stop hook payload.

Result Fields

ask() and Session.send() return Result:

FieldMeaning
textThe CLI result text.
is_errorWhether Claude Code marked the result as an error. This is returned as a normal Result, not raised.
subtypeThe CLI result subtype.
session_idThe worker session id reported by Claude Code.
usageToken and cache usage reported by Claude Code.
cost_usdReported total cost in USD.
duration_msReported turn duration.
rate_limitLatest rate-limit event seen during the turn, if any.
rawThe original result message.

Branch on result.is_error before treating result.text as ordinary model output. Claude Code can put error text in the same field as successful output.

Exceptions

ExceptionWhen it is raised
ClaudePoolErrorBase class for pool errors and closed sessions.
PoolClosedWork is requested after a pool has closed.
WorkerStartErrorA worker process cannot be started.
WorkerAuthErrorWorker startup output matches a known authentication-failure marker.
WorkerCrashErrorA worker exits before producing a result.
AskTimeoutA prompt exceeds its timeout and the worker is killed.

WorkerStartError and WorkerCrashError expose stderr_tail, a bounded tail of worker stderr. WorkerAuthError also exposes the matched marker and a bounded stdout_tail.

Supported Platforms

Linux and macOS are supported. Windows is unsupported in v0.x because worker cleanup uses POSIX process groups.

FAQ

Does this bypass subscription limits?

No. claude-pool runs the local claude CLI with your existing Claude Code login. It uses the same account, subscription, authentication state, and rate limits as the interactive CLI.

Is this an Anthropic project?

No. This project is not affiliated with Anthropic.

When should I use the official Claude Agent SDK instead?

Use the official Claude Agent SDK for long-running agents, tool orchestration, streaming partial outputs, and API-key based applications. claude-pool is for fire-and-forget prompt-to-result calls through the local Claude Code CLI.

What if the Claude Code protocol changes?

The implementation is based on the stream-json behavior captured in PROTOCOL.md. If behavior changes, run claude-pool doctor first to check the local binary, authentication, and one real round trip.

Changelog

See CHANGELOG.md.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages