Run
connect() and act()
connect() holds one connection open for the life of the loop; act()
drives the arm until it finishes, times out, or you stop it.
import sequence_ai with sequence_ai.connect(model="my-pi05") as policy: out = policy.act( "pick up the cup", observe=robot.read_observation, # cameras + joints -> Observation act=robot.apply_action, # execute one step until=robot.cup_in_gripper, # optional: return True to finish early max_seconds=30, # required — this drives hardware ) print(out.reason) # completed | until | timeout | interrupted | validate policy.interrupt() # thread-safe, from anywhere
act(instruction, observe, act, *, max_seconds, until=None) pins the instruction
and requires a time bound. It overwrites the observation’s instruction on every call.
observe returns the current observation; act executes one step.
One Policy per control loop: it holds one action buffer, and a second thread on
the same handle raises. Drive many robots by giving each its own connect().
model= names a deployment of your own. To start from one
of our templates, copy it and deploy it (seq init pi05-droid my-pi05, then
seq deploy my-pi05/policy.py); its sessions run on your own workers and are billed to your account.
Action chunks
One call returns a block of future actions (covers_seconds of motion), so the
loop needs no round trip per step. The next chunk must arrive before the current one finishes,
so the client fetches it while the current one is still playing:
out = policy.run(observe=..., act=..., max_actions=250, on_underrun=robot.hold_position) print(out) # RunOutcome(250 actions over 32 chunks in 16.8s, completed) out.underruns # times the buffer ran dry before the next chunk arrived out.underrun_s # seconds the arm spent with no command out.max_seam_jump # largest per-dimension step across a chunk boundary
A prefetched chunk is computed from an observation taken before the previous one
finished, so that overlap is open loop. prefetch=False restores strictly
closed-loop behaviour, stall included.
Each chunk carries action_space (e.g. joint_delta or
ee_absolute); reading one as another is silent. smooth_seam=N applies
only to an absolute action space and never to the first chunk of a run; a delta chunk
is passed through untouched.
policy.interrupt() is thread-safe and stops between actions, not at a chunk
boundary. A stop unwinds through your hold callback and is not raised;
RunOutcome.reason says which ending happened:
reason | meaning |
|---|---|
completed | ran out of actions to play |
until | your until() returned True — you judged the subtask done |
timeout | max_seconds elapsed |
interrupted | someone called interrupt() |
validate | a validate callback refused an action |
raised:<Class> | your callback or the network raised |
validate and hold callbacks.
Episodes and streaming
A stateful policy declares streaming=True and implements @seq.on_episode
to free an episode’s state. Each act() carries an episode_id, and the same
episode always lands on the same replica. run() ends its episode when it returns, even
on an exception; an episode left idle for 5 minutes is ended by the worker. A policy that only needs
the last N frames needs no state: declare observation=seq.ObservationContract(history=N)
and the robot sends them (frames_per_chunk is retired).
Warming happens at connect(), not mid-loop. Wait it out once before the arm needs
to move; the wait starts the worker if it is cold, and is not billed:
policy.wait_until_ready() # starts a cold worker and blocks until it is up; not billed while running: policy.next_action(observe()) # warm by construction
run() and act() wait out warming automatically, but only before the
first action; warming met mid-run is raised and hold fires. ready()
is the non-blocking form, returning (ready, eta_seconds).
Field data
Keep what your robots saw and did, to fine-tune on. With capture on, the worker keeps a share of episodes,
each one whole: every observation, the chunk it returned and the inference parameters. Camera frames are
kept only with --images. Label an episode from the robot, then export a LeRobot dataset.
seq policy capture my-pi05 --rate 0.1 --images --retention-days 30 # one episode in ten, with frames seq policy capture my-pi05 # what is being kept seq policy data export my-pi05 --out ./field --format lerobot # a LeRobot v2.1 dataset (or --format jsonl) seq policy capture my-pi05 --off # stop; what was kept stays for its retention # on the robot: label the episode the last act() / run() drove out = policy.act("pick up the cup", observe=robot.read, act=robot.apply, max_seconds=30) policy.label(success=robot.cup_in_gripper(), notes="") # episode_id= labels another
The export carries each label as its episode’s success. Captured data is billed as storage
(capture:<name> on your usage), deleted after its retention (30 days by default), and
deleted with the deployment.