Python client + `sp-claude` / `sp-codex` CLIs for a sub-pool credential broker.
Project description
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)
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sub_pool_client-0.6.7.tar.gz.
File metadata
- Download URL: sub_pool_client-0.6.7.tar.gz
- Upload date:
- Size: 34.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
65442b5669cbd5b4f43371601d3f82de1ea9898a3b96088a4258cdfe2e6aabf0
|
|
| MD5 |
e3ab13da903a6575e38eb7b75fe42a72
|
|
| BLAKE2b-256 |
476f3b649dab767cbf4a6aa24bd7217214a0e458d63052a1d4d2350b3b69eaa2
|
Provenance
The following attestation bundles were made for sub_pool_client-0.6.7.tar.gz:
Publisher:
publish.yml on subs-pool/sub-pool-client
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sub_pool_client-0.6.7.tar.gz -
Subject digest:
65442b5669cbd5b4f43371601d3f82de1ea9898a3b96088a4258cdfe2e6aabf0 - Sigstore transparency entry: 2115601844
- Sigstore integration time:
-
Permalink:
subs-pool/sub-pool-client@4013c50f4a680a2f34dd93a7718867c4ffa0f71c -
Branch / Tag:
refs/tags/v0.6.7 - Owner: https://github.com/subs-pool
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4013c50f4a680a2f34dd93a7718867c4ffa0f71c -
Trigger Event:
push
-
Statement type:
File details
Details for the file sub_pool_client-0.6.7-py3-none-any.whl.
File metadata
- Download URL: sub_pool_client-0.6.7-py3-none-any.whl
- Upload date:
- Size: 42.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
53037c95aeca307a17c0e527277b955d7a0976f2618507eca87576c801810b38
|
|
| MD5 |
22919260320ef6525b1517d694682d59
|
|
| BLAKE2b-256 |
9b98401967bd0236e6b4d4151feedb10a0f2df7085ed8bc461394e333b2e9a17
|
Provenance
The following attestation bundles were made for sub_pool_client-0.6.7-py3-none-any.whl:
Publisher:
publish.yml on subs-pool/sub-pool-client
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sub_pool_client-0.6.7-py3-none-any.whl -
Subject digest:
53037c95aeca307a17c0e527277b955d7a0976f2618507eca87576c801810b38 - Sigstore transparency entry: 2115601933
- Sigstore integration time:
-
Permalink:
subs-pool/sub-pool-client@4013c50f4a680a2f34dd93a7718867c4ffa0f71c -
Branch / Tag:
refs/tags/v0.6.7 - Owner: https://github.com/subs-pool
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4013c50f4a680a2f34dd93a7718867c4ffa0f71c -
Trigger Event:
push
-
Statement type: