Skip to main content
hud.environment.Environment is the control channel that exposes capabilities and tasks. Import it from the top level or the subpackage:
from hud import Environment
# or: from hud.environment import Environment

Constructor

Environment(name="environment", *, version="0.0.1", capabilities=None)
ParameterTypeDefaultDescription
namestr"environment"Environment identity (used as the env-ref name).
versionstr"0.0.1"Version string surfaced in the manifest.
capabilitieslist[Capability] | NoneNoneCapabilities to publish — concrete wire data for services that already exist (Capability.cdp(url=...)). Daemons the env runs itself publish theirs at serve time: env.workspace(root) for the shell case, env.add_capability(...) from an @env.initialize hook in general.
Passing v5-only keywords emits a DeprecationWarning and ignores them. See Migrate to v6.

Registering tasks

@env.template(*, id=None, description="", input=None, returns=None)
Registers a template: an async generator that yields a prompt and a reward. Calling the decorated object mints a public Task.
ParameterTypeDescription
idstr | NoneTask id (defaults to the function name).
descriptionstrHuman-readable description, surfaced in the manifest.
inputAnyOptional type for the agent’s input (JSON schema in the manifest).
returnsAnyOptional type the agent must produce; the answer arrives as an Answer[T]. See Types.
@env.template(id="count", description="Count a letter", returns=int)
async def count_letter(word: str = "strawberry", letter: str = "r"):
    answer = yield f"How many '{letter}'s in '{word}'?"
    yield 1.0 if str(word.count(letter)) in str(answer.content) else 0.0

Capabilities

env.workspace("/workspace")    # attach a Workspace; publishes "shell" (ssh/2) at serve
env.add_capability(cap)        # publish concrete wire data (replaces a same-named entry)
A Capability is always concrete wire data — the URL of something serving the protocol. Pass capabilities for services that already exist to the constructor; for a daemon the env runs itself, start it in an @env.initialize hook and publish its address with env.add_capability(...). env.workspace(root) wires the common shell case: nothing touches the filesystem until the env serves. See Capabilities.

Lifecycle hooks

@env.initialize
async def _seed():
    (ROOT / "fixture.txt").write_text("...")

@env.shutdown
async def _stop():
    ...
Hooks run once around serving — seed state, or stand up a daemon and publish its capability with env.add_capability(...). By the time a client says hello, every published capability is concrete.

Serving

Serving belongs to hud.environment.server — the same entry point a container CMD runs (python -m hud.environment.server <source>):
FunctionDescription
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.
In practice you serve with hud serve and run through hud eval, task.run(), or Taskset.run() — placement (runtime=LocalRuntime(...)) brings substrates up for you.
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 Robotics.

The wire protocol

An environment answers a small JSON-RPC control channel over tcp:
MethodReturns
hellosession id, env identity, capability bindings
tasks.listtask id/description metadata
tasks.startthe task’s prompt (holds the session across disconnect)
tasks.gradethe evaluation (score + metadata)
tasks.cancelcancels the held task
byeends the session and tears the held task down
The held task survives a dropped connection, so a client can tasks.start, disconnect, then reconnect to tasks.grade — which is how hud task start / hud task grade work against a packaged image.

See also

Tasks & Tasksets

Capabilities