From a software developer to robotics engineering.
Studying Controls, Dynamics, and Machine Learning to create congruent robots that
assimilate and integrate into our daily lives.
I spent the last three years working at NCR Voyix, building
production-grade merchandising, inventory, point-of-sale, and payments architecture serving
multiple retailers in North America.
I graduated with a B.S. Comp Sci from Georgia Tech in 2023 and I have now returned to pursue -
what I believe to be the next jump in technological advancement - a M.S. Robotics! I am excited
to apply my test-driven, always curious mind to solve complex, multi-step problems in controls,
dynamics, mechanics, machine learning, and HRI.
I now pursue research at the RAIL Lab with Prof. Sonia Chernova. I build a VLM task
planner that helps household robots, a Stretch 4 and a Sprout humanoid, carry out long,
multi-step tasks and recover when a step fails. I also keep notes on this work. You can read
more blog entries here.
Before that, I worked with the IVA Lab on gap cost functions (endpoint proximity and width
clearance), density features for a GRU-based planner, and Kalman filter optimizations for
motion and planning related tasks in dynamic environments.
Outside of lab work I am building a geriatric, bed-side, caregiving robot. My main research focus is
on evaluating shear stress requirements for garment manipulation and then implementing a consent-gated
system through irreversible intermediate steps.
02 — Research
Six threads, one question.
How does a machine act safely when its model of the world is wrong,
incomplete, or about to go stale?
Hierarchical STAG: One Factor Graph From Fleet to Wheels
In progressBorg LabProf. Frank DellaertAug 2026 — Present
A warehouse robot answers three questions at once. Which job do I take? Which path
through the aisles? What do I send to the wheels right now? Most stacks give each
question its own module. H-STAG puts them in one hierarchy of factor graphs in GTSAM.
Each level sends a sub-goal down and a summary of what it saw back up.
My latest piece is the control architecture at the bottom of that stack. It has three
layers. A decision layer picks a path of grid cells that satisfies a task, such as
“visit X1, then X2”. A motion layer turns those cells into a smooth plan every 0.5 s.
A feedback layer tracks that plan on a unicycle robot every 0.05 s. The decision layer
works on a discrete space. The other two work on continuous states and share one GTSAM
graph, solved with Levenberg–Marquardt.
3
Layers, discrete to continuous
0.05 s
Feedback step
8
Stages in every tick
One tick of the loop in the interactive inspector. You can step through
all eight stages on data recorded from a real run.
Most of the logic lives in the seams
The layers are simple on their own. The hard part is passing information between them.
A plan adapter turns cells into motion objectives. Decode turns the motion plan into a
reference for the feedback layer. Encode sends the measured position back up. The
inspector shows which factor pulls on which variable at each of those hand-offs.
The full run. The robot passes through X1, then reaches X2. Task done.
Decision layer on top. Motion and feedback layers share one GTSAM graph below.
To test all of this I also built the warehouse in NVIDIA Isaac Sim. Pallet trucks and
counterbalance AMRs drive through palletizing, staging, drop, marshalling, and outbound
dock zones. The scene gives ground truth to check the estimates against.
Conductor: A VLM Planner That Watches Its Own Skills
In progressRAIL LabProf. Sonia ChernovaAug 2026 — Present
“Go to the living room, pick up a glass, and place it on the kitchen table.” To a
person that is one task. To a Stretch 4 it is a long chain of small steps. The skills
that move the robot are local specialists. Each one sees only the current camera frame.
It has no map, no memory, and no idea of the full task.
So a VLM planner composes the task from a fixed library of eight primitives. It calls a
skill only after a grounder confirms that the object is in view and in reach. Skills are
objects that can be stopped, not calls that block. The planner watches each skill while
it runs and stops it the moment something is wrong. A monitor decides success by
looking. It never trusts the planner or the skill.
8
Primitives, closed vocabulary
3
Supervision tiers, by cost
−180 ms
Image-processing latency
One episode, end to end. Each dashed band runs at a different rate. Click to enlarge.
Cheap checks do most of the work
Each skill call declares a stop predicate, such as “glass in gripper”. The supervisor
checks it every tick while the skill runs. The monitor checks it again on a fresh
observation after the skill ends. A VLM look at every frame would cost about a second
and real money per check. So supervision has three tiers. A free tier runs at 10 Hz and
reads the skill's own status: empty grasp, stall, timeout. A grounding tier runs at
1 Hz and asks if the object is still in view. A VLM tier runs at 0.2 Hz, and only when
the planner asks for it. A test asserts that the default run makes zero model calls.
One pick-up, seen at four rates. The free tier fires on every tick. The VLM tier almost never fires.
Memory that does not go stale
Task memory keeps plans that worked. Each coordinate is replaced by the query that found
it, such as “the drinking glass on the coffee table”. The trace says what to do. The live
RGB-D image says where. Move the glass and the trace still applies. Global memory keeps
success rules and named failure modes with their recoveries, such as an empty grasp or
a false claim of success.
I also moved every Stretch 4 sensor stream across Zenoh and ROS 2 DDS, which cut
180 ms from image processing.
UnannouncedSolo research projectGeorgia TechAug 2026 — Apr 2027
A simulated two-arm robot helps a caregiver change an incontinence brief on a care
bed. The robot assists. It does not replace the caregiver.
Most safety work bounds the force pushed into a surface. For aging skin that is the
wrong quantity. Skin tears from sideways drag well below the push limit. Sliding a
brief out from under a body is drag. So I estimate shear in real time and check it
against a second sensor in the bed. Furthermore I am attempting to design a care-giving
bed that works in conjunction with the robot.
The recovery system also has to know which steps it can undo. Stopping is not always
the safe move.
2 / 19
Sprints done, both early
12
Decision records written
12 GB
GPU budget. One laptop.
Sprint 2. The simulator and the robot-description tools read one model,
so both views move together. Building it surfaced three bugs. One let an arm pass
through its own torso!
Isaac Sim / Isaac LabROS 2C++20Safety-Critical ControlDeformable Manipulation
Code and write-up stay private for now.
Learned Gap Density Estimation for Reactive Navigation
Dynamic Gap steers a robot through the gaps between pedestrians. Its Kalman filter
assumes linear obstacle motion. It also puts false velocity on static obstacles. We
have explored using a GRU to correct this behavior.
Density alone is a snapshot. In one collision the chosen gap went from 0.12 to 2.79
in six planning cycles. By then the robot had committed. So I added two features. One
is the rate of change of density. The other is aspect ratio, 1/θ, which tells a
corridor apart from an opening.
For one experiment, I ran three configurations over 50 episodes each. With the planner config held fixed,
the future-density model cut collision events by 30%. The goal rate rose from 53% to
67%. No episode got worse.
−30%
Collision events
53 → 67%
Goal rate, hardest episodes
0
Episodes regressed
Candidate trajectories through each gap. The purple arc is the selected
one. Each gap shows its predicted and true density.
The useful result was a failure
Predictions tracked ground truth. Navigation still got worse. Walls carry near-zero
density, so minimising it pulled the robot into them. The planner also blew its
10 Hz compute budget and fell back to idling. Neither problem was in the network.
Retuning three planner weights cut the mean path from 52.7 m to 30.0 m.
I also rewrote two gap cost functions. The old width term had no ceiling and reached
540 at a 1 mm gap. It now rejects gaps the robot cannot fit through, then prices
the clearance that is left.
Fifty episodes each. No configuration wins every row.
A 65-pedestrian benchmark I built. Each zone stresses one skill.
Two quadrupeds search an 11 m arena of 18 trees for a moving target. There is no
central controller and no shared memory. Each agent has its own gait control, eight IR
rays, and a private belief. They exchange belief over UDP at 10 Hz and fuse it by
covariance intersection.
10s / 12s
Time to find, each agent
< 0.2
Track uncertainty over 120 s
0
Collisions in the tree field
Both agents sweeping the forest.
A second run, closing on the target.
So far, I've found and fixed six defects. Most originated from unexpected places.
One check decided when the robot could say it had found the target. It mixed position uncertainty with
speed uncertainty, and speed uncertainty is always far larger. The total never fell below the threshold, so
the robot kept reporting a search it had already finished.
Frontier navigation also failed and a fixed waypoint approach replaced it. That works in this
arena. It cannot work in an unexplored one so fixing that is the next problem.
Ethiopian languages have little labelled data. Pretrained models handle them poorly. I
built a fastText classifier to separate the languages first. I then ran graphSAGE over
the interaction graph to find inflammatory accounts on twitter during the Ethiopian elections.
The work shipped into Aggie, an open-source platform used for election observation.
Notes from my first semester building household autonomy in the RAIL Lab.
September 2026 · RAIL Lab · Prof. Sonia Chernova
Teaching two very different robots to help at home
This fall I joined the Robot Autonomy and Interactive Learning Lab. Our goal for December
is easy to say. We want a robot that does household tasks on its own. Our test sentence
is “Go to the living room, pick up a glass, and place it on the kitchen table.” To a
person that is one task. To a robot it is a long list of small decisions, and any one of
them can go wrong.
We have two robots, built by two different companies. Stretch 4 from Hello Robot is a
mobile manipulator: a base, a tall mast, and one long arm. Sprout from Fauna Robotics is
a humanoid. Early on I pushed for a hardware abstraction layer, so the same autonomy
work runs on both and we do not write everything twice.
Photoshoot day. Please do not ask about the controller in my hand.
Stretch reaching into the fridge. Opening it is on our task list.
My part: the planner
I build the VLM planner. It reads the task, looks at the camera images, and checks its
memory. Then it breaks the task into a short list of primitives: navigate, pick up, put
down, observe, and four analytic calls. It calls a skill only after a grounder confirms
the target is in view and in reach. Another team builds the skills themselves: grab,
pick and place, and move. The planner decides what to do next. The skills do it.
A planner is easy to demo and hard to trust.
So most of my time goes into what happens when things go wrong. Monitor nodes run at
three rates and cancel a skill the moment it fails. Memory keeps the plans that worked,
and the failures we have already seen with their recoveries. The least exciting win so
far was plumbing. I moved every Stretch sensor stream across Zenoh and ROS 2 DDS, and cut
180 ms from image processing.
Cleaning up a room is on the list too. The toy airplanes are not impressed.
Stretch handed me my glasses. It found them faster than I usually do.
Meet Sprout
Sprout is the other half of the project. It walks instead of rolling, so every task
needs balance before it needs a plan. It is also, as far as I can tell, the only member
of the lab who naps on the job.
Sprout between test runs. Humanoids need rest days too.
Back on its feet and warming up. We are still deciding if that counts as a dance.
Same question, different building
My other lab this fall is the Borg Lab, where the robots live in a warehouse instead of
an apartment. There I work on H-STAG, the factor-graph hierarchy in the Research section
above. The two labs look nothing alike, but they ask the same question. How does a robot
turn one high-level goal into thousands of small, safe actions, and notice when one of
them fails?
Coming from software, I thought the hard part would be the code. Mostly it is not. It is
the gap between what the robot thinks happened and what actually happened. Closing that
gap is the part I like most. More notes soon.
A TurtleBot3 turns in place to keep a red ball in front of its camera. Three ROS 2 nodes
split the work. find_object runs on the robot and finds the ball in each
frame. It publishes the ball's pixel position in a custom ObjInfo message.
rotate_robot turns that offset into a turn command.
disp_cam runs on a laptop and draws the detection for debugging.
Red sits at both ends of the hue circle, so the detector joins two HSV bands. A
morphological open removes background specks. A close fills in the white print on the
ball. The largest contour wins. The turn rate is proportional to the pixel offset, with
a 20 px deadband and a 0.55 rad/s cap, so the robot slows near center and does not
overshoot. The camera topic keeps only the newest frame, so the controller never acts
on an old image.
3
ROS 2 nodes, robot and laptop
20 px
Deadband at center
0.55 rad/s
Turn-rate cap
The node graph. Everything but the debug view runs on the robot.
I ran one LLM agent at three decision frequencies over 2024. Five stocks, 1,585 API
calls. Monthly reached a Sharpe of 1.10 against 1.17 for daily. It cut maximum
drawdown by 36% and used 95% fewer tokens. Weekly was worst of the three.
A chatbot for portfolio and market questions, on the BlackRock Aladdin API. It sorts
holdings by sector, ranks performers, and recommends against a stated risk appetite.
Dialogflow handles intent. Flask serves the charts.
Shipped contributions to the test runner.
Run All Specs,
over a recursive multi-folder spec list. The in-app Cloud integration, split across a
Debug page header
and a
Debug spec component.
Its GraphQL schema is generated from type definitions. A
fix
for component hover in the command log. I also built a nightly system that tests each
binary against pre-release packages.
Launched a point-of-sale product for small and medium businesses. Led a TypeScript
payments integration across APAC and EMEA. Cut self-checkout queue wait by 23.6 s with
ID pre-validation.
I like planning multi-day trips, figuring out resources required with deadlines and
no resupply. Love the journey, the views, and the difficultly all this brings!
So far: Rainier, Glacier, Grand Teton, Yellowstone, Yosemite, Zion, Bryce Canyon, Great
Smoky Mountains, Crater Lake. The alpine trips take the most planning.