- the
Environmentobject - a lightweight control handle. It doesn’t hold the world itself; it’s where you register what the environment exposes. - the
env.pyfile - the whole environment you author, serve, and ship: the object plus everything registered on it.
Declaring an environment
Everything lives in one file, conventionallyenv.py: an ordinary Python module that constructs the
Environment and registers its capabilities, hooks, and tasks against it. A complete one is small -
declare the object, give it the access the agent needs, optionally set up and tear down state, and
define at least one task:
env.py
Environment object in it, and runs everything
registered on it. The only contract is “this module defines an Environment”, which is what makes
the declaration portable: the same env.py runs locally, in a container, or on HUD with nothing
changed but the runtime.
Capabilities: the access you expose
A capability is the agent’s way in - a connection the system already speaks. A machine has a shell, so it speaksssh; a web app has a browser, so it speaks cdp. You expose the connection the
system already has, and the agent drives it natively with its own tools. Two things fall out for
free: wrapping any system is trivial, and nothing about the agent is baked in - the
same environment keeps working with any model or harness, today’s or next year’s.
The most common capability is a shell: a Workspace is a sandboxed directory the agent works in
over ssh, and env.workspace(root) brings it up, publishes its ssh capability, and tears it down
with the env - one line, no hook:
env.py
ssh - Claude Code, a coding agent,
your own - can open a shell and edit files in the workspace.
You register capabilities three ways: in the constructor for a service that already exists (the
ssh capability in the scaffold above), with env.workspace(root) for the common shell case, or
with env.add_capability(...) from a hook for a daemon the env runs itself (next section). Each
is concrete wire data - the URL of something serving the protocol. Every protocol has a
copy-pasteable spin-up in the Capabilities reference, with the library that
backs it.
Lifecycle hooks: set up and tear down
When a task needs state - seeded files, a running service, a browser - you bring it up in@env.initialize and release it in @env.shutdown. @env.initialize runs once before the env
accepts any connection, so by the time an agent connects, everything it needs is already in place:
env.py
env.add_capability(...).
It replaces any same-named entry, so re-serving overwrites a stale address rather than duplicating
it. The full pattern - starting a server task and blocking until it binds - lives in
Capabilities.
Tasks: a prompt and a reward
A task is what the agent actually does in the environment, and you register it the same way you register everything else - on the object, with a decorator.@env.template() turns an async
generator into a task template: it yields a prompt, receives the agent’s answer back, and
yields a reward (0.0-1.0). Everything the agent does in the environment happens between those
two yields.
env.py
word, letter) fill in per run, so
one definition describes a whole space of tasks. Calling it - count_letter(word="raspberry") -
binds those arguments and mints one concrete Task you can run. Register as many
templates as you like on one environment; each is advertised in the manifest, so a harness can
discover what the environment offers and the orchestrator can pick which to run.
@env.template() takes a few optional arguments:
| Parameter | Type | Description |
|---|---|---|
id | str | None | Task id (defaults to the function name). |
description | str | Human-readable description, surfaced in the manifest. |
input | Any | Optional type for the agent’s input (JSON schema in the manifest). |
returns | Any | Optional type the agent must produce; the answer arrives as an Answer[T]. See Types. |
The Environment object
hud.environment.Environment is the lightweight control object the whole file hangs off. When
served, it acts as the server an agent harness connects to over the protocol:
it answers hello with its capabilities and runs its tasks on request.
| Parameter | Type | Default | Description |
|---|---|---|---|
name | str | "environment" | Environment identity (used as the env-ref name). |
version | str | "0.0.1" | Version string surfaced in the manifest. |
capabilities | list[Capability] | None | None | Wire data for services that already exist; see Capabilities. |
Passing v5-only keywords emits a
DeprecationWarning and ignores them. See Migrate to v6.Serving
You rarely serve by hand -hud eval, task.run(), and Taskset.run() bring the environment up for
you, and the runtime you pass decides where. Serving itself belongs to
hud.environment.server, the same entry point a container CMD runs
(python -m hud.environment.server <source>):
| Function | Description |
|---|---|
await serve(env, host="127.0.0.1", port=0) | Start daemons and accept control-channel connections (blocks). |
await bind(env, host="127.0.0.1", port=0) | Bind the socket and return an asyncio.Server without serving. |
await env.start() / await env.stop() | Run @env.initialize / @env.shutdown hooks directly. |
A dependency that must own the process main thread (e.g. Isaac Sim / Omniverse) can’t run under
hud serve, which runs the asyncio loop on main. Run serve(env, host, port) on a worker thread
instead and keep the main thread for the dependency - see Robots.Learn from real environments
The fastest way to internalize the patterns is to read complete ones. Each cookbook walks anenv.py
end to end:
Coding agent
A shell + files env that grades a test suite.
Ops diagnostics
Seed state in
@env.initialize, grade by inspection.Robot benchmark
A simulator env over the
robot capability.More on GitHub
Full, runnable environments in the SDK repo.
See also
Capabilities
Every protocol factory, its params, and how to spin it up.
Tasks & Tasksets
Add tasks that prompt and grade against this environment.
Runtime
Point a harness at your environment and run it anywhere.
Composing richer environments
Multi-capability envs, stateful daemons, and custom setups.