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.1.2.tar.gz (28.1 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.1.2-py3-none-any.whl (34.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: sub_pool_client-0.1.2.tar.gz
  • Upload date:
  • Size: 28.1 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.1.2.tar.gz
Algorithm Hash digest
SHA256 01563201e9d427304c66a90825cc543fe76a9d1b15b95d908e551aa2018b3af0
MD5 23f2523d3f4ef1e18d71cb0bc1a595e2
BLAKE2b-256 826dfc06fa52afcff96335add90bebdab7dbf1e2e5863137a6281c9ddaa2de24

See more details on using hashes here.

Provenance

The following attestation bundles were made for sub_pool_client-0.1.2.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.1.2-py3-none-any.whl.

File metadata

  • Download URL: sub_pool_client-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 34.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for sub_pool_client-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 28c4930f0437d531f17a145c3a1acfeb3a1d3c1e6b0ef7490455c8fcb26d984e
MD5 2c48b938d241d455ecdf3637cac13ae5
BLAKE2b-256 7c677af090cd8a0b66122db84f422a19be1cde5133f87ea273670043d657f0a4

See more details on using hashes here.

Provenance

The following attestation bundles were made for sub_pool_client-0.1.2-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