HUD Python SDK

repository·main·Indexed 42 days ago

https://github.com/hud-evals/hud-python

SDK for the HUD platform, a framework for building Reinforcement Learning (RL) environments for AI agents across domains such as coding, web browsing, computer use, and robotics. It enables developers to define environments, write tasks, and run evaluations or training at scale, featuring support for GRPO advantage calculation and various RL loss functions like PPO and CISPO.

Tokens
86.8K
Snippets
252
Records
372
Agent score
53%

What's inside HUD

  1. Overview of HUD SDK module roles

    main

    The SDK is split into two halves that communicate via a wire protocol: one half declares and serves an environment, while the other places and drives it. The only data that crosses between them at rest is a Task row, and they connect at runtime via a TCP control channel.

    Core Modules

    ModuleRole
    hud/environment/env.pyThe Environment declaration (capabilities, task templates, lifecycle hooks). Pure data, no runtime state.
    hud/environment/server.pyImplements the declaration on the wire using a JSON-RPC control channel and per-task suspended generators.
    hud/eval/task.pyDefines the Task row (env name, task id, bound args) using Pydantic.
    hud/eval/runtime.pyHandles placement via the Provider contract and built-in runtimes (e.g., LocalRuntime, DockerRuntime).
    hud/eval/run.pyContains the rollout atom and the Run handle for driving a single task to a graded result.
    hud/eval/taskset.pyThe scheduler: expands tasks, resolves placement, and gathers rollouts into a Job.
    hud/clients/client.pyProvides connect and HudClient (the client-side protocol and capability tunneling).
    hud/agents/base.pyDefines the Agent contract via async __call__(run).
  2. A2A Chat cookbook file structure

    main

    The A2A Chat cookbook consists of the following components:

    • server.py: An A2A server that manages one Chat (conversation) per A2A context, including agent cards and citations artifacts.
    • client.py: A minimal A2A client used to send messages and print replies.
    • llm_client.py: An LLM-fronted client where an OpenAI model decides when to invoke the A2A agent as a tool.
    • chat_env.py: A sample chat environment that serves messages-style tasks.

    The server publishes an agent card at /.well-known/agent-card.json and accepts A2A messages at the root endpoint. Configured tasks must accept a messages argument to handle multi-turn history.

  3. What is a Workspace and how to use it

    main

    A Workspace is a built-in workspace daemon that provides ssh access for agents and, optionally, file tracking for rollout telemetry. It consists of a directory and a bwrap-isolated SSH exec server where shell commands and file operations run through the same channel.

    To use a workspace within an Environment, use env.workspace(root, ...). To run a workspace independently of an environment, you must manually manage its lifecycle using .start() and .stop() and then publish its capability using ws.capability().

    Note:

    • Sandbox isolation via bwrap is Linux-only. On other platforms, it is unisolated unless running in a built image.
    • For a complete repository workspace with hidden-test grading, use the command: hud init my-coding-env --template coding.
    from hud.environment import Environment, Workspace
    
    env = Environment(name="coder")
    ws = Workspace("workspace", host="127.0.0.1", port=0)   # port 0 = ephemeral
    
    @env.initialize
    async def _up():
        await ws.start()                          # binds, generates keys; idempotent
        env.add_capability(ws.capability("shell"))
    
    @env.shutdown
    async def _down():
        await ws.stop()
  4. What is a Compose environment?

    main

    A Compose environment allows you to declare a multi-service world (e.g., an agent, a database, and a network-reachable application) as a standard Docker Compose project.

    Key concepts:

    • main service: The primary service that runs hud serve. It is the only service whose control port (typically 8765) is published by HUD.
    • Container Boundaries: Instead of managing privilege drops or hidden paths manually inside one container, you use container boundaries and depends_on conditions to isolate services.
    • The Build Graph: All dependencies, including base images and injected payloads, are expressed within the Compose document, ensuring docker compose build reproduces the entire environment.
    • The Agent's Workspace: The agent's shell resides in the main service's workspace. Sibling services are only accessible to the agent via the network.
  5. Ensure taskset diversity

    main

    A taskset where every task performs the same operation (varying only proper nouns) will not train general capability.

    Best Practice: Diversify your taskset across:

    • Targeted failure modes
    • Substrate sources
    • Deliverable shapes
    • Capabilities exercised
    • Difficulty distribution (avoiding all-zero or all-saturated scores)
  6. How robot environments and agents work together

    main

    Robotics in HUD is implemented as a continuous observation/action loop over WebSocket rather than standard tool calls. This allows high-frequency policies (e.g., 50 Hz) to stream actions and receive observations (camera frames, robot state) as fast as the policy can run.

    The architecture is split into two sides:

    Environment Side

    Responsible for translating actions into environment changes and serving observations. It consists of:

    • RobotBridge: The class you implement to wrap your simulator. It must implement reset, step, and get_observation. The framework handles the WebSocket server loop.
    • RobotEndpoint: A handle that wraps the bridge, used by the agent to interact with the simulator.

    Agent Side

    Responsible for running the policy. It consists of:

    • RobotAgent: The harness that connects to the environment, owns the adapter and model, and drives the model until the environment terminates.
    • Model: A stateless checkpoint of the policy, including pre- and post-processing.
    • Adapter: Translates the environment's observation space to the model's input space, and the model's action space to the environment's input space.

    The Contract

    Both sides share a contract: a self-describing JSON schema (carried in the capability's manifest params) that defines the embodiment's control rate, observation spaces, and action spaces. The agent uses this manifest to wire observations to policy inputs without needing a shared configuration file.

  7. Understand SubScore and EvaluationResult

    main

    HUD uses two primary structures to represent grading data:

    SubScore

    A SubScore represents a single node in a grading tree. It can be a leaf (a single measurement) or a parent (a combination of other scores).

    • name: The name of the component or requirement.
    • value: The measurement (0.0 to 1.0).
    • weight: The weight applied in the parent's combination (negative values indicate penalties).
    • children: A list of input SubScores used to derive this node's value.
    • info: A dictionary for grader-specific metadata.

    EvaluationResult

    This is the final payload yielded from a task. It represents the complete grading outcome.

    • reward: The final aggregated score (float).
    • done: Boolean indicating if the episode is complete.
    • subscores: The breakdown of the score (the grading tree).
    • info: Extra metadata.
    • content: A human-readable explanation.
    • isError: Indicates if the grading process itself failed.

    You can wrap a simple float into an EvaluationResult using EvaluationResult.from_float(value).

  8. Evaluate difficulty using a spanning set of models

    main

    Difficulty is relative to a specific model's capability. Validating a task only against a cluster of similar frontier models can lead to noisy results where models don't order cleanly.

    Best Practice: Evaluate against a deliberate weak anchor and a strong anchor to create a capability range that spans. Always state the model and reasoning regime you calibrated against.

  9. Ensure non-degenerate reward spread for training signal

    main

    When designing tasks for RL post-training (like GRPO), the training signal depends on the spread within a group of rollouts, not the mean reward. If every rollout in a group receives the same reward, the advantage is zero and no gradient is produced.

    To verify your task is trainable, run it as a group and check that the rewards vary.

    Red Flags:

    • All-zero rewards: At small group sizes, this may indicate the task is too hard, though it might surface successes at larger scales.
    • All-one (saturated) rewards: The task is too easy and provides no useful training surface.
    • Variance destruction: Avoid using hard caps, vocabulary gates, or oversized penalties that clamp rewards to a narrow band, as this prevents the model from learning from quality differences.
    taskset = Taskset("spread-check", [my_task(seed=s) for s in range(5)])
    job = await taskset.run(agent, group=16)
    rewards = [run.reward for run in job.runs]
    # All 0.0 (or all 1.0) -> no signal. You want a non-degenerate spread.
  10. How HUD v6 tasks and environments work together

    main

    The HUD v6 model is composed of three core abstractions:

    1. Environment: Where the agent acts, exposed via Capabilities (e.g., ssh, mcp, cdp, rfb, robot).
    2. Task: An async generator that yields a prompt, receives an answer, and yields a reward (0.0–1.0). Calling a decorated task function creates a runnable Task.
    3. Trace: A single graded evaluation, represented in the SDK as a Run.

    To create a task, use the @env.template() decorator on an async generator function.

    from hud import Environment
    
    env = Environment(name="letter-count")
    
    @env.template()
    async def count_letter(word: str = "strawberry", letter: str = "r"):
        answer = yield f"How many '{letter}'s in '{word}'?"
        yield 1.0 if answer and str(word.count(letter)) in answer else 0.0
    
    # Creating tasks from the template
    tasks = [count_letter(word=w) for w in ("strawberry", "raspberry", "blueberry")]
  11. Align prompts and graders for monotonicity

    main

    A well-designed task requires alignment between the prompt and the grader:

    1. Prompt-grader alignment: Do not score for content the prompt never requested, and do not ask for work that the grader ignores.
    2. Score-quality monotonicity: A rollout that performs better substantively must not receive a lower score. If a generic response outscores a thorough one, the grader is likely measuring shape rather than substance.

    Tip: Use subscores (via the combine method) to make partial rewards legible. This helps identify which component earned the reward and makes it easier to catch monotonicity violations.