Skip to content

abridge: direct mode — serve @on clients as a standalone HTTP service#131

Merged
FatPigeorz merged 2 commits into
masterfrom
feat/abridge-serve-direct
Jul 2, 2026
Merged

abridge: direct mode — serve @on clients as a standalone HTTP service#131
FatPigeorz merged 2 commits into
masterfrom
feat/abridge-serve-direct

Conversation

@Meirtz

@Meirtz Meirtz commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Why

The tunnel exists for sandboxes with no egress: LLM traffic rides the runtime's Socket.IO connection, at the cost of two extra hops and the host process fanning in every concurrent rollout's calls. When the sandbox can reach the model-serving network (in-cluster vLLM/SGLang), that detour is pure overhead — the RL-at-scale scenario wants agents talking to the engine directly, with translation and identity stamping next to the engine instead of on the host.

What

agentix.bridge.serve — "the tunnel without the tunnel":

  • build_app(*clients) — one POST route per @on(path) handler; tunnel-shaped contract (JSON-object bodies, ClientResponse out, in-band errors; AbridgeError statuses survive here, which the tunnel wire can't do).
  • build_session_app(factory) — one client per caller identity: each request's API key (x-api-key/Bearer) maps to session_id_for(key) (public, so the minting side correlates), factory(session_id) builds that caller's client. LRU-bounded with in-flight-safe eviction (an evicted client closes only after its live requests drain), full cleanup via lifespan at shutdown, factory runs off-loop.
  • agentix-bridge-serve — Anthropic-speaking front for an OpenAI-compatible engine. Mint a key per rollout (the placeholder you already inject) and one server groups them all; agent keys are never forwarded upstream. Opt-in gating with --require-key-prefix <secret> (401 otherwise); loopback bind by default; trust model documented.

Deliberately not in scope: multi-backend routing and token capture — that's the full gateway's territory (#122); this PR only removes the host from the data path for the direct-connect case, and the tunnel remains the universal fallback.

Verification

  • 12 tests including: per-key session stability, eviction never closing an in-flight client (concurrent ASGI test), shutdown draining all clients, 401 gating, and an end-to-end proving two agent keys become two distinct upstream x-session-ids with the raw keys never reaching upstream headers.
  • Adversarial review (23-agent pass) on the first cut confirmed 3 lifecycle holes + the auth gap; all fixed in the second commit and re-verified.
  • Live smoke: /_health 200; bad key → 401; minted key with upstream down → clean in-band 502.
  • Full suite 398 passed; ruff + pyright clean.

Notes for review

This intentionally overlaps in purpose (not code) with #122's SessionForward — it's the minimal direct-connect enabler using only existing clients, meant to be subsumed by the full gateway when that lands. Happy to adjust naming/scope to whatever #122 settles on.

🤖 Generated with Claude Code

Meirtz and others added 2 commits July 2, 2026 15:19
The tunnel exists for sandboxes with no egress: LLM traffic rides the
runtime's Socket.IO connection at the cost of two extra hops and the
host process fanning in every rollout's calls. When the sandbox can
reach the model-serving network (in-cluster vLLM/SGLang), that detour
is pure overhead — serve the same @on(path) handlers next to the
engine instead and point the agent's SDK straight at it.

- build_app(*clients): one POST route per handler, tunnel-identical
  contract (JSON-object bodies, ClientResponse out, in-band errors)
- build_session_app(factory): per-caller sessions — the API key each
  agent sends (x-api-key / Bearer) hashes to a session id, the factory
  builds that caller's client, an LRU caps live clients and acloses
  evictions. Mint a key per rollout and one server groups them all;
  agent keys are never forwarded upstream.
- agentix-bridge-serve: Anthropic-speaking front for an
  OpenAI-compatible engine (lazy openai extra, loopback bind default)

Multi-backend routing and token capture stay out of scope — that is
the full gateway's job; this is the tunnel without the tunnel, and the
tunnel remains the mode for egress-less sandboxes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adversarial review of the first cut confirmed three lifecycle holes
and one trust-model gap; all fixed:

- eviction is now in-flight-safe: the session table stays LRU-bounded,
  but an evicted client closes only when its last live request
  releases (previously a burst of fresh caller keys aclosed clients
  mid-upstream-call, turning valid LLM calls into 502s — also removes
  the eviction-thrash DoS lever)
- a FastAPI lifespan closes the route-probe client at startup and
  drains every remaining session client at shutdown, matching the
  cleanup guarantee the tunnel's Proxy.session() gives
- the client factory runs outside the table lock via asyncio.to_thread
  (SDK construction is ~30ms of synchronous SSL/pool setup that was
  stalling the whole event loop under the lock)
- opt-in caller verification: build_session_app(verify_key=...) /
  --require-key-prefix gates requests to keys the harness minted (401
  otherwise); trust model documented in the module and README, and the
  README example now shows the non-loopback bind it implies
- session_id_for() is public and documented so the minting side can
  correlate upstream x-session-id values; build_app's docstring now
  states honestly that AbridgeError statuses survive here while the
  tunnel wire collapses them to 502

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@FatPigeorz FatPigeorz merged commit e3421fe into master Jul 2, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants