Skip to content

[RFC] SkyRL TITO Proxy #1959

Description

@xinze-zheng

Summary

This RFC focuses on the agent integration item in roadmap #1846.

A generator-owned proxy server exposes an OpenAI-compatible Chat Completions
endpoint to agent frameworks such as Harbor. Instead of forwarding chat messages
directly to vLLM, the proxy renders messages into token IDs, sends exact prompt
IDs to SkyRL's inference engine, records sampled completion IDs and logprobs, and
returns a compatible chat response to the agent.

For subsequent turns, the proxy preserves sampled token IDs and safely appends
new environment messages using a model-aware renderer. The recorded trace is
finally converted to GeneratorOutput.

Functional Requirements

  1. Provide a /chat/completions-compatible endpoint that requires Harbor to
    change only its endpoint URL.
  2. Transparently run token-in/token-out inference through the SkyRL router and
    record tokens, masks, logprobs, and potentially R3 data efficiently.
  3. Handle compaction and expose branch and transition views.

Design

Trace Design

Trace is the bookkeeping object for one trial attempt.

  • Trace maintains a message graph. Each node stores one canonical message, its
    parent ID, and the token, mask, and logprob delta introduced by that message.
  • Trace finds the longest message prefix with an in-memory
    (parent_id, message_hash) -> node_id index.
  • Before reusing a prefix, commit verifies that node token deltas exactly match
    the prompt IDs sent to inference.
  • Every root-to-leaf path is one exact trajectory. Shared prefixes are stored
    once.
  • Trace exposes branch and transition views without duplicating the underlying
    message graph.
  • Trace's underlying data structure is similar to Trie, Therefore the complexity for matching is $O(m)$ where $m$ is the number of messages. We do hope the branch will not happen often so we will also maintain the hash of the latest context for fast match.

Proxy Design

  • HarborGenerator.generate() starts one FastAPI proxy. Concurrent Harbor
    trials share that proxy.
  • Each trial attempt creates a Trace and registers it with the proxy. Harbor's
    model endpoint is changed to the proxy endpoint.
  • For the first call, the proxy renders the complete prompt. For later calls,
    Trace finds the latest matching message node and returns the new message tail.
  • The renderer safely extends the previous exact (prompt_ids, completion_ids)
    with the new tail.
  • The proxy runs token-in inference, commits the exact result to Trace, and then
    returns the Chat Completions response to the agent.

Example

The easiest way to illustrate this design is the following example.

Round 1

  • Proxy receives initial messages $R_1$ and renders prompt IDs $P_1$.
  • Inference returns completion IDs $C_1$ and logprobs $L_1$. Renderer parses
    $C_1$ into assistant message $M_1$.
  • Trace commits one node per message in $R_1$, followed by a sampled $M_1$ node.
  • Proxy returns $M_1$ to the agent.

Round 2

  • Agent runs an environment step and receives message $T_1$.
  • Agent sends $R_1 \Vert [M_1, T_1]$.
  • Trace matches $R_1 \Vert [M_1]$, recovers exact $(P_1, C_1)$, and identifies
    $[T_1]$ as the new tail.
  • Renderer constructs $P_2 = \mathrm{Bridge}(P_1, C_1, [T_1])$.
  • Inference returns $(C_2, L_2)$ and renderer parses assistant message $M_2$.
  • Trace commits a nonsampled $T_1$ node and a sampled $M_2$ node. The path
    reconstructs $P_2 \Vert C_2$.
  • Proxy returns $M_2$ to the agent.

Round 3

  • Agent sends $R_1 \Vert [M_1, T_1, M_2, T_2]$.
  • Trace matches through $M_2$ and identifies $[T_2]$ as the new tail.
  • Renderer constructs $P_3 = \mathrm{Bridge}(P_2, C_2, [T_2])$.
  • Inference returns $(C_3, L_3)$ and renderer parses $M_3$.
  • Trace commits $T_2$ and $M_3$. The path reconstructs $P_3 \Vert C_3$.
  • Proxy returns $M_3$ to the agent.

Branch Cases

  • Linear extension: Message and token prefixes match. New nodes extend the
    current leaf.
  • Compaction or rewritten history: Matching stops at the last unchanged
    message. New messages form a branch from that node or the dummy root.
  • Unsafe renderer extension: Renderer returns None. The proxy fully renders
    the prompt, and exact token comparison selects the longest reusable node.
  • Retry: An identical retry reuses the in-flight or cached response and does
    not commit new nodes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions