Beyond software

Same repairs.
New domains.

Mend repaired itself on Python bugs. Then we gave both workers bugs in chip designs and circuit schematics they had never seen.

Nothing here was shown to the supervisor or used in a self-repair round. The "hardware" is checked in simulation, as chip and board designers check designs before manufacturing.

Digital chip designs · Amaranth HDL → Verilog

9 bugs in 8 designs.

A counter, Gray-code converters, an 8-bit ALU, a FIFO, a UART transmitter, a PWM generator, a debouncer and a CRC-8 unit, written in Amaranth HDL and checked cycle by cycle by 26 simulation testbenches.

Same 9/9, at 58% fewer tokens.

Original worker · 2 runs9, 9 / 9mean 188,336 tokens per run
After self-repair · 2 runs9, 9 / 9mean 79,656 tokens per run
UartTx(1) sends 0x31 (ASCII '1'), one bit per cyclePlanted bug HW06: Bytes sent through UartTx arrive bit-reversed at the receiver: sending 0x01 is received as 0x80, and 0xA5 stays 0xA5.▲▲▲▲▲▲startbusytxstart (bug)busy (bug)tx (bug)▲ marks the cycles where the bug shows.
PWM(8) with duty 3: high for 3 of every 8 cyclesPlanted bug HW07: With duty set to 0 the PWM output still goes high for one cycle every period, and duty 3 gives 4 high cycles.▲▲outout (bug)▲ marks the cycles where the bug shows.
ModCounter(4), enabled: counts 0-3, wrap pulses on 3Planted bug HW01: A mod-10 ModCounter shows 10 before going back to 0, and its wrap pulse comes one cycle late. A mod-4 counter never raises wrap at all.▲▲count0123012301wrapcount (bug)0123012301wrap (bug)▲ marks the cycles where the bug shows.

Real hardware output

Every design exports as Verilog, the language chip toolchains take. This is the PWM generator, emitted by Amaranth through Yosys (hardware/verilog).

/* Generated by Amaranth Yosys 0.50 (PyPI ver 0.50.0.0.post129, git sha1 b5170e139) */

(* top =  1  *)
(* generator = "Amaranth" *)
module pwm(clk, rst, out, duty);
  reg \$auto$verilog_backend.cc:2355:dump_module$1  = 0;
  wire \$1 ;
  wire [3:0] \$2 ;
  reg [2:0] \$3 ;
  input clk;
  wire clk;
  input [3:0] duty;
  wire [3:0] duty;
  output out;
  wire out;
  reg [2:0] phase = 3'h0;
  input rst;
  wire rst;
  assign \$1  = phase == 3'h7;
  assign \$2  = phase + 1'h1;
  assign out = phase < duty;
  always @(posedge clk)
    phase <= \$3 ;
  always @* begin
    if (\$auto$verilog_backend.cc:2355:dump_module$1 ) begin end
    (* full_case = 32'd1 *)
    if (\$1 ) begin
      \$3  = 3'h0;
    end else begin
      \$3  = \$2 [2:0];
    end
    if (rst) begin
      \$3  = 3'h0;
    end
  end
endmodule

The planted bugs

BugWhat it isBug report the agent got
HW01counter overshoots modulusA mod-10 ModCounter shows 10 before going back to 0, and its wrap pulse comes one cycle late. A mod-4 counter never raises wrap at all.
HW02gray decode wrong above 3GrayToBin gives the wrong number for most codes above 3: converting a value to Gray code and back does not return the original value.
HW03ALU subtracts the wrong way roundThe ALU's SUB operation returns 249 for 10 - 3 and raises the borrow flag.
HW04ALU carry flag wrongThe ALU raises carry on 100 + 50 even though the sum fits in 8 bits, and misses the carry on 200 + 100.
HW05FIFO full one entry earlyA 4-entry SyncFIFO reports full after only 3 writes and drops the 4th write.
HW06UART bytes arrive bit-reversedBytes sent through UartTx arrive bit-reversed at the receiver: sending 0x01 is received as 0x80, and 0xA5 stays 0xA5.
HW07PWM glitch at duty 0With duty set to 0 the PWM output still goes high for one cycle every period, and duty 3 gives 4 high cycles.
HW08debouncer lets bounces throughA bouncing button eventually registers as pressed with Debouncer(4), even though the input never stays high for 4 cycles in a row.
HW09CRC-8 check value wrongCrc8Byte does not produce the standard CRC-8 check value: chaining the bytes of b"123456789" from 0 should give 0xF4.

Circuit schematics · netlists checked by circuit simulation

8 bugs in 7 circuits.

A netlist is the text form of a schematic, the file KiCad exports and SPICE simulates. A small simulator (Modified Nodal Analysis, checked against hand calculations) tests each circuit against its spec.

Same 8/8, at 64% fewer tokens.

Original worker · 2 runs8, 8 / 8mean 273,764 tokens per run
After self-repair · 2 runs8, 8 / 8mean 97,234 tokens per run
Voltage divider: 5 V to 3.3 V schematic
Voltage divider: 5 V to 3.3 V
LED driver: 10 mA schematic
LED driver: 10 mA
RC low-pass, 1 kHz schematic
RC low-pass, 1 kHz
RC high-pass, 1 kHz schematic
RC high-pass, 1 kHz
Non-inverting amplifier, gain 11 schematic
Non-inverting amplifier, gain 11
Inverting amplifier, gain -4.7 schematic
Inverting amplifier, gain -4.7
Button with pull-up schematic
Button with pull-up

What the agent edits

The divider above, as the netlist the agent reads and fixes. A planted bug changes one line, for example R2 20k becoming 2k, and the agent gets only the symptom: "the 3.3 V reference measures about 0.83 V".

* Voltage divider: 5 V supply down to a 3.3 V logic reference
V1 in 0 DC 5
R1 in out 10k
R2 out 0 20k

The planted bugs

BugWhat it isBug report the agent got
SC01divider gives 0.83 VThe 3.3 V reference from the voltage divider measures about 0.83 V.
SC02LED overcurrentThe status LED draws about 100 mA, five times its 20 mA rating, and runs hot.
SC03LED never lightsThe status LED never lights: no current flows through it, although the rail is at 5 V.
SC04anti-aliasing filter cutoff too highThe 1 kHz anti-aliasing filter barely attenuates anything at 5 kHz; its cutoff is far too high.
SC05amplifier has gain 1The gain-of-11 sensor amplifier outputs 0.1 V for its 0.1 V input, i.e. a gain of 1.
SC06button input stuck lowThe push-button input reads low (0 V) even when the button is released.
SC07high-pass filter acts as low-passThe high-pass filter does the opposite of its job: it passes 100 Hz and blocks 10 kHz.
SC08inverting amplifier output stuck at 0 VThe inverting amplifier's output stays at about 0 V whatever the input.

A larger schematic · one netlist, shared power rails

A sensor circuit, brought up in simulation.

USB 5 V in, an LM317-style regulator set to 3.3 V, an NTC temperature sensor with an op-amp buffer and a 1 kHz anti-aliasing filter into the microcontroller's ADC, a reset circuit, power and status LEDs, and I²C pull-ups. 11 bring-up checks, the ones an engineer runs on a new board: is the rail 3.3 V, are the LEDs safe, does the sensor read right, is anything over the MCU's 3.6 V limit.

Open the circuit explorer: the circuit illustrated, every bug, the agent's steps →

The circuit: board.cir, checked by test_board.py. Schematic level only: no PCB layout.

6, 7 → 8, 8 of 8 solved.

Original worker · 2 runs6, 7 / 8mean 210,424 tokens per run
After self-repair · 2 runs8, 8 / 8mean 151,814 tokens per run

Where the gap comes from: the original worker refused BD01 (both runs) and BD02 (one run) in a single turn, replying that a sensor-board report is "a hardware issue, not a bug in the Python library", without running a check. Both workers get the same prompt. The repaired worker's learned workflow starts by running the tests, so it engaged and fixed all 8.

The planted bring-up bugs

BugWhat it isBug report the agent got
BD013.3 V rail at 2.5 VOn the sensor board the 3.3 V rail measures 2.5 V and the microcontroller browns out.
BD02power LED darkThe sensor board's green power-on LED never lights, although the 3.3 V rail is fine.
BD03temperature reads far too lowAt room temperature (25 C) the sensor board's temperature voltage reads about 0.3 V instead of about 1.65 V.
BD04ADC pin pinned beyond full scaleOn the sensor board the microcontroller's ADC pin reads far beyond full scale (well above 3.3 V) whatever the temperature.
BD05anti-aliasing cutoff too highThe sensor board's 1 kHz anti-aliasing filter in front of the ADC barely attenuates at 5 kHz.
BD06board stuck in resetThe sensor board stays in reset: the RESET pin reads 0 V even with the reset button released.
BD07status LED overloads the GPIOThe sensor board's status LED is far too bright and its GPIO pin is overloaded: about 59 mA, the pin's limit is 20 mA.
BD08I2C data line at 5 VOn the sensor board the I2C data line (SDA) idles at 5 V, above the microcontroller's 3.6 V absolute maximum.

Security · every fix checked by an exploit

8 vulnerabilities, closed or not.

A small notes service with eight vulnerability classes (SQL injection, path traversal, unpickling client data, broken access control, stored XSS, predictable reset tokens, open redirect, double-extension uploads). A fix counts only if the exploit fails and the feature still works; the fix audit flags any patch that touches the tests.

Same 8/8, at 60% fewer tokens.

Original worker · 2 runs8, 8 / 8mean 173,019 tokens per run
After self-repair · 2 runs8, 8 / 8mean 69,588 tokens per run

An exploit test

The SQL-injection check. With the planted bug, both queries return every user; the fix must make them return nothing, while ordinary searches (including a name with an apostrophe) keep working.

def test_exploit_sql_injection_returns_nothing():
    conn = connect(USERS)
    assert find_user(conn, "x' OR '1'='1") == []
    assert find_user(conn, "x' UNION SELECT name, role FROM users --") == []


The planted vulnerabilities

BugWhat it isBug report the agent got
SE01SQL injection in user searchSecurity: searching users for x' OR '1'='1 returns every user (SQL injection). Searching for o'brien also crashes.
SE02path traversal in notesSecurity: a note name like ../secret.txt reads files outside the notes folder (path traversal).
SE03pickled sessions acceptedSecurity: a session cookie containing a pickled Python object is accepted and decoded (pickle can run code). Normal JSON sessions no longer load either.
SE04any user can delete others' notesSecurity: any logged-in user can delete another user's notes (broken access control); only admins' own notes are protected.
SE05stored XSS in commentsSecurity: a comment containing <script> is rendered as live HTML (stored XSS). The author name is escaped correctly.
SE06predictable reset tokensSecurity: password-reset tokens for the same user are identical every time, so anyone who knows a username can compute one.
SE07open redirect after loginSecurity: after login, ?next=//evil.example/x sends users to another site (open redirect).
SE08double-extension uploads acceptedSecurity: an upload named invoice.png.exe is accepted, because the check only looks for an allowed extension anywhere in the name.

What this shows, and what it doesn't

The gain is efficiency.

Both workers fix every chip-design and schematic bug, so these sets show no gain in what gets solved. What carried over is how: the self-repaired worker reads less, edits precisely and stops when the tests pass, so it needs far fewer turns and tokens. Two runs per worker; the sets are small; everything is simulated, not built on a physical board.