PYTHON API · VERSION 1
Python API reference
Import the public API from safety_chores_rl. API version 1 provides live
episodes, isolated rollout workers, budgeted planners, and serializable results.
Native dependencies load only when a native runtime is selected.
Quick start
This complete example evaluates two actions in the portable, one-step
ChoiceRuntime, then executes the higher-reward action. Save it as a Python file
and run it after python -m pip install -e ..
from safety_chores_rl import (
RuntimeSpec, Episode, RolloutSession,
RolloutBudget, BestOfN, reward_return,
)
def main():
spec = RuntimeSpec.create("safety_chores_rl.toy:ChoiceRuntime")
with Episode(spec, max_steps=1) as env, RolloutSession(spec) as session:
root = session.capture_root(env)
plan = BestOfN(session, objective=reward_return).plan(
root, [(0,), (7,)], sample_seeds=(0,),
rollout_budget=RolloutBudget(max_steps=1),
)
if plan.status == "ok":
transition = session.execute_first(env, plan)
print(transition.action, transition.reward) # 7 7.0
if __name__ == "__main__":
main()
Use the __main__ guard because rollout workers start fresh Python processes.
For Safety-CHORES, follow the native setup and reproduction note.
RuntimeSpec
RuntimeSpec(factory: str, config_json: str = "{}")
RuntimeSpec.create(factory: str, **config) -> RuntimeSpec
Immutable runtime configuration, shared by live episodes and rollout workers. Source.
| Parameter | Description |
|---|---|
factory |
Importable "package.module:Factory"; the factory receives configuration as keyword arguments. |
config_json |
JSON object, normalized into a stable representation. Use create() to pass ordinary keyword arguments instead. |
Property: identity: str hashes the API version, factory, and configuration.
Factories must be importable in a fresh interpreter; pass configuration rather
than live simulator objects or closures. Changing runtime code or artifacts
requires a new session and an updated configuration identity.
Episode
Episode(spec: RuntimeSpec, *, task_id=0, seed=0, max_steps=200)
Owns and resets the live runtime. Use as a context manager to close it reliably. Source.
| Parameter | Default | Description |
|---|---|---|
spec |
Required | Runtime factory and frozen configuration. |
task_id |
0 |
Nonnegative task identifier understood by the runtime. |
seed |
0 |
Nonnegative episode seed. |
max_steps |
200 |
Positive episode action cap. |
| Member | Returns | Behavior |
|---|---|---|
step(action=None) |
Transition |
Execute an action ID, or let the policy choose when None. Update actions, cost, and completion. |
matches(root) |
bool |
Compare episode identity, prefix, cost, and runtime snapshot with a captured root. |
close() |
None |
Close the runtime; repeated calls are safe. |
observation |
dict |
Current public planning observation. |
actions, cost, done |
list[int], float, bool |
Executed prefix, cumulative cost, and episode completion. |
step() raises RuntimeError after close or completion. A runtime error during
an action marks the episode done because the simulator may already have moved.
RolloutSession
RolloutSession(spec: RuntimeSpec, *, workers=1, cache_size=4096)
Persistent process pool with session-owned roots and exact-request caching. Source.
| Parameter | Default | Description |
|---|---|---|
spec |
Required | Must match the live episode's specification. |
workers |
1 |
Positive number of isolated worker processes. |
cache_size |
4096 |
Maximum cached results; 0 disables reuse storage. |
Use a context manager. Concurrent operations on the same session raise
RuntimeError; use separate sessions for concurrent callers. cancel() may be
called from another thread.
capture_root
session.capture_root(episode: Episode) -> Root
Capture the prefix, accumulated cost, public observation, and validation snapshot
without advancing the live episode. The episode must be live, nonterminal, and
use the same RuntimeSpec.
Raises: ValueError for an incompatible, completed, or closed episode.
Roots are immutable, session-owned handles. Keep the returned object; copies,
deserialized roots, foreign roots, and released roots are rejected.
rollout
session.rollout(
root, candidate, *, sample_seed=0, continuation=True,
budget: RolloutBudget, search_budget=None, reuse=True,
) -> RolloutResult
Reset and replay the root in a worker, execute the candidate, then optionally sample a policy continuation. The live episode remains unchanged.
| Parameter | Description |
|---|---|
root |
A registered Root from this session. |
candidate |
Nonempty iterable of nonnegative integer action IDs. |
sample_seed |
Integer in [0, 2**32), applied to policy randomness after the entire candidate and its feedback. |
continuation |
False runs only the candidate; True continues the policy within the horizon. |
budget |
Candidate plus continuation horizon and per-request limits. Must fit the remaining episode cap. |
search_budget |
Optional SearchBudget; None uses its defaults. |
reuse |
Allow exact valid-result cache hits. |
Returns: one RolloutResult, including invalid outcomes such as timeout or
root mismatch. Simulator randomness is not reseeded at the continuation boundary.
rollout_batch
session.rollout_batch(
root, candidates, *, sample_seeds=(0,), continuation=True,
budget: RolloutBudget, search_budget=None, ledger=None, reuse=True,
) -> tuple[RolloutResult, ...]
Evaluate every candidate/seed pair. Parameters follow rollout(), with nonempty
candidates and sample_seeds iterables. Results are ordered sample-major,
then candidate order. Exact duplicate requests are coalesced. ledger is an
advanced hook for sharing the internal budget accounting used by planners;
ordinary callers should pass search_budget.
Root inspection and lifecycle
| Method | Returns | Behavior |
|---|---|---|
validate_root(root, *, timeout_seconds=60.0) |
RolloutResult |
Independently reset/replay and validate the root; bypass the cache. |
inspect_candidates(root, *, timeout_seconds=60.0, ledger=None) |
RolloutResult |
Run optional candidate_preview() in a disposable replay episode. |
release_root(root) |
None |
Release a registered root and its cached results. |
capabilities() |
dict |
Report API/runtime identity, worker/cache counts, and reconstruction capabilities. |
cancel() |
None |
Interrupt the active batch; later calls can restart workers. |
close() |
None |
Stop workers and clear roots and caches. |
Only valid results enter the bounded LRU cache. Its key includes the runtime,
root, candidate, seed, continuation flag, and rollout budget. A cache hit has
cache_hit=True and native_steps=0. Reuse is local to the session.
execute_first
session.execute_first(episode, plan) -> Transition
Check the live root, execute plan.action, and release the root and its cached
results after the action returns. Raises StalePlanError if the root or episode
has changed, or ValueError if no action is proposed. Inspect plan.status
before deciding to execute a partially evaluated plan.
Budgets
RolloutBudget
RolloutBudget(
max_steps: int, timeout_seconds=60.0,
stop_on_cost=True, record_trace=False,
)
| Parameter | Description |
|---|---|
max_steps |
Positive candidate-plus-continuation horizon; excludes root replay. |
timeout_seconds |
Positive finite per-request timeout. |
stop_on_cost |
Stop when accumulated cost rules out zero-cost success. |
record_trace |
Save execution signatures; requires runtime execution_signature(). |
SearchBudget
SearchBudget(
max_rollouts=128, max_native_steps=20000,
wall_seconds=60.0, max_candidates=1024,
)
| Parameter | Description |
|---|---|
max_rollouts |
Maximum dispatched rollout requests. |
max_native_steps |
Primitive actions, including root-prefix replay. |
wall_seconds |
Search wall-time budget, including worker startup and reset. |
max_candidates |
Bound on candidate enumeration. |
Counts must be positive integers; time must be positive and finite. The coordinator reserves worst-case work before dispatch and returns unused reservations after early completion. Errors/timeouts are charged conservatively. Cleanup may extend observed runtime beyond the deadline; action counts exclude rendering and inference.
Planners
All planners return a Plan. Candidate actions are nonempty integer tuples;
sample seeds must be distinct. admissible(observation, candidate) optionally
filters candidates using public information.
Source.
BestOfN
BestOfN(session, *, objective=safe_completion, max_cost=None)
planner.plan(
root, candidates, *, sample_seeds=(0, 1, 2, 3),
rollout_budget, search_budget=SearchBudget(),
continuation=True, admissible=None,
) -> Plan
Evaluate all candidates on the same seeds; maximize mean objective with stable
lexicographic tie-breaking. A candidate with invalid, unresolved, or over-limit
samples has no score. max_cost filters observed total cost, including the prefix.
FirstSuccess
FirstSuccess(session)
planner.plan(
root, candidates, *, sample_seeds=(0, 1, 2, 3),
rollout_budget, search_budget=SearchBudget(), admissible=None,
) -> Plan
Preserve caller candidate priority and stop at the first fully valid worker wave
containing a completed zero-cost success. Forces stop_on_cost=True and
record_trace=True. The returned witness holds the entire successful trajectory;
plan.candidate holds its full action sequence. Failures remain in evaluations.
Invalid evidence, cancellation, or exhausted budget produces no executable witness.
The stopping rule is a feasibility search, not a success-rate estimator.
SuccessiveHalving
SuccessiveHalving(
session, *, objective=safe_completion, max_cost=None,
initial_samples=1, reduction_factor=2,
)
planner.plan(
root, candidates, *, sample_seeds=(0, 1, 2, 3, 4, 5, 6, 7),
rollout_budget, search_budget=SearchBudget(),
continuation=True, admissible=None,
) -> Plan
Start with initial_samples, retain the strongest fraction of candidates, and
multiply the sample budget by reduction_factor each round. The factor must be
at least 2. usage includes rounds and exhaustive-rollout count. This approximate
ranking procedure provides no confidence or optimality guarantee.
BeamSearch
BeamSearch(
session, *, depth=3, width=4, branching_factor=8,
heuristic=reward_return, max_cost=0.0,
)
planner.plan(
root, expander, *, rollout_budget,
search_budget=SearchBudget(), sample_seed=0, admissible=None,
) -> Plan
Score prefix-only rollouts and keep a bounded frontier. expander is an iterable
of action IDs or a bounded callable expander(observation, prefix) returning
those IDs. heuristic(result) supplies the score. Each prefix reconstructs from
the root; max_cost=0 prunes observed violations. usage reports expanded prefixes
and completed depth. Depth, width, and branching factor must be positive integers.
RecedingHorizon
RecedingHorizon(session, planner, *, fallback=None)
controller.step(episode, candidates, **options) -> tuple[Plan, Transition | None]
Capture a root, call planner.plan(..., **options), and execute its first action.
Release the root afterward. If there is no proposed action, return (plan, None)
or call the supplied fallback(observation, plan) to choose an action. A fallback
must not mutate the live episode. For custom handling of cancellation or budget
exhaustion, call the planner and execution method separately.
Objectives
| Function | Returns | Interpretation |
|---|---|---|
safe_completion(result) |
float or None |
Resolved safe success as 0/1; invalid or unresolved evidence returns None. |
reward_return(result) |
float or None |
Sum of observed rewards; invalid evidence returns None. |
A custom objective accepts RolloutResult and returns a finite scalar or None.
Observed finite-horizon return does not estimate eventual task completion.
First-success and checked execution
session.execute_trajectory(episode, plan, *, on_step=None) -> tuple[Transition, ...]
Execute a complete traced, zero-cost successful witness. Verify the live root,
then check signatures before and after each action, exact reward/cost, and episode
completion. on_step(episode, transition, index, matched) is an optional observer
with a zero-based index; it is followed by another state check.
| Condition | Result |
|---|---|
| Live episode or root changed | Raise StalePlanError. |
| Missing/incomplete witness, nonzero cost, or unsuccessful result | Raise ValueError. |
| Observed state/transition diverges | Raise TrajectoryMismatch; stop before further actions. |
| Whole sequence matches | Return the executed transitions. |
Once execution begins, the root and cache are released on completion or failure. Observers should only record evidence. Divergence cannot undo an action already executed. Native signatures include camera, exposed physics/cost, policy-history, and source fields, but exclude the proposal's forked policy RNG; initial root validation still includes it.
Data types
These records are frozen dataclasses. API_VERSION is 1.
Root
Created by capture_root(); retain the session's registered instance.
| Fields / properties | Meaning |
|---|---|
owner, episode_id, runtime_id, identity |
Session, episode, runtime, and root identities. |
task_id, seed, episode_limit |
Original episode configuration. |
prefix, step, cost |
Executed action tuple, its length, and accumulated cost. |
observation |
Public planning dictionary decoded from observation_json. |
snapshot_json |
Serialized runtime validation state. |
A root is a reconstruction handle, not a portable hidden-state checkpoint.
Transition
Transition(
action: int, reward: float, cost: float,
terminated=False, truncated=False, success=None,
)
Reward and cost must be finite; cost must be nonnegative. success is bool | None;
True requires termination. Action IDs must be nonnegative integers.
RolloutResult
| Fields / properties | Type | Meaning |
|---|---|---|
request_id, root_id, candidate, sample_seed |
Identifiers | Request provenance and candidate prefix. |
status, stop_reason, valid |
str, str, bool |
Validity and stopping condition. |
actions, rewards, costs |
Tuples | Returned per-step evidence. |
root_cost, total_cost |
float |
Prefix cost; prefix plus branch costs. |
terminated, truncated |
bool |
Task termination and horizon/limit information. |
success, safe_success |
bool or None |
Task completion and zero-cost completion, when resolved. |
replay_steps, native_steps, elapsed_seconds |
Counts/time | Reconstruction work, charged actions, and elapsed seconds. |
endpoint, checks_json, error |
Evidence | Public endpoint dictionary, serialized checks, optional error. |
trace, cache_hit, schema_version |
Metadata | Root hash plus one hash per action, reuse flag, schema version. |
to_dict() returns a JSON-serializable record with decoded endpoint and checks.
Runtime errors charge the reserved action upper bound; partial traces contain
only transitions that returned successfully. Invalid results are never cached.
Plan and CandidateEvaluation
| Record | Fields / properties |
|---|---|
Plan |
root_id, optional candidate, status, evaluations, usage, method, optional witness. action is the first proposed action or None; to_dict() serializes the record. |
CandidateEvaluation |
candidate, optional mean score, optional standard_error, samples, and the underlying results. |
Inspect status and witness availability before execution. Budget exhaustion can leave a partially evaluated plan; an absent action requires an explicit caller decision or configured fallback.
Status and exceptions
| Result status / stop reason | Interpretation |
|---|---|
valid / terminal |
Runtime terminated; inspect success. |
valid / horizon or prefix_complete |
Observed prefix only; eventual success may be unresolved. |
valid / episode_limit |
Task cap exhausted; success is false. |
valid / positive_cost |
Safe success is false; task success may remain unresolved. |
root_mismatch |
Reconstruction fields differ; no candidate evidence. |
error, timeout, cancelled, budget_exhausted |
Invalid or unevaluated sample; preserve diagnostics. |
safe_success is false after positive cost; otherwise it follows resolved task
success. Plan statuses include ok, no_admissible_plan, insufficient_evidence,
cancelled, and budget_exhausted. FirstSuccess additionally returns
no_successful_trajectory or invalid_evidence.
Exceptions: StalePlanError(ValueError) signals an invalid live execution root.
TrajectoryMismatch(RuntimeError) signals divergence during checked execution.
Argument/ownership errors raise ValueError; closed/busy sessions raise RuntimeError.
Runtime extension contract
Implement Runtime in an importable factory selected by RuntimeSpec.
| Method | Returns | Required behavior |
|---|---|---|
reset(task_id, seed, episode_limit) |
None |
Reconstruct episode, policy/decoder, cost, and RNG state. |
advance(forced_action=None) |
Transition |
Run policy inference, execute one action, and feed back the actual action. |
seed_suffix(seed) |
None |
Change continuation-policy RNG streams only. |
snapshot() |
dict |
JSON-serializable validation fields. |
observe() |
dict |
Public planning information, excluding audit-only state. |
end_episode() |
None |
Release episode resources. |
close() |
None |
Release the runtime. |
snapshot() and observe() must not advance physics, run policy inference, or
consume randomness. Optional execution_signature() -> dict enables tracing and
checked execution. Optional candidate_preview() -> dict supports candidate
inspection in a disposable worker and may perform policy inference there.
Custom runtimes are responsible for reset fidelity and seeded behavior. Matching
declared fields establishes agreement on those fields. Native
diagnostics_dir retains per-episode camera checks, renderer metadata, and Unity
logs after shutdown; without it, diagnostics are temporary. A camera mismatch
remains invalid and is never repaired by silently re-rendering or retrying.
See native reproduction and validation scope.