Skip to content

Contracts

Every plugin group's contract lives in hylo-core, so a plugin depends on hylo-core alone. What a plugin must provide, and the data that passes between the kernel, environments and agents. The plugin system explains how plugins are registered; the interfaces explain the design behind each contract; the plugin catalogue lists what implements them.

Environments

env

Environment contract. An Environment = Simulator + Task logic behind reset/step.

Optional capabilities are declared in EnvSpec.capabilities and exposed through separate protocols, never by duck-typing, so the kernel can refuse a run that needs a missing capability.

Environment

Bases: Protocol

spec
spec() -> EnvSpec

Must be cheap: no simulator construction (ManiSkill builds obs space via a real reset).

reset
reset(task: TaskInstance) -> Observation

Initialise the episode for task (seed lives on the instance). The realised instruction is returned in Observation.instruction.

step
step(action: Action) -> StepResult
close
close() -> None

Snapshottable

Bases: Protocol

snapshot
snapshot() -> bytes
restore
restore(blob: bytes) -> None

PrivilegedStateSource

Bases: Protocol

Ground-truth state for Tasks/Scorers/diagnosis. The kernel never passes this to an Agent.

privileged_state
privileged_state() -> dict[str, Any]

Agents

agent

Agent contract. The kernel only knows Agent; reasoners, planners, policies, world models and memory are an Agent's internal composition (hylo-agents).

EpisodeStatus module-attribute

EpisodeStatus = Literal['success', 'failure', 'error', 'safety_abort', 'infra_invalid', 'unlabeled']

AgentSpec

Bases: Contract

name instance-attribute
name: str
version instance-attribute
version: str
required_state class-attribute instance-attribute
required_state: tuple[str, ...] = ()
required_cameras class-attribute instance-attribute
required_cameras: tuple[str, ...] = ()
action_semantics class-attribute instance-attribute
action_semantics: ActionSemantics | None = None
action_dim class-attribute instance-attribute
action_dim: int | None = None
components class-attribute instance-attribute
components: dict[str, str] = Field(default_factory=dict)
weights_license class-attribute instance-attribute
weights_license: str | None = None

EpisodeOutcome

Bases: Contract

episode_id instance-attribute
episode_id: str
task instance-attribute
task: TaskInstance
status instance-attribute
status: EpisodeStatus
success instance-attribute
success: bool | None
steps instance-attribute
steps: int
termination_reason instance-attribute
termination_reason: str

EpisodeContext dataclass

EpisodeContext(episode_id: str, task: TaskInstance, env_spec: EnvSpec, emit: Callable[[str, Mapping[str, Any]], None], seed: int = 0)

Runtime handle given to the Agent at reset. Not serialized.

episode_id instance-attribute
episode_id: str
task instance-attribute
task: TaskInstance
env_spec instance-attribute
env_spec: EnvSpec
emit instance-attribute
emit: Callable[[str, Mapping[str, Any]], None]
seed class-attribute instance-attribute
seed: int = 0

Agent

Bases: Protocol

spec
spec() -> AgentSpec
reset
reset(ctx: EpisodeContext) -> None
act
act(observation: Observation) -> ActionChunk
on_step
on_step(result: StepResult) -> None
on_episode_end
on_episode_end(outcome: EpisodeOutcome) -> None

AgentBase

Convenience base with no-op hooks. Not required by the protocol.

reset
reset(ctx: EpisodeContext) -> None
on_step
on_step(result: StepResult) -> None
on_episode_end
on_episode_end(outcome: EpisodeOutcome) -> None

Policies

policy

Policies (plugin group "policies"): action-chunk generators that agents run, steer and compose.

An Agent is what the kernel talks to (core.agent). A ChunkPolicy is the model inside: given an observation it draws action chunks, one per noise seed. Separating the two lets one policy be run, steered (seeded noise, sample-and-select, recovery rules), temporally ensembled (ACT), shadowed or memory-steered by agents in hylo-agents without the policy knowing, and lets one agent wrap any policy by configuration:

agent: {type: policy, params: {policy: {type: policy.flow, params: {path: artifacts/flow}}}}
agent: {type: steered, params: {sampler: {type: lerobot, params: {repo_id: lerobot/smolvla_libero}},
                                steering: {n_samples: 4, selector: medoid}}}

Deterministic policies (regression, retrieval, scripted) ignore the seeds and return identical samples. Stochastic ones (diffusion, flow matching, sampled tokens) must derive their randomness from the seeds only, so a candidate and its parent face the same noise on the same init state and an episode can be replayed.

ChunkPolicy

Bases: Protocol

spec
spec() -> AgentSpec
reset
reset(ctx: EpisodeContext) -> None
default_execution_steps
default_execution_steps() -> int
sample
sample(observation: Observation, seeds: list[int] | None, noise_scale: float) -> np.ndarray

(N, T, A) action chunks in the environment's units, one per seed (N = len(seeds), or 1 if None).

build_policy

build_policy(policy: dict[str, Any] | Any) -> ChunkPolicy

A policy from a registry spec {type, params}, or an already built one.

Reasoners, choosers, planners, reflectors

reasoning

Reasoning plugin contracts, one per role (each its own plugin group and version):

  • reasoners (hylo.reasoner): the slow, event-triggered part of an agent; turns observations and memory into a Decision (a subgoal, a skill, an abort), with begin / deliberate / end around an episode.
  • choosers (hylo.chooser): which object an instruction refers to, among perceived or remembered objects.
  • planners (hylo.planner): decomposes an instruction into short sequential subgoals and judges when one is done.
  • reflectors (hylo.reflector): distils lessons from a campaign's diagnosis into operations on a lesson book.

Rule-based, LLM and VLM implementations satisfy the same protocols; the kernel never sees the difference. The agents that use them (composable object reach, SubgoalAgent, the experience generator) live in hylo-agents and hylo-evolve; the LLM ones in hylo-reasoner-llm and hylo-proposer-llm.

Emit module-attribute

Emit = Callable[[str, Mapping[str, Any]], None]

Decision

Bases: Contract

kind instance-attribute
kind: str
output instance-attribute
output: JsonAny
rationale instance-attribute
rationale: str
trigger instance-attribute
trigger: str
component instance-attribute
component: str
inputs class-attribute instance-attribute
inputs: dict[str, JsonAny] = Field(default_factory=dict)

ReasonerContext dataclass

ReasonerContext(task_id: str, episode_id: str, working: WorkingMemory, memory: MemoryStore | None, emit: Emit)
task_id instance-attribute
task_id: str
episode_id instance-attribute
episode_id: str
working instance-attribute
working: WorkingMemory
memory instance-attribute
memory: MemoryStore | None
emit instance-attribute
emit: Emit

Reasoner

Bases: Protocol

name instance-attribute
name: str
required_state instance-attribute
required_state: tuple[str, ...]
visibility_key instance-attribute
visibility_key: str
begin
begin(rctx: ReasonerContext) -> None
deliberate
deliberate(obs: Observation, trigger: str, rctx: ReasonerContext) -> Decision
end
end(success: bool | None, rctx: ReasonerContext) -> None

SceneObject

Bases: Contract

id instance-attribute
id: str
rgb instance-attribute
rgb: tuple[float, float, float]
position instance-attribute
position: tuple[float, float]
source instance-attribute
source: Literal['seen', 'remembered']

Choice

Bases: Contract

object_id instance-attribute
object_id: str | None
rationale instance-attribute
rationale: str
meta class-attribute instance-attribute
meta: dict[str, Any] = Field(default_factory=dict)

Chooser

Bases: Protocol

name instance-attribute
name: str
choose
choose(instruction: str, objects: list[SceneObject], robot_xy: tuple[float, float], obs: Observation | None = None) -> Choice

Subgoal

Bases: Contract

instruction instance-attribute
instruction: str
max_steps class-attribute instance-attribute
max_steps: int = Field(default=250, ge=1)

Plan

Bases: Contract

subgoals class-attribute instance-attribute
subgoals: list[Subgoal] = Field(min_length=1, max_length=6)
rationale class-attribute instance-attribute
rationale: str = ''
meta class-attribute instance-attribute
meta: dict[str, Any] = Field(default_factory=dict)

Planner

Bases: Protocol

name instance-attribute
name: str
plan
plan(instruction: str, observation: Observation) -> Plan
done
done(subgoal: Subgoal, observation: Observation) -> tuple[bool, str, dict[str, Any]]

Reflector

Bases: Protocol

Reads only what a generator may read (search-split diagnostics and timelines) and returns operations (ADD / UPVOTE / DOWNVOTE / EDIT / RETIRE) that the lesson book applies deterministically.

name instance-attribute
name: str
reflect
reflect(diagnosis: Mapping[str, Any], book: Any) -> list[dict[str, Any]]

Shared data types

types

Core data contracts shared by every layer (design/interfaces/environment.md, agent.md).

  • Observations and actions are typed, not free-form dicts (vla-eval's Dict[str, Any] ends up with inconsistent keys across benchmarks).
  • Action semantics live on the action space (inspect-robots ActionSemantics); compatibility is checked at bind time and a mismatch is an error.
  • success is reported separately from terminated / truncated.

ControlMode module-attribute

ControlMode = Literal['joint_pos', 'joint_delta', 'joint_vel', 'eef_delta_pos', 'eef_delta_pose', 'eef_abs_pose', 'planar_vel']

ActionSemantics

Bases: Contract

control_mode instance-attribute
control_mode: ControlMode
frame class-attribute instance-attribute
frame: str = 'base'
rotation_repr class-attribute instance-attribute
rotation_repr: Literal['axis_angle', 'quat_xyzw', 'rot6d'] | None = None
gripper class-attribute instance-attribute
gripper: str | None = None
dim_labels class-attribute instance-attribute
dim_labels: tuple[str, ...] = ()
max_step class-attribute instance-attribute
max_step: tuple[float | None, ...] | None = None

BoxSpace

Bases: Contract

shape instance-attribute
shape: tuple[int, ...]
low instance-attribute
low: tuple[float, ...]
high instance-attribute
high: tuple[float, ...]
semantics class-attribute instance-attribute
semantics: ActionSemantics | None = None
dtype class-attribute instance-attribute
dtype: Literal['float32', 'float64'] = 'float64'
low_array cached property
low_array: ndarray
high_array cached property
high_array: ndarray
contains
contains(x: ndarray) -> bool

ObservationSpec

Bases: Contract

state instance-attribute
state: dict[str, tuple[int, ...]]
cameras class-attribute instance-attribute
cameras: dict[str, tuple[int, int, int]] = Field(default_factory=dict)
units class-attribute instance-attribute
units: dict[str, str] = Field(default_factory=dict)
has_instruction class-attribute instance-attribute
has_instruction: bool = True

EnvCapabilities

Bases: Contract

seedable class-attribute instance-attribute
seedable: bool = True
snapshot class-attribute instance-attribute
snapshot: bool = False
render class-attribute instance-attribute
render: bool = False
privileged_state class-attribute instance-attribute
privileged_state: bool = False
batch_size class-attribute instance-attribute
batch_size: int = Field(default=1, ge=1)
self_paced class-attribute instance-attribute
self_paced: bool = False
real_robot class-attribute instance-attribute
real_robot: bool = False

WorkspaceLimit

Bases: Contract

Box the controlled point must stay in. action_dims[i] is the action dimension that moves along axis i of state_key (delta-position control); a move that pushes further outside the box is zeroed.

state_key instance-attribute
state_key: str
low instance-attribute
low: tuple[float, ...]
high instance-attribute
high: tuple[float, ...]
action_dims instance-attribute
action_dims: tuple[int, ...]

EnvSpec

Bases: Contract

name instance-attribute
name: str
version instance-attribute
version: str
observation instance-attribute
observation: ObservationSpec
action instance-attribute
action: BoxSpace
control_hz class-attribute instance-attribute
control_hz: float = Field(gt=0)
capabilities class-attribute instance-attribute
capabilities: EnvCapabilities = Field(default_factory=EnvCapabilities)
workspace class-attribute instance-attribute
workspace: WorkspaceLimit | None = None

Observation

Bases: Contract

state instance-attribute
state: dict[str, NDArray]
images class-attribute instance-attribute
images: dict[str, NDArray] = Field(default_factory=dict)
instruction class-attribute instance-attribute
instruction: str | None = None
time class-attribute instance-attribute
time: float = 0.0
extra class-attribute instance-attribute
extra: dict[str, JsonAny] = Field(default_factory=dict)

Action

Bases: Contract

data instance-attribute
data: NDArray
meta class-attribute instance-attribute
meta: dict[str, JsonAny] = Field(default_factory=dict)

ActionChunk

Bases: Contract

T actions, of which the executor runs execution_steps before replanning (LeRobot ChunkPolicySpec: prediction steps != execution steps).

actions instance-attribute
actions: NDArray
execution_steps class-attribute instance-attribute
execution_steps: int | None = Field(default=None, ge=1)
provenance class-attribute instance-attribute
provenance: dict[str, JsonAny] = Field(default_factory=dict)
single classmethod
single(action: ndarray, **provenance: object) -> 'ActionChunk'

StepResult

Bases: Contract

observation instance-attribute
observation: Observation
reward instance-attribute
reward: float | None
terminated instance-attribute
terminated: bool
truncated instance-attribute
truncated: bool
success class-attribute instance-attribute
success: bool | None = None
info class-attribute instance-attribute
info: dict[str, JsonAny] = Field(default_factory=dict)

Tasks

task

Task contracts. A TaskSpec covers both LIBERO-style declarative tasks with fixed init states and RoboCasa-style code tasks with seeded/snapshot init and dynamic instructions.

InitSpec module-attribute

InitSpec = Annotated[Union[SeedInit, InitStateRef, SnapshotInit], Field(discriminator='kind')]

SeedInit

Bases: Contract

kind class-attribute instance-attribute
kind: Literal['seed'] = 'seed'

InitStateRef

Bases: Contract

kind class-attribute instance-attribute
kind: Literal['state_ref'] = 'state_ref'
uri instance-attribute
uri: str
count class-attribute instance-attribute
count: int = Field(ge=1)

SnapshotInit

Bases: Contract

kind class-attribute instance-attribute
kind: Literal['snapshot'] = 'snapshot'
uri instance-attribute
uri: str

Predicate

Bases: Contract

Success predicate: builtin name, declarative expr (e.g. BDDL goal) or callable ref.

kind instance-attribute
kind: Literal['builtin', 'expr', 'callable']
name class-attribute instance-attribute
name: str = ''
expr class-attribute instance-attribute
expr: str | None = None
ref class-attribute instance-attribute
ref: str | None = None

TaskSpec

Bases: Contract

id instance-attribute
id: str
version instance-attribute
version: str
instruction instance-attribute
instruction: str
init class-attribute instance-attribute
init: InitSpec = Field(default_factory=SeedInit)
success class-attribute instance-attribute
success: Predicate = Field(default_factory=lambda: Predicate(kind='builtin', name='goal_reached'))
max_steps class-attribute instance-attribute
max_steps: int = Field(default=200, ge=1)
params class-attribute instance-attribute
params: dict[str, JsonAny] = Field(default_factory=dict)
tags class-attribute instance-attribute
tags: tuple[str, ...] = ()
digest
digest() -> str

TaskInstance

Bases: Contract

One concrete episode request: a task, a seed, and where it sits in the protocol.

spec instance-attribute
spec: TaskSpec
seed instance-attribute
seed: int
episode_index class-attribute instance-attribute
episode_index: int = 0
split class-attribute instance-attribute
split: str = 'train'
key property
key: str

Stable pairing key used for same-seed comparisons across agent versions.

Instruments

instrument

Instruments (RFC-0004): learned components that measure or label instead of act.

A success judge or progress model labels episodes (so memory and training data can be written without simulator ground truth); a world model pre-screens candidates. Instruments are trusted through calibration, not the gate: hylo.learn.calibrate scores a judge against simulator outcomes on runs the judge was not built from and stores the CalibrationReport in the judge's artifact manifest. An instrument never decides a gate verdict: in simulation the gate always scores with ground truth, and a world model may only choose which candidates are worth real evaluation budget.

Judgement

Bases: Contract

p_success class-attribute instance-attribute
p_success: float = Field(ge=0.0, le=1.0)
progress class-attribute instance-attribute
progress: list[float] | None = None
info class-attribute instance-attribute
info: dict[str, JsonAny] = Field(default_factory=dict)

SuccessJudge

Bases: Protocol

Judges an episode from what a real robot would have: observations and actions, no privileged state.

name instance-attribute
name: str
judge
judge(episode: EpisodeTrace) -> Judgement

ValueModel

Bases: Protocol

Expected (discounted) success from a state, optionally after executing a chunk.

name instance-attribute
name: str
value
value(obs: Observation, chunk: ndarray | None = None) -> float

WorldModel

Bases: Protocol

Action-conditioned predictor; used to pre-screen candidates, never to replace their evaluation.

name instance-attribute
name: str
rollout
rollout(obs: Observation, actions: ndarray) -> list[Observation]

CalibrationReport

Bases: Contract

n instance-attribute
n: int
base_rate instance-attribute
base_rate: float
auc instance-attribute
auc: float | None
brier instance-attribute
brier: float
ece instance-attribute
ece: float
threshold instance-attribute
threshold: float | None
precision instance-attribute
precision: float | None
recall instance-attribute
recall: float | None
target_precision instance-attribute
target_precision: float
ready instance-attribute
ready: bool
runs class-attribute instance-attribute
runs: list[str] = Field(default_factory=list)
scores class-attribute instance-attribute
scores: NDArray | None = None

Learners

learning

Learning pipeline contracts (RFC-0004): data source -> labeler -> learner -> sealed artifact -> candidate -> gate.

Contracts only. A learner turns development data into one artifact directory and seals it (artifact.write_manifest). Running jobs (in-process or with another venv's interpreter), calibration and data ingestion live in hylo-learn (hylo.learn).

TrainJob

Bases: Contract

learner instance-attribute
learner: str
inputs instance-attribute
inputs: list[str]
out instance-attribute
out: str
splits class-attribute instance-attribute
splits: tuple[str, ...] = DEV_SPLITS
params class-attribute instance-attribute
params: dict[str, JsonAny] = Field(default_factory=dict)
digest
digest() -> str

Learner

Bases: Protocol

Builds and seals one artifact from a job. Must refuse forbidden splits (use artifact.development_runs).

name instance-attribute
name: str
fit
fit(job: TrainJob) -> ArtifactManifest

OnlineLearner

Bases: Protocol

Learns by acting (policy search, on-policy RL, iterative self-training): each iteration it proposes agent specs to roll out, the runner evaluates them on a development split (recorded as ordinary runs) and hands back the results, the learner updates. seal writes the artifact; its provenance lists every rollout run.

Run by hylo.learn.online.run_online; a TrainJob whose learner has policies / update is online and carries the experiment config in params["experiment"].

name instance-attribute
name: str
start
start(job: TrainJob, agent: dict[str, Any]) -> None
policies
policies() -> list[dict[str, Any]]
update
update(results: list[dict[str, Any]]) -> dict[str, Any]
finished
finished() -> bool
seal
seal(out: str, sources: list[Any]) -> ArtifactManifest

DataSource

Bases: Protocol

Episodes from outside the campaign's own runs (demonstrations, videos, other robots).

core.datasource.ingest turns them into a run under split external: with origin and licence. Episodes may lack actions (videos) or outcomes; labelers fill those in and say who produced each label.

name instance-attribute
name: str
origin instance-attribute
origin: str
licence instance-attribute
licence: str
episodes
episodes() -> Iterator[Any]

Labeler

Bases: Protocol

Adds labels an episode is missing (outcome, progress, latent actions). Labels from an instrument carry its artifact digest; an instrument never labels the data it was calibrated on.

name instance-attribute
name: str
label
label(episode: EpisodeTrace) -> dict[str, Any]

Benchmarks

benchmark

Benchmark = versioned set of TaskSpecs + evaluation protocol (splits, seeds, episodes); the benchmarks plugin contract (hylo.benchmark): a plugin is a factory that returns a Benchmark.

STANDARD_SPLITS module-attribute

STANDARD_SPLITS = ('train', 'validation', 'heldout')

RSI_SPLITS module-attribute

RSI_SPLITS = ('search', 'search_val', 'gate', 'heldout')

Benchmark

Bases: Contract

id instance-attribute
id: str
version instance-attribute
version: str
tasks instance-attribute
tasks: tuple[TaskSpec, ...]
splits instance-attribute
splits: dict[str, tuple[int, ...]]
requires_privileged class-attribute instance-attribute
requires_privileged: bool = False
metadata class-attribute instance-attribute
metadata: dict[str, str] = Field(default_factory=dict)
instances
instances(split: str) -> list[TaskInstance]
digest
digest() -> str

standard_splits

standard_splits(train: int, validation: int, heldout: int) -> dict[str, tuple[int, ...]]

Disjoint seed ranges: train 0.., validation 10000.., heldout 20000..

rsi_splits

rsi_splits(n_states: int = 50, bounds: tuple[int, int, int] = (20, 30, 40), replicates: int = 1, gate_looks: int = 1) -> dict[str, tuple[int, ...]]

RFC-0002 split of init-state indices: search (diagnosis), search_val (selection), gate (confirmation), heldout (report only). Replicate r reuses init state (seed % n_states) with new policy noise (seed + r * n_states). gate_looks > 1 adds gate_r1 .. gate_r{looks-1}: further noise replicates of the gate init states, evaluated only when an earlier look is inconclusive (group-sequential gate).

gate_split_names

gate_split_names(gate_looks: int) -> list[str]

Scorers and gate tests

scoring

The scorers and gate_tests plugin contracts (hylo.scorer, hylo.gate_test) and the data they exchange.

A scorer reads one episode's trace (never the agent or the environment) and returns a Score; a Score of None is an abstention (infra failure, agent error) and is excluded from metrics. A gate test compares per-episode values of one metric between a candidate and its parent, paired on (task, seed), and returns a PairedComparison. Implementations (success, steps, McNemar, sign, permutation) live in hylo-eval.

Score

Bases: Contract

value instance-attribute
value: float | None
explanation class-attribute instance-attribute
explanation: str = ''
metadata class-attribute instance-attribute
metadata: dict[str, Any] = Field(default_factory=dict)

Scorer

Bases: Protocol

name instance-attribute
name: str
score
score(trace: EpisodeTrace, task: TaskInstance) -> Score

PairedComparison

Bases: Contract

n_pairs instance-attribute
n_pairs: int
candidate_wins instance-attribute
candidate_wins: int
parent_wins instance-attribute
parent_wins: int
p_value instance-attribute
p_value: float

GateTest

Bases: Protocol

name instance-attribute
name: str
metric instance-attribute
metric: str
compare
compare(candidate: Mapping[str, float | None], parent: Mapping[str, float | None]) -> PairedComparison

Generators and campaign records

improvement

The generators plugin contract (hylo.generator) and the records of an improvement campaign.

A generator (Proposer) reads a parent's diagnosis and the campaign's archive and proposes candidates: declarative patches of dotted config paths, each with a hypothesis. The cascade, the gate and the registry that decide what happens to a candidate live in hylo-evolve; these are the shapes a generator plugin sees and returns.

CandidateKind module-attribute

CandidateKind = Literal['config_patch', 'critic_rule', 'recovery_rule', 'prompt', 'skill', 'policy_ref', 'code']

Candidate

Bases: Contract

patch instance-attribute
patch: dict[str, Any]
rationale instance-attribute
rationale: str
parent_id instance-attribute
parent_id: str | None
generator instance-attribute
generator: str
kind class-attribute instance-attribute
kind: CandidateKind = 'config_patch'
evidence class-attribute instance-attribute
evidence: dict[str, Any] = Field(default_factory=dict)
id class-attribute instance-attribute
id: str = Field(default_factory=lambda: 'cand-' + uuid.uuid4().hex[:10])
digest
digest() -> str

StageResult

Bases: Contract

stage instance-attribute
stage: int
success class-attribute instance-attribute
success: float | None = None
parent_success class-attribute instance-attribute
parent_success: float | None = None
wins class-attribute instance-attribute
wins: int = 0
losses class-attribute instance-attribute
losses: int = 0
n_pairs class-attribute instance-attribute
n_pairs: int = 0
p_value class-attribute instance-attribute
p_value: float | None = None
notes class-attribute instance-attribute
notes: str = ''

ArchiveEntry

Bases: Contract

candidate instance-attribute
candidate: Candidate
hypothesis instance-attribute
hypothesis: str
stage_reached instance-attribute
stage_reached: int
stages class-attribute instance-attribute
stages: list[StageResult] = Field(default_factory=list)
verdict class-attribute instance-attribute
verdict: str = 'pending'
evidence class-attribute instance-attribute
evidence: dict[str, Any] = Field(default_factory=dict)
created_at class-attribute instance-attribute
created_at: float = Field(default_factory=time.time)

Proposer

Bases: Protocol

name instance-attribute
name: str
propose
propose(diagnosis: Mapping[str, Any], cfg: ExperimentConfig, parent_id: str | None, archive: list[ArchiveEntry]) -> list[tuple[Candidate, str]]

Safety

safety

SafetyShield: the single, non-bypassable checkpoint between any Agent and any Environment.

  • Built by the kernel from the EnvSpec plus config; Agents never see or construct it.
  • Mandatory guards (finite check, bounds clamp) are always installed; config can only add guards or tighten limits. RSI candidates cannot target the safety.* config scope (evolve/candidate.py).
  • Extra guards are plugins ("guards" group): safety: {guards: [{type: keep_out, params: {...}}]}. They run between the mandatory finite check (first) and bounds clamp (last), so no plugin can let a non-finite or out-of-bounds action through. Built in: delta_limit, workspace, norm_limit, keep_out.
  • Rotation-aware: per-dimension clamping is refused for quaternion / rot6d dims (inspect-robots approver.py makes the same choice).

SafetyContext dataclass

SafetyContext(space: BoxSpace, clip_tolerance: float = 0.0, state: Any = None, last_action: ndarray | None = None, step: int = 0)
space instance-attribute
space: BoxSpace
clip_tolerance class-attribute instance-attribute
clip_tolerance: float = 0.0
state class-attribute instance-attribute
state: Any = None
last_action class-attribute instance-attribute
last_action: ndarray | None = None
step class-attribute instance-attribute
step: int = 0

GuardVerdict dataclass

GuardVerdict(data: ndarray, modified: bool, reason: str = '', minor: bool = False)
data instance-attribute
data: ndarray
modified instance-attribute
modified: bool
reason class-attribute instance-attribute
reason: str = ''
minor class-attribute instance-attribute
minor: bool = False

Guard

Bases: Protocol

name instance-attribute
name: str
review
review(data: ndarray, ctx: SafetyContext) -> GuardVerdict

FiniteGuard

name class-attribute instance-attribute
name = 'finite'
review
review(data: ndarray, ctx: SafetyContext) -> GuardVerdict

BoundsGuard

name class-attribute instance-attribute
name = 'bounds'
review
review(data: ndarray, ctx: SafetyContext) -> GuardVerdict

WorkspaceGuard

WorkspaceGuard(limit: Any)

Zeroes delta-position components that would push the controlled point further outside its workspace.

name class-attribute instance-attribute
name = 'workspace'
limit instance-attribute
limit = limit
review
review(data: ndarray, ctx: SafetyContext) -> GuardVerdict

DeltaLimitGuard

DeltaLimitGuard(max_delta: float | list[float])

Limits per-step change of the action signal (rate limit).

name class-attribute instance-attribute
name = 'delta_limit'
max_delta instance-attribute
max_delta = np.asarray(max_delta, dtype=np.float64)
review
review(data: ndarray, ctx: SafetyContext) -> GuardVerdict

SafetyReview dataclass

SafetyReview(proposed: ndarray, approved: ndarray, modified: bool, reasons: list[str] = list(), clipped: bool = False)
proposed instance-attribute
proposed: ndarray
approved instance-attribute
approved: ndarray
modified instance-attribute
modified: bool
reasons class-attribute instance-attribute
reasons: list[str] = field(default_factory=list)
clipped class-attribute instance-attribute
clipped: bool = False
to_payload
to_payload() -> dict[str, Any]

SafetyShield

SafetyShield(env_spec: EnvSpec, extra_guards: list[Guard] | None = None, *, abort_on_modification: bool = False, clip_tolerance: float = 0.05)
MANDATORY class-attribute instance-attribute
MANDATORY = (FiniteGuard, BoundsGuard)
abort_on_modification instance-attribute
abort_on_modification = abort_on_modification or env_spec.capabilities.real_robot
interventions instance-attribute
interventions = 0
guard_names property
guard_names: list[str]
reset
reset() -> None
observe
observe(state: Any) -> None

Latest observation state, for state-dependent guards (called by the kernel every step).

review
review(action: Action) -> tuple[Action, SafetyReview]

NormLimitGuard

NormLimitGuard(max_norm: float, dims: list[int] | None = None)

Scales the selected action dimensions down so their Euclidean norm stays within max_norm (e.g. a speed limit on the translational part of an end-effector delta).

name class-attribute instance-attribute
name = 'norm_limit'
review
review(data: ndarray, ctx: SafetyContext) -> GuardVerdict

KeepOutGuard

KeepOutGuard(state_key: str, low: list[float], high: list[float], action_dims: list[int], margin: float = 0.0)

A box the controlled point must not enter (a fixture, a person's side of the table). Delta-position components that move the point into the box, or deeper into it, are zeroed.

name class-attribute instance-attribute
name = 'keep_out'
review
review(data: ndarray, ctx: SafetyContext) -> GuardVerdict

workspace_guard

workspace_guard(state_key: str, low: list[float], high: list[float], action_dims: list[int]) -> WorkspaceGuard

build_guards

build_guards(cfg: dict[str, Any] | None) -> list[Guard]

Extra guards from the safety config: max_delta (shorthand) and guards: [{type, params}] plugins.