Inspiration
Cornucopia Bio is a medical lab aiming at creating self-driving labs, and the challenge we solved was a problem that they faced and wanted a solution for, even lending us their equipment to build for it.
They move exact volumes of blood into vials for various blood tests. the volumes of the contents moved i miniscule, and the contents transported are incredibly sensitive and have to be handled with care. Today that work is either done by hand with a pipette, or it goes to machinery that costs more than most labs can justify (upto $250k).
Automated liquid handlers are very expensive since they're primed for extreme accuracy and high-precision, however this machinery is not easily adaptable to a lab's environment. They all get accuracy the same way, by deleting variables: fixed labware, fixed positions, a head that only ever performs one motion. That works until the lab needs to change something. The labware lives where the deck says it lives. A new protocol means new carriers and a recalibration.
We wanted to know if you could get that accuracy back on a ~$2k general purpose arm, two webcams, and nothing bolted to the table.
What it does
Laminar is able to conduct completely autonomous blood testing using actual medical and lab equipment in a manner that is adaptable to any lab and can accomplish a task that normally requires machinery worth 5-6 figures, for just ~$2k.
We built a live dashboard so that any scientist/lab technician can view the real-time data and analysis of past logs. They can send experiments to be conducted on the live dashboard, and the robot adaptively conducts stated experiments and shows real-time status through an interactive agent and live render.
How we built it
The arm. It's a Seeed reBot B601-RS: six RobStride joints plus a gripper, with RS06 motors on the shoulder and elbow (J1–J3) and RS00 motors on the wrist and gripper (J4–J7). We drive it from a laptop over a candleLight USB-to-CAN adapter at 1 Mbps, with no vendor stack in the loop. We wrote our own driver for the RobStride protocol (29-bit extended IDs) and run every joint in MIT mode (impedance control: position, velocity, stiffness and damping in one command) at 100 Hz. On top of that sit our own forward and inverse kinematics, joint limits, and a gravity and friction feedforward we measured on the real arm.
Motion. Grasps are taught by teleop from a StarArm 102 leader arm at 100 Hz, recorded per vial and played back exactly. Each transfer is a lift straight up 150 mm, a flat move at that height, and a lower down to the pour pose. Paths are smoothed and resampled before they're sent, so joint commands stay continuous at 100 Hz. Under load the arm sags 20–40 mm at full reach. We measure that from the joint encoders through the kinematics, correct it iteratively, and carry the learned offset into the next move. If the motors stop replying, the arm holds its position with torque, eases back to rest on commands alone, and turns the motors off only once it's at rest, so it never drops.
Cameras. One camera sits on the gripper in a mount we designed and printed, pitched 38° out over the fingertips so the vial lip sits just above the centre of the frame. The second watches the station from outside. It feeds OpenMind's OM1 runtime, which runs a vision-language model over the stream to identify the compartments, and our own level-segmentation model. That model classifies, pixel by pixel, the liquid in each channel's crop (resized to 128×32) and turns liquid height into millilitres.
Finding the station. ArUco markers on the tray (4×4 dictionary, IDs 10–13, 20 mm squares) are found from a fixed scan pose. Their corners give a pixel-to-table homography, and a rigid fit of the known marker layout gives the tray's position and heading in the arm's frame. Two visible markers are enough. Since the tray geometry is in CAD, every channel position follows from that one transform, so moving the station needs no recalibration.
Pour control. Tilting only the wrist roll joint tops out around 80°, too little to empty a conical tube. So the pour tilts the vial about a horizontal axis through its lip: a precomputed inverse-kinematics table across all six joints keeps the lip fixed, reaching 120–150°. The controller works from the delivered volume the side camera reads.
- Starting the flow: it searches for the moment liquid starts flowing (15°/s, then 6°/s past 40°).
- Hitting the target: it schedules tilt against the measured fill rate, and starts coming back before the target to cover the camera's lag, so it doesn't overshoot and correct afterwards.
- Aborting: it stops if a neighbouring channel starts filling, the column nears overflow, the camera drops out, or nothing flows at maximum tilt.
Dashboard. The bench laptop streams joint angles, gripper state and both camera views into Convex. Uploads use kept-alive HTTPS connections, several at a time, sending the newest frame only, which gives about 8–10 fps per camera with no backlog. The page subscribes with Convex live queries, so frames and telemetry are pushed over a websocket as they change. The arm on the page is a glTF export of the real URDF rendered in three.js, driven by the same telemetry. Runs are planned on the page and checked by plain code, not a model, then approved by a named person and executed on the real arm. Every run is logged to Tiger Data (TimescaleDB), including a 100 Hz joint trace we use to tune smoothness.
Challenges we ran into
Making the arm pour at all. The wrist joint stops at about 81°, but a conical tube needs 100–120° before liquid comes out. We rebuilt the pour as a tilt using every joint, pivoting on the gripper tip, which reaches 139°. Then the arm shook at 25.8 Hz because we were streaming joint targets too slowly. We fixed it by sending the whole motion once and letting the arm interpolate at 100 Hz.
Hitting a 10 mm target. Early pours landed one channel to the side, and one spilled over the tray's edge where the camera couldn't see it. The arm also sags 20–40 mm when stretched. We correct for the sag using the motor encoders, aim relative to ArUco markers on the tray, and made the wrong-column check fast enough to stop a pour mid-stream. Aiming reliably wherever the tray sits is still a work in progress.
Teaching a camera to read liquid. No existing model reads levels in a clear 12-channel reservoir, so we recorded our own fills and spills, auto-labelled them, and trained a small model to about 0.26 mL mean error. Glare, new camera angles and an overloaded laptop all made readings noisy.
Hardware that kept failing. The arm fell once when its CAN connection dropped, so we redesigned it to freeze in place instead. The USB-CAN adapter kept disconnecting, the gripper motor silently stopped responding, and a joint overheated to 81 °C while we taught poses.
Keeping AI safe around a robot. Claude plans and explains but never moves the arm. Every plan passes a plain-code checker, the spoken read-back comes from the checked plan rather than model text, and a named person must approve, whether the request comes from chat, voice or another lab's Fetch.ai agent.
Accomplishments that we're proud of
A real robot made a real, camera-controlled pour. The arm picked up a vial, carried it without sloshing, tilted it using all six joints, and filled container b from 2% to 75% (3.4 mL). The camera watched the whole time and stopped the pour on its own the moment liquid began reaching the next channel.
Safety is built into the code, not just promised. The arm can't fall: if anything fails, it freezes where it is. Claude plans and explains but can never move the arm. Every plan goes through a plain-code checker and needs a named person's approval, and every request, approval and pour lands in an audit log.
Hands-free voice control with Cartesia. A technician in gloves can say what they need, hear the plan read back, say "approve", and hear progress and results. The read-back is built from the checked plan, so what they hear is exactly what will run.
The full stack works together. A live dashboard shows the arm in 3D, both cameras and every pour as it happens. A chat assistant plans runs and answers questions about the bench. Telemetry streams to Tiger Data, and other labs can send test requests through Fetch.ai agents on ASI:One, still gated by a technician at our bench.
Built in a weekend by four people. That includes a robot arm, a custom vision model, voice, agents, a website, and over 200 automated tests.
What we learned
You can't buy precision at this price, but you can measure your way to it. A hundred thousand dollar liquid handler is accurate because the mechanism itself is accurate, and a €2,249 arm never will be no matter how carefully you write the kinematics, so we stopped trying to make the arm precise and started measuring what came out of the vial.
Most things shouldn't close the loop. The vertical distances never change so they're hardcoded, the transport came from teleop and plays back identically every run, and the only closed loop in the whole system is the wrist roll during the pour, because that is the only place the variance lives. We spent the first day making everything sensor driven and it came out slower and less reliable.
Finding the station got easy once we stopped solving the wrong problem. Recognizing twelve identical wells on a reflective tray is hard vision, but finding one ArUco marker is not, and the marker gives you the pose of the whole tray, so every channel position falls out of the CAD geometry we already had.
The thread through all of it is that we kept finding a cheaper version of the problem, whether that was precision from measurement instead of from the mechanism, a loop around one joint instead of seven, or one marker instead of twelve wells, and in every case the expensive version was the one we tried first.
What's next for Laminar
Next, we want to validate repeatability across more pours, volumes, and liquid types, and keep improving reliability when the tray or camera position changes. We also want to make it easier to configure new labware and protocols, and continue testing the safety checks and human-approval flow with real lab workflows.
Mana
We built the technician's planning and QC console entirely in Mana. The Laminar Lab Console has a protocol builder (typed, spoken or CSV plate maps), approvals with an audit log, a run log with a QC chart against our ±0.2 mL target, inventory for four liquids, a daily 08:00 QC report, and a "Beat the Robot" game. Mana's agents interpret protocols and write the QC summaries and the daily recommendation.
It's connected to the rest of Laminar, not a mock-up. Every 5 seconds it reads the latest jobs, pours and channel levels from our Convex backend. It sends plans to the bench, where they appear in our console marked "from Mana" and still go through the same code checker and need a named person's approval before anything runs. During the hackathon, those plans run on our simulated bench.
The moment Mana changed what we built: when our bench went from two liquids to four, one Mana chat updated the whole app (liquids, colors, inventory, per-liquid QC, the request format and volume limits), and it worked with our backend on the first try.
- App: https://www.mana.space/home/FFIzXyDYQwYiO0Q11ujq7sfya3A3/apps/laminar-lab?chatId=001792899827861bd784ede37a045cc9bfe
- Chat that built it: https://www.mana.space/shared-chats/FFIzXyDYQwYiO0Q11ujq7sfya3A3/0017928649411033251da4142324b3588aa
- Four-liquid chat: https://www.mana.space/shared-chats/FFIzXyDYQwYiO0Q11ujq7sfya3A3/001792899827861bd784ede37a045cc9bfe
Convex
The dashboard is a TypeScript app on Convex that updates live. Live queries drive everything: the console tabs, and on the landing page the 3D arm, the pour counter, the console stats and both camera views. Camera JPEGs are pushed as bytes through a live query, one document per camera overwritten in place, at about 210 ms from frame to screen. Each assay runs as a durable Workflow (component), one step per move, so Stop cancels it and a crash or redeploy can't strand a run; the Rate Limiter component protects the submit, voice and Mana endpoints. We also use crons (the 08:00 QC report), file storage (protocol CSVs, camera snapshots, cached speech), full-text search over the audit log, actions for Claude, Cartesia and Tiger, and HTTP actions for the bench and the Mana console.
Cartesia
Voice is how a gloved technician runs the arm. Hold the mic and speak: Cartesia Ink (ink-whisper) transcribes the request in a Convex action, and the audio is never stored. Cartesia Sonic (sonic-3.6) reads back the checked plan, says "Approved. Starting now.", calls out each pour and speaks the result; alerts are faster and louder, and repeated phrases are cached. "Stop" anywhere in a sentence stops the run. The read-back is built from the checked plan, never from model text, so what you hear is what runs. Limits: push-to-talk, not always listening; voice stop can't interrupt a move already running on the real arm (the hardware e-stop can).
Tiger Data
Every run logs a 100 Hz trace of all six joints (commanded, measured, error, velocity, torque) to Tiger Data hypertables, alongside every pour sample and event, with real-time continuous aggregates and compression to stay on the free tier. SQL functions in Tiger (run_smoothness, run_trace) compute per-joint tracking error, jerk and stick-slip, and the console's Tiger Data tab charts and replays any real run on the 3D arm. We used that data to fix the arm: fitting gravity and friction from Tiger traces cut the shoulder joint's tracking error from 2.16° to 0.28° RMS and peak acceleration by 41–74% on every joint.
Fetch.ai.
Our laminar-bench agent (uAgents, registered on Agentverse, Agent Chat Protocol) runs the whole workflow inside an ASI:One chat: a lab asks for a pour ("pour 1 mL of the blue dye into container 2"), the agent plans it and checks it in code, an approved technician says approve, the bench runs it, and status reports the camera-measured volume (1.005 mL against a 1 mL target in our test). During the hackathon these runs execute on our simulated bench. Try it in ASI:One: https://asi1.ai/invite?channelInviteKey=pt4JtuNel3NcRDi9tboMdn8FRmM-ZlFLqtr67PXQo30&utm_source=share&utm_medium=desktop&utm_campaign=channel_invite · agent address and run instructions in software/README.md.
Anthropic, Claude for Public Benefit
Who it's for: research and clinical lab technicians in small labs who aliquot blood by hand for coagulation and other tests, where a volume error changes the patient's result. Claude is the lab assistant between them and the robot: it turns loose requests into plans, asks instead of guessing, and explains each run. Where it could go wrong, and our guard for each: it could misread a request (the plan is read back and a named person approves); propose something unsafe (plain code rejects it before anyone sees it); or explain a result wrongly (the measured numbers sit next to its note). It can never move the arm.
Microsoft, Built for the Real World
Customer: a working lab gave us the problem and lent us their equipment. Measurable: a camera-controlled pour of 3.4 mL stopped on its own; our level model reads at about 0.26 mL mean error and catches 99% of spills into neighbouring channels. Path to production: a €2,249 arm instead of $16k–$2M liquid handlers, labware found with ArUco markers instead of fixed decks, protocols as data. Responsible design: the arm freezes on any fault, Claude can only propose, every plan passes a code checker and a named approval, and everything is audit-logged. End to end: request → plan → approve → pour → measure → report, live on the dashboard.
Built With
- anthropic
- asi:one
- cartesia
- claude
- convex
- fetch.ai
- mana
- numpy
- onnx
- opencv
- postgresql
- python-can
- tiger-data
- timescaledb
- uagents


Log in or sign up for Devpost to join the conversation.