ghuntley/underclass

★ 115⑂ 5

underclass: an OpenAI-compatible pooling proxy that pins sessions to one account (prompt cache stays warm), cools quota-exhausted subscriptions until their window resets

About ghuntley/underclass

ghuntley/underclass is an open-source project on GitHub, mainly written in Rust. underclass: an OpenAI-compatible pooling proxy that pins sessions to one account (prompt cache stays warm) It currently holds 115 stars and 5 forks with 1 open issues, and was last pushed on 2026-09-20 (repository created 2026-09-20).

Project Overview

AI Homed tracks it on the Today's Trending board, currently at rank #88 with 0 new stars today.

GitHub Repository Details

Repository ghuntley/underclass · default branch main · size 170 KB · watchers 0 · source: GitHub REST API and repository README

README

underclass

A local proxy that pools multiple ChatGPT/Codex and GitHub Copilot subscriptions behind one OpenAI-compatible endpoint.

                       ┌────────────────────────────────────┐
                       │            underclass              │
                       │                                    │
 opencode ────────────►│  /v1/responses                     │
 (any OpenAI-compatible│  /v1/chat/completions              │────► chatgpt.com
  client)              │  /v1/models                        │      (N Codex subs,
                       │                                    │       OAuth device flow)
 web UI ◄─────────────►│  sticky sessions · health pool     │
 (accounts, catalog,   │  fail-fast saturation · tracing    │────► api.githubcopilot.com
  live request feed)   │                                    │      (M Copilot subs,
                       └────────────────────────────────────┘       GitHub device flow)

One subscription runs out of quota? It leaves rotation until its window resets — and comes back on its own. Sessions stay pinned to one subscription so upstream prompt caches stay warm. When everything is exhausted, the proxy fails fast with the earliest reset time instead of hanging.

---

Why

Subscription-based model access has a per-account quota. One account is a ceiling; twenty accounts are a pool. underclass turns a pile of personal subscriptions into a single durable endpoint that behaves like one well-provisioned provider:

Quick start

UNDERCLASS_PROXY_KEY="$(openssl rand -hex 32)" \
UNDERCLASS_UI_TOKEN="$(openssl rand -hex 32)" \
cargo run -- serve
underclass listening on http://127.0.0.1:8080
web ui: http://127.0.0.1:8080/

Keep the generated values in a password manager or runtime secret file; underclass never writes them to diagnostics. Open the web UI and paste the value supplied as UNDERCLASS_UI_TOKEN.

1. Open the web UI and paste the configured admin token. 2. Click Add account → pick ChatGPT / Codex or GitHub Copilot → enter the device code at the shown URL. The account is labeled automatically with the account's email or username. 3. Repeat for every subscription you want in the pool. 4. Point opencode at the pool:

cargo run -- connect
opencode --provider underclass --model underclass/gpt-5.5

underclass connect writes the provider block and credentials into your global opencode config (~/.config/opencode/opencode.json{,c} + auth.json), idempotently and with backups. Re-run it any time; --remove undoes it.

How routing works

Design decisions and their trade-offs live in docs/adr/ — start with ADR 0010 for the routing model.

Configuration

Optional ~/.config/underclass/config.toml:

| key | default | meaning | |---|---|---| | bind | 127.0.0.1:8080 | listen address | | proxy_key | minted on first run | bearer key clients must send to /v1/* | | ui_token | minted on first run | admin token for the web UI + /admin/api/* | | codex_cooldown_secs | 1800 | cooling window when upstream omits retry-after | | copilot_cooldown_secs | 1800 | same, for Copilot |

Environment overrides: UNDERCLASS_BIND, UNDERCLASS_PROXY_KEY, UNDERCLASS_UI_TOKEN. For testing against a mock upstream: UNDERCLASS_CODEX_UPSTREAM, UNDERCLASS_COPILOT_UPSTREAM (default to the real endpoints).

State (credentials, sticky bindings, model catalog, minted keys) lives in ~/.local/share/underclass/pool.db. Delete it to start fresh.

CLI

underclass serve [--bind ADDR]
underclass connect [--base-url URL] [--api-key KEY] [--model MODEL]
                   [--project] [--no-default-model] [--dry-run] [--remove]

connect targets the global opencode config by default; --project writes ./.opencode/opencode.json instead. --dry-run prints the merged documents without writing.

Endpoints

| route | auth | purpose | |---|---|---| | POST /v1/responses | proxy key | Responses API, streamed through to the pool | | POST /v1/chat/completions | proxy key | Chat Completions, same | | GET /v1/models | proxy key | union catalog with merged limits | | GET / | none | web UI | | GET /admin/api/state | admin token | accounts, catalog, last 200 requests | | POST /admin/api/flows | admin token | start a device-flow onboarding | | GET /admin/api/flows/{id} | admin token | poll an onboarding flow | | POST /admin/api/accounts/{id}/enable|disable|relogin | admin token | account controls | | DELETE /admin/api/accounts/{id} | admin token | remove from pool | | GET|PUT /admin/api/catalog/{backend} | admin token | inspect/edit model catalog | | GET /admin/api/client-key | admin token | retrieve the proxy key for connect |

Every response carries x-request-id; logs are JSON (RUST_LOG filters, --log-format json|pretty) and each request logs the account (label) that served it.

Correlation with preflight

When preflight fronts underclass, it generates a UUIDv4 and forwards it as x-request-id. Underclass preserves that ID in its response, routing/retry logs, and request history. Search the same request_id in both services to trace inspection through pool routing.

Underclass accepts one hyphenated RFC4122 UUIDv4 and normalizes it to lowercase. Missing, duplicate, invalid, nil, and other-version IDs are replaced before authentication or logging. Every handled response receives the resulting ID, including 401s, 429s, model discovery, and unmatched routes. Provider responses cannot replace it. Correlation IDs convey no authorization or proof that preflight inspected a request. See ADR 0012.

Models

The catalog is data, not code: seeded with the Codex families (gpt-5.4, gpt-5.4-mini, gpt-5.3-codex-spark, gpt-5.5, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-6-astra) and Copilot's live /models list. Edit it in the UI or via the admin API; routing eligibility and the opencode model block follow it. See ADR 0007.

Nix flake

The repository is a Nix flake: it exposes the CLI as a package/app and the devenv development shell as devShells.default.

Run the proxy without installing:

nix run github:ghuntley/underclass -- serve

Install it into your profile:

nix profile install github:ghuntley/underclass

Use the devenv shell (Rust toolchain, cargo) for development:

nix develop --no-pure-eval
cargo test

--no-pure-eval is required for the devenv shell (devenv inspects the working directory; this matches devenv's own flake template). The package and app outputs are pure — nix run and nix profile install need no flags.

Long-term devenv users can keep using devenv shell / devenv test directly — nix develop and devenv shell activate the same devenv.nix.

NixOS module

Add underclass to your flake inputs and import its module:

{
  inputs.underclass.url = "github:ghuntley/underclass";

outputs = { nixpkgs, underclass, ... }: { nixosConfigurations.my-host = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; modules = [ underclass.nixosModules.default { services.underclass = { enable = true; bindAddress = "127.0.0.1:8080"; environmentFile = "/run/secrets/underclass.env"; settings = { codex_cooldown_secs = 1800; copilot_cooldown_secs = 1800; }; }; } ]; }; }; }

The runtime environment file can provide credentials without placing them in the Nix store:

UNDERCLASS_PROXY_KEY=sk-underclass-...
UNDERCLASS_UI_TOKEN=...

The service uses a dynamic user, persists its database in /var/lib/underclass, binds to localhost by default, and leaves the firewall closed. Set services.underclass.openFirewall = true only when intentionally binding beyond localhost.

The flake also exports overlays.default. Validate the module and its QEMU machine test with:

nix build .#checks.x86_64-linux.underclass-module
nix build .#checks.x86_64-linux.underclass-vm

Notes:

Security

Development

cargo build
cargo test        # 43 unit tests + 8 Hegel property tests + e2e suite

Testing is two-tier: plain unit tests for exact behavior (headers, merges, redaction), and Hegel property tests over the pure pool core — stickiness stability, health-state invariants, saturation minimums, TTL/cap bounds. The core (src/pool.rs, src/health.rs) is synchronous with an injected clock; async lives only at the edges. New backends implement the provider::Backend trait and register — nothing else changes.

Agent conventions and the ADR policy are in AGENTS.md. Architecture decision records: docs/adr/.

License

MIT

Project layout

src/
  main.rs      bootstrap, router, background tasks
  config.rs    TOML + env config
  models.rs    domain types (Account, status, catalog, log entries)
  store.rs     SQLite persistence (accounts, bindings, catalog, config)
  pool.rs      pure pool core: stickiness, eligibility, selection
  health.rs    quota classification, retry-after parsing
  provider.rs  Backend trait
  codex.rs     ChatGPT/Codex backend (device flow, refresh, headers)
  copilot.rs   GitHub Copilot backend (device flow, catalog, headers)
  tokens.rs    single-flight token refresh
  proxy.rs     /v1/* handlers, failover, streaming
  logging.rs   structured logs, correlation IDs, redaction
  ui.rs        admin API
  ui.html      embedded single-page web UI
  cli.rs       connect verb, JSONC-safe opencode config merge
tests/
  properties.rs  Hegel property tests over the pure core
  e2e.rs         full-pool story against a mock upstream

GitHub Stars & Activity

115Stars
5Forks
1Open issues
RustLanguage

GitHub Popularity

GitHub stars115
Forks5
Open issues1
Primary languageRust
LicenseMIT
Stars gained today0
Created2026-09-20
Last pushed2026-09-20

Trending History

Daily boardrank #88 · ▲ 0 stars

Related AI Projects

1

zeroclaw-labs / zeroclaw

Rust★ 32,845⑂ 4,954▲ 11 stars
2

dmtrKovalenko / fff

Rust★ 10,788⑂ 448▲ 20 stars
3

akitaonrails / ai-memory

Rust★ 7,367⑂ 509▲ 114 stars
4

smol-machines / smolvm

Rust★ 6,203⑂ 297▲ 45 stars
5

HelixDB / helix-db

Rust★ 5,969⑂ 360▲ 14 stars
6

yynxxxxx / Codex-X

Rust★ 3,572⑂ 456▲ 210 stars
7

yyjeqhc / webcodex

Rust★ 1,544⑂ 196▲ 99 stars
8

lahfir / agent-desktop

Rust★ 1,357⑂ 88▲ 43 stars

More AI Rankings