The AY-Robots SO-100 hub page, showing the arm whose gripper design this article takes apart
SO-100gripper designgraspingimitation learningLeRobothardware

SO-100 Gripper Design: Why the End Effector Decides the Task

AY-Robots ResearchAugust 23, 202620 min read

The SO-100 gripper is one revolute joint with 110 degrees of travel and a torque cap written into the servo. What that means for grasping, compliance and what a policy can learn.

What you need to know

  • The SO-100 gripper is one revolute joint. The URDF limits are -0.174533 to 1.74533 radians, that is -10 to +100 degrees: 110 degrees of jaw swing against a fixed jaw.
  • It is not a parallel jaw. The contact normal rotates as the jaw opens, so a wide grasp and a narrow grasp are different mechanical events.
  • LeRobot writes a force ceiling into the servo on connect: Max_Torque_Limit 500, Protection_Current 250. No policy learns past a limit stored in the motor.
  • The observation is Present_Position only. No current, no torque, no touch, so the camera is the only reliable evidence a grasp worked.
  • In lerobot/svla_so101_pickplace (50 episodes, 11939 frames) the gripper action never exceeds 33.0 out of 100 and sits below 5 in 70.4 percent of frames.
  • gripper.pos is a percentage of whatever span you swept during calibration. Not millimetres, not degrees, not portable between arms.

The gripper is not an accessory, it is the task

Most people building an SO-100 give their attention to the five arm joints and treat the sixth as a switch. Those five move the end effector to a place. The gripper decides whether anything happens once it gets there. Plenty of failures that look like a vision or policy problem are contact problems, and contact is geometry plus stiffness plus force: a printed part and three numbers written into a servo.

Everything below comes from the SO-ARM100 design files, the LeRobot driver source, the Feetech datasheet and two public LeRobot datasets you can measure yourself. Where a number came out of a file, the command that produced it is here too.

Which version this describes

Design files: TheRobotStudio/SO-ARM100, main branch on 23 August 2026, SO-101 revision of the parts. Driver: lerobot so_follower, same branch and date. Upstream moves; these numbers are pinned to that snapshot.

What the SO-100 gripper actually is

Open the kinematic model, not the product photo. In so101_new_calib.urdf there are five revolute arm joints plus one called gripper, and that one is revolute too. No prismatic joint, no second finger joint. One motor, one hinge, one moving jaw closing against a jaw moulded into the wrist roll body.

PropertyValueSource
Joint typerevolute (hinge, not slide)so101_new_calib.urdf
Limits-0.174533 to 1.74533 rad, that is -10 to +100 degso101_new_calib.urdf
Total jaw travel110 degreesupper minus lower
Arm freedoms5 revolute joints plus the gripperjoint count, same URDF
ServoFeetech STS3215, 1/345 on the followerLeRobot SO-101 guide
Stall torque19.5 kg.cm at 7.4 VFeetech product page
Position resolution4096 steps per 360 deg, 0.088 degFeetech product page
Moving jaw part22.3 x 92.0 x 48.0 mm bounding boxMoving_Jaw_SO101.stl, measured
Normalisationgripper RANGE_0_100, arm joints DEGREES by default (use_degrees)so_follower.py

Two rows do the damage. The first is revolute. A parallel jaw keeps both faces opposed however far it opens; a pivoting jaw does not. Near 100 degrees the moving face points almost sideways, the contact normals stop being opposed, and the grasp is no longer antipodal. Squeeze a cylinder in that pose and the jaw wedges it toward the hinge or squirts it past the tip. For a two-point grasp to resist anything at all the line joining the contacts has to lie inside the friction cone at both of them, and a pivoting jaw walks the contacts off that line as it opens. Roa and Suarez (Autonomous Robots, 2015) is the standard review of the grasp quality measures built on this, and the first family it surveys is exactly where the contact points sit on the object.

The second is five degrees of freedom plus the gripper, not six. Five joints cannot span pose. For any object position there is one family of approach azimuths, set by shoulder pan, plus roll about the tool axis. If the object's long axis lies badly relative to that plane the grasp is unavailable, and no training data changes it. Both arms in the SO-100 against SO-101 comparison share this layout.

Check the geometry yourself

bash
curl -sL -o so101.urdf \
  https://raw.githubusercontent.com/TheRobotStudio/SO-ARM100/main/Simulation/SO101/so101_new_calib.urdf

python3 - <<'PY'
import re, math
t = open('so101.urdf').read()
for m in re.finditer(r'<joint name="([^"]+)" type="([^"]+)">(.*?)</joint>', t, re.S):
    name, typ, body = m.groups()
    lim = re.search(r'lower="([-\d.]+)" upper="([-\d.]+)"', body)
    if not lim:
        print(f'{name:20s} {typ}')
        continue
    lo, hi = float(lim.group(1)), float(lim.group(2))
    print(f'{name:20s} {typ:10s} {math.degrees(lo):7.1f} to {math.degrees(hi):7.1f} deg'
          f'   span {math.degrees(hi-lo):6.1f}')
PY
Pull the URDF and print every joint limit in degrees, no simulator required

The gripper line prints -10.0 to 100.0 deg, span 110.0. That is the entire vocabulary your imitation learning run has for the word grasp.

The force ceiling LeRobot writes into your servo

The STS3215 takes a target angle, not a target force. Grip force is emergent: command the jaw past the object surface, the servo sees a standing position error and pushes with whatever the gains and torque limit allow. So grip force is not tuned in the policy. It is written into the motor, and LeRobot writes it on every connect.

python
for motor in self.bus.motors:
    self.bus.write("Operating_Mode", motor, OperatingMode.POSITION.value)
    self.bus.write("P_Coefficient", motor, self.config.position_p_coefficient)  # 16
    self.bus.write("I_Coefficient", motor, self.config.position_i_coefficient)  # 0
    self.bus.write("D_Coefficient", motor, self.config.position_d_coefficient)  # 32

    if motor == "gripper":
        self.bus.write("Max_Torque_Limit", motor, 500)   # 50% of max torque to avoid burnout
        self.bus.write("Protection_Current", motor, 250) # 50% of max current to avoid burnout
        self.bus.write("Overload_Torque", motor, 25)     # 25% torque when overloaded
lerobot/robots/so_follower/so_follower.py, configure(), main branch on 23 August 2026

Read the comments. The stock configuration gives the gripper half the available torque and half the current, dropping to a quarter once overload protection trips. That is sensible: the gripper spends its life stalled against something, and a stalled STS3215 cooks itself. It is also a firm bound on what your task can require, and better training does not negotiate with it.

Contact distance from the jaw pivotClamping force at the stock torque capNote
30 mmabout 32 Nstrongest, but the faces diverge most here
50 mmabout 19 Nmid jaw, the usual contact zone
70 mmabout 14 Nweakest, best face alignment

That is arithmetic on the published stall torque, not a measurement: 19.5 kg.cm is 1.91 N.m, half is 0.96 N.m, force is torque over lever arm. It is optimistic twice over, since stall torque is a peak not a continuous spec and Protection_Current usually trips first. Treat it as the number you never exceed.

7.4 V, and only 7.4 V

The STS3215 in an SO-100 or SO-101 is the 7.4 V part. Feeding it 12 V destroys the servos, and it destroys the gripper servo first because that is the one that stalls. Koch v1.1 uses Dynamixel motors on 5 V and 12 V rails and LeKiwi runs a 7.4 V arm on a 12 V base, so a bench with several arms is where this goes wrong. Label your supplies. If an arm started twitching and sagging after a power change, read arm twitches then sags first.

The policy is blind to contact

This is the entire observation the driver assembles per control step:

python
obs_dict = self.bus.sync_read("Present_Position", num_retry=self.config.num_read_retries)
obs_dict = {f"{motor}.pos": val for motor, val in obs_dict.items()}
so_follower.py, get_observation()

Present_Position, nothing else. The STS3215 reports current, load, voltage and temperature over the same bus, and none of it reaches the observation vector. So a vision-language-action model has two ways to know a grasp worked: see the object, or notice commanded and reported gripper position disagreeing because a blocked jaw stops short.

That second channel is weak. Across the 11939 frames of lerobot/svla_so101_pickplace the mean gap between commanded and reported gripper position is 2.34 on the 0 to 100 scale, buried in a channel that also carries servo lag. Hence the wrist camera, and hence the people bolting sensors onto the jaw. LeFlexiTac (May 2026) swaps the SO-10x stock jaw for a tactile gripper and reports an ACT policy going from 7 out of 30 to 23 out of 30 on retrieving a pen from a cluttered bag, where the camera sees nothing at contact.

Adding tactile sensing to the jaw
What it buys
  • A contact signal that survives occlusion, exactly when the jaw hides the object from the wrist camera
  • Large gains where vision is blocked: 23/30 against 7/30 in the LeFlexiTac pen-in-bag result
  • It changes the observation space, a real capability change rather than a hyperparameter
What it costs
  • The observation stops matching the stock so100_follower feature set, so existing datasets and checkpoints break
  • You now maintain a hardware fork, and every LeRobot upgrade becomes a merge
  • Every dataset in the public directory assumes the stock six-value state, so warm-starting from other people's data stops working
  • For pick and place on rigid objects it rarely pays for itself

Measure the gripper span in your own data before you train

Almost nobody does this, and it takes five minutes. Your episodes contain a gripper channel; look at its distribution, because a fine-tuning run reproduces whatever degeneracy is in there.

  1. 1
    Read the metadata

    info.json gives the format version, the robot type, the frame count and the names of the six action dimensions.

    bash
    curl -sL -o info.json \
      https://huggingface.co/datasets/lerobot/svla_so101_pickplace/resolve/main/meta/info.json
    python3 -c "import json;d=json.load(open('info.json'));\
    print(d['codebase_version'], d['robot_type'], d['total_episodes'], d['total_frames']);\
    print(d['features']['action']['names'])"
  2. 2
    Read the summary statistics

    stats.json holds min, max, mean and std per dimension. The gripper is the last of the six.

    bash
    curl -sL -o stats.json \
      https://huggingface.co/datasets/lerobot/svla_so101_pickplace/resolve/main/meta/stats.json
    python3 -c "import json;s=json.load(open('stats.json'))['action'];\
    print({k:[round(float(x),2) for x in s[k]] for k in ('min','max','mean','std')})"
  3. 3
    Get the histogram, not just the extremes

    Min and max hide the shape, and the shape is where the surprise lives. Download one parquet shard and bin the gripper channel.

    bash
    curl -sL -o f0.parquet \
      https://huggingface.co/datasets/lerobot/svla_so101_pickplace/resolve/main/data/chunk-000/file-000.parquet
    
    python3 - <<'PY'
    import pandas as pd, numpy as np
    df = pd.read_parquet('f0.parquet')
    g = np.stack(df['action'].values)[:, 5]
    n = len(g)
    print(f'frames {n}  episodes {df.episode_index.nunique()}')
    print(f'gripper min {g.min():.2f}  max {g.max():.2f}  mean {g.mean():.2f}')
    for lo, hi in [(0,5),(5,10),(10,20),(20,30),(30,40),(40,100)]:
        m = ((g >= lo) & (g < hi)).sum()
        print(f'  [{lo:3d},{hi:3d})  {m:6d}  {100*m/n:5.1f}%')
    PY
  4. 4
    Cross-check against a second dataset

    One dataset is an anecdote. Run the same query against lerobot/svla_so100_pickplace and see whether the pattern repeats.

    bash
    curl -sL https://huggingface.co/datasets/lerobot/svla_so100_pickplace/resolve/main/meta/stats.json \
      | python3 -c "import json,sys;s=json.load(sys.stdin)['action'];\
    print('min', round(float(s['min'][5]),2), 'max', round(float(s['max'][5]),2))"

Against lerobot/svla_so101_pickplace, 50 episodes of SO-100 follower pick and place at 30 fps, this is what comes back.

Gripper action band (0 to 100 of calibrated range)FramesShare
0 to 5 (effectively closed)840970.4 percent
5 to 102932.5 percent
10 to 2011539.7 percent
20 to 30193216.2 percent
30 to 401521.3 percent
40 to 10000.0 percent

The maximum across the dataset is 33.0. Per episode the widest opening runs from 17.8 to 33.0, median 23.2. Two thirds of the jaw's travel never appears once. lerobot/svla_so100_pickplace is a separate 19631-frame recording and stops at 34.93. The pattern repeats, and a policy cannot output an action it never saw.

The AY-Robots public dataset directory, listing LeRobot datasets with their episode counts
The public dataset directory. Before planning a task around a wide grasp, open a candidate dataset's stats.json and look at what the gripper channel actually covers.
gripper.pos is not a physical unit

In so_follower.py the arm joints use MotorNormMode.DEGREES (the use_degrees default) and the gripper alone is hard-coded to MotorNormMode.RANGE_0_100. That 0 to 100 is a percentage of the span you swept during lerobot-calibrate, which asks you to move each joint through its full range of motion. Sweep lazily and 100 means a narrow opening; sweep hard and it means a wide one. Recalibrating after a dataset exists silently changes what every recorded action meant. If a policy works on the arm it was recorded on and nowhere else, start at policy only works in one setup and check calibration before touching the model.

Why a narrow distribution is worse than it looks

ACT and SmolVLA in LeRobot default to MEAN_STD normalisation on the action vector, so each dimension is divided by its own standard deviation before the loss. (Pi0.5 is the exception: configuration_pi05.py sets QUANTILES for state and action.) The gripper's std here is 9.0, the smallest of the six, but normalisation compensates for scale, so it is not underweighted. The problem is worse: the channel is nearly constant, so it is nearly free to predict. A useful diagnostic is the copycat baseline from Wen et al. (NeurIPS 2020), where an imitator echoes the current state instead of predicting the next action. Predict action equals state, then take the mean squared error per dimension in those normalised units:

python
import pandas as pd, numpy as np, json
df = pd.read_parquet('f0.parquet')
a = np.stack(df['action'].values)
s = np.stack(df['observation.state'].values)
std = np.array([float(x) for x in json.load(open('stats.json'))['action']['std']])

mse = ((a - s) ** 2).mean(0) / std ** 2          # copy-the-state error, MEAN_STD units
names = ['shoulder_pan','shoulder_lift','elbow_flex','wrist_flex','wrist_roll','gripper']
for n, v in zip(names, mse):
    print(f'{n:16s} {v:.3f}   {100*v/mse.sum():4.1f}% of total')
Reproduces the table below from the parquet shard and stats.json downloaded above
Action dimensionCopy-the-state error (mean squared, normalised)Share of the total
shoulder_pan0.0255.7 percent
shoulder_lift0.0358.0 percent
elbow_flex0.0378.4 percent
wrist_flex0.09621.9 percent
wrist_roll0.0286.5 percent
gripper0.21749.5 percent

Four of the six dimensions sit under nine percent of that error each, and the one that decides success carries half of it while contributing one sixth of the loss. That is how a training curve stays clean while the arm reaches the object, hovers and never closes. If that is your symptom, loss falls but the policy does nothing and gripper does not close are the two pages to read.

Compliance is the cheapest accuracy you will buy

A rigid jaw demands the policy place the end effector within a millimetre or two. A compliant jaw deforms around the object, turning positioning error into a harmless bend. Dollar and Howe's SDM Hand (IJRR, 2010) argued for underactuated elastomer-jointed fingers on exactly that ground, and the Fin Ray effect gripper of Crooks et al. (2016) showed how far a passive triangular structure with buckling crossbeams goes.

The SO-ARM100 repository ships this. Optional/Compliant_Gripper holds Compliant_Moving_Jaw_SO101.stl and Compliant_Wrist_Roll_Follower_SO101.stl, replacing both jaws. They are hollowed out with internal ribs, printed in TPU 95A at 20 percent, and the README states the external geometry is identical to the rigid parts apart from some removed holes and the added cavities and ribs, so the assembly does not change. It came out of the June 2025 Hugging Face LeRobot hackathon and credits the Fin Ray Effect.

Rigid PLA jaws against compliant TPU jaws
Compliant TPU 95A
  • Tolerates positioning error, so fewer episodes cover the same envelope
  • Lower contact force on fragile objects, which the official README targets
  • Drop-in: the README states no change to the assembly or installation process
  • Widens the set of shapes one grasp pose works for, which is the same as widening what a policy gets away with
What you give up
  • The jaw deflects under load, so reported position stops mapping to a fixed gap and your weak contact signal gets weaker
  • Not every printer handles flexible filament; the README warns about print times and support removal
  • Heavy or slippery objects slip where a rigid jaw would hold
  • It changes what the gripper channel physically means, so rigid and compliant episodes should not be mixed

How much does jaw shape matter next to the algorithm on top of it? Georgadarellis et al. (17 March 2026, UMass Amherst) had eight participants open bandage packages across 15 bimanual trials in three conditions: bare hands, a modified UMI gripper spreading load across the whole finger face, and the same gripper with angled fingers concentrating load at the tips. The distributed version succeeded on 65.8 percent of trials; the concentrated version and bare hands on all of them. Hands were roughly 15 times faster than the distributed grippers and 4 times faster than the concentrated ones.

What varied was the angle of two printed fingers, and a third of the demonstrations stopped existing. Collect data through a gripper like that and you have a hardware problem wearing a policy problem's clothes. The UMI paper (Chi et al., 2024) makes the general case for handheld rigs; its own gripper is a 780 g printed parallel jaw with an 80 mm finger stroke, soft fingers, and a fiducial marker so finger width is logged continuously rather than as a flag.

What a policy can learn about your gripper, and what it cannot

Splitting this cleanly is the most useful thing to do before spending money on a run. One column is worth more data; the other is worth a different printed part.

BehaviourFixable with data?Why
When to close, given the wrist cameraYesVisual timing, what a VLA is good at
How far to open for an object classOnly inside the span your data used40 to 100 never appeared in the reference dataset
Landing the object mid-jaw, not at the hingeYesA pose preference, teachable by consistent demonstrations
Recovering after a slipPartlyOnly if slips and recoveries are in the data; operators usually restart instead
Clamping harder than the stock torque capNoMax_Torque_Limit 500 reaches the servo before the policy sees anything
Regrasping without releasingNoOne actuator, one hinge, no in-hand freedom
Grasping wider than 110 degrees of jaw travelNoKinematic limit in the URDF
Approaching from an azimuth 5 DOF cannot reachNoFive joints cannot span pose
Modulating force by feelNoPresent_Position is the only proprioceptive channel
A sanity rule

If the failure is when or where, more data usually helps. If it is how hard or from which side, more data almost never helps. Sort your failures into those buckets before renting a GPU. The failure mode index is organised along roughly the same split.

The AY-Robots failure mode index listing symptoms such as gripper does not close, joint stops early and policy only works in one setup
The failure index. Three entries here are gripper problems in disguise: gripper does not close, joint stops early, policy only works in one setup.

Getting a gripper-aware dataset onto a trained policy

Your machine, your arm. Know this path even if you never use it.

  1. 1
    Install LeRobot with the Feetech SDK

    The feetech extra talks to the STS3215 bus. Without it the gripper motor is invisible.

    bash
    git clone https://github.com/huggingface/lerobot.git
    cd lerobot
    pip install -e ".[feetech]"
  2. 2
    Calibrate deliberately and write down that you did

    This step defines what gripper.pos means. Sweep the jaw fully closed to fully open every time; recalibrate later and old datasets stop meaning what they meant.

    bash
    lerobot-calibrate \
      --robot.type=so101_follower \
      --robot.port=/dev/ttyACM0 \
      --robot.id=my_follower
  3. 3
    Record with the jaw span you intend to use

    Decide before episode one whether the task needs a wide opening, then use it. A dataset topping out at 33 has decided your policy never opens wider.

    bash
    lerobot-record \
      --robot.type=so101_follower \
      --robot.port=/dev/ttyACM0 \
      --robot.id=my_follower \
      --teleop.type=so101_leader \
      --teleop.port=/dev/ttyACM1 \
      --dataset.repo_id=${HF_USER}/gripper_span_test \
      --dataset.num_episodes=50 \
      --dataset.single_task="Pick up the block and drop it in the bin"
  4. 4
    Audit the gripper channel before training

    Run the histogram above against your own stats.json. If over 90 percent of frames sit in one bin, that dimension has nothing to teach.

    bash
    python3 -c "import json;s=json.load(open('meta/stats.json'))['action'];\
    print('gripper min',round(float(s['min'][5]),2),'max',round(float(s['max'][5]),2),\
          'std',round(float(s['std'][5]),2))"
  5. 5
    Train, and own the GPU problem

    ACT and SmolVLA fit a 24 GB card. GR00T and Pi0.5 need an 80 GB A100 or H100 you rent and babysit.

    bash
    lerobot-train \
      --policy.type=act \
      --dataset.repo_id=${HF_USER}/gripper_span_test \
      --output_dir=outputs/train/act_gripper
The trap that eats a day

The arm reaches perfectly and never closes, so you conclude the model is undertrained. Check three things first: does the gripper channel in meta/stats.json actually vary; was the arm recalibrated between recording and inference; is Max_Torque_Limit still 500. Five minutes each, and all three are likelier than the model.

A gripper checklist before you record 50 episodes

CheckHowIf it fails
Object sits mid-jawHold it in the jaws with torque offToo small or too round for a pivoting jaw
Widest opening the task needsOpen the jaw there and read the valueYour data has to reach that value, so plan the demonstrations around it
Rigid or compliant, decidedPick before episode oneMixing them mid-dataset changes what the gripper channel means
Wrist camera sees the contact pointWatch the stream while closingThe jaw occludes the object when the decision happens and the policy has no evidence
Approach azimuth reachableRotate the object 90 degrees and retryFive joints cannot get there; see joint stops early
Calibration date written downNote it with the datasetRecalibrating mid-dataset silently rewrites every recorded action

The recording workflow sits in the SO-100 data collection guide and in how to collect high-quality VLA training data. Still assembling? Start at the complete SO-100 setup guide.

Where none of this helps

  • No software layer changes the mechanics. AY-Robots can rent the GPU, size the run and serve the checkpoint; it can do nothing about a 110 degree jaw or a 50 percent torque cap. If the task needs a parallel jaw, a suction cup or three fingers, the answer is a different end effector.
  • All five trainable policies consume the same six-value action vector, so bolting a tactile sensor onto the jaw takes you off the supported path. Legitimate research, not something a hosted form absorbs.
  • Grasp planning is a separate discipline from behaviour cloning. If the real question is which grasp on a novel object is robust, that is what Dex-Net 2.0 answers: 6.7 million synthetic point clouds, 0.8 second planning, 93 percent success on eight known objects and 99 percent precision on 40 novel ones. A demonstration-trained SO-100 policy reproduces the grasps you showed it, nothing more.
  • None of it removes the need for consistent human demonstrations, and gripper timing is what separates a usable dataset from an unusable one. The operator page is where that side starts.
The AY-Robots operator page, Become a Robot Operator from anywhere in the world, with a photo of the SO-100 arm operators drive remotely
Remote operators driving SO-100 arms. Gripper timing varies most between people and matters most to the trained policy.

Record the span before you train on it

The desktop client records LeRobot-format datasets straight out of a teleoperation session, so you can set the jaw span you actually need and check it in meta/stats.json before renting a GPU.

Get the desktop client
Is the SO-100 gripper a parallel jaw?

No. In so101_new_calib.urdf the gripper is a revolute joint limited to -0.174533 and 1.74533 radians, that is -10 to +100 degrees. One jaw swings on a hinge against a fixed jaw moulded into the wrist roll follower body. The faces are only near parallel at the closed end of that 110 degree travel, which is why narrow grasps beat wide ones here.

Why does my policy reach the object but never close the gripper?

Check the gripper channel first. In lerobot/svla_so101_pickplace, 70.4 percent of frames sit below 5 on the 0 to 100 scale, so a model can drive that dimension's loss down while learning almost nothing about when to close. Then check the arm has not been recalibrated since recording, and that Max_Torque_Limit is still 500.

What does gripper.pos = 30 mean in millimetres?

Nothing fixed. LeRobot declares the gripper with MotorNormMode.RANGE_0_100 while the arm joints use DEGREES, so the value is a percentage of the span you swept during lerobot-calibrate. Two arms with different sweeps report different numbers for the same gap. For millimetres, measure with calipers at a few known values on your own arm.

Should I print the compliant TPU gripper?

Worth it for fragile, irregular or hard-to-align objects, which is what the official Optional/Compliant_Gripper README targets. It costs grip strength on heavy or slippery objects and weakens your one contact signal, since a deflecting jaw no longer maps position to a fixed gap. Decide before recording: mixing rigid and compliant episodes changes what the channel means partway through.

How many episodes do I need if I change the gripper?

Treat a jaw change as a new robot. Platform minimums are 30 episodes for SmolVLA and 50 for ACT, GR00T N1.5, GR00T N1.7 and Pi0.5, and those assume the data matches the hardware. A dataset recorded with rigid jaws does not describe a compliant one: the same commanded position gives a different gap and a different force.

Ready for high-quality robotics data?

AY-Robots connects your robots to skilled operators worldwide.

Get Started