arkennemasis fal · Flux 3 Action SO-101 · $0.003/compute s
Flux 3 Action SO-101 is a robot arm node, and yes it really is in your ComfyUI menu
- scene_image
- wrist_image
- history_scene_image
- history_wrist_image
- action_hz
- actions
- action_labels
- seed
- timings
- execute_steps
- info
No image comes out of this node. No video either. What comes out is a string of numbers describing where six servo joints should be, forty-two times over, and if that sounds like the wrong thing to find in an image-generation menu - it is, and it's also exactly what FLUX 3 turned out to be.
BFL announced FLUX 3 on 23 July 2026 as one model trained jointly across image, video and audio, extended to predict robot actions. Their own line was "one multimodal model generating image, video, audio and action". The robotics variant is FLUX-mimic, partners-only at launch and tested with Audi on manipulation tasks - reported as fine-tuning a task on as little as 30 minutes of robot data where prior methods wanted 30+ hours. This node is that capability pointed at the SO-101 arm, sold on fal by the compute second.
What a single run actually is
You hand it what the robot can see, what the robot currently is, and a sentence. It hands back an action chunk: 42 target joint positions at 30 Hz - 1.4 seconds of motion.
Then you execute. The author's own guidance is to run the first 32 steps (about one second) and call again with fresh images and joint state. That's the standard action-chunking pattern: you don't ask a network for one tick at a time, you ask it for a plan and commit to most of it, then replan before you run out. The execute_steps output tells you the number that's safe to consume - 32.
The inputs, which are unusual
prompt is the task instruction in plain language, e.g. "Pick up the yellow cube and place it inside the black rectangle". Not a style prompt. A work order.
scene_image and wrist_image are the two camera feeds. Fixed camera tiled on the left, wrist camera on the right, each resized to 256×256 internally - so there is no point uploading 4K frames, and a correct framing matters more than resolution.
state is required and is JSON: shoulder pan, shoulder lift, elbow flex, wrist flex, wrist roll in degrees, plus gripper as a percentage. The default value shows the shape it wants - six numbers in a flat array. This is the reading from your arm's own encoders, and it has to be right, because the model conditions on it.
state_history and command_history are the two that trip people up: the last seven control ticks of measured state and of the commands that were in effect, oldest first, and they require each other. Feed one without the other and the model gets a picture of a moving arm with no idea how it got there. Both empty is a legitimate "fresh episode" call. There are matching history_scene_image and history_wrist_image sockets at the oldest tick, which default to the current frame if you leave them.
num_inference_steps defaults to 4 because the checkpoint was validated at 4. Four. If you crank it to 30 expecting quality, you're just spending compute seconds.
guidance_scale defaults to 3, applied on video and action both - set it to 1.0 to disable classifier-free guidance.
seed at -1 is random; the reference evaluation seeds are 0 for DROID and 42 for SO-101, which is what you want when you're checking whether your setup reproduces published behaviour rather than making something new.
Outputs
actions is the JSON chunk - the actual product. action_hz (30) and execute_steps (32) are the timing contract for your loop. action_labels names the joints so you're not guessing at index 4. seed, timings, and info for the receipt.
Cost, and the cap that can't help you
$0.003 per compute second, and fal estimates a typical call at 0.5–2 s of compute - so roughly $0.0015–$0.006 per plan, about a second of robot motion. Round-trip cost is genuinely negligible next to the hardware.
But this endpoint has no per-run estimate, so max_cost_usd has nothing to compare against and can't stop a runaway. That's a property of every "billed by compute time" node in the pack. Budget with your loop rate instead: a 10 Hz replan is 10 paid calls a second.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/Hishamahmer/comfyui-arkennemasis
Restart, or install through ComfyUI-Manager → Install via Git URL. requirements.txt declares httpx for the pack's Codex nodes; the fal nodes use the standard library. No weights are downloaded - the model runs on fal.
FAL_KEY=your-key-from-fal.ai/dashboard/keys
in the .env next to run_nvidia_gpu.bat (portable) or in your ComfyUI folder. No key widget exists on any fal node in this pack.
Where this actually hurts
The uncomfortable part isn't the node, it's the topology. A robot policy wants a tight loop with fresh state every second. ComfyUI is a graph runner with a queue, and a paid network round trip per replan is not a control loop. People do drive hardware from ComfyUI, and it works fine for slow, deliberate tasks - pick and place with a pause between cycles. It is the wrong shape for anything that needs reflexes.
Also: reuse_identical_run defaults on. If you send the exact same images, state and instruction, you'll get the previous chunk back instead of a fresh inference. For a static test rig that's a free repeat; if you're measuring whether the policy actually re-plans, turn it off.
And one practical note - you are driving a physical arm off a cloud API. When fal has a bad minute, your arm doesn't get a new plan. Have your own watchdog and a hold position.
Inputs (15)
| Name | Type | Default | Description |
|---|---|---|---|
| prompt | STRING | Natural-language task instruction for the robot. | |
| scene_image | IMAGE | Current frame from the fixed scene camera (tiled left). Resized to 256x256. | |
| wrist_image | IMAGE | Current frame from the wrist camera (tiled right). Resized to 256x256. | |
| state | STRING | [-18.94, 134.52, 120.15, 28.3, -32.17, 27.02] | Measured state: shoulder pan, shoulder lift, elbow flex, wrist flex, wrist roll (degrees), gripper (percent). JSON. Required. |
| state_history | STRING | Measured states at the 7 control ticks (30 Hz) before `state`, oldest first. Requires `command_history`. Omit for a fresh episode. JSON. Empty leaves it out. | |
| command_history | STRING | Absolute commands in effect at each of the 8 observation ticks, oldest first; the last row is the most recent command sent. Requires `state_history`. JSON. Empty leaves it out. | |
| num_inference_steps | INT | 41–8 | Euler denoising steps. The checkpoint was validated at 4. |
| guidance_scale | FLOAT | 3.001–10 | Classifier-free guidance on video and action; 1.0 disables CFG. |
| seed | INT | -1-1–2147483647 | Sampling seed. Omit for a random seed; the reference evaluation seed is 0 for DROID and 42 for SO-101. -1 leaves it to the model. |
| history_scene_imageopt | IMAGE | Scene frame from the oldest history tick (7 ticks before now). Requires `state_history`. Defaults to the current frame. | |
| history_wrist_imageopt | IMAGE | Wrist frame from the oldest history tick. Requires `state_history`. | |
| max_cost_usdopt | FLOAT | 20.00–100000 | Safety cap. If the estimated cost of this run is above this many US dollars the node stops BEFORE sending anything to fal. 0 = no cap. |
| reuse_identical_runopt | BOOLEAN | true | If these exact inputs (the settings AND the same pictures/clips/audio) were already paid for and the files are still in output/fal, return them instead of paying again - also after a ComfyUI restart. Turn off for a fresh take with the same settings. |
| extra_jsonopt | STRING | Advanced: a JSON object merged into the request last, for any fal field this node has no box for. Example: {"seed": 7}. Leave empty normally. | |
| max_concurrentopt | INT | 11–32 | How many paid fal calls may run at the same time in one run, across every fal node. 1 (default) = one after another. This node starts its paid part only while fewer than this many fal calls are running. Once any fal node fails, the ones still waiting are not started, so nothing more is billed. |
Outputs (7)
| Name | Type | Description |
|---|---|---|
| action_hz | FLOAT | — |
| actions | STRING | — |
| action_labels | STRING | — |
| seed | INT | — |
| timings | STRING | — |
| execute_steps | INT | — |
| info | STRING | JSON: request id, cost estimate, saved files and fal's full answer. |