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.6.7.tar.gz (34.5 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.6.7-py3-none-any.whl (42.7 kB view details)

Uploaded Python 3

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

Hashes for sub_pool_client-0.6.7.tar.gz
Algorithm Hash digest
SHA256 65442b5669cbd5b4f43371601d3f82de1ea9898a3b96088a4258cdfe2e6aabf0
MD5 e3ab13da903a6575e38eb7b75fe42a72
BLAKE2b-256 476f3b649dab767cbf4a6aa24bd7217214a0e458d63052a1d4d2350b3b69eaa2

See more details on using hashes here.

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

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.6.7-py3-none-any.whl.

File metadata

File hashes

Hashes for sub_pool_client-0.6.7-py3-none-any.whl
Algorithm Hash digest
SHA256 53037c95aeca307a17c0e527277b955d7a0976f2618507eca87576c801810b38
MD5 22919260320ef6525b1517d694682d59
BLAKE2b-256 9b98401967bd0236e6b4d4151feedb10a0f2df7085ed8bc461394e333b2e9a17

See more details on using hashes here.

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

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