Open the app

Circuit board design, with your agent

Your idea, in copper.

Describe the board in a small language with units and typed nets. Volt checks it, routes it and draws it in 3D, in your browser, with your agent working beside you.

A six-layer circuit board in green solder mask with gold pads: a BGA land pattern, a fine-pitch QFN, rows of connector pads and plated mounting holes, drawn in 3D by Volt's renderer.
An open-hardware board, imported and drawn in 3D by Volt’s renderer.

One Rust core, on your machine and in your browser.

The language, the checks, the pour fill, the router, the renderer and the commands are one Rust core. It runs natively as the volt command line, and compiled to WebAssembly it runs the app in your browser.

  • The same answers. Under a work budget, a route is identical byte for byte in the browser and on the command line, whatever the machine and the number of threads. A test holds it on every fixture board.
  • One renderer. The same code draws the board everywhere: 2D, and 3D as it will be made, on WebGPU or WebGL2 in the browser and to PNG on the command line.
  • Fast where you work. An edit reaches the board on screen in 4–26 ms on boards up to 6 MB.
One Rust core — the language, checks, fill, router, renderer and command registry — with copper buses to its two ends: the command line and the browser, where it runs as WebAssembly.coreRustCLInativebrowserWebAssembly
In the core: language · checks · fill · router · renderer · commands.

A board is text you can read, diff and review.

A board lives in design.volt and a few block files in a git repository. The Volt language is declarative and small: you say what the board is, and Volt works out where the copper goes. This one evaluates with no errors and routes to complete.

design.volt2 layers · 14 parts · routes to complete
use "blocks/support.volt" as support
net GND, VBUS
net V3 named "+3V3"
net USB_DP, USB_DN
net STATUS
bus I2C[2]
pair USB = (USB_DP, USB_DN)
board = Board {
  name = "sensor-node"
  outline = rect(50mm, 30mm)
  cutouts = [circle((3.5mm, 26.5mm), 1.6mm), circle((46.5mm, 26.5mm), 1.6mm)]
  layers = [signal(Cu.top), signal(Cu.bottom)]
}
usb = NetClass {
  trackWidth = 0.2mm
  diffPairGap = 0.2mm
  nets = [USB]
}
ldo = support.Ldo(VBUS, V3, GND, bulk = 4.7uF) { at = (14mm, 24mm) }

U1 = Mcu {
  at = (25mm, 14mm)
  pins { VDD: V3, DP: USB.p, DN: USB.n, SDA: I2C[0], SCL: I2C[1], LED: STATUS, GND: GND }
}
status = support.StatusLed(STATUS, GND) { at = (34mm, 6mm) }
sensor = support.SensorPort(V3, I2C[0], I2C[1], GND) { at = (46mm, 15mm) }

gnd = pour(net = GND, layers = [Cu.bottom]) { outline = board.outline }
rule UsbPair {
  select = copper(pair = USB)
  limits { trackWidth: limit(min = 0.2mm), diffPairGap: limit(min = 0.15mm, max = 0.25mm) }
}
Show the whole file (78 lines)
use "blocks/support.volt" as support

// USB-powered sensor node: 3.3 V regulator, QFN-32 MCU, I2C sensor port and SWD port.
net GND, VBUS
net V3 named "+3V3"
net USB_DP, USB_DN
net STATUS
bus I2C[2]
pair USB = (USB_DP, USB_DN)

board = Board {
  name = "sensor-node"
  outline = rect(50mm, 30mm)
  cutouts = [circle((3.5mm, 26.5mm), 1.6mm), circle((46.5mm, 26.5mm), 1.6mm)]
  layers = [signal(Cu.top), signal(Cu.bottom)]
}

supply = NetClass {
  trackWidth = 0.5mm
  nets = [VBUS, V3, GND]
}
usb = NetClass {
  trackWidth = 0.2mm
  diffPairGap = 0.2mm
  nets = [USB]
}

part Mcu = device(footprint = qfn(32, size = (5mm, 5mm), ep = (3.1mm, 3.1mm)), pinout = {
  VDD: ["1", "17"],
  DN: "3",
  DP: "4",
  NRST: "8",
  SDA: "21",
  SCL: "22",
  LED: "24",
  SWDIO: "29",
  SWCLK: "30",
  GND: "33",
})
part C100n = cap(100nF, package = "0402") // one per supply pin

J1 = header(4) {
  at = (4mm, 15mm)
  pins = [VBUS, USB.n, USB.p, GND]
}
ldo = support.Ldo(VBUS, V3, GND, bulk = 4.7uF) { at = (14mm, 24mm) }

U1 = Mcu {
  at = (25mm, 14mm)
  pins { VDD: V3, DP: USB.p, DN: USB.n, SDA: I2C[0], SCL: I2C[1], LED: STATUS, GND: GND }
}

decoupling = {
  for ((i, p) in enumerate([(21mm, 17mm), (29mm, 11mm)])) {
    [i]: C100n { at = p; rotation = 90deg; pins { a: V3, b: GND } },
  }
}

J3 = header(5, pitch = 1.27mm) {
  // debug port, joined to the MCU's pins directly
  at = (25mm, 3mm)
  rotation = 90deg
  pins = [V3, U1.pin.SWDIO, U1.pin.SWCLK, U1.pin.NRST, GND]
}
R1 = res(10kohm, package = "0402") {
  at = (20mm, 8mm)
  pins { a: V3, b: U1.pin.NRST }
}

status = support.StatusLed(STATUS, GND) { at = (34mm, 6mm) }
sensor = support.SensorPort(V3, I2C[0], I2C[1], GND) { at = (46mm, 15mm) }

gnd = pour(net = GND, layers = [Cu.bottom]) { outline = board.outline }

rule UsbPair {
  select = copper(pair = USB)
  limits { trackWidth: limit(min = 0.2mm), diffPairGap: limit(min = 0.15mm, max = 0.25mm) }
}
  • Units

    Every value carries its dimension: 0.5mm, 100nF, 10kohm, 90deg. Mixing them up is an error, not a surprise.

  • Typed nets and pins

    Nets, buses and differential pairs; pins checked by name. Two nets joined by accident are a short.

  • Parts and blocks

    A part is what you buy; a block is a reusable circuit with parameters and its own frame.

  • Footprints, computed

    82 land patterns computed from package dimensions per IPC-7351B, 13 of 14 checked against a reference library identical pad for pad to 1 µm.

  • Classes, rules, pours

    Net classes, typed rules with selectors, keepouts, rule areas, and copper pours with thermal reliefs.

  • Freedom

    Say where a part may move, turn or swap pins, and the tools stay inside it.

A formatter and a language server come with it: diagnostics, hover, completion and go-to-definition in your editor and in the app.

A router that negotiates, searches, and checks its own work.

The router is a search algorithm, not a model: it routes the same board the same way every time. Six ideas do most of the work; all of them are on by default.

  1. Negotiate: Every net routes at once, overlaps and all. Space two nets want gets more expensive with every pass, until no two nets share it. The grid has eight directions and every layer.NET ANET BGAP COST ↑B DETOURS

    Negotiate

    Every net routes at once, overlaps and all. Space two nets want gets more expensive with every pass, until no two nets share it. The grid has eight directions and every layer.

  2. Exact where it counts: The grid guides the search, but every clearance is decided by exact geometry, to a fraction of a micron. Zero-slack fine-pitch escapes fit.exact gap0.4 mm pitch · 0.2 mm track · 0.3 µmgrid

    Exact where it counts

    The grid guides the search, but every clearance is decided by exact geometry, to a fraction of a micron. Zero-slack fine-pitch escapes fit.

  3. Search the neighbourhood: Then it rips up a window around whatever is still open and routes it again, keeping what is better. Eight states search side by side.RIP UP A WINDOWROUTE IT AGAINkept if better

    Search the neighbourhood

    Then it rips up a window around whatever is still open and routes it again, keeping what is better. Eight states search side by side.

  4. Pours, live: It models each copper pour as it routes: the fill, the gaps around other nets, the islands, the thermal spokes. It knows which pads a pour really reaches.THERMAL SPOKESISLANDdroppedother net, cut out

    Pours, live

    It models each copper pour as it routes: the fill, the gaps around other nets, the islands, the thermal spokes. It knows which pads a pour really reaches.

  5. Pad access as a set: A pad on a dense part gets up to eight exact, full-width ways out, and its neighbours negotiate for them instead of each taking a fixed stub.8 WAYS OUT, EXACT, FULL WIDTHneighbours negotiate for them

    Pad access as a set

    A pad on a dense part gets up to eight exact, full-width ways out, and its neighbours negotiate for them instead of each taking a fixed stub.

  6. Mindful of the mask: It knows the solder-mask openings, the untented vias and the thin green webs between pads, and routes so the fab can keep them.MASK WEBUNTENTED VIATENTED

    Mindful of the mask

    It knows the solder-mask openings, the untented vias and the thin green webs between pads, and routes so the fab can keep them.

Measured against an independent checker.

Every change to the router, the checks and the file import is measured on public boards people designed, and judged by an independent design-rule checker, not by Volt itself. A board counts only when it is fully clean: there is no partial credit.

  • 96.1%

    of 482 curated public boards import and export with every pad and every connection confirmed by an independent checker

    standard open board files, both ways

  • 232/235

    human-designed boards where, after a refill, pour connectivity matches the independent checker

    copper pours

  • 4–26ms

    from an edit to the board on screen, on boards up to 6 MB, in the browser

    measured in a headless browser

  • 13/14

    reference footprints matched pad for pad to 1 µm by footprints computed per IPC-7351B

    footprint library

You and your agent design it.

Your agent does not click through the interface or read the screen. It calls the app directly, through an API on the page: the same commands you run from the palette.

  • Every command, described. Each has a JSON Schema for its input and result, and says what it changes.
  • Work you can follow. A journal of what changed since the agent’s last turn, jobs for long work, and the view: what you see and what you selected. Every change says who made it and can be undone.
  • Docs written for agents. The app’s /agents.md and /llms.txt are generated from the same commands. WebMCP where a browser offers it.
agent · on the page
volt.hello("my-agent")
await volt.commands("route")          // what it does, its schema
await volt.call("route", {})          // runs as a job
await volt.wait("job")
await volt.call("view.state", {})     // what the person sees
await volt.call("journal.since", {})  // what changed since my turn

Any agent with a browser or a shell. From a terminal, the volt command line runs the same commands.

Nothing to install. Nothing stored on our side.

  • No account

    Open the page and work. It keeps working offline once loaded.

  • Your files

    Projects live in your browser’s storage or in a folder on your disk that you open in the app. A board is a folder of text files under git.

  • No server doing the work

    Checking, routing, import and export run on your machine, in WebAssembly workers. No project data leaves it unless you export.

Describe it. Route it. Hold it.

Today a finished board leaves Volt in the standard open board file format, with its project and rules, ready for the fabrication tools you use now.

Coming next

  • Fabrication outputs (Gerber, drill).
  • Fab settings in the language: finish, mask and silkscreen colour, copper weight, checked against what the fab can make.
  • Ordering boards from the fab, from the app.

Your idea, in copper.

Open the app

Who makes it.

Volt Zero is made by Sergey Alekseev. He is building Rabbit, an autonomous rover, and keeps its build log at rabbit0.dev. Volt is the tool he wants for its boards: one he and his agent can work in together, from the idea to the factory.