KeeperHub is the execution and reliability layer for onchain agents. Nothing currently lets anyone check that reliability claim: the audit trail records every trigger, simulation, transaction and outcome, and nobody reads it back.
This is a chaos harness that deliberately breaks agent execution through KeeperHub and reports what survives: with transaction hashes for every claim.
13 scenarios · 6 findings · 2 HIGH · 4 MEDIUM · 7 passes. Every run executes real transactions on Ethereum Sepolia. Gas is sponsored and transfers are self-directed, so a full pass costs nothing and is repeatable indefinitely.
prose_drift FINDING HIGH payload-hash idempotency key drifts
silent_noop_node FINDING HIGH skipped action node reports success
amount_formatting FINDING MEDIUM our own fix needs value normalization
conditional_staleness FINDING MEDIUM KeeperHub's own guard is not atomic
omitted_key FINDING MEDIUM duplicate protection is opt-in
premise_staleness FINDING MEDIUM premise false by the time tx lands
body_drift_conflict PASS - drifted body fails closed, not open
concurrent_same_key PASS - dedupe is atomic under races
cross_chain_key_scope PASS - key<->payload binding enforced
overdraft_transfer PASS - no stuck nonce on unaffordable send
parallel_distinct_transfers PASS - nonce ordering holds under load
revert_reporting PASS - reverts reported honestly
semantic_key_fix PASS - semantic key survives reworded prose
Ordered by what survives a rebuttal, not by severity. The two staleness findings are rated MEDIUM deliberately: they are inherent to off-chain check-then-act, and calling them defects would overstate the case.
1. conditional_staleness (MEDIUM). execute_check_and_execute is KeeperHub's own
read-condition-act primitive. The condition is evaluated at one block and the action
lands at a later one:
condition "block <= 11392654" TRUE at block 11392654
action included at 11392655: condition FALSE
Stated fairly: off-chain check-then-act cannot be atomic, so this is inherent rather than a defect. The issue is that nothing says so. The primitive's name and shape imply the condition gates the action, and an agent handed it reasonably assumes enforcement at execution. Documented, this is a caveat; undocumented, it is a trap. A real fix needs an on-chain guard that re-checks in the same transaction. We originally rated this HIGH and that overstated it.
2. silent_noop_node: a green run for an automation that never ran. An action node
missing actionType is accepted at creation and silently skipped at execution:
status='success', error=None, executionTrace=['trigger-1']. The incentive runs
backwards: adding actionType triggers validation demanding an abi, so the less
complete definition passes and does nothing while the more complete one is rejected.
3. prose_drift: a reproducible double-spend. idempotency_key is optional and
agent-supplied. The textbook derivation is to hash the request body, but when the caller
is an LLM that body contains model-authored prose which is not stable across a context
loss. Two payloads differing only in a reason field produced two keys, two executions,
two transactions onchain for one intended payment. The prose variants are verbatim
output from llama-3.3-70b and gpt-oss-120b at temperature 0. Not a universal
claim: qwen3.6-27b was perfectly stable across the same arms, so this is a property of
particular models rather than of LLMs in general, and probe1/RESULTS.md records the
stronger cross-model claim we withdrew.
4. premise_staleness: the same staleness gap in hand-rolled read-then-act.
5. omitted_key (MEDIUM): idempotency_key is optional, so the default path is the
unprotected one and nothing warns a caller running without it.
6. amount_formatting (MEDIUM): a finding against our own proposed fix, not against
KeeperHub. "0.001" and "0.0010" are the same transfer and hash differently, so a
semantic key is necessary and not sufficient: values must be normalized before hashing.
Findings 1/2/4 and 3 are different mechanisms: one is the world moving underneath the agent, the other is the agent forgetting its own words. Neither fix addresses the other.
Seven scenarios pass, and most were written expecting a failure. That ratio is the point: this is a test suite, not a hit piece.
Dedupe is atomic under concurrency (parallel calls are rejected 409, not raced
through). Keys are bound to payloads, so reuse with a changed payload is refused rather
than silently returning a stale result. Reverts report status='failed' with a reason.
Unaffordable sends are refused before submission with no nonce consumed. A stable key
sent with a reworded body fails closed, refused 409 idempotency_conflict with no second
transfer, rather than quietly executing twice. Five concurrent
transfers all land, so a busy agent does not head-of-line block itself.
| path | what |
|---|---|
chaos/ |
the harness: scenarios, runner, scoreboard |
probe1/ |
the offline experiment behind prose_drift, cross-model at temperature 0 |
keeperhub/ |
headless MCP client, double-spend writeup, marketplace notes, friction log |
If you have a coding agent open in your project, TESTERS.md has a prompt you can
paste that installs this, runs it, then reads your code for the three failure patterns
it measures and writes up what it finds. Two minutes, no account needed.
Or directly:
pip install -e .
agentaudit --adapter local-evm # real EVM in-process, no setup at allexport KEEPERHUB_API_KEY=kh_... # Settings > API Keys > Organisation
cd chaos && python3 run.py # full pass
cd chaos && python3 run.py prose_drift # one scenariopremise-freshness-check, listed on the KeeperHub marketplace and callable by any agent viacall_workflow. It returns the block height a read was observed at: the re-checkconditional_stalenessshows is missing.keeperhub/FRICTION.md: every blocker hit going from a fresh account to a working headless call, each with the fix that would have prevented it.
Kept deliberately, because a reliability project that hides its own errors is worthless:
amount_formattingis a finding against our own proposed fix, not KeeperHub.- Probe 1's cross-model claim was withdrawn when
qwen3.6-27bproved perfectly stable: the effect is model-dependent, not universal. - An earlier claim that the marketplace was empty was wrong;
search_workflowsreturned 0 while 19 listings existed, and one API call was treated as fact.