Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ hud/rl/checkpoints_test/

docs/internal

environments/
environments/*
!environments/blank/
!environments/coding/
!environments/cua/

experiments/
.memories/
Expand Down
5 changes: 3 additions & 2 deletions docs/skill.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,9 @@ hud init my-env --preset browser

Available presets: `blank`, `browser`, `cua` (computer-use desktop),
`deepresearch`, `coding`, `ml`, `ml-triage`, `verilog`, `autonomous-businesses`,
`gdpval`, `worldsim`, `robot`, `videogamebench`, and `arc-agi-3`. Each downloads
a complete, runnable starting point you adapt — prefer it over hand-writing an
`gdpval`, `worldsim`, `robot`, `videogamebench`, and `arc-agi-3`. Each is a
complete, runnable starting point you adapt — `blank`, `coding` and `cua` ship
with the SDK, the rest download from GitHub. Prefer one over hand-writing an
environment from scratch. Cite [Quickstart](/v6/start/quickstart).

---
Expand Down
6 changes: 3 additions & 3 deletions docs/v6/reference/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ Install the CLI with `uv tool install hud --python 3.12`. Authenticate once with

### `hud init`

Scaffold a new environment package in a fresh `<name>` directory. The `blank` template (the default with no preset, and the first option in the interactive picker) writes a minimal local scaffold - `env.py` (environment, templates, capabilities), `tasks.py` (concrete task rows), `Dockerfile.hud`, and `pyproject.toml` - no network, no API key. Every other preset downloads a starter environment from GitHub instead.
Scaffold a new environment package in a fresh `<name>` directory. The `blank` template (the default with no preset, and the first option in the interactive picker) writes a minimal scaffold - `env.py` (environment, templates, capabilities), `tasks.py` (concrete task rows), `Dockerfile.hud`, and `pyproject.toml` - no network, no API key. `coding` and `cua` are bundled with the SDK too, so they always match your installed version; every other preset downloads a starter environment from GitHub.

```bash
hud init # pick a template → ./<template>
hud init my-env # pick a template (or minimal scaffold) → ./my-env
hud init my-env --preset browser # download the "browser" starter into ./my-env
hud init --preset cua # download the "cua" starter into ./cua-template
hud init --preset cua # the bundled "cua" starter into ./cua-template
hud init my-env --dir envs # create ./envs/my-env
```

| Option | Description |
|--------|-------------|
| `--preset`, `-p` | Starter to use: `blank` (minimal local scaffold), `browser`, `cua`, `deepresearch`, `coding`, `ml`, `ml-triage`, `verilog`, `autonomous-businesses`, `gdpval`, `worldsim`, `videogamebench` (all downloaded from GitHub). Omit for the interactive picker (TTY); with a `NAME` in a non-interactive shell, omitting it writes the blank local scaffold. |
| `--preset`, `-p` | Starter to use: `blank` (minimal scaffold), `coding` and `cua` (bundled with the SDK), or `browser`, `deepresearch`, `ml`, `ml-triage`, `verilog`, `autonomous-businesses`, `gdpval`, `worldsim`, `videogamebench` (downloaded from GitHub). Omit for the interactive picker (TTY); with a `NAME` in a non-interactive shell, omitting it writes the blank scaffold. |
| `--dir`, `-d` | Parent directory (default `.`). |
| `--force`, `-f` | Overwrite existing files. |

Expand Down
13 changes: 13 additions & 0 deletions environments/blank/Dockerfile.hud
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM python:3.11-slim

RUN apt-get update && apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY pyproject.toml uv.lock* ./
RUN pip install uv && uv sync --frozen --no-dev 2>/dev/null || uv sync --no-dev
COPY . .

# Serve the Environment's control channel (tcp JSON-RPC) on 8765.
EXPOSE 8765
CMD ["uv", "run", "hud", "serve", "env:env", "--host", "0.0.0.0", "--port", "8765"]
62 changes: 9 additions & 53 deletions hud/cli/templates.py → environments/blank/env.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,12 @@
"""File templates written by ``hud init``."""

DOCKERFILE_HUD = """\
FROM python:3.11-slim

RUN apt-get update && apt-get install -y --no-install-recommends curl \\
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY pyproject.toml uv.lock* ./
RUN pip install uv && uv sync --frozen --no-dev 2>/dev/null || uv sync --no-dev
COPY . .

# Serve the Environment's control channel (tcp JSON-RPC) on 8765.
EXPOSE 8765
CMD ["uv", "run", "hud", "serve", "env:env", "--host", "0.0.0.0", "--port", "8765"]
"""

# fmt: off
ENV_PY = '''\
"""{env_name} - HUD Environment"""
"""blank - HUD Environment"""

import asyncio
import tempfile
from pathlib import Path

from hud.environment import Environment

env = Environment(name="{env_name}")
env = Environment(name="blank")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the requested name in blank scaffolds

When hud init my-env or hud init my-env --preset blank copies this template, it now writes the literal blank instead of deriving the environment name from the requested target as the previous scaffold did. hud deploy treats the declared Environment name as the platform identity and uses get-or-rebuild semantics, so independently named blank projects all deploy as blank and later deployments can rebuild the same registry rather than create the intended environments. Restore target-name substitution for the blank scaffold and its project metadata.

AGENTS.md reference: AGENTS.md:L76-L78

Useful? React with 👍 / 👎.


# =============================================================================
# 1. WORKSPACE - give the agent a bash shell and file system
Expand All @@ -36,19 +16,20 @@
# The path is created fresh each run; change it to a fixed path if you need
# to pre-populate files (e.g. a git clone, dataset, or config).

WORKSPACE = Path(tempfile.mkdtemp(prefix="hud-{env_name}-"))
WORKSPACE = Path(tempfile.mkdtemp(prefix="hud-blank-"))
ws = env.workspace(WORKSPACE, network=True)


# =============================================================================
# 2. TASKS - a prompt for the agent, then how to score its answer
# =============================================================================


@env.template(id="count")
async def count(sentence: str, letter: str):
"""Agent must count a letter; we check if it got the answer right."""
# Yield the prompt, receive the agent\'s final answer back via ``asend``.
answer = yield f"How many times does \'{{letter}}\' appear in: \'{{sentence}}\'?"
# Yield the prompt, receive the agent's final answer back via ``asend``.
answer = yield f"How many times does '{letter}' appear in: '{sentence}'?"

# Score: 1.0 if correct, else 0.0.
correct = str(sentence.lower().count(letter.lower()))
Expand All @@ -64,7 +45,7 @@ async def count(sentence: str, letter: str):
# from fastmcp import FastMCP
# from hud.capabilities import Capability
#
# server = FastMCP(name="{env_name}-tools")
# server = FastMCP(name="blank-tools")
#
# @server.tool()
# async def my_tool(arg: str) -> str: ...
Expand All @@ -81,9 +62,10 @@ async def count(sentence: str, letter: str):
# TEST - run with: uv run python env.py
# =============================================================================


async def test():
from hud.agents.claude import ClaudeAgent
from hud import LocalRuntime
from hud.agents.claude import ClaudeAgent

agent = ClaudeAgent()

Expand Down Expand Up @@ -113,29 +95,3 @@ async def test():
# [count(sentence=s, letter="r") for s in ["strawberry", "raspberry"]],
# )
# job = await ts.run(ClaudeAgent(), group=4, max_concurrent=8)
'''
# fmt: on

TASKS_PY = '''\
"""Tasks for {env_name} — run with: hud eval tasks.py <agent> (e.g. claude)."""

from env import count

# ``hud eval`` collects these Tasks — each is the ``count`` task bound to
# concrete args. Add your own, or build them in a loop.
tasks = [
count(sentence="Strawberry world", letter="r"),
count(sentence="banana", letter="a"),
]
'''

PYPROJECT_TOML = """\
[project]
name = "{name}"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["hud"]

[tool.uv]
package = false
"""
8 changes: 8 additions & 0 deletions environments/blank/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
name = "blank-env"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["hud"]

[tool.uv]
package = false
10 changes: 10 additions & 0 deletions environments/blank/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""Tasks for blank — run with: hud eval tasks.py <agent> (e.g. claude)."""

from env import count

# ``hud eval`` collects these Tasks — each is the ``count`` task bound to
# concrete args. Add your own, or build them in a loop.
tasks = [
count(sentence="Strawberry world", letter="r"),
count(sentence="banana", letter="a"),
]
8 changes: 8 additions & 0 deletions environments/coding/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# The build context is this directory; keep it to what the images COPY, plus
# the Dockerfile itself (remote builders read it from the context).
*
!Dockerfile.hud
!env.py
!coding
!instances
**/__pycache__
36 changes: 36 additions & 0 deletions environments/coding/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Python
__pycache__/
*.py[cod]
.pytest_cache/
.ruff_cache/
.coverage
.venv
.env
build/
dist/
*.egg-info/

# OS / IDE
.DS_Store
.vscode/
.idea/
*.swp

# HUD
hud.lock.yaml
.hud
.hud_local/
.hud_eval.toml
.hud-images.json

# SWE-bench Pro instance assets are fetched per-user (swe_tasks.py) and carry
# the golden patches; only the empty .none dir (generic image builds) ships.
instances/*
!instances/.none/

# The SDK repo ignores uv.lock at its root; this template pins its own so the
# example resolves reproducibly.
!uv.lock

# Logs
*.log
72 changes: 72 additions & 0 deletions environments/coding/Dockerfile.hud
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# syntax=docker/dockerfile:1
# One image definition for both flavors.
#
# Generic (default) — the repo-clone stage bakes your target repo into /app:
# docker build -f Dockerfile.hud --build-arg REPO_URL=https://github.com/you/repo .
# (private repos: --secret id=CODING_GITHUB_TOKEN,env=CODING_GITHUB_TOKEN)
#
# SWE-bench Pro (driven by swe_tasks.py) — BASE selects the instance's
# prebuilt image (repo already at /app) and INSTANCE_ID bakes its assets:
# docker build -f Dockerfile.hud --build-arg BASE=jefzda/sweap-images:<tag> \
# --build-arg INSTANCE_ID=<id> .
ARG BASE=repo-clone

# ─── generic head: toolchain + target repo at /app ───
FROM ubuntu:24.04 AS repo-clone

RUN apt-get update -y \
&& apt-get install -y --no-install-recommends \
bash build-essential ca-certificates curl git openssl \
python-is-python3 python3 python3-dev python3-pip python3-venv util-linux \
&& rm -rf /var/lib/apt/lists/* \
&& update-ca-certificates

# The sample tasks' test command uses the image's python; adjust per repo.
RUN pip3 install --break-system-packages pytest

ARG REPO_URL="https://github.com/hud-evals/coding-template-sample"
# chown in the same RUN as the clone (a separate layer would duplicate /app):
# env.py serves agent shells as uid 1000, so the baked tree must be theirs.
# Doing it at build makes it free at runtime, whatever the repo's size.
RUN --mount=type=secret,id=CODING_GITHUB_TOKEN \
if [ -f /run/secrets/CODING_GITHUB_TOKEN ]; then \
git clone "https://$(cat /run/secrets/CODING_GITHUB_TOKEN)@${REPO_URL#https://}" /app; \
else \
git clone ${REPO_URL} /app; \
fi \
&& chown -R 1000:1000 /app
# Add a build step for the target repo here if it needs one (deps, compile).

# ─── the served image: BASE + hud serving layer (+ instance assets) ───
FROM ${BASE}

# ".none" is a committed empty dir, so generic builds bake no instance and
# env.py serves only the generic coding-task template.
ARG INSTANCE_ID=".none"
COPY env.py /hud/env.py
COPY coding /hud/coding
COPY instances/${INSTANCE_ID}/ /hud/instance/

# Self-contained hud venv under /hud: bootstrap uv (with its own managed
# Python), never touching the base image's Python or site-packages. chmod 700
# keeps the vault and instance assets out of the uid-dropped agent's reach.
RUN <<'INSTALL'
set -eu
export PATH="$HOME/.local/bin:$PATH" UV_INSTALL_DIR="$HOME/.local/bin"
command -v uv >/dev/null 2>&1 || {
{ command -v curl >/dev/null 2>&1 || command -v wget >/dev/null 2>&1; } \
|| { apt-get update -qq && apt-get install -y -qq curl ca-certificates; } \
|| apk add --no-cache curl ca-certificates
{ command -v curl >/dev/null 2>&1 && curl -LsSf https://astral.sh/uv/install.sh | sh; } \
|| { command -v wget >/dev/null 2>&1 && wget -qO- https://astral.sh/uv/install.sh | sh; } \
|| pip install -q -U uv
}
uv python install 3.12
uv venv /hud/venv --python 3.12
uv pip install --python /hud/venv/bin/python 'hud>=0.6.11'
chmod -R go-rwx /hud
INSTALL
ENV REPO_DIR=/app PYTHONPATH=/hud
EXPOSE 8765
ENTRYPOINT []
CMD ["/hud/venv/bin/hud", "serve", "/hud/env.py", "--host", "0.0.0.0", "--port", "8765"]
Loading