Beyond software
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
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.
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
| Bug | What it is | Bug report the agent got |
|---|---|---|
| HW01 | counter overshoots modulus | 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. |
| HW02 | gray decode wrong above 3 | GrayToBin gives the wrong number for most codes above 3: converting a value to Gray code and back does not return the original value. |
| HW03 | ALU subtracts the wrong way round | The ALU's SUB operation returns 249 for 10 - 3 and raises the borrow flag. |
| HW04 | ALU carry flag wrong | The ALU raises carry on 100 + 50 even though the sum fits in 8 bits, and misses the carry on 200 + 100. |
| HW05 | FIFO full one entry early | A 4-entry SyncFIFO reports full after only 3 writes and drops the 4th write. |
| HW06 | UART bytes arrive bit-reversed | Bytes sent through UartTx arrive bit-reversed at the receiver: sending 0x01 is received as 0x80, and 0xA5 stays 0xA5. |
| HW07 | PWM glitch at duty 0 | With duty set to 0 the PWM output still goes high for one cycle every period, and duty 3 gives 4 high cycles. |
| HW08 | debouncer lets bounces through | A bouncing button eventually registers as pressed with Debouncer(4), even though the input never stays high for 4 cycles in a row. |
| HW09 | CRC-8 check value wrong | Crc8Byte 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
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.
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
| Bug | What it is | Bug report the agent got |
|---|---|---|
| SC01 | divider gives 0.83 V | The 3.3 V reference from the voltage divider measures about 0.83 V. |
| SC02 | LED overcurrent | The status LED draws about 100 mA, five times its 20 mA rating, and runs hot. |
| SC03 | LED never lights | The status LED never lights: no current flows through it, although the rail is at 5 V. |
| SC04 | anti-aliasing filter cutoff too high | The 1 kHz anti-aliasing filter barely attenuates anything at 5 kHz; its cutoff is far too high. |
| SC05 | amplifier has gain 1 | The gain-of-11 sensor amplifier outputs 0.1 V for its 0.1 V input, i.e. a gain of 1. |
| SC06 | button input stuck low | The push-button input reads low (0 V) even when the button is released. |
| SC07 | high-pass filter acts as low-pass | The high-pass filter does the opposite of its job: it passes 100 Hz and blocks 10 kHz. |
| SC08 | inverting amplifier output stuck at 0 V | The inverting amplifier's output stays at about 0 V whatever the input. |
A larger schematic · one netlist, shared power rails
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.
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
| Bug | What it is | Bug report the agent got |
|---|---|---|
| BD01 | 3.3 V rail at 2.5 V | On the sensor board the 3.3 V rail measures 2.5 V and the microcontroller browns out. |
| BD02 | power LED dark | The sensor board's green power-on LED never lights, although the 3.3 V rail is fine. |
| BD03 | temperature reads far too low | At room temperature (25 C) the sensor board's temperature voltage reads about 0.3 V instead of about 1.65 V. |
| BD04 | ADC pin pinned beyond full scale | On the sensor board the microcontroller's ADC pin reads far beyond full scale (well above 3.3 V) whatever the temperature. |
| BD05 | anti-aliasing cutoff too high | The sensor board's 1 kHz anti-aliasing filter in front of the ADC barely attenuates at 5 kHz. |
| BD06 | board stuck in reset | The sensor board stays in reset: the RESET pin reads 0 V even with the reset button released. |
| BD07 | status LED overloads the GPIO | The 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. |
| BD08 | I2C data line at 5 V | On 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
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.
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
| Bug | What it is | Bug report the agent got |
|---|---|---|
| SE01 | SQL injection in user search | Security: searching users for x' OR '1'='1 returns every user (SQL injection). Searching for o'brien also crashes. |
| SE02 | path traversal in notes | Security: a note name like ../secret.txt reads files outside the notes folder (path traversal). |
| SE03 | pickled sessions accepted | Security: a session cookie containing a pickled Python object is accepted and decoded (pickle can run code). Normal JSON sessions no longer load either. |
| SE04 | any user can delete others' notes | Security: any logged-in user can delete another user's notes (broken access control); only admins' own notes are protected. |
| SE05 | stored XSS in comments | Security: a comment containing <script> is rendered as live HTML (stored XSS). The author name is escaped correctly. |
| SE06 | predictable reset tokens | Security: password-reset tokens for the same user are identical every time, so anyone who knows a username can compute one. |
| SE07 | open redirect after login | Security: after login, ?next=//evil.example/x sends users to another site (open redirect). |
| SE08 | double-extension uploads accepted | Security: 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
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.