sub-pool-client
Python client + interactive CLIs for a sub-pool server — a
credential broker for N Claude and OpenAI Codex subscription
accounts.
You get three things in one package:
sp-claude— aclaudeCLI wrapper. Leases a Claude account from the pool, runs the realclaudebinary against a per-sessionCLAUDE_CONFIG_DIR, rotates the access_token in the background, and swaps accounts transparently if the leased one cools mid-run.sp-codex— same flow for OpenAI Codex. Leases a Codexauth.json, drops it into an isolatedCODEX_HOME, spawns the realcodexbinary, and rotates the token in the background.PooledClient/PooledCodexClient— Python SDK classes for programs and services.PooledClientis a drop-in subclass ofClaudeSDKClientfrom the officialclaude-agent-sdk;PooledCodexClientis a thin async wrapper aroundcodex exec.
The agent loop — tool execution, filesystem I/O, hooks, MCP servers, the codex subprocess — all stays on the consumer's machine. The pool only sees lease lifecycles.
Install
pip install sub-pool-client
# or
uv pip install sub-pool-client
Latest release tracks main. To install from source instead:
pip install git+https://github.com/subs-pool/sub-pool-client
Environment
Both the CLIs and the SDK read:
export SUB_POOL_URL=http://your-pool-host:8787
export SUB_POOL_KEY=cp-... # admin issues this from the sub-pool UI
SUB_POOL_KEY's strategy decides which accounts you can reach.
1. CLIs
sp-claude — claude wrapper
sp-claude --setup # one-time interactive config wizard
sp-claude # start the claude REPL on a leased account
sp-claude "explain this" # single-prompt mode
sp-claude --account alice # pin to a specific account
sp-claude --status # show effective config and exit
sp-claude --setup writes pool URL + API key to ~/.sub-pool/cli.toml
at 0o600. Subsequent invocations exec claude against a per-session
config dir that holds the leased .credentials.json. The persistent
home (~/.sub-pool/claude-home/) keeps your conversation history
across sessions and is isolated from ~/.claude/ — sp-claude never
reads or writes your real claude config.
If the leased account starts cooling (Anthropic rate-limit or quota
window), a background watcher swaps to a different account
transparently. Your running session sees one continuous claude
process; only the access_token under the hood changes.
sp-codex — codex wrapper
sp-codex --setup
sp-codex # interactive REPL on a leased Codex account
sp-codex exec "summarize ." # one-shot
sp-codex --account codex1
Shares ~/.sub-pool/cli.toml with sp-claude. Persistent
CODEX_HOME lives at ~/.sub-pool/codex-home/.
2. Python SDK
PooledClient (Claude)
import asyncio
from claude_agent_sdk import ClaudeAgentOptions
from sub_pool_client import PooledClient
async def main():
options = ClaudeAgentOptions(system_prompt="Be terse.")
async with PooledClient(options=options) as client:
print(f"leased {client.account} lease_id={client.lease_id}")
await client.query("List three prime numbers.")
async for msg in client.receive_response():
print(type(msg).__name__, "->", msg)
asyncio.run(main())
PooledClient subclasses ClaudeSDKClient, so every option the SDK
accepts (hooks, MCP servers, cwd, allowed_tools,
permission_mode) works unchanged.
Rate-limit handling
from claude_agent_sdk import ClaudeSDKError
async with PooledClient(options=options) as client:
try:
await client.query("...")
async for msg in client.receive_response():
...
except ClaudeSDKError as e:
if "429" in str(e) or "rate" in str(e).lower():
await client.report_error("RateLimit", str(e))
raise
report_error posts to /credentials/lease/{id}/report-error; the
pool marks the account COOLING so the next lease skips it.
Concurrent agents
Multiple async with PooledClient() calls in the same process — or
across sibling processes — with identical routing inputs (user_id,
required_model) share a single lease. Coordination happens
through ~/.sub-pool/client/<hash>/ (flock + holder list). First
arriver leases; later arrivers refcount; last out releases. So N
parallel agents on one account work up to Anthropic's per-account
concurrency limit. Vary user_id (with the sticky_user strategy)
to force different accounts in parallel.
PooledCodexClient (Codex)
import asyncio
from sub_pool_client import PooledCodexClient
async def main():
async with PooledCodexClient() as codex:
proc = await codex.exec(
"summarize this repository",
cwd=".",
model="gpt-5.5",
)
stdout, stderr = await proc.communicate()
print(stdout.decode())
asyncio.run(main())
PooledCodexClient writes the leased (sanitized) auth.json into an
isolated CODEX_HOME and spawns codex exec against it. A
background poll task rotates the on-disk auth.json before the
access_token expires. On a backend error, call
await codex.report_error("RateLimit", "...") so the pool cools the
account.
Pool invariants this client relies on
- The pool exclusively owns the
refresh_tokenchain (both Anthropic and OpenAI). Leases hand out only the access_token; therefreshTokenfield in the leased credentials file is blank. - Token rotation happens through
POST /credentials/lease/{lease_id}/token— a consumer crash can never leak the pool's refresh state. account_names come from the pool's admin config. The pool decides which account fulfills each lease based on the API key's strategy.
The sub-pool server (admin UI, strategy reference, account
lifecycle) is hosted separately — point this client at whatever pool
URL your admin gave you via SUB_POOL_URL above.
Layout under ~/.sub-pool/
~/.sub-pool/
├── cli.toml # sp-claude / sp-codex shared config (0o600)
├── claude-home/ # sp-claude persistent CLAUDE_CONFIG_DIR
├── codex-home/ # sp-codex persistent CODEX_HOME
└── client/<hash>/ # PooledClient / PooledCodexClient
# cross-process lease coordination
Override the SDK shared-dir root with SUB_POOL_CLIENT_DIR.
Examples
examples/hello.py—PooledClientend-to-end with a retry loop on 429.examples/codex_hello.py—PooledCodexClientrunningcodex exec.
License
(TBD — defer to upstream)
Release files for sub-pool-client 0.8.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sub_pool_client-0.8.0.tar.gz | 38.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sub_pool_client-0.8.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 87.1 kB
Release files / sub_pool_client-0.8.0.tar.gz
| Download URL | sub_pool_client-0.8.0.tar.gz |
|---|---|
| Size | 38.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
518cda51ee7e91378b4c91597344766e50fb4b711a2e950edba00e2177f56b91
|
|
BLAKE2b-256 checksum How to use checksums |
751236bc0acf80c9d1ac6c6b620b2f93db2e27968a5faf4f5081f3ffd2ff93d8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.
Transparency logRelease files / sub_pool_client-0.8.0-py3-none-any.whl
| Download URL | sub_pool_client-0.8.0-py3-none-any.whl |
|---|---|
| Size | 48.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7348035d257c9cbdc87cabdeb880ce836a8b8dde01400471700cd03ac4ad92ec
|
|
BLAKE2b-256 checksum How to use checksums |
57141a150e84776e5f426412c7cd37d0e83db94efbc6b66389cb423278f4957a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.
Transparency log