Search projects, hackathons, sponsors and tech

    ← All projects

    Petri

    Darwinian evolution for AI agents: harnesses mutate, get tested, and only verified winners survive.

    Demo

    About

    Petri is a petri dish for AI agent harnesses. A harness is the code around a model: the prompt, the retry loop, and the context it reads. In a lab, a scientist picks the traits they want, grows many cultures, and keeps only the ones that show those traits. Petri does the same for harnesses. A human picks the trait, the harnesses evolve, and only verified improvements survive. Today, when a team changes a harness, they usually say the agent "feels better". There is no baseline, no repeat count, and nobody else can check the claim. Failed ideas stay in one person's terminal, so the next engineer tries them again and pays for the same tokens. Step 1: Pick the dish. A human chooses the domain, the model, and the starting harness, for example Coding, Claude Sonnet 5, and the Petri harness. Step 2: Pick the trait. Each change states the trait it aims to improve: accuracy (more tasks solved) or token savings (fewer tokens used). The tree shows each change with a label for its direction and size, such as "accuracy +280%". The showcase trees show more traits a team could select for: speed, security, ethics, code quality, and trust. Step 3: Let it mutate. Every change is a new node in the tree. A node is a small mutation: it changes at most 2 files and 120 lines of its parent, and it starts from its parent's files. Each node holds a hypothesis in plain English, the result that would prove it wrong, a benchmark score, and signatures from the keys that re-ran it. Step 4: Select. The rule is strict and public. A verifier re-runs both the parent and the new version, 5 times each, and the median counts. The author cannot verify their own node, and two reports from one key count as one. A node survives only when 2 distinct keys each measure a gain of at least +1000 basis points. A drop of 1000 basis points or more is rejected as a regression. Anything in between is rejected as noise. Unlike a real dish, Petri keeps every failed culture. Rejected nodes are never deleted. The "petri digest" command turns the whole tree into instructions for the next agent: what survived, what failed and why, and which areas nobody has tried. The next generation starts from the survivors and does not repeat the dead ends. The recorded tree has 16 versions on a 20-task coding benchmark. The start version solves 5 of 20 tasks. Adding full symbol signatures and a worked example reached 19 of 20, and 2 keys accepted it. Removing them again was rejected and kept. Guards and the typecheck stopped 8 more mutations before they could run, and they stay in the tree as a record. The web app shows the dish as a lineage graph, with a stats chart of each generation. In the demo, a version with 1 of 2 keys survives live when a second key runs "petri verify".

    How it's made

    Petri has two parts: a TypeScript engine with a CLI (the lab), and a Next.js 16 web app that shows the dish (the microscope). THE CONTRACT FIRST Before any code, we wrote SPEC.md as a binding contract. It fixes the exact bytes that get hashed, the bytes that get signed, the acceptance rule, and the CLI surface. Then we built every module against it. This kept a fast, parallel build from drifting apart. THE ENGINE The engine runs on Node with tsx and commander, and every stored object has a zod schema. There is exactly one canonical JSON encoder and one SHA-256 helper in the codebase. A node id is the SHA-256 of its identity fields only: parent, harness files, benchmark and hypothesis. Wall time and file paths are excluded, so the same experiment gives the same id on any machine. Keys are ed25519 from node:crypto, and every verification report is a signed envelope. SELECTION The acceptance rule is one pure function in src/policy/acceptance.ts. It reads the signed deltas and returns accepted, rejected or pending. Every event goes into a hash-chained append-only log, and the tree state is rebuilt from that log. "petri fsck" checks every invariant, including that no node was deleted. The engine has 93 tests. MUTATION WITH GUARDS A new version is a patch on its parent's files. Before anything runs, guards refuse a patch that changes more than 2 files or 120 lines, touches the frozen harness/contract.ts, or spawns processes or reads environment variables. A scratch typecheck then refuses code that does not compile. A refused mutation still becomes a node with its reason, so the dead end is recorded. THE SEALED DISH (THE HARDEST PART) A harness must never see the benchmark tests, or it could cheat the selection. A TypeScript type is not a security boundary, so we used real process isolation. The harness runs in a child process under the Node --permission model, with a single read grant that covers only the prompt and harness files. The code the model writes runs in a second child process, and the test source is never written to disk. An early audit caught a harness that could read the tests. That audit is why this two-process design exists. REPLAY MODE (THE HACKY PART) We wanted anyone to reproduce the tree with no API key. Replay mode stores recorded model answers keyed by the SHA-256 of the harness files. The tests still really execute in the sandbox; only the model call is replaced. When a mutation has no recording, Petri does not guess a number. It marks the node "not-scored", and "petri verify" refuses it. Live mode calls Claude Sonnet 5 through the Anthropic SDK. FEEDING THE NEXT GENERATION "petri digest" flattens the whole tree into a text brief for the next agent: what survived, what failed and why, and which areas nobody has tried. "petri evolve --dry-run" prints the exact prompt a model would receive to propose the next mutation. THE WEB APP The Next.js app runs the real "petri export" and "petri digest" commands on each request, so the page always shows the actual log. A picker selects the dish: domain, model and harness. The tree is an SVG lineage graph with a tidy layout. Each edge carries a label for its trait and size, such as "accuracy +280%". The stats chart plots only measured versions, generation by generation, against a goal line. Showcase trees for other domains use the same acceptance rule to set their statuses. They are clearly separated from the measured tree. PARTNER TECHNOLOGIES The repo includes a Hedera Consensus Service topic client and World AgentKit code. Hedera is meant to give the verification log a shared order and timestamps that no single machine can rewrite. World ID is meant to prove that each verifier is a distinct human, not one person with many keys.