Skip to main content

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:

  1. sp-claude — a claude CLI wrapper. Leases a Claude account from the pool, runs the real claude binary against a per-session CLAUDE_CONFIG_DIR, rotates the access_token in the background, and swaps accounts transparently if the leased one cools mid-run.
  2. sp-codex — same flow for OpenAI Codex. Leases a Codex auth.json, drops it into an isolated CODEX_HOME, spawns the real codex binary, and rotates the token in the background.
  3. PooledClient / PooledCodexClient — Python SDK classes for programs and services. PooledClient is a drop-in subclass of ClaudeSDKClient from the official claude-agent-sdk; PooledCodexClient is a thin async wrapper around codex 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

PyPI

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-claudeclaude 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-codexcodex 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_token chain (both Anthropic and OpenAI). Leases hand out only the access_token; the refreshToken field 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.pyPooledClient end-to-end with a retry loop on 429.
  • examples/codex_hello.pyPooledCodexClient running codex 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

sub_pool_client-0.2.0.tar.gz (28.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sub_pool_client-0.2.0-py3-none-any.whl (34.4 kB view details)

Uploaded Python 3

File details

Details for the file sub_pool_client-0.2.0.tar.gz.

File metadata

  • Download URL: sub_pool_client-0.2.0.tar.gz
  • Upload date:
  • Size: 28.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for sub_pool_client-0.2.0.tar.gz
Algorithm Hash digest
SHA256 7af688ed701782740a874d00d05d190537bf04c3922bec954fc99841548ab5dd
MD5 abeff252ef594cbae52b549d8a9bf93e
BLAKE2b-256 05c4ec5de165b9f81075423d1f0e49d45820753e18e119520cc46574f37ea5b4

See more details on using hashes here.

Provenance

The following attestation bundles were made for sub_pool_client-0.2.0.tar.gz:

Publisher: publish.yml on subs-pool/sub-pool-client

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file sub_pool_client-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for sub_pool_client-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 10297fd7b0d1f042d9fe2012b86b98ba826ac607b19fd59adbfe06b12bd594bf
MD5 f1dabd2b7ee418edf1f923a1ffa5e66e
BLAKE2b-256 f1dc4b7a0282cef5d0c7dc05c9fe60080068b4586ff9cd06f695bab7a7135cdf

See more details on using hashes here.

Provenance

The following attestation bundles were made for sub_pool_client-0.2.0-py3-none-any.whl:

Publisher: publish.yml on subs-pool/sub-pool-client

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page