Skip to content

tito: token-in-token-out session-recording gateway (PR #122 stage B)#134

Merged
Meirtz merged 1 commit into
masterfrom
tito/gateway
Jul 2, 2026
Merged

tito: token-in-token-out session-recording gateway (PR #122 stage B)#134
Meirtz merged 1 commit into
masterfrom
tito/gateway

Conversation

@Meirtz

@Meirtz Meirtz commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Second slice of the #122 split (stage map in the coordination comment; stage A was #133). Ports plugins/tito from origin/abridge/gateway-tito onto master as a self-contained uv workspace member, plus fixes from the planned list and two adversarial review passes.

What's in

The TITO gateway — a FastAPI session server that keeps a token-aligned trajectory per session and proxies chat completions across a pool of OpenAI-compatible sglang replicas. The native engine does incremental pretokenization (merge onto the stored token prefix instead of re-rendering), append-only message validation, single-step rollback on agent retries, and from-scratch-vs-accumulated mismatch reporting. No vendored Miles; imports nothing from agentix core beyond the namespace. Server side of the Stage A SessionForward contract (POST /sessions, POST /sessions/{id}/v1/chat/completions, harvest via GET, reap via DELETE).

Planned fixes (from the #122 review)

  • Backend recovery: report_up wired into the proxy success path + down marks expire after down_cooldown (half-open) — a downed replica was previously blacklisted forever
  • stream:true 500: the chat route forces stream=false upstream (the TITO flow needs the full JSON completion with logprobs)
  • Unbounded session pins: sticky pins LRU-bounded at max_pins

Adversarial review fixes (2 passes, 3 perspectives + 2 rebutters per finding)

  • Tool-call turns with content:null no longer 502 — previously the first tool call of an agentic rollout died (sglang/vLLM emit null content on tool-call-only turns)
  • trust_remote_code is an explicit opt-in (config + --trust-remote-code), was a hardcoded True
  • Checkpoint memory: keep only the two rollback-reachable checkpoints instead of every full prefix+completion list (O(turns²) per session); retry chains that outrun the window re-render from scratch (lossless — incremental == from-scratch is the engine invariant)
  • Rollback atomicity: plan+validate before mutating — a rejected (400) request no longer commits truncation that bricked the original branch
  • ABA guard: phase-3 interference detection uses a monotonic version (rollback+update restored num_assistant, admitting stale updates)
  • DELETE robustness: closing set inside the lock (a cancelled DELETE no longer wedges the session forever); request body read before the lock (a dribbling upload no longer blocks DELETE)
  • Error shapes: malformed request JSON → 400; structurally malformed 200 upstream → clean 502 (was unhandled 500); upstream bodies pass through verbatim (no JSON re-encode — 500'd on NaN, perturbed byte-sensitive output)
  • CLI: --backend-url repeatable + --routing-policy (the multi-replica pool was Python-API-only); packaging: requires-python >=3.11, unused setproctitle dropped

Verification

  • uv run pytest tests/ plugins/ — 486 passed (26 new tests: real HTTP surface via httpx.ASGITransport — session flow, forced non-streaming, tool-call null content, failover + cooldown recovery, error shapes, lock/cancellation behavior — plus pool + engine regression tests)
  • uv run ruff check . / uv run pyright — clean / 0 errors
  • Port fidelity: byte-identical to the source branch except the fixes above (git diff origin/abridge/gateway-tito -- plugins/tito)

Deferred (disputed in review, engine semantics for follow-up): TEMPLATE_RELEVANT_KEYS omitting name/tool_call_id in default-mode append-only matching.

🤖 Generated with Claude Code

Port plugins/tito from origin/abridge/gateway-tito onto master as the
second slice of the #122 split: a self-contained workspace member
providing the TITO gateway — a FastAPI session server that keeps a
token-aligned trajectory per session and proxies chat completions
across a pool of OpenAI-compatible sglang replicas, with a native
engine (incremental pretokenization, append-only message validation,
single-step rollback, mismatch reporting). No vendored Miles; imports
nothing from agentix core beyond the namespace.

Planned fixes on top of the port:

- BackendPool: a downed replica now recovers — report_up is wired into
  the proxy success path, and a down mark expires after down_cooldown
  seconds (half-open) so a replica that is never picked isn't
  blacklisted forever.
- BackendPool: sticky session pins are LRU-bounded (max_pins) — the
  recommended rollout flow never DELETEs its session (the trajectory
  must survive for harvest), so the pin map grew without bound.
- chat route: force stream=false upstream (drop stream_options) — the
  TITO flow needs the full JSON completion; a stream:true agent
  previously got a 500 from json-parsing the SSE bytes.

Fixes from the pre-PR adversarial review:

- chat route: accept tool-call-only assistant turns with content:null
  (sglang/vLLM emit them routinely) — previously the first tool call
  of an agentic rollout 502'd.
- trust_remote_code is now an explicit opt-in (config field +
  --trust-remote-code), never a hardcoded True: loading a tokenizer
  executes Python shipped inside the checkpoint repo.
- trajectory: keep only the two reachable checkpoints (single-step
  rollback) instead of every full prefix+completion token list —
  O(turns^2) dead memory per session.
- chat route: malformed request JSON is a 400; structurally malformed
  200 upstream bodies are a clean 502 (not an unhandled 500).
- proxy responses pass the upstream body through verbatim — no JSON
  re-encode (which 500'd on NaN/Infinity and perturbed byte-sensitive
  bodies).
- prefix-mismatch diagnostic no longer raises a bare ValueError when
  the new sequence is shorter than the stored checkpoint.
- CLI: --backend-url is repeatable (multi-replica pool) and
  --routing-policy is exposed — the pool was Python-API-only.
- packaging: requires-python >=3.11 (StrEnum), drop unused
  setproctitle dep, sglang-specific docs corrected.

- concurrency (from the concurrency review pass): a retry chain that
  outruns the trimmed checkpoint window now re-renders from scratch
  instead of merging onto an empty prefix (which silently dropped the
  whole stored history and recorded a corrupt trajectory); rollback is
  planned+validated before any mutation, so a rejected (400) request
  no longer commits truncation side effects; the phase-3 interference
  guard uses a monotonic version counter (num_assistant was ABA-prone);
  DELETE sets `closing` inside the lock (a cancelled DELETE no longer
  wedges the session); the request body is read before taking the
  session lock (a dribbling upload no longer blocks DELETE); the
  forget-on-delete middleware matches only the exact session resource.

Tests: real HTTP-surface coverage (httpx ASGITransport over the actual
FastAPI app: session flow, forced non-streaming, tool-call null
content, failover + cooldown recovery, error shapes, verbatim
passthrough) on top of the ported unit tests. plugins/tito wired into
the uv workspace + pyright include/extraPaths.

Co-authored-by: FatPigeorz <wjhhhhhha0@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant