Execution Lifecycle¶
Source
Maintained in hylo-harness: docs/execution-lifecycle.md.
Implementation: hylo-core
hylo/core/kernel.py[Validated in prototype]
1. Hierarchy¶
Experiment ──▶ Evaluation Run (benchmark × split) ──▶ Episode (TaskInstance) ──▶ Step
│ │ │ │
config digest run_id, manifest episode_id, seed seq, sim_time
2. Driven mode (default): the Kernel owns the loop¶
sequenceDiagram
autonumber
participant R as EvaluationRunner
participant K as Kernel (EpisodeRunner)
participant A as Agent
participant X as ChunkExecutor
participant S as SafetyShield
participant E as Environment
participant T as Trace (via EventBus)
R->>K: run(TaskInstance)
Note over K: compat check already done at bind time (failure = IncompatibleComponents, run does not start)
K->>T: episode.start {task digest, seed, split, agent components, guards}
K->>E: reset(task)
E-->>K: Observation (includes actual instruction)
K->>T: observation
K->>A: reset(EpisodeContext{emit, seed, env_spec})
loop until terminated / truncated / max_steps
alt executor is empty
K->>A: act(obs)
A-->>T: agent.decision / memory.retrieval (via ctx.emit)
A-->>K: ActionChunk
K->>K: measure latency, exceeds act_timeout_s → AgentTimeout
K->>X: load(chunk)
K->>T: agent.chunk
end
K->>X: next()
K->>S: review(action)
S-->>K: approved (+ modified/reasons) or SafetyAbort
K->>T: safety.review, action
opt modified
K->>X: clear() — plan is invalidated, replan at next step
end
K->>E: step(approved)
E-->>K: StepResult(obs, reward, terminated, truncated, success, info)
K->>T: step.result, observation, privileged.state (Trace only)
K->>A: on_step(result)
end
K->>T: episode.end {status, success, steps, reason, safety_interventions}
K->>A: on_episode_end(outcome)
A-->>T: memory.write (via ctx.emit)
2.1 Status and errors¶
| Situation | episode status | Scored? | Prototype behavior |
|---|---|---|---|
| Task judged successful | success |
1 | ✓ |
| Steps exhausted or truncated | failure |
0 | ✓ |
| Agent raises an exception / times out | error |
Abstain | ✓ |
| Env reset/step exception | infra_invalid |
Abstain | ✓ |
| Simulation divergence (MuJoCo BADQPOS/QVEL/QACC) | infra_invalid |
Abstain | ✓ |
| Guard rejection (NaN, rotation out of bounds, any modification in real-robot mode) | safety_abort |
0 + safety event | ✓ |
| Incompatible at bind time | run does not start | — | ✓ |
2.2 Chunk execution¶
ActionChunk(actions[T], execution_steps): after executing execution_steps steps, act is called again. If SafetyShield modifies any action → the queue is cleared to force a replan (the plan has diverged from reality).
Timing (RFC-0005, part of what an experiment measures) selects how policy time relates to robot time, in the one
loop that evaluation and deployment share:
- sync (default): the above; the world waits for every act.
- realtime: a chunk is requested when at most refill_steps actions remain (earlier when recent latency needs
it); a chunk computed at step s and merged at step t has its first t - s actions dropped; an empty queue
executes a hold action; a watchdog aborts after max_hold_steps holds. Clock virtual (deterministic: latency
measured or fixed by latency_s, the steps it covers run before the merge) or wall (the policy in a worker
thread, the loop paced at the control rate). The workcell (hylo-serve) runs this mode; a benchmark protocol can
score it (libero-10-heldout-realtime).
Temporal ensembling is an agent (temporal_ensemble), not a loop variant.
2.3 Clock and timeouts¶
actlatency is measured after the fact; exceedingact_timeout_syieldsAgentTimeout(soft timeout).- v0.2: hard timeouts for remote policies are guaranteed by client deadlines; hard timeouts for in-process policies require thread/subprocess isolation.
- Real robot: when
EnvCapabilities.self_paced=True, the Environment is responsible for wall-clock pacing and the Kernel does not sleep; the Reasoner must be asynchronous (see §4).
3. Served mode: an external harness owns the loop¶
sequenceDiagram
participant B as vla-eval Benchmark container
participant V as hylo-bridge-vlaeval (ModelServer)
participant A as Agent
participant S as SafetyShield
participant T as Trace
B->>V: EPISODE_START {task}
V->>A: reset(ctx)
V->>T: episode.start (mode=served)
loop
B->>V: OBSERVATION
V->>T: observation
V->>A: act(obs)
A-->>V: ActionChunk
V->>S: review(each action)
V->>T: agent.chunk, safety.review, action
V-->>B: ACTION
end
B->>V: EPISODE_END {success}
V->>T: step.result(final), episode.end
Differences: no per-step step.result information (only the final success), no privileged state, no snapshot. Therefore Served mode runs cannot be used for shadow replay, nor as the sole evidence for the RSI validation split. The EvalLog records mode=served.
4. Dual-rate Agent (v0.2 design)¶
sequenceDiagram
participant K as Kernel (control rate, e.g. 20 Hz)
participant P as Policy
participant Q as Reasoner (async, 0.1–2 Hz)
K->>P: act(obs) every chunk
K-->>Q: trigger(event) [episode_start, subgoal_reached, failure_signal, periodic]
Q-->>P: update subgoal / instruction (atomic swap)
Note over P: Policy always uses the most recently committed subgoal; the Reasoner does not block the control loop
The prototype implements a synchronous version (the Reasoner is triggered and executed inside act), which is sufficient to validate the interface and Trace semantics.
5. Passive observers and online critics¶
Non-critical Event Bus subscribers (real-time visualization, online critics) are detached on failure without affecting the episode [Validated in prototype]. Online critics (Zetta-style TemporalCritic) are implemented in v0.3 as Agent components, not Kernel components: what they output is a "suggested recovery", and the final action still passes through SafetyShield.