Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
47c6b24
bench/individuality: p0 passes repository-relative paths; EXPECTED re…
BitmapAsset Oct 14, 2026
67c49ac
persona: learning.credit read strictly; await/for/cancel read and ech…
BitmapAsset Oct 14, 2026
eaae16d
live: control lines close open decisions, verify summary open, verify…
BitmapAsset Oct 14, 2026
b32a013
bench/individuality: MANIFEST p0.py line and the README log for the c…
BitmapAsset Oct 14, 2026
c24e1ed
fuzz: payments resolved against each state (late, reordered, previous…
BitmapAsset Oct 14, 2026
349d8c4
examples/agent-harness: the addressed-outcome variant (addressed.json…
BitmapAsset Oct 14, 2026
ce57dac
fuzz: re-running a shrunk counterexample with its payments resolved i…
BitmapAsset Oct 14, 2026
b06ed98
persona, live: tests for action-addressed feedback: eligibility at ag…
BitmapAsset Oct 14, 2026
cbe7cb2
docs: action-addressed feedback (persona.md §2.8 credit, §4 stance cr…
BitmapAsset Oct 14, 2026
694a836
bench/individuality: metric c frozen before its first run (dispatch.y…
BitmapAsset Oct 14, 2026
9d52f8e
bench/individuality: credit.py runs its 50 host-driven runs side by s…
BitmapAsset Oct 14, 2026
7798437
bench/individuality: twins_addressed.py re-measures metric a with Des…
BitmapAsset Oct 14, 2026
739b935
examples/agent-harness: README section for the addressed variant; doc…
BitmapAsset Oct 14, 2026
dbcb364
bench/individuality: README intro: this release runs metric c; metric…
BitmapAsset Oct 14, 2026
fb9ee7b
bench/individuality: record the frozen metric a result on this release
BitmapAsset Oct 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions BENCHMARKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1141,3 +1141,47 @@ re-measured when action-addressed feedback (RFC 0001 Design B, metric c) lands.
block (9,600 live turns and as many replayed, `describe`, `lint --seeds 0-99`, `fuzz` and `prove` documents): 0 differ. 629 are
byte-identical; 24 are the stderr of `live` runs whose scripts send a `senses` key, identical once the one line naming it is
removed. All 8 recorded strands verify, and the tutor's continues with the recorded bytes.

## §11 Action-addressed feedback: credit accuracy, and the twins re-measured (`python3 bench/individuality/run.py --only c,d,e`, `python3 bench/individuality/twins_addressed.py`, stdlib; development build of 0.9.0)

Metric c's fixtures, draws and bars were frozen in `bench/individuality/fixtures/MANIFEST` before it was ever run; three runs
gave the same values. Ten individuals of an act-or-ask policy (`fixtures/dispatch.yaml`: the shipped dispatch policy with a
credit block) make 300 decisions each; a host reads each stance's action, draws its outcome (good with probability 0.8 after
act, 0.4 after ask) and sends it back, on time (stream 1) or 0 to 6 events late with cancels, duplicates and payments of
turns never opened (stream 2). An independent Python learner replays RFC 0001 B.2 from the stance documents and the events.

| metric | measured | bar | |
|---|---|---|---|
| c1 share of the applied step equal to the learner's (and the same paid turn and eligibility), over 8,393 accepted payments in 50 runs | 100 % | >= 99 % | passes |
| c2 events whose Δ equals the learner's, after every one of 15,000 accepted events | 100 % | 100 % | passes |
| c3 outcomes accepted, of the 3,802 whose decision was open on arrival (streams 1 and 2, addressed) | 100 % | >= 99 % | passes |
| c4 duplicates, outcomes after a cancel and unknown turns (addressed) and ambiguous unaddressed feedback refused; runs equal to the run without their refused events | 100 % of 2,793; 50 of 50 byte-identical | 100 %; all | passes |

Also measured (not gated): the learner's open decisions equal the engine's `open_credit` after every event. Without the block
(0.8 semantics) 27 % of the outcomes credit a stance that took another level than the decision they were about, and all 138
duplicates and unknown payments move the weights; the 0.8 workaround (keep an outcome only when the previous stance took the
paid level) keeps 72 % of them. Act rate in the last 100 decisions: 0.655 on time, 0.628 addressed, 0.424 under 0.8
semantics, 0.657 with the workaround.

Reading it. Every accepted payment moved exactly the step RFC 0001 B.2 gives for the decision it named, and every duplicate,
cancelled or unknown payment was refused without changing anything. The stream is busier than its design intended: each
decision yields a message and an event carries at most one, so messages queue (254 still undelivered at the end over the
ten individuals) and with every event awaiting, 67 % of the addressed decisions were evicted at `max_open` 8 before their
outcome arrived (1,710 such outcomes refused, B-R2); paid outcomes were 0 to 7 events late. The fixture is not changed after
the fact.

**The twins re-measured with Design B** (`twins_addressed.py`, the frozen metric a fixture, streams, probes and bars; the
credit block and the addressed feedback added at run time: room A's praise names the decision 2 events earlier, room B's
criticism the one 3 events earlier):

| run | a1 mean d_life (95 % interval) | a2 tv_life vs min(tv_seed_A, tv_seed_B) | |
|---|---|---|---|
| as frozen (rewards pay the previous stance) | 0.145 (0.096 to 0.199) | 0.1307 vs 0.1329 | fails |
| addressed (Design B) | 0.1725 (0.1175 to 0.235) | 0.1223 vs 0.1366 | fails |
| control: readings as plain 0.8 number inputs against a fixed prior, rewards unaddressed (reported) | 0.155 (0.099 to 0.219) | 0.0295 vs 0.1296 | — |

Reading it. Addressing the rewards raises the share of probes whose action differs between two lives from 0.145 to 0.1725,
below the 0.30 bar. In both rooms the feedback still moves the learned caution weights away from `careful` (mean Δ careful
−0.32 after room A, −0.11 after room B), so the two lives drift the same way, and on the 10 probes with a tool failure the
habit and the failure's push make both lives careful or measured (23 of 400 actions differ). The control shows what the
normals add: with plain inputs the odds distance between two lives falls from 0.13 to 0.03.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,23 @@ All notable changes to this project are documented here. The format follows
per sense and an alert when a normal moved over `drift_cap / 2` within 50 events. `examples/persona/sensed.yaml` (with its
golden and rules) and `bench/individuality/` (benchmark metrics a, b, d and e; the P0 corpus) are new. Design notes:
`docs/rfcs/0001-senses-and-reward.md`.
- **Action-addressed feedback** (docs/persona.md §2.8): an optional `learning.credit` block. An event can mark its decision
as awaiting an outcome (`await`), a reward can name the decision it pays for (`for: <turn>`), and decisions can be
cancelled (`cancel`). Credit goes to the paid decision's own odds, scaled by an eligibility that halves per
`half_life_events` of delay; at most `max_open` decisions wait; ambiguous, duplicate, cancelled and unknown payments are
refused whole. Without `for`, feedback pays the previous stance as before.
- `for: {turn, stance}` binds the paid stance's digest (a mismatch is refused); every stance with a credit block carries
`credit` (the payment, the decisions closed, the ones still open, its own digest). A control line closes the open
decisions. `live verify` adds `open` to its summary, and `live verify --actions FILE` compares a host's action log with
the released levels; `monitor` draws the open decisions with their eligibility; `fuzz` adds late, reordered, duplicated,
cancelled and unknown payments; the MCP turn tools refuse `cancel`. `examples/agent-harness/addressed.json` (with
`late.py` and its golden) and benchmark metric c are new.

### Changed
- `live` says once on stderr when a persona without a senses block receives a `senses` key (still an undeclared input,
listed in `ignored`). The monitor's panel of the event's inputs is labelled INPUTS (it was SENSES).
- `live` says once on stderr when a persona without a credit block receives `await`, `for` or `cancel` (still undeclared
inputs, listed in `ignored`; feedback pays the previous stance).

## 0.8.3 - unreleased

Expand Down
67 changes: 62 additions & 5 deletions bench/individuality/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ PROBBIT_BIN=target/release/probbit python3 bench/individuality/run.py --only a,b
PROBBIT_BIN=target/release/probbit python3 bench/individuality/p0.py check # P0: byte-identical documents
```

**This release** ships senses (RFC 0001 Design A) and metrics a, b, d and e. Metric c (credit) needs action-addressed feedback
(Design B) and metric f a lineage budget (Design C); `run.py` skips both with a note. Without the credit block, rewards pay the
stance just before them, so the streams below carry no `await` or `for` keys.
**This release** ships senses (RFC 0001 Design A), action-addressed feedback (Design B) and metrics a, b, c, d and e. Metric f
needs a lineage budget (Design C); `run.py` skips it with a note. Metric a's frozen streams carry no `await` or `for` keys (the
fixture was frozen before Design B, so its rewards pay the stance just before them); `twins_addressed.py` re-measures it with
the credit block and addressed feedback added at run time.

**Freezing.** The fixtures, generators and bars are committed with their sha256 in `fixtures/MANIFEST` before any run.
`run.py` checks every listed file before it measures anything (exit 2 when one differs). Any later change is logged at the end
Expand All @@ -32,7 +33,12 @@ bench/individuality/
lives.py the seeded generator of the streams; its outputs are committed in lives/ and checked against MANIFEST
bars.json the gated bars
gen.py, p0/ the P0 corpus: personas, scripts, the recorded outputs and strands
twins.py habituation.py replay.py habits.py
dispatch.yaml metric c: the shipped dispatch policy with a credit block; dispatch-noblock.yaml without it
credit_draws.py metric c's seeded draws per decision, committed in credit/ and checked against MANIFEST
bars-c.json metric c's gated bars
sensed-plain.yaml metric a's reported control: the senses as plain 0.8 number inputs
twins.py habituation.py credit.py replay.py habits.py
twins_addressed.py metric a re-measured with Design B (and the control), on the frozen fixture and bars
```

## Shared fixtures
Expand Down Expand Up @@ -98,14 +104,32 @@ Measures whether the same reading lands differently after a learned normal, and
b4 ≥ 95 %; b5 ≥ 95 %.
- **Script.** `habituation.py`.

## Metric c: credit accuracy (with RFC Design B; skipped in this release)
## Metric c: credit accuracy (with RFC Design B)

Measures whether a late, reordered or cancelled outcome credits the decision it was for: an act-or-ask policy, 10 individuals
× 300 events, outcomes delivered on time and up to 6 events late (10 % cancelled, 5 % sent twice, 2 % paying a turn never
opened), against an independent Python learner. Bars: c1 ≥ 99 % of the applied step equal to the oracle's, c2 = 100 % of
events with the oracle's Δ, c3 ≥ 99 % of open outcomes accepted, c4 = 100 % of duplicates, late payments and unknown turns
refused, each refused run byte-identical to the run without the refused events.

- **Fixture.** `fixtures/dispatch.yaml` is the shipped dispatch policy (`examples/persona/dispatch-policy.yaml`) with a credit
block (`max_open` 8, `half_life_events` 32); `fixtures/dispatch-noblock.yaml` is the same without it, for variants (iii)
and (iv). `fixtures/credit_draws.py` draws, per individual from `random.Random(1000 + seed)`, each decision's blast radius
(0.05 to 0.65: no habit binds), its outcome draw, its delay, and whether it is cancelled, sent twice or joined by a payment
of a turn never opened; the draws are committed as `fixtures/credit/draws-<seed>.jsonl`.
- **Host.** `credit.py` drives one `probbit live --checkpoint-every 1` per run event by event: it reads each stance's action,
decides the outcome (good when the draw is below 0.8 after `act`, 0.4 after `ask`) and queues it. One message rides on
an event; a second one due on the same event moves to the next (the older decision goes ahead). A second copy of an outcome
is due one event after the original; for a cancelled decision the second copy is an outcome sent after the cancel. A payment
of a turn never opened names turn k + 1000. A refused event is followed by the same decision without its message, so
each run makes 300 decisions. Five runs per individual: stream 1, and on stream 2 (i) addressed, (ii) unaddressed, (iii)
0.8 semantics (cancels not sent), (iv) the 0.8 workaround.
- **Statistics.** The oracle replays RFC section B.2 from the stance documents and the events, over all five runs: c1 weighs
each accepted payment by the oracle's applied step (and requires the engine's `paid` turn and `e` to match), c2 compares Δ
after every accepted event, c3 counts the outcomes of runs stream 1 and (i) whose decision was open on arrival, c4 the
duplicates, outcomes after a cancel and unknown turns of (i) and the ambiguous unaddressed feedback of (ii), and every run
is re-run without its refused events.

## Metric d: replay

Measures whether an individual is reproducible from its log.
Expand Down Expand Up @@ -159,10 +183,43 @@ One run on an Apple M4 (development build, 10.0 s): a1 0.145 (interval 0.096–0
bars; b1 0.304149 with 0 of 4,522 values differing, b2 3.422977, b3 100 %, b4 100 % of 232 decisions, b5 100 % of 116 probes,
d 0 differing bytes over 240 strands, e 0 violations and 0 breaks over 9,442 stances with 72 bound pass. See BENCHMARKS.md §10.

With action-addressed feedback (development build): c1, c2, c3 and c4 100 % (8,393 payments, 15,000 events, 3,802 open
outcomes, 2,793 refusable, 50 of 50 runs byte-identical without their refused events); metric a re-measured with Design B
(`twins_addressed.py`): a1 0.1725 (interval 0.1175–0.235), a2 0.1223 against 0.1366, both still below their bars; the
plain-inputs control 0.155 and 0.0295. See BENCHMARKS.md §11.

Metric a fails its bars on this release: a1 0.1725 (interval 0.1175–0.235) against ≥ 0.30, a2 0.1223 against 0.1366.
The bars and the fixtures stay frozen and unchanged. Why they fail: the frozen fixture's readings and its feedback push the
same probes in opposite directions, so the two mechanisms cancel; and at the fixture's gene spread, two individuals differ in
odds more than one individual's two lives do. A pre-registered revision of the fixture is planned for 0.9.1; nothing will be
relabelled.

## Changes after freezing

- `p0.py` (listed in MANIFEST since the P0 record): `check` accepts the one stderr line `live` writes when a script sends a
`senses` key to a persona without the block, only on such a script, once, at its earliest such event, and reports those
outputs apart (`stderr_senses_warning_only`). The recorded outputs, scripts and strands are unchanged. Reason: the line is
part of the senses release (an undeclared key is named once); it is not a document, and every document stays
byte-identical. MANIFEST's p0.py line was updated with the change.
- `p0.py` and `fixtures/p0/EXPECTED`: the corpus now passes personas and rules by their paths relative to the repository
root, where every command runs. Reason: a fuzz document that finds a counterexample names the persona path as given in
its `explain` and `replay` strings, so `fuzz/adaptive-fixture` (43,726 bytes recorded from one clone) differed from a
clone at another path while the engine's bytes were the same. EXPECTED was re-recorded with the same released 0.8.2
binary (sha256 in MANIFEST) using relative paths: one line changed (`fuzz/adaptive-fixture`, now 43,646 bytes); every
other output, the scripts and the strands are unchanged. MANIFEST's EXPECTED and p0.py lines were updated with the
change.
- `p0.py` (wave 2): `check` also accepts the one stderr line `live` writes when a script sends `await`, `for` or
`cancel` to a persona without a credit block (RFC 0001 B.4: undeclared inputs, listed in `ignored`, and `live` says
so), on the same terms as the senses line: only where the script carries those keys, once, at its earliest such event,
each mandated line present exactly once. The outputs equal but for these lines are reported apart as
`stderr_warnings_only` (the key was `stderr_senses_warning_only`). EXPECTED, the scripts and the strands are
unchanged; MANIFEST's p0.py line was updated.
- Metric c (wave 2): its fixtures, the draws generator, the draws and its bars (`fixtures/bars-c.json`, the bars of the
metric c section above) were added to MANIFEST in their own section before they were ever run; `run.py` runs metric c.
`fixtures/sensed-plain.yaml` (metric a's reported control: the senses replaced by plain 0.8 number inputs) was frozen in
the same section. No earlier fixture, stream or bar changed.
- `credit.py`, changed after it had run once (no fixture, bar or gated computation changed; every gated value was the same in each
run): the 50 runs go side by side (the host waits on each stance, so a run is latency-bound); a run that refused no
event is not re-run (without refused events it is the same run, and metric d replays every strand); the reported
eviction and expiry shares are given per run, with the age of the addressed payments. The numbers of the run before the change stay in
the history: c1, c2, c3 and c4 1.0, 50/50 pairs identical.
Loading
Loading