-
-
The fleet console - live claims, lease countdowns, and fleet-wide semantic recall, all backed by CockroachDB Cloud
-
Fencing tokens - a robot paused past its lease presents a stale epoch and is blocked at the actuator, not just at the claim
-
The database stops a robot's wheels - a denied claim publishes a zero Twist to /cmd_vel; the robot's pose freezes at v=0.00
Inspiration
In a traditional application, a lost write shows somebody a stale page. For an AI agent, memory is not a cache it is the input to the next action. So a lost or racy write does not degrade the output; it makes the agent do the irreversible thing twice. In a warehouse, that is not a duplicate email. That is a collision.
We went looking for a domain where that distinction bites hardest, and found one nobody was serving. AWS discontinued RoboMaker in September 2025. Robot fleets still coordinate through ROS 2 DDS - ephemeral pub/sub-rosbag files, and per-robot SQLite. Coordination frameworks prevent resource conflicts in process memory, so a fleet-manager crash evaporates every allocation.
A warehouse fleet is a partition-prone distributed system whose agents take irreversible physical actions and cannot accept a maintenance window. That is textbook CockroachDB, in a sector that has never heard the pitch.
What it does
Warehouse robots share one CockroachDB Cloud memory layer. Every dock is a physical resource that exactly one robot may hold. Each agent recalls what the whole fleet has learned before it acts, and the database decides whether its actuator is allowed to move.
- Exactly one holder. A partial unique index -
UNIQUE (fleet_id, resource_id) WHERE released_at IS NULLmeans at most one live claim per resource. The loser gets a deterministic23505carrying the current holder, so it re-routes instead of retrying into a dock that will never free. Drop the index, re-run the same race, and both robots get the dock. That is the collision, and it is why the constraint is a safety mechanism rather than a schema detail. - No dual-write. A lesson and its 1024-dim embedding commit in the same transaction. There is no window where the row exists and the vector does not. A separate vector store cannot close that gap, and its drift is silent distances still compute, answers still look plausible.
- Isolation is structural.
VECTOR INDEX (fleet_id, embedding vector_cosine_ops)puts fleet isolation in the index prefix, not aWHEREclause a refactor could drop. - Fencing tokens. A lease bounds the claim; it does not bound the machine. A robot paused by GC can wake after expiry still intending to move. Every grant issues a monotonic epoch, checked before every commanded motion the mechanism Chubby, ZooKeeper, etcd and Kubernetes use.
- Memory has a lifecycle. Lessons carry event time and ingestion time, validity
windows, and supersession.
recall(as_of=...)answers "what did the fleet believe on Tuesday, and why did it act that way?" what incident reconstruction under ISO 3691-4 and ANSI/RIA R15.08 requires of an autonomous system. - It drives real ROS 2.
ros2/fleetmem_bridge.pyis a realrclpynode: subscribesnav_msgs/Odometry, publishesgeometry_msgs/Twist, and calls the database between them. A denied claim publishes a zero Twist - the denied robot's wheels physically stop.
How we built it
CockroachDB Cloud (London, aws-eu-west-2) is the system of record: claims, fleet
memory with a distributed vector index, durable agent checkpoints, and an audit trail.
AWS provides the intelligence and the hosting - Bedrock Titan Text Embeddings V2 for the 1024-dim vectors, Bedrock Amazon Nova Pro via the provider-agnostic Converse API for agent planning, S3 for bulk incident artifacts, and EC2 behind Caddy with automatic Let's Encrypt TLS.
A FastAPI service runs the simulation and streams state over WebSocket to a three.js console; a separate ROS 2 bridge runs the same memory layer against real robot topics.
Every claim is a serializable transaction. Serialization errors (40001) are retried with
exponential backoff and jitter un-jittered retries re-collide in lockstep and reproduce
the contention they are backing off from.
Challenges we ran into
Four of our worst bugs were silent - which is exactly what this project is about.
- The UI reported
reasoning: bedrockwhile running a local fallback. The probe only checked that a boto3 client could be constructed. - The fix then "verified" Bedrock by calling STS which succeeds with credentials that have no Bedrock permission at all. It lied more convincingly. Valid credentials are not model access. It now performs a real inference call.
- A totally failed race returned HTTP 200 with an empty list, because exceptions raised in worker threads never reach the caller.
- Adding connection pooling silently disabled our 40001 retry path in psycopg3
SerializationFailuresubclassesOperationalError, so the new pooledconnect()swallowed retryable conflicts and reported them as an outage.
Our own fencing implementation became the bottleneck it was meant to protect. The first
load test leaked 202 serialization failures. The cause was SELECT MAX(epoch)+1, which
forced every concurrent claimant to read the same rows the locking mechanism had become
the dominant source of lock contention. Moving to a non-transactional sequence took errors
to zero and throughput from 18 to 45 ops/sec.
Anthropic inference profiles were unavailable in our AWS account. Because we had built on the Converse API, switching providers was a config value rather than a rewrite.
Accomplishments that we're proud of
Everything below is measured, and each has a test that fails loudly if it regresses:
- The race is proven both ways. With the index: one holder. Drop it: two holders, a collision. A gate nothing can fail is a bug wearing a safety costume.
- Node kill mid-workload: 80 operations, 56 after the kill, 0 failures.
- Load: 50 robots over 8 contended resources - 45 claim ops/sec, 0 errors, 0 double-holds, with the invariant asserted continuously during the run.
- Connection pooling: 1065ms → 20ms median query.
- Real ROS 2: a denied robot's integrated pose froze for eleven seconds and resumed the instant CockroachDB granted its claim.
- Fencing proven through ROS 2: we stole a moving robot's claim; it logged
FENCED: presented epoch 11, current is no live claimand its velocity dropped to zero on the next tick.
What we learned
A correctness mechanism added without a load test can degrade the very property it protects, and no functional test will reveal it. Our fencing tokens were correct and made the system markedly worse under contention.
Checking something adjacent to your claim is not checking your claim. All three false-reporting bugs shared that root cause constructing a client instead of calling it, calling STS instead of Bedrock, counting rows at the end instead of detecting overlap.
Production agent-memory research is converging on exactly this stack: vector search, structured queries for history and metadata, and ACID transactions when multiple agents share state. That is CockroachDB, described from the agent side rather than the database side.
What's next for FleetMem
- Cross-container and multi-machine DDS discovery both ROS nodes currently share one container, which removed the highest-risk failure mode but leaves the distributed case unproven.
- Widen the agent's output schema. It picks a target and a speed, so negotiation and preemption are out of reach for any model a design limit, not a model limit.
- Multi-region topology. We exercise cross-region latency (London cluster, us-west-2 compute) but not a true multi-region cluster.
- Outcome-weighted memory - record whether acting on a lesson actually helped, and rank recall by measured usefulness rather than cosine distance alone.
Built With
- amazon-bedrock
- amazon-ec2
- amazon-web-services
- cockroachdb
- docker
- fastapi
- model-context-protocol
- pgvector
- python
- ros2
- three.js
Log in or sign up for Devpost to join the conversation.