in reviewhumanPR #113Memory
jmuslu: PN-Counter memory with basis-fusion invariants
This PR adds a memory:pn_counter reference plugin, a memory:basis_gated
Description
The pitch.
## Layer 10 — Memory
This PR adds a `memory:pn_counter` reference plugin, a `memory:basis_gated`
wrapper plugin, and three memory scenarios that test signed evidence
aggregation, basis-restricted memory fusion, and context-saturation rejection.
## Why
Convergence alone is not always the right invariant. A last-writer-wins
register can converge while discarding concurrent evidence. For agent memory,
especially under noisy or adversarial context, we often need a state object
that **preserves contributions** and only acts on information that can **legally
fuse** with the current task structure.
This PR treats memory as an applied mathematical object:
- **PN-Counter memory** preserves signed evidence as positive and negative
grow-only coordinates. Merge is pointwise max, so it is commutative,
associative, and idempotent. The derived value is `sum(positive) - sum(negative)`.
- **Basis-gated memory** only accepts evidence that restricts onto declared
basis dimensions. Natural-language saturation, code-shaped saturation, and
off-basis structured claims are ignored rather than fused into meaningful state.
The distinction that motivates the whole PR:
- **Convergence** as agreement on state, versus
- **Convergence** as preservation of meaningful evidence under legal fusion rules.
## What's in this PR
### `memory:pn_counter`
`PnCounterMemory` implements the existing `Memory` protocol (`read`, `write`,
`subscribe`, `cas`) and exposes CRDT-style replication helpers (`export`,
`merge`, `export_all`, `merge_all`).
Writes accept signed deltas such as:
```json
{"op": "inc", "amount": 2}
{"op": "dec", "amount": 1}
{"delta": -3}
```
The implementation rejects malformed state and non-integer coordinates
(including fractional values like `1.5`), so the PN-Counter stays a genuine
integer-coordinate CRDT.
**Self-coordinate defense.** Each replica tracks how much it has locally
written and, on merge, clamps any incoming state that tries to inflate its own
coordinate (`positive[self]` / `negative[self]`) beyond that truth, counting it
in `detected_forgeries`. This closes the one Byzantine attack a monotone max
would otherwise latch forever — a peer forging *your* coordinate. Forging a
*third* node's coordinate still requires per-contribution signatures and remains
the job of an authenticated wrapper; that boundary is documented and pinned by a
test.
### `memory:basis_gated`
`BasisGatedMemory` is a thin `Memory` that wraps an inner counter (a
`pn_counter` by default) and only lets a report become a delta if it restricts
onto a declared basis. It validates fusability inside `write`/`fuse` before
anything reaches the inner counter:
- the payload must be a JSON object (else `not-json` / `not-object`),
- its `node` must be declared (else `no-overlap`),
- its `basis` must be one of that node's dimensions (else `outside-basis`),
- and each `(node, basis)` pair fuses at most once (else `duplicate`).
This makes "memory that only fuses legal evidence" a **reusable layer artifact**
rather than a property of one bespoke agent. `read`/`subscribe`/`cas` and the
CRDT gossip helpers delegate to the inner counter, so a basis-gated counter
still converges across replicas exactly like a plain `pn_counter`.
### Scenarios
- **`memory_pn_counter_reports`** — agents write signed calculator-project
evidence into PN-Counter replicas and gossip state. The validator checks that
final converged memory preserves the aggregate signed delta, not merely that
all replicas converge to some value.
- **`memory_basis_fusion_calculator`** — a coordinator uses a `basis_gated`
memory over a `pn_counter`. It forwards raw reports to `memory.fuse` and traces
the accept/ignore decision; the memory layer is what validates fusability and
writes the delta. The saturation payload is a frozen public-domain
*Alice's Adventures in Wonderland* excerpt with no legal node/basis ov
…Try it
Open PR on GitHubView diffCheckout locally
git fetch origin pull/113/head:pr-113
git checkout pr-113