📌 Placement (master thread): seed-refiner, router-in-the-loop repair, and the block-scale gap #118
Replies: 9 comments 7 replies
|
Ngl the router-in-the-loop idea seems like the least cursed version of placement optimization. The proxy scores are useful for generating candidates, but the router is the only one actually living in reality. So I'd let proxies do the cheap "maybe this is better" search, then use routing as the final accept/reject step. For small boards, router-based ranking seems fine if each run is only ~1-2s. For bigger boards, I'd make it budgeted/adaptive, otherwise it's gonna turn into a laptop space heater. Maybe only route when there are failures, when the proxy finds a big delta, or before accepting a candidate. For seeds, rough imports and AI/KiCad-ish placements probably have the most free elo to farm. Careful hand layouts should stay in repair mode only, because the tool can't see the secret engineer brain constraints. Also +1 on explicit BGA/QFP escape room. Generic halo is a decent "don't be stupid" proxy, but it doesn't really know about pin density, bus corridors, via pressure, or which side of the chip needs breathing room. |
|
Note Merged from discussion #407, "Where should KRT stop?" — original post by @edgehero, 15 Jul 2026. Where should KRT stop?(original post — @edgehero, 15 Jul 2026) I don't use KRT as a plugin. I drive it headless, as the routing engine inside a pipeline that takes a board from a written spec to production ready fab outputs. KRT is load bearing in it, and it replaced FreeRouting as my primary router months ago. The part that is not solved is placement, and specifically: knowing when to stop routing and go move something. The goalNot "autoroute this board." Make a PCB from nothing to production ready. Spec in, gerbers out, autonomously, with the human gating judgement instead of operating a mouse. Routing is one stage of maybe fifteen. Every stage has to be machine drivable and machine checkable, which is why my setup looks the way it does, and it's the root of everything I want to ask you. The board, in the only terms that matter here: 10 layers, ~290 parts, ~150x100 mm, five controlled impedance classes. A via-less 50 ohm coplanar waveguide feed, a 5 pair 100 ohm source synchronous camera class bus with a tight skew budget, PCIe Gen2 x1 (85 ohm, die terminated), USB3 Gen1, Gigabit Ethernet, and a pile of slower buses. Fine pitch QFN and connector escapes everywhere. No BGA, which is why I keep filing QFN escape issues rather than BGA ones. It's a real product so I'll keep the application out of it, but as a routing problem it's genuinely nasty, and that's the part relevant to you. The stack, and why each piece is in itEverything here is open source, and every piece was chosen because an agent can drive it and a gate can check it.
KiCad, three interfaces, three reasons:
Which of your entry points are actually load bearing for me:
(There's a second, entirely separate mechanical stack for the enclosure: OpenSCAD and trimesh for the model, Gmsh with CalculiX and FEniCSx for structural and thermal FEA. The board never touches it. I mention it only so the scope is honest.) The loopflowchart TB
spec["Spec + datasheets<br/>(pcb scan)"] --> zen["Capture: Zener .zen<br/>pcb build, 0 ERC gate"]
zen --> sim["ngspice testbenches"]
sim --> sync["pcb layout<br/>netlist to KiCad, no autorouter"]
sync --> P0["P0 stackup + netclasses"]
P0 --> P1["P1 placement<br/>scripted packer + refine"]
P1 --> gate{"floorplan_health<br/>congestion / bus-crossings /<br/>displacement / cohesion"}
gate -->|FAIL| P1
gate -->|PASS| review["HUMAN GATE: render sign-off"]
review --> P2["P2 criticals<br/>KRT route_diff + escapes"]
P2 --> diag{"route_diagnose<br/>one RESULT= line"}
diag -->|"RETRY (max 3)"| P2
diag -->|"corridor_congestion OR<br/>stop-loss: 2 strategies burned"| repair["place_route_repair<br/>diagnose, move BLOCK, re-route,<br/>accept or revert"]
repair -->|success| P2
repair -->|HARD_WALL| human["HUMAN: re-block"]
human --> P1
diag -->|PASS| P3["P3 bulk: KRT route.py"]
P3 --> P4["P4 planes: KRT route_planes"]
P4 --> P5["P5 validate<br/>adversarial verifier panel"]
P5 -->|FAIL| P1
P5 --> P6["P6 fab outputs"]
style repair fill:#fff4cc,stroke:#cc9900,stroke-width:2px
style human fill:#ffdddd,stroke:#cc0000,stroke-width:2px
Two doctrines that came out of being burned, in case they're useful: "Renders are for INTENT, the oracle is for LEGALITY." No agent may adjudicate clearance from pixels. Images tell an agent what the board is trying to be; a geometry oracle decides what's legal. When I let vision near legality it hallucinated confidently. Connectivity is geometry, never DRC. Where KRT sits, honestly"KRT is better than FreeRouting" isn't a claim I can defend, so here's the version I can:
That's n=1. Your 45 board stress test (#117) is the real evidence; mine is one vote. What made 10 layers tractable was forbidden layers ( The actual problem: placement, and when to rip up#118 is the best writing on PCB placement I've found anywhere. I think you're mostly right and interestingly wrong. Your corpus holds placement fixedflowchart LR
subgraph A["Your 45 boards (#117)"]
direction TB
a1["Shipped production board"] --> a2["STRIP: tracks, vias, pours"]
a2 --> a3["<b>Placement KEPT</b><br/>a human already moved parts<br/>until it routed, for weeks"]
a3 --> a4["KRT re-routes it"]
end
subgraph B["My board (n=1)"]
direction TB
b1["Spec + datasheets"] --> b2["AI places it"]
b2 --> b3["<b>Placement NEVER<br/>router-validated</b><br/>there is no hand"]
b3 --> b4["KRT routes it"]
end
style a3 fill:#ddffdd,stroke:#00aa00,stroke-width:2px
style b3 fill:#ffdddd,stroke:#cc0000,stroke-width:2px
You strip tracks, vias and pours. You never strip the placement. So a production placement isn't merely "hand made", it's a post routing artifact: the human moved parts until it routed, then shipped it. Both boards in #118 (
None of it is wrong. It's fitted to boards where placement is solved before your tools ever see them, so the corpus can't contain the failure I keep hitting. Where I think you might be wrongYour blocker is constraint capture:
True of the board file. Not true of my pipeline. The same agent that places the board also parsed the datasheets into structured text, authored the schematic as source, chose the impedance classes, sized the hot loops, and knows which pins form a bus, because it wrote them. The intent isn't trapped in a human skull. It's upstream of the Where you're right, and my scars prove itAnd yet it didn't save me. Twice. A corridor error. A magnetics block sat ~80 mm from both of its own endpoints, so 8 of its traces cut straight through the camera bus corridor. I burned days hand routing the 5th pair. Root cause was a floorplan error, visible from move zero to anyone looking at the right number. ~22 residual nets that would not route, no matter which knob I turned. Floorplan bound, not routing bound. The fix was re-floorplanning an entire quadrant. Every signal needed to catch both already existed in my toolchain, just wired non-blocking: congestion advisory, bus-crossings report-only, the probe a whole-board aggregate (a dead 5 net bus is noise under a 228 unrouted baseline), cohesion per-group and blind to a split block. They're per-bus blockers now. The gapflowchart LR
A["<b>Nudge a passive</b><br/>~3 mm cap<br/>place_optimize<br/>place_route_loop<br/>(--max-target-pins 40)"]
B["<b>THE GAP</b><br/>move a 160-pin block 80 mm<br/>re-block a subsystem<br/><i>nothing lives here</i>"]
C["<b>Human re-floorplans</b><br/>the quadrant<br/>(days)"]
A --> B --> C
style A fill:#ddffdd,stroke:#00aa00,stroke-width:2px
style B fill:#ffdddd,stroke:#cc0000,stroke-width:3px
style C fill:#ddddff,stroke:#0000cc,stroke-width:2px
Your scoping is obviously right for your corpus. But your own dose response says the gap isn't merely unimplemented: at a 10 mm cap the quench destroyed the Xilinx to MCU corridor, the macro structure the proxies can't see. The proxies get worse exactly where I need them to work. That smells less like "tune the weights" and more like block level moves being a constraint problem, not an optimization problem, which is where I think AI belongs and a quench doesn't. An experiment you could run tomorrowYou already have the 45 boards, the harness, and the grader. You strip the routing and keep the placement. What if you also perturbed the placement? Displace whole blocks by 10 to 80 mm, or shuffle a subsystem to the wrong side, then run That gives you what the corpus lacks: boards whose floorplan is known-wrong by a known amount. My prediction, which I'd love to be wrong about: It also answers your own #118 question ("which seed sources are worth refining?") with something better than my anecdote. My anecdote, for what it's worth: an AI seed is good locally (it read the datasheet, it knows the hot loop) and bad globally (it put the magnetics 80 mm from both endpoints). That's inverted from a human seed, which has good macro structure and sloppy local detail. If that generalises, an AI seed may need a larger cap than 3 mm, because its macro structure is exactly the thing you shouldn't preserve by construction. Three questions1. Where should KRT stop? Routing engine wearing a GUI, or the engine under a whole pipeline with KiCad as file format, DRC oracle and renderer? You already built a harness checking the GUI against the CLI, which reads like CLI is ground truth, GUI is a client. Is a headless agent driver a third client you want, or one you tolerate? Either answer helps me; I'd rather build with the grain than against it. 2. What tells you the floorplan is wrong? My stop-loss (2 strategies per critical net, then examine the corridor) is a heuristic I invented after losing days to not having one. You have router iteration counts across 45 boards. Is there a signal in there that separates "the router needs another knob" from "the placement is wrong"? 3. Would you emit the blocker analysis as JSON? Your loop already computes it: failed nets plus the blocker nets walling them off, from the frontier analysis. To a GUI user that's a progress detail. To an outer loop it's the whole ballgame, the difference between "it failed" and "it failed because that bus is in the way". That one output is what lets the layer above choose between re-route, move a part, move a block, or ask a human. (Related to #352.) Thanks for the tool. It's load bearing in a pipeline you didn't design it for, which I mean as a compliment. |
|
Note Merged from discussion #407 — the reply thread. Both comments reproduced verbatim, in their original order, with their original authors and dates. Reply by @drandyhaas — 15 Jul 2026(original) This is all very interesting and forward looking - thanks for the ideas and feedback. I have also had in the back of my mind since early on that placement should be also addressed to some degree here. After all, the routing DEPENDS on the placement. And doing the placement DEPENDS on what can be routed successfully. I did take a stab at placement, and played around with it, as you saw. And it resulted in a critical mini-win, the under-BGA cap placement optimizer. But as for how to go further with placement board-wide, I only have a placeholder issue filed... I suspect a promising avenue may be:
All this can be in the CLI and GUI, as they are intended to be two interfaces to the same tools. Even AI can be fun from the Claude tab in the GUI as just like it can from the terminal. So we would have a Placement tab in the GUI. I also know, by the way, that we have significant gains yet to achieve from just optimizing the routing settings. We have yet to reproduce the human 100% connectivity on the corpus boards (91 now, plus monsters!), on the same human placement input. But getting all the way there might be too challenging. And working on placement to make the routing easier might be lower hanging fruit. Reply by @edgehero — 15 Jul 2026(original) Thanks Andy, that answers the vision question directly: an engine with two interfaces, and AI as a user of either one rather than a separate thing. Useful to know, and it means I'm building with the grain instead of against it. Your steps 1 to 4 are close to the loop I already run headless, so rather than theorise here I've put the substantive follow-up in #110, where you said the work lives. It covers the two parts that were not obvious to me (a hard stop-loss, and accept-or-revert graded by the router rather than the proxy), plus three concrete, self-contained issues:
The last one is the one I'd most encourage, since I think it blocks #110 more than the algorithm does. |
|
Note Merged from issue #110. All placement discussion lives in this thread now, so #110 carries no Comment by @edgehero on #110 — 15 Jul 2026Andy, following up here rather than in #407, since this is where you said the work lives. Your steps 1 to 4 are, almost exactly, the loop I've been running headless in production for the last few months. So rather than theorise, here's what it looks like from the other side, plus three concrete issues. On your step 3 (AI parses results, figures out what should move). This is the right shape, and it's the step that needs an interface. Most of it already exists: Two parts of the loop that were not obvious to me, and cost me months to learn: 1. A stop-loss. Without one, an agent (or a person) will happily grind the router forever, because every failure looks like it might yield to one more knob. My rule is that a critical net gets at most ~2 distinct routing strategies before its corridor is re-examined for a placement root cause. Trying a third tool/layer/setting after a saturated-corridor verdict is treated as a rule violation, not a judgement call. That single rule is what turned "grind for days, then realise it was placement" into a verdict in seconds. I'd suggest step 4's "unless we decide to give up" is actually the most important step, and it wants to be a hard rule rather than a vibe. 2. Accept-or-revert, graded by the router, never by the proxy. Every round must be reverted unless the router says it improved, and the round's only write path must be one already-proven placement mutation, so a rejected round leaves the board byte-identical. You already found this in the #118 data (two of four candidates made things worse and were correctly rejected). It's worth making it structural rather than a step: proxies propose, the router disposes, and the loop is only ever allowed to ratchet. On testing any of this. This is the one I'd most encourage, because I think it blocks #110 more than the algorithm does. Your corpus strips the routing but keeps the placement, so every board in it has a floorplan a human already iterated until it routed and shipped. That's ideal for grading a router, which is what it was built for. But it means there are no bad seeds in it, and #110 explicitly asks to cover "a range of placement quality" and to characterise "bad seeds vs good hand placements". Perturbing the corpus placements manufactures exactly those, and the original placement comes free as ground truth. Details in the issue. That framing also answers your #118 question about which seeds are worth refining, better than my anecdote can. For what the anecdote is worth: an AI seed is good locally (it read the datasheet, it knows the hot loop, it groups by subsystem) and bad globally (mine put a magnetics block ~80 mm from both of its own endpoints, and 8 of its traces then cut through a camera bus corridor). That's inverted from a human seed, which usually has good macro structure and sloppier local detail. If that generalises, an AI seed may want a larger displacement cap than 3 mm, because its macro structure is precisely the thing you shouldn't preserve by construction. Filed as three concrete, self-contained items:
One request: I can't apply labels (no triage rights). If you want them grouped, a And noted on the 91 boards not yet matching human 100% connectivity on the same human placement. That's a striking number, and I think it quietly makes the case for this issue: if the router still can't match a human on a placement that was built around a human's routing solution, then some of that remaining gap probably isn't router settings at all. It's that the placement encodes an answer the router is trying to rediscover from the outside. Appendix: the original body of issue #110(by @drandyhaas — preserved here in full; #110 itself was rewritten as the tracking issue, so this is the only copy of its original text) GoalThe placement optimizer ( Work to do
References
|
|
Note Comment by @edgehero — 2 Aug 2026. Reposted verbatim and unedited, purely to keep this Hi Andy, wanted to report back on this thread, because I now have a full end to end test of the "proxies propose, the router disposes" idea on a board where every constraint is actually written down. Long comment, but there are pictures. The board firstI tried everything on https://github.com/edgehero/test-board (you have access). Deliberately a small test board: 51 x 21 mm, 2 layers, 42 parts. RP2350A, USB C, QSPI flash, a crystal, castellated GPIO edges. The interesting property is that it is 100 percent AI generated, spec first. The hardware spec and design brief were written before anything else, the netlist is generated from a zen source (there is no hand drawn schematic, the zen file plus a written schematic review stand in for it), and the initial placement plan is derived from the spec rather than from taste: connectors on the edges the spec names, the MCU centered because the spec says centered, the regulator cluster on the exact RP2350 datasheet fig 23 geometry, decaps inside the 3 mm rule, castellated rows at 2.54 mm pitch. The quench then only refines that seed under locks. I think that makes it a good testing point for how far we can push this and which blockers we hit, because a failure is always attributable to a written requirement. It is also your constraint capture argument from the top post played out in practice: once the constraints existed as text, the seed mostly placed itself, and the optimizer's job shrank to legalizing the passives. What the recent placement PRs added, and whyThe theme of all of them: the optimizer was flying blind and so was I. Nothing graded whether a placement respected the spec, and when a route failed I could not tell a placement problem from a parameter problem.
On the data side the important part is that every image now pairs with json: the render carries crossings, hpwl, overlap area and out of board counts in its caption and in a JSON_SUMMARY, and board_score gives per net work lists (which pad is stranded, where, on which layer) instead of bare counts. On the video side, camera auto recovers part moves without needing loop sidecars. On my branch (next PR) there is more of that: a converge ledger with byte exact step back and replay, a film composer that shows the whole search including the reverted attempts with red TRIED badges and the diagnostic renders spliced in as cards, a 10 frame minimum tween per part move so you can actually see which part went where, and a broken work list in board_score that names each break and the step that fixes it. The loopThe other thing I built is a loop in the claude skill instead of one shotting. Short video explaining it: https://www.youtube.com/watch?v=VQy50fuxI34 The idea is to compensate for how these models fail: a single attempt bakes its first mistake into everything after it. So the skill forces multiple different attempts, scores each against the spec, keeps what measured better, reverts what did not, and writes every step to a ledger so the next attempt starts from evidence instead of from memory of what it expected. flowchart TD
A[seed placement from spec] --> B[quench refine under locks]
B --> C{gates: optimizer numbers improved, intent passes, repo spec gate passes}
C -- no --> A2[adjust seed or locks, by measurement] --> B
C -- yes --> D[routing chain: constrained nets first, then bulk, pour, repair, reconnect]
D --> E[board_score plus repo check_spec plus kicad oracle]
E --> F{blocking 0 and spec pass?}
F -- no --> G[pick the cheapest lever: named rip set, finer grid, layer change, order change, placement change]
G --> H[scoped retry, seconds not minutes]
H --> E
F -- yes --> I[done, promote board]
G -. every attempt, kept or reverted .-> L[ledger and film]
Latest attemptThe film of the whole run. Placement beats animate, every reverted attempt gets a red TRIED badge, diagnostic renders are spliced in where they were made: And the result, honestly a failure, but a failure that is starting to look like something: What routed: every routable net carries copper (unrouted 0), drc 0 at the routed floor, the QSPI bus 7 of 7 on a single layer with 0 vias (first run out of five that managed that), crystal legs 7.79 and 8.08 mm against a 10 mm limit with 0.29 mm symmetry against a 1 mm limit, chip select chain 8.13 against 15. Still failing: 5 supply pad joins stranded in the escape ring around the QFN, USB routed at 0.15 instead of the specified 0.8 (that one is unsatisfiable as written, a 0.8 trace onto a 0.2 pad at 0.4 pitch lands on the neighbours), two QSPI nets at 16.91 and 18.89 against 15 (the flash pinout order reverses against the MCU pins, so only one net fits through the short way), and the bottom pour continuity under USB and QSPI reads 38 crossings. Everything is in the run PR with a journal where every claim has its measurement: https://github.com/edgehero/test-board/pull/26. Board file: https://github.com/edgehero/test-board/blob/p8-layout-run5/layout/layout.kicad_pcb. The schematic equivalents: https://github.com/edgehero/test-board/blob/p8-layout-run5/test-board.zen and https://github.com/edgehero/test-board/blob/p8-layout-run5/docs/schematic-capture-review.md. Since the schematic is code, here is a module level view generated from the netlist, one box per zen module with the inter module nets: I think this class of result improves a lot with the perturbation recovery work in #411 (known bad seeds with the human placement as free ground truth, dose response curves for the loop) and with better tooling generally. Five runs on this one board went from 39 of 44 nets and 762 drc errors to what you see above, and almost all of that came from tooling and measurement, not from the model getting smarter. The placement physics problem, where I could use your helpThe thing I keep hitting and honestly do not know how to solve: how do you tell the model where the best place for a component is, and what moving it would do to every other component. Routing has a gradient. You can score a candidate pose in milliseconds and trial route it in seconds. Placement barely has one: moving one part changes the channel capacity for six nets that route later, and nothing local predicts that. On this run the decisive placement moves (flipping the crystal, rotating the flash 180 degrees, moving one resistor 1.3 mm) each came from sitting down and doing the interference geometry by hand, and one of them I got wrong until a second pair of eyes redid the arithmetic. I tried a vector approach, embedding parts and corridors with similar logic to how vector databases do similarity, hoping nearest neighbour queries would answer "where does this part want to be". It failed. The interactions are too nonlocal, two parts that never touch still compete for the same escape lane three steps downstream. So, three questions for the physicist in the room:
Thanks! |
Runs 6, 7 and 8 on the test board: the placement question, answered by placementFollowing up my run-5 report above. Same 51 × 21 mm, 2 layer, 42 part RP2350A board, all headless. Run 5 seeded from a 42 entry hand measured ScoreAll three boards re-graded at run 8's flags and floors, so they are commensurable (
Blocking reads 19 against run 6's 18 only because run 8 has copper on every net to measure: 43 plane void crossings against 29, where run 6's four dead nets contribute none. On connectivity, which outranks it, run 8 leads 2 oracle items to 4. What run 8 testedRun 7 shipped six open nets behind a west fan capacity claim. By my own stop rules that was a hypothesis, not a finding: no sweep envelope, no finest grid route, no crop read. Run 8 tested it, and it was wrong. The cause was placement, and the levers were small.
Result: all eight header GPIOs plus SWDIO routed, with the placement metrics improving at the same time (crossings 71 to 70, HPWL 565.9 to 559.3 mm, courtyard overlap 0). Ordering was the other half, and it surprised me. Six nets (both VREG rails, both CC pins, GPIO0, SWCLK) failed after the USB pair was committed, and rip levers did nothing because the search was dying on static obstacles: The board can now draw itselfThere is no schematic export anywhere in the toolchain, verified rather than assumed: Generated schematic, single flat sheet (PDF) Eleven module frames wired by global label, the net to pin table, the BOM with MPNs, and the design notes, on one page. The gate is a round trip netlist proof: the drawing is re-netlisted and compared to the design, and the generator may only claim success if the two are electrically identical. ERC reports 4 errors, all Vision, and what I think fills the gapI said earlier in this thread that renders are for INTENT and the oracle is for LEGALITY, and that vision near legality hallucinated confidently. I still believe it. Run 8 sharpened it in both directions. One pad on the MCU east face resisted every mechanical lever: every rip set, every grid from 0.1 mm down to 0.0125 mm. I wrote it up as geometrically sealed, then fanned out an independent agent with only the pad coordinates, the claim and a crop, told to refute it. It refuted me, with an exact two via path through a 6.4 µm throat (margins of 3.7 to 13 µm at five pinches). No gridded router finds that, which is why every lap had failed. On a second pad it caught an arithmetic slip in my own write up, then confirmed that cage, deficit 0.07 mm. So refutation works, but it works from coordinates and costs an agent round trip per claim. The crop I read myself was consistent with both the true and the false claim. The doctrine held, and the gap is that vision contributed nothing. What fills it is a picture whose colour is a measurement rather than an appearance: Every pixel is Other places the toolchain still costs me
Two questions
Thanks! |
Weeks 6–7: the assembly gate, the fix loop, and four set-1 boards vs their human originalsContinuing the perturbed-corpus series (#411). Week 6 answered "why did a board with overlapping components ever reach routing"; week 7 fixed the bugs that answer exposed and ran the machinery on four set-1 boards: each perturbed, recovered, routed headless by one Claude worker + the What went wellWeek 6 (tigard):
Week 7 (set-1):
The tigard series, six runs in
Week 7: four boards vs their human originalsPlacement (recovery vs the human placement; perturbation in brackets):
Routing: what routed correctly and what errors remain (graded at margin 0 against each board's own rules; KiCad's own DRC oracle agrees on all four):
(vias/copper vs-human on glasgow is not like-for-like: its placement sits in the wrong basin, see below.) The stable pattern: we beat the human on copper length and lose on vias. (The "and on legality" half of this claim is WITHDRAWN, see the correction below.) The splitflap watcher corrected the attribution: it is not the pour (our pour used 29 fewer GND vias than the human there), it is signal-side layer changes, with the via-cost knee (≈150 vs default 50) left unapplied by the workers. Skill-adherence finding as much as a tool one. Warning Correction: "we grade cleaner than the human" was not true on two of these A cross-board audit after these runs found that our DRC writeback lowers the Relaxing a clearance is a deliberate, documented decision here (stock Fixed in PR #576: the relaxation stays (removing it re-manufactures the Two smaller corrections from the same audit. The ottercast row above was Comparisons (human left, KRT right)Note Image correction + what the visual differences mean. The first version of this tigard (week 6), recovery +1.000: splitflap_driver, recovery 1.000, all nets: bitaxe_ultra, routed clean on a partially-recovered placement: glasgow_revC, DRC-0 routing on the wrong placement basin: The honest findings (what week 8 is made of)
Every number above was independently re-derived by the watcher agents from the boards, not quoted from the workers. Ottercast's close, the cross-board sweep, and the week-8 plan (wrong-basin gates + the airwire vector source) follow. Try it yourself: the exact prompt, watchers includedI (@edgehero) am almost through my Claude usage for this week, so more boards won't come from me until it resets. If you want to test the skill on your own boards in the meantime, and generate the same kind of validated bug findings, below is the exact prompt pattern these runs use. It works in a checkout of this repo, with any KiCad board. The prompt (paste into Claude Code at the repo root, fill in BOARD)Notes for runners:
|
Run 14 (castor_pollux): three skills, and the routing half is a loop now tooPerturbed corpus series (#411). 230 parts, 150 nets, 4 layers, 70.8 x 108.0 mm, staged blind, headless end to end. The headline is not the board: we now have three skills with a real control flow between them, and the routing half finally loops the way placement always did.
Separate, because the two halves have contradictory accept rules: placement accepts a lap when the defect it aimed at is gone, routing accepts one when How the driver.py loop worksEach skill is a driver script, and the driver never runs a tool. You ask for one stage, it prints an instruction block, you do the work, you come back with evidence. The agent is the interpreter; the driver is a state machine that emits and refuses. Four properties make that a loop rather than a checklist:
Outer and innerflowchart TD
L1["L1 place<br/>read the board, choose inline or teammate"]
P["INNER LOOP: /plan-pcb-placement<br/>P0 gate, P1 unplaced, P2 mechanical, P3 reconstruct,<br/>P4 fix loop, P5 slate, P6 intent, P-close"]
L2["L2 freeze, then route<br/>gate: 4 keys of check_assembly JSON"]
R["INNER LOOP: /plan-pcb-routing<br/>plan, A1..A6 analysis, R1..R9 chain,<br/>V1 score, V2 classify, V3 apply or revert, V5 close"]
L5["L5 close out<br/>agreement gate: check_complete vs converge"]
L1 --> P --> L2 --> R --> L5
P -. "placement laps" .-> P
R -. "routing laps" .-> R
L5 -. "L3 classify, then L4 reenter at the named point" .-> L1
The outer loop is a gatekeeper, not a controller. It touches no copper and moves no part; it picks inline versus teammate, refuses to start routing on a placement nobody proved, and refuses to call the run finished. It fired 3 times in 61 minutes and idled for 39. The inner loops do the work and iterate locally. The placement half, and why it has no movieWhat routing was handed. Panel side left, SMD side right, copper free, metrics in the header, locked parts hatched.
There is no placement animation for this run, because zero parts moved. P0 measured both gates clean, which is its terminal verdict, so the ladder ended and the run correctly declined to search. A film of that is one still frame. Which raises the fair question: is the gate actually capable of firing, or is it just quiet? So we took the same 24 part block the perturbation had drawn and walked it further along the same axis, regrading at every step. The gate is live; the dose was not.
At the dose the rig meant to deal, the board picks up 5 body stacks, 6 pad pairs and a part off the outline. At the dose it actually dealt, everything is clean and there is genuinely nothing to recover. Recommendation: make /plan-pcb-routing a loop as wellThis is the main thing I would suggest adopting upstream. Routing used to be a straight line here too: analyse, pour, fanout, pairs, bulk route, verify, done. That is fine when the first set of parameters happens to be right and useless when it is not, and nobody ever writes down the option that lost. Mine is a loop now, V1 to V5, and it progresses by trying several options and keeping the best, with the losing arms recorded rather than deleted. It costs very little, because the arms are cheap next to the analysis that precedes them, and it changes what the run can tell you afterwards. Plane mapping, three arms off the same input, differing only in
A does not fail. It buys the same A fourth arm pinned the fab floor with The whole search, in orderBuilt off the ledger, so ordering comes from Board quality against the human original
Connectivity is complete, confirmed by KiCad's own engine with zones refilled: 126 of 126 nets, The via gap is worse than 1.52x suggests: by net our signal vias are 300 against 173, so 1.73x. Part of their 358 is a deliberate GND stitching field, copper well spent; ours is layer changes, copper spent getting lost. It shows in the pictures, the human routing in channels with long clean sweeps while ours takes diagonals and switches layer when a diagonal runs out. They also ran a perimeter GND stitching ring and kept In2.Cu at zero signal copper so it stays an unbroken return plane. One caveat outranks the table: our board grades 0 DRC only against a project this run rewrote. At the floor the board originally declared, KiCad reports 88 errors. The hard part: getting the outer loop and the two inner loops to agreeComposing them was much harder than writing either half, and every problem below is a composition problem rather than a routing or placement one. The outer loop's return arm never fired, and that is the big one. L3 classify and L4 reenter are how the outer loop is supposed to close. Both stayed dark. There was a failure and it was classified, but in routing's V2, one level down, and the answer never travelled up. The re entry that followed, a full R1 to R7 rerun with the fab floor pinned, is real work the outer loop never authorised and never saw. That is the dotted red edge in the diagram above. An inner loop that quietly does the outer loop's job is the default failure mode of a nest, because the inner one always has more context. The two halves disagree about what better means. Placement accepts a lap when the named defect it aimed at is gone; routing accepts one when The handoff document is a trap. State has to cross on disk or a re entry cannot say what was already tried. Ledger of content addressed boards plus the JSON reports, nothing in a head. This is also what makes the film above trustworthy, since the ordering comes from Doing a stage's work is not the same as entering it. V1 scoring, V3 apply or revert, and V5 close out all happened in this run and produced the numbers in this post, but the driver was never asked for those stages, so their guard text was never in front of the agent. V5 is specifically how the routing verifier gets fanned out; I dispatched it by hand instead and it came back with two FAILs. Having an outer close out available makes skipping the inner one very easy. Three tool bugs also cost real time, all now fixed with tests: Two watcher agents ran alongside the whole run, writing to files rather than reporting back, which is why their findings survived. 50 items between them, and the single most valuable finding came from one of them rather than the main worker. Routing is a real result. Placement is a null for staging reasons. Next lever is via count, 545 against 358, and that needs a different global escape plan rather than another parameter. |
|
Router in the loop repair, demonstrated end to end on a blind board. We staged urchin (103 parts, 82 nets) with every free part collapsed onto one coordinate (blocking 13708) and ran the combined skill loop. It closed at blocking 0 with all 68 copper bearing nets routed and DRC clean at the board's own floors, over two cycles. The second cycle is the point of this thread. Cycle 1 routed 65 of 66 signals, and the probe measured the one failure precisely: SW17's pad sat inside the milled seam slot, and a single net route probe failed at 219k iterations with no copper competition. The loop classified that as placement shaped. A scoped reseat then moved both thumb switches into their pockets, after the engine measured which two diodes blocked the seats (0 legal poses with them in place, 46 and 32 without them). The reroute closed clean: 68 of 68 nets, including the net cycle 1 proved unroutable. The pile, before: Routed, front and back: The whole run as one film (both halves record into one ledger, so it is one movie): Full report: REPORT.md The tooling that drove this is in three stacked PRs: #616 (routing floors and false success fixes), #623 (placement engine and the stress rig), #624 (the combined skill loop). Findings we measured but did not patch are filed as #615 and #617 through #621. How the loop is built, and why it has inner loops. The outer loop is five stages: place, freeze and route, classify, re enter, close out. Each half of the work is itself a loop run by its own agent. The placement half accepts a lap when the named finding it aimed at is gone; the routing half accepts an iteration only when blocking strictly decreased. Those are two different definitions of better, and one agent holding both at once quietly trades them off against each other, so the halves run as separate teammates and the only thing that crosses between them is files: a ledger both record into, and close out documents the gates reopen and reread. The outer loop exists for the moment a route fails. All routing failures look the same from inside the router (nets that did not route), but they have three different causes with three different fixes: a parameter (retry the routing step with one knob changed), the placement (no router setting adds a lane; re enter placement), or the floorplan (no arrangement at this placement satisfies the clause). Classifying before retrying is the whole game, because retrying a placement shaped failure with router parameters spends iterations on a board no parameter can fix. That is exactly what cycle 2 above shows: the router's evidence (a pad inside the milled slot, a probe that failed with zero copper competition) re entered the placement half as a scoped fix, and the reroute then closed. The close out is measured too. A run may stop only when a verdict from the ledger says done, stuck, or budget spent, and the terminal verdict must survive an independent check of the shipped board plus a cross check against the recorded lens verdicts; a run that stops because it is finished must not look like one that stopped because it was stuck. Against the human routed original (
The human board is the better one on routing style: 3.34x fewer vias, larger drills, and balanced two layer usage where ours leans on the front layer plus pours. Connectivity and DRC are equal; via economy and layer planning are the honest quality gap. The human layout is a benchmark to approach, not a pose to match. Downsides of this setup, stated plainly. The whole harness is built for Claude and Claude Code today: the skills, the stage drivers, the refusal protocol and the delegated halves are English contracts executed by LLM agents, so none of it runs standalone or in plain CI, and another model or a human would have to reimplement the orchestration to use it. The loops are also slow. This run took roughly a day of wall clock end to end: the placement half alone fought the piled board for hours (the seeder needed four engine patches plus a disclosed hand seeded arrangement before it could finish), the cooperative deadlines overshot their budgets by about a third, and idle gaps between agent handoffs added hours more. A cycle that re enters placement repeats a full route on the new arrangement, so every extra cycle costs another routing pass. The run report and a learnings file with twelve ranked findings (four of them placement engine defects this run exposed, including interior milled contours being invisible to placement legality) live next to the board in the assets branch. The routed board in 3D: Learnings file with the twelve ranked findings: LEARNINGS.md |






























Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Important
📌 This is the master thread for placement in KiCadRoutingTools.
Placement discussion used to be spread over three places. It now all lives here:
discussion #407 ("Where should KRT stop?") and the discussion that had grown on issue
#110 have been merged into this thread. Nothing was dropped — every post is
reproduced verbatim below, with its original author, date and a link back to where it was
first written. #407 is closed and points here; #110 is now the tracking issue — goals,
settled findings and open questions — and carries no discussion of its own.
Please post new placement discussion in this thread.
Placement optimization: a seed-refiner + router-in-the-loop repair (and why not a from-scratch autoplacer)
We took a hard look at component placement — surveyed ~40 years of the literature and the current commercial/academic landscape, prototyped both approaches, and measured them on real boards. The conclusion shaped what shipped: a perturbative refiner (
place_optimize.py) and a router-in-the-loop repair loop (place_route_loop.py). We even built a from-scratch constructive placer first and then removed it once the data came in. Full write-up:docs/placement-optimization.md. Tracking issue: #110.Why not from-scratch autoplacement?
The blocker isn't optimization power — it's constraint capture. The constraints that actually govern placement (enclosure fit, connector/button positions, mounting holes, thermal spreading, EMI zoning, datasheet hot-loop intent, assembly/test access, plain designer intent) live in mechanical CAD, datasheets, and the engineer's head — not in the board file. By the time you've entered enough rooms/keepouts/rules for a tool to place well, you've already placed the board by hand.
This isn't just our opinion: Altium deleted their autoplacer at v18 (2017) after decades of "produces garbage" feedback, and the ISPD 2025 best paper (Cypress, Cornell/NVIDIA) states stock VLSI analytical placers "fail to find a routable placement for multilayer PCB designs with components of diverse sizes." PCBs are also structurally unlike ASIC placement (which is solved): 50–1,000 wildly heterogeneous parts (0402 next to a BGA), arbitrary rotations and two board sides, and only 2–8 routing layers — so a placement mistake can't simply be routed around.
The validated niche: refine a human/AI seed
Start from the existing (hand- or AI-made) placement and bound each part within
--max-displacementof where it started. The human's implicit constraints — bus corridors, clusters, connector positions — survive by construction, while the tool fixes local problems. This is exactly the formulation the refinement-from-seed literature says works.place_optimize.py— a greedy quench: small nudges, 90° rotations (with correct pad-angle rewriting), and same-footprint swaps, minimizingairwire length + pin-pair crossings + pin-count-scaled halo + edge margin. Locked footprints never move;--lockpins connectors;--ignore-netsexcludes plane-routed power from the airwire objective.What the measurements showed
interf_u (25 parts, 2 layers) — router iterations ≈ effort:
kit-dev-coldfire (160 parts, 4 layers) — dose–response on the displacement cap:
At a 3 mm cap the quench matched completion while cutting router effort 5×; at 10 mm it destroyed the Xilinx↔MCU data-bus corridor — the macro structure the proxies can't see.
--max-displacement(~3 mm) is the dominant safety knob.A recurring finding: proxy↔routability correlation is weak (the best crossing-reduction variant failed nets). So don't trust the proxy alone.
Closing the loop:
place_route_loop.pyProxies propose, the router disposes. Each round:
--max-target-pins 40— moving a pull-up is low-risk; dragging a 144-pin QFP destroys placements).Result on kit-dev-coldfire, from the hand placement: 3 → 0 failed nets and 4.8× less router effort, moving only resistors/caps/jumpers (ICs and connectors never moved). Two of four candidate placements made things worse and were correctly rejected — exactly the proxy-blindness the loop exists to catch. Cost: ~5 routing runs (~4 min).
Practical upshot & where it's going (#110)
place.py+ therust_placerscoring crate) was removed — it survives in git history.Discussion
All reactions