Docs

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.

first_loop.pypython
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:

run — prefetch and the counters it returnspython
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:

reasonmeaning
completedran out of actions to play
untilyour until() returned True — you judged the subtask done
timeoutmax_seconds elapsed
interruptedsomeone called interrupt()
validatea validate callback refused an action
raised:<Class>your callback or the network raised
An action returned by a policy is model output, not a safe robot command. The client checks no joint limit, reachability, collision or velocity. Put a bounds check, a watchdog and an e-stop between it and your motors — attach them at the 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:

connect first, then drivepython
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.

capture, label, exportshell + python
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.