Running Antithesis SDK code
Antithesis is a commercial testing platform with its own Go SDK, a library for writing checks inside your program. If your code already uses that SDK, seedfall runs it unchanged and turns each SDK call into a seedfall check. Read this page if you are coming from Antithesis or share code with a team that uses it.
Code instrumented with the Antithesis Go SDK runs under seedfall unchanged. Assertions become seedfall properties, lifecycle.SetupComplete becomes seedfall's setup signal, and random.GetRandom draws from the seed.
examples/ledger has no seedfall imports at all. It uses plain net (through net_overlay) and only the Antithesis SDK:
lifecycle.SetupComplete(map[string]any{"ledger": addr})
amount := int(random.GetRandom()%100) + 1
assert.Sometimes(unacked > 0, "a crash interrupted a deposit", nil)
lifecycle.SendEvent("deposits finished", map[string]any{"total": total, "acked": acked, "unacked": unacked})
assert.AlwaysGreaterThanOrEqualTo(total, acked, "ledger total covers every acknowledged deposit", map[string]any{"unacked": unacked})
assert.AlwaysLessThanOrEqualTo(total, acked+unacked, "ledger total holds no deposit that was never sent", nil)
assert.SometimesAll([]assert.NamedBool{{First: "interrupted", Second: unacked > 0}, {First: "kept", Second: total > acked}}, "an unacknowledged deposit survived a crash", nil)
$ seedfall run -c seedfall.json -seeds 1-20
property seeds reached seeds true
sometimes 20 18 a crash interrupted a deposit
sometimes 20 2 an unacknowledged deposit survived a crash
always 20 20 ledger total covers every acknowledged deposit (smallest margin 0)
always 20 20 ledger total holds no deposit that was never sent (smallest margin 0)
sometimes 20 5 recovery dropped a partial entry
ok 20 seeds passed (0 hit the time horizon) in 908ms
$ seedfall run -c seedfall.json -profile nofsync -seeds 1-20
FAIL seed 1 at t=2.940793302s (618 scheduler steps, 336ms real)
client#1: ledger total covers every acknowledged deposit: {"left":4203,"right":5608,"unacked":1729}
How it works
On wasip1 the SDK has no native Antithesis library to talk to, so it writes each event as a JSON line to the file named by ANTITHESIS_SDK_LOCAL_OUTPUT. seedfall sets that variable for every node and serves the path as a virtual file. Each line is decoded as it is written:
| SDK call | seedfall property |
|---|---|
assert.Always |
always: fails the seed when false, fails the sweep if never reached |
assert.AlwaysOrUnreachable |
always_or_unreachable: fails the seed when false |
assert.Sometimes |
sometimes: fails the sweep if never true |
assert.Reachable |
reachable: fails the sweep if never reached |
assert.Unreachable |
unreachable: fails the seed when reached |
assert.AlwaysGreaterThan, AlwaysGreaterThanOrEqualTo, AlwaysLessThan, AlwaysLessThanOrEqualTo |
always, with the smallest margin in the property table |
assert.SometimesGreaterThan, SometimesGreaterThanOrEqualTo, SometimesLessThan, SometimesLessThanOrEqualTo |
sometimes, with the largest margin in the property table |
assert.AlwaysSome |
always: at least one named boolean is true |
assert.SometimesAll |
sometimes: every named boolean is true |
lifecycle.SetupComplete |
ends the setup phase when faults_after_setup is on |
lifecycle.SendEvent |
an event: a trace line with the details as JSON, returned by seedfall_replay |
The SDK's details map appears as JSON in the failure message; the numeric variants add left and right, and the grouped variants add each named boolean. seedfall's source catalog recognizes every assert.* call above with a constant message, so Antithesis assertions that never run are reported too.
The margin of a numeric assertion comes from the SDK's own guidance records. Like Antithesis, the SDK only reports a new value when it gets closer to failing (for Always) or closer to true (for Sometimes), so the smallest and largest margins are exact. The SDK also reports each assertion only the first time it passes and the first time it fails in a process, so the number of hits is lower than with sim assertions; seeds reached and seeds true are unaffected.
sim has the same variants with the same names (properties.md), and sim.Event matches lifecycle.SendEvent.
random.GetRandom and random.RandomChoice read crypto/rand, which seedfall feeds from the seed, so they replay exactly and draw from the same source as sim.Random and sim.RandomChoice. Antithesis custom faults are commands its platform runs from outside your program and have no SDK call to map; mark the same spots with sim.Buggify instead (buggify.md).
What you get compared with Antithesis
The same assertions and the same setup signal, run locally or in CI for free, with seed replay, shrinking, causality analysis and triage reports. What you don't get: Antithesis runs arbitrary containers in a deterministic hypervisor, while seedfall runs Go programs that build for wasip1.