Who wants this, and why
A proposal engineer at a company that sells and installs tote-carrying robots (AMRs) gets the same request every week. A customer sends a PDF of their warehouse and a number: "110 orders an hour at peak". They want to know how many robots, and why.
The engineer needs three things for the answer: a model of the building that matches the drawing, a simulation of the peak with two, three and four robots, and a pack to send back (a drawing with the routes and the chargers, a comparison table, and the files behind them). They want their own AI to do the modelling and the runs, and to see the evidence themselves.
What they had
- The customer's drawing: CV-U4-GA-101 Rev C, a ground floor general arrangement on A3 at 1:250 with dimensions in millimetres. It shows a 60 × 40 m building with twelve shelving rows in three pick zones, a pick-and-drop (P&D) stand at the end of each zone, four packing benches near three dock doors, four rows of pallet racking, an office, and an area marked "possible AMR charging". (PDF)
- The customer's brief: 110 orders an hour at peak, one tote per order, 45 / 35 / 20 % of orders from zones A / B / C, four packers at about 100 s an order, and an offer to move the packing benches nearer the pick zones if that saves robots.
- The integrator's datasheet for its robot: one tote, 1.0 m/s average for layout studies, 12 s to take or put a tote, a 0.5 m radius for routing, and 6 minutes on the charger per hour of peak work. (brief and datasheet)

The drawing the agent was given. The customer, the site and the robot are fictional.
The drawing is made, not received. To measure the agent's error we needed the true layout. So we first built the warehouse in Laydyne with direct tool calls: that model is the answer key (answer-key.laydyne.json). We drew it as a 2D sheet, replaced Laydyne's title block with a customer's one, converted the dimensions to millimetres and printed it to PDF. The agent worked in an empty folder that held only the PDF, its PNG and the brief. We checked every shell command and every script in its session record: it read nothing outside that folder while it built the model.
The flow
- The engineer puts the drawing, the brief and the datasheet in a folder.
- The agent lays the PDF under the floor, scales it and reads it.
- It builds the warehouse and checks it against the drawing.
- It describes the tote transport and the charging, and Laydyne simulates the peak.
- Laydyne compares two, three and four robots, then the customer's layout change.
- The agent writes the proposal pack.
The steps, their tools and their outputs are in flow.json.
Step by step
The agent was Codex (CLI 0.160.1) with the engineer's default model, gpt-6.1-sol, at medium reasoning effort. It was connected to Laydyne Studio over MCP. Each step was a new session: what was built stays in the Studio tab and in the folder, so each prompt says only what to do next. These are the prompts as sent.
1. The building from the drawing
I'm a proposal engineer at Brindlefield Automation; we sell and install tote-carrying AMRs. A customer, Corrin & Vale Homewares, has sent the layout of their warehouse: CV-U4-GA-101_RevC.pdf in this folder (the PNG of the same sheet is attached). Their brief and our robot's datasheet are in brief.md.
First I need an accurate model of the building in Laydyne. Start a new project (create_space with fresh: true), lay the drawing under the floor with set_underlay, set the scale from the 60 000 dimension and check it against the 40 000 one, and read the drawing with trace_underlay. Then build what matters for the robots' routes: the outer walls with the columns, the doors and dock doors, the office, the shelving rows R01–R12, the pallet racking PR1–PR4, the three P&D stands, the four packing benches, the dock levellers, and the zones on the drawing (pick zones, packing, outbound staging, reserve storage, the possible charging area), with the drawing's labels, each part with source "drawing". Check the model against the drawing with measure_underlay_fit and fix what is off, run check_space, and show me the 2D plan and a 3D view. Save the project in the browser (save_local_project). Tell me what you could not read from the drawing.
This took 4 min 46 s and 26 tool calls. The agent sent the PDF to Laydyne in four pieces; Laydyne drew the page in the Studio tab. It set the scale from the 60 000 line and read the 40 000 line as 40.014 m. Then it built 72 parts from the candidates trace_underlay returned, each marked as read from the drawing.
- Fit to the drawing:
measure_underlay_fitput the model's edges a median 1.4 mm and a 95th percentile 49.6 mm from the drawn lines. - Checks: no overlaps, blocked doors or parts outside the floor. All three P&D stands and all four benches were reachable for a robot of 0.5 m radius.
- What it could not read: wall and column heights, door heights, workstation heights, the dock pits and what the unlabelled fixture in the office is. It noted each as unconfirmed.
One edit failed: three doors read off the drawing reached a few millimetres past the outer wall face, so Laydyne refused the edit. The agent clamped them and sent it again.

The agent's own check: the model drawn over the customer's drawing (the underlay, faint).

The agent's 3D view of the rebuilt warehouse.
2. The tote transport, with charging
The warehouse model is open in Laydyne (built from the customer's drawing in this folder). Now set up the robot tote transport described in brief.md, for the customer's peak. Read get_process_catalog first.
Add chargers for the robots in the customer's possible charging area (one per robot, up to 4) if that works for the routes. Finished totes appear on the three P&D stands at the brief's rates; the nearest free robot takes each tote to a free packing bench and puts it on the bench; a packer then packs it. Use our datasheet for the robot. Represent charging as well as Laydyne allows, and tell me exactly how you did it and what it leaves out.
Run it for the peak with 3 robots (at least 20 runs, with a warm-up), and tell me the throughput, how long totes wait on the P&D stands for a robot, the robot utilisation, the distance each robot drives and the packer utilisation. Look at a moment of the run in a 2D picture to check that the robots go where they should.
This step took 15 min 3 s and 131 tool calls. The agent put three chargers (and a spare) in the marked area, each with 1.5 m clear in front. It described the work like this:
- Totes appear on the stands at random (Poisson) at 49.5, 38.5 and 22 an hour.
- The nearest free robot drives to the stand and takes the tote (12 s).
- It waits there until a packing bench is free. A bench takes one tote at a time and is reserved from the moment a robot sets off for it until the packer has finished.
- The robot puts the tote on the bench (12 s) and is free again. Packing takes 100 s on average (a mix of two triangular distributions).
- Charging: Laydyne has no battery model, so the agent added three 6-minute charging jobs an hour, 20 minutes apart. Each takes the nearest free robot to a charger. It said this models the time and the trips, but not the battery level, and does not guarantee that each robot gets its share.
The runs were three hours each, 30 minutes of them warm-up, 20 times with the same seeds. Three robots did not keep up: 94.2 totes packed an hour against 110, a mean 15.3 minutes for a tote to be collected, and the robots busy 99.9 % of the time.
The agent did not stop there. Unasked, it tried the customer's offer, the benches moved south next to the pick zones: 109.0 totes an hour, a 2.3-minute wait and 86.0 % busy. It then restored the original layout.
Most of the 131 calls went on one detail of the prompt: "the distance each robot drives". Laydyne reports the fleet's distance over all runs, but each robot's distance only in the first run's recorded routes. So the agent ran the 20 seeds again one at a time for each layout, 40 extra runs, to read each robot's trace. We have recorded this as an open issue.
3. Two, three or four robots
The tote transport scenario from before is saved in the Laydyne project. Now I need the fleet size. On the current layout, compare 2, 3 and 4 robots under the same arrivals, seeds and number of runs (sweep_process_parameters is fine for this). Then save the current layout with the fleet size you would recommend as option A with save_process_option.
Tell me, with the numbers and their 95 % intervals: throughput against the 110 orders per hour, tote waits on the P&D stands, robot utilisation and distance, and which differences between the fleet sizes are beyond the noise. Which fleet size would you put in the proposal for this layout, and why?
This took 3 min 1 s and 17 calls; one sweep_process_parameters call ran all three fleet sizes on the same 20 seeds.
| Benches where they are | 2 robots | 3 robots | 4 robots |
|---|---|---|---|
| Orders packed an hour (agent's count, whole run) | 73.2 | 92.15 | 97.33 |
| Robots busy | 100 % | 99.9 % | 98.7 % |
| Mean wait at stand A for a robot | 34.3 min | 16.0 min | 10.8 min |
Every step up was beyond the noise. But even four robots fell short of 110. The agent proposed four "with a stated capacity gap". It also pointed at the cause: the benches were reserved 98.7 % of the time. Its model reserves a bench for the whole drive from the stand plus the packing, so each long drive across the building holds a bench and its packer idle. On this layout the benches, not the robots, set the limit.
4. The customer's layout change
The customer offered one layout change: they could move all four packing benches to the open floor south of the pick zones (between the P&D stands and the office); outbound staging and the docks stay. Try it in Laydyne: move the benches there, keeping clear aisles for the robots and away from the office door, and run check_space. With the same arrivals, seeds and runs, find the smallest fleet that keeps up there, save that as option B with save_process_option, and compare option A (saved before) with B using compare_process_options.
Tell me which differences are beyond the noise, what the move gains and what it costs that the simulation does not count (for example, packed parcels now further from the docks). Then give me your recommendation: fleet size and layout.
This took 1 min 58 s and 17 calls. The agent moved the benches into a row 4 m south of the P&D stands, 5 m apart and 3.8 m from the office partition, and checked the layout. A sweep of one to four robots there found three to be the smallest fleet that keeps up. It saved that as option B and compared it with option A (four robots, benches where they are) on the same 20 seeds:
| Per peak run, mean of 20 | A: 4 robots, benches where they are | B: 3 robots, benches moved | B − A, 95 % interval |
|---|---|---|---|
| Jobs completed an hour after warm-up* | 102.4 | 112.1 | +7.3 to +12.1 |
| Mean wait for a tote at stand A | 10.8 min | 2.3 min | −10.6 to −6.5 min |
| Robots busy | 98.7 % | 85.7 % | −16.5 to −9.6 points |
| Robot travel, whole fleet, 2.5 h | 15.5 km | 5.7 km | −10.0 to −9.6 km |
| Jobs unfinished at the end | 40.5 | 10.9 | −35.7 to −23.6 |
\*Including the three charging jobs an hour; 110 orders plus 3 charges arrive an hour.
All of these differences are beyond the noise. One robot fewer, and the totes are collected in about a quarter of the time. The agent listed what the move costs that the simulation does not count: packed parcels now further from the docks (more handling or a conveyor), moving power and data to the benches, downtime to move them, and people working near the office door. It recommended option B with three robots, and checking the parcel transfer on site before purchase.

From the two sweeps (20 runs each, the same seeds). Moved south, three robots keep up and the fourth adds nothing beyond the noise.
5. The proposal pack
Now prepare the proposal pack for Corrin & Vale in this folder:
1. Drawings as PDF: the dimensioned plan of the recommended layout with the zones, the chargers and the packing benches (get_view_image, delivery sheet as PDF; join the pieces and check the SHA-256), and the robots' routes from the run of the recommended option (get_process_drawing).
2. The fleet comparison as a CSV table: each option and fleet size you ran, with throughput, tote wait on the P&D stands, robot utilisation, distance per robot and packer utilisation, with the 95 % intervals where you have them. Also save the saved comparison of A and B as JSON (get_process_comparison).
3. The whole project as a .laydyne.json file (export_project; check the SHA-256), and save it in the browser again.
4. A short summary.md for the customer's operations director: the recommendation, the evidence, the assumptions taken from their brief and our datasheet, and what this simulation does not cover.
Tell me where each file is.
This took 6 min 26 s and 10 tool calls, plus 91 shell commands. The plan came as a PDF from one Laydyne call. The routes did not: get_process_drawing gave HTML or SVG only. The agent spent 19 shell commands making a PDF itself. It looked for a converter: the system's Chrome crashed in the sandbox, pip had no network, and a Python environment it found in another folder lacked the libraries. In the end it found rsvg-convert installed on the machine. On a machine without such a tool there would have been no PDF of the routes.
We added a PDF format to get_process_drawing and ran step 5 again with the same prompt from the same starting point. This time the routes PDF came from two Laydyne calls and 2 shell commands. The step still took 6 min 13 s: the agent used the time to run option A again for its evidence, and it wrote the 1.7 MB project file through 88 shell commands, one piece at a time (an open issue). The files below are from this second run.

The first run's recorded routes of the three robots in option B, from get_process_drawing as a PDF. The lines join the recorded points; they do not show congestion or speed.

The dimensioned A3 sheet of option B: the benches moved south, three chargers (and a spare) by the east wall.
The pack: the plan and the routes as PDFs, the fleet table, the saved A/B comparison, the project and the agent's summary for the operations director.
Results
The rebuild against the answer key
We scored the model from step 1 with the same script as the photo use case. It places the rebuild on the answer key by the middle of its walls and matches parts of the same class, nearest first. Shelving and racks count as one class, and stands and benches as another, because the agent may name the same thing by a different kind. The full results are in score-first-run.json and score-rerun.json.
| Answer key | First run | Run again after the fix | |
|---|---|---|---|
| Building | 60 × 40 m | 60 × 40 m | 60 × 40 m |
| 40 000 dimension at the 60 000 scale | 40.000 m | 40.014 m | 40.06 m |
| Parts matched (of 62 outside zones and walls) | — | 60 | 60 |
| Position error: median / 95th pct / max | — | 6 / 17 / 23 cm | 7 / 14 / 17 cm |
| Columns found | 24 | 22 | 24 |
| Depth of pallet racks PR1–PR3 | 1.1 m | 0.55 m | 1.1 m |
| Zones matched | 8 | 8 | 8 |
A drawing gives centimetres; photos gave metres. The same scoring put the photo rebuild's median error at 1.97 m. Here, every dimension came from the drawing's scale. The remaining few centimetres are the drawn line widths: the agent built walls as thick as the drawn lines, and wall columns together with the wall around them.
Three of the four pallet racks came out at half their depth. The drawing shows each rack with a line along its middle and its label written across that line. The label's white halo ran into the fill on one side, so Laydyne's tracing returned only one half of each rack, and the fit check agreed with the half, because its edges lie on drawn lines. The agent even reported the gap between PR2 and PR3 as a narrow aisle. We fixed the tracing (below). Run again, the agent built all four racks 1.1 m deep. In that run it also found all 24 columns. It drew the office partitions as walls, so they did not match as partitions.

Grey: the answer key. Orange: the first rebuild. Blue, dashed: the rebuild after the fix.
The half-depth racks did not change the robot results: the robots never enter the reserve storage.
Tokens and time
| Step | Time | Tool calls (failed) | Shell commands | Input tokens (cached) | Output tokens |
|---|---|---|---|---|---|
| 1. Building from the drawing | 4 min 46 s | 26 (1) | 12 | 1.05M (0.97M) | 5.8k |
| 2. Transport and charging | 15 min 3 s | 131 (8) | 9 | 3.95M (3.80M) | 25.5k |
| 3. Two, three or four robots | 3 min 1 s | 17 (0) | 0 | 1.19M (1.08M) | 4.3k |
| 4. Layout change | 1 min 58 s | 17 (0) | 2 | 0.96M (0.86M) | 2.9k |
| 5. Proposal pack | 6 min 26 s | 10 (0) | 91 | 1.84M (1.72M) | 11.4k |
| Total | 31 min 14 s | 201 (9) | 114 | 8.99M (8.42M, 94 %) | 49.9k |
| 1 again, after the fix | 4 min 33 s | 32 (6) | 26 | 1.35M (1.25M) | 7.0k |
| 5 again, after the fix | 6 min 13 s | 24 (1) | 140 | 1.99M (1.86M) | 11.0k |
The failed calls were the agent's inputs, and each came back with a reason it could act on:
- scenario details Laydyne refused (a work activity on a step that was not a delay; a priority on a robot pool);
- two scenarios with different charging jobs compared as one workload;
- two saves sent with an old revision just after an edit;
- picture options that apply only in 3D (state tags, a heatmap);
- running a scenario that a design option it had just restored did not contain;
- a display setting sent with the first piece of a PDF, which then made the next pieces fail (fixed);
- doors a centimetre or less outside the floor (the message now says by how much).
What we changed in Laydyne
- Tracing: a part whose fill a label's halo ran into is read again without the spur, so both halves of a rack drawn with a middle line come back, noted
trimmed. - Route drawings as PDF:
get_process_drawingwithformat: "pdf"gives the scaled sheet with the routes, the seed, the time window and a colour key per robot. - Underlay pieces:
opacityandvisiblemay come with any piece of a picture sent in pieces. Points and placement sent too early now drop the piece with a clear message, instead of leaving later pieces to fail. - Parts outside the floor: the message says how far past which edge, and that
openingskeeps a door within its wall. - Revision marks: the title block's revision column widens for a longer mark ("Proposal" was cut to "Propos…").
What it cannot do
- Traffic between robots. Laydyne computes routes, capacities and queues. It does not compute robots meeting, passing or blocking each other in an aisle, or slowing down near people. The 0.5 m radius only decides where a robot fits.
- Batteries. Charging was three 6-minute jobs an hour for the whole fleet, kept the same for two, three and four robots. That is less charging per robot than the datasheet for four robots, and more for two. Battery level and charge curves are not modelled.
- Picking and outbound. Totes appeared on the stands at the brief's rates; the pickers were not simulated. What happens to packed parcels was outside the model, and it is the main cost of moving the benches.
- The bench assumption decides the answer. The brief did not say how many totes a bench can hold. The agent assumed one, reserved from dispatch until packing ends, and that is why the existing layout could not reach 110 an hour with any fleet. The next question to the customer is how many totes fit on a bench's roller shelf.
- A drawing is not a survey. Heights, the dock pits and the function of unlabelled fixtures are not on the drawing; the agent marked them as unconfirmed. The sheet says its dimensions are not measured on site.
- A real site's numbers. The peak, the shares, the packing times and the robot's speed are the brief's and the datasheet's. Laydyne turns them into consistent, reproducible results, not a prediction of the real warehouse.
Try it yourself
- Connect your agent to Laydyne over MCP (Codex, Claude Code or another client; see the docs) and keep Studio open.
- Put a customer's drawing (PDF or PNG with at least one dimension) and the throughput in a folder. To repeat this test, use the drawing and the brief.
- Send the five prompts above, one at a time.
- Score your model of step 1 against the answer key:
node apps/studio/scripts/score-photo-rebuild.mjs docs/articles/amr-proposal/answer-key.laydyne.json your-model.laydyne.json --class shelf=rack --class conveyor=bench --class cabinet=bench
Modelling, drawings, route drawings and export are free. The simulation runs need Pro.
