借助 Strands Agents、LeRobot 和 Hugging Face Storage Buckets 在一处完成记录、训练与部署
英文标题:Record, train, and deploy from one place with Strands Agents, LeRobot, and Hugging Face Storage Buckets
本文介绍 Strands Robots 的流式数据闭环:Agent 可记录机器人示范、将 LeRobotDataset 同步至 Hugging Face Storage Bucket、直接从 Hub 流式训练,并将模型部署回硬件。Storage Bucket 是由 Xet 支持的可变、非版本化对象存储,可通过内容定义分块实现字节级去重,避免持续采集和训练时重复传输大量数据。文章指出,LeRobot 数据格式已被 Hub 上超过 8,000 个发布者的 9 万多个数据集和模型采用,因此 Strands Robots 采集的数据无需转换即可被兼容工具读取。对开发者而言,该方案将采集、训练和部署纳入同一个 `Robot()` 后端与 Agent 工作流,并支持从笔记本模拟环境扩展到 GPU 集群和实体机器人。
11.8 KB
24.6 KB
4.9 KB
7.3 KB
3.7 KB
325.6 KB
105.8 KB
181.6 KB
1.4 KB
5.9 KB
3.5 KB
58.0 KB
103.6 KB
219.5 KB
中文
借助 Strands Agents、LeRobot 和 Hugging Face Storage Buckets 在一处完成记录、训练与部署
本指南介绍 Strands Robots 中的流式数据闭环:一个 Agent 循环可记录机器人示范,通过直接从 Hub 读取数据对其训练,并将策略部署回硬件;全程数据集均保持为相同的磁盘 LeRobot 格式。
你已经有一个能记录示范并将其推送到 Hugging Face Hub 的 Agent。现在,你希望持续运行这一闭环:全天收集回合,在不断增长的数据集上训练策略,将其部署出去,再拉回下一批数据以继续改进。这个闭环运行一次时,每个组件都能正常工作;但如果每天都运行,你就会开始为相同的字节传输反复付费。上传的录制数据会不断增长,每次训练开始前都会将整个数据集复制到 GPU,而每个新检查点都会被传出,同时下一批录制数据又被拉回。
本系列的第一篇文章介绍了 Strands Robots:这是 AWS 推出的开源 SDK(Apache 2.0),将机器人抽象、仿真和 LeRobot 技术栈作为可组合为单个 Strands Agent 的 AgentTools 提供。该文涵盖了 Robot() 工厂、在仿真中记录示范、运行策略,以及将相同 Agent 代码部署到实体 SO-101。该工厂会依据机器人、类人机器人、移动底座和机械手的注册表解析名称,因此本文始终使用的 SO-100 只是众多受支持具身形态中的一种。机器人目录列出了该工厂支持的所有机器人。LeRobot 的数据集格式目前已被 Hub 上超过 8,000 个发布者发布的 90,000 多个数据集和模型采用(LeRobot Project Pulse)。Strands Robots 的一次录制也只是其中之一,因此,任何为读取 LeRobot 数据而构建的工具都可直接读取它,无需转换。如果你刚开始接触 Strands Robots,请先阅读那篇文章;本文假设你已完成其中的设置。
上一篇文章沿一个方向追踪了 Agent 闭环,即从 Hub 数据集到实体机器人。本文则沿相反方向追踪数据:从最初记录的第一帧,经由 Hugging Face Storage Buckets 回到已部署的策略。Storage Bucket 是一种可变、非版本化、由 Xet 支持的对象存储仓库类型,于 2026 年 3 月发布。Bucket 与你的数据集仓库位于同一个 hf:// 命名空间中,并使用你已有的 hf CLI,因此它成为从录制数据到训练数据之间承载数据的工作层。
必须有人决定保留哪些回合、何时场景漂移已足够明显而需要重新录制、今天收集的数据是否足以用于训练,以及哪个检查点应替换机械臂上现有的检查点。在一次采集活动期间,每项决策都会出现数十次,并且每次都需要先查看返回的数据,再发出下一条命令。这正是 Agent 的工作。本文将带你了解单个 Agent 内的数据闭环:将示范录制到 Storage Bucket,以便每次同步只上传发生变化的字节;直接从 Hub 流式读取数据集进行训练,而非下载它;并通过一次关键字参数变更将检查点部署回硬件。本文配套的可运行示例位于 examples/notebooks/05_streaming_data_loop.ipynb。
你将构建什么
第一篇文章记录了一个数据集并将其推送到 Hub,而你将在这里构建的 Agent 会根据自然语言提示记录一个 LeRobotDataset,将其同步到 Storage Bucket,并以逐帧方式将同一数据集流式读回,在运行时解码摄像头视频,无需本地副本。你会在写入数据的同一进程中读取它:记录数据集的同一个 Strands Robots Robot() 会流式读取它。之后,训练得到的检查点只需修改一个关键字参数即可部署到同一个 Robot(),而它在硬件上记录的示范数据也会回到同一个 bucket。

图 1。 四个阶段共享一个后端。 Robot("so100") 通过共享的 DatasetRecorder 记录 LeRobotDataset; sync_dataset_to_bucket(...) 将其同步到 Storage Bucket; stream_dataset(...) 通过 Hub 将其读回,无需完整下载;训练后的检查点则以 mode="real" 部署到同一个 Robot 。磁盘上的格式始终与 LeRobot 写入时完全一致。
由于同一个 Robot() 既能记录数据集,也能将其读回,数据采集和训练便成为同一个对象、同一个后端上的两种方法。Agent 决定运行一个回合并调用一个工具;随后 rollout 会以机器人的控制频率持续执行,直至该回合结束,且每一个动作都由训练后的策略产生。整个闭环只需几行代码:
from strands import Agent
from strands_robots import Robot
sim = Robot("so100")
agent = Agent(tools=[sim])
agent("Record a pick-the-cube demo and sync it to my-org/robot-fave.")
for batch in sim.stream_dataset("my-org/robot-fave/cube_pick", repo_type="bucket").dataloader(batch_size=64):
...
下面将逐步说明这个闭环内部实际发生的过程。
前置条件
最小配置(默认仿真路径)
- Python 3.12+,Linux 或 macOS(MuJoCo 后端支持 Apple Silicon)。
- 供 Agent 推理使用的、兼容 Strands 的模型提供方。可以是使用 AWS 凭证的 Amazon Bedrock、Anthropic API、OpenAI,或本地运行的 Ollama。
- 含数据集扩展的 Strands Robots:
uv pip install -U "strands-robots[sim-mujoco,lerobot]>=0.5.1"。lerobot扩展会引入 LeRobot(>=0.6.1)、datasets、av和torchcodec,因此录制和视频解码无需进一步设置即可工作。请参阅安装指南。
仅凭这三项即可在笔记本电脑上运行本文的所有阶段。能够运行的是闭环,而不是可用的工作策略:默认路径使用模拟策略,它会记录有效的数据集,但数据本身没有实际用途。
高级配置(buckets、硬件与真实策略)
- Hugging Face 账户,以及具有写入权限的 token;还需要用于创建 bucket 和同步数据集的
hfCLI:pip install -U "huggingface-hub>=1.6.0,<2.0.0",然后执行hf auth login。 - 硬件路径需要一对 SO-101 从臂和主臂,或任何其他 LeRobot 支持的机器人,并在
~/.cache/huggingface/lerobot/calibration/下提供校准文件。 - 本地视觉-语言-动作(VLA)推理需要 NVIDIA GPU。大规模训练则需要能够从 Hub 读取数据的 GPU 集群。
- 运行训练步骤需安装:
uv pip install "lerobot[training]"。录制和流式读取不需要它。如果跳过该安装,trainer.train()将返回错误结果,而不是检查点。故障排除指南说明了该错误及修复所需的安装命令。
第 1 步:将示范记录到 bucket 中
你会在一天中录制新的回合,每个回合都是摄像头帧与关节状态-动作遥测数据的连续序列。LeRobot 会将其写为少量大型文件,并随着录制不断增长。若将它们推送到版本化数据集仓库,每次追加都会生成一次提交,且每个修订版本都会被保留。数据采集需要相反的机制:一个可写入字节并原地覆盖的地方。这正是 Storage Bucket:它位于你的 Hugging Face 工作区中,并使用你已有的权限。你无需配置身份与访问管理(IAM)角色、跨域资源共享(CORS)规则,也无需维护上传服务。
你的 Agent 会以与 LeRobot 在硬件上写入时相同的格式记录 LeRobotDataset。记录回合后,再将完成的数据集同步到 bucket。提示中要求使用模拟策略,它是一个无需训练模型即可生成关节动作的替代方案,因此你可以在尚无检查点可运行前先执行整个闭环:
from strands import Agent
from strands_robots import Robot, sync_dataset_to_bucket
sim = Robot("so100")
agent = Agent(tools=[sim])
agent(
"Create a world with the so100 robot, add a red cube and a front camera, "
"start recording (repo_id='local/cube_pick', root='/tmp/cube_pick', fps=30, "
"overwrite=True, task='pick up the red cube'), run the mock policy for "
"60 steps, then stop recording."
)
sync_dataset_to_bucket("/tmp/cube_pick", "my-org/robot-fave")
同步会写入 hf://buckets/{bucket}/{run_id},其中 run_id 默认使用数据集目录名称。第 3 步中的流式读取也会指定该运行名称:ID 的前两段是 bucket,之后的所有内容则是其中的路径。
sync_dataset_to_bucket(root, bucket, run_id=...) 会验证数据集并通过 hf CLI 执行同步,与录制生命周期解耦。如果你直接驱动一个处于打开状态的 recorder,也可使用 DatasetRecorder.sync_to_bucket(bucket, run_id=...) 执行相同操作;而 stop_recording(bucket=...) 则会在停止活跃录制时同步。Bucket 是全天持续写入的工作层;对于版本化、已发布的产物,你仍然需要调用 push_to_hub()。两者保存的是相同格式。
这个回合在结构上是完整的,但其中的动作只是占位符,因此并不是你希望用于训练的数据。对于真实抓取,请使用 create_policy("<hf_repo>") 替换为真实策略;提示、格式和 bucket 同步方式都保持不变。
在硬件上录制
若要在实体 SO-101 上录制,LeRobot 的 record CLI 可负责主从臂启动:
lerobot-record \
--robot.type=so101_follower --robot.id=my_follower \
--teleop.type=so101_leader --teleop.id=my_leader \
--dataset.repo_id=my_user/cube_picking \
--dataset.single_task='Pick up the red cube'
该数据集会以与仿真录制相同的格式写入磁盘,因此可通过相同的同步调用将其传送到 bucket:sync_dataset_to_bucket("./recordings", "my-org/robot-fave", run_id="run-021")(或它所封装的 hf sync ./recordings hf://buckets/my-org/robot-fave/run-021 CLI)。采集运行会追加到同一个位置,而你发布的仓库中只会包含你选择发布的版本。
第 2 步:通过字节级去重存储
现在数据集已位于 bucket 中,接下来的问题是下一次同步需要多少成本。让两台固定摄像头对准机械臂,在同一张桌子上连续工作八小时,所记录的大部分内容都是你已有的像素:相同的照明、相同的机身、相同的背景,跨越数千个回合。在版本化仓库中,情况会更糟,因为修改多 GB 视频分片中的一帧,就会重新上传整个文件。
Buckets 由 Xet 支持,它通过内容定义分块在字节级别对上传内容进行去重。分块边界由内容决定,因此插入少量字节只会改变其所在的分块,而不会导致之后的每个边界都发生偏移。根据 Hugging Face 自己的测量数据(HF Storage),内容定义分块可将整个 Hub 上每次上传的数据传输量降低约四倍;而在 Enterprise 计划中,计费基于去重后的存储占用。他们的 bucket 基准测试展示了这对单个文件意味着什么。从一次 500 MB 上传开始,修改 1% 的字节并重新上传时,只传输了 5.5 MB;修改 5% 时传输了 27.5 MB;修改 10% 时传输了 55 MB。没有分块级去重时,覆盖一个对象意味着无论字节是否变化,都需要再次发送全部内容。
节省多少取决于文件布局,而 Strands Robots recorder 使用的是 LeRobot 的布局。回合会写入 Parquet 分片(data/chunk-000/file-000.parquet)和每个摄像头对应的 MP4 分片(videos/observation.images.front/chunk-000/file-000.mp4),只有当前文件写满后才会滚动创建新文件;LeRobot 的默认大小分别为数据 Parquet 100 MB、视频 MP4 200 MB。因此,在一天录制结束后进行同步时,上传的是新的末尾分片,以及那个仍在增长的部分填充分片,而不是整个数据集。明天再次同步同一个 bucket 时,Xet 会处理去重。
图 2。 一次同步只上传发生变化的内容。全新数据集的首次同步会上传全部分块;记录更多回合后,Xet 的内容定义分块机制意味着下一次同步只会上传新分块,并跳过已存储的分块。
第 3 步:通过 Hub 流式读取进行训练
English / 原文
Record, train, and deploy from one place with Strands Agents, LeRobot, and Hugging Face Storage Buckets
A walkthrough of the streaming data loop in Strands Robots, one agent loop that records robot demonstrations, trains on them by reading straight from the Hub, and deploys the policy back to hardware, with the dataset in the same on-disk LeRobot format the whole way through.
You have an agent that can already record a demonstration and push it to the Hugging Face Hub. Now you want to run that loop continuously: collect episodes through the day, train a policy on the growing dataset, deploy it, and pull the next batch back to improve it. Run that loop once and every piece works. Run it every day and you start paying for the same byte transfers over and over. The recordings you upload keep growing, each training run copies the whole dataset to the GPUs before it starts, and every new checkpoint ships out while the next batch of recordings comes back.
The first post in this series introduced Strands Robots, an open source SDK from AWS (Apache 2.0) that exposes robot abstractions, simulation, and the LeRobot stack as AgentTools you compose into a single Strands agent. It covered the Robot() factory, recording a demonstration in simulation, running a policy, and deploying the same agent code to a physical SO-101. That factory resolves a name against a registry of arms, humanoids, mobile bases, and hands, so the SO-100 used throughout this post is one of many supported embodiments. The robot catalog lists every robot the factory knows about. LeRobot's dataset format is already used by over 90,000 datasets and models on the Hub from more than 8,000 publishers (LeRobot Project Pulse). A Strands Robots recording is one more of them, so anything built to read LeRobot data can read it without conversion. If you are new to Strands Robots, start there; this post assumes that setup.
That post followed the agent loop in one direction, from a Hub dataset to a physical robot. This one follows the data the other way, from the first recorded frame back to the deployed policy, over Hugging Face Storage Buckets - a mutable, non-versioned, Xet-backed object-storage repository type announced in March 2026. A bucket sits beside your dataset repositories in the same hf:// namespace and uses the hf CLI you already have, so it becomes the working layer that holds your data between the day you record it and the day you train on it.
Someone has to decide which episodes to keep, when the scene has drifted far enough to re-record, whether today's batch is enough to train on, and which checkpoint replaces the one on the arm. Each of those decisions comes up dozens of times over a collection campaign, and each one needs a look at what came back before the next command goes out. That is the work an agent is for. This post walks you through the data loop inside a single agent: record a demonstration into a Storage Bucket, store it so that each sync uploads only the bytes that changed, train by streaming the dataset straight from the Hub instead of downloading it, and deploy the checkpoint back to hardware with one keyword argument change. The runnable companion to this post lives at examples/notebooks/05_streaming_data_loop.ipynb.
What you'll build
Where the first post recorded a dataset and pushed it to the Hub, the agent you build here records a LeRobotDataset from a natural-language prompt, syncs it into a Storage Bucket, and streams that same dataset back frame by frame, decoding camera video on the fly, with no local copy. You read it back in the same process that wrote it: the same Strands Robots Robot() that recorded the dataset streams it. Your trained checkpoint then deploys to that same Robot() with one keyword argument change, and the demonstrations it records on hardware return to the same bucket.

Figure 1. The four stages share one backend. Robot("so100") records a LeRobotDataset through the shared DatasetRecorder; sync_dataset_to_bucket(...) syncs it into a Storage Bucket; stream_dataset(...) reads it back over the Hub with no full download; and the trained checkpoint deploys to the same Robot with mode="real". The on-disk format stays exactly as LeRobot wrote it.
Because one Robot() both records a dataset and reads it back, collecting data and training on it are two methods on one object over one backend. The agent decides to run an episode and invokes one tool; the rollout then proceeds at the robot's control frequency until the episode ends, with the trained policy producing every action. The whole loop, in a handful of lines:
from strands import Agent
from strands_robots import Robot
sim = Robot("so100")
agent = Agent(tools=[sim])
agent("Record a pick-the-cube demo and sync it to my-org/robot-fave.")
for batch in sim.stream_dataset("my-org/robot-fave/cube_pick", repo_type="bucket").dataloader(batch_size=64):
...
What follows is what's actually happening inside that loop, step by step.
Prerequisites
Minimal (default simulation path)
- Python 3.12+, on Linux or macOS (Apple Silicon supported for the MuJoCo backend).
- A Strands-compatible model provider for the agent's reasoning. Amazon Bedrock with AWS credentials, the Anthropic API, OpenAI, or Ollama running locally.
- Strands Robots with the dataset extras:
uv pip install -U "strands-robots[sim-mujoco,lerobot]>=0.5.1". Thelerobotextra pulls in LeRobot (>=0.6.1),datasets,av, andtorchcodec, so recording and video decode both work without further setup. Refer to installation guide.
That's it. Every stage in this post runs on a laptop with these three. What runs is the loop, not a working policy: the default path uses a mock policy, which records a valid dataset but not a useful one.
Advanced (buckets, hardware, real policies)
- A Hugging Face account and a token with write permission, plus the
hfCLI for creating buckets and syncing datasets:pip install -U "huggingface-hub>=1.6.0,<2.0.0", thenhf auth login. - For the hardware path: an SO-101 follower and leader pair, or any other LeRobot-supported robot, with calibration files under
~/.cache/huggingface/lerobot/calibration/. - For local vision-language-action (VLA) inference: an NVIDIA GPU. For training at scale, a GPU cluster reading from the Hub.
- To run the training step:
uv pip install "lerobot[training]". Recording and streaming do not need it. If you skip it,trainer.train()returns an error result rather than a checkpoint. The troubleshooting guide names that error and the install that fixes it.
Step 1 - Record a demonstration into a bucket
You record new episodes through the day, each a continuous run of camera frames and joint state-action telemetry. LeRobot writes that as a small set of large files that grow as you record. Push them into a versioned dataset repository and every append becomes a commit, and every revision is retained. Collection wants the reverse: somewhere to write bytes and overwrite them in place. That is a Storage Bucket, which lives inside your Hugging Face workspace and uses the permissions you already have. There are no identity and access management (IAM) roles to configure, no cross-origin resource sharing (CORS) rules, and no upload service to maintain.
Your agent records a LeRobotDataset in the same format LeRobot writes on hardware. Record the episode, then sync the finished dataset into a bucket. The prompt asks for the mock policy, a stand-in that produces joint actions without a trained model, so you can run the whole loop before you have a checkpoint to run:
from strands import Agent
from strands_robots import Robot, sync_dataset_to_bucket
sim = Robot("so100")
agent = Agent(tools=[sim])
agent(
"Create a world with the so100 robot, add a red cube and a front camera, "
"start recording (repo_id='local/cube_pick', root='/tmp/cube_pick', fps=30, "
"overwrite=True, task='pick up the red cube'), run the mock policy for "
"60 steps, then stop recording."
)
sync_dataset_to_bucket("/tmp/cube_pick", "my-org/robot-fave")
The sync writes to hf://buckets/{bucket}/{run_id}, where run_id defaults to the dataset directory name. The streaming read in Step 3 names the run too: the first two segments of the id are the bucket, and everything after them is the path inside it.
sync_dataset_to_bucket(root, bucket, run_id=...) validates the dataset and syncs it through the hf CLI, decoupled from the recording lifecycle. The same capability is on DatasetRecorder.sync_to_bucket(bucket, run_id=...) if you drive an open recorder directly, and stop_recording(bucket=...) syncs at the moment you stop an active recording. The bucket is the working layer you write to through the day; for the versioned, published artifact you still call push_to_hub(). Both hold the same format.
The episode is structurally complete, but the actions are placeholders, so it is not training data you would want. Swap in a real policy with create_policy("<hf_repo>") for actual grasping; the prompt, the format, and the bucket sync stay identical.
Recording on hardware
To record on a physical SO-101, LeRobot's record CLI handles the leader-follower bring-up:
lerobot-record \
--robot.type=so101_follower --robot.id=my_follower \
--teleop.type=so101_leader --teleop.id=my_leader \
--dataset.repo_id=my_user/cube_picking \
--dataset.single_task='Pick up the red cube'
The dataset lands on disk in the same format as the simulation recording, so the same sync call takes it to a bucket: sync_dataset_to_bucket("./recordings", "my-org/robot-fave", run_id="run-021") (or the hf sync ./recordings hf://buckets/my-org/robot-fave/run-021 CLI it wraps). Collection runs append into one place, and your published repositories only get the versions you choose to publish.
Step 2 - Store with byte-level deduplication
Now that a dataset is in the bucket, the question is what the next sync costs you. Point two fixed cameras at an arm clearing the same table for eight hours and most of what you record is pixels you already have: the same lighting, the same chassis, the same background, across thousands of episodes. On a versioned repository it gets worse, because changing one frame in a multi-gigabyte video shard re-uploads the whole file.
Buckets are backed by Xet, which deduplicates your uploads at the byte level using content-defined chunking. Chunk boundaries follow the content, so inserting a few bytes changes only the chunk it lands in instead of shifting every boundary after it. In Hugging Face's own measurements (HF Storage), content-defined chunking reduces data transferred per upload by about four times across the Hub, and on Enterprise plans billing is on the deduplicated footprint. Their bucket benchmarks show what that looks like on a single file. Starting from a 500 MB upload, changing 1% of the bytes and re-uploading moved 5.5 MB, changing 5% moved 27.5 MB, and changing 10% moved 55 MB. Without chunk-level deduplication, overwriting an object means sending all of its bytes again, whether or not they changed.
How much that saves you depends on the file layout, and the Strands Robots recorder uses LeRobot's. Episodes go into Parquet shards (data/chunk-000/file-000.parquet) and per-camera MP4 shards (videos/observation.images.front/chunk-000/file-000.mp4), rolling to a new file only when the current one fills, at LeRobot's defaults of 100 MB for data Parquet and 200 MB for video MP4. So a sync after a day of recording uploads the new trailing shards plus the one partially-filled shard that grew, rather than the whole dataset. Sync the same bucket again tomorrow and Xet handles the deduplication.
Figure 2. A sync uploads only what changed. The first sync of a fresh dataset uploads every chunk; after recording more episodes, Xet's content-defined chunking means the next sync uploads only the new chunks and skips the ones already stored.
Step 3 - Train by streaming from the Hub
To train, you point GPUs at your dataset. Download it first and those GPUs sit idle until hundreds of gigabytes finish copying. Streaming straight from the Hub works here because of the shard layout from Step 2: a batch becomes a few byte-range reads over large shards rather than thousands of small fetches. LeRobot's StreamingLeRobotDataset turns that into a drop-in torch iterable, and Strands Robots exposes it through stream_dataset():

Figure 3. Stream, don't download. The download path copies the whole dataset to local disk first, so the GPU waits; stream_dataset() reads batches straight from the bucket with nothing on local disk, so the GPU trains from the first batch.
reader = sim.stream_dataset("my-org/robot-fave/cube_pick", repo_type="bucket",
shuffle=False, max_num_shards=1, buffer_size=1,
)
print(reader.num_episodes, reader.num_frames, reader.fps)
for frame in reader:
frame["observation.images.front"]
frame["observation.state"]
frame["action"]
break
Nothing lands on local disk except the small meta/ folder of schema, statistics, and episode index. Camera frames are decoded from the remote MP4 shards as you iterate; state and action come from the Parquet shards. That loop reads one frame at a time, which suits inspecting an episode. To train, pass the reader to a DataLoader and iterate batches instead. The streaming dataset shuffles internally through a bounded reservoir buffer, so video decoding parallelizes across worker processes, and the training step itself is the ordinary PyTorch one:
for batch in reader.dataloader(batch_size=64, num_workers=4):
loss, _ = policy(batch)
loss.backward()
If you would rather not write the loop at all, LeRobot's own trainer reads through the same engine, so the dataset your agent collected trains without a line of new code. It takes a bucket through the same keyword argument the in-process reader uses:
lerobot-train --policy.type=act \
--dataset.repo_id=my-org/robot-fave/cube_pick \
--dataset.repo_type=bucket \
--dataset.streaming=true \
--num_workers=4
Buckets are streaming-only, so --dataset.repo_type=bucket requires --dataset.streaming=true and the config rejects the combination otherwise. Reach for stream_dataset() when you want the loop in your own process: validating an episode, replaying it in simulation, or feeding a custom evaluation loop. For proprioceptive-only streaming, drop_videos=True skips video decode entirely, which is what makes this work on an edge device with no torchcodec wheel. The recording and datasets guide documents that argument along with the delta_timestamps map it requires.
Provider names are shared between running a policy and training one. create_trainer("lerobot_local") returns a Trainer that works like create_policy(), and a TrainSpec describes the run; the record-train-deploy loop then closes in a few lines:
import os
os.environ["STRANDS_TRUST_REMOTE_CODE"] = "1"
from strands_robots import create_policy
from strands_robots.training import TrainSpec, create_trainer
trainer = create_trainer("lerobot_local", device="cuda")
spec = TrainSpec(dataset_root="/tmp/cube_pick", output_dir="/tmp/cube_pick_ft",
base_model="", steps=500, extra={"policy_type": "act"})
result = trainer.train(spec)
policy = create_policy(result.checkpoint_dir)
On a single NVIDIA L4 (g6.4xlarge), 500 optimizer steps of ACT (51.6M parameters, effective batch size 8) over a 120-frame episode completed in 133 seconds and wrote a checkpoint that create_policy() loads back through the same entry point used to run any other policy. Training time scales with dataset size, batch size, and step count, so treat this as one measured configuration rather than a benchmark. The "groot" and "cosmos3" providers target the same TrainSpec and Trainer lifecycle, so the surrounding loop is unchanged; each one validates its own required fields first, so a GR00T run needs a base_model and an embodiment tag, and a Cosmos 3 run needs a base_model and an SFT recipe. Call trainer.validate(spec) before train() and it returns the exact list of what a given backend is missing.
Hugging Face's pre-warming caches bucket data at edge locations near the cloud and region where your jobs run, so your cluster reads locally and the dataloader stays ahead of the GPU. In Hugging Face's own bucket benchmarks, a warm content delivery network (CDN) read hit about 1,086 MB/s on a 10 GB payload against 780 MB/s cold, and roughly 1,124 MB/s warm at 100 GB, measured on an m5dn.24xlarge in us-east-1. The full comparison against plain object storage, upload as well as download, is on that dashboard. Choosing where that data lives is a Storage Regions setting on Team and Enterprise plans, as of this writing US and EU, with Asia-Pacific and Gulf Cooperation Council (GCC) regions announced as coming; outside those plans repositories are stored in the US.
On macOS, import strands_robots puts Homebrew's ffmpeg on the loader path for you, so torchcodec decodes streamed video without extra setup.
Step 4 - Deploy the policy and return data to the loop
In this step you take the checkpoint you just trained, run it on a physical robot, and record the next round of demonstrations with it. This is the same agent code from the first post, with one keyword argument changed to mode="real":
robot = Robot("so100", mode="real", port="/dev/ttyACM0",
cameras={"front": {"type": "opencv", "index_or_path": "/dev/video0", "fps": 30}})
agent = Agent(tools=[robot])
agent("Pick up the red cube.")
The checkpoint runs against the physical arm, and the demonstrations that arm records are saved to disk in the same LeRobot format you started with, ready to sync back to the bucket for the next training run.
If your data already lives on Amazon Simple Storage Service (Amazon S3), none of the format work in this post changes. A LeRobotDataset is a directory of Parquet and MP4 shards, so it stores on Amazon S3 the same as anywhere else, and the recording, training, and deploy steps read that format wherever it sits. What a bucket adds is the Hub-native route: sync_dataset_to_bucket and stream_dataset(repo_type="bucket") target hf:// directly, so you get the sync and the streaming read with no separate storage path to wire up. Both paths run the same loop: Amazon S3 if that is where your data already sits, a bucket if you want the sync and the streaming read without provisioning storage first.
Run the loop again tomorrow and you are recording into that bucket, syncing only the bytes that changed, and streaming those bytes to the GPUs without waiting for a download. The data never leaves the LeRobot format, and it never leaves the Hub.
Try it using the sample application
The full Strands Robots sample is on GitHub at strands-labs/robots in examples/notebooks/05_streaming_data_loop.ipynb. It walks you through the full loop cell by cell: record, render, sync to a bucket, stream back, train, and load the checkpoint. Every cell runs in simulation on the mock policy, so no GPU, no Docker, and no Hugging Face credentials are needed.
git clone https://github.com/strands-labs/robots.git
cd robots
uv pip install -U "strands-robots[sim-mujoco,lerobot]>=0.5.1"
jupyter notebook examples/notebooks/05_streaming_data_loop.ipynb
Run the cells top to bottom. The recorded dataset lands under /tmp/nb5_dataset. To sync it to a bucket, set BUCKET = "my-org/robot-fave" in the first cell (after hf auth login); the neighboring RUN_ID names the folder inside the bucket, and the notebook streams back from f"{BUCKET}/{RUN_ID}". To train on a GPU, raise steps to 500 and set device="cuda". The agent-driven version of the same loop lives at examples/06_agent_collect_and_stream.py.
Security Considerations
The snippets here are a "hello world" of the Strands Robots data loop. Five things change once you run it against real data.
-
Prompt injection. Supplying untrusted data to an agent can lead to prompt injection, where untrustworthy context is treated as LLM instructions. These agents actuate robots and now also write to and read from shared storage, so this is an important risk to track. Feed the agent only data from trusted sources. If not all input can be trusted, restrict the tools available to the agent so it cannot take safety-critical actions or overwrite bucket contents.
-
Training data is a trust boundary. An agent that can write into the collection bucket can also write episodes that a policy later trains on, and that policy drives a physical arm. Keep the credential that writes collection data separate from the one a training job reads with, sync each run under its own
run_idso an episode can be traced to the run that produced it and removed on its own, and treat the versioned dataset repository as the reviewed artifact, because the bucket keeps no revisions to audit against. -
Bucket credentials and scope.
sync_dataset_to_bucket(...),stop_recording(bucket=...), andsync_to_bucketupload through thehfCLI using the token fromhf auth login. Use a token scoped to the specific namespace you are writing to, prefer--privatebuckets for collection data, and keep the bucket distinct from the versioned dataset repository youpush_to_huband share. -
Overwrite in place keeps no revisions. A bucket overwrites in place and retains no revisions, which is what makes it a working layer and also means a repeated
run_idreplaces the run already stored there. Pass an explicitrun_idper collection run, as insync_dataset_to_bucket("./recordings", "my-org/robot-fave", run_id="run-021"). For anything you need to be able to return to,push_to_hub()to a versioned dataset repository, where every revision is retained. -
Only use trusted Hugging Face orgs. The local inference path loads Hugging Face models with
trust_remote_code=True. SetSTRANDS_TRUST_REMOTE_CODE=1to opt in, and only load checkpoints from organizations you trust. When loading pre-trained weights from the Hub (e.g., viapretrained_name_or_path), verify the organization is trusted before loading. Model weights can contain arbitrary code (pickle-based checkpoints). Prefer safetensors-format checkpoints where available.
Clean up
The loop leaves a bucket, datasets under /tmp, and a checkpoint on disk. Bucket contents count toward your stored volume, so remove what you no longer need:
hf buckets rm my-org/robot-fave/cube_pick/ --recursive --dry-run # lists, removes nothing
hf buckets rm my-org/robot-fave/cube_pick/ --recursive # --yes skips the prompt
hf buckets delete my-org/robot-fave # takes everything in it
rm -rf /tmp/cube_pick /tmp/cube_pick_ft /tmp/nb5_dataset /tmp/nb5_ft
Stop any training process still on a GPU instance, and stop the instance. If you ran the notebook, substitute its RUN_ID (nb5_demo by default) for cube_pick. Anything you published with push_to_hub() is in a versioned repository and is untouched.
Where to go from here
The Strands Robots documentation covers the robot catalog, simulation, policy providers, recording, and the mesh in depth. The recording and datasets guide documents the DatasetRecorder API, sync_dataset_to_bucket / sync_to_bucket, and stream_dataset in full.
If you collect from more than one robot, give each one its own run_id and they write into the same bucket in parallel. The multi-robot mesh fans one agent out across those robots, so the same loop becomes a fleet collecting through the day into shared storage. A streaming reader reads one run at a time. The recording and datasets guide describes how to train across several of them.
If you want a larger policy than ACT, the TrainSpec and Trainer lifecycle from Step 3 covers GR00T and Cosmos 3 behind their own provider names, so fine-tuning a VLA on the dataset you just streamed is the same calls with a different provider string and a base model. Running the result is where the paths diverge, because a VLA checkpoint deploys to hardware rather than to the simulator you trained from. For heavier simulation to generate that data, the Newton (sim-newton) and Isaac Sim (isaac) backends sit behind the same Robot() factory, so the agent code does not change as you scale up.
Bucket streaming reached LeRobot through contributions from both the Strands Robots and LeRobot teams, upstream in LeRobot itself, so the datasets your agent collects are readable by every tool in that ecosystem. That runs both ways: the reader in Step 3 opens any of the LeRobot datasets already published on the Hub, so an agent can replay and evaluate against existing demonstrations before it records one of its own.
Contributions are welcome under Apache 2.0. If you build something with this loop, open an issue with what worked and what didn't.
Resources
Strands Robots
LeRobot and the Hub
Policies
Background
