Managers¶
A manager owns one aspect of the step loop (actions, observations, rewards, ...) and holds the terms that implement it. Each section below documents one manager module: the manager class, its term base class, and, for the kinds that accept plain functions, the function wrapper. For how to write a term, read the terms guide; for the order managers run in, read the environments guide.
from eden.managers import REWARD_TERM_REGISTRY, RewardTerm
from eden.terms import DoneTerm, ObsTerm, RewardTerm # short aliases of the function wrappers
Some term kinds declare their shared fields (a reward's weight, an event's mode, ...) on a private mixin. Those
blocks list the fields with inherited_members so they render on the term classes.
Base classes¶
ManagerTermBase is the base of every term, ConfigurableTermBase of every class term, and
ConfigurableTermFuncWrapper of every function wrapper. ManagerBase builds terms from options through
build_term, which adds a note naming the config entry to any exception a term's build() raises.
Base classes and shared logic for managers and their terms.
A manager owns an ordered set of terms resolved from its options through a
:class:~eden.utils.registry.Registry. Terms come in two flavors:
- function-based (stateless): a plain function registered with
@REGISTRY.register()and wrapped in aFuncWrapper. - class-based (stateful): subclass the kind's term base (a :class:
ConfigurableTermBase) and implementcompute(), optionallybuild()andreset().
Concrete managers (action, observation, reward, termination, ...) live in sibling
modules and are imported lazily inside the env's _load_managers() to break the
manager <-> env circular import.
Attributes¶
Classes¶
ManagerTermBase
¶
ManagerTermBase(env: EnvBase)
Bases: RBC
Base class for all manager terms.
Source code in eden/managers/base.py
Attributes¶
Methods:¶
resolve_entity
¶
Resolve a named entity from the env — the declared way for a term to reach an entity.
Resolve through this in compute() rather than caching the handle in build():
the deployment hot-swaps env.entities[name] (sim entity → RobotStateEntity) so
observations read real robot state, and a cached handle would keep pointing at the stale
sim entity. Cache only derived data (resolved link/DOF indices stay valid across the
swap), not the entity itself. (DeploymentBase repoints any straggler cached handles
by object identity as a safety net; resolving lazily means a term never needs it.)
An unknown name raises here, listing the names the scene does have.
Source code in eden/managers/base.py
post_reset
¶
post_reset(envs_idx: slice | Tensor) -> None
Hook invoked after ALL managers have reset envs_idx.
Runs after every manager's reset(), the reset-mode events, and the zeroing of
episode_length_buf — the guaranteed point to rebuild per-step caches from the
final post-reset scene state, without depending on the order managers reset in.
(A term's own reset() runs mid-sequence: state it derives from other entities
or terms may still change before observations are computed.) No-op by default.
Managers dispatch source caches before their consumers (for example, command
terms before metric terms). Runs under torch.inference_mode: mutate
build()-allocated buffers in place; never rebind attributes to tensors
created inside this hook.
| PARAMETER | DESCRIPTION |
|---|---|
envs_idx
|
The envs that were reset — never
TYPE:
|
Source code in eden/managers/base.py
ManagerTermFuncWrapperBase
¶
ManagerTermFuncWrapperBase(func: FunctionType, env: EnvBase)
Bases: ManagerTermBase
Runtime contract for manager terms defined as a function: hold func and call it.
Like :class:ManagerTermBase, this carries no options machinery. Concrete wrappers pair
it with ConfigurableFuncWrapperMixin[KindOptions], which contributes configure()
and the params attribute that :meth:compute forwards to the wrapped function.
Source code in eden/managers/base.py
ConfigurableTermBase
¶
ConfigurableTermBase(env: EnvBase, options: _TermOptionsT)
Bases: ManagerTermBase, ConfigurableMixin[_TermOptionsT]
A class term bound to its options: the base every kind's class-term base derives from.
Parameterize it with the kind's options class (ConfigurableTermBase[RewardTermOptions]);
ConfigurableMixin reads the type argument through this intermediate generic base. The
configurable fields every term of a kind shares go on a ConfigurableMixin subclass the
kind's class base and its :class:ConfigurableTermFuncWrapper both inherit, so they are
declared once; such a mixin must not define __init__.
Source code in eden/managers/base.py
Methods:¶
ConfigurableTermFuncWrapper
¶
ConfigurableTermFuncWrapper(func: FunctionType, env: EnvBase, options: _TermOptionsT)
Bases: ManagerTermFuncWrapperBase, ConfigurableFuncWrapperMixin[_TermOptionsT]
The function-term counterpart of :class:ConfigurableTermBase: each kind's *FuncWrapper base.
_func is set before ConfigurableFuncWrapperMixin.__init__ runs, because that is where
the params keys are checked against the wrapped function's signature.
Source code in eden/managers/base.py
Methods:¶
ManagerBase
¶
Base class for all managers.
Source code in eden/managers/base.py
Attributes¶
Methods:¶
compute_once
¶
:meth:EnvBase.compute_once <eden.envs.base.EnvBase.compute_once> of this manager's env.
The memo belongs to the env's pass, not to a manager: a key a term of one manager produced is served to a term of another within one pass.
Source code in eden/managers/base.py
compute_pass
¶
compute_pass() -> AbstractContextManager[None]
:meth:EnvBase.compute_pass <eden.envs.base.EnvBase.compute_pass> of this manager's env.
A manager wraps its term loop in this; inside a pass the env already opened, it joins that one.
Source code in eden/managers/base.py
sync_device_fields
¶
reset
¶
iter_post_reset_terms
abstractmethod
¶
iter_post_reset_terms() -> Iterable[ManagerTermBase]
Yield every term this manager owns, for the :meth:post_reset fan-out.
Abstract rather than defaulted so a new manager cannot silently drop the hook:
:meth:post_reset is the only generic thing the env can call on an arbitrary
:class:ManagerBase, and a no-op default would make a missed term look like a
term that simply had nothing to rebuild. May yield the same term more than once
(a term shared across observation groups or event modes); the fan-out dedupes.
Source code in eden/managers/base.py
post_reset
¶
post_reset(envs_idx: slice | Tensor | None = None) -> None
Fan :meth:ManagerTermBase.post_reset out once per unique term.
The env calls this from its public entry points (:meth:~eden.envs.base.EnvBase.reset,
:meth:~eden.envs.base.RLEnvBase.step) right after _reset_idx returns — not from
inside it, so a subclass's extra reset work finishes first (the mimic env refills the
motion buffer after super()._reset_idx). By then every manager's reset(), the
reset-mode events and the episode_length_buf zeroing have all run, so terms can
rebuild per-step caches from the final post-reset state. The env owns that call-time
contract, like CommandManager.post_physics_refresh.
Decorated @torch.inference_mode() so terms whose buffers were created under an
inference-moded manager path (metric and reward terms) can mutate them here; the flip
side is the in-place-only contract in :meth:ManagerTermBase.post_reset.
| PARAMETER | DESCRIPTION |
|---|---|
envs_idx
|
The envs that were reset;
TYPE:
|
Source code in eden/managers/base.py
FlatManagerBase
¶
Bases: ManagerBase[T]
Base for managers whose terms form a single flat, ordered set.
Owns the flat-term API (terms, active_terms, get_term,
get_term_options) backed by _terms / _term_names, which
:meth:_prepare_terms populates. The hierarchical managers (observations by
group, events by mode) inherit :class:ManagerBase directly and expose their
own named views instead.
Source code in eden/managers/base.py
Attributes¶
terms
property
¶
terms: dict[str, ManagerTermBase]
Mapping from term name to the built :class:ManagerTermBase instance.
active_terms
property
¶
The names of the active terms, in registration order (the live internal list; do not mutate).
Methods:¶
get_term
¶
get_term(name: str) -> ManagerTermBase
Return the active term registered under name.
| RAISES | DESCRIPTION |
|---|---|
KeyError
|
If |
Source code in eden/managers/base.py
get_term_options
¶
get_term_options(term_name: str) -> Options
Return the options object for the active term term_name.
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If |
Source code in eden/managers/base.py
iter_post_reset_terms
¶
iter_post_reset_terms() -> Iterable[ManagerTermBase]
Functions:¶
build_term
¶
build_term(registry: Registry, env: EnvBase, options: Options, *, label: str) -> ManagerTermBase
Construct the term options names from registry and build() it.
The one place a term is built from its options — by every manager (:meth:ManagerBase._build_term,
the observation manager's groups) and by the composite terms that build children of their own.
| PARAMETER | DESCRIPTION |
|---|---|
registry
|
The term registry
TYPE:
|
env
|
The env the term is built for.
TYPE:
|
options
|
The term's options;
TYPE:
|
label
|
Names the config entry being built, starting with the class that holds it
(
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If |
Source code in eden/managers/base.py
ActionManager¶
Action manager and ActionTerm base: map policy actions to actuator commands.
The manager slices the policy action vector across its active :class:ActionTerm
instances and applies them every control step (re-applied across decimation
physics sub-steps). ActionTerm.dofs_order maps a DOF name to its position in the term's
dofs_name; mind the DOF-ordering caveat in :mod:eden.entities.rigid when wiring actions to joints.
Attributes¶
ACTION_TERM_REGISTRY
module-attribute
¶
ACTION_TERM_REGISTRY = TermRegistry('ACTION_TERM', ActionTerm, populated_by=('eden.managers.terms.actions',))
Classes¶
ActionTerm
¶
ActionTerm(env: EnvBase, options: ActionTermOptions)
Bases: ConfigurableTermBase[ActionTermOptions]
Base class for action terms.
Source code in eden/managers/action_manager.py
Attributes¶
genesis_dofs_idx
property
¶
genesis_dofs_idx: range | Tensor
dofs_idx_local in the form to hand the entity's DOF accessors: a range when contiguous.
Selects exactly the DOFs dofs_idx_local does, in the same order, but a range takes
Genesis' slice path — sync-free and ~4x cheaper on the host than an index tensor, which
matters in apply_actions(), called once per physics substep (see
:func:eden.utils.misc.contiguous_range). Derived from whichever tensor dofs_idx_local
currently holds, so a subclass that rebinds it in its own build() never reads a stale one.
action
property
¶
The raw action this term received from the policy this step, shape (num_envs, action_dim).
prev_action
property
¶
The raw action this term received on the previous step, shape (num_envs, action_dim).
processed_action
property
¶
The command this term issued this step — what a deployment sends to the robot.
For the joint PD controllers this is the position target the controller
tracks — raw_action * scale + offset + reference_offset, after any
configured :class:~eden.managers.modifiers.ActionModifier's control-step
modify_processed_action. Other terms command other quantities (a velocity,
an effort, a binary weld flag), so both the units and the trailing width are
term-specific; only the leading num_envs dimension is guaranteed.
A physics-step modifier (modify_applied_target — e.g.
:class:~eden.managers.modifiers.actions.actuators.ActionDelay) is not folded in:
under a delay the simulated actuator tracks an earlier processed_action,
exactly as the real robot tracks the command it received a latency ago.
Read this rather than recomputing the target at the call site — a recomputation silently drops whatever control-step modifiers are configured, so the logged target diverges from the one the controller issued.
sim_surrogate
property
¶
sim_surrogate: bool
Whether this term only stands in, in simulation, for something the real robot does itself.
True means the physical effect follows from another term's command (or from physics)
on hardware, so a deployment has nothing to send for this term:
:class:~eden.managers.terms.actions.welding_actions.ParallelJawWelding welds the object
to the gripper because sim contact friction is unreliable, while on the robot the gripper
joints closing — commanded by a separate joint term — hold it.
False (the default) means the term commands something in its own right, and a
deployment that cannot carry that command must say so rather than drop it:
:class:~eden.managers.terms.actions.welding_actions.SuctionCupWelding is the vacuum
on/off bit, which no other term supplies. See
:func:eden.extensions.deployment.base.build_term_action_maps.
Methods:¶
build
¶
Source code in eden/managers/action_manager.py
verify_command_shape
¶
Fail loud — naming this term — the first time a control command's width != the DOF count.
A wrong-width joint command otherwise reaches the raw Genesis call and surfaces as an opaque
GenesisException: Invalid input shape: (N, W)... required size D naming neither the term,
the entity, nor the DOF set. Controllers call this from apply_actions
with the exact tensor they hand to entity.control_dofs_* — the only layer that knows both
the term identity and the true (post-slice) command width.
Checked once per term, not every step: the command width is structural (fixed by the term's
dofs_name and controller), so a first-step check catches a misconfiguration without any
steady-state cost. No-op for a shape-less command (a Python scalar Genesis handles itself).
Source code in eden/managers/action_manager.py
ActionManager
¶
ActionManager(env: EnvBase, options: ActionManagerOptions)
Bases: FlatManagerBase[ActionManagerOptions]
Action manager for processing actions sent to the environment.
Source code in eden/managers/action_manager.py
Attributes¶
action_term_dim
cached
property
¶
The order corresponds to the .active_terms property.
dofs_term_dim
cached
property
¶
The order corresponds to the .active_terms property.
action
property
¶
The raw action vector received this step, shape (num_envs, total_action_dim).
prev_action
property
¶
The raw action vector received on the previous step, shape (num_envs, total_action_dim).
processed_actions
property
¶
Each term's :attr:ActionTerm.processed_action, keyed by term name.
A mapping rather than one concatenated tensor (unlike :attr:action): terms
command different quantities with different widths and dtypes, so there is no
meaningful flat layout to concatenate them into. Hence the plural name — it
keeps the type visible at the call site, so indexing it like the flat
:attr:action fails with a clear AttributeError on the singular spelling
rather than a puzzling KeyError from a tensor-style slice on a dict.
Methods:¶
summary
¶
summary() -> str
Source code in eden/managers/action_manager.py
clear_action_history
¶
clear_action_history(envs_idx: slice | Tensor | None = None) -> None
Zero the raw actions :meth:reset zeroes, the manager's and each term's, current and previous.
For a caller that restarts what a policy observes without resetting the env: a deployment's
START or policy switch, where the robot keeps running. A last_action observation then reads
zeros, as it does after an env reset. :meth:reset itself does more, which such a caller must
not: it resets every term's modifiers and an ACCUMULATE target, state a reset-mode DR event
rewrites right after it in an env reset and that nothing rewrites on a robot.
Source code in eden/managers/action_manager.py
reset
¶
Source code in eden/managers/action_manager.py
compute
¶
Source code in eden/managers/action_manager.py
Functions:¶
ObservationManager¶
Observation manager for computing observations.
Attributes¶
ObservationTermLike
module-attribute
¶
ObservationTermLike: TypeAlias = ObservationTerm | ObservationTermFuncWrapper
Union of a class-based observation term and its function-wrapper form.
OBSERVATION_TERM_REGISTRY
module-attribute
¶
OBSERVATION_TERM_REGISTRY = TermRegistry('OBSERVATION_TERM', ObservationTerm, ObservationTermFuncWrapper, populated_by=('eden.managers.terms.observations',))
Classes¶
ObservationTerm
¶
ObservationTerm(env: EnvBase, options: ObservationTermOptions)
Bases: ConfigurableTermBase[ObservationTermOptions], _ObservationTermFields
Base class for observation terms.
compute() is a read, not a tick. The manager calls it more often than once per control
step and never promises which call is "the" step: once per term at build to learn the output shape
(before any reset()), from EnvBase.reset(), at phase 19 of RLEnvBase.step, again at
phase 12 for the final-observation snapshot when record_final_observations is on, from
RslRlVecEnvWrapper.get_observations() outside the step, and from the deploy pre-flight probe.
The cross-group raw cache goes the other way and may call compute() on only one of several
identically configured instances. A term that advances state inside compute() — an integral,
a counter, a latch — therefore double-counts under any of those, silently: the failure is a wrong
number, not an exception.
Per-step state lives on the term and moves in the lifecycle hooks instead: build() allocates
the buffer, :meth:post_physics_refresh advances it exactly once per control step on the
post-physics state (phase 7 — the same hook, epoch and reasoning as a command term's measurement
state at 5, and after the metrics at 6 so a term may integrate one), reset(envs_idx) zeroes the
rows of the envs that restart, post_reset(envs_idx) rebuilds what the post-reset scene
determines, and compute() returns the buffer.
:class:~eden.managers.terms.observations.locomotion.VelocityTrackingErrorIntegral is the shipped
worked example. Function terms have none of these hooks — see :class:ObservationTermFuncWrapper.
The post-compute fields (modifiers/noise/clip/scale/history) are options-only (see
ObservationTermOptions.POST_COMPUTE_FIELDS): configure() accepts them and stores them on the
options, but they are NOT mirrored as term attributes — the manager applies them from outside so a
compute() can't accidentally read (and be wrongly deduped on) a modifier value.
When each modifier is read. clip and history_length are read off self._options on every
step, so a post-build write to them takes effect immediately. noise and scale are resolved once
at build — into self._noise and self._resolved_scale — because both need setup the hot path must
not repeat (a noise model instance; a length check against the term's feature dim plus a device tensor).
Writing term._options.scale after env.build() therefore has no effect: it is not the value the
manager multiplies by, and nothing detects the divergence for you. Read what is actually applied off
:attr:effective_scale; to retune it at runtime, call
:meth:~eden.managers.observation_manager.ObservationManager.set_term_scale, which re-runs the feature-dim
check. Modifiers stay the manager's business in both directions — that is what keeps them off this class.
Source code in eden/managers/observation_manager.py
Attributes¶
effective_scale
property
¶
effective_scale: float | Tensor | None
The scale actually applied to this term's output, as resolved at build.
Not the configured scale option: a vector is resolved to a device tensor, and anything that
multiplies out to identity — the scalar 1.0 or an all-ones vector — resolves to None,
meaning the manager skips the multiply entirely. None before the term is built into a group.
Mirrors :attr:~eden.managers.terms.actions.joint_actions.JointAction.effective_scale.
Methods:¶
post_physics_refresh
¶
Advance per-step state on the post-physics scene (phase 7 — the grading epoch).
No-op by default. The one call per control step that a stateful term may count on: it runs
after the physics sub-steps, after command_manager.post_physics_refresh (5) and
metric_manager.compute (6) — so a term may integrate a refreshed command cache or a shared
metric and see this step's value — and before termination/reward; and, unlike compute(),
exactly once, whatever else reads the observation this step. Accumulate here (add_ into a
build()-time buffer, in place), and return the buffer from compute().
Running above the auto-reset (phase 13) is what gives a stateful observation the semantics
one expects of it: the final-observation snapshot at phase 12 carries this step's increment,
reset() then zeroes the envs that restart, and the fresh episode's first observation at
phase 19 reads zero rather than one post-reset increment. EnvBase.reset() and the build-time
shape probe run no phase 7 at all, so a term reads as zero there too.
Invoked by :meth:ObservationManager.post_physics_refresh from RLEnvBase.step and
EnvBase.step, and, on hardware, once per control tick from
DeploymentBase.state_to_observation after the robot state is synced (a command term's
post_physics_refresh does not run there; this one does, because the policy's
observations are the hardware path). Never from compute(), and never from a reset.
Source code in eden/managers/observation_manager.py
ObservationTermFuncWrapper
¶
ObservationTermFuncWrapper(func: FunctionType, env: EnvBase, options: ObservationTermOptions)
Bases: ConfigurableTermFuncWrapper[ObservationTermOptions], _ObservationTermFields
Base class for observation terms defined as function. See :class:ObservationTerm for the modifier
fields (options-only, not mirrored as attributes), for when each one is read, and for
:attr:effective_scale.
Stateless by construction. A function term is a pure forward of env state: the manager never
dispatches reset(), post_reset() or post_physics_refresh() to it (the inherited reset
and post_reset are no-ops, and there is no refresh hook to call), so state kept in a closure or a
module global is never zeroed at episode reset and advances on every one of the several compute()
calls a step may make. A term that needs per-episode or per-step state is a class term.
Source code in eden/managers/observation_manager.py
Attributes¶
effective_scale
property
¶
effective_scale: float | Tensor | None
The scale actually applied to this term's output, as resolved at build.
Not the configured scale option: a vector is resolved to a device tensor, and anything that
multiplies out to identity — the scalar 1.0 or an all-ones vector — resolves to None,
meaning the manager skips the multiply entirely. None before the term is built into a group.
Mirrors :attr:~eden.managers.terms.actions.joint_actions.JointAction.effective_scale.
Methods:¶
ObservationManager
¶
ObservationManager(env: EnvBase, options: ObservationManagerOptions)
Bases: ManagerBase[ObservationManagerOptions]
Source code in eden/managers/observation_manager.py
Attributes¶
group_term_names
property
¶
The active observation term names keyed by group, in registration order (a copy, safe to mutate).
active_terms
property
¶
The flat, de-duplicated union of all active term names across groups.
Methods:¶
summary
¶
summary() -> str
Source code in eden/managers/observation_manager.py
iter_terms
¶
iter_terms() -> Iterator[tuple[str, str, ObservationTermLike]]
Yield (group, name, term) for every active observation term, in group order.
Public accessor so callers (e.g. the per-term profiler) can scan term objects
grouped, without reaching into the private per-group storage; mirrors
:meth:~eden.managers.reward_manager.RewardManager.iter_terms.
Source code in eden/managers/observation_manager.py
get_term
¶
get_term(name: str) -> ObservationTermLike
Source code in eden/managers/observation_manager.py
set_term_scale
¶
Retune one term's post-compute scale after the env is built.
scale is resolved once during env construction (see :func:_resolve_term_scale), so assigning
term._options.scale afterwards changes what the options report but not what the manager
multiplies by. This is the supported way to change it: the value goes through exactly the check the
build-time resolution ran — feature dim, length, identity short-circuit — and both halves are written
together, so a term can never report a scale it is not applying. A rejected value leaves both
untouched, because the resolution runs first.
Addressed by (group_name, term_name) rather than a term handle because the same term name in two
groups is two independent instances with their own scale, which :meth:get_term cannot distinguish.
Call this once, at setup. Scale is applied per frame before the frame enters a history buffer,
so retuning a term whose window already holds frames would leave the older ones at the previous scale
and the newer ones at the new one — one feature carrying two scales for history_length steps,
which nothing in the config can show. That is refused. The window counts as empty before the first
compute() and again after a full reset(), so the usual setup orders — retune before the first
step, or after env.reset(), which is where the training wrappers leave you — are all allowed.
A term with no history holds no cross-step state in the observation pipeline, so it stays retunable at any point as far as this manager is concerned. That is not a licence to retune mid-training: the RL side holds state derived from the old scale (an observation normalizer's running statistics, most obviously) that nothing here can see or invalidate.
| PARAMETER | DESCRIPTION |
|---|---|
group_name
|
The observation group holding the term.
TYPE:
|
term_name
|
The term to retune, as named in the group.
TYPE:
|
scale
|
The new scale. A list is matched against the term's raw feature dim, as at build. |
Source code in eden/managers/observation_manager.py
reset
¶
Source code in eden/managers/observation_manager.py
iter_post_reset_terms
¶
iter_post_reset_terms() -> Iterator[ObservationTermLike]
Every observation term, in group order (a term shared across groups repeats).
iter_post_physics_terms
¶
iter_post_physics_terms() -> Iterator[tuple[str, str, ObservationTerm]]
Yield (group, name, term) for every term :meth:post_physics_refresh ticks, in tick order.
The class terms (a term shared across groups repeats, as it is ticked once per group): what a CUDA graph
of the graded phase (EnvOptions.cuda_graph_options) watches beside the graded managers' terms.
Source code in eden/managers/observation_manager.py
post_physics_refresh
¶
Advance every class term's per-step state on the post-physics scene — phase 7, once per control step.
The once-per-step tick that compute() is not (see :class:ObservationTerm). Called by the
env's step after command_manager.post_physics_refresh() (5) and metric_manager.compute()
(6), before the graded layer and above the auto-reset; and once per control tick from the deploy
loop. This method owns that call-time contract; terms override the no-op
:meth:ObservationTerm.post_physics_refresh.
Reaches class terms only: a function term is stateless by construction
(:class:ObservationTermFuncWrapper). Every instance is ticked, including the duplicates the
cross-group raw cache computes only once — that is what keeps their state identical and the
cache sound.
Source code in eden/managers/observation_manager.py
compute
¶
Source code in eden/managers/observation_manager.py
compute_group
¶
Source code in eden/managers/observation_manager.py
579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 | |
Functions:¶
RewardManager¶
Reward manager for computing reward signals.
Attributes¶
RewardTermLike
module-attribute
¶
RewardTermLike: TypeAlias = RewardTerm | RewardTermFuncWrapper
Union of a class-based reward term and its function-wrapper form.
REWARD_TERM_REGISTRY
module-attribute
¶
REWARD_TERM_REGISTRY = TermRegistry('REWARD_TERM', RewardTerm, RewardTermFuncWrapper, populated_by=('eden.managers.terms.rewards',))
Classes¶
RewardTerm
¶
RewardTerm(env: EnvBase, options: _TermOptionsT)
Bases: ConfigurableTermBase[RewardTermOptions], _RewardTermFields
Base class for reward terms.
Source code in eden/managers/base.py
Attributes¶
RewardTermFuncWrapper
¶
RewardTermFuncWrapper(func: FunctionType, env: EnvBase, options: _TermOptionsT)
Bases: ConfigurableTermFuncWrapper[RewardTermOptions], _RewardTermFields
Base class for reward terms defined as a function.
Source code in eden/managers/base.py
Attributes¶
RewardManager
¶
RewardManager(env: RLEnvBase, options: RewardManagerOptions)
Bases: FlatManagerBase[RewardManagerOptions]
Computes the scalar step reward as the weighted sum of all active terms.
Terms are declared flat (:class:RewardTermOptions) or inside named groups
(:class:RewardGroupOptions); flat terms implicitly belong to
:data:DEFAULT_REWARD_GROUP. Grouping never changes the scalar reward the
environment returns — :meth:compute always sums over all groups — it adds a
per-group readout (:meth:get_group_reward, :meth:get_group_episode_sum)
for multi-channel consumers (CMDP-style task/style/penalty critics,
per-group logging). Groups partition the active terms, so summing
:meth:get_group_reward over :attr:group_term_names reconstructs the
value :meth:compute returned for the same step (up to floating-point
summation order).
Unlike observation groups (which may share a term across groups), reward
groups are a strict partition of one flat term set — duplicate term names
across groups raise at construction. That is why this stays a
:class:FlatManagerBase: the terms, buffers, and the flat API
(terms, active_terms, get_term, iter_terms,
get_episode_sum) are unchanged; groups are a labeling on top.
Source code in eden/managers/reward_manager.py
Attributes¶
group_term_names
property
¶
The active term names keyed by group, in declaration order (a copy, safe to mutate).
Methods:¶
summary
¶
summary() -> str
Source code in eden/managers/reward_manager.py
iter_terms
¶
Yield (name, term) pairs for every active reward term, flat across all groups.
Public accessor for sibling managers (e.g. curricula) that need to scan terms
without reaching into the private _terms dict.
Source code in eden/managers/reward_manager.py
get_episode_sum
¶
Return the per-env running reward sum for term name as a (num_envs,) view.
raw=True (default) returns the unweighted, mask-applied accumulation;
raw=False returns the weighted post-curriculum sum. Both buffers are
reset to zero per env in :meth:reset. Raises KeyError (with the
active term list) if name is not registered. See :meth:iter_terms
for full-buffer scanning.
Source code in eden/managers/reward_manager.py
get_term_value
¶
get_term_value(name: str) -> Tensor
Return term name's output at the last computed step as a (num_envs,) view.
The value is the term's compute() output, temporally masked and zero for an env Genesis flagged as
corrupt, with no weight and no dt — what an off-policy learner that weights its own reward channels
stores per transition (e.g. FB-CPR's auxiliary rewards). The weighted, dt-scaled share of the scalar
reward is :meth:get_group_reward's. Like MetricManager.get_value, the view is the manager's
buffer: :meth:compute overwrites it in place, so treat it as read-only and clone it to keep a step. It
reads zeros before the first :meth:compute and still reads the pre-reset step after an auto-reset.
Raises KeyError (with the active term list) if name is not registered.
Source code in eden/managers/reward_manager.py
get_group_reward
¶
get_group_reward(group: str) -> Tensor
Return the reward of group group for the last computed step as a (num_envs,) tensor.
The value is weighted, temporally masked, and dt-scaled — the group's
contribution to the scalar reward :meth:compute returned, so summing
this over all groups reconstructs that scalar (up to floating-point
summation order). Valid until the next :meth:compute call (it reads
the per-term step buffer that call refills); before the first compute
it returns zeros, and after :meth:reset it still reads the pre-reset
step — consistent with the scalar :meth:compute returned for that
step (auto-reset runs after reward). Raises KeyError (with the
active group list) for an unknown group.
Source code in eden/managers/reward_manager.py
get_group_episode_sum
¶
Return the per-env running reward sum of group group as a (num_envs,) tensor.
raw=False sums the weighted post-curriculum accumulation — the
quantity whose per-group parts add up to the episode's scalar reward
(e.g. the CMDP task-return signal); raw=True sums the unweighted,
mask-applied accumulation for diagnostics. raw is deliberately
keyword-only with no default: the per-term :meth:get_episode_sum
defaults to the raw view, so a silent (opposite) default here would
flip semantics on consumers porting per-term code. Raises KeyError
(with the active group list) for an unknown group.
Source code in eden/managers/reward_manager.py
is_timestep_in_range
staticmethod
¶
Check whether a batch of timesteps falls within any of the given intervals.
| PARAMETER | DESCRIPTION |
|---|---|
timestep
|
TYPE:
|
ranges
|
List of
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
|
Source code in eden/managers/reward_manager.py
reset
¶
Source code in eden/managers/reward_manager.py
compute
¶
compute(dt: float) -> Tensor
Source code in eden/managers/reward_manager.py
sync_device_fields
¶
Copy the terms' current weight onto the device, when one changed since the last call.
A curriculum may move a term's weight between steps. compute() calls this; the CUDA graph of the
graded phase (EnvOptions.cuda_graph_options) calls it before each replay instead, since a host-side
comparison is exactly what a replay skips.
Source code in eden/managers/reward_manager.py
Functions:¶
TerminationManager¶
Termination manager for computing done signals.
Attributes¶
TerminationTermLike
module-attribute
¶
TerminationTermLike: TypeAlias = TerminationTerm | TerminationTermFuncWrapper
Union of a class-based termination term and its function-wrapper form.
TERMINATION_TERM_REGISTRY
module-attribute
¶
TERMINATION_TERM_REGISTRY = TermRegistry('TERMINATION_TERM', TerminationTerm, TerminationTermFuncWrapper, populated_by=('eden.managers.terms.terminations', 'eden.managers.terms.metrics'))
Classes¶
TerminationTerm
¶
TerminationTerm(env: EnvBase, options: _TermOptionsT)
Bases: ConfigurableTermBase[TerminationTermOptions], _TerminationTermFields
Base class for termination terms.
Source code in eden/managers/base.py
TerminationTermFuncWrapper
¶
TerminationTermFuncWrapper(func: FunctionType, env: EnvBase, options: _TermOptionsT)
Bases: ConfigurableTermFuncWrapper[TerminationTermOptions], _TerminationTermFields
Base class for termination terms defined as a function.
Source code in eden/managers/base.py
TerminationManager
¶
TerminationManager(env: EnvBase, options: TerminationManagerOptions)
Bases: FlatManagerBase[TerminationManagerOptions]
Source code in eden/managers/termination_manager.py
Attributes¶
solver_errors
property
¶
Per-env mask of the envs Genesis flagged on the last :meth:compute (see
EnvBase.get_solver_error_envs_mask, which also says how a misconfiguration of the scene still raises).
They are reset as truncated (:attr:timeouts), never terminated, and logged only as
Episode_Termination/solver_error: the simulator failed, not the task, so the learner bootstraps
the step instead of reading it as a terminal outcome, and no term's verdict on the corrupt state
counts.
Methods:¶
reset
¶
Source code in eden/managers/termination_manager.py
compute
¶
Source code in eden/managers/termination_manager.py
Functions:¶
CommandManager¶
Command manager for generating and updating commands.
Attributes¶
COMMAND_TERM_REGISTRY
module-attribute
¶
COMMAND_TERM_REGISTRY = TermRegistry('COMMAND_TERM', CommandTerm, populated_by=('eden.managers.terms.commands',))
Classes¶
CommandTerm
¶
CommandTerm(env: EnvBase, options: CommandTermOptions)
Bases: ConfigurableTermBase[CommandTermOptions]
Base class for command terms.
| PARAMETER | DESCRIPTION |
|---|---|
resampling_time_range
|
The range of time before commands are changed [s].
|
debug_vis
|
Whether to visualize debug information. Defaults to False.
|
Source code in eden/managers/command_manager.py
Attributes¶
resampling_time_range
class-attribute
instance-attribute
¶
command_counter
instance-attribute
¶
Methods:¶
reset
¶
Source code in eden/managers/command_manager.py
compute
¶
compute(dt: float) -> None
Tick the resampling clock, then resample and publish (phase 17 — the preparation epoch).
In :meth:RLEnvBase.step this runs after the auto-reset and below the reward, so what it
produces is the command the next action is chosen from and phase 19's observations carry —
not the command this step was graded against. :meth:_update_command says what belongs
here and what does not.
On the deployment path it is one of two command hooks that run: DeploymentBase.step
drives :meth:advance_reference first — so a time-advancing reference keeps moving on
hardware — then this method and :meth:draw_vis. :meth:post_physics_refresh does not run
there at all. A deployed term must produce whatever the policy's observations read from here
or from the advance — see docs/guides/terms.md.
Source code in eden/managers/command_manager.py
draw_vis
¶
post_physics_refresh
¶
Refresh state derived from the post-physics robot pose (phase 5 — the grading epoch).
No-op by default; override to refresh caches. This runs after the physics sub-steps and
before metric/termination/reward, which makes it the hook for everything that grades
this step: pose and contact caches, progress counters, hold latches, tracking errors.
A value written here is read by phase 9's reward in the same step — no lag, and nothing
to snapshot across the auto-reset. Writing it in :meth:_update_command instead is the
bug class docs/guides/terms.md documents under "Which hook: grade the step, or
prepare the next one".
Invoked by :meth:CommandManager.post_physics_refresh, which owns the call-time contract —
which means it is not invoked on the deployment path: DeploymentBase.step drives
:meth:advance_reference and :meth:compute, and nothing between them, because on
hardware there are no physics sub-steps to bracket and no reward to grade. State a deployed
term's observations read has to be produced from :meth:compute too; an idempotent cache
refresh can simply live in both, the way
:class:~eden.managers.terms.commands.mimic.MotionCommand refreshes its robot-state cache.
Source code in eden/managers/command_manager.py
pre_physics_advance
¶
Advance the command BEFORE the physics sub-steps.
No-op by default. Overridden by terms that track a time-advancing reference
and must advance it before physics so the post-physics robot is graded
against the temporally-aligned frame ("advance-then-grade"; e.g.
:class:~eden.managers.terms.commands.mimic.MotionCommand). Invoked once
per control step by :meth:CommandManager.pre_physics_advance in
:meth:RLEnvBase.step, before the decimation loop.
The first of the term's three per-step hooks; the other two are
:meth:post_physics_refresh (grade the step that just happened) and
:meth:_update_command (publish the command the next step is chosen against). Unlike
:meth:post_physics_refresh, this one also runs on deployment through
:meth:advance_reference by default. A term whose simulation advance resets the robot
must override that deployment hook with a clock-only advance.
Source code in eden/managers/command_manager.py
advance_reference
¶
Advance a deployed reference without resetting the robot.
Preserve the existing advance hook by default. Terms that resample or teleport at
clip boundaries in :meth:pre_physics_advance must provide a clock-only version here.
Source code in eden/managers/command_manager.py
rewind_reference
¶
Restart a time-advancing reference at its first frame, for every env (no-op by default).
The reset-side twin of :meth:advance_reference, and it exists for the deployment
path alone: DeploymentBase.reset_motion_reference drives it at deploy reset, at the
operator's START and on every policy switch, so a clip hands over from its beginning the
way the manager path already does through MotionManager.rewind_to_start. Without it
a term that advances a clock resumes wherever the clock happened to stop — mid-clip, at a
pose the robot standing in its INIT stance is nowhere near.
Rewind the clock only. This runs on hardware, where there is no robot to place and no
episode to randomize, so it must not reach for the reset-state initialization a wrap in
:meth:pre_physics_advance performs in sim (MotionCommand._resample_command teleports
the robot and perturbs its pose). Clip selection is not resampled either: a deploy
replays the same clip deterministically.
Source code in eden/managers/command_manager.py
CommandManager
¶
CommandManager(env: EnvBase, options: CommandManagerOptions)
Bases: FlatManagerBase[CommandManagerOptions]
Source code in eden/managers/command_manager.py
Methods:¶
draw_vis
¶
post_physics_refresh
¶
Refresh every term's robot-state cache on the just-computed post-physics state.
Called once per control step in :meth:RLEnvBase.step after the physics sub-steps
but BEFORE metric/termination/reward, so terms that cache robot state (e.g. the
motion-imitation command) grade the CURRENT step's state (S_N) rather than the
previous step's (S_{N-1}). This method owns that ordering contract; individual terms
override the no-op :meth:CommandTerm.post_physics_refresh.
Being above the graded layer is also what makes this the right hook for a term's own
per-step grading state, rather than :meth:CommandTerm._update_command below the
reward — see that method, and docs/guides/terms.md.
Source code in eden/managers/command_manager.py
pre_physics_advance
¶
Advance every term's command BEFORE the physics sub-steps (no-op by default).
Called once per control step in :meth:RLEnvBase.step, before the decimation
loop, so terms tracking a time-advancing reference (e.g. the motion-imitation
command) advance it ahead of physics — see :meth:CommandTerm.pre_physics_advance.
Source code in eden/managers/command_manager.py
rewind_reference
¶
Restart every term's time-advancing reference at its first frame (no-op by default).
Driven only by DeploymentBase.reset_motion_reference — see
:meth:CommandTerm.rewind_reference for what a term may and may not do there.
Source code in eden/managers/command_manager.py
advance_reference
¶
Advance each deployed reference; simulation still uses pre_physics_advance.
reset
¶
Source code in eden/managers/command_manager.py
compute
¶
compute(dt: float) -> None
Resample and publish every term's command (phase 17 of :meth:RLEnvBase.step).
Called after the auto-reset and below the reward, so the command it leaves behind is the
one phase 19's observations carry and the next action is chosen against — deliberately not
the one this step was graded on, which is what keeps obs -> action -> reward aligned.
See :meth:CommandTerm._update_command.
Source code in eden/managers/command_manager.py
swap_term
¶
swap_term(name: str, options: CommandTermOptions) -> CommandTerm
Replace the built term name with one configured by options; return the old one.
The post-build counterpart of what the config does before build: a deployment
substitutes a task's trained command generator for one an operator drives
(:class:~eden.managers.terms.commands.velocity.OperatorVelocityCommand) or one that
simply holds a value, the way it substitutes the simulated entity for a
:class:~eden.extensions.deployment.utils.state.RobotStateEntity. The caller owns the
returned term and can pass its options back here to undo the swap.
The replacement is built and build()-ed exactly as _prepare_terms would, and its
command must have the same shape as the one it replaces: the observation buffers and
the trained policy's input width were fixed by the old term, so a different width would
reach the policy as a silently misaligned observation vector.
Nothing here repoints terms that cached the replaced term in their own build()
(the pattern self._command = env.command_manager.get_term(...)). A caller that needs
those to follow the swap must repoint them — see
:meth:~eden.extensions.deployment.base.DeploymentBase._repoint_command_term_refs.
| RAISES | DESCRIPTION |
|---|---|
KeyError
|
If |
ValueError
|
If the replacement's command shape differs from the replaced term's. |
Source code in eden/managers/command_manager.py
restore_term
¶
restore_term(name: str, term: CommandTerm) -> None
Put back a term :meth:swap_term returned, undoing that swap.
Deliberately narrow — it takes an already-built term rather than options, because the
only correct argument is the exact object swap_term displaced. Rebuilding the
original from its options instead would drop whatever state it had accumulated and
leave any reference the caller repointed pointing at a third object.
Source code in eden/managers/command_manager.py
Functions:¶
EventManager¶
Event manager for applying randomization and other event-driven changes.
Attributes¶
EVENT_TERM_REGISTRY
module-attribute
¶
EVENT_TERM_REGISTRY = TermRegistry('EVENT_TERM', EventTerm, EventTermFuncWrapper, populated_by=('eden.managers.terms.events',))
Classes¶
EventTerm
¶
EventTerm(env: EnvBase, options: _TermOptionsT)
Bases: ConfigurableTermBase[EventTermOptions], _EventTermFields
Base class for event terms.
Source code in eden/managers/base.py
Attributes¶
interval_range_s
class-attribute
instance-attribute
¶
min_step_count_between_reset
class-attribute
instance-attribute
¶
min_step_count_between_reset: int = 0
Methods:¶
compute_batch
classmethod
¶
Fire terms, RESET terms of this kind adjacent in priority order, on the same envs_idx.
The default fires them one by one. A kind whose terms each write simulator state overrides it
to write once for all of them (the reference-state-initialization terms: one Genesis write and
one forward-kinematics pass per reset, not one per entity). The event manager hands a run
of adjacent RESET terms to one call when each term's class takes both compute and
compute_batch from the same class (a subclass that overrides compute alone runs alone)
and none sets min_step_count_between_reset (so every term fires on exactly envs_idx). A
throttle set on a batched term after the build splits its batch back into single terms.
Source code in eden/managers/event_manager.py
EventTermFuncWrapper
¶
EventTermFuncWrapper(func: FunctionType, env: EnvBase, options: _TermOptionsT)
Bases: ConfigurableTermFuncWrapper[EventTermOptions], _EventTermFields
Base class for event terms defined as function.
Source code in eden/managers/base.py
ResetTermBatch
dataclass
¶
RESET terms adjacent in priority order that one :meth:EventTerm.compute_batch fires together.
Every batchable term belongs to one, alone or with its neighbours. The event manager fires it
through :meth:compute, never through the terms' own compute, so a per-term timer has to
wrap this instead (RLStepProfiler does; see :meth:EventManager.iter_reset_batches).
EventManager
¶
Bases: ManagerBase[EventManagerOptions]
Source code in eden/managers/base.py
Attributes¶
mode_term_names
property
¶
The active event term names keyed by mode, in priority order.
active_terms
property
¶
The flat, de-duplicated union of all active term names across modes.
Methods:¶
summary
¶
summary() -> str
Source code in eden/managers/event_manager.py
iter_terms
¶
iter_terms() -> Iterator[tuple[EventMode, str, ManagerTermBase]]
Yield (mode, name, term) for every active event term, in mode/priority order.
Public accessor mirroring :meth:~eden.managers.reward_manager.RewardManager.iter_terms
so callers can scan term objects without touching the private per-mode storage.
Source code in eden/managers/event_manager.py
get_term
¶
get_term(name: str) -> ManagerTermBase
Return the active event term registered under config key name, whatever its mode.
For a term another one reads from, e.g. ResetFromStateBuffer.current_column serving a goal stored
with the reset states.
| RAISES | DESCRIPTION |
|---|---|
KeyError
|
If |
Source code in eden/managers/event_manager.py
iter_reset_batches
¶
iter_reset_batches() -> Iterator[ResetTermBatch]
Yield every batch of RESET terms (see :class:ResetTermBatch), in priority order.
Their terms' own compute is never called on a reset, so time the batch instead.
Source code in eden/managers/event_manager.py
reset
¶
reset(envs_idx: slice | Tensor | None = None)
Source code in eden/managers/event_manager.py
iter_post_reset_terms
¶
iter_post_reset_terms() -> Iterator[ManagerTermBase]
Every event term, in mode/priority order (a term shared across modes repeats).
compute
¶
compute(mode: EventMode, envs_idx: slice | Tensor | None = None, dt: float | None = None, global_env_step_count: int | None = None, n_envs: int | None = None)
Source code in eden/managers/event_manager.py
Functions:¶
CurriculumManager¶
CurriculumManager.get_state(name) returns what another curriculum term published this step and raises, naming
the fix, whenever there is no such value. See the curricula guide.
Curriculum manager for updating environment quantities subject to a training curriculum.
Attributes¶
CURRICULUM_TERM_REGISTRY
module-attribute
¶
CURRICULUM_TERM_REGISTRY = TermRegistry('CURRICULUM_TERM', CurriculumTerm, CurriculumTermFuncWrapper, populated_by=('eden.managers.terms.curricula',))
Classes¶
CurriculumTerm
¶
CurriculumTerm(env: EnvBase, options: _TermOptionsT)
Bases: ConfigurableTermBase[CurriculumTermOptions]
Base class for curriculum terms.
compute() returns the term's published state: a dict of scalars, logged as
Curriculum/<term_name>/<key> and served to other terms by
:meth:CurriculumManager.get_state. Publish it on every step. A term that returns a dict
on some steps and None on others makes get_state raise on exactly the steps it skipped
— the contract is "never None", so there is no quiet no-value for a consumer to branch on.
A term with genuinely nothing to share simply returns None always, and its state
is then not readable at all, which get_state says in as many words.
Source code in eden/managers/base.py
CurriculumTermFuncWrapper
¶
CurriculumTermFuncWrapper(func: FunctionType, env: EnvBase, options: _TermOptionsT)
Bases: ConfigurableTermFuncWrapper[CurriculumTermOptions]
Base class for curriculum terms defined as a function.
Source code in eden/managers/base.py
CurriculumManager
¶
CurriculumManager(env: EnvBase, options: CurriculumManagerOptions)
Bases: FlatManagerBase[CurriculumManagerOptions]
Curriculum manager for managing the curriculum of the environment.
Source code in eden/managers/curriculum_manager.py
Methods:¶
reset
¶
Publish the cached per-term state, then feed the finished episodes to the terms.
The term half is frozen at eval, exactly like :meth:compute — a curriculum term's
reset() is how it accumulates the statistics compute() would adapt from (an episode-
length EMA, a success rate), so running one without the other would have the internal state
drift away from the published one for the whole rollout, on evidence nothing is allowed to
act on.
The publish half reports whatever the last :meth:compute cached — and an eval env has
no last compute(): env.training is fixed at construction, so an eval rollout logs no
Curriculum/* key at all, from its first reset to its last. That is deliberate: the
level an eval was graded at is the one its config pins, which the
saved config already records; publishing it from here would mean running compute() at
build, and the two terms whose compute() has side effects —
StageRewardWeightCurriculum and VelocityCommandCurriculum — would then overwrite
the configured reward weights and command ranges with their step-0 stage.
Source code in eden/managers/curriculum_manager.py
compute
¶
Adapt every term and cache what it published — a no-op at eval.
Curricula adapt task difficulty from training statistics, so an eval rollout freezes them:
otherwise its difficulty is non-stationary, e.g. an EMA-gated goal curriculum at
num_envs=1 collapses its window to a single episode, so one failed episode retreats the
level mid-rollout and every later episode is graded on an easier task than the one being
reported. Terms still apply their configured initial level at build, so eval entry points
pin difficulty through the term's own config.
The freeze is all-or-nothing and lives here rather than at the call site so it cannot go
asymmetric: :meth:reset is gated the same way. Nothing is cached either, so
:meth:get_state raises for every term for the whole rollout rather than handing a
consumer a None that reads like "not ready yet".
Source code in eden/managers/curriculum_manager.py
get_state
¶
Return the most recent compute() output cached for the named term.
Never returns None. Every way there can be no state is a raise, because the one
thing a consumer must not do is read the absence as "not ready yet" and fall back to a
default: that fallback then stands for the whole run, with nothing logged and nothing
raised. When you want a level that is correct in both modes, read it off the term
itself — curriculum_manager.get_term(name) — which is what ObjectVelocityDisturbance
does; get_state is only for consuming what another term published this step, and it
enforces that rather than only asking for it: a cached value from an earlier step raises
too. Without that the same read is treated two ways -- the run's first step raises, and
every step after silently serves the previous step's level, which is the intermittency this
contract exists to remove.
Freshness is dated against env.common_step_counter, which advances once per step with
the physics (:meth:RLEnvBase.step), so a read from phases 5-20 is placed exactly. Phases
1-3 run before that advance and so still date as the previous step; there is no earlier
per-step marker to key off, and what they would read is in any case the newest publish that
can exist, since this step's :meth:compute has not run.
| RAISES | DESCRIPTION |
|---|---|
KeyError
|
If no curriculum term is named |
RuntimeError
|
If the named term has no state this step. The five causes get five messages, each
naming the one edit that fixes it: the env is at eval, so nothing ever computes; the
term is declared after the consumer now reading it; the read happens before the run's
first |
Source code in eden/managers/curriculum_manager.py
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 | |
MetricManager¶
Metric manager and MetricTerm base for task success / evaluation signals.
Attributes¶
MetricTermLike
module-attribute
¶
MetricTermLike: TypeAlias = MetricTerm | MetricTermFuncWrapper
Union of a class-based metric term and its function-wrapper form.
METRIC_TERM_REGISTRY
module-attribute
¶
METRIC_TERM_REGISTRY = TermRegistry('METRIC_TERM', MetricTerm, MetricTermFuncWrapper, populated_by=('eden.managers.terms.metrics',))
Classes¶
SuccessCheckerMixin
¶
SuccessCheckerMixin(options: T)
Bases: ConfigurableMixin
The configurable fields every metric term has, class or function, and the success check they drive.
| PARAMETER | DESCRIPTION |
|---|---|
success_threshold
|
The threshold for success. The threshold is inclusive. Defaults to 1.0.
|
direction
|
The metric direction. Defaults to
|
metric_mode
|
The metric success mode. Defaults to
|
is_cumulative
|
Whether the metric is cumulative. Defaults to False.
|
contributes_to_success
|
Whether this term participates in task-success accounting (
|
Notes
These five settings are fixed once the metric manager is built: it reads them into its
batched reset() then, so a later write to one (from a curriculum, say) would not take
effect. Set them through configure.
Source code in eden/utils/common.py
Attributes¶
Methods:¶
is_success
¶
Check whether a metric is successful (default implementation).
This method can be overridden by subclasses to provide a custom implementation.
| PARAMETER | DESCRIPTION |
|---|---|
value
|
The value of the metric.
TYPE:
|
envs_idx
|
The indices of the environments to evaluate. If None, all environments are used. Defaults to None.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
A boolean tensor indicating whether the metric is successful. |
Source code in eden/managers/metric_manager.py
MetricTerm
¶
MetricTerm(env: EnvBase, options: _TermOptionsT)
Bases: ConfigurableTermBase[MetricTermOptions], SuccessCheckerMixin
Base class for metric terms. The fields every metric term has are on :class:SuccessCheckerMixin.
Source code in eden/managers/base.py
Attributes¶
MetricTermFuncWrapper
¶
MetricTermFuncWrapper(func: FunctionType, env: EnvBase, options: _TermOptionsT)
Bases: ConfigurableTermFuncWrapper[MetricTermOptions], SuccessCheckerMixin
Base class for metric terms defined as function. See :class:SuccessCheckerMixin for the shared fields.
Source code in eden/managers/base.py
Attributes¶
MetricManager
¶
MetricManager(env: EnvBase, options: MetricManagerOptions)
Bases: FlatManagerBase[MetricManagerOptions]
Manager for computing metrics.
Every term's value lives in one (num_envs, num_terms) buffer (get_value returns its
column), so reset(), which the auto-reset runs every step, handles all terms at once. The
terms' success_threshold, direction, metric_mode, is_cumulative and
contributes_to_success are read at construction and fixed from then on: configure them,
never write them at runtime.
Source code in eden/managers/metric_manager.py
Attributes¶
num_step_success_terms
property
¶
num_step_success_terms: int
How many INTERVAL metrics contribute to :attr:success_buf — with none, it never turns True.
Methods:¶
last_episode
¶
Each env's last finished episode: its Metrics/<name> values (and Metrics/Success /
Metrics/Progress when a term contributes to success), and whether Genesis left the env healthy.
reset() logs the mean of these rows over the envs that reset (the healthy ones); an evaluation that has to
pick which episodes it counts (each env's first k, say) reads the rows instead. An env that has not
finished an episode yet reads zeros, unhealthy.
| RETURNS | DESCRIPTION |
|---|---|
tuple[dict[str, Tensor], Tensor]
|
|
Source code in eden/managers/metric_manager.py
get_value
¶
get_value(name: str) -> Tensor
Return the latest cached per-env value buffer for metric name.
The returned tensor is the manager-owned buffer updated in place every
:meth:compute (stable identity), so callers — e.g. the metric_reward /
metric_observation pull terms — may grab the reference once and reuse it.
For a cumulative metric this is the running episode sum; otherwise the
current-step value. metric_manager runs before the reward and observation
managers in env.step(), so the value reflects the current step. Treat it
as read-only — mutating it corrupts the metric.
| RAISES | DESCRIPTION |
|---|---|
KeyError
|
If |
Source code in eden/managers/metric_manager.py
is_cumulative
¶
Whether metric name accumulates over the episode.
When True, :meth:get_value returns the running episode sum (reset per-env on
episode reset); when False, the current-step value. Pull terms use this to reject
a cumulative metric where a per-step value is expected (see metric_reward).
| RAISES | DESCRIPTION |
|---|---|
KeyError
|
If |
Source code in eden/managers/metric_manager.py
is_current_step_success
¶
is_current_step_success(name: str) -> Tensor
Per-env success mask for an INTERVAL metric evaluated on the current step's value.
Thin public accessor over the metric's :meth:is_success check applied to the
current-step value buffer — used by the on_metric_success termination so it
need not reach into the manager's private term/value dicts.
| RAISES | DESCRIPTION |
|---|---|
KeyError
|
If |
ValueError
|
If |
Source code in eden/managers/metric_manager.py
summary
¶
summary() -> str
Source code in eden/managers/metric_manager.py
reset
¶
Source code in eden/managers/metric_manager.py
compute
¶
Source code in eden/managers/metric_manager.py
Functions:¶
RecorderManager¶
Recorder manager and RecorderTerm base for episode data capture and export.
Attributes¶
RECORDER_TERM_REGISTRY
module-attribute
¶
RECORDER_TERM_REGISTRY = TermRegistry(name='RECORDER_TERM', term_class=RecorderTerm, populated_by=('eden.managers.terms.recorders',))
Classes¶
RecorderTerm
¶
RecorderTerm(env: EnvBase, options: _TermOptionsT)
Bases: ConfigurableTermBase[RecorderTermOptions]
Source code in eden/managers/base.py
RecorderManager
¶
RecorderManager(env: EnvBase, options: RecorderManagerOptions)
Bases: FlatManagerBase[RecorderManagerOptions]
Lifecycle orchestrator for transition-first recording.
Public methods expose recording/export control to users. Internal _on_*
methods are called from env lifecycle events and translate those events into
recorder-term collection, step closure, episode rotation, and export.
Source code in eden/managers/recorder_manager.py
Attributes¶
Methods:¶
start_recording
¶
start_recording(reset_scene: bool = False) -> None
Source code in eden/managers/recorder_manager.py
end_recording
¶
end_recording(*, export_unfinished: bool = True) -> None
Stop recording.
| PARAMETER | DESCRIPTION |
|---|---|
export_unfinished
|
Export the episodes still in progress, cut short where they stand (the default — a teleoperator stopping means "keep what I did"). Pass False to drop them instead, when only complete episodes belong in the dataset — a batch generator stopping once it has enough.
TYPE:
|
Source code in eden/managers/recorder_manager.py
get_exported_successful_episode_count
¶
get_exported_failed_episode_count
¶
get_episode
¶
get_episode(env_idx: int) -> EpisodeData
load_episode
¶
Source code in eden/managers/recorder_manager.py
set_episode_success
¶
close
¶
Source code in eden/managers/recorder_manager.py
reset
¶
Fan the term lifecycle reset() out to every recorder term.
Episode bookkeeping does not live here: the env brackets a reset with
:meth:_on_reset_started / :meth:_on_reset_finished, which close and open episodes
around the whole _reset_idx sequence. This is the per-term hook every other
manager's reset() already fans out, so a recorder term gets the same two-phase
contract as any other term — reset() here, then post_reset() from
:meth:~eden.managers.base.ManagerBase.post_reset. Returns no log extras.
Source code in eden/managers/recorder_manager.py
Functions:¶
MotionManager¶
The motion manager of MimicRLEnvBase, with MotionTerm and MOTION_TERM_REGISTRY, is documented on the
motion reference page together with the collaborators
MotionTerm delegates to.
Reset-configuration sampling¶
Which reset configuration each env starts its next episode from, for tasks that reset from a bank of states.
Which reset configuration each env starts its next episode from.
A reset configuration is one row of a fixed, indexed set of episode starts the task can reset to: a stored simulator state (robot and object poses), a start state paired with a goal, a scene variant such as an object instance or a terrain patch. What a row means is the consumer's business; the sampler sees only indices. Two roles meet at reset time and this module is the seam between them:
- the consumer -- a RESET event term that owns the set, knows its size, and turns an index into a reset (writes the state, places the robot, sets the goal);
- the sampler -- whatever decides which index each resetting env gets. Uniform by default; an adaptive sampler (Success Guided Sampling, a curriculum term) when the task opts in.
They meet through :class:ResetConfigSampler. The consumer resolves its sampler in build()
(:func:resolve_reset_config_sampler), tells it the set size once (:meth:ResetConfigSampler.bind),
and asks for indices from its RESET compute() (:meth:ResetConfigSampler.sample).
Why a RESET event drives the sampler rather than CurriculumManager.reset: the scene and the
RESET events run before any manager reset (EnvBase._reset_idx), so by the time a curriculum
term's own reset() runs, the env has already been placed.
The assignment is returned as the full (num_envs,) buffer, not as the selected rows: a reset
selector may be a bool mask, and compacting one to its selected rows is a GPU->CPU sync on every
step that resets anything. Consumers index the buffer with the same envs_idx
they were handed.
Classes¶
ResetConfigSampler
¶
Bases: ABC
Chooses a reset-configuration index for every env being reset.
Subclassed by :class:UniformResetConfigSampler and by adaptive samplers, which are curriculum terms
(class SuccessGuidedSampling(CurriculumTerm, ResetConfigSampler)). A consumer calls :meth:bind
exactly once from its build(), then :meth:sample from every RESET compute().
The base owns everything every sampler shares: bind validation, the (num_envs,) assignment buffer,
which envs' next reset is a first spawn (:attr:first_spawn), and turning a reset selector into a mask. A
subclass implements :meth:_draw -- one proposed configuration per env, of which only the selected rows are
kept -- and may allocate per-configuration state in :meth:_on_bind. It provides num_envs and device,
which a manager term already has.
The base also owns the checkpoint: :meth:state_dict / :meth:load_state_dict record and check the size of
the configuration set, and a subclass with state of its own adds it in :meth:_sampler_state /
:meth:_load_sampler_state. Every load ends with every env's next reset a first spawn, whatever the subclass:
the episodes running at the load started from the pre-load state, and grading them into the loaded one would
mix the two. Both methods are final: a subclass that overrides either one, or whose MRO resolves either one
to another class first (a default added to a curriculum-term base, say), raises TypeError when the class
is defined, instead of silently skipping the size check, the first-spawn reset or the sampler's own state.
The bound state lives in class-level defaults rather than in an __init__: a curriculum term's
__init__ chain calls its bases explicitly and never reaches this one.
Attributes¶
assignment
property
¶
(num_envs,) long: the configuration each env's current episode started from.
first_spawn
property
¶
(num_envs,) bool: envs whose next reset finishes no episode this sampler should learn from.
True for every env until its first :meth:sample, and again for every env after a
:meth:load_state_dict. Where it is False, :attr:assignment is the configuration the env's running
episode started from, drawn by this sampler's current state.
Methods:¶
bind
¶
Fix the size of the configuration set this sampler draws from, then run :meth:_on_bind.
consumer labels the binding term in error messages, e.g. ResetFromStateBuffer(path='…').
A sampler bound twice raises: two consumers sharing one sampler would draw indices into two
different sets while the sampler's statistics conflate them.
Source code in eden/managers/resets/sampling.py
sample
¶
sample(envs_idx: slice | Tensor) -> Tensor
Assign a configuration to each env in envs_idx and return the assignment buffer.
envs_idx is a sanitized selector (eden.utils.misc.sanitize_envs_idx): a slice, a
long index tensor, or a (num_envs,) bool mask. Returns the sampler's (num_envs,)
long buffer of current assignments, with only the envs_idx rows changed by this call.
The buffer is live -- the consumer must index it now, not keep it for later. The selected
envs leave :attr:first_spawn after the draw, so :meth:_draw still sees which are first spawns.
Source code in eden/managers/resets/sampling.py
state_dict
¶
state_dict() -> dict
The sampler's checkpoint: the size of the set it was bound to, plus :meth:_sampler_state.
load_state_dict
¶
load_state_dict(state: dict) -> None
Restore a :meth:state_dict, then make every env's next reset a first spawn.
Raises if the checkpoint was saved for a configuration set of another size: its per-configuration state would index configurations it was never recorded on. The episodes running now started from the pre-load state, so none of them is graded into the loaded one.
Source code in eden/managers/resets/sampling.py
UniformResetConfigSampler
¶
Bases: ResetConfigSampler
Draws every reset configuration with equal probability -- the default when a task opts out.
Not a manager term: a consumer with no sampler configured constructs one of these itself, so
the uniform path has no config entry, no logging and no checkpoint state. Draws from the global
torch RNG, like every other reset-time randomization, so seeding the run seeds it.
Source code in eden/managers/resets/sampling.py
CategoryWeightedSampler
¶
Bases: ResetConfigSampler
Draws a category by weight, then a configuration uniformly within it (OmniReset's reset-family mix).
category labels each configuration ((num_configs,) long, values index weights); a category with
weight 0 is never drawn, and its configurations with it. Unlike uniform sampling over the whole set, a category's
share does not grow with its size. Like :class:UniformResetConfigSampler, a consumer constructs it itself and
it draws from the global torch RNG. Sync-free: the configurations are sorted by category once, at bind, and
again only on :meth:relabel.
Source code in eden/managers/resets/sampling.py
Attributes¶
Methods:¶
relabel
¶
Take new labels for the same configurations, e.g. after ResetStateBuffer.write_rows changed some rows'.
Re-checks the labels and re-sorts the configurations by them: a few device-to-host reads, so call it when the labels change, not on every reset.
Source code in eden/managers/resets/sampling.py
Functions:¶
resolve_reset_config_sampler
¶
resolve_reset_config_sampler(env: EnvBase, sampler_name: str | None, consumer: str) -> ResetConfigSampler
The sampler a reset consumer named, or a fresh uniform one when it named none.
sampler_name is a curriculum term's config key. Resolved from the consumer's build():
the curriculum manager is constructed before the event manager, so the term already exists, and
a wrong name raises at env construction rather than on the first reset.
Source code in eden/managers/resets/sampling.py
Term catalog¶
The machinery behind eden terms and the generated term catalog.
A catalog of every registered term: the parameters it accepts, their types, and their defaults.
Without it, a term's accepted parameter set exists in exactly one place — the function signature or
class fields in the source — so the only way to learn that dofs_vel_limits takes entity_name
and max_vel is to open eden/managers/terms/rewards/common.py. Measured on autonomous coding
agents without a catalog, a capable one paid for that with a median 29-62 reads of Eden's installed
source per task against 2-3 documentation reads; a weaker one skipped the reading and guessed until
its wall clock ran out.
Nothing here is new information: the registries already enumerate the terms and
get_parameter_specs / get_function_parameter_specs already extract the parameters. This
module emits them, for three consumers:
eden terms(:mod:eden.utils.cli.terms) — in the terminal,--jsonfor tooling.-
two generated pages, both written by
python -m eden.managers.catalog(:func:main), checked in, and verified current bytests/resources/test_term_catalog.py: -
eden/resources/term_catalog.md(:func:render_index) ships inside the package, because an agent working against an installed Eden haseden/but notdocs/. It lists every term by name, form, defining module and summary, and no parameters: an agent reads a page it finds whole and keeps it in context for the rest of its session, so this page is sized to be read whole. A term's parameters are oneeden terms --grep <name>away. docs/api/terms/catalog.md(:func:render_markdown) is the full catalog, parameters included, for the docs site. It is whateden terms --markdownprints.- the same
ParamInforendering theconfigure()errors use (:mod:eden.utils.params), so the catalog and the error that fires when you ignore it describe a term identically.
:data:KINDS is a curated in-tree table rather than a discovered one — the set of term registries is
closed and each entry carries wiring (the import alias, the wrapper class) that no registry knows
about. tests/managers/test_catalog.py asserts it covers every :class:~eden.utils.registry.TermRegistry
in the tree, so adding one and forgetting the catalog fails a test instead of quietly shipping a term
nobody can find.
Attributes¶
KINDS
module-attribute
¶
KINDS: tuple[TermKind, ...] = (TermKind('action', 'eden.managers', 'ACTION_TERM_REGISTRY', 'actions'), TermKind('observation', 'eden.managers', 'OBSERVATION_TERM_REGISTRY', 'observations', 'ObsTerm'), TermKind('reward', 'eden.managers', 'REWARD_TERM_REGISTRY', 'rewards', 'RewardTerm'), TermKind('termination', 'eden.managers', 'TERMINATION_TERM_REGISTRY', 'terminations', 'DoneTerm'), TermKind('command', 'eden.managers', 'COMMAND_TERM_REGISTRY', 'commands'), TermKind('event', 'eden.managers', 'EVENT_TERM_REGISTRY', 'events', 'EventTerm'), TermKind('curriculum', 'eden.managers', 'CURRICULUM_TERM_REGISTRY', 'curricula', 'CurrTerm'), TermKind('metric', 'eden.managers', 'METRIC_TERM_REGISTRY', 'metrics', 'MetricTerm'), TermKind('recorder', 'eden.managers', 'RECORDER_TERM_REGISTRY', 'recorders'), TermKind('motion', 'eden.managers.motion_manager', 'MOTION_TERM_REGISTRY'), TermKind('noise_model', 'eden.managers.modifiers.base', 'NOISE_MODEL_REGISTRY'), TermKind('observation_modifier', 'eden.managers.modifiers.base', 'OBSERVATION_MODIFIER_REGISTRY'), TermKind('action_modifier', 'eden.managers.modifiers.base', 'ACTION_MODIFIER_REGISTRY'))
Classes¶
TermKind
dataclass
¶
TermKind(name: str, module: str, registry_attr: str, terms_module: str | None = None, wrapper: str | None = None)
One term registry, plus how a config reaches the terms inside it.
module/registry_attr are resolved lazily (:meth:registry) so importing this module
costs only what the caller already imported — eden.managers does not pull the motion manager
or the modifiers, and listing rewards should not either.
Attributes¶
Methods:¶
registry
¶
registry() -> TermRegistry
This kind's registry, which fills itself on the first read.
Which modules have to be imported for a registry to hold its terms is the registry's own
business (populated_by, see :meth:~eden.utils.registry.Registry._populate), so it is
not declared here a second time.
Source code in eden/managers/catalog.py
TermEntry
dataclass
¶
TermEntry(name: str, kind: str, form: str, qualname: str, module: str, source: str, summary: str, params: tuple[ParamInfo, ...], requires_env_options: tuple[str, ...] = ())
One registered term, described the way an author has to write it.
Attributes¶
requires_env_options
class-attribute
instance-attribute
¶
haystack
property
¶
haystack: str
Everything eden terms --grep searches, as one lowercase line.
Name, kind, class/function name, defining module, summary and parameter names and types
— because "which term takes max_vel?" and "is there an observation for base height?"
are the two questions that send a reader to the full catalog, and only one of them is
answerable from term names alone.
Methods:¶
to_dict
¶
A JSON-serializable form, with each param expanded — what eden terms --json emits.
Source code in eden/managers/catalog.py
Functions:¶
builtin_catalog
¶
:func:catalog restricted to the terms Eden itself ships — what the generated page holds.
A term registry is a global, open table: an out-of-tree term registers into the same one, and
eden terms listing it is the point. The checked-in page must not depend on which of those
happen to be imported in the process that generated it, or it would read as stale as often as it
is right (the test suite alone registers a dozen throwaway terms).
"Shipped" is the eden.managers namespace — the term modules each registry names in its
populated_by. eden.tasks.* / eden.extensions.* terms register when their task
module is imported and would otherwise leak in by import order — they match a plain
startswith("eden.") but not the eden.managers prefix tested here.
Source code in eden/managers/catalog.py
render_markdown
¶
entries as the full catalog page, parameters included: docs/api/terms/catalog.md.
What eden terms --markdown prints. The page that ships in the wheel is :func:render_index.
One row per term, so a grep for a parameter name lands on a line that also carries the term
name, its form and its summary — the shape that answers "which term takes max_vel?" in one
hit. Rows are grouped by defining module, which doubles as the source pointer and keeps the
Source column (and its width) off every row.
| PARAMETER | DESCRIPTION |
|---|---|
entries
|
The entries to render, in catalog order (grouped by kind). |
grep
|
The
TYPE:
|
Source code in eden/managers/catalog.py
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 | |
render_index
¶
entries as the shipped eden/resources/term_catalog.md index: every term, no parameters.
What :func:render_markdown carries minus its Parameters column, which was most of that page:
grouped by kind and then by defining module, one line per term with its registry key, the class
to import for a class term, and its summary; each kind keeps its configure recipe and the names of
the fields all its terms share. Module headings are fully qualified, because a class term's recipe
says to import it from the module its section names. An agent that finds a catalog in the installed
package reads it whole and carries it for the rest of its session, so this page answers "is there a
term for X, and where is it defined?" and leaves "what does it take?" to eden terms --grep <name>.
| PARAMETER | DESCRIPTION |
|---|---|
entries
|
The entries to render, in catalog order (grouped by kind). |
Source code in eden/managers/catalog.py
main
¶
Regenerate both checked-in catalog pages from the registries (--check: report, don't write).
Run it from a repo checkout after adding, renaming or re-parameterizing a term;
tests/resources/test_term_catalog.py fails while either page is stale::
uv run python -m eden.managers.catalog # write both pages
uv run python -m eden.managers.catalog --check # exit 1 if either would change
Source code in eden/managers/catalog.py
Registries¶
Each term kind has a registry. A term registers under to_snake_case(<ClassOrFunctionName>); register() takes no
name=.
| Registry | Import |
|---|---|
ACTION_TERM_REGISTRY |
from eden.managers import ACTION_TERM_REGISTRY |
OBSERVATION_TERM_REGISTRY |
from eden.managers import OBSERVATION_TERM_REGISTRY |
REWARD_TERM_REGISTRY |
from eden.managers import REWARD_TERM_REGISTRY |
TERMINATION_TERM_REGISTRY |
from eden.managers import TERMINATION_TERM_REGISTRY |
COMMAND_TERM_REGISTRY |
from eden.managers import COMMAND_TERM_REGISTRY |
EVENT_TERM_REGISTRY |
from eden.managers import EVENT_TERM_REGISTRY |
CURRICULUM_TERM_REGISTRY |
from eden.managers import CURRICULUM_TERM_REGISTRY |
METRIC_TERM_REGISTRY |
from eden.managers import METRIC_TERM_REGISTRY |
RECORDER_TERM_REGISTRY |
from eden.managers import RECORDER_TERM_REGISTRY |
MOTION_TERM_REGISTRY |
from eden.managers.motion_manager import MOTION_TERM_REGISTRY |
The registry classes themselves are documented under core utilities.