Strands Agents と LeRobot で一元的な AI エージェント開発
本文の状態
日本語全文を表示中
詳細モードで約32分の本文を読めます。
同じ出来事の情報源
この情報源を基点に整理
Hugging Face Blog
Hugging Face が提供するストレージバケットと連携することで、Strands Agents や LeRobot を用いたエージェントの記録、トレーニング、デプロイを単一の場所から実行可能にする。
Continue in AI NEW LAB
このニュースを、実務の判断につなげる
AI NEW LABで、試したことや先に確認したい条件を共有できます。まずはログインなしで読めます。
AI NEW LABで論点を見るAI深層分析を開く2026年8月14日 02:55
AI深層分析
キーポイント
一元化されたワークフローの実現
Hugging Face が提供するストレージバケットと連携することで、Strands Agents や LeRobot を用いたエージェントの記録、トレーニング、デプロイを単一の場所から実行可能にする。
Amazon と Hugging Face の技術連携
この発表は Amazon のエンジニアが執筆しており、両社のインフラやツールが統合された環境での開発体験の向上を目的としている。
エージェント開発ツールの強化
Strands Agents や LeRobot といった具体的なツール名が挙げられており、これらを活用した複雑なエージェントシステムの構築プロセスが簡素化される。
ストリーミングデータループによる一貫したワークフロー
Strands Robots のエージェントループは、ロボットのデモ記録から学習、そしてポリシーの展開までを一つの場所で実行する。このプロセスでは Hugging Face Storage Buckets を介してバイトレベルの重複排除を行い、効率的なデータ管理を実現する。
Hugging Face Hub を活用したトレーニングとデプロイ
学習モデルは Hugging Face Hub からストリーミングされ、展開されたポリシーはフィードバックデータをループに返すことで継続的な改善が可能となる。
重要な引用
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
Deploy the policy and return data to the loop
Run that loop once and every piece works. Run it every day and you start paying for the same byte transfers over and over.
編集コメントを表示
編集コメント
エージェント開発の複雑さを軽減する統合環境の提供は、実用化段階にある AI アプリケーションにとって極めて重要なインフラ整備である。特に大規模なトレーニングデータを扱う際、ストレージと計算リソースの一元管理が実現される点は注目に値する。
Source Article
元記事を日本語で読む
本文に関係しない購読案内、埋め込み通知、サイト内プロモーションは除いています。
Strands Agents、LeRobot、Hugging Face Storage Buckets を使えば、記録から学習、デプロイまでをワンストップで行えます。
- 何を作るか
- 事前準備
- 手順 1 - デモをバケットに記録する
- 手順 2 - バイトレベルの重複排除で保存する
- 手順 3 - Hub からストリーミングして学習する
- 手順 4 - ポリシーをデプロイし、データをループに戻す
- サンプルアプリで試す
- セキュリティの考慮事項
- クリーンアップ
- 次のステップ
- リソース
Strands Robots のストリーミングデータループの解説です。この 1 つのエージェントループでは、ロボットのデモを記録し、それを Hugging Face Hub から直接読み込んで学習を行い、学習済みのポリシーをハードウェアにデプロイします。データセットは、ディスク上の LeRobot フォーマットで一貫して保持されます。
デモの記録を Hugging Face Hub へプッシュできるエージェントはすでに存在します。しかし、このループを継続的に回すにはどうすればよいでしょうか?一日中エピソードを収集し、蓄積されるデータセットでポリシーを学習させ、デプロイして、改善のために次のバッチを引き戻す。一度だけ実行すれば各パーツは正常に動作しますが、毎日実行すると同じバイト転送に対してコストがかかり続けることになります。
アップロードする録画データは増え続け、各トレーニングランでは開始前に全データセットを GPU へコピーする必要があります。また、新しいチェックポイントが送信されるたびに、次の録画バッチが戻ってきます。
本シリーズの最初の投稿では、AWS が提供するオープンソース SDK「Strands Robots」を紹介しました。このプロジェクトは Apache 2.0 ライセンスのもと、ロボットのアブストラクションやシミュレーション機能を、単一の Strands エージェントに統合するためのコンポーネントとして LeRobot スタックを AgentTools として公開しています。
これまでの記事では、Robot() ファクトリの使い方や、シミュレーション環境でのデモ記録、ポリシーの実行、そして同じエージェントコードを物理的な SO-101 ロボットへ展開する方法について解説しました。このファクトリはアーム、ヒューマノイド、移動ベース、ハンドなどのロボット種別を登録したレジストリに対して名前解決を行う仕組みです。そのため、本記事で頻出する SO-100 も、サポートされている多数のエンボディメント(物理的実装)の一つに過ぎません。
robot catalog では、ファクトリが認識しているすべてのロボットの一覧を確認できます。LeRobot のデータ形式はすでに Hugging Face Hub 上で 90,000 件以上のデータセットやモデルで採用されており、8,000 名を超えるパブリッシャーによって利用されています(LeRobot Project Pulse)。Strands Robots で記録されたデータもその一つであり、LeRobot データの読み込みに対応したツールであれば、変換処理なしでそのまま利用可能です。
Strands Robots が初めての方は、まずは上記の最初の投稿から始めることをお勧めします。本記事では、すでにセットアップが完了していることを前提に話を進めます。
前回の投稿では、エージェントループをハブ上のデータセットから物理ロボットへと一方向に追跡しました。今回はその逆、つまり記録された最初のフレームからデプロイされたポリシーに至るまでのデータを、Hugging Face Storage Buckets を介して追います。これは 2026 年 3 月に発表された Xet に基づくオブジェクトストレージリポジトリタイプで、可変でありバージョン管理されません。バケットはデータセットリポジトリの隣に同じ hf:// ネームスペース内に配置され、すでに使用している hf CLI を利用するため、データを記録した日からトレーニングを行う日までの間、作業用レイヤーとしてデータを保持する役割を果たします。
誰かが、どのエピソードを保存すべきか、シーンが十分にずれたと判断して再記録するタイミングはいつか、今日のデータセットで学習に十分な量があるかどうか、そしてアーム上のチェックポイントをどれに置き換えるかを決定する必要があります。これらの判断は、収集キャンペーン全体で数十回発生し、次のコマンドを出す前に、戻ってきたデータを必ず確認しなければなりません。まさにそれがエージェントの役割です。
この記事では、単一のエージェント内にあるデータループを詳しく解説します。Storage Bucket にデモを記録し、変更されたバイトのみがアップロードされるように保存し、ダウンロードせずに Hub から直接ストリーミングして学習し、最後にキーワード引数を変更するだけでチェックポイントをハードウェアにデプロイします。
この記事の実行可能なコンパニオンは `examples/notebooks/05_streaming_data_loop.ipynb` にあります。
構築するもの
最初の投稿ではデータセットを記録して Hugging Face Hub にプッシュしましたが、ここで構築するエージェントは自然言語のプロンプトから LeRobotDataset を記録し、Storage Bucket へ同期します。そして、ローカルにコピーを残すことなく、カメラ映像をその場でデコードしながら、フレーム単位で同じデータストリームをストリーミング再生します。
データを書き込んだのと同じプロセスで読み込みます。つまり、データセットを記録した Strands Robots の Robot() がそのままストリーミングを行うのです。学習済みのチェックポイントは、キーワード引数を変更するだけで、その Robot() へデプロイされます。ハードウェア上で記録されたデモも、再び同じ Bucket に戻ります。

Figure 1. *4 つのステージは共通のバックエンドを共有します。* Robot("so100") は、共有される DatasetRecorder を通じて LeRobot データセットを記録し、sync_dataset_to_bucket(...) で同期を行います。
Storage Bucket に格納し、stream_dataset(...) で Hub を介して読み戻します。
完全なダウンロードは不要で、学習済みのチェックポイントは同じ Robot に mode="real" でデプロイされます。ディスク上のフォーマットは、LeRobot が作成したままの形式を維持します。
Robot() を一つ使うだけで、データセットの記録と読み込みが同じオブジェクト上で完結します。つまり、データの収集とトレーニングは、単一のバックエンドを介した一つのオブジェクト上の二つのメソッドに過ぎません。エージェントはエピソードの実行を決定し、ツールを呼び出します。ロールアウトはエピソード終了までロボットの制御周波数で進行し、学習済みポリシーがすべての動作を生成します。この一連のループは、わずか数行で記述できます。
from strands import Agent
from strands_robots import Robot
sim = Robot("so100") # mode="sim" (default - safe, no hardware)
agent = Agent(tools=[sim])
# Record a demonstration and sync it to a bucket.
agent("Record a pick-the-cube demo and sync it to my-org/robot-fave.")
# Stream it back from the bucket to train, without downloading it first.
for batch in sim.stream_dataset("my-org/robot-fave/cube_pick", repo_type="bucket").dataloader(batch_size=64):
...
次に、このループ内で実際に何が起こっているのかを、ステップごとに解説します。
前提条件
最小構成(デフォルトのシミュレーションパス)
- Python 3.12 以上。Linux または macOS 環境で動作します(Apple Silicon 搭載機でも MuJoCo バックエンドに対応)。
エージェントの推論に使用する 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」を使用すれば、追加の設定なしで録画と動画のデコードが両方とも動作します。
インストールガイド を参照してください。
これで完了です。この記事で説明したすべての工程は、これら 3 つのツールを備えたラップトップ上で実行可能です。ただし、ここで動作しているのはループ処理そのものであり、実際に使えるポリシーではありません。デフォルトではモック(模擬)ポリシーが使用されるため、有効なデータセットは記録されますが、実用的な内容にはなりません。
上級者向け(バケット、ハードウェア、実際のポリシー)
Hugging Face アカウントと書き込み権限を持つトークン、およびバケット作成やデータセットの同期を行うための hf CLI を用意してください。pip install -U "huggingface-hub>=1.6.0" でインストールできます。
LeRobot では、一日を通じて新しいエピソードを記録します。これはカメラフレームと関節状態・行動のテレメトリデータを連続的に取得するランです。LeRobot はこれを、記録が進むにつれてサイズが大きくなる少数の大規模ファイルとして保存します。これらを一元的なバージョン管理付きデータセットリポジトリにプッシュすると、追加されるたびにコミットが発生し、すべての改訂履歴が保持されます。
一方、コレクション(Collection)では逆のニーズがあります。つまり、バイト列を書き込み、既存の内容をその場で上書きできる場所が必要です。それが Storage Bucket です。これは Hugging Face ワークスペース内に存在し、すでに持っている権限を利用します。IAM ロールの設定や CORS ルールの調整、アップロードサービスの維持管理は不要です。
エージェントは、ハードウェア上で LeRobot が書き込むのと同じ形式で LeRobotDataset を記録します。エピソードを記録したら、完成したデータセットをバケットに同期します。ここでは、学習済みモデルがない状態でも関節動作を生成するモックポリシー(代わりの実装)を使用するようプロンプトが指示しています。これにより、チェックポイントを取得する前に全体のループを実行できます:
from strands import Agent
from strands_robots import Robot, sync_dataset_to_bucket
sim = Robot("so100") # mode="sim" by default
agent = Agent(tools=[sim])
# One prompt drives scene setup, cameras, policy, and recording.
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 the finished on-disk dataset into the bucket (no live recording session needed).
sync_dataset_to_bucket("/tmp/cube_pick", "my-org/robot-fave")
# -> {"status": "success", "bucket_uri": "hf://buckets/my-org/robot-fave/cube_pick"}
同期処理は hf://buckets/{bucket}/{run_id} へ書き込まれます。ここで run_id はデフォルトでデータセットディレクトリ名が使用されます。ステップ 3 のストリーミング読み込みでも同様にラン名を指定します。ID の最初の 2 つのセグメントがバケット名となり、それ以降は内部パスとなります。
sync_dataset_to_bucket(root, bucket, run_id=...) は、記録のライフサイクルから切り離され、データセットを検証した上で hf コマンドラインツールを通じて同期を行います。
オープンなレコーダーを直接操作する場合は、DatasetRecorder.sync_to_bucket(bucket, run_id=...) を使用することで同様の機能を実現できます。また、stop_recording(bucket=...) で記録を停止することも可能です。
アクティブな録画を停止した瞬間に同期されます。バケットは、1 日の間に書き込みを行う作業用レイヤーです。バージョン管理され公開されたアーティファクトについては、依然として push_to_hub() を呼び出します。両者は同じフォーマットを持っています。
このエピソードは構造的には完成していますが、アクションがプレースホルダーであるため、そのまま学習データとして使用することはできません。実際の把持動作を行うためには、create_policy("<hf_repo>") を用いて実在するポリシーに置き換えてください。プロンプトの形式やバケット同期の手順は変更されません。
ハードウェアでの記録
物理的な 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'
データセットは、シミュレーション記録と同じ形式でディスクに保存されます。そのため、同じ同期コマンドを使ってバケットへ転送できます:sync_dataset_to_bucket("./recordings", "my-org/robot-fave", run_id="run-021")(あるいは、そのラッパーである CLI コマンド hf sync ./recordings hf://buckets/my-org/robot-fave/run-021)。収集処理はすべて 1 つの場所に追加されていき、公開されるリポジトリには、あなたが選択したバージョンのみが反映されます。
ステップ 2 - バイトレベルでの重複排除で保存
データセットをバケットに保存した後、次に重要になるのは同期にかかるコストです。アームが同じテーブルを片付ける様子を定点カメラで 8 時間撮影した場合、記録されるデータの大部分はすでに存在するピクセルの繰り返しになります。照明も車体も背景も、数千エピソードを通じて全く同じだからです。
バージョン管理されたリポジトリでは状況が悪化します。数ギガバイトある動画のシャード(断片)の 1 フレームを変更しただけで、ファイル全体が再アップロードされてしまうからです。
バケットは Xet に支えられており、コンテンツ定義チャンキング(content-defined chunking)を用いてバイトレベルで重複データを削除します。チャンクの境界線はコンテンツ自体に依存するため、数バイトを挿入しても影響を受けるのはそのデータが入ったチャンクだけで、それ以降のすべての境界がずれることはありません。
Hugging Face 自身の測定結果 (HF Storage) では、コンテンツ定義チャンキングによりハブ全体でのアップロードあたりの転送データ量が約 4 分の 1 に削減されています。また、エンタープライズプランでは課金が重複削除後の実際のデータ量に基づいて行われます。
バケットベンチマーク は、単一ファイルでの具体的な効果を示しています。500 MB のアップロードを基準に、1% のバイトを変更して再アップロードすると転送量は 5.5 MB に留まり、5% の変更では 27.5 MB、10% の変更でも 55 MB で済みます。
チャンクレベルでの重複削除がない場合、オブジェクトを上書きする際に変更の有無に関わらず、すべてのバイトを再送信しなければなりません。
節約できる量はファイル構成次第です。Strands Robots のレコーダーは 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 です。
したがって、記録開始から 1 日後に同期を実行すると、新たに生成された末尾のシャードと容量が増加した部分的なファイルのみがアップロードされ、データセット全体が転送されるわけではありません。翌日にも同じバケットへ再度同期すれば、Xet が重複データの排除を自動で行います。
図2. 同期では変更された部分のみがアップロードされます。新しいデータセットの初回同期ではすべてのチャンクがアップロードされますが、その後にエピソードを記録すると、Xet のコンテンツ定義型チャンキングにより、次の同期では新しいチャンクのみがアップロードされ、既に保存済みの部分はスキップされます。
ステップ3 - ハブからストリーミングしてトレーニング
学習には GPU をデータセットに接続する必要があります。まずデータをダウンロードし、数百ギガバイトの転送が完了するまで GPU はアイドル状態になります。しかし、Step 2 で示したシャード構成のおかげで、Hugging Face Hub から直接ストリーミングすれば、大量の小さなファイル取得を行う代わりに、大きなシャードに対する数回のバイト範囲読み込みでバッチを処理できます。LeRobot の StreamingLeRobotDataset はこれを Torch のイテラブルとして即座に利用可能にし、Strands Robots では 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, # one episode, in capture order
)
print(reader.num_episodes, reader.num_frames, reader.fps)
for frame in reader:
frame["observation.images.front"] # (3, H, W) tensor, decoded on the fly from the MP4 shard
frame["observation.state"] # joint vector, from the Parquet shard
frame["action"]
break
ローカルディスクに書き込まれるのは、スキーマや統計情報、エピソードインデックスを含む小さな meta/ フォルダだけです。カメラフレームは、イテレーション中にリモートの MP4 シャードから逐次デコードされ、状態と行動データは Parquet シャードから読み出されます。このループは 1 フレームずつ読み込むため、エピソードの調査に適しています。一方、学習を行う場合は、このリーダーを DataLoader に渡してバッチ単位でイテレーションします。ストリーミングデータセットは内部で有界なリザーバーバッファを通じてシャッフルされるため、ビデオデコードはワーカープロセス間で並列化されます。学習ステップ自体も、通常の PyTorch の処理と同じです。
# policy here is a LeRobot policy you constructed, such as ACTPolicy.
for batch in reader.dataloader(batch_size=64, num_workers=4):
loss, _ = policy(batch) # lerobot ACTPolicy.forward returns (loss, loss_dict)
loss.backward()
もしループを記述したくない場合は、LeRobot 独自のトレーナーが同じエンジンを読み込むので、エージェントが収集したデータセットを新しいコードを書かずにそのまま学習させることができます。バケットは、プロセス内のリーダーで使用するのと同じキーワード引数を通じて指定されます。
lerobot-train --policy.type=act \
--dataset.repo_id=my-org/robot-fave/cube_pick \
--dataset.repo_type=bucket \
--dataset.streaming=true \
--num_workers=4
バケットはストリーミング専用であるため、--dataset.repo_type=bucket を指定する場合は --dataset.streaming=true も必須となり、それ以外の組み合わせでは設定が拒否されます。エピソードの検証やシミュレーションでの再生、あるいはデータ供給を自プロセス内でループ処理したい場合は、stream_dataset() を利用してください。
固有感覚のみによるストリーミング処理の場合、drop_videos=True を設定すると動画のデコード自体がスキップされます。これにより、torchcodec のホイールが存在しないエッジデバイス上でも動作が可能になります。
記録とデータセットに関するガイド(recording and datasets guide)では、この議論について記述するとともに、必要な delta_timestamps マップについても解説しています。
ポリシーの実行と学習では、プロバイダー名を共通で利用します。create_trainer("lerobot_local") は、create_policy() と同様に動作する Trainer を返します。
TrainSpec は実行内容を記述するものであり、これにより記録・学習・展開のループが完結します。
数行で:
import os
os.environ["STRANDS_TRUST_REMOTE_CODE"] = "1" # create_policy loads with trust_remote_code=True
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) # train ACT on the streamed dataset
policy = create_policy(result.checkpoint_dir) # load the checkpoint straight back
NVIDIA L4 (g6.4xlarge) 1 台上で、120 フレームのエピソードに対する ACT(5,160 万パラメータ、実効バッチサイズ 8)のオプティマイザステップ 500 を実行すると、完了まで 133 秒かかりました。生成されたチェックポイントは create_policy() によって読み込まれ、他のポリシーを実行する際と同じエントリーポイントが利用されます。
トレーニング時間はデータセットの規模、バッチサイズ、ステップ数に比例して変化するため、これはベンチマークというより、測定済みの構成例の一つとして捉えてください。
groot と cosmos3 のプロバイダは、どちらも同じ TrainSpec および Trainer のライフサイクルを対象としているため、周囲のループ構造は変更されません。各プロバイダは、各自が必要とするフィールドを独自に検証します。 (原文の技術表記: "groot"、"cosmos3")
まず、GR00T の実行には base_model と embodiment タグ、そして Cosmos 3 が必要です。
トレーニングには base_model と SFT レシピが必要です。train() を実行する前に trainer.validate(spec) を呼び出すと、特定のバックエンドで不足しているものが一覧として返されます。
Hugging Face のプリウォーミングキャッシュ機能は、ジョブが実行されるクラウドやリージョンに近いエッジロケーションにデータを配置します。これにより、クラスターはローカルからデータを読み込み、データローダーが GPU の処理速度を常に上回る状態を維持できます。
Hugging Face 自身が行った バケットベンチマーク では、10 GB のペイロードに対して暖かいコンテンツデリバリネットワーク(CDN)の読み込み速度が約 1,086 MB/s を記録したのに対し、冷たい状態では 780 MB/s でした。また、100 GB の場合でも暖かい状態では約 1,124 MB/s を達成しています。これらの測定は us-east-1 リージョンの m5dn.24xlarge インスタンス上で行われたものです。
純粋なオブジェクトストレージとの比較、アップロードおよびダウンロードの両方を含めた詳細データは、上記ダッシュボードでご確認いただけます。
データ保存先は、チームプランおよびエンタープライズプランでは ストレージリージョン 設定で選択できます。現時点では米国と欧州が利用可能で、アジア太平洋地域や湾岸協力会議(GCC)地域の追加も発表されています。それらのプランに該当しないリポジトリは米国に保存されます。
macOS では import strands_robots を実行すると、Homebrew 経由でインストールした ffmpeg がローダーパスに自動的に追加されるため、torchcodec でストリーミング動画のデコードを余計な設定なしで行えます。
このステップでは、先ほど学習させたチェックポイントを実際のロボットで実行し、その結果に基づいて次のデモを記録します。使用するエージェントコードは最初の投稿と同じですが、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.")
チェックポイントは物理的なアームに対して実行され、そのアームが記録したデモデータは、開始時に使用したのと同じ LeRobot 形式でディスクに保存されます。これにより、次のトレーニングランに向けてバケットへ同期する準備が整います。
もしデータがすでに Amazon Simple Storage Service (Amazon S3) に存在する場合でも、本記事で紹介されるフォーマットに関する作業に変更はありません。LeRobotDataset は Parquet と MP4 のシャードを格納したディレクトリであるため、Amazon S3 上でも他の場所と同様に保存可能です。記録、トレーニング、デプロイの各ステップは、データがどこに配置されていても同じ形式を読み込みます。バケットが追加する価値は、Hub ネイティブなルートです。sync_dataset_to_bucket と stream_dataset(repo_type="bucket") は直接 hf:// を対象とするため、同期とストリーミング読み取りを別々のストレージ経路を設定することなく実現できます。どちらのパスも同じループを実行します。データがすでに Amazon S3 にある場合はその通り利用し、バケットを使用する場合は、事前にストレージをプロビジョニングすることなく同期とストリーミング読み取りを実現できます。
翌日にもう一度このループを実行すれば、データはバケットへ記録され、変更されたバイトのみが同期され、ダウンロードを待たずに GPU へストリーミングされます。データは LeRobot 形式から離れることも、Hub から離れることもありません。
サンプルアプリケーションで試す
Strands Robots の完全なサンプルコードは、GitHub の strands-labs/robots リポジトリにあります。具体的には `examples/notebooks/05_streaming_data_loop.ipynb` にて、記録・レンダリング・バケットへの同期・ストリーミングによる復元・学習・チェックポイントの読み込みまでを、セル単位で順を追って解説しています。なお、すべてのセルはモックポリシー上でのシミュレーション環境で動作するため、GPU や Docker の準備、Hugging Face の認証情報は不要です。
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
セルを上から順に実行します。記録されたデータセットは /tmp/nb5_dataset 配下に保存されます。これをバケットに同期するには、最初のセルで hf auth login の後に BUCKET = "my-org/robot-fave" を設定してください。
RUN_ID はバケット内のフォルダ名を指定し、ノートブックは f"{BUCKET}/{RUN_ID}" からストリーミングされます。
GPU で学習を行う場合は、steps を 500 に引き上げ、device="cuda" と設定してください。同様のループのエージェント駆動版は `examples/06_agent_collect_and_stream.py` にあります。
セキュリティに関する考慮事項
ここに示すスニペットは、Strands Robots のデータループにおける「Hello World」です。これを実データの環境で実行すると、5 つの変化が生まれます。
プロンプトインジェクション。信頼できないデータをエージェントに提供すると、プロンプトインジェクションが発生するリスクがあります。これは、信頼性の低い文脈が LLM の指示として扱われてしまう現象です。これらのエージェントはロボットを操作するだけでなく、共有ストレージへの書き込みや読み出しも行うため、このリスクは特に重要です。エージェントに供給するのは信頼できるソースからのデータだけに限定してください。すべての入力データを信頼できない場合があるなら、利用可能なツールを制限し、安全性に関わる重要なアクションを実行したり、バケット内のコンテンツを上書きしたりできないようにする必要があります。
トレーニングデータは信頼の境界線です。収集バケットへの書き込み権限を持つエージェントは、後にポリシーが学習するエピソードも書き込むことが可能であり、そのポリシーが物理的なアームを制御します。収集データの書き込み用資格情報と、トレーニングジョブが読み取りに使用する資格情報を分離し、各実行を固有の run_id で同期して、エピソードからそれが生成された実行を追跡できるようにし、不要な場合は個別に削除できるようにしてください。また、バージョン管理されたデータセットリポジトリを検証済みアーティファクトとして扱うべきです。バケットには監査可能な改訂履歴が保存されていないためです。
バケットの認証情報とスコープ。sync_dataset_to_bucket(...)、stop_recording(bucket=...)、および sync_to_bucket は、hf を通じてアップロードされます。
token を使用した CLI は、hf auth login で実行します。
書き込み先の名前空間に紐付いたトークンを使用し、収集データには --private バケットを優先してください。また、バケットは push_to_hub でバージョン管理され共有されるデータセットリポジトリとは別に保つようにしてください。
上書き保存では履歴が保持されません。バケットは上書き保存方式であり、過去のバージョンを一切残さないため、作業レイヤーとして機能します。この仕組みにより、同じ run_id で再度実行すると、既に保存されているランニングデータが上書きされます。
コレクションごとの実行には、sync_dataset_to_bucket("./recordings", "my-org/robot-fave", run_id="run-021") のように明示的に run_id を渡してください。後から参照する必要があるデータは、push_to_hub() でバージョン付きのデータセットリポジトリにプッシュし、すべての改訂履歴を保持するようにしましょう。
- 信頼できる Hugging Face の組織のみを使用してください。 ローカル推論パスでは、Hugging Face モデルを
trust_remote_code=Trueで読み込みます。STRANDS_TRUST_REMOTE_CODE=1を設定して有効化し、信頼できる組織からのチェックポイントのみを読み込むようにしてください。Hub から事前学習済み重み(例:pretrained_name_or_pathを経由)を読み込む際は、その組織が信頼できることを確認してから読み込んでください。
モデルの重みには任意のコード(pickle ベースのチェックポイント)が含まれる可能性があります。利用可能な場合は、safetensors フォーマットのチェックポイントを優先してください。
クリーンアップ
このループでは、バケット、/tmp 配下のデータセット、ディスク上のチェックポイントが残されます。バケットの中身は保存ボリュームの使用量に含まれるため、不要になったものは削除しましょう:
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
GPU インスタンス上で実行中のトレーニングプロセスを停止し、インスタンス自体も停止してください。ノートブックを実行している場合は、RUN_ID(デフォルトは nb5_demo)を cube_pick に置き換えてください。
push_to_hub() で公開したものは、バージョン管理されたリポジトリに保存され、そのままの状態で残ります。
ここから先は
『Strands Robots』のドキュメントでは、ロボットカタログやシミュレーション、ポリシープロバイダー、記録機能、そしてメッシュについて詳しく解説されています。
「記録とデータセットガイド」では、DatasetRecorder API や sync_dataset_to_bucket について解説しています。
sync_to_bucket と stream_dataset をフル機能で利用可能です。
複数のロボットからデータを収集する場合は、各ロボットに固有の run_id を割り当て、並列で同じバケットへ書き込ませます。マルチロボットメッシュ はエージェントをこれらのロボット全体に展開し、1 つのループが一日中共有ストレージへとデータを収集するファームとして機能します。ストリーミングリーダーは 1 回ごとに 1 つの run を読み込みます。記録とデータセットガイド では、複数の run にわたるトレーニング方法について解説しています。
ACT よりも大規模なポリシーが必要であれば、Step 3 で紹介した TrainSpec と Trainer のライフサイクルが GR00T や Cosmos 3 にも適用されます。これらはそれぞれ独自のプロバイダー名で管理されていますが、ストリーミングされたデータセット上で VLA をファインチューニングする手順は、プロバイダー文字列とベースモデルを変更するだけで同じ呼び出しで実行可能です。
実行結果の展開は、ここで道が分かれます。VLA のチェックポイントは、学習に使用したシミュレータではなく、実際のハードウェアへデプロイされるためです。より大規模なシミュレーションでデータを生成する必要がある場合でも、Newton(sim-newton)と Isaac Sim(isaac)のバックエンドは同じ Robot() ファクトリの下に配置されているため、スケールアップしてもエージェントコードを変更する必要はありません。
Strands Robots チームと LeRobot チーム双方の貢献により、Bucket streaming が LeRobot に実装されました。これにより、エージェントが収集したデータセットは、そのエコシステム内のあらゆるツールで読み込み可能になります。
この仕組みは双方向に機能します。ステップ 3 のリーダーは、すでに Hugging Face Hub に公開されている LeRobot データセットをすべて開くことができます。つまり、エージェントは独自のデータを記録する前に、既存のデモを再生して評価することが可能です。
Apache 2.0 ライセンスの下での貢献を歓迎します。このループを活用して何かを構築された場合は、何がうまくいき、何が失敗したかを記載した Issue を作成してください。
リソース
Strands Robots
- SDK、AgentTools、および
Robot()ファクトリ: github.com/strands-labs/robots、Apache 2.0 ライセンス
- ドキュメント: strands-labs.github.io/robots
- 記録とデータセットガイド: strands-labs.github.io/robots/recording
- 本記事用のノートブック: `examples/notebooks/05_streaming_data_loop.ipynb` - 1 セルずつ実行してフルループを確認できます
- Strands Agents SDK: github.com/strands-agents/harness-sdk
LeRobot と Hub
- LeRobot: github.com/huggingface/lerobot - データセット、ポリシー、ハードウェアドライバ
リンクとリソース
- Hugging Face Storage Buckets: Storage Buckets 公式ドキュメント
- Xet データイデム化: ファイルからチャンクへ
- ピック&プレイスデータセット(本記事で記録した形式): lerobot/svla_so101_pickplace
ポリシー
- SmolVLA: lerobot/smolvla_base
- Pi0: lerobot/pi0_base
- NVIDIA Isaac-GR00T N1.7: nvidia/GR00T-N1.7-3B
- NVIDIA Cosmos 3 Nano: nvidia/Cosmos3-Nano
- MolmoAct2(SO-100/101向けにトレーニング済み): allenai/MolmoAct2-SO100_101 -
lerobot_localを通じて読み込み、molmoact2拡張機能が必要です
背景
- 本シリーズの最初の投稿: Hugging Face Hub から Strands Agents と LeRobot を使ってロボットハードウェアへ
- このワークフローが従う物理 AI データループ: The Physical AI Data Loop(Steven Palma、Hugging Face、2026 年)
- バケットのスループットと重複排除の測定結果: hf-buckets-benchmark
原文を表示
- What you'll build
- Prerequisites
- Step 1 - Record a demonstration into a bucket
- Step 2 - Store with byte-level deduplication
- Step 3 - Train by streaming from the Hub
- Step 4 - Deploy the policy and return data to the loop
- Try it using the sample application
- Security Considerations
- Clean up
- Where to go from here
- Resources
*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") # mode="sim" (default - safe, no hardware)
agent = Agent(tools=[sim])
# Record a demonstration and sync it to a bucket.
agent("Record a pick-the-cube demo and sync it to my-org/robot-fave.")
# Stream it back from the bucket to train, without downloading it first.
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". The lerobot extra pulls in LeRobot (>=0.6.1), datasets, av, and torchcodec, 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 hf CLI for creating buckets and syncing datasets: pip install -U "huggingface-hub>=1.6.0,<2.0.0", then hf 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") # mode="sim" by default
agent = Agent(tools=[sim])
# One prompt drives scene setup, cameras, policy, and recording.
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 the finished on-disk dataset into the bucket (no live recording session needed).
sync_dataset_to_bucket("/tmp/cube_pick", "my-org/robot-fave")
# -> {"status": "success", "bucket_uri": "hf://buckets/my-org/robot-fave/cube_pick"}
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, # one episode, in capture order
)
print(reader.num_episodes, reader.num_frames, reader.fps)
for frame in reader:
frame["observation.images.front"] # (3, H, W) tensor, decoded on the fly from the MP4 shard
frame["observation.state"] # joint vector, from the Parquet shard
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:
# policy here is a LeRobot policy you constructed, such as ACTPolicy.
for batch in reader.dataloader(batch_size=64, num_workers=4):
loss, _ = policy(batch) # lerobot ACTPolicy.forward returns (loss, loss_dict)
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" # create_policy loads with trust_remote_code=True
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) # train ACT on the streamed dataset
policy = create_policy(result.checkpoint_dir) # load the checkpoint straight back
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_id so 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=...), and sync_to_bucket upload through the hf CLI using the token from hf auth login. Use a token scoped to the specific namespace you are writing to, prefer --private buckets for collection data, and keep the bucket distinct from the versioned dataset repository you push_to_hub and 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_id replaces the run already stored there. Pass an explicit run_id per collection run, as in sync_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. Set STRANDS_TRUST_REMOTE_CODE=1 to opt in, and only load checkpoints from organizations you trust. When loading pre-trained weights from the Hub (e.g., via pretrained_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
- SDK, AgentTools, and the Robot() factory: github.com/strands-labs/robots, Apache 2.0
- Documentation: strands-labs.github.io/robots
- Recording and datasets guide: strands-labs.github.io/robots/recording
- The notebook for this post: examples/notebooks/05_streaming_data_loop.ipynb - run the full loop cell by cell
- Strands Agents SDK: github.com/strands-agents/harness-sdk
LeRobot and the Hub
- LeRobot: github.com/huggingface/lerobot - datasets, policies, hardware drivers
- Hugging Face Storage Buckets: Storage Buckets documentation
- Xet deduplication: From Files to Chunks
- A pick-and-place dataset in the format this post records: lerobot/svla_so101_pickplace
Policies
- SmolVLA: lerobot/smolvla_base
- Pi0: lerobot/pi0_base
- NVIDIA Isaac-GR00T N1.7: nvidia/GR00T-N1.7-3B
- NVIDIA Cosmos 3 Nano: nvidia/Cosmos3-Nano
- MolmoAct2, trained for the SO-100/101: allenai/MolmoAct2-SO100_101 - loads through lerobot_local, needs the molmoact2 extra
背景
- First post in this series: From the Hugging Face Hub to robot hardware with Strands Agents and LeRobot
- The physical-AI data loop that this workflow follows: The Physical AI Data Loop, Steven Palma, Hugging Face, 2026
- Bucket throughput and dedup measurements: hf-buckets-benchmark
関連記事
今日のまとめ
AIデイリーブリーフで今日の重要ニュースをまとめ読み