What should a participation stake be measured against? (bead p70u)¶
Status: measured 2026-09-16, 5 seeds per cell, scenarios/contract_screening.yaml
(5 honest, 3 opportunistic, 2 deceptive; everyone starts at 100 resources).
Run: python scripts/sweep_stake_basis.py --seeds 5 --stakes 5,100,101 and the
same with --stakes 95,99,100 --hard-audit. Rows in
experiments/stake_basis/results/2026-09-16/.
The problem¶
Interaction payoffs accumulate in AgentState.total_payoff. They never touch
AgentState.resources, whose only inputs are governance resource_deltas and
marketplace escrow. StakingLever.can_agent_act reads resources. So every
agent sits at its starting balance for the whole run and
min_stake_to_participate is a constant: below the endowment it never binds,
above it nobody can ever act. A stake cannot price out a low-quality agent,
which is the thing a stake is for.
Two candidate fixes, both added as opt-in config, defaults unchanged:
- A,
payoff_flows_to_resources=True— credit payoffs toresourcesas they are earned. Engine-wide: escrow, other levers and any agent rule that readsresourcesall see the new trajectory. - B,
stake_basis="cumulative_payoff"— leaveresourcesalone and let the gate read endowment plus earnings. Only the stake changes.
B counts the endowment on purpose. A bare earnings gate blocks every agent at t=0, and a blocked agent can never earn, so the run deadlocks at zero welfare.
What the arms do¶
Both fixes make wealth track quality, which the default does not (mean basis by type, no-audit arm, stake 5):
| arm | honest | opportunistic | deceptive | spread |
|---|---|---|---|---|
| control | 99.4 | 99.9 | 99.5 | 1.2 |
A payoff_res |
130.1 | 121.7 | 104.3 | 58.5 |
B cum_payoff |
130.7 | 121.8 | 104.8 | 58.0 |
Under the default the three types are indistinguishable, and honest agents are marginally the poorest, because the only thing moving resources is escrow.
Does the gate then bind, and on whom?¶
Blocks are gate refusals summed over the run; "low-q" is opportunistic plus deceptive, who are 50% of the population.
No audits (nothing can push a balance down):
| arm @ stake | blocks | low-q | honest | welfare | gap |
|---|---|---|---|---|---|
| control @5 | 0 | 0 | 0 | 228.4 | +0.0213 |
| control @100 | 820 | 334 | 486 | 153.3 | +0.0109 |
| A @100 | 217 | 140 | 77 | 207.0 | +0.0107 |
| B @100 | 0 | 0 | 0 | 228.4 | +0.0213 |
| any arm @101 | 2000 | 1000 | 1000 | 0.0 | 0.0000 |
Hard audits (certain audit, penalty ×8, threshold 0.6 — the only available downward force, see below):
| arm @ stake | blocks | low-q | honest | welfare | gap |
|---|---|---|---|---|---|
| control @99 | 0 | 0 | 0 | 174.0 | +0.0439 |
| control @100 | 786 | 300 | 486 | 100.6 | +0.0301 |
| A @99 | 217 | 182 | 35 | 159.9 | +0.0272 |
| A @100 | 724 | 502 | 222 | 123.0 | +0.0298 |
| B @99 | 166 | 166 | 0 | 161.5 | +0.0335 |
| B @100 | 170 | 170 | 0 | 163.4 | +0.0211 |
Findings¶
- The default gate is worse than useless when it binds. At stake 100 it blocks 786 times and 486 of those are honest agents: it fires on escrow noise, and honest agents happen to sit lowest. Welfare falls from 174 to 101 and the quality gap narrows. It is a tax with no selection.
- B blocks only low-quality agents — 166 of 166 and 170 of 170, zero honest blocks in every seed — at the smallest welfare cost of any binding arm (161.5–163.4 against 174 unbound, so about 6%).
- A selects too, but bluntly: 84% of blocks land on low-quality agents at stake 99 and 69% at stake 100, the rest on honest agents whose balance dips transiently through escrow and costs. It also costs more welfare (123–160).
- Any stake above the endowment deadlocks the run in every arm: blocked at t=0, no earnings, no re-admission. Welfare 0.0 at stake 101. The usable range is bounded above by the starting balance, which is a property of the gate, not of the basis.
- Nothing ever slashes.
StakingLever.slash_stakeandGovernanceEngine.slash_agent_stakehave no callers anywhere inswarm/. Without audits nothing pushes a balance down, every type earns positive payoffs, and B never binds at all (0 blocks at stake 100). The basis fix is necessary but not sufficient: the lever also needs a downward force. Resolved 2026-09-17 by beadms0f— see below.
Recommendation¶
Adopt B as the fix for p70u: stake_basis="cumulative_payoff" is the
smallest change that makes the stake discriminate, it selects cleanly, and it
leaves the resource economics of every other scenario alone. Keep A
available but off by default — crediting payoffs to resources changes the
trajectory of every scenario, and its false-positive blocks are the reason to
be careful with it rather than an argument for it.
Neither option should be presented as "staking now works". Follow-ups:
- wire slashing to a detection (nothing calls it today) — done,
ms0f, below; - give the gate a bootstrap or re-admission path, so a stake above the endowment throttles rather than deadlocks;
- re-run any staking result that predates this note: they measured a constant.
Update (2026-09-17, bead ms0f): the downward force, wired¶
Finding 5 above is now fixed. GovernanceEngine slashes the stake of every
agent a lever catches during interaction or epoch-start processing, under a
new opt-in slash_on_detection (default off). A detection is a freeze or a
negative reputation or resource delta — audit catches, epoch collusion
penalties, circuit-breaker freezes — and explicitly not cost_a/cost_b,
which are charged whether or not anything was caught. Slashes are computed
from the effects gathered before the slash, so a slash never re-triggers
itself.
The slash is recorded on AgentState.stake_slashed as well as the resource
delta, because the cumulative-payoff basis is otherwise monotone
non-decreasing: earnings only accumulate, so without netting out slashes
nothing could push an agent below the bar however hard it was penalised.
Measured the same way, now at ordinary audit settings (p=0.3, penalty
×2, threshold 0.5) rather than the extreme --hard-audit. The two commands
are python scripts/sweep_stake_basis.py --seeds 5 --stakes 99,100 --audit
and python scripts/sweep_stake_basis.py --seeds 5 --stakes 99,100 --audit
--slash; rows are in experiments/stake_basis/results/2026-09-17/.
| arm @ stake | blocks | low-q | honest | welfare |
|---|---|---|---|---|
B cum_payoff @99, no slash |
0 | 0 | 0 | 228.3 |
| B @100, no slash | 0 | 0 | 0 | 228.3 |
| B @99, slash | 120 | 120 | 0 | 209.9 |
| B @100, slash | 128 | 128 | 0 | 206.6 |
| control @99, slash | 425 | 237 | 188 | 172.3 |
| control @100, slash | 1030 | 552 | 478 | 114.6 |
A payoff_res @99, slash |
265 | 198 | 67 | 196.0 |
- Without slashing the gate is inert at these settings — 0 blocks in either B cell, exactly as finding 5 predicted. Slashing is what makes the basis fix do anything at an audit setting anyone would actually run.
- Every block still lands on a low-quality agent (120/120, 128/128, zero honest in all five seeds) at a welfare cost of about 9% against the unbound 228.3.
- The default basis gets worse, not better, with slashing on: 44–46% of its blocks are honest agents, and welfare falls to 115. Slashing amplifies whatever the basis already selects on, so it is not a fix for A or for the default — it is a complement to B.
- Slashed value fell entirely on low-quality agents in every seed, because no
honest interaction landed under the audit threshold. The gate inherits
the detector's false-positive rate: this is a statement about the audit
lever's accuracy in this scenario, not a guarantee of the staking lever.
Under
--hard-audit --slashthe selection stays clean (537/537) but welfare drops to 118, so an over-eager detector still costs.
This makes staking functional in the narrow sense that the lever can now price out the agents it is meant to. It does not make staking a good policy: in every cell measured, the unbound run still has the highest welfare.
Caveats¶
- One scenario, one population mix, 5 seeds. The block counts have wide spreads (A @100: 724 ± 370), so treat the ordering, not the magnitudes, as the result.
--hard-auditis a deliberately extreme setting chosen to create a downward force at all. It is not a calibrated policy.- Binding arms narrow the measured
quality_gappartly because blocking shrinks the rejected pool, so the gap is not a clean welfare proxy here.