This file records long-lived architecture decisions for codex-lb. New decisions are appended and superseded by later entries rather than edited in place.
- Date: 2026-06-05
- Status: Accepted
- Scope:
app/modules/proxy/service.pyand proxy runtime internals
app/modules/proxy/service.py grew into a large god object that mixed HTTP
bridge orchestration, WebSocket proxying, streaming retry and settlement,
request logging, API-key usage, file operations, compact responses, warmup,
rate-limit payloads, and observability helpers in one ProxyService class.
The service is production-critical and contains compatibility behavior that is hard to safely rewrite in one pass. A big-bang replacement would create high review risk, hidden behavior drift, and poor rollback properties. Random small file-shaving also fails to create durable boundaries and lets the façade grow again during later feature work.
Use a target-architecture cutover refactor for proxy service decomposition.
app/modules/proxy/service.py remains the stable public façade and compatibility
import surface. Extracted implementation lives behind the private package
app/modules/proxy/_service/.
Small cohesive domains are leaf modules under _service/. Large domains are
folder packages with their own internal structure:
app/modules/proxy/
├── service.py # public façade / compatibility surface
├── _support.py # compatibility shim
├── _warmup.py # compatibility shim
└── _service/ # private implementation package
├── support.py
├── warmup.py
├── api_key_usage.py
├── request_log.py
├── rate_limit.py
├── file_ops.py
├── transcribe.py
├── codex_control.py
├── compact.py
├── observability.py
├── http_bridge/ # future: session lifecycle, relay, retry
├── websocket/ # future: connect, request state, replay
├── streaming/ # future: retry, once, settlement
└── account_selection/ # future: budget, failover, admission
For high-risk future domains such as HTTP bridge, WebSocket, streaming, and account selection, use this sequence:
- Add characterization/golden-master tests for the current behavior
- Use Mikado-style discovery to map hidden couplings and prerequisites
- Introduce narrow protocols/adapters where needed, following Branch by Abstraction inside the monolith
- Build the target domain package behind the façade
- Switch
ProxyServiceto the new package in one controlled cutover - Keep the codebase releasable at each commit
Architecture fitness functions are part of the decision. They ratchet the current decomposition direction and prevent accidental regression:
service.pymust stay below the accepted line-count thresholdProxyServicemaximum method span must not grow beyond the accepted threshold_support.pyand_warmup.pymust remain compatibility shims onlyservice.pymust preserve required façade re-exports for existing consumers_service/*modules must not form arbitrary cross-domain dependencies
| Alternative | Why rejected |
|---|---|
Big-bang rewrite of ProxyService |
Too much behavior would move at once; difficult to review, test, and roll back |
| Continue small ad-hoc extraction PRs | Reduces line count but does not create durable domain ownership or prevent regression |
| Split into external microservices now | Deployment/runtime complexity is unnecessary for the current problem; this is an internal modularity issue first |
Keep service.py as the implementation owner indefinitely |
Preserves compatibility but leaves the god-object failure mode in place |
- Future proxy work should add new domain behavior under
_service/, not directly intoservice.py - Compatibility shims may remain while internal consumers migrate, but they must not gain new behavior
- Large domain migrations should prefer complete package cutovers with targeted characterization tests over shallow helper movement
- CI can enforce architectural ratchets through
scripts/check_proxy_architecture.py - The accepted thresholds are starting ratchets, not final goals; future PRs should lower them after major domain packages are extracted