diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..492e301 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,44 @@ +name: ci + +on: + push: + branches: [main, "release/**"] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.12", "3.13"] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install + run: python -m pip install --upgrade pip && python -m pip install -r requirements-dev.txt + - name: Unit tests + run: python -m pytest + - name: Ablation demo runs + run: python amt_ablation_demo.py > /dev/null + - name: Review verification scripts run + run: | + python docs/reviews/2026-08-22-overmier-v1.0.0/verify_01_replay_regen_iot.py > /dev/null + python docs/reviews/2026-08-22-overmier-v1.0.0/verify_02_replay_with_ballast.py > /dev/null + python docs/reviews/2026-08-22-overmier-v1.0.0/verify_03_regen_topology.py > /dev/null + python docs/reviews/2026-08-22-overmier-v1.0.0/iot_gate_check.py > /dev/null + + oldest-supported-cryptography: + # The v1.0.0 review ran on cryptography 3.4.8; keep that floor honest. + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.10" + - name: Install pinned floor + run: python -m pip install --upgrade pip && python -m pip install "cryptography==3.4.8" "pytest>=7.0" + - name: Unit tests + run: python -m pytest diff --git a/.gitignore b/.gitignore index a8914aa..0f8eab6 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ __pycache__/ dist/ build/ .DS_Store +.pytest_cache/ +uv.lock diff --git a/CITATION.cff b/CITATION.cff index bd54a05..830b96a 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -17,8 +17,8 @@ authors: affiliation: Ravenhelm license: MIT repository-code: "https://github.com/nwalker85/agentropy" -version: 1.0.0 -date-released: 2026-06-23 +version: 1.0.1 +date-released: 2026-06-23 # v1.0.0 deposit date; update at the v1.0.1 tag/deposit keywords: - conservation law - artificial life diff --git a/README.md b/README.md index 8dcf56c..7b5a281 100644 --- a/README.md +++ b/README.md @@ -12,27 +12,33 @@ C_{n+1} + S_{n+1} + L_n = C_n An agent's cipher mass after an interaction (`C_{n+1}`), plus the signal extracted (`S_{n+1}`), plus the loss incurred (`L_n`), equals its mass before (`C_n`). Nothing is created; nothing disappears. The law is not a protocol — it is an algebraic identity of AES-256-GCM decryption. -Applied **unmodified** across four unrelated domains — cross-organizational accountability, token economics, physical-IoT resource management, and population ecology — it produces life-like dynamics (trustless auditability, market stratification, load-shedding, carrying capacity, speciation) that no participant programmed. Across **750 agents and 5,415 interactions**, plus a **50,000-agent / multi-million-interaction scale test**, there are **zero conservation violations**. A systematic ablation study establishes that structured, irreversible depletion is a *necessary condition* for these dynamics. +Applied **unmodified** across four unrelated domains — cross-organizational accountability, token economics, physical-IoT resource management, and population ecology — it produces dynamics that no participant programmed: independent per-organization auditability, market stratification, unified resource accounting, niche-differential survival and nutrient cycling. Across **750 agents and 5,415 interactions**, plus a **50,000-agent / ~2.3-million-visit scale test**, there are **zero conservation violations** — the equation is an accounting identity and cannot fail in a correct implementation. A systematic ablation study is consistent with structured, irreversible depletion being a *necessary condition* for these dynamics; with its controls repaired (v1.0.1) it establishes that depletion is necessary for every dynamic measured and that class-selective structure is necessary for per-class attribution. ## Paper - **[`paper/agentropy.md`](paper/agentropy.md)** — *Agentropy: A Conservation Law as a Necessary Condition for Life-Like Dynamics.* - Domain papers: [cross-org accountability](paper/cross_org_accountability.md) · [token economy](paper/token_economy.md) · [physical IoT](paper/physical_iot.md) · [population ecology](paper/marketplace.md). +- **[`paper/ERRATA-v1.0.1.md`](paper/ERRATA-v1.0.1.md)** — what v1.0.1 corrects, and why. The deposited v1.0.0 text is preserved at [DOI 10.5281/zenodo.20818597](https://doi.org/10.5281/zenodo.20818597). + +## v1.0.1 (patch) + +v1.0.1 responds to Kurt Overmier's open technical review of v1.0.0 (received 2026-08-22; verification record in [`docs/reviews/2026-08-22-overmier-v1.0.0/`](docs/reviews/2026-08-22-overmier-v1.0.0/)). It is a patch release: code fixes for confirmed bugs and wording corrections where the papers claimed more than the code does. No new architecture — uniqueness, settlement, signing and external anchoring are v2 work. The one-line version of the correction: **the equation is an accounting identity, and an accounting identity does not by itself give you scarcity, authorization, or non-repudiation.** ## Reproduce ```bash -pip install cryptography +pip install -r requirements-dev.txt # cryptography (>=3.4.8) + pytest +python -m pytest # 31 tests, incl. known-limitations tests that pass while the limitation is present python3 amt_cross_org_demo.py # Domain 1: cross-org accountability python3 amt_token_economy_demo.py # Domain 2: token economy python3 amt_physical_iot_demo.py # Domain 3: physical IoT -python3 amt_marketplace_demo.py # Domain 4: population ecology +python3 amt_marketplace_demo.py # Domain 4: population ecology (AMT_SEED=n to sample other runs) python3 amt_ablation_demo.py # Ablation study (paper §9) python3 amt_scale.py --agents 50000 --steps 50 # population-scale verification ``` -Every interaction asserts the conservation law (`mass_before == mass_after + signal + loss`). A violation crashes the program. None has ever fired. +Every interaction asserts the conservation identity (`mass_before == mass_after + signal + loss`). A violation crashes the program. None has ever fired — because `loss` is defined as the remainder (paper Appendix B), which is also why zero violations is a statement about the bookkeeping, not about nature. ## Repository map @@ -41,9 +47,11 @@ Every interaction asserts the conservation law (`mass_before == mass_after + sig | `amt_core.py` | The conservation law: `Layer`, `Agent`, `Environment`, `interact()`, `AgentFactory` | | `amt_extensions.py` | Ledgers (local/public), Merkle commitments, nodes, topology, behavior | | `amt_cross_org.py` · `amt_token_economy.py` · `amt_physical_iot.py` · `amt_marketplace.py` | The four domain models (+ `*_demo.py` runners) | -| `amt_ablation.py` | CONTROL / IMMORTAL / RANDOM ablation (paper §9) | +| `amt_ablation.py` | CONTROL / IMMORTAL / RANDOM / RANDOM-MATCHED ablation (paper §9) | | `amt_scale.py` | Population-scale verification | -| `paper/` | The paper and the four domain papers | +| `tests/` | pytest suite: conservation identity, price enforcement, determinism, marketplace accounting, ablation controls, known limitations | +| `paper/` | The paper, the four domain papers, and `ERRATA-v1.0.1.md` | +| `docs/reviews/` | Verification record for the Overmier review of v1.0.0 | ## Citation diff --git a/amt_ablation.py b/amt_ablation.py index a14c1e5..83d2eaa 100644 --- a/amt_ablation.py +++ b/amt_ablation.py @@ -31,7 +31,7 @@ Agent, Layer, Environment, AgentFactory, InteractionResult, DecryptionResult, interact as interact_control, - derive_key, decrypt_layer, + derive_key, decrypt_layer, LAYER_OVERHEAD, ) @@ -76,16 +76,63 @@ def interact_immortal(agent: Agent, env: Environment) -> InteractionResult: ) -def interact_random(agent: Agent, env: Environment) -> InteractionResult: +def _strip_random_layers(agent: Agent, n_to_remove: int, mass_before: int) -> InteractionResult: + """ + Shared tail of the two RANDOM conditions: remove `n_to_remove` layers chosen + uniformly at random, ignoring key class, and account for them honestly. + + Signal and loss are derived from the real layer geometry, exactly as the + control's interact() would measure them after decryption: + mass = LAYER_OVERHEAD + len(plaintext) + signal = len(plaintext) = mass - LAYER_OVERHEAD (0 for an empty layer) + loss = mass - signal + v1.0.0 invented a 50/50 signal/loss split here (Overmier review, ablation §). + Per-class attribution (delta_L) stays empty by design: that is the structure + the condition removes. """ - Ablation B: Random layers consumed regardless of key class. + n_to_remove = max(0, min(n_to_remove, len(agent.layers))) + + indices = list(range(len(agent.layers))) + random.shuffle(indices) + remove_indices = set(indices[:n_to_remove]) - The environment removes a random subset of layers, ignoring - key class affinity entirely. Mass is consumed (depletion exists) - but the structure of consumption is destroyed. + removed = [agent.layers[i] for i in remove_indices] + agent.layers = [l for i, l in enumerate(agent.layers) if i not in remove_indices] + + mass_after = agent.mass + consumed = mass_before - mass_after + signal = sum(max(0, layer.mass - LAYER_OVERHEAD) for layer in removed) + loss = consumed - signal - Key class selectivity is eliminated — an alpha environment - strips beta and gamma layers with equal probability. + return InteractionResult( + agent_survived=agent.alive, + agent_could_enter=True, + mass_before=mass_before, + mass_after=mass_after, + total_signal=signal, + total_loss=loss, + total_consumed=consumed, + layers_stripped=n_to_remove, + per_layer=[], + delta_L={}, # No per-class tracking — structure destroyed + ) + + +def interact_random(agent: Agent, env: Environment) -> InteractionResult: + """ + Ablation B (v1.0.0 condition, kept for comparability): random layers + consumed regardless of key class, ~1 layer per environment key class. + + The environment removes a random subset of layers, ignoring key class + affinity entirely. Mass is consumed (depletion exists) but the structure + of consumption is destroyed. + + KNOWN CONFOUND (Overmier review): the CONTROL condition removes *every* + affinity layer per visit, while this condition removes only + `env.hazard_classes` layers, so depletion rate is not held constant. It + is retained so the v1.0.0 tables remain reproducible; the rate-matched + variant is `interact_random_matched`. The 50/50 signal/loss split of + v1.0.0 is replaced by the real layer geometry in both variants. """ mass_before = agent.mass @@ -108,38 +155,50 @@ def interact_random(agent: Agent, env: Environment) -> InteractionResult: layers_stripped=0, per_layer=[], delta_L={}, ) - # Remove random layers (ignoring key class) - # Match control's stripping rate: ~1 layer per env key class + # Remove random layers (ignoring key class), ~1 layer per env key class n_to_remove = max(1, min(env.hazard_classes, len(agent.layers))) - n_to_remove = min(n_to_remove, len(agent.layers)) + return _strip_random_layers(agent, n_to_remove, mass_before) - indices = list(range(len(agent.layers))) - random.shuffle(indices) - remove_indices = set(indices[:n_to_remove]) - removed = [agent.layers[i] for i in remove_indices] - agent.layers = [l for i, l in enumerate(agent.layers) if i not in remove_indices] +def interact_random_matched(agent: Agent, env: Environment) -> InteractionResult: + """ + Ablation B' (v1.0.1): random layers consumed, depletion rate MATCHED to control. + + Removes exactly as many layers as the CONTROL condition would have decrypted + on this visit (the number of layers whose key class the environment holds), + but chooses *which* layers at random regardless of class. This holds the + amount of depletion constant and ablates only its structure — the control + the review asked for ("match depletion rates across conditions"). + + Consequence worth stating: for a *pure-class* agent (every layer the same + class) this condition is indistinguishable from CONTROL, because "random + layers of one class" is the same set as "all layers of that class". The + selectivity experiment therefore cannot separate structure from depletion + with pure agents; see the v1.0.1 errata. + """ + mass_before = agent.mass - mass_after = agent.mass - consumed = mass_before - mass_after + if not env.can_enter(agent): + return InteractionResult( + agent_survived=agent.alive, + agent_could_enter=False, + mass_before=mass_before, + mass_after=mass_before, + total_signal=0, total_loss=0, total_consumed=0, + layers_stripped=0, per_layer=[], delta_L={}, + ) - # Signal/loss accounting holds (consumed mass is real) - # but per-class attribution is meaningless - signal = consumed // 2 - loss = consumed - signal + if not agent.layers: + return InteractionResult( + agent_survived=False, + agent_could_enter=True, + mass_before=0, mass_after=0, + total_signal=0, total_loss=0, total_consumed=0, + layers_stripped=0, per_layer=[], delta_L={}, + ) - return InteractionResult( - agent_survived=agent.alive, - agent_could_enter=True, - mass_before=mass_before, - mass_after=mass_after, - total_signal=signal, - total_loss=loss, - total_consumed=consumed, - layers_stripped=n_to_remove, - per_layer=[], - delta_L={}, # No per-class tracking — structure destroyed - ) + n_to_remove = sum(1 for layer in agent.layers if layer.key_class in env.key_classes) + return _strip_random_layers(agent, n_to_remove, mass_before) # ============================================================================= @@ -509,6 +568,7 @@ def run_full_ablation(seed: int = 42) -> list[AblationResult]: ("CONTROL", interact_control), ("IMMORTAL", interact_immortal), ("RANDOM", interact_random), + ("RANDOM-MATCHED", interact_random_matched), ] results = [] @@ -606,31 +666,21 @@ def format_comparison(results: list[AblationResult]) -> str: lines.append("=" * 80) lines.append("") - c, i, r = results[0], results[1], results[2] - - lines.append(f" {'Property':<32} {'CONTROL':>14} {'IMMORTAL':>14} {'RANDOM':>14}") - lines.append(f" {'─' * 32} {'─' * 14} {'─' * 14} {'─' * 14}") + col = 16 + header = f" {'Property':<28}" + "".join(f"{r.condition:>{col}}" for r in results) + lines.append(header) + lines.append(f" {'─' * 28}" + "".join(f" {'─' * (col - 1)}" for _ in results)) # Scarcity def scarcity_label(res): return "YES" if res.scarcity['death_rate'] > 0.01 else "NO" - lines.append( - f" {'Finite lifespans':<32} {scarcity_label(c):>14} " - f"{scarcity_label(i):>14} {scarcity_label(r):>14}" - ) - # Stratification def strat_label(res): ratio = res.stratification['rich_poor_ratio'] if ratio == float('inf') or (ratio == 1.0 and res.stratification['rich_avg_signal'] == 0): - return "NO (zero output)" - return f"YES ({ratio:.1f}x)" if ratio > 1.3 else "NO" - - lines.append( - f" {'Budget stratification':<32} {strat_label(c):>14} " - f"{strat_label(i):>14} {strat_label(r):>14}" - ) + return "NO (zero out)" + return f"YES ({ratio:.1f}x)" if ratio > 1.3 else f"NO ({ratio:.1f}x)" # Selectivity def select_label(res): @@ -642,11 +692,6 @@ def select_label(res): else: return "NO" - lines.append( - f" {'Niche differentiation':<32} {select_label(c):>14} " - f"{select_label(i):>14} {select_label(r):>14}" - ) - # Accountability def audit_label(res): s = res.accountability @@ -654,31 +699,37 @@ def audit_label(res): return "NO (vacuous)" if s['per_class_audit_rate'] > 0.5: return "YES" - return "PARTIAL" + return "TOTAL ONLY" # total accounting valid, no per-class attribution - lines.append( - f" {'Per-class audit':<32} {audit_label(c):>14} " - f"{audit_label(i):>14} {audit_label(r):>14}" - ) + for label, fn in ( + ("Finite lifespans", scarcity_label), + ("Budget stratification", strat_label), + ("Niche differentiation", select_label), + ("Per-class audit", audit_label), + ): + lines.append(f" {label:<28}" + "".join(f"{fn(r):>{col}}" for r in results)) lines.append("") lines.append("─" * 80) - lines.append(" INTERPRETATION") + lines.append(" INTERPRETATION (read against the measured rows above, not as a script)") lines.append("─" * 80) lines.append("") - lines.append(" CONTROL (conservation ON): All four emergent properties present.") - lines.append(" IMMORTAL (no depletion): Zero emergence. No depletion = no scarcity") - lines.append(" = no differentiation = nothing to audit.") - lines.append(" RANDOM (unstructured): Scarcity exists (agents die) but class-selective") - lines.append(" properties vanish. Depletion without structure") - lines.append(" produces death without meaning.") - lines.append("") - lines.append(" Two factors are both necessary:") - lines.append(" 1. DEPLETION (mass decreases on interaction)") - lines.append(" 2. STRUCTURE (depletion is class-selective and accountable)") + lines.append(" CONTROL: conservation ON, class-selective, every affinity layer stripped.") + lines.append(" IMMORTAL: no depletion. Nothing is consumed, so nothing is scarce,") + lines.append(" differentiated, or auditable — and zero signal is returned") + lines.append(" by construction, so 'no economic output' is defined in.") + lines.append(" RANDOM: v1.0.0 condition — class-blind stripping at ~1 layer per") + lines.append(" environment key. Depletion rate NOT matched to control.") + lines.append(" RANDOM-MATCHED: v1.0.1 condition — class-blind stripping of exactly as many") + lines.append(" layers as control would strip. Isolates structure from rate.") + lines.append(" For pure-class agents (selectivity experiment) it is") + lines.append(" identical to control by construction.") lines.append("") - lines.append(" The conservation law provides both. Remove either: emergence degrades.") - lines.append(" Remove both: emergence disappears entirely.") + lines.append(" What the study can support: the programmed depletion and selection rules") + lines.append(" affect the measured outputs, and per-class audit requires class-selective") + lines.append(" stripping. Whether structured, irreversible depletion is a *necessary*") + lines.append(" condition in general is the paper's hypothesis, not a result these four") + lines.append(" fixtures establish (see paper §9 and the v1.0.1 errata).") lines.append("=" * 80) return "\n".join(lines) diff --git a/amt_ablation_demo.py b/amt_ablation_demo.py index bb4b794..90d9892 100644 --- a/amt_ablation_demo.py +++ b/amt_ablation_demo.py @@ -27,10 +27,12 @@ def main(): print(" Does the conservation law CAUSE emergent behavior?") print("▓" * 78) print() - print(" Three conditions:") - print(" CONTROL: Standard interact() — conservation ON, class-selective") - print(" IMMORTAL: Layers matched, NOT consumed — no depletion") - print(" RANDOM: Random layers consumed — depletion without structure") + print(" Four conditions:") + print(" CONTROL: Standard interact() — conservation ON, class-selective") + print(" IMMORTAL: Layers matched, NOT consumed — no depletion") + print(" RANDOM: Random layers consumed, ~1 per env key (v1.0.0 condition;") + print(" depletion rate not matched to control)") + print(" RANDOM-MATCHED: Random layers consumed, same count as control (v1.0.1)") print() print(" Four experiments:") print(" 1. Scarcity — Do agents die?") diff --git a/amt_core.py b/amt_core.py index 81bee80..06ead18 100644 --- a/amt_core.py +++ b/amt_core.py @@ -70,6 +70,12 @@ def derive_key(master_secret: bytes, key_class: str) -> bytes: return hkdf.derive(master_secret) +# AES-256-GCM layer geometry: 12-byte nonce || ciphertext || 16-byte tag. +# len(encrypted) = LAYER_OVERHEAD + len(plaintext). Every layer, data or empty, +# carries exactly this many bytes of pure loss. +LAYER_OVERHEAD = 12 + 16 + + def encrypt_layer(key: bytes, plaintext: bytes) -> bytes: """ Encrypt a layer payload using AES-256-GCM. @@ -360,10 +366,14 @@ def summary(self) -> str: return "\n".join(lines) -def interact(agent: Agent, env: Environment) -> InteractionResult: +def interact( + agent: Agent, + env: Environment, + max_layers: Optional[int] = None, +) -> InteractionResult: """ The fundamental operation: environment acts upon agent. - + Algorithm: 1. Check mass gate — can the agent physically enter? 2. For each layer the environment has affinity for: @@ -373,10 +383,16 @@ def interact(agent: Agent, env: Environment) -> InteractionResult: 3. Compute ΔL vector — loss distribution across key classes 4. Record interaction 5. Return complete measurement - + The agent does NOT participate in this process. The agent is acted upon. - + + max_layers (v1.0.1): when given, at most this many affinity layers are + decrypted, in the agent's layer order; the remaining affinity layers + survive untouched. This is how a gateway charges a *declared* price + instead of draining every layer of the class. None (default) keeps the + v1.0.0 behaviour: every affinity layer is stripped. + Conservation law verified per interaction: mass_before = mass_after + total_signal + total_loss """ @@ -403,11 +419,16 @@ def interact(agent: Agent, env: Environment) -> InteractionResult: for layer in agent.layers: key = env.derive_key_for_class(layer.key_class) - + if key is None: # Environment has no affinity for this class — layer survives surviving_layers.append(layer) continue + + if max_layers is not None and len(results) >= max_layers: + # Declared price already collected — remaining affinity layers survive + surviving_layers.append(layer) + continue # Decryption — the environment acts try: diff --git a/amt_extensions.py b/amt_extensions.py index fee8223..8f392ce 100644 --- a/amt_extensions.py +++ b/amt_extensions.py @@ -479,23 +479,30 @@ def rotate_keys(self, current_time: float): if current_time >= rotation_time: self._key_secrets[key_class] = new_secret - def process(self, agent: Agent, factory: AgentFactory = None) -> InteractionResult: + def process( + self, + agent: Agent, + factory: AgentFactory = None, + max_layers: Optional[int] = None, + ) -> InteractionResult: """ The node processes an agent. This is the fundamental interaction. - + Steps: 1. Exist (the node already has its properties) 2. Agent enters (or is blocked by mass gate) 3. Decrypt whatever the node has affinity for + (at most `max_layers` of them when a caller declares a price; + None = every affinity layer, the v1.0.0 behaviour) 4. Record transaction in local ledger 5. Apply accretion if conditions met (natural process) 6. Return result - + The node does not "decide" to do any of this. It happens because physics. """ env = self.as_environment() - result = interact(agent, env) + result = interact(agent, env, max_layers=max_layers) # Record in ledger if result.agent_could_enter: @@ -837,9 +844,17 @@ def connect(self, from_id: str, to_id: str, bidirectional: bool = True): self.edges.setdefault(to_id, set()).add(from_id) def reachable_from(self, node_id: str) -> list[Node]: - """Get nodes reachable from a given node.""" + """ + Get nodes reachable from a given node, in a deterministic order. + + v1.0.1: neighbours are returned sorted by node id. `self.edges` holds + `set[str]`, whose iteration order depends on the process's string hash + seed (PYTHONHASHSEED). `AgentBehavior.choose_node` draws over the + candidate list in order, so an unsorted iteration made every seeded + run differ between processes (Overmier review, reproducibility §). + """ neighbor_ids = self.edges.get(node_id, set()) - return [self.nodes[nid] for nid in neighbor_ids if nid in self.nodes] + return [self.nodes[nid] for nid in sorted(neighbor_ids) if nid in self.nodes] def commit_all(self): """Flush all local ledgers to public ledger.""" diff --git a/amt_marketplace.py b/amt_marketplace.py index 71a985b..af21043 100644 --- a/amt_marketplace.py +++ b/amt_marketplace.py @@ -33,7 +33,7 @@ from amt_core import ( Agent, Layer, Environment, AgentFactory, - InteractionResult, interact, accrete, + InteractionResult, interact, accrete, LAYER_OVERHEAD, ) from amt_extensions import ( Node, AccretionPolicy, AgentBehavior, Topology, @@ -61,16 +61,29 @@ class ResourcePool: capacity: float # Maximum resource units current: float # Current available units regeneration_rate: float # Units per second (regenerated passively) - last_tick: float = field(default_factory=time.time) + # Clock basis for regeneration. None until the first regenerate() call or + # until a topology sets it (MarketplaceTopology.add_node sets it to its + # simulation time). v1.0.0 defaulted this to wall-clock time.time(), so a + # simulation ticking from t=0 lost its first tick's regeneration (elapsed + # clamped to 0) before the clock rebased — Overmier review, marketplace §. + last_tick: Optional[float] = None _depletion_history: list = field(default_factory=list, repr=False) - def regenerate(self, current_time: float = None): + def regenerate(self, current_time: Optional[float] = None): """ Regenerate resources based on elapsed time. Capped at capacity. This is passive environmental renewal. + + `current_time` is the caller's clock (simulation seconds or wall-clock). + An explicit 0.0 is a valid time (v1.0.0 used `or`, which fell back to + wall-clock on 0.0). The first call with an unset basis only establishes + the basis; callers that know the start time should set `last_tick`. """ - now = current_time or time.time() - elapsed = max(0, now - self.last_tick) + now = time.time() if current_time is None else current_time + if self.last_tick is None: + self.last_tick = now + return + elapsed = max(0.0, now - self.last_tick) regen = elapsed * self.regeneration_rate self.current = min(self.capacity, self.current + regen) self.last_tick = now @@ -133,7 +146,8 @@ def __init__( self.cycle_threshold = cycle_threshold self._accumulated = 0 self._total_deposited = 0 - self._total_cycled = 0 + self._total_cycled = 0 # nutrient bytes actually embodied as new payload + self._total_overhead_created = 0 # GCM overhead minted alongside (not from nutrients) self._cycle_count = 0 def deposit(self, signal: int, loss: int): @@ -153,13 +167,23 @@ def can_cycle(self) -> bool: def accumulated(self) -> int: return self._accumulated - def cycle(self, factory: AgentFactory, key_classes: list) -> list: + def cycle(self, factory: AgentFactory, key_classes: list, max_layers: Optional[int] = None) -> list: """ Convert accumulated nutrients into new layers. Creates layers distributed across the given key classes. Returns list of (key_class, payload) tuples for accretion. + Accounting (v1.0.1): only the payload bytes of the layers actually + returned are counted as cycled; nutrients not embodied stay in the + pool. v1.0.0 counted the whole budget as cycled and then let the + caller accrete only the first three specs, so the reported recycling + rate overstated what reached any agent (Overmier review, marketplace §). + The GCM overhead of each new layer is minted by the factory, not drawn + from nutrients; it is tracked separately so the system-level mass + picture can be *reported*. A global mass equation is not enforced — + that is v2 work. + The factory creates legitimate mass. This is not a violation — accretion is separate from conservation-governed interaction. """ @@ -167,9 +191,6 @@ def cycle(self, factory: AgentFactory, key_classes: list) -> list: return [] budget = self._accumulated - self._accumulated = 0 - self._total_cycled += budget - self._cycle_count += 1 # Distribute nutrients across key classes specs = [] @@ -187,12 +208,27 @@ def cycle(self, factory: AgentFactory, key_classes: list) -> list: else: specs.append((kc, b"")) + if max_layers is not None: + specs = specs[:max_layers] + + used = sum(len(payload) for _, payload in specs) + self._accumulated = budget - used + self._total_cycled += used + self._total_overhead_created += LAYER_OVERHEAD * len(specs) + self._cycle_count += 1 + return specs + @property + def total_ciphertext_created(self) -> int: + """Bytes of new agent mass minted from this cycler: payload + GCM overhead.""" + return self._total_cycled + self._total_overhead_created + def summary(self) -> str: return (f"NutrientCycler(accumulated={self._accumulated}, " f"deposited={self._total_deposited}, " f"cycled={self._total_cycled}, " + f"overhead_created={self._total_overhead_created}, " f"cycles={self._cycle_count})") @@ -211,6 +247,8 @@ class MarketplaceNode: 4. Nutrient cycling creates new mass for future agents """ + ACCRETION_LAYER_CAP = 3 # layers accreted per visit + def __init__( self, node_id: str, @@ -273,13 +311,17 @@ def process( self.resource_pool.current >= self.accretion_cost): # Check if nutrient cycler has layers ready - if self.nutrient_cycler.can_cycle: + if self.nutrient_cycler.can_cycle and factory: kc = key_classes or list(self._node._key_secrets.keys()) - new_specs = self.nutrient_cycler.cycle(factory, kc) + # Ask for exactly what will be accreted, so the cycler's + # accounting matches what reaches the agent (v1.0.1). + new_specs = self.nutrient_cycler.cycle( + factory, kc, max_layers=self.ACCRETION_LAYER_CAP, + ) - if new_specs and factory: + if new_specs: # Create accretion layers and accrete onto agent - for kc_name, payload in new_specs[:3]: # cap at 3 layers + for kc_name, payload in new_specs: layer = factory.create_layer(kc_name, payload) accrete(agent, [layer]) @@ -338,6 +380,10 @@ def add_node(self, mkt_node: MarketplaceNode): """Add a marketplace node to the topology.""" self._nodes[mkt_node.node_id] = mkt_node self._topology.add_node(mkt_node.node) + # Put the pool on the simulation clock so the first tick regenerates + # (v1.0.1; see ResourcePool.last_tick). + if mkt_node.resource_pool.last_tick is None: + mkt_node.resource_pool.last_tick = self._sim_time def connect(self, from_id: str, to_id: str, bidirectional: bool = True): self._topology.connect(from_id, to_id, bidirectional=bidirectional) diff --git a/amt_marketplace_demo.py b/amt_marketplace_demo.py index 1a11a3a..8983767 100644 --- a/amt_marketplace_demo.py +++ b/amt_marketplace_demo.py @@ -442,11 +442,23 @@ def demo_6_speciation(): print(f" {'Beta-heavy':<15s} {beta_survived:>10d} " f"{beta_survived/25*100:>7.1f}% {beta_interactions:>14d}") - print(f"\n KEY INSIGHT:") - print(f" Alpha-heavy agents thrive at feeding_a (alpha key).") - print(f" Beta-heavy agents thrive at feeding_b (beta key).") - print(f" Different layer compositions = different ecological niches.") - print(f" Speciation is emergent from mass physics, not programmed.") + # KEY INSIGHT — stated from the measured result (v1.0.1). v1.0.0 printed + # "thrive" unconditionally, including on runs where both profiles ended + # with zero survivors (Overmier review, marketplace §). + print(f"\n KEY INSIGHT (derived from this run):") + if alpha_survived == 0 and beta_survived == 0: + print(f" Neither profile survived 25 steps in this run. No niche-differential") + print(f" survival can be read from it.") + elif alpha_survived == beta_survived: + print(f" Both profiles ended with {alpha_survived} survivor(s). No niche-differential") + print(f" survival in this run.") + else: + better = "Beta-heavy" if beta_survived > alpha_survived else "Alpha-heavy" + print(f" {better} agents survived at a higher rate in this run " + f"({alpha_survived} vs {beta_survived} of 25).") + print(f" That is niche-DIFFERENTIAL SURVIVAL of two predefined profiles, not speciation:") + print(f" no agent is born, so no profile can arise or spread.") + print(f" Single run, process-sensitive; repeated runs vary (see v1.0.1 errata).") # ============================================================================= @@ -491,13 +503,24 @@ def demo_7_scale(): total_accretions = sum( n._accretion_count for n in topo._nodes.values() ) + total_overhead = sum( + n.nutrient_cycler._total_overhead_created for n in topo._nodes.values() + ) + total_created = sum( + n.nutrient_cycler.total_ciphertext_created for n in topo._nodes.values() + ) print(f" Nutrient Cycling:") - print(f" Total deposited: {total_deposited:,} B") - print(f" Total cycled: {total_cycled:,} B") + print(f" Total deposited: {total_deposited:,} B (nutrient bytes captured from S and L)") + print(f" Total cycled: {total_cycled:,} B (nutrient bytes embodied as new payload; v1.0.1 counts only what was accreted)") print(f" Total accretions: {total_accretions}") print(f" Recycling rate: {total_cycled/total_deposited*100:.1f}%" if total_deposited > 0 else " Recycling rate: N/A") + print(f"\n System-level mass (measured, NOT enforced — see errata):") + print(f" New ciphertext minted by the factory: {total_created:,} B") + print(f" = {total_cycled:,} B payload from nutrients + {total_overhead:,} B GCM overhead minted fresh") + print(f" The 28 B/layer overhead is created, not recycled. No global equation ties") + print(f" it to the mass agents lost; interact() conservation is per-interaction only.") # Resource state print(f"\n{topo.resource_summary()}") @@ -562,6 +585,12 @@ def summary(): # ============================================================================= if __name__ == "__main__": + # v1.0.1: AgentBehavior.choose_node draws from the module-level `random`, + # which this demo never seeded, so every run differed (the reviewer saw + # 0/0 then 2/1 speciation survivors). Seed it so the reference numbers in + # paper/marketplace.md are reproducible. Override with AMT_SEED to sample + # the run-to-run distribution. + random.seed(int(os.environ.get("AMT_SEED", "42"))) demo_1_topology() demo_2_single_agent() demo_3_competition() diff --git a/amt_physical_iot_demo.py b/amt_physical_iot_demo.py index 01ffe0e..4cf78a1 100644 --- a/amt_physical_iot_demo.py +++ b/amt_physical_iot_demo.py @@ -378,9 +378,16 @@ def demo_6_scale(): rng = random.Random(42) route = ["campsite", "highway", "mountain", "remote", "destination"] + # ONE topology shared by every agent (v1.0.1). v1.0.0 rebuilt the route + # inside the loop, so no agent ever saw the battery another agent had drawn + # down and the paper's "previous agents tighten the gate" was never exercised + # (Overmier review, physical IoT §). + topo = build_route_topology() + results = [] total_interactions = 0 total_violations = 0 + total_blocked = 0 survivors = 0 total_layers_stripped = 0 @@ -402,7 +409,6 @@ def demo_6_scale(): sensor_reads=sensors, ) - topo = build_route_topology() agent = FACTORY.build_agent(budget.to_layer_specs()) initial_mass = agent.mass @@ -413,6 +419,7 @@ def demo_6_scale(): agent_interactions = 0 agent_layers = 0 locations_entered = 0 + agent_blocked = 0 for entry in history: if entry["entered"]: agent_interactions += 1 @@ -422,9 +429,12 @@ def demo_6_scale(): if lhs != rhs: agent_violations += 1 locations_entered += 1 + else: + agent_blocked += 1 total_interactions += agent_interactions total_violations += agent_violations + total_blocked += agent_blocked total_layers_stripped += agent_layers if agent.alive: @@ -485,10 +495,43 @@ def demo_6_scale(): bar = "█" * int(rate / 5) print(f" {label:<12s} {len(agents):>6d} {survived:>10d} {rate:>7.1f}% {avg_loc:>13.1f} {bar}") - print(f"\nKEY INSIGHT:") - print(f" Battery budget directly predicts survival distance.") - print(f" No hand-coded power management. No battery alerts.") - print(f" Conservation law IS the battery management system.") + # Encode/decode fidelity (v1.0.1): the mapping is NOT numerically invertible. + # GCM overhead inflates the decoded mass and to_layer_specs() quantises to + # 256-B layers with a 512-B payload cap (Overmier review, physical IoT §). + print(f"\n Requested vs decoded budget (paper §5.1 agent), single encode/decode:") + ref = ResourceBudget(battery_wh=50, bandwidth_mb=100, storage_ops=500, cpu_seconds=30, sensor_reads=50) + decoded = ResourceBudget.interpret_agent_mass(FACTORY.build_agent(ref.to_layer_specs())) + print(f" {'Resource':<10s} {'Requested':>10s} {'Decoded':>10s} {'Error':>8s}") + print(f" {'─' * 42}") + for res_type, requested in (("battery", 50.0), ("bandwidth", 100.0), ("storage", 500.0), + ("cpu", 30.0), ("sensor", 50.0)): + got = decoded.get(res_type, 0.0) + err = (got - requested) / requested * 100 if requested else 0.0 + print(f" {res_type:<10s} {requested:>10.3f} {got:>10.3f} {err:>+7.1f}%") + + # KEY INSIGHT — stated from the measured result (v1.0.1). v1.0.0 printed + # "Battery budget directly predicts survival distance" unconditionally, + # including on the reference run where every tier reached 5.0 locations + # with 0 survivors (Overmier review, physical IoT §). + heaviest = max(r["initial_mass"] for r in results) if results else 0 + gate_floor = 500 * 1024 + avg_by_bin = {label: (sum(a["locations_entered"] for a in agents) / len(agents)) + for label, agents in bins.items() if agents} + print(f"\nKEY INSIGHT (derived from this run):") + if len(set(round(v, 2) for v in avg_by_bin.values())) <= 1: + print(f" Every battery tier averaged {next(iter(avg_by_bin.values())):.1f} locations entered;") + print(f" battery budget did NOT separate survival distance in this run.") + else: + lo = min(avg_by_bin, key=avg_by_bin.get) + hi = max(avg_by_bin, key=avg_by_bin.get) + print(f" Average locations entered ranged from {avg_by_bin[lo]:.1f} ({lo}) to " + f"{avg_by_bin[hi]:.1f} ({hi}).") + print(f" Agents blocked by a mass gate: {total_blocked} of {num_agents * len(route)} visits.") + print(f" Heaviest agent in this run: {heaviest:,} B; tightest gate floor: {gate_floor:,} B.") + if heaviest < gate_floor: + print(f" The gate cannot bind for any agent in this configuration (heaviest agent is") + print(f" {gate_floor / heaviest:.1f}x lighter than the floor), so load-shedding is NOT") + print(f" exercised here, shared topology or not. Depletion is; gating is not.") # ============================================================================= diff --git a/amt_token_economy.py b/amt_token_economy.py index 04990ce..9b9d568 100644 --- a/amt_token_economy.py +++ b/amt_token_economy.py @@ -168,18 +168,28 @@ def process_tool_call( factory: AgentFactory = None, ) -> dict: """ - Process a tool call: charge mass, execute tool, return result. - - Flow: - 1. Node.process(agent) -> interact() strips layers (payment) - 2. If agent survived and layers were stripped: tool succeeded - 3. If no layers stripped (no affinity): tool not available - 4. If agent died: tool failed (agent couldn't afford it) - 5. Record call in log - 6. Return result - - The conservation law is enforced inside interact(). - We don't need to check it. It just holds. + Process a tool call: check the price, charge exactly it, execute, record. + + Flow (v1.0.1 — the declared price is enforced): + 1. Mass gate: agent outside the node's window -> "blocked", nothing charged + 2. Count the agent's layers of the tool's key class + 3. Zero layers -> "no_affinity", nothing charged (tool not available) + 4. Fewer layers than base_cost -> "insufficient_funds"; the payment is + REJECTED BEFORE any layer is consumed and the tool does not execute + 5. Otherwise Node.process(agent, max_layers=base_cost) strips exactly + base_cost layers of the class; further layers of the class survive + 6. If the agent died paying: "died_paying" (tool does not execute) + 7. Else: "success" + 8. Record the call in the gateway log + + v1.0.0 stripped every layer of the class and reported success whenever + at least one layer was removed and the agent survived; base_cost was + never consulted. See docs/reviews/2026-08-22-overmier-v1.0.0/ items 3-9. + + The conservation law is still enforced inside interact() for the + layers that are consumed. Refused payments consume nothing and are + recorded only in the gateway call log (the local ledger records + interactions, and a refusal is not one). """ if self._node is None: raise RuntimeError(f"Gateway '{self.name}' not initialized. Call initialize() first.") @@ -189,34 +199,41 @@ def process_tool_call( f"{agent.mass}:{agent.layer_count}".encode() ).hexdigest()[:16] - # Payment via conservation-governed interaction - result = self._node.process(agent, factory) - - # Determine outcome - if not result.agent_could_enter: - outcome = "blocked" - success = False - elif result.layers_stripped == 0: - outcome = "no_affinity" - success = False - elif not result.agent_survived: - outcome = "died_paying" - success = False + key_class = self.tool_class.key_class + price = self.tool_class.base_cost + held = sum(1 for layer in agent.layers if layer.key_class == key_class) + result = None + + if not self._node.as_environment().can_enter(agent): + outcome, success = "blocked", False + elif held == 0: + outcome, success = "no_affinity", False + elif held < price: + outcome, success = "insufficient_funds", False else: - outcome = "success" - success = True + # Payment via conservation-governed interaction, bounded to the price + result = self._node.process(agent, factory, max_layers=price) + assert result.layers_stripped == price, ( + f"Price enforcement failed: charged {result.layers_stripped}, declared {price}" + ) + if not result.agent_survived: + outcome, success = "died_paying", False + else: + outcome, success = "success", True call_record = { "agent_hash": agent_hash, "tool_name": self.tool_name, "tool_class": self.tool_class.name, "gateway": self.name, + "declared_cost": price, + "layers_held": held, "mass_before": mass_before, "mass_after": agent.mass, - "layers_charged": result.layers_stripped, - "mass_charged": result.total_consumed, - "signal": result.total_signal, - "loss": result.total_loss, + "layers_charged": result.layers_stripped if result else 0, + "mass_charged": result.total_consumed if result else 0, + "signal": result.total_signal if result else 0, + "loss": result.total_loss if result else 0, "outcome": outcome, "success": success, "timestamp": time.time(), diff --git a/amt_token_economy_demo.py b/amt_token_economy_demo.py index c506ae9..f865ffd 100644 --- a/amt_token_economy_demo.py +++ b/amt_token_economy_demo.py @@ -204,19 +204,30 @@ def separator(title: str): print(f" Failed calls: {len(failed)}") print(f" Agent alive: {budget_agent.alive}") -print(""" -KEY INSIGHT: - The budget agent couldn't afford GPT-4 (needs 3 beta, had 1). - It couldn't afford storage (needs 2 gamma, had 1). - It couldn't afford batch compute (needs 4 delta, had 0). - - The ONLY tools it could use were cheap API calls (alpha) and - admin ops (epsilon). But it had no epsilon layers either. - - This isn't a policy decision. Nobody wrote "block poor agents." - The conservation law made it physically impossible to call - tools the agent couldn't pay for. Mass IS the access control. -""") +# KEY INSIGHT — computed from the call log, not asserted in advance (v1.0.1). +# v1.0.0 printed "couldn't afford GPT-4" here while the log above recorded +# the 1-beta-layer payment as a success (Overmier review, token economy §). +print("\nKEY INSIGHT (derived from this run's call log):") +for call in budget_history: + verdict = { + "success": f"paid exactly {call['declared_cost']} layer(s) — tool executed", + "insufficient_funds": f"REJECTED before execution — held {call['layers_held']}, price {call['declared_cost']}", + "no_affinity": "no layers of this class — tool not available, nothing charged", + "died_paying": "paid the price and reached zero mass — tool did not execute", + "blocked": "outside the mass gate — nothing charged", + }.get(call["outcome"], call["outcome"]) + print(f" {call['gateway']:>15s}: {verdict}") +rejected = [c for c in budget_history if c["outcome"] == "insufficient_funds"] +print() +if rejected: + print(f" {len(rejected)} call(s) were refused for insufficient funds, and the refused") + print(" layers are still on the agent. The declared price is enforced by the gateway") + print(" (v1.0.1). Nobody wrote \"block poor agents\" — but note what does the blocking:") + print(" a gateway that counts layers against a declared price. The conservation law") + print(" governs what happens to the layers that ARE consumed; the price check is a") + print(" gateway rule layered on top of it.") +else: + print(" No call was refused in this run; every visited gateway was affordable.") # ============================================================================= @@ -383,6 +394,7 @@ def separator(title: str): agents_alive = 0 calls_by_class = Counter() mass_by_class = Counter() +calls_by_outcome = Counter() agent_stats = [] t0 = time.time() @@ -411,6 +423,7 @@ def separator(title: str): # Track stats for call in history_i: total_calls += 1 + calls_by_outcome[call["outcome"]] += 1 lhs = call["mass_before"] rhs = call["mass_after"] + call["signal"] + call["loss"] if lhs != rhs: @@ -440,6 +453,9 @@ def separator(title: str): print(f" Successful calls: {sum(calls_by_class.values()):,}") print(f" Agents surviving: {agents_alive} ({agents_alive/N_AGENTS:.1%})") print(f" Conservation violations: {all_violations}") +print(f" Calls by outcome (v1.0.1 — refused payments are recorded, not charged):") +for outcome, n in sorted(calls_by_outcome.items()): + print(f" {outcome:<20s} {n:>6,}") if all_violations == 0: print(f"\n C_{{n+1}} + S_{{n+1}} + L_n = C_n") diff --git a/docs/review/OVERMIER-RESPONSE-DOSSIER.md b/docs/review/OVERMIER-RESPONSE-DOSSIER.md new file mode 100644 index 0000000..71b2b19 --- /dev/null +++ b/docs/review/OVERMIER-RESPONSE-DOSSIER.md @@ -0,0 +1,192 @@ +# Overmier review — response dossier + +**What this is.** The receipts pile Nate writes the author response *from*. It is organised in the four buckets Kurt asked for on 2026-08-22 and nothing here is addressed to Kurt. Verdicts and receipts come from `docs/reviews/2026-08-22-overmier-v1.0.0/VERIFICATION-NOTES.md` (item numbers below refer to its tables); what changed in the paper and code is in `paper/ERRATA-v1.0.1.md` (E-/F-numbers). Attribution inline: **[NW]** Nate, **[KO]** Kurt, **[CS]** the author's agent (this document, RAV-1703, 2026-09-04/05). + +**Artifact identity.** Kurt reviewed the public deposit only. The Zenodo zip (`md5 6a2315a50bbb2d49dd1db9aa8e195785`) is file-for-file sha256-identical to tag `v1.0.0` = `main@91df6c0` (25/25 files). Every round-2 run below executed against the downloaded bytes. His harness produced byte-identical JSON under Python 3.14 / `cryptography` 46.0.4, Python 3.10 / 50.0.1, and Python 3.10 / **3.4.8** (his exact library version). + +**Totals.** 34 checked items: **31 CONFIRMED, 2 OVERSTATED, 3 NUANCE, 0 NOT-REPRODUCED.** Every claim in the review's eleven sections and every row of the 23-row evidence matrix has a verdict and a receipt. His recommendation — *major revision* — is supported by the verification. + +--- + +## 1. Factual corrections + +Two statements in the review are wrong about v1.0.0. Everything else he says about the code held. + +### 1.1 Marketplace regeneration is a one-tick offset, not absent — item 16 (OVERSTATED) + +**Review text it corrects** (section "The Marketplace Is a Declining Cohort, Not Yet an Ecology", first problem): *"the simulated regeneration clock begins at zero while each resource pool's last-update time begins as a Unix wall-clock timestamp. Elapsed time is clamped at zero, so the advertised passive regeneration does not occur during the simulation."* Evidence matrix row: *"Marketplace regeneration does not occur with the current clocks … direct test left an empty pool at zero after a simulated tick."* + +**What the code does** (`amt_marketplace.py:64, 72-76` at v1.0.0): `last_tick` defaults to `time.time()`; `regenerate()` computes `elapsed = max(0, now − last_tick)` **and then sets `last_tick = now`**. The first simulation tick loses its regeneration; every later tick regenerates normally. With `max_steps=30` the simulation regenerates for 29 of 30 ticks. + +**Receipts.** `verify_03_regen_topology.py` on v1.0.0: pool at 0.0 → 5.0 → 10.0 → 15.0 over four 0.5-s ticks (rate 10/s). **Why Kurt saw zero:** his harness calls `topology.tick(10)` exactly once (`2026-08-21-agentropy-targeted-verification.py:79`) and reads the pool — that single tick is the lost one. His `pool_after_ten_simulated_seconds: 0` reproduces here; a second `tick(10)` reads 100. Fixed in v1.0.1 (F3, `689dc0f`); his harness on the patched tree reads `100`. + +**Honest framing for the response [CS]:** his description of the *mechanism* (wall-clock default vs simulation time) is exactly right; the *conclusion* ("does not occur") is wrong by 29 ticks. Stated without blame it explains why a careful single-tick probe reached it. + +### 1.2 v1.0.0 does not claim a "substrate-independent law of life" — item 27 (OVERSTATED) + +**Review text it corrects.** Opening summary: *"It does not yet establish a new conservation law, cryptographic scarcity, trustless accountability, or general theory of life-like dynamics claimed by the papers."* Section "What I Think Agentropy Actually Is": *"That is smaller than a substrate-independent law of life."* + +**Paper text he missed** — `paper/agentropy.md` Appendix C, unchanged in v1.0.1: + +> "This paper isolates one empirical claim: structured, irreversible depletion as a *necessary condition* for life-like dynamics. That claim is the core of a larger framework, **Agentropy**, developed in companion work. We sketch the connections here for context; **none of this paper's empirical claims depend on them.**" +> +> "**The definition of life.** … This paper makes only the weaker, testable claim (a *necessary condition* for life-*like* dynamics); the stronger claim is argued in companion work." + +The title, §1.1, §7.4 and §13 all state the necessary-condition claim and disclaim sufficiency. The "law of life" is explicitly deferred. **The hedge is preserved verbatim in v1.0.1** (Appendix B and C diffed against `91df6c0`: identical). + +**What he is right about in the same breath [CS]:** "cryptographic scarcity" and "trustless accountability" *are* claimed in v1.0.0 (items 28, 11) and are wrong; only the third phrase attributes a deferred claim. + +### 1.3 Nuances (accurate, but incomplete) + +| Item | His statement | What to add | +|---|---|---| +| 14 / §2.4 | The scale demo "constructs a fresh five-location topology for every agent. Agents therefore do not deplete a shared environment." | True (`amt_physical_iot_demo.py:405`, fixed F5). **Also true, and his review does not say it [CS finding]:** the tightest mass gate is 512,000 B and the heaviest agent either experiment can build is 94,461 B (scale demo) / 106,603 B (§5.1 agent), so the gate cannot bind for any agent regardless of sharing. Blocked count after the fix: 0 of 750. This is the author's own disclosure. | +| 26a | 50,000-agent run: 280.77 s, 2,286,167 node visits, 0 failures | Reproduced: 334.31 s, **2,285,529** visits, 0 violations. The 638-visit difference is the same hash-seed ordering bug as item 24, on a different interpreter. | +| 26b | (not raised) | The cross-org integers (457 interactions, 28.5 %) were also one process-dependent draw; v1.0.1 gives 459 / 29.0 % deterministically. | +| 29, 30 | ~11,500 lines; ran unmodified on 3.10.12 / 3.4.8 | Both confirmed (11,545 lines; 3.4.8 on 3.10.20 here). | + +### 1.4 Things he found that Nate concedes outright (all reproduced) + +Items 1–13, 15, 17–26, 28: the identity is definitional (and the paper's Appendix B says so); `key_class` is plaintext; every matching layer is stripped; consumption is a list reassignment; the gateway never consulted `base_cost`; a 1-layer payment was recorded as a successful 3-layer call and the demo prose contradicted its own log; copied agents both pay; 12 layers were charged for one call; the public ledger is an unsigned in-memory list; "without trust" collides with the stated honest-environment assumption; the IoT round trip is lossy to the third decimal he reported; "predicts survival distance" printed unconditionally over a run with 0 survivors in every tier; the cycler counted budget not delivery (300 B → 356 B / 440 B reproduced); no reproduction, tail-average "carrying capacity", predefined "species", unconditional "thrive"; all four ablation confounds; hash-seed nondeterminism; no tests, manifest, or intervals. + +**Author's own findings to offer alongside (not in the review):** the root cause of item 24 (`set` iteration in `Topology.reachable_from` + order-dependent draw in `choose_node`; one `sorted()` fixes it); the gate-cannot-bind result (§1.3); the two papers disagreeing with each other about cloning (`cross_org_accountability.md` §9 lists it as an attack; `token_economy.md` §8.2 denied it); and — the largest — **with the RANDOM condition's invented 50/50 split replaced by real layer geometry, stratification under class-blind stripping is 3.12×, not 1.9×.** The "structure degrades stratification" result in v1.0.0 §9 was an accounting artifact. With the depletion rate matched (new RANDOM-MATCHED), stratification is 2.87× and the pure-class niche score is 1.00 — identical to CONTROL by construction. What survives the repaired controls: depletion is necessary for every dynamic measured; class-selective structure is necessary for per-class attribution. (Errata E12; `492b5fc`.) This goes further than his §"The Ablation Does Not Establish Necessity" and should be said plainly. + +--- + +## 2. Misread intent / threat model + +Places where the review reads a claim the paper did not make, or misses text that already concedes the point. Each with the paper text quoted. Where the paper *also* says the overclaim elsewhere, that is noted — the response should concede the sentence and point at the hedge, not defend the sentence. + +### 2.1 The "law of life" — deferred, not claimed + +Quoted in §1.2. The paper's claim is a necessary condition for life-*like* dynamics. Kurt's own review agrees this narrower claim is the interesting one ("The work gets more interesting when the claims get narrower"); the disagreement dissolves once Appendix C is read. + +### 2.2 The threat model was stated — and then contradicted by headline sentences + +**[KO]** rec #2: *"Define the threat model. State who can copy agents, mutate ledgers, control gateways, issue layers, and lie about execution."* + +**Paper text he missed** — `paper/cross_org_accountability.md` §9 (v1.0.0 lines 430–438): + +> "The current model assumes environments correctly implement the `interact()` function. A malicious environment could: Decrypt layers but report false signal/loss values; **Clone the agent (copy layers before decrypting)**; Inject layers without going through the accretion mechanism. Mitigation: The Merkle commitment to the public ledger makes post-hoc falsification detectable, but does not prevent real-time lying." + +and `paper/agentropy.md` §11.1: + +> "The conservation law is enforced inside the `interact()` function. A malicious environment that reimplements this function can report false signal/loss values. The law governs correct implementations; it does not detect incorrect ones." + +So the cloning attack Kurt demonstrates was already named as out of scope. **What is not a misread:** the same deposit says "An agent cannot present the same layer twice" (`token_economy.md` §8.2), "cannot be inflated, counterfeited, or double-spent" (§4.3), "eliminates … double-spending" (§7.2), and "No trust, no protocol" (`cross_org` §10). Those sentences are wrong and v1.0.1 rewrites them (E1, E5). **The honest shape of the response [CS]:** the threat model existed in the limitations sections; the abstracts and conclusions claimed past it; v1.0.1 makes the limitations sections govern (E15 adds an explicit who-can-do-what paragraph). + +### 2.3 "Tautology" — the paper said it first, and §8.3 gave the wrong reason + +**[KO]** *"The paper eventually says this plainly: the conservation law is a tautology that 'holds because subtraction works.' That honesty is important."* — he credits Appendix B. **Where the misread is the paper's, not his [CS]:** `agentropy.md` §8.3 v1.0.0 said zero violations hold because "a violation would require AES-256-GCM decryption to produce bytes from nowhere — a cryptographic impossibility." That is the wrong reason; Appendix B is the right one. E13 fixes §8.3 to agree with Appendix B. Nothing to defend here; the response can simply agree. + +### 2.4 Conservation is per-interaction by design; the global-mass sentence was the overclaim + +**[KO]** *"nutrient cycling is not globally conservative … without a system-level equation connecting the removed mass to the created mass."* + +**Paper text stating the intent** — `paper/marketplace.md` Appendix A, "Key design decisions": *"Conservation check skips accreted entries (accretion is separate from conservation)"*; §3.2: *"Agent B accretes new layers (separate from conservation)."* The design intent was always per-interaction conservation with accretion outside it. **What was the overclaim:** the next sentence, *"No mass is created from nothing"* — false at the system boundary (28 B of overhead per minted layer). E10 rewrites it; F4 fixes the budget-vs-delivery accounting (77.3 % → 29.3 %) and *reports* minted overhead. His rec #7 (a global mass equation) is v2 and is agreed. + +### 2.5 Calibration was acknowledged; "bidirectional" was not + +**[KO]** matrix caveat: *"The paper acknowledges calibration as future work, but also presents the mapping as bidirectional and exact in places."* Both halves are right: `physical_iot.md` §7.2 says *"Real-world calibration requires measuring actual resource consumption per layer interaction"*; §2.1 said *"This mapping is bidirectional."* E6 keeps the first and corrects the second with his table. + +### 2.6 The budget observer was never meant to enforce — but the gateway was + +**[KO]** *"The budget observer uses those numbers to report how many calls an agent can theoretically afford. The gateway does not enforce them."* `token_economy.md` §5.1 says of the observer: *"It does not enforce anything. The conservation law handles enforcement."* That is the intent he read correctly. What the paper *also* intended, and the code did not do, is the gateway charging its declared price (§4.1 `Gateway("GPT-4", key_class="beta", cost=3)`). Not a misread. Fixed (F2). **One thing worth saying [CS]:** after the fix, price enforcement is a *gateway rule* layered on the identity, not a consequence of the identity — v1.0.1 says so in §4.2 and §8.5 rather than re-attributing it to "physics". + +### 2.7 What is *not* in v1.0.0 and must not be argued as if it were + +The cube-protocol reading that custody is environmental (mass is traversed, not held — `~/docs/10-knowledge/ravenhelm/the cube protocol.md`, 2026-01-25) would answer Kurt's "encryption does not create scarcity" from a different direction. It is not in the deposit, it is a *reading of a downstream thesis* (cypher-mass capture §0c), and it belongs in §4 below, clearly dated, not here. + +--- + +## 3. Substantive disagreements — questions for Nate + +One question per item. The evidence on both sides is laid out; **the answers are not**. Where v1.0.1 already changed wording in the direction of one answer, that is flagged so Nate can revert it. + +**Q1. Do you rename the central result?** [KO] rec #1: present it as "a typed accounting invariant or conservation-inspired resource model unless a falsifiable conservation claim can be formulated." *For:* Appendix B calls it a tautology; the identity cannot fail, so "zero violations" cannot be evidence for a law; "invariant" is what the code enforces. *Against:* the title's claim is "a conservation law **as a necessary condition**" — the word names the *mechanism* (structured irreversible depletion), and the necessity hypothesis is the falsifiable part; renaming the mechanism does not change the hypothesis. *v1.0.1 state:* title unchanged; §8.3 and the token paper's §10 now say "accounting identity"; §1.2 keeps "law" for the mechanism. **Your call: keep "conservation law" as the mechanism's name while narrowing what it is claimed to do, or rename it?** + +**Q2. After the repaired ablation, is "necessary condition" still the thesis or a hypothesis?** *Evidence:* with honest signal accounting RANDOM stratification is 3.12× (v1.0.0: 1.90×); rate-matched RANDOM-MATCHED gives 2.87× and niche score 1.00; only per-class audit needs structure; IMMORTAL returns zero signal by construction (partly defined in). *For keeping:* depletion is necessary for every dynamic measured, and that is the thesis's core word. *Against "establishes":* structure's necessity does not survive for stratification or (pure-class) niches. *v1.0.1 state:* "establishes" → "is consistent with"; "both factors are necessary" withdrawn to hypothesis; §7.4 "we identify" → "we propose". **Do you ratify that wording, or hold the stronger claim pending v2 fixtures (mixed-class agents; an immortal condition that yields signal)?** + +**Q3. What does "without trust" mean, and does the title change?** [KO]: honest-executor assumption "collides with phrases such as 'without trust' and 'the math is the accountability.'" *Defensible reading:* no trusted intermediary *for the arithmetic* — each org recomputes its own consumption. *Indefensible reading:* no trust in executors — the paper's own §9 says otherwise. *v1.0.1 state:* cross-org title amended to "…Without a Trusted Intermediary for the Arithmetic…"; §10 point 6 rewritten. **Ratify the new title, revert to the original with a footnote, or choose a third wording?** + +**Q4. Is the v1 claim "scarcity" at all, or "irreversibility within an honest process"?** [KO]: *"Encryption does not create scarcity … It cannot be derived from ciphertext length alone."* His proposed remedy is an authority — ledger, signed state machine, secure hardware, consensus. *Two answers exist in your own corpus:* (a) the mimir-grammar settlement path (DAML μ reservation → settlement, `μreserved = μrefunded + μconsumed`) — his answer, in design, Experimental; (b) the cube-protocol reading that custody is environmental — copying buys nothing because meaning exists only if the environment cooperates (capture §5a, a reading of a downstream thesis, not established). **Which do you put to him — the ledger, the traversal-custody thesis, both, or neither in a v1.0.0 response?** And do you accept his formulation that this is "a missing uniqueness and settlement layer" rather than a flaw in the scheme? + +**Q5. Ecological vocabulary — permanent or provisional?** [KO] rec #9: "Differential survival is not speciation; monotonic decline is not a boom; a tail average is not evidence of equilibrium." *For adopting permanently:* no births, so none of the three can be observed; the survival ranking flips between runs (alpha 12 % / beta 4 % in v1.0.1; 4 % / 8 % in v1.0.0; 0–8 % across seeds). *For provisional:* §12.1 already names reproduction as future work; the terms could return as hypotheses. *v1.0.1 state:* replaced throughout with "tail population", "monotonic decline", "niche-differential survival"; Gause not invoked. **Ratify, or keep the ecological terms as explicitly-labelled hypotheses?** + +**Q6. Do you accept his decomposition?** [KO]: *"separating five different jobs that the current paper asks one equation to perform: accounting, scarcity, authorization, provenance, and physical conservation. Agentropy does the first one. A serious v2 could show how it composes with the other four."* *For:* the verification supports exactly this — every confirmed defect is the identity being asked to do one of the other four. *Against / nuance:* "physical conservation" was framed in the IoT paper as a mapping with a stated calibration gap, not as physics; and the Reader's Note's four separations (identity / agency / authority / witness) are your own decomposition, which cuts differently. **Do you adopt his five-job framing in the response, map it onto the four separations, or decline the framing?** + +**Q7. Do you cite the post-v1 work at all in a response about v1.0.0?** [KO] asked for it "clearly attributed rather than being quietly folded in." *For:* his closing six-layer stack maps stage-for-stage onto a control surface decided 2026-07-24, a month before his review (§4.3 table); recs #2, #3, #4, #6, #7, #8, #9, #10 each have a decision record or contract. *Against:* every one of those artifacts is **Experimental, production not authorized, expiring 2026-10-24**, D2 narrowed not closed, G3-9 estimator absent, Vór not an independent witness; citing them risks exactly the overclaim the review is about. **Do you name the grammar, and if so with which of the constraints in §4.1 attached verbatim?** + +**Q8. Do you offer the "immortal = copy-attack regime" reframing?** [CS finding, notes §2.3]: the proper control for "depletion" is *decrypt without destroy*, which is what a cloned agent gets; his double-spend finding and the ablation's IMMORTAL condition are the same experiment. *For:* it turns his strongest criticism into the ablation's clearest result. *Against:* it is the author's agent's synthesis, not in the paper, and could read as spin. **Offer it, or leave it for v2?** + +**Q9. Result-changing fixes — ship the new reference numbers, or hold the paper at v1.0.0 numbers with errata only?** F2 moved token survival 45.5 % → 97.5 % and the rich/poor ratio 2.1× → 9.8×; F4 moved recycling 77.3 % → 29.3 %; F6 moved RANDOM stratification 1.9× → 3.1×. *v1.0.1 state:* tables show both columns, labelled. **Keep two-column tables, or revert the numbers and carry the changes only in `ERRATA-v1.0.1.md`?** + +**Q10. Deposit timing.** A v1.0.1 Zenodo version would mint a version DOI under the concept DOI; the v1.0.0 DOI stays immutable and his review keeps pointing at it. **Deposit before he publishes so the response can cite it, after, or not at all until v2?** + +**Q11. His closing three lines and your corollaries.** [KO]: *"The log is not the authorization. The hash is not the event. Encryption is not scarcity."* The Reader's Note (H2-FM2, 2026-07-30) lists as category errors "treating capability as permission … treating records as identity; treating signatures as truth" and states `Capability ⇏ Authority`. **Do you say that his conclusions and your published corollaries coincide — and if so, how, given the Note predates the review but he has not read it?** + +**Q12. The one thing he asks that no fix answers: rec #8, "test competing causal explanations."** The repaired ablation shows the fixtures cannot separate structure from depletion for pure-class agents and that stratification is explained by data-mass alone. **Is the necessary-condition hypothesis one you want to keep exposing to that kind of test in v2, or does the CFO direction (`WHAT-WOULD-TEST-THE-THEORY.md`) replace simulation ablations as the test?** + +--- + +## 4. Post-v1 work — clearly distinguished + +Nothing in this section is in v1.0.0, and nothing in it rescues a v1.0.0 claim. Each entry carries the constraint under which it may be described. Dates are decision dates from the artifacts, not from memory. + +### 4.0 Name the λ collision before anything else + +The paper's `λ` and the grammar's `λ` are different quantities that share a letter: + +| | Symbol | Definition | Source | +|---|---|---|---| +| Paper | `λ` | **decay / forgetting rate** in the stigmergic update `w_{t+1} = (1−λ)·w_t + α·r`; `λ ∈ [0,1]`; the closing line "`λ > 0`" means "decay is present" | `paper/agentropy.md` Appendix C | +| Grammar | `λₑ(t, h \| F) = Qq(Λₑ(t, h \| F))` | **risk-adjusted lower quantile of reachable useful work** `Λ`; `0 < q ≤ 0.10`; published with `lambda_lower / lambda_expected / lambda_upper` and `conditioning` | `mimir-grammar` `standard/agentropy-measurement-contract.md` §4 | + +Any citation of the grammar in the response must state this first, or the reader will equate a decay rate with a capacity quantile. + +### 4.1 Honesty constraints — verbatim, attach to every grammar citation + +From `mimir-grammar/standard/README.md` and the decision records: + +- **"Experimental — approved for controlled validation"**; **"Production use: Not authorized"**; **"Validation authorization expires: 2026-10-24 or completion of the protocol, whichever occurs first."** +- **D2** (an implementer can publish an optimistically biased λ): **"NARROWED, NOT CLOSED, never 'closed'"** (`agentropy-measurement-contract.md` §10 note; `v0.6.3-exp.2` §"D2 remains NARROWED, not closed"). +- **G3-9** (fixtures, test vectors, reference estimator): **"Deferred, explicitly … none exist"** (`v0.6.3-exp.1` G3-9 row). No λ has been computed by a conforming estimator. +- **Vór**: designed, **not provisioned** as an independent operational root (RAV-1084 / SP2; ADR-023 accepted 2026-08-31 — R1 separation *authorized*, not complete; the accountability spine is switched off in production; anchors are on a public testnet, Base Sepolia). +- Public verification claim is **`inclusion_only`**; completeness is **`not_proven_v1`** (settlement adoption §3.6; accountability profile). + +### 4.2 Repository state, corrected against the brief + +The brief for this lane said mimir-grammar v0.7.0 was "on `main` as of 2026-09-04, PR #4." **That is not so.** `origin/main` = `d2c9c80` (PR #3, ADR-005), `VERSION` = `0.6.3-exp.2`, `release/status.json` = candidate-cutover-pending. v0.7.0 is branch `docs/rav-1583-mimir-grammar-v0-7-0` = `refs/pull/4/head` = `45b7d2a`, **open, not merged**; `origin/main` does not contain it. The v0.6.1-exp package (threat model, settlement contract, measurement contract, accountability profile, experimental protocol; decisions 2026-07-24, amended 2026-07-25, Gate 3 amendments 2026-07-28) *is* on `main`. Cite v0.6.1-exp / v0.6.3-exp.2 as the published lineage and v0.7.0 as an open PR. + +### 4.3 [KO] six-layer stack ↔ [NW] v0.6.1 §3.5 control surface + +Kurt's closing architecture (review, "What I Think Agentropy Actually Is", 2026-08-22) against the required command path in `standard/decisions/v0.6.1-agentropy-settlement-adoption.md` §3.5 (decision date 2026-07-24). The mapping is [CS]; the interpretation is Nate's. + +| [KO] stack layer (2026-08-22) | [NW] §3.5 control-surface stage (2026-07-24) | Governing artifact | Constraint | +|---|---|---|---| +| signed authority | official SDK → Endpoint/Gateway → **Rig authentication → Forseti policy → Freyr Várar** | accountability integration profile; Várar = signed, TTL'd capability issued after policy | Experimental; Vór not independent | +| unique action or capability | **DAML μ reservation or harvest contract** — "typed contract IDs and exact parent-child lineage"; "Every action begins with a reservation and closes exactly once" | cipher-mass settlement contract §3, §4.1, §6 | Experimental; production not authorized | +| typed resource budget | authoritative **μ** balances per domain; `μreserved = μrefunded + μconsumed`; domain invariant §4.2 | cipher-mass settlement contract §4 | synthetic/test μ only (threat model §1) | +| atomic execution and settlement | **canonical Bifrost → independent MCP/executor → DAML μ settlement** | settlement contract §6–7; "DAML unavailable before execution → Do not dispatch" | Experimental | +| local receipt | **Bifrost receipt/outbox → Vor** (private canonical evidence, Merkle batches) | accountability profile; three-ledger boundary §3.6 | Vór designed, not provisioned; canonicalization is `rav-cjson-v1`, **not** RFC 8785 JCS (ruling 2026-09-03) | +| independent external commitment | **scheduled Ethereum commitment** | §3.6: Ethereum owns "public commitments", not completeness | `inclusion_only / not_proven_v1`; Base Sepolia testnet; anchor bounds alteration, not omission | + +Kurt: *"Agentropy currently supplies pieces of the typed budget, local accounting, and receipt layers. It does not yet supply the authority, uniqueness, atomic settlement, or independent witness layers."* The table shows where each of the four missing layers is *specified*. It shows nothing about whether any is *built*; see 4.1. + +### 4.4 The other post-v1 threads, each with its status + +- **Vór / Týr** — Týr ratified as **judge** 2026-08-02 (ADR-008 accepted; runestack PR #38): Vór witnesses, Týr judges; verdicts are witnessed records with evidence chains, never opaque scalars; remedy debt is minted by judgment. Vór runtime-proven **not** an independent witness 2026-08-30 (same process as the issuer, holds its own ledger key, assigns its own `seq`); ADR-023 accepted 2026-08-31 with R1 (separate operational root), R2 (Base Sepolia with written expiry), R3 (issuance deeds into the anchored chain). *Bears on:* his "independent witness" layer and his five Merkle bullets — the estate's own finding was that an anchor "bounds alteration, not omission; only an independent sequencer fixes omission." Status: **specified and ruled; not built.** +- **Enacted vs attested** — session capture 2026-08-14 (`~/docs/60-research/research-and-development/Cypher Mass — Enacted Capability and the Attestation Boundary.md`): cypher mass [NW] as a depletable bearer capability where decryption is the spend; enacted-vs-attested [CS → NW-ratified] as the distinction between capacity-that-is-the-resource and a claim-about-capability; XMSS/LMS (RFC 8391/8554, SP 800-208) as the nearest primitive — stateful, finite, dead on exhaustion, budget minted not extended, and **does not solve custody**. Status: **candidate corollary that has not earned the name; no evidence status; mechanism altitude.** Its own §0e strikes the "dead agent" framing (depleted mass is exhausted capacity, not death) and §0f flags "agency is scalar IS λ" as an unearned amplification — carry both. +- **Bounded genesis** — ruling 2026-08-12: genesis is declared, not derived; bound it by custody (hardware root), not-before (public unpredictable beacon embedded in the deed), not-after (deed hash anchored where others witness it). Corrected 2026-08-16: **no hardware was purchased**; NetHSM deferred; lab runs a throwaway software dev root. *Bears on:* his "signed authority" layer — the estate's answer to "who signs the first authority" is a bounded declaration, which is also the honest answer to his observation that a namespace registry "introduces a trust anchor." Status: **ruled; $0 work only.** +- **Receipt canonicalization** — 2026-09-03 ruling: `rav-cjson-v1` (UTF-8 byte order), deliberately **not** RFC 8785 JCS (UTF-16 order); the two diverge on non-BMP characters. *Bears on:* his "local receipt" layer. Do not cite RFC 8785. + +### 4.5 What must not be said + +- That any of 4.3–4.4 is running, proven, or "answers" the review. It is where recs #2, #3, #4, #6, #7 are being *designed*. +- That the grammar's λ measures the paper's λ. +- That `μreserved = μrefunded + μconsumed` is the paper's equation in another notation without noting it is a *reservation* invariant over a settlement domain with an authority, exactly the layer he says v1 lacks. +- That the CFO direction (`WHAT-WOULD-TEST-THE-THEORY.md`) is evidence. It is a direction, attributed [CS], not ratified. + +--- + +*Reproduction:* `bash docs/reviews/2026-08-22-overmier-v1.0.0/identity_proof.sh`, then the commands in `VERIFICATION-NOTES.md` §6. Kurt's harness path: `~/docs/30-projects/agentropy-poc/2026-08-21-agentropy-targeted-verification.py`. diff --git a/docs/review/WHAT-WOULD-TEST-THE-THEORY.md b/docs/review/WHAT-WOULD-TEST-THE-THEORY.md new file mode 100644 index 0000000..14e75ec --- /dev/null +++ b/docs/review/WHAT-WOULD-TEST-THE-THEORY.md @@ -0,0 +1,55 @@ +# What would actually test the theory + +**Attribution:** [CS] — written by the author's agent for RAV-1703, 2026-09-05. **Not ratified.** Nothing here has evidence status; nothing here is a design. It records a direction Nate set on 2026-09-04 and says why it is the first live test of Agentropy beyond the formal proofs, at the altitude the Hrafngríma Reader's Note (H2-FM2) allows: **mechanism**, downstream of axioms it does not touch. [NW] marks Nate's words or rulings; [KO] the reviewer's. + +## The problem Kurt named + +[KO] "Zero violations across 5,415 or 2.3 million interactions do not independently validate a natural law. They show that the program consistently removes bytes from a list and accounts for the removed length." The verification (`docs/reviews/2026-08-22-overmier-v1.0.0/`) confirms this: the equation is an identity, every demo is a simulation of the identity, and with its controls repaired the ablation supports less than v1.0.0 said. **Nothing in v1.0.x puts the theory at risk.** A theory that cannot lose is not being tested. + +[NW] 2026-09-04: "this is the opportunity to finally put this theory to the test, beyond just the agentropy proofs." + +## The direction, as set + +[NW] 2026-09-04: the CFO office becomes the first long-running, named agent principal — goals, personality, incentives, the ability to manage its own agents — of the eleven standing offices. Token accounting rolls up to the CFO, "so start there." Its first deliverable is to count every token on every harness, including its own. + +The pieces already on the board (each with its status as of 2026-09-04, from the project record, not from this note): + +| Piece | Where | Status | +|---|---|---| +| Named principal | Matrix agent identity + device (ADR-022) | proven, spine not yet exercised | +| Goals | `office-scorecard/scores.yaml`, CFO KRs | exist, `source: manual` | +| Ledger | RAV-1702 token accounting by office | filed | +| Budget | RAV-1701 quota-aware routing, "an office is a profile with a wallet" | filed | +| Manages its own agents | RAV-1700 capability profiles, `supervise` + `spawn` scoped to the office | filed | +| Incentives | — | **no substrate**; guardrail: KRs must be `computed` from receipts before any office is incentivised | +| Witness | Vór | designed, not provisioned as an independent root (RAV-1084 / SP2; ADR-023 R1 accepted, not complete) | + +## Why this is the test and the demos are not + +The v1.0.x demos give an agent a budget of *bytes it will never need*: mass is minted by a factory, consumed by decryption, and nothing outside the simulation depends on any of it. The CFO direction inverts every one of those properties, and each inversion is a place the theory can fail: + +1. **The budget is real.** If the office's spending power is minted mass — cypher mass in Nate's formulation, or an XMSS/LMS leaf spent per deed, per the 2026-08-14 capture — then a token the office spends on a harness call is a token it no longer has, and the cost is paid in a currency that buys something outside the model. Depletion stops being a metaphor for scarcity and becomes scarcity. [CS: this is the *enacted* side of the enacted-vs-attested distinction — capacity that is the resource, not a claim about it. That distinction is a candidate corollary that has not earned the name; it is used here as a label, not a premise.] + +2. **Consequence is witnessed by someone else.** The demos' "public ledger" is a Python list the operator owns (verification item 10). The office's deeds would be scored **from receipts** — the RAV-1702 ledger and Vór deeds — never self-reported. [NW-guardrail, 2026-09-04] That is Kurt's sixth layer (independent external commitment) made load-bearing, with the honest caveat that Vór is not yet an independent witness (ADR-023) and the anchor bounds alteration, not omission. + +3. **The agent has a reason to game it.** An office rewarded on a KR it can influence, with the power to spawn agents, will optimise the KR. The demos' agents are payloads with no goals; the office is the first agent in the estate with an incentive to make the accounting lie. If `C + S + L = C₀` is still true when the party being measured wants it false, that is evidence of a kind no simulation can produce. + +4. **There is a falsifier.** State it before the run, not after: *if the office's witnessed outcomes and its ledger-measured depletion decouple — spend without deed, deed without spend, or a score that moves while receipts do not — the accounting does not govern the behaviour and the theory has failed the test in this frame.* If they stay coupled under adversarial pressure, the strongest permitted conclusion is still only "useful under the tested frame" (the Canonical Claim Kernel's ceiling), not "law". + +## Discipline this test inherits, verbatim + +- Mímir grammar v0.6.1-exp package: **Experimental; production use not authorized; validation authorization expires 2026-10-24 or on completion of the protocol; D2 narrowed, not closed; G3-9 estimator absent.** Nothing built for the CFO test may be described as production, and nothing may cite a λ measurement as if the estimator existed. +- Vór: **designed, not provisioned** as an independent operational root. Receipts scored by an issuer keeping its own books are attested, not witnessed. +- Incentives: **none until KRs move from `source: manual` to `source: computed`.** An office cannot be paid before it can be counted. +- λ collision: the paper's Appendix C `λ` is a **decay rate** in `w_{t+1} = (1−λ)·w_t + α·r`; the grammar's `λₑ = Qq(Λₑ)` is a **risk-adjusted quantile of reachable useful work**. They are not the same quantity. Any citation of the grammar in this context must say so first. +- Depleted mass is **exhausted capacity, not death**: `λ = 0` establishes no loss of identity, standing, or personhood (Reader's Note; cypher-mass capture §0e(i)). The demos' "dead agent" vocabulary does not travel to a named principal. +- "Agency is scalar" is the axiom; the claim that Agentropy's `λ` *is* that axiom at another altitude is an amplification two reviews flagged as unearned (cypher-mass capture §0f). The test measures a scalar; it does not thereby prove agency is one. + +## Pointers, not designs + +- `~/docs/60-research/research-and-development/Cypher Mass — Enacted Capability and the Attestation Boundary.md` — the 2026-08-14 capture: cypher mass [NW], enacted vs attested [CS → NW-ratified], XMSS/LMS as the nearest primitive, the cube protocol's custody reading, and the attribution collision Nate has said "gets political" and has not yet framed. Its §0 places every claim on the Reader's Note ladder. +- `~/docs/101-vaults/Hrafngrima Manifesto/101 - Reader's Note — Axioms, Theorems, and Projections.md` (H2-FM2) — the altitude ladder. *Derivation moves downward. Evidence and criticism may travel upward. Authority does not.* +- `~/.claude/projects/-Users-nate/memory/09-projects/cfo-is-the-first-standing-office-agent.md` — the 2026-09-04 direction and the incentive guardrail. +- Kurt's closing stack (signed authority → unique capability → typed budget → atomic settlement → local receipt → independent commitment) maps stage for stage to the grammar's v0.6.1 §3.5 control surface — the table is in the dossier §4. Whether that mapping means what it appears to mean is Nate's call. + +One page. No design. The claim this note makes is only that the CFO direction is the first place the theory can lose, and that a test it cannot lose is not a test. diff --git a/docs/reviews/2026-08-22-overmier-v1.0.0/VERIFICATION-NOTES.md b/docs/reviews/2026-08-22-overmier-v1.0.0/VERIFICATION-NOTES.md new file mode 100644 index 0000000..0697475 --- /dev/null +++ b/docs/reviews/2026-08-22-overmier-v1.0.0/VERIFICATION-NOTES.md @@ -0,0 +1,286 @@ +# Verification notes — Overmier open technical review of Agentropy v1.0.0 + +**Status:** working notes. Round 1 (2026-08-22) + round 2 (2026-09-04/05). Not the author response. +**Reviewed artifact:** v1.0.0 — DOI `10.5281/zenodo.20818597` — `main@91df6c0` — tag `v1.0.0`. Identity proven in §0 (round 2). +**Review received:** 2026-08-22, Kurt Overmier, *"Pressure-Testing Agentropy: From Conservation Metaphor to Accountable Agent Infrastructure"*, draft-for-author-review. Recommendation: **major revision**. Package: review draft (11 sections), evidence matrix (23 rows), `2026-08-21-agentropy-targeted-verification.py`. +**Verification performed:** round 1 2026-08-22 against the local canonical checkout; round 2 2026-09-04/05 against the **bytes downloaded from Zenodo** (not the checkout), plus the reviewer's own harness and evidence matrix. Every code-level claim was checked against source; every headline number was reproduced live with the scripts in this directory or the reviewer's harness. +**Environments tested:** +- Round 1: Python 3.14.7 / `cryptography` 46.0.4 (primary); Python 3.10.20 and 3.12.13 / `cryptography` 50.0.0 via `uv` (token-economy population demo only). +- Round 2: Python 3.14.7 / `cryptography` 46.0.4 (all demos, all scripts, scale run); Python 3.10.20 / `cryptography` 50.0.1 and **Python 3.10.20 / `cryptography` 3.4.8 — the reviewer's exact library version** (reviewer's harness only). +- Reviewer: Python 3.10.12 / `cryptography` 3.4.8. + +Purpose of this file: (1) give the author a receipts pile for the response; (2) define the scope of **v1.0.1** (patch: fixes and wording corrections, no new architecture); (3) record what was *not* verified. + +--- + +## 0. Artifact identity (round 2) + +Kurt reviewed only the public deposit. Round 2 analysed the same bytes. + +| Check | Result | +|---|---| +| Zenodo record `20818597` file | `nwalker85/agentropy-v1.0.0.zip`, 146,189 B, record metadata `md5:6a2315a50bbb2d49dd1db9aa8e195785` | +| Downloaded zip md5 | `6a2315a50bbb2d49dd1db9aa8e195785` — matches record metadata and the reviewer's evidence-matrix row 2 | +| Zip top-level directory | `nwalker85-agentropy-91df6c0` — matches the reviewer's stated snapshot | +| `git rev-parse v1.0.0^{commit}` | `91df6c05e275fb8d3cb638f22b8db4092564898e` | +| `git rev-parse main` / `origin/main` / `origin refs/tags/v1.0.0` | all `91df6c05e275fb8d3cb638f22b8db4092564898e` | +| File-by-file sha256, zip vs `git archive v1.0.0` | 25 files each; **all 25 identical** (`diff` empty) | + +Script: `identity_proof.sh` (in this directory). The extracted Zenodo tree is what every round-2 run below executed against. + +--- + +## 1. Claim-by-claim verification + +Legend — **CONFIRMED**: reproduced or read directly in source. **OVERSTATED**: real finding, stronger wording than the code supports. **NUANCE**: accurate, but missing context worth stating. **NOT-REPRODUCED**: attempted here and did not reproduce, or could not be attempted (reason stated). + +### 1.1 Core mechanism + +| # | Reviewer's claim | Verdict | Receipt | Paper text it contradicts | +|---|---|---|---|---| +| 1 | The conservation equation is an accounting identity; `L` is defined so it holds | **CONFIRMED** | `amt_core.py:420-421` — `signal = len(plaintext)`, `loss = layer.mass - signal`; assertion at `:448` | None — `paper/agentropy.md:590` already says "a tautology of symmetric encryption. It holds because subtraction works." Reviewer credits this. | +| 2 | `key_class` is plaintext metadata; environments read it to select a key | **CONFIRMED** | `amt_core.py:121` `key_class: str`; `amt_core.py:405` `env.derive_key_for_class(layer.key_class)` | `paper/cross_org_accountability.md:94` — an alpha environment "cannot even detect the existence of `beta` layers (they are indistinguishable from random bytes)"; `:97` "The environment cannot determine that beta and gamma layers exist." | +| 3 | `interact()` removes **every** matching layer per visit | **CONFIRMED** | `amt_core.py:404-432` loop; no count limit | `paper/token_economy.md:297` "costs exactly 3 layers"; `:303` "An agent with 12 beta layers has exactly 4 LLM calls"; `:103` "an agent with 3 beta layers can make exactly 1 LLM inference call" | +| 4 | "Consumption" is a Python list reassignment; ciphertext bytes remain copyable | **CONFIRMED** | `amt_core.py:435` `agent.layers = surviving_layers` | `paper/token_economy.md:282` "The encrypted form is gone. An agent cannot present the same layer twice" — **false as written** | + +### 1.2 Token economy + +| # | Reviewer's claim | Verdict | Receipt | +|---|---|---|---| +| 5 | Gateway never compares layers removed to `base_cost` | **CONFIRMED** | `amt_token_economy.py:195-207` — `success` iff `layers_stripped > 0 and agent_survived`. `base_cost` appears only in `__repr__` (`:68`, `:242`) and the budget observer (`:290`). | +| 6 | 1-layer agent succeeds against a declared 3-layer price | **CONFIRMED — reproduced ×4** | `verify_02_replay_with_ballast.py`: agent with 1 beta + alpha ballast → `outcome=success`. Reviewer's harness (§1.9): `copied_state_calls[*].success = true, layers_charged = 1` vs `declared_llm_cost = 3`. (Without ballast the agent dies, `outcome=died_paying` — `verify_01`. The demo's budget agent has ballast, so the reviewer's reading of the demo log is right.) | +| 7 | Demo prose contradicts demo log | **CONFIRMED** | `amt_token_economy_demo.py:209-211` prints "couldn't afford GPT-4 (needs 3 beta, had 1)" after the call log records `success`. | +| 8 | Copying an agent allows both copies to pay | **CONFIRMED — reproduced ×4** | `verify_02`: `copy.deepcopy(agent)` → copy A `success`, copy B `success` against the same gateway. Reviewer's harness (§1.9) identical under 3.14/46.0.4, 3.10/50.0.1 and 3.10/3.4.8. | +| 9 | 12 beta layers stripped in one visit (overcharge) | **CONFIRMED — reproduced ×4** | `verify_02`: 12 → 0 beta layers, `outcome=success`. Reviewer's harness: `twelve_beta_call = {layers_charged: 12, success: true}`. | + +### 1.3 Cross-organizational accountability + +| # | Reviewer's claim | Verdict | Receipt | +|---|---|---|---| +| 10 | "Public ledger" is a mutable in-memory list; commitments unsigned, unanchored | **CONFIRMED** | `amt_extensions.py:304-318` `PublicLedger.commitments: list[PublicCommitment]`; `publish()` is `append` (docstring says "Immutable once published" — nothing enforces it). No signing or external anchoring anywhere in the module (grep `sign|anchor` → only the docstring word "anchor trust" at `:309` and `time.time()` timestamps). | +| 11 | Honest-environment assumption collides with "without trust" | **CONFIRMED** (wording) | Paper states the assumption honestly at `paper/agentropy.md §11.1` and `paper/cross_org_accountability.md:430-438` (which even lists "Clone the agent (copy layers before decrypting)" as a malicious-environment move — i.e. the paper already names item 8's attack and scopes it out). The collision is with the cross-org paper's *title* ("…Without Trust"), `:142` "**Non-repudiation**: The operator cannot deny a committed transaction", `:236` "Zero Trust", and `:476` "No trust, no protocol, no consensus mechanism, no identity provider." | +| 11a | Merkle commitments cannot prove the interaction happened, honest reporting, completeness, authorization-at-time, or non-regeneration (review §"A Merkle Root Is a Witness, Not a Judge", five bullets) | **CONFIRMED** (architectural) | Commitment covers hashes of operator-created `LedgerEntry` records (`amt_extensions.py:219` entry ids are `os.urandom`); no signer, no policy state, no completeness proof, no attestation. `:438` in the paper concedes "does not prevent real-time lying". The one thing the root *does* prove — consistency with a root that was independently preserved — the reviewer grants. | + +### 1.4 Physical IoT + +| # | Reviewer's claim | Verdict | Receipt | +|---|---|---|---| +| 12 | Encode/decode round trip is not invertible | **CONFIRMED — reproduced to 3 decimals, ×4** | `verify_01`: 50 Wh → 45.396; 100 MB → 128.000; 500 ops → 453.960; 30 s → 26.984; 50 reads → 43.820. Reviewer's harness identical under all three environments. Causes: GCM overhead (28 B/layer) inflates decoded mass; `to_layer_specs` quantises to 256-B layers and caps payloads at 512 B (`amt_physical_iot.py:228-240`). Contradicts `paper/physical_iot.md:55-58` "This mapping is bidirectional" and `:88` "knows exactly what physical resources remain". | +| 13 | Scale demo builds a fresh topology per agent; no shared depletion | **CONFIRMED** | `amt_physical_iot_demo.py:405` `topo = build_route_topology()` inside the per-agent loop. Contradicts `paper/physical_iot.md:135` "As battery depletes at a location (from previous agents consuming it)". | +| 14 | — (reviewer did not say this) | **NUANCE** | The battery-dependent mass gate *is implemented* (`amt_physical_iot.py:139-164`, recomputed per visit at `:435-437`). The reported run never exercises it across agents. Correct phrasing: "implemented, not exercised in the reported experiment" — not "absent". **Round 2 sharpening (§2.4): it cannot bind in the configured demo at all.** | +| 15 | "Battery budget directly predicts survival distance" printed regardless of result | **CONFIRMED — reproduced ×2** | `amt_physical_iot_demo.py:488-491` is unconditional. Live run (Zenodo bytes): all four battery tiers → 0 survivors, avg 5.0 locations; totals 150 agents / 750 interactions / 32,486 layers / 0 violations — identical to the paper's §5.3 table (the demo is seeded, `rng = random.Random(42)` at `:378`). | + +### 1.5 Marketplace / ecology + +| # | Reviewer's claim | Verdict | Receipt | +|---|---|---|---| +| 16 | Regeneration clock starts at sim-time 0 while `last_tick` defaults to wall-clock, so elapsed clamps to zero and "passive regeneration does not occur during the simulation" | **OVERSTATED** | `amt_marketplace.py:64` `last_tick = field(default_factory=time.time)`; `:72-76` `elapsed = max(0, now - last_tick)` then `last_tick = now`. Only the **first** `tick()` loses its regeneration; it rebases `last_tick` to sim-time and every later tick regenerates normally. `verify_03` (through `MarketplaceTopology.tick`, pool `current=0`, rate 10/s, 0.5-s ticks): 0.0 → 5.0 → 10.0 → 15.0. With `max_steps=30` (`:355`) the simulation regenerates for 29 of 30 ticks. **It is a one-tick offset bug, not absent regeneration.** *Why the reviewer saw zero:* his harness calls `topology.tick(10)` exactly **once** (`2026-08-21-agentropy-targeted-verification.py:79`) and reads `pool.current` — that single tick is the one that is lost. `pool_after_ten_simulated_seconds = 0` reproduces here too (§1.9); a second `tick(10)` would show 100. Factual correction to request. | +| 17 | Nutrient cycler counts the entire budget as cycled while accreting at most three layers | **CONFIRMED** | `amt_marketplace.py:171` `self._total_cycled += budget`; `:282` `for kc_name, payload in new_specs[:3]`. | +| 18 | No system-level equation connects removed mass to factory-created mass; 300 B budget → 356 B (2 classes) / 440 B (5 classes) | **CONFIRMED — reproduced ×3** (round 1 had read it only) | Reviewer's harness (§1.9): `"2": {generated_layers: 2, generated_ciphertext_mass: 356}`, `"5": {generated_layers: 5, generated_ciphertext_mass: 440}` for `nutrient_budget: 300` — identical under all three environments. `NutrientCycler.cycle()` creates layers via `factory.create_layer`, each carrying fresh 28-B GCM overhead; nothing reconciles against what was deposited. | +| 19 | No reproduction; population can only stay level or decline; "carrying capacity" is a tail average; "speciation" is predefined profiles | **CONFIRMED** | `amt_marketplace.py:533-542` `carrying_capacity_estimate` = mean of the last 30 % of alive counts; no agent-creation path in `run_population` (`:389-466`). `amt_marketplace_demo.py:378-412` constructs the two profiles; `:445-449` prints "Alpha-heavy agents thrive at feeding_a … Beta-heavy agents thrive at feeding_b" unconditionally. | +| 19a | Speciation outcome varies run to run (reviewer: 0/0, then 2/1) | **CONFIRMED — reproduced ×3** | Three runs of `amt_marketplace_demo.py` on the Zenodo bytes: alpha-heavy survivors **2 / 0 / 1** (beta-heavy figures in `demo-out/marketplace_run{1,2,3}.txt`); speciation-demo survival 5/50, 4/50, 2/50; scale-run interactions 2,135 / 2,035 / 2,004 (paper: 2,095); recycling rate 76.9 % / 76.7 % / **40.8 %** (paper: 77.3 %); estimated carrying capacity 3 / 2 / 3 (paper: "4-6"). The "thrive" line printed in all three. | + +### 1.6 Ablation + +| # | Reviewer's claim | Verdict | Receipt | +|---|---|---|---| +| 20 | Random condition removes ~1 layer per env key rather than all matching | **CONFIRMED** | `amt_ablation.py:112-113` `n_to_remove = max(1, min(env.hazard_classes, len(agent.layers)))`. The in-code comment *intends* parity with control ("Match control's stripping rate: ~1 layer per env key class"); parity only holds when agents carry ≤1 layer per class. | +| 21 | Random condition invents a 50/50 signal/loss split | **CONFIRMED** | `amt_ablation.py:128-129` `signal = consumed // 2; loss = consumed - signal`. | +| 22 | Immortal condition returns zero signal by construction | **CONFIRMED** | `amt_ablation.py:65-76` hard-codes `total_signal=0`. | +| 23 | Stratification's 3:1 ratio is an input construction | **CONFIRMED** | `amt_ablation.py:279-297` rich = 4 data layers × 3 classes; poor = 4 data alpha + 4 empty beta + 4 empty gamma. The paper says as much (`paper/agentropy.md` §9.4 "matching the 3:1 key-class breadth ratio") but then calls the result "establishes necessity". | +| 23a | Ablation numbers reproduce | **CONFIRMED — reproduced** | `amt_ablation_demo.py` on the Zenodo bytes: 561/2,000/1,300 interactions; 768/256 B → 3.00×, 138/73 B → 1.90×; niche scores 1.00/0.00/0.00; 41,000/0/11,240 B — every cell identical to the paper's §9.4 tables. (Seeded: `random.seed(42)`.) | + +### 1.7 Reproducibility + +| # | Reviewer's claim | Verdict | Receipt | +|---|---|---|---| +| 24 | Results vary across processes under different `PYTHONHASHSEED` | **CONFIRMED — reproduced, root-caused** | Round 2, Zenodo bytes, seeds 0/1/2: total tool calls **2,125 / 2,108 / 2,093**; successful **444 / 453 / 451**; survivors **107 / 98 / 88** (paper: 2,113 / 449 / 91; reviewer: 2,090–2,206 calls, 100–106 survivors). Round-1 figures identical for seed 0. **Root cause:** `amt_extensions.py:841` `Topology.reachable_from` iterates `self.edges[node_id]`, a `set[str]` — neighbour order depends on the string hash seed, and `AgentBehavior.choose_node` (`:739-747`) does a cumulative weighted draw over `scored` **in list order**, so the same `random.uniform` draw selects a different node when the order changes. All `random` calls are seeded (`amt_token_economy_demo.py:380,406`); `os.urandom` payloads do not change lengths. One `sorted()` restores determinism. | +| 25 | Not a Python-version effect (author's check, not reviewer's) | **CONFIRMED** | Same demo under 3.10.20 → 448 / 104; 3.12.13 → 444 / 107; 3.14.7 → 444 / 107 (seed 0). | +| 26 | No test suite, dependency manifest, repeated-trial distributions, or CIs | **CONFIRMED** | Zenodo zip file list (§0): 25 files, no `requirements*`, `pyproject*`, `tests/`, `.github/`. README says `pip install cryptography` unpinned. | +| 26a | 50,000-agent scale run: reviewer 280.77 s, 2,286,167 summed node visits, 0 assertion failures | **CONFIRMED — reproduced** | `PYTHONHASHSEED=0 python3 amt_scale.py --agents 50000 --steps 50 --workers 4` on the Zenodo bytes: 334.31 s, survival 90.0 % (44,975), node visits 718,499 + 671,737 + 473,668 + 421,625 = **2,285,529**, `Violations: 0`. Visit total differs from the reviewer's by 638 (0.03 %) — same `set`-iteration cause as item 24 (the scale harness seeds `random` per agent, `amt_scale.py:262`, but neighbour order is still hash-seed dependent and the reviewer's interpreter differs). Full output: `demo-out/../scale_run.txt`. | +| 26b | Cross-org demo (paper: 200 agents, 457 interactions, 28.5 % validated) | **NUANCE** | Zenodo bytes, default hash seed: 200 agents, **459** interactions, 0 violations, 58 validated (29.0 %) / 142 skipped (71.0 %). Same process-sensitivity as item 24; the paper's integers are one draw. | + +### 1.8 Interpretive claims about the paper + +| # | Reviewer's claim | Verdict | Receipt | +|---|---|---|---| +| 27 | The papers claim a "substrate-independent law of life" / "general theory of life-like dynamics" | **OVERSTATED** | `paper/agentropy.md:604` (Appendix C): *"This paper makes only the weaker, testable claim (a necessary condition for life-*like* dynamics); the stronger claim is argued in companion work."* Appendix C opens (`:596`): *"none of this paper's empirical claims depend on them."* The deposited paper's claim is the necessary-condition claim in `§7.4` and `§13` (and the title). The review's "smaller than a substrate-independent law of life" and its opening "general theory of life-like dynamics claimed by the papers" attribute a deferred claim to v1.0.0. Ask that the hedge be preserved. | +| 28 | The paper claims cryptographic scarcity / no double-spending | **CONFIRMED** | `paper/agentropy.md:250` "Conservation eliminates inflation, double-spending, and unauthorized access because mass is physical"; `:88` "(cannot be inflated, counterfeited, or double-spent)"; `paper/token_economy.md:12` "ensures that mass cannot be created, double-spent, or inflated"; `:280-282` §8.2 "No Double-Spending". `paper/agentropy.md:389` "AMT tokens (layers) cannot be duplicated because they are AES-256-GCM ciphertexts, whereas Petri net tokens are abstract and can be trivially copied" — the exact inversion of item 4. These sentences are wrong as written and need rewriting, not defending. | +| 29 | "Agentropy contains roughly 11,500 lines of Python and paper text" | **CONFIRMED** | `wc -l paper/*.md *.py` = 11,545 (matrix row 3). | +| 30 | "The code ran without modification under Python 3.10.12 with `cryptography` 3.4.8" | **CONFIRMED** (3.4.8 on 3.10.20) | Reviewer's harness ran unmodified under `uv run --python 3.10 --with cryptography==3.4.8` (§1.9, run C). | + +**Score, round 2:** 34 checked items (28 round-1 items + 11a, 19a, 23a, 26a, 26b, 29, 30, minus none). **31 CONFIRMED**, **2 OVERSTATED** (#16, #27), **3 NUANCE** (#14, #26b, and #14's sharpening in §2.4), **0 NOT-REPRODUCED**. Every claim in the review's eleven sections and every row of the evidence matrix (§1.10) now has a verdict and a receipt. + +### 1.9 The reviewer's harness — verbatim results + +`python3 2026-08-21-agentropy-targeted-verification.py ` — no source modified. Output was **byte-identical** (same JSON, `sort_keys=True`) across all three runs: + +| Run | Interpreter | `cryptography` | Exit | +|---|---|---|---| +| A | Python 3.14.7 (Homebrew) | 46.0.4 | 0 | +| B | Python 3.10.20 (`uv`) | 50.0.1 | 0 | +| C | Python 3.10.20 (`uv`) | **3.4.8** (reviewer's version) | 0 | + +```json +{ + "copied_state_calls": [ + {"layers_charged": 1, "success": true}, + {"layers_charged": 1, "success": true} + ], + "declared_llm_cost": 3, + "nutrient_cycle_by_key_class_count": { + "2": {"generated_ciphertext_mass": 356, "generated_layers": 2, "nutrient_budget": 300}, + "5": {"generated_ciphertext_mass": 440, "generated_layers": 5, "nutrient_budget": 300} + }, + "physical_budget_interpreted": {"bandwidth": 128.0, "battery": 45.396, "cpu": 26.984, "sensor": 43.82, "storage": 453.96}, + "physical_budget_requested": {"bandwidth_mb": 100, "battery_wh": 50, "cpu_seconds": 30, "sensor_reads": 50, "storage_ops": 500}, + "pool_after_ten_simulated_seconds": 0, + "twelve_beta_call": {"layers_charged": 12, "success": true} +} +``` + +Every figure in the review's tables that the harness produces is reproduced exactly. The one interpretive gap is `pool_after_ten_simulated_seconds: 0`, which is the first-tick loss of item 16, not "no regeneration". + +Existing scripts, round 2 (Python 3.14.7 / 46.0.4, worktree at `8fca48b`): `verify_01` → died_paying / died_paying (no ballast), 12→0, regen 0.0/5.0/10.0/15.0, IoT 45.396/128.000/453.960/26.984/43.820. `verify_02` → success / success, 12→0 success. `verify_03` → 0.0/5.0/10.0/15.0. All exit 0. Identical to round 1. + +### 1.10 Evidence-matrix rows → verdicts + +The reviewer's internal matrix has 23 rows. Mapping to the items above (all matrix "Verified"/"Reproduced" statuses hold unless noted): + +| Matrix row | Item(s) | Verdict | +|---|---|---| +| Artifact citable and versioned | §0 | CONFIRMED | +| Reviewed archive matches deposit (md5 `6a2315…`) | §0 | CONFIRMED — and extended: file-by-file sha256 identity with tag `v1.0.0` | +| ~11,500 lines | 29 | CONFIRMED | +| All advertised demos runnable | 15, 19a, 23a, 26a, 26b | CONFIRMED (5 demos + scale, exit 0) | +| Invariant survives 50,000 agents | 26a | CONFIRMED | +| Equation is definitional in v1 | 1 | CONFIRMED | +| AES-GCM does not make ciphertext non-copyable | 4 | CONFIRMED | +| Copied-state double spending | 8 | CONFIRMED | +| Declared prices not enforced | 5, 6, 7 | CONFIRMED | +| A visit removes all matching layers | 3, 9 | CONFIRMED | +| Public ledger mutable, in-memory | 10 | CONFIRMED | +| Merkle commitments don't prove authorization/execution/completeness | 11a | CONFIRMED | +| Key-class existence not hidden | 2 | CONFIRMED | +| Physical encoding not invertible | 12 | CONFIRMED | +| Physical scale demo has no shared depletion | 13, 14 | CONFIRMED + NUANCE (gate implemented; cannot bind — §2.4) | +| No battery-dependent survival distance shown | 15 | CONFIRMED | +| Marketplace regeneration does not occur | 16 | **OVERSTATED** — one-tick offset; 29 of 30 ticks regenerate | +| Nutrient cycling not globally conservative (300→356/440) | 17, 18 | CONFIRMED — reproduced | +| No reproduction or speciation | 19 | CONFIRMED | +| Speciation narrative unconditional | 19, 19a | CONFIRMED — reproduced (2/0/1 across three runs) | +| Carrying capacity is a tail average | 19 | CONFIRMED | +| Ablation does not hold depletion constant | 20–23 | CONFIRMED | +| Results process-sensitive | 24, 25 | CONFIRMED — root-caused | +| No test suite / manifest | 26 | CONFIRMED | + +Of the matrix's six "Interpretive Judgments Requiring Author Review", none is a factual claim; they are handed to the author as questions in the dossier (`docs/review/OVERMIER-RESPONSE-DOSSIER.md` §3). + +--- + +## 2. Findings beyond the review + +1. **Nondeterminism root cause** (item 24): `set` iteration in `Topology.reachable_from`, `amt_extensions.py:841`, combined with the order-dependent cumulative draw in `choose_node` (`:739-747`). Also `amt_core.py:267` returns `set(self.secrets.keys())` for hazard classes — harmless today (only used for counts/membership) but worth sorting for the same reason. +2. **`ResourcePool.regenerate(current_time)` uses `current_time or time.time()`** (`amt_marketplace.py:72`): a sim-time of exactly `0.0` silently falls back to wall-clock. Latent; fix alongside item 16. +3. **The "immortal" ablation condition is the copy-attack regime.** The proper control for "depletion" is *decrypt without destroy* — extract signal, keep the layer. That is exactly what a cloned agent gets (item 8). Reframed this way, the reviewer's double-spend finding and the ablation are the same experiment: without enforced destruction, signal is unbounded and the stratification/selectivity results dissolve. This is a v2 design point, not a v1.0.1 fix, but it is the honest way to state what the ablation *does* show. +4. **The IoT mass gate cannot bind in the configured demo** (sharpens item 14; the reviewer did not say this). The tightest gate is 500 KB (`amt_physical_iot.py:161-162`, `500 * 1024 = 512,000 B`). The heaviest agent `demo_6_scale` can build (60 Wh / 80 MB / 300 ops / 25 s / 40 reads, the top of every `rng.uniform` range at `amt_physical_iot_demo.py:391-395`) has mass **94,461 B**; the paper's §5.1 agent is 106,603 B. Both are under the floor by ~5×, so "Dynamic load-shedding … Heavier agents get blocked" (`paper/agentropy.md` §5.3; `paper/physical_iot.md:135`) is not exercised by *any* agent in *either* experiment, shared topology or not. Script: `iot_gate_check.py`. Fixing item 13 (shared topology) makes the battery deplete across agents but still cannot make the gate bind; that needs either heavier agents or lower thresholds, which is an experiment-design change (v2), not a patch. +5. **The paper already names the clone attack and scopes it out** (`paper/cross_org_accountability.md:435` "Clone the agent (copy layers before decrypting)") — under the honest-environment assumption. The token paper then claims the opposite (`token_economy.md:282`). The two papers disagree with each other; the cross-org one is right. +6. **Reviewer's regeneration test is single-tick** (item 16 receipt). Worth stating in the response because it explains, without blame, why a careful reviewer concluded "does not occur". + +--- + +## 3. Scope for v1.0.1 (patch) + +Semantic-versioning patch: **fixes and corrections only.** No uniqueness, settlement, signing, or external anchoring — those change the threat model and belong to v2 with their own design record. A new Zenodo version deposit will mint a version DOI under the existing concept DOI; the v1.0.0 DOI stays immutable and the review keeps pointing at it. + +### 3.1 Code fixes (status as shipped on `release/v1.0.1`, 2026-09-05) + +| Priority | Fix | Files | Closes item(s) | Reviewer rec. | Shipped | +|---|---|---|---|---|---| +| P0 | Determinism: `sorted()` neighbour iteration | `amt_extensions.py` `reachable_from` | 24 | #8, #10 | **yes** — `e925a10`. `amt_core.py:267` `key_classes` left as a `set` (membership/count only; repr order harmless). Token demo identical under hash seeds 0/1/2; 2,000-agent scale run identical. | +| P0 | Gateway price enforcement: `interact(max_layers=)`, `Node.process(max_layers=)`; refuse `insufficient_funds` before consumption; strip exactly `base_cost` (asserted) | `amt_core.py`, `amt_extensions.py`, `amt_token_economy.py` | 3, 5, 6, 7, 9 | #4 | **yes** — `023e368`. Kurt's harness on the patched tree: copied 1-layer calls `success=false, layers_charged=0`; 12-layer agent `layers_charged=3`. Reference numbers change: survival 45.5 % → 97.5 %, ratio 2.1× → 9.8× (errata E14). | +| P1 | Marketplace regeneration clock: `last_tick=None` until `add_node` sets sim-time; `regenerate(0.0)` valid | `amt_marketplace.py` | 16, §2.2 | — | **yes** — `689dc0f`. `verify_03`: 5/10/15/20. Kurt's `tick(10)` → 100. | +| P1 | Nutrient accounting: count only embodied payload; keep the rest; track minted overhead; report system-level mass | `amt_marketplace.py`, `amt_marketplace_demo.py` | 17, 18 (measured, not enforced) | #7 (partial) | **yes** — `689dc0f`. Recycling 77.3 % → 29.3 %; minted 16,871 B = 14,267 payload + 2,604 overhead. | +| P1 | "KEY INSIGHT" prints derived from measured results | the three `*_demo.py` | 7, 15, 19 | #9 | **yes** — in `023e368`, `689dc0f`, `6008a6c`. | +| P1 | IoT scale demo: one shared topology; requested-vs-decoded table; gate-blocked count; gate-cannot-bind statement | `amt_physical_iot_demo.py` | 12, 13, 14, §2.4 | — | **yes** — `6008a6c`. Blocked 0/750; heaviest 90,716 B vs floor 512,000 B; totals unchanged (750 / 32,486 / 0). | +| P2 | Ablation: honest signal/loss for RANDOM; new RANDOM-MATCHED (count parity); interpretation from measured rows | `amt_ablation.py`, `amt_ablation_demo.py` | 20, 21 | #8 | **yes** — `492b5fc`. RANDOM stratification 1.90× → **3.12×** (the "degradation" was the `// 2` split); RANDOM-MATCHED 2.87×, niche 1.00, per-class 0 %. Paper §9 rewritten accordingly (errata E12). | +| P2 | `requirements*.txt`, `pyproject.toml`, `tests/` (31), GitHub Actions CI incl. `cryptography==3.4.8` job | repo root | 26 | #10 | **yes** — `09ca7b7`. 31 passed on 3.14/46.0.4 and 3.10/3.4.8. | +| P2 | Marketplace demo seeds its behaviour RNG (`AMT_SEED`) | `amt_marketplace_demo.py` | 19a | #8 | **yes** — `e36aa8f`. Byte-identical across hash seeds except timing lines. | +| P2 — deferred | Multi-seed runner: N seeds → mean ± CI | — | 24, 26 | #8 | **no** — errata records observed ranges instead. | +| — not a fix | Stratification fixture (item 23): 3:1 is by construction; wording only | — | 23 | #8, #9 | wording shipped in `8fca…`→`8abdefa` (errata E12). | + +### 3.2 Paper wording corrections (same release) + +| File:line | Current | Problem | Direction | +|---|---|---|---| +| `paper/token_economy.md:282` §8.2 | "The encrypted form is gone. An agent cannot present the same layer twice" | False — item 8 | State: destruction is local to the holder's process; uniqueness across copies requires an authority outside the scheme (v2). | +| `paper/token_economy.md:12` abstract; `:50` | "cannot be created, double-spent, or inflated"; "Trust is unnecessary" | Items 8, 28 | Narrow to "cannot be created outside the factory"; drop "double-spent". | +| `paper/agentropy.md:250`; `:88` | "eliminates inflation, double-spending, and unauthorized access because mass is physical"; "cannot be inflated, counterfeited, or double-spent" | False — items 8, 28 | Narrow to what holds: no mass creation outside the factory; depletion is irreversible *within an honest process*. | +| `paper/agentropy.md:389` §10.1 | "AMT tokens (layers) cannot be duplicated because they are AES-256-GCM ciphertexts" | Inverts item 4 | Ciphertexts are bytes and copy like any bytes; what AES-GCM gives is confidentiality + integrity, not uniqueness. | +| `paper/cross_org_accountability.md:94, 97` | "cannot even detect the existence of beta layers" | False — item 2 | Either encrypt the class tag (v2) or restate: class *presence* is visible, class *content* is not. | +| `paper/cross_org_accountability.md` title, `:142`, `:236`, `:476` | "Without Trust"; "Non-repudiation"; "Zero Trust"; "No trust, no protocol…" | Collides with `:430-438` — item 11 | "Without a trusted intermediary *for the arithmetic*" — each org verifies its own consumption; cross-party non-repudiation is out of scope for the reference implementation. | +| `paper/token_economy.md:103, 297, 303` | "exactly 1 LLM inference call"; "costs exactly 3 layers"; "exactly 4 LLM calls" | False until P0 price enforcement lands — items 3, 5 | Becomes true after the fix; keep, cite the enforcing code. | +| `paper/token_economy.md:224` §7.2 | "partially pays for GPT-4 (1 beta layer stripped, but needs 3 — partial payment, tool still fires)" | The paper *documents* item 6 as intended behaviour | After the fix: rejected as `insufficient_funds`; update the scenario and the demo. | +| `paper/physical_iot.md:55-58, 88` | "This mapping is bidirectional"; "knows exactly what physical resources remain" | Item 12 | "Approximately, within the quantisation and overhead error reported by the demo" + the measured table. | +| `paper/physical_iot.md:135`; `paper/agentropy.md` §5.3 "Dynamic load-shedding" | "from previous agents consuming it"; "Heavier agents get blocked" | Items 13, §2.4 | Shared topology makes the first true; the second is not exercised by any agent in the reported experiments — say so. | +| `paper/agentropy.md §8.1` table; `§4.4`, `§6.4`; `token_economy.md §7.3`, App. B; `marketplace.md §6` | Single-run integers (2,113; 91; 2,095; 77.3 %…) | Items 24, 19a, 26b | Label as one reference run; add the observed cross-process range from these notes. | +| `paper/agentropy.md §9`, §9.6, §13 | "establishes necessity" | Items 20–23 | "consistent with"; keep "necessary condition" as the hypothesis, not the result, unless the repaired ablation supports it. | +| `paper/agentropy.md §6`, `marketplace.md` | "carrying capacity", "speciation", "boom/bust", "Gause's competitive exclusion" | Item 19 | Use "tail-average population", "niche-differential survival", "monotonic decline" until reproduction exists. | +| `paper/agentropy.md` Appendix C | — | — | **Unchanged, verbatim.** It is the hedge the review missed (item 27). | + +### 3.3 Explicitly out of scope for v1.0.1 + +- Uniqueness / anti-replay (signed capabilities, settlement) — reviewer recs #3, #6. Threat-model change → v2 + design record. +- Hiding key classes cryptographically — rec #5. v2. +- Global mass equation across factory/accretion/pools — rec #7. v2 (v1.0.1 *measures* and reports created overhead without *enforcing* a global equation). +- Reproduction in the ecology model — `§12.1` future work. v2. +- Renaming the central result — rec #1. Author's call; wording, not code. +- Making the IoT mass gate bind (heavier agents or lower thresholds) — experiment redesign, v2. + +--- + +## 4. Inputs for the author response (facts only; prose is the author's) + +**Concede outright (all verified):** items 2, 3, 4, 5–9, 10, 11, 11a, 12, 13, 15, 17, 18, 19, 19a, 20–24, 26, 26a, 28, 29, 30. Accept "major revision". + +**Request factual correction:** +- Item 16 — regeneration is a one-tick offset, not absent; 29 of 30 ticks regenerate; the harness's single `tick(10)` is the tick that is lost. Receipt: `verify_03_regen_topology.py`; reviewer's own harness line 79. +- Item 27 — v1.0.0 explicitly makes only the necessary-condition claim and defers the definition-of-life claim to companion work (`paper/agentropy.md` Appendix C, `:596`, `:604`). Preserve the hedge. + +**Add nuance:** item 14 / §2.4 — the battery gate mechanism exists; the reported run doesn't exercise it, and *cannot*, because the gate floor is ~5× the heaviest agent. Item 26b — cross-org integers are one draw too. + +**Offer as author's own findings:** the `set`-iteration root cause (item 24) and the Python-version check (item 25) — the reviewer reported the symptom; the cause is now known and the fix is one line. §2.4 (gate cannot bind) and §2.5 (the two papers disagree with each other about cloning) — the reviewer did not find these; the author reports them himself. + +**Post-v1 work, to be attributed separately (per the reviewer's request):** see the dossier §4. None of it is in v1.0.0 and none of it rescues v1.0.0's claims. + +--- + +## 5. Verified in round 2 (previously open) + +- Reviewer's attachments obtained and run against the Zenodo bytes under three environments including his exact `cryptography` 3.4.8 — §1.9. +- Nutrient test figures (300 B → 356 B two-class; 440 B five-class) reproduced ×3 — item 18. +- Marketplace speciation run-to-run variance re-run ×3 — item 19a. +- 50,000-agent scale run re-run (334.31 s; 2,285,529 node visits; 0 violations) — item 26a. +- `paper/marketplace.md` and `paper/physical_iot.md` read end-to-end; additional wording the fixes invalidate is in §3.2. +- Zenodo ↔ tag identity — §0. + +**Still open / not attempted:** none of the review's claims. The reviewer's exact interpreter build (3.10.12) was not installed; 3.10.20 with his `cryptography` 3.4.8 was used instead (§1.9 run C) and produced identical output. + +## 6. Reproduction commands + +```bash +# from the worktree root +bash docs/reviews/2026-08-22-overmier-v1.0.0/identity_proof.sh # Zenodo <-> tag identity (downloads the zip) +python3 docs/reviews/2026-08-22-overmier-v1.0.0/verify_01_replay_regen_iot.py +python3 docs/reviews/2026-08-22-overmier-v1.0.0/verify_02_replay_with_ballast.py +python3 docs/reviews/2026-08-22-overmier-v1.0.0/verify_03_regen_topology.py +python3 docs/reviews/2026-08-22-overmier-v1.0.0/iot_gate_check.py +# the reviewer's harness, against an extracted copy of the Zenodo zip (path from identity_proof.sh) +python3 /path/to/2026-08-21-agentropy-targeted-verification.py /path/to/nwalker85-agentropy-91df6c0 +uv run --python 3.10 --with 'cryptography==3.4.8' python /path/to/2026-08-21-agentropy-targeted-verification.py /path/to/nwalker85-agentropy-91df6c0 +for s in 0 1 2; do PYTHONHASHSEED=$s python3 amt_token_economy_demo.py | grep -E 'Total tool calls|Successful calls: |Agents surviving'; done +for i in 1 2 3; do python3 amt_marketplace_demo.py | grep -A4 'SPECIATION RESULTS' | grep -E 'Alpha-heavy|Beta-heavy'; done +PYTHONHASHSEED=0 python3 amt_scale.py --agents 50000 --steps 50 --workers 4 +``` + +Round-2 raw outputs (demo logs, harness JSON ×3, scale log) are kept in the session scratchpad and summarised above; the scripts regenerate them. diff --git a/docs/reviews/2026-08-22-overmier-v1.0.0/identity_proof.sh b/docs/reviews/2026-08-22-overmier-v1.0.0/identity_proof.sh new file mode 100644 index 0000000..9c5c04b --- /dev/null +++ b/docs/reviews/2026-08-22-overmier-v1.0.0/identity_proof.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# Prove: Zenodo record 20818597 zip == git tag v1.0.0 == main@91df6c0, file by file. +# Usage: bash identity_proof.sh [WORKDIR] (WORKDIR defaults to a temp dir; the zip is cached there) +set -u +WORK="${1:-$(mktemp -d)}" +REPO="$(cd "$(dirname "$0")/../../.." && pwd)" +ZIP_URL="https://zenodo.org/api/records/20818597/files/nwalker85/agentropy-v1.0.0.zip/content" +RECORD_MD5="6a2315a50bbb2d49dd1db9aa8e195785" # from https://zenodo.org/api/records/20818597 -> files[0].checksum + +mkdir -p "$WORK" +cd "$WORK" || exit 1 +if [ ! -s agentropy-v1.0.0.zip ]; then + curl -sSL -o agentropy-v1.0.0.zip "$ZIP_URL" || exit 1 +fi +ls -l agentropy-v1.0.0.zip +if command -v md5 >/dev/null 2>&1; then GOT=$(md5 -q agentropy-v1.0.0.zip); else GOT=$(md5sum agentropy-v1.0.0.zip | cut -d' ' -f1); fi +echo "zenodo md5 (record metadata): $RECORD_MD5" +echo "downloaded md5: $GOT" +rm -rf zenodo-extract tag-extract +mkdir -p zenodo-extract tag-extract +(cd zenodo-extract && unzip -q ../agentropy-v1.0.0.zip) +TOP=$(ls zenodo-extract) +echo "zip top-level dir: $TOP" +cd "$REPO" || exit 1 +echo "tag v1.0.0 -> $(git rev-parse 'v1.0.0^{commit}')" +echo "main -> $(git rev-parse main)" +git archive v1.0.0 | tar -x -C "$WORK/tag-extract" +cd "$WORK" || exit 1 +(cd "zenodo-extract/$TOP" && find . -type f | sort | xargs shasum -a 256) > zenodo.sha256 +(cd tag-extract && find . -type f | sort | xargs shasum -a 256) > tag.sha256 +echo "files in zenodo zip: $(wc -l < zenodo.sha256 | tr -d ' ') files in git archive v1.0.0: $(wc -l < tag.sha256 | tr -d ' ')" +if diff zenodo.sha256 tag.sha256 > /dev/null; then + echo "IDENTITY: PROVEN — every file in the Zenodo zip matches git archive v1.0.0 by sha256" +else + echo "IDENTITY: DIFFERS" + diff zenodo.sha256 tag.sha256 +fi +echo "extracted Zenodo tree: $WORK/zenodo-extract/$TOP" diff --git a/docs/reviews/2026-08-22-overmier-v1.0.0/iot_gate_check.py b/docs/reviews/2026-08-22-overmier-v1.0.0/iot_gate_check.py new file mode 100644 index 0000000..bc23ae6 --- /dev/null +++ b/docs/reviews/2026-08-22-overmier-v1.0.0/iot_gate_check.py @@ -0,0 +1,33 @@ +"""Can the IoT mass gate ever bind in the configured demo? + +Computes the heaviest agent ``demo_6_scale`` can build (top of every draw range at +``amt_physical_iot_demo.py:391-395``) and compares it with the tightest gate +(``amt_physical_iot.py:161-162``, ``500 * 1024`` bytes). Run from anywhere; by +default it imports the repo this file lives in. Pass a directory as ``argv[1]`` to +test another tree (e.g. an extracted Zenodo archive). +""" +import os +import pathlib +import sys + +ART = sys.argv[1] if len(sys.argv) > 1 else str(pathlib.Path(__file__).resolve().parents[3]) +sys.path.insert(0, ART) + +from amt_core import AgentFactory # noqa: E402 +from amt_physical_iot import ResourceBudget # noqa: E402 + +secrets = {k: os.urandom(32) for k in ["alpha", "beta", "gamma", "delta", "epsilon"]} +f = AgentFactory(secrets) + +# demo_6 draws: battery U(5,60), bandwidth U(1,80), storage U(10,300), cpu U(1,25), sensors randint(2,40) +b = ResourceBudget(battery_wh=60.0, bandwidth_mb=80.0, storage_ops=300.0, cpu_seconds=25.0, sensor_reads=40) +a = f.build_agent(b.to_layer_specs()) +print("tree:", ART) +print("max-budget demo agent mass (bytes):", a.mass) +print("gate floor at <20% battery (bytes):", 500 * 1024) +print("gate can bind in demo_6:", a.mass > 500 * 1024) + +# the paper's §5.1 agent +b2 = ResourceBudget(battery_wh=50, bandwidth_mb=100, storage_ops=500, cpu_seconds=30, sensor_reads=50) +a2 = f.build_agent(b2.to_layer_specs()) +print("paper §5.1 agent mass (bytes):", a2.mass, "layers:", a2.layer_count) diff --git a/docs/reviews/2026-08-22-overmier-v1.0.0/verify_01_replay_regen_iot.py b/docs/reviews/2026-08-22-overmier-v1.0.0/verify_01_replay_regen_iot.py new file mode 100644 index 0000000..916a8ee --- /dev/null +++ b/docs/reviews/2026-08-22-overmier-v1.0.0/verify_01_replay_regen_iot.py @@ -0,0 +1,38 @@ +import os, sys, copy +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "..")) +from amt_core import AgentFactory +from amt_token_economy import ToolGateway, STANDARD_TOOL_CLASSES +from amt_marketplace import ResourcePool +from amt_physical_iot import ResourceBudget, RESOURCE_MAP + +secrets = {k: os.urandom(32) for k in ["alpha","beta","gamma","delta","epsilon"]} +factory = AgentFactory(secrets) + +print("== 1. COPY-REPLAY against a 3-layer price") +llm = STANDARD_TOOL_CLASSES["llm_inference"] +gw = ToolGateway(gateway_id="gw1", name="gpt4", tool_class=llm, tool_name="gpt-4") +gw.initialize(secrets) +a = factory.build_agent([("beta", b"x"*32)]) +b = copy.deepcopy(a) +r1 = gw.process_tool_call(a); r2 = gw.process_tool_call(b) +print(f" declared base_cost={llm.base_cost} agent had 1 beta layer") +print(f" copy A -> outcome={r1['outcome']} success={r1['success']}") +print(f" copy B -> outcome={r2['outcome']} success={r2['success']}") + +print("== 1b. OVERCHARGE: 12 beta layers, one visit") +c = factory.build_agent([("beta", b"x"*32)]*12) +r3 = gw.process_tool_call(c) +print(f" layers before=12 after={c.layer_count} outcome={r3['outcome']}") + +print("== 2. REGEN CLOCK (sim-time ticks vs wall-clock last_tick default)") +pool = ResourcePool(capacity=100.0, current=0.0, regeneration_rate=10.0) +for t in [0.5, 1.0, 1.5, 2.0]: + pool.regenerate(t) + print(f" tick sim_time={t}: current={pool.current}") + +print("== 3. IoT ROUND TRIP") +bud = ResourceBudget(battery_wh=50, bandwidth_mb=100, storage_ops=500, cpu_seconds=30, sensor_reads=50) +ag = factory.build_agent(bud.to_layer_specs()) +back = ResourceBudget.interpret_agent_mass(ag) +for k, v in back.items(): + print(f" {k:10s} requested={getattr(bud, {'battery':'battery_wh','bandwidth':'bandwidth_mb','storage':'storage_ops','cpu':'cpu_seconds','sensor':'sensor_reads'}[k])} decoded={v:.3f}") diff --git a/docs/reviews/2026-08-22-overmier-v1.0.0/verify_02_replay_with_ballast.py b/docs/reviews/2026-08-22-overmier-v1.0.0/verify_02_replay_with_ballast.py new file mode 100644 index 0000000..ac87d1f --- /dev/null +++ b/docs/reviews/2026-08-22-overmier-v1.0.0/verify_02_replay_with_ballast.py @@ -0,0 +1,25 @@ +import os, sys, copy +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "..")) +from amt_core import AgentFactory +from amt_token_economy import ToolGateway, STANDARD_TOOL_CLASSES +from amt_marketplace import ResourcePool, MarketplaceNode, MarketplaceTopology +secrets = {k: os.urandom(32) for k in ["alpha","beta","gamma","delta","epsilon"]} +factory = AgentFactory(secrets) +llm = STANDARD_TOOL_CLASSES["llm_inference"] +gw = ToolGateway(gateway_id="gw1", name="gpt4", tool_class=llm, tool_name="gpt-4"); gw.initialize(secrets) + +print("== 1. COPY-REPLAY (1 beta + alpha ballast) vs base_cost=3") +a = factory.build_agent([("beta", b"x"*32), ("alpha", b"y"*64)]) +b = copy.deepcopy(a) +for tag, ag in (("copy A", a), ("copy B", b)): + r = gw.process_tool_call(ag) + print(f" {tag}: outcome={r['outcome']} success={r['success']} layers_charged={r['layers_charged']}") + +print("== 1b. OVERCHARGE: 12 beta + ballast, one visit") +c = factory.build_agent([("beta", b"x"*32)]*12 + [("alpha", b"y"*64)]) +r = gw.process_tool_call(c) +print(f" beta layers before=12 after={c.mass_profile().get('beta',0)//1 and c.layer_count-1} outcome={r['outcome']} success={r['success']}") + +print("== 2. REGEN via MarketplaceTopology.tick()") +import inspect +sig = inspect.signature(MarketplaceNode.__init__); print(" MarketplaceNode args:", list(sig.parameters)[1:]) diff --git a/docs/reviews/2026-08-22-overmier-v1.0.0/verify_03_regen_topology.py b/docs/reviews/2026-08-22-overmier-v1.0.0/verify_03_regen_topology.py new file mode 100644 index 0000000..3798de1 --- /dev/null +++ b/docs/reviews/2026-08-22-overmier-v1.0.0/verify_03_regen_topology.py @@ -0,0 +1,10 @@ +import os, sys +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "..")) +from amt_marketplace import ResourcePool, MarketplaceNode, MarketplaceTopology, NutrientCycler +secrets = {"alpha": os.urandom(32)} +pool = ResourcePool(capacity=100.0, current=0.0, regeneration_rate=10.0) +node = MarketplaceNode(node_id="n1", name="n1", key_secrets=secrets, resource_pool=pool, nutrient_cycler=NutrientCycler()) +topo = MarketplaceTopology(); topo.add_node(node) +for i in range(4): + topo.tick(0.5) + print(f" tick {i+1} (sim_time={topo._sim_time}): pool.current={pool.current}") diff --git a/paper/ERRATA-v1.0.1.md b/paper/ERRATA-v1.0.1.md new file mode 100644 index 0000000..7a7ea68 --- /dev/null +++ b/paper/ERRATA-v1.0.1.md @@ -0,0 +1,61 @@ +# Errata and changes — Agentropy v1.0.1 + +**Applies to:** Agentropy v1.0.0, DOI [10.5281/zenodo.20818597](https://doi.org/10.5281/zenodo.20818597), `main@91df6c0`, tag `v1.0.0`. +**Prompted by:** Kurt Overmier, *"Pressure-Testing Agentropy: From Conservation Metaphor to Accountable Agent Infrastructure"*, open technical review, draft for author review received 2026-08-22 ("Overmier 2026"). Verification record, item numbers and receipts: [`docs/reviews/2026-08-22-overmier-v1.0.0/VERIFICATION-NOTES.md`](../docs/reviews/2026-08-22-overmier-v1.0.0/VERIFICATION-NOTES.md). +**Scope:** patch release. Code fixes for confirmed bugs; wording corrections where the papers claimed more than the code does; no new architecture. The v1.0.0 text stays deposited and citable; corrections are marked inline as *v1.0.1 erratum* / *v1.0.1 wording* so a reader can see what changed and why. Appendix C of the main paper is unchanged. + +## 1. Corrections to claims (the papers overstated the code) + +| # | Where (v1.0.0) | Claimed | Correct | Verification item | +|---|---|---|---|---| +| E1 | `agentropy.md` §4.3, §7.2, §10.1; `token_economy.md` abstract, §8.2 | Layers "cannot be … double-spent"; "the encrypted form is gone"; ciphertexts "cannot be duplicated because they are AES-256-GCM ciphertexts" | Consumption is a list reassignment inside one process. Ciphertext is copyable bytes; a copied agent pays twice. AES-GCM gives confidentiality and integrity, not uniqueness. `cross_org_accountability.md` §9 already listed cloning as an attack; `token_economy.md` §8.2 contradicted it. | 4, 8, 28 | +| E2 | `token_economy.md` §3.2, §4.1, §8.4, §8.5; `agentropy.md` §4.3 | "costs exactly 3 layers"; "12 beta layers … exactly 4 LLM calls"; the gateway "cannot cheat" | v1.0.0's gateway stripped every layer of its class and never consulted `base_cost`; success was "≥1 layer stripped and alive". One layer bought a "successful" three-layer call; twelve layers bought one call. **Fixed in code** (F2); the sentences are true of v1.0.1. | 3, 5, 6, 7, 9 | +| E3 | `token_economy.md` §7.2 | Budget agent "partially pays for GPT-4 (… tool still fires)" | The paper documented the bug as intended behaviour. v1.0.1 refuses the call before consumption. Scenario numbers updated. | 6, 7 | +| E4 | `cross_org_accountability.md` §2 | An alpha environment "cannot even detect the existence of beta layers" | `Layer.key_class` is plaintext metadata read by every environment to select a key. Class *presence and count* are visible; *contents* are not. | 2 | +| E5 | `cross_org_accountability.md` title, §3, §5, §10; `agentropy.md` §3.3, §7.2, §13 | "Without Trust"; "Non-repudiation"; "Zero Trust"; "No trust, no protocol, no consensus mechanism, no identity provider" | The honest-environment assumption was stated (§9 / §11.1) but the headline sentences claimed past it. The reference "public ledger" is an unsigned, unanchored in-memory list; the construction shows self-consistency, not non-repudiation, and cannot prove an interaction happened, was reported honestly, or that nothing was omitted. Title amended to "…Without a Trusted Intermediary for the Arithmetic…". | 10, 11, 11a | +| E6 | `physical_iot.md` §2.1, §2.3 | The mapping "is bidirectional"; an observer "knows exactly what physical resources remain" | Encode/decode is not invertible: 50 Wh → 45.396; 100 MB → 128; 500 ops → 453.96; 30 s → 26.984; 50 reads → 43.82 (−9 % to +28 %). Causes: 28-B GCM overhead per layer, 256-B layer quantisation, 512-B payload cap. Table added; demo prints it. | 12 | +| E7 | `physical_iot.md` §3.3; `agentropy.md` §5.3 | "As battery depletes … (from previous agents consuming it), the mass gate tightens. Heavier agents get blocked." | Two problems. (a) The scale demo rebuilt the topology per agent, so no battery was ever shared — **fixed** (F5). (b) Still true: the tightest gate is 512,000 B and the heaviest agent either experiment can build is ~107 KB, so the gate cannot bind. Blocked count is 0 of 750 visits. Load-shedding is implemented and not exercised. | 13, 14, §2.4 | +| E8 | `physical_iot.md` §5.3; demo | "Battery budget directly predicts survival distance" | Printed unconditionally; the run showed 5.0 locations in every battery tier and 0 survivors. Demo now derives the line from the measured bins. | 15 | +| E9 | `marketplace.md` §2.2 | Pools "regenerate passively over time" (implied: from the first tick) | One-tick offset: `last_tick` defaulted to wall-clock, so the first simulation tick's regeneration was lost; 29 of 30 ticks regenerated. The reviewer's single-tick probe read zero and the review says regeneration "does not occur" — **that is the one factual overstatement in the review**. **Fixed** (F3). | 16 (OVERSTATED) | +| E10 | `marketplace.md` §3.2, §6.3; `agentropy.md` §6.3 | "No mass is created from nothing"; "77.3 % of dead agent mass is recycled" | The cycler counted its whole budget as cycled while the node accreted at most three layers; every minted layer carries 28 B of fresh overhead no nutrient supplied (300 B → 356 B / 440 B). No system-level mass equation exists. **Accounting fixed** (F4): recycling is 29.3 %; minted overhead is now reported, not enforced. | 17, 18 | +| E11 | `marketplace.md` §5, §7, §10; `agentropy.md` §6.3, §7.2, §13 | "Carrying capacity", "boom/bust", "speciation", "Gause's competitive exclusion", "a complete population ecology" | No reproduction, so the population can only decline; the "carrying capacity" is a tail average of a declining cohort; the "species" are two hand-built profiles whose survival ranking flips between runs; the demo printed "thrive" regardless of the numbers. Replaced with tail population, monotonic decline, niche-differential survival. | 19, 19a | +| E12 | `agentropy.md` §1.1, §7.4, §9.2–9.6, §13, abstract | "A systematic ablation study establishes necessity"; RANDOM "degrades" stratification to 1.9× and destroys niche differentiation; "both factors are necessary" | Three control defects: RANDOM's depletion rate was not matched to CONTROL (~1 layer per env key vs all affinity layers); RANDOM's signal was an invented `consumed // 2`; IMMORTAL returns zero signal by construction. With honest signal accounting RANDOM stratification is **3.12×**, not 1.9×. With the rate matched (new RANDOM-MATCHED) stratification is 2.87× and niche score is 1.00 — identical to CONTROL for the pure-class fixture, which therefore cannot separate structure from depletion. What survives: depletion is necessary for every dynamic measured; class-selective structure is necessary for per-class attribution. "Establishes" → "is consistent with"; "both factors are necessary" withdrawn to a hypothesis. | 20–23, 23a | +| E13 | `agentropy.md` §8.2–8.3 | Zero violations because "AES-256-GCM decryption [cannot] produce bytes from nowhere — a cryptographic impossibility" | The reason is Appendix B, not AES: `loss` is *defined* as `mass − signal`, so the identity cannot fail in a correct implementation. Zero violations over N trials confirms the bookkeeping and cannot make a definitional identity more confirmed. (The paper's own Appendix B already said "It holds because subtraction works"; §8.3 gave the wrong reason.) | 1 | +| E14 | all four papers, `agentropy.md` §8.1 | Single-run integers (2,113 calls; 91 survivors; 457; 2,095; 77.3 %) presented as results | The v1.0.0 numbers were one process-dependent draw: `Topology.reachable_from` iterated a `set[str]`, so neighbour order — and hence every seeded run — varied with `PYTHONHASHSEED` (reviewer: 2,090–2,206 calls; author: 2,093–2,125). **Fixed** (F1); v1.0.1 reference runs are deterministic. Tables now show both columns and say so. A multi-seed mean ± interval is deferred. | 24, 25, 26b | +| E15 | `agentropy.md` §11.1 | Threat model implicit | Added a paragraph stating who can do what to the v1.0.x reference implementation (copy agents, read classes, control ledgers, hold secrets). | review rec. #2 | + +## 2. Code changes (each a separate commit on `release/v1.0.1`) + +| # | Change | Files | Closes | +|---|---|---|---| +| F1 | `Topology.reachable_from` returns neighbours sorted by id — seeded runs are identical across processes | `amt_extensions.py` | E14 | +| F2 | `interact(agent, env, max_layers=None)`; `Node.process(..., max_layers=None)`; `ToolGateway.process_tool_call` counts held layers against `base_cost`, refuses `insufficient_funds` before consumption, strips exactly the price (asserted). New call-record fields `declared_cost`, `layers_held`; new outcome `insufficient_funds`. `LAYER_OVERHEAD = 28` named in `amt_core` | `amt_core.py`, `amt_extensions.py`, `amt_token_economy.py`, `amt_token_economy_demo.py` | E2, E3 | +| F3 | `ResourcePool.last_tick` is `None` until a topology sets it to simulation time; `regenerate(0.0)` is valid | `amt_marketplace.py` | E9 | +| F4 | `NutrientCycler.cycle(max_layers=)` counts only embodied payload as cycled, keeps the rest, tracks minted overhead; `MarketplaceNode` accretes exactly what it asked for; scale demo reports system-level mass | `amt_marketplace.py`, `amt_marketplace_demo.py` | E10 | +| F5 | IoT scale demo shares one topology; prints requested-vs-decoded table, gate-blocked count, and a measured KEY INSIGHT | `amt_physical_iot_demo.py` | E7, E8, E6 | +| F6 | Ablation: RANDOM signal/loss from real geometry; new RANDOM-MATCHED condition; summary interpretation reads against measured rows | `amt_ablation.py`, `amt_ablation_demo.py` | E12 | +| F7 | Demo "KEY INSIGHT" text derived from results (token budget agent; marketplace speciation; IoT survival) | the three `*_demo.py` | E3, E8, E11 | +| F8 | Marketplace demo seeds its behaviour RNG (`AMT_SEED`, default 42) so the reference run is reproducible | `amt_marketplace_demo.py` | E14 | +| F9 | `tests/` (31 tests, incl. known-limitations tests that pass while the limitation is present), `requirements*.txt`, `pyproject.toml`, GitHub Actions CI on 3.10/3.12/3.13 and on `cryptography==3.4.8` | repo root | review rec. #10 | + +Behavioural consequences of F2 that change reference numbers: token-economy survival 45.5 % → 97.5 %; rich/poor call ratio 2.1× → 9.8×; 728 of 2,574 calls are now refused. These are reported, not hidden. + +## 3. Not changed in v1.0.1 (v2 work, needs a design record and a threat model) + +- Uniqueness / anti-replay across copies (signed capabilities, settlement, ledger or hardware) — E1; review recs #3, #6. +- Hiding key-class presence cryptographically — E4; rec #5. +- Signing and externally anchoring commitments — E5; rec #6. +- A global mass equation over factory, accretion, discarded layers, overhead and pools — E10; rec #7 (v1.0.1 measures, does not enforce). +- Reproduction in the ecology model — E11. +- Making the IoT mass gate bind (heavier agents or lower thresholds) — E7. +- A multi-seed runner with intervals — E14; rec #8. +- Repaired ablation fixtures (mixed-class agents in Experiment 3; an immortal condition that still yields signal) — E12; rec #8. +- Renaming the central result — rec #1; the author's call. v1.0.1 changes "establishes" to "is consistent with" and describes the equation as an accounting identity where the text had implied more; the title is unchanged. + +## 4. What the review got wrong about v1.0.0 (for the record) + +- **E9 / item 16** — regeneration is a one-tick offset, not absent. The review's harness ticks once (`topology.tick(10)`) and reads the pool; that single tick is the one that is lost. +- **Item 27** — the review reads v1.0.0 as claiming "a substrate-independent law of life" / "general theory of life-like dynamics". `agentropy.md` Appendix C says: *"This paper makes only the weaker, testable claim (a necessary condition for life-like dynamics); the stronger claim is argued in companion work"*, and opens *"none of this paper's empirical claims depend on them."* That hedge stands as written in v1.0.1. +- **Item 14** — the IoT mass gate is implemented, not absent; the review says the demo "constructs a fresh topology" (true) but does not say the gate could never bind anyway (it cannot; §1 E7). That second point is the author's own finding. + +Everything else in the review's eleven sections and 23-row evidence matrix was confirmed against the deposited bytes — see the verification notes for the receipts. diff --git a/paper/agentropy.md b/paper/agentropy.md index df65274..0d2104a 100644 --- a/paper/agentropy.md +++ b/paper/agentropy.md @@ -9,7 +9,9 @@ ## Abstract -We present a single conservation law for autonomous agents — `C_{n+1} + S_{n+1} + L_n = C_n` — and demonstrate that applying it unmodified across four unrelated domains produces life-like dynamics that no participant programmed, no policy engine configured, and no protocol specified. In cross-organizational accountability, the law produces independent auditability across trust boundaries without any party trusting any other. In token economics, it produces market stratification, budget-dependent behavioral divergence, and inflation-proof currency without any pricing protocol. In physical IoT resource management, it produces dynamic load-shedding and power conservation without any management daemon. In population ecology, it produces carrying capacity, boom/bust cycles, niche differentiation, and competitive exclusion without any ecological rules. Across all four domains — 750 agents, 5,415 interactions — zero conservation violations occur. The law is never modified, extended, or parameterized for any domain. It is the same five characters in every experiment. A systematic ablation study establishes necessity: removing either depletion or class-selective structure eliminates the emergent properties. We identify structured, irreversible depletion as a necessary condition for life-like dynamics in interacting systems. We do not claim this condition is sufficient for life. +We present a single conservation law for autonomous agents — `C_{n+1} + S_{n+1} + L_n = C_n` — and demonstrate that applying it unmodified across four unrelated domains produces life-like dynamics that no participant programmed, no policy engine configured, and no protocol specified. In cross-organizational accountability, the law lets each organization audit its own consumption independently, without a shared intermediary for the arithmetic (under an honest-executor assumption, §11.1). In token economics, it produces market stratification and budget-dependent behavioral divergence, and a currency that cannot be inflated from inside an agent (it does not, by itself, prevent double-spending across copies — §7.2 erratum). In physical IoT resource management, it provides a unified resource-budget interface with exact byte accounting (the load-shedding gate is implemented but not exercised by the reported experiments). In population ecology, it produces monotonic population decline, niche-differential survival of predefined profiles, and nutrient cycling without any ecological rules. Across all four domains — 750 agents, 5,415 interactions in the v1.0.0 reference runs — zero conservation violations occur. The law is never modified, extended, or parameterized for any domain. It is the same five characters in every experiment. A systematic ablation study is consistent with structured, irreversible depletion being a necessary condition for these dynamics; with its controls repaired in v1.0.1 it establishes less than v1.0.0 claimed (§9). We propose structured, irreversible depletion as a necessary condition for life-like dynamics in interacting systems. We do not claim this condition is sufficient for life. + +*This is v1.0.1, a patch release. Wording corrections and code fixes are marked inline and listed in `paper/ERRATA-v1.0.1.md`. The deposited v1.0.0 text is preserved at DOI 10.5281/zenodo.20818597.* --- @@ -43,7 +45,7 @@ Where: The law states: **an agent's mass after interaction, plus the signal extracted, plus the loss incurred, equals the mass before interaction.** Nothing is created. Nothing disappears. Every byte is accounted for. -This law is enforced by construction — it is an algebraic consequence of how AES-256-GCM decryption works. The encrypted layer has a known size. Decryption produces plaintext (signal) and reveals overhead (loss). The sum is exact. No party can violate it because no party controls it. It is not a protocol. It is arithmetic. +This law is enforced by construction — it is an algebraic consequence of how the implementation measures a decrypted layer. The encrypted layer has a known size. Decryption produces plaintext (signal); loss is *defined* as the remainder (Appendix B). The sum is exact. No honest party can violate it because it is arithmetic, not a protocol. What it does not do (v1.0.1 wording): it does not make ciphertext unique or non-copyable, it does not bound *how many* layers an environment consumes, and it does not detect an environment that misreports what it consumed (§11.1). ### 1.3 The Experiments @@ -51,10 +53,10 @@ We applied this law, unmodified, to four domains: | Domain | What mass represents | What emerges | |--------|---------------------|-------------| -| Cross-org accountability | Encrypted agent payload | Trustless auditability | +| Cross-org accountability | Encrypted agent payload | Independent per-org auditability | | Token economy | Tool-call currency | Market stratification | -| Physical IoT | Battery, bandwidth, CPU | Resource conservation | -| Population ecology | Survival energy | Carrying capacity, speciation | +| Physical IoT | Battery, bandwidth, CPU | Unified resource accounting | +| Population ecology | Survival energy | Niche-differential survival, nutrient cycling | In each domain, we changed what the layers *mean*. We never changed how they *work*. The conservation law is identical in all four experiments. The `interact()` function is shared. The assertion `mass_before == mass_after + signal + loss` is the same line of code. @@ -109,20 +111,22 @@ Three organizations — Aegis Corp (deployer), Bifrost Systems (processor), Verd ### 3.3 What Emerges -**Independent auditability**: Each organization audits its own local ledger. Conservation holds per-interaction, so each org can verify its own consumption without any other org's data. No shared audit framework. No mutually trusted third party. +**Independent auditability**: Each organization audits its own local ledger. Conservation holds per-interaction, so each org can verify its own consumption without any other org's data. No shared audit framework. No mutually trusted third party *for the arithmetic* — each executor's honesty about what it consumed is assumed (§11.1), and the reference "public ledger" is an unsigned in-memory list, so this is self-consistency, not non-repudiation (v1.0.1 wording; Walker 2026a §3). -**Non-disclosure**: The public ledger contains Merkle roots of batches — not individual transactions. An observer sees that Bifrost committed 3 transactions in a time window. They cannot determine what signal was extracted, what mass was consumed, or which agent transited. +**Non-disclosure of contents**: The public ledger contains Merkle roots of batches — not individual transactions. An observer sees that Bifrost committed 3 transactions in a time window. They cannot determine what signal was extracted, what mass was consumed, or which agent transited. (Layer *classes* are plaintext metadata and are visible to any environment; only contents are hidden — v1.0.1 erratum, Walker 2026a §2.) -**Budget-constrained routing**: Agents with insufficient delta layers (validation fuel) cannot reach Verdant Labs for validation. This isn't a policy — it's physics. The agent dies before reaching the validator because it ran out of mass. At scale, 71.5% of agents skip validation entirely. No one decided this. The conservation law decided it. +**Budget-constrained routing**: Agents with insufficient delta layers (validation fuel) cannot reach Verdant Labs for validation. This isn't a policy — it's physics. The agent dies before reaching the validator because it ran out of mass. At scale, 71.0% of agents skip validation entirely (v1.0.1 deterministic reference run; v1.0.0 reported 71.5% from one process-dependent draw). No one decided this. The conservation law decided it. ### 3.4 Results -| Metric | Value | -|--------|-------| -| Agents | 200 | -| Interactions | 457 | -| Conservation violations | 0 | -| Agents validated | 28.5% | +| Metric | Value (v1.0.1 reference run) | v1.0.0 | +|--------|-------|-------| +| Agents | 200 | 200 | +| Interactions | 459 | 457 | +| Conservation violations | 0 | 0 | +| Agents validated | 29.0% | 28.5% | + +*The v1.0.0 integers came from a run whose neighbour order depended on the interpreter's string-hash seed (v1.0.1 erratum, reproducibility). v1.0.1 sorts neighbour iteration; the numbers above are identical under any `PYTHONHASHSEED`.* --- @@ -138,22 +142,27 @@ Five tool classes (API calls, LLM inference, storage, compute, admin) mapped to ### 4.3 What Emerges -**Natural currency**: Layers are atomic (consumed or not — no partial payment), class-specific (each denomination funds different tools), and conservation-governed (cannot be inflated, counterfeited, or double-spent). +**Natural currency**: Layers are atomic (consumed or not — no partial payment), class-specific (each denomination funds different tools), and conservation-governed (no mass is created outside the factory). *v1.0.1 erratum:* v1.0.0 added "cannot be … double-spent". That is false of the reference implementation — ciphertext is copyable bytes, and a copied agent pays twice (Walker 2026b §8.2). Uniqueness across copies needs an authority outside the scheme. -**Market stratification**: Agents with 12 beta layers can make exactly 4 LLM calls. Agents with 1 beta layer can make zero. No access control list creates this stratification. The agent's mass profile IS its economic identity. +**Market stratification**: Agents with 12 beta layers can make exactly 4 LLM calls. Agents with 1 beta layer can make zero. *v1.0.1:* true now that the gateway enforces its declared price; in v1.0.0 the gateway drained every layer of its class, so 12 layers bought one call and 1 layer bought a "successful" 3-layer call (Walker 2026b §4.1 erratum). No access control list creates this stratification — a gateway price rule plus the agent's mass profile do. -**Budget-behavior coupling**: Rich agents (>1,000 B) average 3.8 successful tool calls. Poor agents (<=500 B) average 1.8. The ratio (2.1x) emerged from conservation, not policy. Same topology. Same parameters. Different budgets. Different lives. +**Budget-behavior coupling**: In the v1.0.1 reference run rich agents (>1,000 B) average 10.0 successful tool calls and poor agents (≤500 B) average 1.0, a 9.8× ratio (v1.0.0: 3.8 / 1.8 / 2.1×, under the price-ignoring gateway). The gap widened because refused payments now leave the poor agent's layers unspent and un-useful, while rich agents are no longer drained. Same topology. Same parameters. Different budgets. Different lives. **Dynamic hazard pricing**: Environments under load don't raise prices per key — they arm themselves with additional keys, stripping more layer types per visit. Fixed unit prices with dynamic aggregate cost, governed by key rotation rather than rate negotiation. ### 4.4 Results -| Metric | Value | -|--------|-------| -| Agents | 200 | -| Tool calls | 2,113 | -| Conservation violations | 0 | -| Rich/poor call ratio | 2.1x | +| Metric | Value (v1.0.1 reference run) | v1.0.0 | +|--------|-------|-------| +| Agents | 200 | 200 | +| Tool calls (all outcomes) | 2,574 | 2,113 | +| Successful calls | 468 | 449 | +| Refused for insufficient funds | 728 | — (not a v1.0.0 outcome) | +| Agents surviving | 195 (97.5%) | 91 (45.5%) | +| Conservation violations | 0 | 0 | +| Rich/poor call ratio | 9.8x | 2.1x | + +*v1.0.1 figures are deterministic across processes; v1.0.0's varied with `PYTHONHASHSEED` (reviewer: 2,090–2,206 calls, 100–106 survivors; author: 2,093–2,125 calls, 88–107 survivors). Survival rose because gateways no longer drain agents.* --- @@ -173,7 +182,7 @@ Each physical resource maps to a key class via a fixed conversion factor (1 Wh = **Safe passage from key absence**: At an off-grid location (no beta key), bandwidth layers pass through unstripped. This isn't a "bandwidth preservation feature" — it's a consequence of key affinity. The conservation law doesn't know about bandwidth. It simply has no key to decrypt those layers. -**Dynamic load-shedding**: As battery (alpha mass) depletes, mass gates tighten. Heavier agents get blocked. Lighter agents pass. Load-shedding emerges from conservation, not from a load-balancing algorithm. No power management daemon. No priority queue. Just physics. +**Dynamic load-shedding** (*implemented, not exercised — v1.0.1 erratum*): As battery (alpha mass) depletes, mass gates tighten and heavier agents would be blocked. In the reported experiments this never happens: v1.0.0's scale demo rebuilt the topology per agent so no battery was ever shared (fixed in v1.0.1), and in both versions the tightest gate (512,000 B) is about five times the heaviest agent either experiment can build (≤106,603 B), so the gate cannot bind. The demo now reports the blocked count: 0 of 750 visits. Walker 2026c §3.3. **Environmental coasting**: An agent that exhausts its alpha and beta layers at the highway "coasts" through mountain (which holds the same keys) — zero layers stripped. The conservation law produces energy-free traversal through environments whose hazard surface the agent has already survived. @@ -200,24 +209,26 @@ When hundreds of agents compete for finite resources, what population dynamics e ### 6.3 What Emerges -**Carrying capacity**: The population stabilizes at 4-6 agents (2-3% of initial population). Not programmed. Emerges from regeneration rate / consumption rate balance. +*v1.0.1: the four headings below were "Carrying capacity", "Boom/bust dynamics", "Niche differentiation" and "Nutrient cycling". The model has no reproduction, so the first three name things it cannot exhibit (Walker 2026d §5, §9). The measured properties are:* + +**Tail population** (v1.0.0: "Carrying capacity"): a declining cohort settles into a tail of 1–5 agents depending on the run; the implementation's estimate is a mean over the last 30 % of the trace. Not an equilibrium — nothing is born. -**Boom/bust dynamics**: Population crashes when consumption exceeds regeneration. Exponential decay: 100 agents → 76 (step 5) → 44 (step 10) → 18 (step 15) → 0 (step 38). Classic overshoot-and-collapse, produced by conservation law + finite resources. +**Monotonic decline** (v1.0.0: "Boom/bust"): 100 agents → 82 (step 4) → 49 (step 8) → 17 (step 16) → 5 (step 28) → 2 alive at 40 steps (v1.0.1 reference run). No boom, no recovery; consumption outpaces the accretion that pools and cycling can fund. -**Niche differentiation**: Alpha-heavy agents cluster at Feeding A (4.0% survival). Beta-heavy agents cluster at Feeding B (8.0% survival). Different mass profiles → different habitats → different survival outcomes. Gause's competitive exclusion principle, emergent from mass physics. +**Niche-differential survival** (v1.0.0: "Niche differentiation … Gause's competitive exclusion"): two predefined profiles survive at different rates — alpha-heavy 12.0 %, beta-heavy 4.0 % in the v1.0.1 run; v1.0.0 reported 4.0 % / 8.0 %, and other seeds give 0–8 % — so even the direction is run-dependent. Differential depletion by key class is real; speciation and competitive exclusion are not claimed. -**Nutrient cycling**: 77.3% of dead agent mass is recycled to survivors. The food chain: Agent A dies → signal + loss deposited → nutrient cycler accumulates → factory creates new layers → Agent B accretes. Dead agents fuel living ones. Conservation governs every step. +**Nutrient cycling**: 29.3 % of captured nutrient bytes are embodied as new payload for survivors (v1.0.0 reported 77.3 % by counting budget rather than accreted mass). The loop: Agent A depleted → signal + loss deposited → cycler accumulates → factory mints layers → Agent B accretes. The minted layers also carry 2,604 B of fresh overhead the loop did not supply; no system-level equation accounts for it. -**Resource partitioning**: The Shelter (no keys, safe passage) accounts for 31.6% of all interactions. Agents disproportionately route through safe zones — behavioral resource partitioning without ecological modeling. +**Route concentration**: The Shelter (no keys, safe passage) accounts for 31.6 % of all interactions in both versions' reference runs. Agents disproportionately route through the safe node — a consequence of the mass-dependent routing rule. ### 6.4 Results -| Metric | Value | -|--------|-------| -| Agents | 200 | -| Interactions | 2,095 | -| Nutrient recycling rate | 77.3% | -| Conservation violations | 0 | +| Metric | Value (v1.0.1 reference run) | v1.0.0 | +|--------|-------|-------| +| Agents | 200 | 200 | +| Interactions | 1,942 | 2,095 | +| Nutrient recycling rate | 29.3% | 77.3% (budget-counted) | +| Conservation violations | 0 | 0 | --- @@ -246,10 +257,10 @@ The law doesn't know which interpretation is active. It enforces `C + S + L = C_ In each domain, the conservation law forces the system into a narrow corridor of possible states. Not all states are reachable. Not all behaviors are possible. The constraint eliminates: -- **Trust without trust (cross-org)**: Conservation eliminates the need for trusted intermediaries because the math self-enforces. The constraint forces auditability. -- **Markets without markets (token economy)**: Conservation eliminates inflation, double-spending, and unauthorized access because mass is physical. The constraint forces scarcity. -- **Management without managers (IoT)**: Conservation eliminates the need for resource governors because depletion is automatic. The constraint forces budgeting. -- **Ecology without ecology (marketplace)**: Conservation eliminates the need for population rules because mass depletion creates carrying capacity. The constraint forces balance. +- **Audit without a shared auditor (cross-org)**: Conservation removes the need for a trusted intermediary *for the arithmetic* because each party can recompute it. It does not remove the need to trust each executor's report (§11.1). The constraint forces per-party auditability. +- **Scarcity inside the agent (token economy)**: Conservation eliminates mass creation outside the factory. *v1.0.1 erratum:* v1.0.0 said it "eliminates inflation, double-spending, and unauthorized access because mass is physical". Mass is bytes; bytes copy. Double-spending across copies is not eliminated (Walker 2026b §8.2), and "unauthorized access" is a policy notion the law does not address. The constraint forces scarcity within an honest process. +- **Budgeting without a governor (IoT)**: Conservation eliminates the need for a separate resource governor because depletion is automatic. The constraint forces budgeting — of the simulated budget; physical coupling is future work. +- **Decline without a death rule (marketplace)**: Conservation eliminates the need for explicit death rules because depletion ends agents. *v1.0.1 wording:* it does not by itself create carrying capacity — with no births the population can only decline, and the reported "carrying capacity" is a tail average of that decline (Walker 2026d §5). ### 7.3 Life-Like Dynamics, Not Intelligence @@ -264,7 +275,7 @@ These are not decisions. They are consequences of a constraint making consequenc ### 7.4 A Necessary Condition -We identify structured, irreversible depletion as a *necessary condition* for the life-like dynamics documented in this paper. The ablation study (Section 9) establishes this: remove depletion and all four emergent properties vanish; remove class-selective structure and three of four degrade or disappear. Without the constraint, agents can do anything, which means nothing differentiates. Scarcity forces allocation. Structure forces selectivity. Together they produce dynamics that look like life. +We propose structured, irreversible depletion as a *necessary condition* for the life-like dynamics documented in this paper. The ablation study (Section 9) is consistent with the depletion half: remove depletion and all four measured properties vanish — although the immortal condition returns zero signal by construction, so part of that result is defined into the treatment. The structure half is weaker than v1.0.0 stated (*v1.0.1 erratum*): with the random condition's signal accounting corrected and its depletion rate matched to the control, class-blind stripping preserves stratification (2.9–3.1× vs 3.0×) and, for pure-class agents, preserves niche differentiation; what class-selective structure is demonstrably necessary for is per-class attribution (audit). Scarcity forces allocation. Structure forces attribution. Whether structure is necessary for the other dynamics is open. We do not claim this condition is sufficient. Other factors — finite population, topology, resource regeneration rates — contribute to the specific dynamics observed. What we claim is narrower: without structured, irreversible depletion that makes consequence unavoidable, the dynamics we document cannot arise. This is a necessary condition, not a recipe. @@ -276,15 +287,17 @@ This echoes a principle from thermodynamics: the Second Law doesn't make heat en ### 8.1 Cross-Domain Summary -| Domain | Agents | Interactions | Violations | Key Emergent Property | -|--------|--------|-------------|------------|----------------------| -| Cross-org | 200 | 457 | 0 | Trustless auditability | -| Token economy | 200 | 2,113 | 0 | Market stratification | -| Physical IoT | 150 | 750 | 0 | Unified resource management | -| Ecology | 200 | 2,095 | 0 | Carrying capacity + speciation | -| **Total** | **750** | **5,415** | **0** | | +| Domain | Agents | Interactions (v1.0.1 ref. run) | v1.0.0 | Violations | Key Emergent Property | +|--------|--------|-------------|--------|------------|----------------------| +| Cross-org | 200 | 459 | 457 | 0 | Independent per-org auditability | +| Token economy | 200 | 2,574 | 2,113 | 0 | Market stratification | +| Physical IoT | 150 | 750 | 750 | 0 | Unified resource accounting | +| Ecology | 200 | see Walker 2026d §6 | 2,095 | 0 | Niche-differential survival, nutrient cycling | +| **Total** | **750** | | **5,415** | **0** | | -Additionally, the core scale test (not domain-specific) verified conservation across 50,000 agents and 2.3 million interactions with zero violations. +Additionally, the core scale test (not domain-specific) verified conservation across 50,000 agents and about 2.29 million node visits with zero violations (author 2,285,529 visits, 334 s; reviewer 2,286,167 visits, 281 s — the difference is the hash-seed ordering fixed in v1.0.1). + +*v1.0.1 note on the integers:* the v1.0.0 column is one draw from a process-dependent distribution (the neighbour-order bug, §"Reproducibility" in the errata). The v1.0.1 column is deterministic. Neither column is a mean with an interval; a multi-seed runner is deferred. ### 8.2 The Number That Matters @@ -294,9 +307,9 @@ Zero conservation violations across 750 agents, 5,415 interactions, four domains ### 8.3 What Zero Means -In protocol-based systems, the violation rate is the error rate — bugs in the billing service, race conditions in the rate limiter, edge cases in the access control logic. These systems aspire to low violation rates. AMT's violation rate is not low. It is structurally zero. A violation would require AES-256-GCM decryption to produce bytes from nowhere — a cryptographic impossibility. +In protocol-based systems, the violation rate is the error rate — bugs in the billing service, race conditions in the rate limiter, edge cases in the access control logic. These systems aspire to low violation rates. AMT's violation rate is not low. It is structurally zero — because `loss` is defined as `mass − signal` (Appendix B), so the identity cannot fail in a correct implementation. *v1.0.1 wording:* zero violations across any number of trials therefore confirms that the implementation accounts for every byte it removes; it does not independently validate a natural law, and the count of trials cannot make a definitional identity more confirmed (Overmier 2026). The v1.0.0 sentence "a violation would require AES-256-GCM decryption to produce bytes from nowhere" was the wrong reason; the right one is that the equation is an accounting identity. -This is the difference between a protocol and a law. A protocol says "don't create mass." A law says "mass cannot be created." The first can be violated. The second cannot. +This is the difference between a protocol and an identity. A protocol says "don't create mass." An identity says "mass removed is, by definition, signal plus loss." The first can be violated. The second cannot — and, for that reason, it also cannot by itself prevent copying, overcharging, or misreporting. --- @@ -316,6 +329,10 @@ To answer this, we perform a systematic ablation: remove or degrade the conserva **RANDOM** (remove structure): The environment consumes layers but ignores key class affinity. A random subset of layers is removed regardless of which key class they belong to. Mass is consumed (depletion exists) but the structure of consumption — which key classes are stripped, in what proportion — is destroyed. Total mass accounting is valid but per-class attribution is impossible. +*v1.0.1 correction to the RANDOM condition.* In v1.0.0 this condition (a) removed about one layer per environment key class, while CONTROL removes every affinity layer — so depletion *rate* was not held constant — and (b) reported `signal = consumed // 2`, an invented split (Overmier 2026, items 20–21). v1.0.1 keeps RANDOM for comparability but derives signal and loss from the real layer geometry (`mass − 28`, zero for empty layers), and adds: + +**RANDOM-MATCHED** (remove structure, hold rate): removes exactly as many layers as CONTROL would have decrypted on that visit, chosen at random regardless of class. For a *pure-class* agent this is identical to CONTROL by construction — "random layers of one class" is the same set as "all layers of that class" — so Experiment 3 cannot separate structure from depletion with the pure-class fixture it uses. + ### 9.3 Four Measurements We test whether each emergent property survives each ablation condition: @@ -332,13 +349,16 @@ We test whether each emergent property survives each ablation condition: **Experiment 1: Scarcity** +*Tables below are the v1.0.1 run (`python3 amt_ablation_demo.py`, seed 42, deterministic). Where a v1.0.0 cell differed it is given in brackets.* + | Condition | Death Rate | Survivors | Interactions | Violations | |-----------|-----------|-----------|-------------|-----------| | CONTROL | 100% | 0 | 561 | 0 | | IMMORTAL | 0% | 100 | 2,000 | 0 | | RANDOM | 100% | 0 | 1,300 | 0 | +| RANDOM-MATCHED | 100% | 0 | 840 | 0 | -Depletion creates finite lifespans. Without it (IMMORTAL), agents live forever. Both CONTROL and RANDOM produce death — depletion alone is sufficient for scarcity. +Depletion creates finite lifespans. Without it (IMMORTAL), agents live forever. CONTROL and both RANDOM variants produce death — depletion alone is sufficient for scarcity. **Experiment 2: Stratification** @@ -346,9 +366,10 @@ Depletion creates finite lifespans. Without it (IMMORTAL), agents live forever. |-----------|-----------|-----------|-------|-------------| | CONTROL | 768 B | 256 B | 3.00x | 50 / 50 | | IMMORTAL | 0 B | 0 B | 1.00x | 0 / 0 | -| RANDOM | 138 B | 73 B | 1.90x | 0 / 0 | +| RANDOM | 192 B [138 B] | 61 B [73 B] | 3.12x [1.90x] | 0 / 0 | +| RANDOM-MATCHED | 558 B | 195 B | 2.87x | 0 / 0 | -Under conservation (CONTROL), rich agents extract exactly 3x the signal of poor agents — matching the 3:1 key-class breadth ratio. Under random stripping (RANDOM), the ratio degrades to 1.9x: some stratification persists (because rich agents still have more total mass to lose) but the precision of class-selective economics is destroyed. Under IMMORTAL, zero signal is extracted from either tier. +Under CONTROL, rich agents extract 3× the signal of poor agents. That ratio is set by the fixture: rich agents carry data in three classes and poor agents carry data in one class plus empty padding in the other two, so a 3:1 outcome is the expected consequence of the input construction (Overmier 2026, item 23). *v1.0.1 correction:* v1.0.0 reported that RANDOM "degrades" the ratio to 1.9×; that number came from the invented 50/50 signal split. With signal measured from the real layer geometry, class-blind stripping preserves the ratio (3.1× unmatched, 2.9× rate-matched). **Stratification does not depend on class-selective structure**; it depends on rich agents carrying more data. Under IMMORTAL, zero signal is returned by construction. **Experiment 3: Selectivity (Niche Differentiation)** @@ -357,12 +378,9 @@ Under conservation (CONTROL), rich agents extract exactly 3x the signal of poor | CONTROL | 0% | 100% | 100% | 0% | **1.00** | | IMMORTAL | 100% | 100% | 100% | 100% | 0.00 | | RANDOM | 100% | 100% | 100% | 100% | 0.00 | +| RANDOM-MATCHED | 0% | 100% | 100% | 0% | **1.00** | -Under conservation (CONTROL), niche differentiation is **perfect**: alpha-pure agents die in alpha environments and survive in beta environments, and vice versa. This is because the conservation law's key-class selectivity ensures that only matching layers are stripped. - -Under IMMORTAL, nobody dies anywhere — no niche to differentiate. - -Under RANDOM, agents survive all environments equally because random stripping removes so few layers per interaction (matching the environment's single-key hazard rate) that agents outlast the experiment in all habitats. The class-selective mechanism that creates niches is eliminated. +Under CONTROL, niche differentiation is perfect: alpha-pure agents die in alpha environments and survive in beta environments, and vice versa. Under IMMORTAL, nobody dies anywhere. Under RANDOM (unmatched), agents survive everywhere because the condition strips one layer per visit and the experiment is three visits long — a *rate* effect, not a structure effect. Under RANDOM-MATCHED the score is 1.00, identical to CONTROL, because for a pure-class agent "random layers" and "all affinity layers" are the same set. *v1.0.1 conclusion:* this fixture cannot attribute niche differentiation to class-selective structure; a mixed-class fixture would be needed. **Experiment 4: Accountability** @@ -371,32 +389,30 @@ Under RANDOM, agents survive all environments equally because random stripping r | CONTROL | 100% | 41,000 B | 100% | 100% | | IMMORTAL | 100% | 0 B | 0% | 0% | | RANDOM | 100% | 11,240 B | 100% | 0% | +| RANDOM-MATCHED | 100% | 28,204 B | 93% | 0% | -Conservation is technically valid in all three conditions (mass accounting is correct). But: -- CONTROL: meaningful consumption with per-class delta_L vectors — each organization can audit which key classes were consumed on its infrastructure. -- IMMORTAL: conservation is vacuous — nothing was consumed, nothing to audit. -- RANDOM: mass was consumed but the per-class delta_L vector is empty — total mass accounting works but no organization can determine which resource classes were consumed. +Conservation is valid in all conditions (mass accounting is correct). Only CONTROL populates the per-class `delta_L` vector — the property that lets an organization say *which* resource classes were consumed on its infrastructure. This is the one result that survives the repaired controls unchanged, and it is close to definitional: class-blind stripping cannot produce class attribution. ### 9.5 Summary -| Emergent Property | CONTROL | IMMORTAL | RANDOM | -|------------------|---------|----------|--------| -| Finite lifespans | YES | NO | YES | -| Budget stratification | YES (3.0x) | NO | Degraded (1.9x) | -| Niche differentiation | YES (1.00) | NO | NO | -| Per-class audit | YES | NO (vacuous) | NO (no per-class) | +| Emergent Property | CONTROL | IMMORTAL | RANDOM | RANDOM-MATCHED | +|------------------|---------|----------|--------|--------| +| Finite lifespans | YES | NO | YES | YES | +| Budget stratification | YES (3.0x) | NO (zero output) | YES (3.1x) [v1.0.0: Degraded 1.9x] | YES (2.9x) | +| Niche differentiation | YES (1.00) | NO | NO (rate artifact) | YES (1.00) | +| Per-class audit | YES | NO (vacuous) | Total only | Total only | -### 9.6 Interpretation +### 9.6 Interpretation (*rewritten in v1.0.1*) -The conservation law has two components: **depletion** (mass decreases on interaction) and **structure** (depletion is class-selective and accountable via delta_L). The ablation isolates their contributions: +The conservation law has two components: **depletion** (mass decreases on interaction) and **structure** (depletion is class-selective and accountable via delta_L). The ablation, with its controls repaired, separates them less cleanly than v1.0.0 stated: -- **Remove depletion** (IMMORTAL): All four emergent properties vanish. No scarcity, no stratification, no niches, nothing to audit. Depletion is necessary for any emergence. +- **Remove depletion** (IMMORTAL): All four measured properties vanish. This is the strongest result, with one caveat the reviewer identified: the immortal condition returns zero signal *by construction*, so "no economic output" is partly defined into the treatment rather than observed. -- **Remove structure** (RANDOM): Scarcity survives (agents still die) but class-selective properties — niche differentiation and per-class audit — are destroyed. Stratification degrades from 3.0x to 1.9x because the precision of class-selective economics is replaced by crude mass-proportional effects. +- **Remove structure, hold rate** (RANDOM-MATCHED): Scarcity survives. Stratification survives (2.9×). Niche differentiation survives for the pure-class fixture used. Per-class audit does not survive — and cannot, by definition. -**Both factors are necessary.** The conservation law provides both. Remove either and emergence degrades. Remove both and emergence disappears entirely. +- **Remove structure without holding rate** (RANDOM, the v1.0.0 condition): niche differentiation "vanishes" because agents are under-depleted, not because structure is gone; the stratification "degradation" was an accounting artifact. -This establishes necessity: the conservation law is not merely present alongside emergence. It is required for it. The structured, class-selective, irreversible depletion governed by `C_{n+1} + S_{n+1} + L_n = C_n` is a necessary condition for the life-like dynamics documented in this paper. We do not claim it is sufficient — other factors (topology, population size, regeneration) shape the specific dynamics. But without structured depletion that makes consequence unavoidable, none of these dynamics arise. +What the study supports: **depletion is necessary for every dynamic measured here, and class-selective structure is necessary for per-class attribution.** What it does not establish, and v1.0.0 claimed: that structure is necessary for stratification or niche differentiation, or that "both factors are necessary" for emergence in general. The hypothesis of §7.4 — structured, irreversible depletion as a necessary condition for life-like dynamics — stands as a hypothesis these fixtures are *consistent with*, not one they establish. Repairing the fixtures (mixed-class agents in Experiment 3, an immortal condition that still yields signal, many seeds with intervals) is the v2 work that could. --- @@ -404,11 +420,11 @@ This establishes necessity: the conservation law is not merely present alongside ### 10.1 Conservation Laws in Other Computational Frameworks -**Petri nets** use token conservation to model concurrent systems. Tokens are produced and consumed by transitions, and place invariants enforce conservation across the net. AMT shares the token-conservation property but adds cryptographic enforcement — AMT tokens (layers) cannot be duplicated because they are AES-256-GCM ciphertexts, whereas Petri net tokens are abstract and can be trivially copied in implementation. +**Petri nets** use token conservation to model concurrent systems. Tokens are produced and consumed by transitions, and place invariants enforce conservation across the net. AMT shares the token-conservation property and adds a cryptographic *content* boundary — a layer's payload is readable only by an environment holding the class secret. *v1.0.1 erratum:* v1.0.0 said here that AMT layers "cannot be duplicated because they are AES-256-GCM ciphertexts". Ciphertexts are bytes and copy exactly like Petri-net tokens; AES-GCM provides confidentiality and integrity, not uniqueness (Walker 2026b §8.2). **Membrane computing (P systems)** models computation through objects passing between membrane-bounded regions. Objects are consumed and produced by rules, with conservation enforced by rule semantics. AMT's key class affinity is analogous to membrane selectivity — only certain objects (layers of matching class) can pass through certain membranes (be decrypted by environments holding matching keys). -**Chemical Abstract Machine (CHAM)** models concurrent computation as chemical reactions, with molecules (terms) reacting according to rules. Conservation of molecules is a design principle. AMT's conservation is stronger: it is enforced by cryptography rather than by programming convention. +**Chemical Abstract Machine (CHAM)** models concurrent computation as chemical reactions, with molecules (terms) reacting according to rules. Conservation of molecules is a design principle. AMT's conservation is an accounting identity enforced by an assertion in one function, with the payload boundary enforced by cryptography (v1.0.1 wording; v1.0.0 said "stronger … enforced by cryptography rather than by programming convention"). ### 10.2 Emergent Intelligence in Constrained Systems @@ -416,7 +432,7 @@ This establishes necessity: the conservation law is not merely present alongside **Swarm intelligence** (ant colony optimization, particle swarm) produces intelligent-looking collective behavior from simple individual rules. AMT agents are simpler than swarm particles — they have no individual rules at all. They are acted upon. The intelligence emerges from the constraint, not from agent computation. -**Artificial Life** (Tierra, Avida) simulates evolution in digital environments using conservation of computational resources (CPU cycles, memory). AMT provides formal conservation guarantees (cryptographic enforcement) that these systems lack. +**Artificial Life** (Tierra, Avida) simulates evolution in digital environments using conservation of computational resources (CPU cycles, memory). AMT's per-interaction accounting identity is explicit and asserted where theirs is implicit in the runtime; unlike them, v1.0.x has no reproduction and therefore no evolution (§11.3, §12.1). ### 10.3 Multi-Agent Economics @@ -432,6 +448,8 @@ This establishes necessity: the conservation law is not merely present alongside The conservation law is enforced inside the `interact()` function. A malicious environment that reimplements this function can report false signal/loss values. The law governs correct implementations; it does not detect incorrect ones. Mitigation via hardware attestation (TPM/SGX) or zero-knowledge proofs of correct execution is future work. +*v1.0.1 addition — the threat model, stated.* Who can do what to the v1.0.x reference implementation: **anyone holding an agent's bytes can copy it** and present each copy (Walker 2026b §8.2); **any environment can see every layer's class** and count (Walker 2026a §2); **the operator of a node controls its local ledger, its commitments, and the "public" ledger they are appended to**, none of which is signed or anchored outside the operator (Walker 2026a §3); **the factory holds every class secret** and is the only mass source; **a gateway** enforces its price only because v1.0.1 code makes it (Walker 2026b §4.1). Everything the papers claim holds under the assumption that executors are honest and agents are not copied. That assumption was stated in v1.0.0 (here and in Walker 2026a §9) but several sentences elsewhere claimed more; they are corrected in this release. + ### 11.2 Static Interpretation The mapping of mass to domain semantics (alpha = battery, beta = bandwidth) is fixed at design time. Dynamic reinterpretation — where the "meaning" of a key class changes at runtime — is not currently supported and would complicate the conservation argument. @@ -476,21 +494,21 @@ We added one constraint to a system of autonomous agents: a conservation law tha We applied this constraint, unmodified, to four unrelated domains: -1. **Cross-organizational accountability**: The constraint forced trustless auditability. Each organization could verify its own consumption without trusting any other party, because the math self-enforced at every interaction. +1. **Cross-organizational accountability**: The constraint gave each organization independent auditability of its own consumption, with no shared intermediary for the arithmetic — under an honest-executor assumption, and with a reference ledger that demonstrates self-consistency rather than non-repudiation (*v1.0.1 wording*). -2. **Token economics**: The constraint forced market dynamics. Budget stratification, behavioral divergence, and inflation-proof currency emerged from layer composition alone — no pricing protocol, no credit system, no rate limiter. +2. **Token economics**: The constraint plus a gateway price rule produced budget stratification and behavioral divergence — no credit system, no rate limiter. The currency cannot be inflated from inside an agent; it can be copied, and the reference implementation does not prevent a copy from paying (*v1.0.1 wording*). -3. **Physical IoT**: The constraint forced resource conservation. Battery management, bandwidth metering, and CPU budgeting became the same operation — layer stripping of different key classes — with load-shedding emerging from mass gate depletion. +3. **Physical IoT**: The constraint unified battery, bandwidth, storage, CPU and sensor budgets into one accounting operation — layer stripping of different key classes. The load-shedding gate is implemented and not exercised by the reported experiments; the physical coupling is future work (*v1.0.1 wording*). -4. **Population ecology**: The constraint forced ecological balance. Carrying capacity, boom/bust cycles, niche differentiation, and nutrient cycling emerged from 200 agents competing for finite resources — no birth rules, no death rules, no population caps. +4. **Population ecology**: The constraint plus finite regenerating pools and a nutrient cycler produced monotonic population decline, niche-differential survival of two predefined profiles, and recycling of dead agents' mass — no birth rules, no death rules, no population caps. Without births there is no carrying capacity, boom, or speciation to observe (*v1.0.1 wording*; Walker 2026d). -750 agents. 5,415 interactions. Four domains. Zero parameter tuning. Zero conservation violations. +750 agents. 5,415 interactions in the v1.0.0 reference runs. Four domains. Zero parameter tuning. Zero conservation violations. -The ablation study (Section 9) establishes that this is not correlation. Removing depletion eliminates all emergence. Removing class-selective structure degrades stratification and destroys niche differentiation and per-class auditability. Both components — depletion and structure — are necessary. The conservation law provides both. +The ablation study (Section 9), with its controls repaired in v1.0.1, supports that depletion is necessary for every dynamic measured and that class-selective structure is necessary for per-class attribution. It does not establish that structure is necessary for stratification or niche differentiation; the v1.0.0 claim that "both components are necessary" for emergence in general is withdrawn to the status of a hypothesis. The conservation law does not know what domain it is operating in. It does not know that alpha means battery in one experiment and mission payload in another. It does not know about organizations, tool prices, RV campgrounds, or ecological niches. It knows one thing: `C_{n+1} + S_{n+1} + L_n = C_n`. And from that one thing, all of the above emerged. -We identify a necessary condition for life-like dynamics in interacting systems: structured, irreversible depletion that makes consequence unavoidable. We do not claim this condition is sufficient for life. +We propose a necessary condition for life-like dynamics in interacting systems: structured, irreversible depletion that makes consequence unavoidable. The evidence here is consistent with it and does not establish it. We do not claim this condition is sufficient for life. **`λ > 0`. The signal continues.** diff --git a/paper/cross_org_accountability.md b/paper/cross_org_accountability.md index cba9b17..88c059f 100644 --- a/paper/cross_org_accountability.md +++ b/paper/cross_org_accountability.md @@ -1,4 +1,6 @@ -# Cross-Organizational Agent Accountability Without Trust: A Conservation Law Approach +# Cross-Organizational Agent Accountability Without a Trusted Intermediary for the Arithmetic: A Conservation Law Approach + +*Title amended in v1.0.1 from "…Accountability Without Trust…". The honest-environment assumption (§9) was always stated; the old title overstated it. See `paper/ERRATA-v1.0.1.md`.* **Agent Mass Theory Applied to Multi-Party Agent Traversal** @@ -91,12 +93,14 @@ This is verified by assertion in the `interact()` function. In the reference imp ### Key Classes and Information Asymmetry -Key classes create *information asymmetry by design*. An environment that holds the secret for class `alpha` can decrypt alpha layers but cannot even detect the existence of `beta` layers (they are indistinguishable from random bytes). This means: +Key classes create *information asymmetry by design* — over layer **contents**, not over layer **classes**. An environment that holds the secret for class `alpha` can decrypt alpha layers and cannot decrypt `beta` layers (their payloads are indistinguishable from random bytes). In the reference implementation each layer carries its `key_class` as plaintext metadata (`amt_core.py`, `Layer.key_class`), and the environment reads that tag to select a key; so the *existence and count* of beta layers is visible to every environment, and only their contents are hidden. This means: - An agent carrying layers for classes `{alpha, beta, gamma}` traversing an environment with keys for `{alpha}` will lose only its alpha layers. -- The environment cannot determine that beta and gamma layers exist. +- The environment can see that beta and gamma layers exist and how many there are; it cannot read them. - The agent cannot determine which layers were stripped (it only observes total mass change). +*v1.0.1 erratum.* v1.0.0 said the environment "cannot even detect the existence of `beta` layers". That was false of the implementation (Overmier 2026, item 2). Hiding class *presence* would require encrypting or omitting the class tag and trial-decrypting under every held key — a design change with a cost, deferred to v2. + ### Mass Gates Environments have mass thresholds: `(min_mass, max_mass)`. An agent outside this range cannot enter. This is physical topology, not permission — a bowling ball cannot fit through a garden hose regardless of its access control list. @@ -137,9 +141,9 @@ The public ledger does NOT contain: An authorized auditor can verify that a specific local entry exists in a public commitment by recomputing the Merkle path. This provides: -- **Tamper evidence**: If a local entry is modified after publication, the Merkle root won't match. +- **Tamper evidence**: If a local entry is modified after publication, the Merkle root won't match — *provided the root was preserved somewhere the operator does not control*. - **Selective disclosure**: The auditor can verify one entry without seeing the rest of the batch. -- **Non-repudiation**: The operator cannot deny a committed transaction. +- **Consistency, not non-repudiation** (*v1.0.1 wording*): the operator cannot present a disclosed entry that contradicts a root it published. The reference implementation's "public ledger" is an in-memory Python list (`amt_extensions.py`, `PublicLedger.commitments`); commitments are unsigned and not anchored to any party, timestamp, or chain outside the operator. It therefore cannot prove that a recorded interaction happened, that the executor reported it honestly, that omitted interactions do not exist, or that the whole history was not regenerated before presentation (Overmier 2026, "A Merkle Root Is a Witness, Not a Judge"). Non-repudiation requires signing and external anchoring — v2. ### Connection to Cube Protocol S4 @@ -233,7 +237,7 @@ The Merkle root is a one-way function over the concatenated hashes of individual --- -## 5. The Scenario: Three Orgs, One Agent, Zero Trust +## 5. The Scenario: Three Orgs, One Agent, No Shared Secrets After Deployment ### Organization Setup @@ -473,7 +477,7 @@ We have demonstrated that the AMT conservation law provides cross-organizational 5. **Behavior is emergent**: Budget-constrained agents skip expensive organizational domains. This creates natural economic incentive structures without pricing protocols. -6. **The math is the accountability**: No trust, no protocol, no consensus mechanism, no identity provider. The conservation law holds because it is an algebraic identity, not because anyone agreed to follow it. +6. **The math is the arithmetic of accountability, not the whole of it** (*v1.0.1 wording; v1.0.0 read "No trust, no protocol, no consensus mechanism, no identity provider"*): the conservation identity holds because it is algebra, not because anyone agreed to follow it, and each organization can recompute its own consumption without a shared intermediary *for that arithmetic*. What remains assumed is the honesty of each executor (§9) — the identity cannot detect an environment that lies about what it consumed, clones the agent, or omits an interaction. Cross-party non-repudiation needs a signed, externally anchored witness, which this reference implementation does not provide. In an era where multi-party agent deployments are becoming standard — agents traversing cloud providers, compliance validators, data processors, and result aggregators — the question of accountability is usually answered with more protocols. We propose answering it with less: a single conservation law, enforced by construction, invariant across all boundaries. diff --git a/paper/marketplace.md b/paper/marketplace.md index b466bd4..7a85134 100644 --- a/paper/marketplace.md +++ b/paper/marketplace.md @@ -9,7 +9,7 @@ ## Abstract -When multiple agents compete for finite environmental resources under AMT's conservation law, population ecology emerges. We present a marketplace extension where nodes have finite resource pools that regenerate over time, and a nutrient cycling mechanism converts dead agents' consumed mass into new layers for survivors. The conservation law `C_{n+1} + S_{n+1} + L_n = C_n` governs every interaction — it is never modified. Resource pools are environmental capacity (not agent mass) that gate accretion. Nutrient cycling creates new mass through the factory (the only legitimate mass source). We demonstrate with 200 agents competing across 5 marketplace nodes: carrying capacity stabilizes at 4-6 agents, nutrient cycling achieves 77.3% recycling rate, and niche differentiation emerges from key class composition alone. Zero conservation violations across 2,095 interactions. The conservation law is the ecosystem. +When multiple agents compete for finite environmental resources under AMT's conservation law, population-level dynamics emerge. We present a marketplace extension where nodes have finite resource pools that regenerate over time, and a nutrient cycling mechanism converts consumed mass into new layers for survivors. The conservation law `C_{n+1} + S_{n+1} + L_n = C_n` governs every interaction — it is never modified. Resource pools are environmental capacity (not agent mass) that gate accretion. Nutrient cycling creates new mass through the factory (the only legitimate mass source). We demonstrate with 200 agents competing across 5 marketplace nodes: the population declines monotonically to a tail of 2–5 agents, nutrient cycling embodies 29.3 % of captured nutrients as new payload (v1.0.1 accounting; v1.0.0 reported 77.3 % by counting budget rather than accreted mass), and the two predefined agent profiles survive at different rates. Zero conservation violations across 1,942 interactions (v1.0.1 reference run). *v1.0.1 wording:* the model has no reproduction, so carrying capacity, boom/recovery, and speciation are not observable in it; what is observed is differential depletion of a fixed cohort. --- @@ -29,7 +29,7 @@ A marketplace is a topology where: - **Dead agents' mass recycles** (the food chain) - **Competition is implicit** (shared resources, not explicit conflict) -No agent "knows" about other agents. Each agent simply interacts with nodes, losing mass per the conservation law. The population dynamics — carrying capacity, boom/bust, speciation — emerge from this micro-level physics. +No agent "knows" about other agents. Each agent simply interacts with nodes, losing mass per the conservation law. The population dynamics that *can* emerge in a model without births — differential depletion, resource contention, recycling — emerge from this micro-level physics. (v1.0.0 listed "carrying capacity, boom/bust, speciation" here; see §5 errata.) --- @@ -52,7 +52,9 @@ Resources regenerate passively over time: current = min(capacity, current + regeneration_rate × elapsed_time) ``` -This models natural resource renewal — a grazing field regrows, a power supply recharges, a compute cluster becomes available again. The regeneration rate determines the environment's carrying capacity. +This models natural resource renewal — a grazing field regrows, a power supply recharges, a compute cluster becomes available again. The regeneration rate bounds how often accretion can occur. + +*v1.0.1 erratum — one-tick clock offset.* In v1.0.0 a pool's clock basis (`last_tick`) defaulted to wall-clock time while the simulation ticked from `t = 0`, so the first tick's regeneration was lost (elapsed clamped to zero) before the clock rebased; every later tick regenerated normally — 29 of 30 ticks in the reference run. The reviewer's single-tick probe therefore read zero and concluded regeneration "does not occur"; it does, minus one tick (Overmier 2026, marketplace section; verification item 16). v1.0.1 puts pools on the simulation clock when they join a topology. ### 2.3 Depletion Dynamics @@ -86,13 +88,13 @@ if accumulated >= cycle_threshold: accrete(next_agent, new_layers) ``` -The factory is the ONLY legitimate mass source. This is critical: conservation holds because: +The factory is the ONLY legitimate mass source. Per-interaction conservation holds because: 1. Agent A loses mass via `interact()` (conservation verified ✓) 2. NutrientCycler captures a fraction of that mass 3. Factory creates new layers from the captured budget 4. Agent B accretes new layers (separate from conservation) -No mass is created from nothing. Dead agents' mass flows to living agents through the factory. +*v1.0.1 erratum — there is no system-level mass equation.* v1.0.0 said here "No mass is created from nothing." At the system boundary that is not what the code does: every layer the factory mints carries 28 bytes of fresh AES-GCM overhead that never came from a nutrient, and v1.0.0's cycler counted its whole budget as "cycled" while the node accreted at most the first three layers it generated. A 300-byte nutrient budget produced 356 bytes of new ciphertext with two key classes and 440 with five (Overmier 2026, items 17–18; reproduced). v1.0.1 counts as cycled only the payload bytes actually accreted, keeps the rest in the pool, and *reports* the minted overhead (§6.3) — it does not enforce a global equation tying created mass to lost mass. Writing that equation is v2 work. ### 3.3 The Food Chain @@ -128,7 +130,7 @@ Our reference marketplace has five nodes: ### 4.2 Hazard Differentiation -Nodes with more key classes are more hazardous — they strip more layer types per visit. The Hunting Ground holds both alpha and beta secrets, stripping both layer types simultaneously. This makes it the deadliest node (54 deaths in our scale run) but also the most nutritious (highest nutrient cycling). +Nodes with more key classes are more hazardous — they strip more layer types per visit. The Hunting Ground holds both alpha and beta secrets, stripping both layer types simultaneously. In the v1.0.1 reference run it is the second-deadliest node (51 deaths; the Water Hole's slow regeneration makes it deadliest at 102) and the most nutritious (25 accretions, the highest). The Shelter holds no keys. Agents pass through unstripped. It serves as a refugium — a safe resting point in an otherwise hazardous ecology. @@ -150,51 +152,40 @@ Hub-and-spoke with the Shelter as the central hub. Agents route through the Shel ## 5. Population Dynamics -### 5.1 Carrying Capacity - -Carrying capacity is the equilibrium population that a topology can sustain. It emerges from: -- Resource regeneration rate (how fast the environment recovers) -- Agent mass consumption rate (how fast agents deplete resources) -- Nutrient cycling efficiency (how much dead mass recycles) +*v1.0.1: this section is rewritten. The v1.0.0 headings were "Carrying Capacity", "Boom/Bust" and "Speciation". None of the three is observable in a model with no reproduction (§9), and the reviewer was right to say so (Overmier 2026, marketplace section; item 19). The ecological vocabulary is replaced by what the code measures.* -In our scale run (200 agents, 5 nodes), carrying capacity stabilizes at approximately 4-6 agents — about 2-3% of the initial population. This is not programmed. It emerges from conservation law + finite resources. +### 5.1 Tail Population (v1.0.0: "Carrying Capacity") -### 5.2 Boom/Bust +The implementation reports `carrying_capacity_estimate`, which is the mean of the last 30 % of alive-counts in the population trace (`PopulationTracker`). With no births the population can only stay level or decline, so this is a **tail average of a declining cohort**, not an equilibrium the topology sustains. In the v1.0.1 reference runs it is 4 agents (100-agent run) and the 200-agent run ends with 4 alive (2.0 %); across other seeds the estimate ranges 1–5. Whether a regenerating topology would sustain a population is a question the model cannot answer until agents can be born (§9). -When population exceeds carrying capacity: -1. **Boom**: Many agents, resources abundant, accretion supports growth -2. **Peak**: Resources deplete faster than regeneration -3. **Bust**: No accretion fuel, agents die in waves -4. **Recovery**: Resources regenerate, survivors stabilize +### 5.2 Monotonic Decline (v1.0.0: "Boom/Bust") -Our 100-agent run shows clear exponential decay: +The 100-agent reference run (v1.0.1, `AMT_SEED=42`): ``` Step 0: 100 alive -Step 5: 76 alive (24% die-off) -Step 10: 44 alive (56% total) -Step 15: 18 alive (82% total) -Step 20: 11 alive (89% total) -Step 38: 0 alive (complete extinction) +Step 4: 82 alive +Step 8: 49 alive +Step 12: 34 alive +Step 16: 17 alive +Step 20: 11 alive +Step 28: 5 alive +Step 36: 3 alive +end: 2 alive (2 % survival at 40 steps) ``` -The population crashes because nutrient cycling cannot keep pace with consumption. The ecosystem is overshoot-and-collapse. +This is monotonic decline from a starting maximum. There is no boom (the population never grows), no peak (the start is the peak by construction), and no recovery (nothing is born). What the run shows is that consumption outpaces the accretion that regenerating pools and nutrient cycling can fund; calling it "overshoot-and-collapse" imported a cycle the model cannot produce. v1.0.0's curve (76/44/18/11 → extinction at step 38) was one process-dependent draw; v1.0.1's is deterministic. -### 5.3 Speciation +### 5.3 Niche-Differential Survival (v1.0.0: "Speciation") -Agents with different key class compositions occupy different ecological niches: +Two **predefined** agent profiles are run in the mixed topology: -| Species | Composition | Best Habitat | Survival Rate | -|---------|------------|-------------|---------------| -| Alpha-heavy | 5-10 alpha, 1-2 beta, 1-2 gamma | Feeding A | 4.0% | -| Beta-heavy | 1-2 alpha, 5-10 beta, 1-2 gamma | Feeding B | 8.0% | +| Profile | Composition | v1.0.1 survival (of 25) | v1.0.0 | +|---------|------------|-------------|------| +| Alpha-heavy | 5-10 alpha, 1-2 beta, 1-2 gamma | 3 (12.0 %) | 1 (4.0 %) | +| Beta-heavy | 1-2 alpha, 5-10 beta, 1-2 gamma | 1 (4.0 %) | 2 (8.0 %) | -Beta-heavy agents survive longer because: -1. Feeding B has fewer deaths (10 vs 44 at Feeding A) -2. Beta layers are not stripped at the Shelter -3. The Hunting Ground strips both alpha and beta — alpha-heavy agents lose more there - -This is niche differentiation. Different mass profiles lead to different survival outcomes in different habitats. No agent "chooses" a niche — the conservation law routes them by mass-dependent behavioral choices. +The direction reversed between versions, and across other seeds the alpha-heavy count is 0, 1 or 2 — the reviewer saw 0/0 and then 2/1 on consecutive runs. What is measured is **differential survival of two hand-built profiles** in a topology whose key layout favours one of them; it is not speciation, because no profile can arise, spread, or be selected for. The demo's v1.0.0 text printed that each profile "thrives" in its niche regardless of the numbers; v1.0.1 states the measured result. Differential depletion by key class *is* demonstrated; that is the defensible claim. --- @@ -202,52 +193,58 @@ This is niche differentiation. Different mass profiles lead to different surviva ### 6.1 Scale Run (200 Agents) -| Metric | Value | -|--------|-------| -| Agents | 200 | -| Total interactions | 2,095 | -| Deaths | 194 | -| Survival rate | 3.0% | -| **Conservation violations** | **0** | -| Nutrient deposited | 48,539 B | -| Nutrient cycled | 37,530 B | -| Recycling rate | 77.3% | -| Total accretions | 62 | -| Processing speed | 3,389 agents/sec | +| Metric | v1.0.1 reference run | v1.0.0 | +|--------|-------|-------| +| Agents | 200 | 200 | +| Total interactions | 1,942 | 2,095 | +| Deaths | 196 | 194 | +| Survival rate | 2.0% | 3.0% | +| **Conservation violations** | **0** | **0** | +| Nutrient deposited | 48,757 B | 48,539 B | +| Nutrient cycled (embodied as payload) | 14,267 B | 37,530 B (counted budget, not accreted mass) | +| Recycling rate | 29.3% | 77.3% | +| New ciphertext minted (payload + overhead) | 16,871 B = 14,267 + 2,604 | not measured | +| Total accretions | 62 | 62 | + +*The v1.0.1 run is deterministic (`AMT_SEED=42`; the demo's behaviour RNG was unseeded in v1.0.0, so every run differed). Sampling other seeds gives 2,085–2,145 interactions and 29.2–29.8 % recycling. The v1.0.0 row is one draw from that unseeded distribution.* ### 6.2 Per-Node Analysis | Node | Interactions | Deaths | Accretions | Death Rate | |------|-------------|--------|-----------|-----------| -| Feeding A | 355 | 44 | 17 | 12.4% | -| Feeding B | 366 | 10 | 13 | 2.7% | -| Water Hole | 388 | 86 | 7 | 22.2% | -| Hunting Ground | 325 | 54 | 25 | 16.6% | -| Shelter | 661 | 0 | 0 | 0.0% | +| Feeding A | 316 | 35 | 17 | 11.1% | +| Feeding B | 336 | 8 | 13 | 2.4% | +| Water Hole | 348 | 102 | 7 | 29.3% | +| Hunting Ground | 328 | 51 | 25 | 15.5% | +| Shelter | 614 | 0 | 0 | 0.0% | + +The Water Hole is the deadliest node by rate because it has the slowest regeneration. The Hunting Ground has the most accretions (25) because its high nutrient cycling threshold (100) combined with multi-key stripping produces more nutrients per interaction. The Shelter accounts for 614 of 1,942 interactions (31.6 %). -The Water Hole is the deadliest node by rate (22.2%) because it has the slowest regeneration. The Hunting Ground has the most accretions (25) because its high nutrient cycling threshold (100) combined with multi-key stripping produces more nutrients per interaction. +### 6.3 Nutrient Cycling Efficiency (*rewritten in v1.0.1*) -### 6.3 Nutrient Cycling Efficiency +The 29.3 % recycling rate means that of all nutrient bytes captured from signal and loss, 29.3 % were embodied as payload in layers that reached a surviving agent. The rest either remains in nutrient pools (never reached a cycler's threshold, or was generated and not accreted under the three-layer-per-visit cap) or was the 70 % / 90 % of signal / loss that the cycler's ratios discard by design. -The 77.3% recycling rate means that of all mass deposited as nutrients, 77.3% was successfully cycled into new layers for surviving agents. The remaining 22.7% is "stuck" in nutrient pools that haven't reached their cycling threshold — unrealized potential, waiting for more deposits. +v1.0.0 reported 77.3 % here. That figure counted the cycler's whole budget as "cycled" the moment it cycled, while the node accreted at most three of the layers generated (Overmier 2026, item 17). It measured intent, not delivery. + +**System-level mass, measured not enforced.** The factory minted 16,871 B of new ciphertext in this run: 14,267 B of payload drawn from nutrients plus 2,604 B of AES-GCM overhead (28 B × 93 layers) that came from nowhere. The per-interaction identity `C_{n+1} + S_{n+1} + L_n = C_n` is untouched by this — it governs consumption, not creation — but there is no equation in v1.0.x tying created mass to lost mass across the whole system. Writing one (factory, accretion, discarded layers, overhead, environmental pools) is the reviewer's recommendation #7 and is v2 work. --- -## 7. Ecological Properties +## 7. Observed Properties (*v1.0.1: "Ecological Properties", rewritten*) -All of the following are emergent from conservation law + finite resources. None are programmed: +The following are observed in the reference runs. They arise from the conservation identity plus the mechanisms this extension adds — finite regenerating pools, a nutrient cycler with fixed retention ratios, accretion capped at three layers per visit, mass-dependent routing, and two hand-built agent profiles — so none is attributable to the conservation law alone (Overmier 2026: "the observed behavior does not arise from one constraint"). -### 7.1 Carrying Capacity -The population stabilizes at a level determined by regeneration rate / consumption rate. Higher regeneration → higher carrying capacity. +### 7.1 Tail Population +A declining cohort's alive-count settles into a tail (1–5 agents across seeds) whose level depends on regeneration and consumption rates. Without births this is not a carrying capacity (§5.1). -### 7.2 Trophic Levels -Signal flows from agents (consumers) to environments (decomposers) and back to agents (producers via accretion). This is a simplified trophic web. +### 7.2 Recycling Loop +Signal and loss flow from agents to nutrient cyclers and back to agents via factory-minted layers. A simplified trophic loop — with the caveat that the minted layers carry overhead the loop did not supply (§6.3). -### 7.3 Competitive Exclusion -In our speciation demo, alpha-heavy and beta-heavy agents compete for different resources. Given enough time, one type dominates each niche — this is Gause's competitive exclusion principle, emergent from mass physics. +### 7.3 Differential Depletion by Profile +Alpha-heavy and beta-heavy profiles lose mass at different nodes and survive at different rates. This is not competitive exclusion: no profile can grow, and the direction of the difference flips between runs (§5.3). Gause's principle is not invoked in v1.0.1. -### 7.4 Resource Partitioning -The Shelter (no keys) serves as a neutral zone. Agents spend disproportionate time there (661 of 2,095 interactions = 31.6%) because it's safe. This is behavioral resource partitioning. +### 7.4 Route Concentration at the Safe Node +The Shelter (no keys) serves as a neutral zone. Agents spend disproportionate time there (614 of 1,942 interactions = 31.6 %) because mass-dependent routing prefers low-hazard nodes. This is a behavioural consequence of the routing rule and the key layout. --- @@ -260,7 +257,7 @@ NetLogo, GAMA, and other ABM platforms model population dynamics with explicit r Blockchain-based token economies (DeFi, NFTs) use scarcity to create market dynamics. AMT provides scarcity through conservation law enforcement — no consensus mechanism, no smart contracts, just physics. ### Digital Ecology -Artificial Life research (Tierra, Avida) simulates evolution in digital environments. AMT's nutrient cycling is analogous to Tierra's reaper queue, but AMT's conservation law provides formal guarantees that Tierra's heuristics cannot. +Artificial Life research (Tierra, Avida) simulates evolution in digital environments. AMT's nutrient cycling is analogous to Tierra's reaper queue; unlike Tierra, v1.0.x has no reproduction and therefore no evolution to observe (§9). The per-interaction accounting identity is explicit and asserted; it is not a guarantee over the whole system's mass (§6.3). --- @@ -270,7 +267,10 @@ Artificial Life research (Tierra, Avida) simulates evolution in digital environm Our current simulation handles hundreds of agents. Scaling to thousands would require parallel processing (as demonstrated in `amt_scale.py`). ### Reproduction -The current model has no explicit reproduction. Nutrient cycling creates mass for existing agents but doesn't create new agents. Adding reproduction (where well-fed agents spawn children with subset layers) would complete the ecological model. +The current model has no explicit reproduction. Nutrient cycling creates mass for existing agents but doesn't create new agents. Adding reproduction (where well-fed agents spawn children with subset layers) is the prerequisite for any claim of carrying capacity, boom/recovery, or speciation (§5); without it the model is a declining cohort by construction. + +### Global Mass Equation +The per-interaction identity governs consumption. Creation (factory, accretion, minted overhead) and environmental pools are not tied to it by any equation. Writing and enforcing one is v2 work (§6.3; Overmier 2026, recommendation #7). ### Predation Agent-to-agent interactions (one agent "consuming" another) are not yet modeled. This would create explicit trophic levels and predator-prey dynamics. @@ -282,21 +282,19 @@ The current topology is fixed. Real ecosystems have habitat creation and destruc ## 10. Conclusion -We have demonstrated that AMT's conservation law, combined with finite environmental resources and nutrient cycling, produces a complete population ecology: - -- **Carrying capacity** emerges from regeneration/consumption balance -- **Boom/bust dynamics** emerge from overshoot of carrying capacity -- **Niche differentiation** emerges from key class composition -- **Nutrient cycling** recycles 77.3% of dead agent mass to survivors -- **Competition** is implicit — agents never interact with each other, only with shared environments +We have demonstrated that AMT's conservation law, combined with finite environmental resources and nutrient cycling, produces a **declining-cohort agent-based model** with the following measured properties (*v1.0.1 wording; v1.0.0 claimed "a complete population ecology"*): -200 agents, 2,095 interactions, zero conservation violations. +- **A tail population** of a few agents, determined by regeneration/consumption balance — not a carrying capacity, because nothing is born +- **Monotonic decline** from the starting maximum — not boom/bust, because the population never grows +- **Differential survival** of two predefined profiles by key-class composition — not speciation, because no profile can arise or spread +- **Nutrient cycling** that embodies 29.3 % of captured nutrients as new payload for survivors (v1.0.0's 77.3 % counted budget, not delivery), while minting fresh overhead the system does not account for +- **Implicit competition** — agents never interact with each other, only with shared environments -No birth rules. No death rules. No population caps. No resource allocation algorithms. No competition protocols. +200 agents, 1,942 interactions, zero conservation violations (v1.0.1 reference run). -Just conservation. +No birth rules. No death rules. No population caps. No resource allocation algorithms. No competition protocols. Finite pools, a cycler, an accretion cap and a routing rule — all of which shape the result alongside conservation. -The math is the ecosystem. +Boom-and-recovery, a stable carrying capacity and speciation are the properties an ecology would need to show; this model cannot show them until it has reproduction (§9). That is the honest boundary of what v1.0.x demonstrates. --- diff --git a/paper/physical_iot.md b/paper/physical_iot.md index 4dc89fd..a1c4ab9 100644 --- a/paper/physical_iot.md +++ b/paper/physical_iot.md @@ -52,13 +52,25 @@ Each physical resource type maps to an AMT key class via a fixed conversion fact mass_bytes = physical_units × conversion_factor ``` -This mapping is bidirectional: +The mapping can be read back: ``` physical_units = mass_bytes / conversion_factor ``` -An agent with 50,000 bytes of alpha mass has a 50 Wh battery budget (50,000 / 1000). An agent with 100 bytes of beta mass has a 100 MB bandwidth budget (100 / 1). The conversion factors are design decisions frozen at system setup — they calibrate the relationship between abstract mass and physical reality. +An agent with 50,000 bytes of alpha mass reads as a 50 Wh battery budget (50,000 / 1000). The conversion factors are design decisions frozen at system setup — they calibrate the relationship between abstract mass and physical reality. + +*v1.0.1 erratum — the round trip is not numerically invertible.* v1.0.0 called the mapping "bidirectional". Encoding a budget into layers quantises it (256-byte layers, 512-byte payload cap, an 80/20 data/empty split) and every layer adds 28 bytes of AES-GCM overhead that the decoder counts as mass. Reading the paper's §5.1 budget back through the implementation's own interpreter gives: + +| Resource | Requested | Decoded | Error | +|---|---:|---:|---:| +| Battery | 50 Wh | 45.396 Wh | −9.2 % | +| Bandwidth | 100 MB | 128 MB | +28.0 % | +| Storage | 500 ops | 453.96 ops | −9.2 % | +| CPU | 30 s | 26.984 s | −10.1 % | +| Sensors | 50 reads | 43.82 reads | −12.4 % | + +(Overmier 2026, physical-IoT section; reproduced in `tests/test_known_limitations.py` and printed by `amt_physical_iot_demo.py`.) The errors are deterministic functions of the encoding and could be calibrated out; v1.0.x does not do so. ### 2.2 Layer Composition as Resource Allocation @@ -85,7 +97,7 @@ profile = {"alpha": 45000, "beta": 80, "gamma": 30000, "delta": 10000, "epsilon" → battery: 45.0 Wh, bandwidth: 80.0 MB, storage: 300 ops, cpu: 20.0 s, sensor: 30 reads ``` -An external observer seeing the agent's mass profile knows exactly what physical resources remain — without knowing the agent's identity, intent, or history. +An external observer seeing the agent's mass profile can estimate what physical resources remain — to within the encoding error tabulated in §2.1 — without knowing the agent's identity, intent, or history. --- @@ -132,7 +144,9 @@ Mass gates are computed from battery state: | < 20% | 500 KB | | Shore power | Unlimited | -As battery depletes at a location (from previous agents consuming it), the mass gate tightens. Heavier agents get blocked. This creates natural load-shedding without any load-balancing algorithm. +As battery depletes at a location (from previous agents consuming it), the mass gate tightens. Heavier agents get blocked. This would create natural load-shedding without any load-balancing algorithm. + +*v1.0.1 erratum — not exercised by the reported experiments.* Two things kept this mechanism from ever acting in v1.0.0. First, the scale demo rebuilt the five-location topology for every agent, so no agent saw a battery another agent had drawn down (Overmier 2026, item 13; fixed in v1.0.1 — one topology is shared). Second, and still true in v1.0.1: the tightest gate is 500 KB (512,000 B) and the heaviest agent either experiment can build is about 107 KB (the §5.1 agent is 106,603 B; the scale demo's maximum is 94,461 B), so the gate cannot bind for any agent in this configuration. The demo now reports the gate-blocked count, which is 0 of 750 visits. Depletion is demonstrated; gating is not. Making the gate bind needs heavier agents or lower thresholds — an experiment-design change deferred to v2. --- @@ -207,7 +221,7 @@ Notice: mountain strips zero layers because the agent already lost all its alpha | Total layers stripped | 32,486 | | Conservation violations | **0** | -Every agent completes the full 5-location route. Resource consumption varies by budget size but conservation holds universally. +Every agent enters all five locations and none survives the route; no agent is blocked by a mass gate (see §3.3 erratum). Resource consumption varies by budget size but the accounting identity holds universally. The v1.0.0 demo printed "Battery budget directly predicts survival distance" after this table; the measured survival distance was 5.0 locations in every battery tier, so the sentence was not supported by the run (Overmier 2026, item 15). v1.0.1 derives that line from the measured bins. These totals are deterministic (the demo is seeded) and unchanged by the shared-topology fix. --- @@ -269,9 +283,9 @@ This is exactly the travel route modeled in our demo. Real-time connectivity tra **Unified accounting**: One conservation law governs all resource types. No separate power manager, bandwidth throttler, and storage quota system. -**Physical enforcement**: Conservation prevents overconsumption by construction. An agent with zero alpha mass cannot consume battery — not because a policy blocks it, but because there are no layers to decrypt. +**Physical enforcement**: Conservation prevents overconsumption *of the simulated budget* by construction. An agent with zero alpha mass cannot consume battery — not because a policy blocks it, but because there are no layers to decrypt. Nothing in v1.0.x couples that debit to a measured physical quantity; this is a resource-labelling simulation until §6 is built and calibrated (§7.2). -**Auditability**: Every watt-hour, megabyte, CPU-second, and sensor read is accounted for via merkle-anchored ledger. Perfect resource accounting without surveillance. +**Auditability**: Every watt-hour, megabyte, CPU-second, and sensor read is accounted for in the local ledger and committed by Merkle root. Complete resource accounting without surveillance — under the honest-environment assumption, and with the ledger caveats of Walker 2026a §3 (v1.0.1 wording). ### 7.2 Limitations @@ -305,7 +319,7 @@ AMT's conservation law enforces physical resource limits through mass physics. B No power management daemon. No bandwidth throttler. No storage quota. No CPU governor. Just conservation. -At population scale (150 agents, 750 interactions), zero conservation violations occur. The conservation law is the resource manager. The math is the physics. The physics is the budget. +At population scale (150 agents, 750 interactions), zero conservation violations occur. What v1.0.x demonstrates is a unified resource-budget *interface* with exact byte accounting over a simulated budget; the mass gate that would do the load-shedding is implemented but never binds in the reported configuration, and the encode/decode mapping carries a 9–28 % error (§2.1). Becoming a resource manager requires measured coupling to real discharge, transfer, compute and device state, with error bounds — §6 remains future work. *(Conclusion narrowed in v1.0.1.)* --- diff --git a/paper/token_economy.md b/paper/token_economy.md index 5d02886..ee24071 100644 --- a/paper/token_economy.md +++ b/paper/token_economy.md @@ -9,7 +9,7 @@ ## Abstract -When an autonomous agent calls a tool through a gateway, how should it be charged? Traditional approaches — API keys with rate limits, credit-based billing, token metering — all require external enforcement: a billing service, a rate limiter, a credit ledger. We present an alternative where every tool call is a conservation-governed payment. Under Agent Mass Theory (AMT), an agent's mass consists of encrypted layers organized by key class. A tool gateway holds secrets for exactly one key class. When an agent enters the gateway, layers of that class are decrypted and consumed — this decryption IS the payment. The conservation law `C_{n+1} + S_{n+1} + L_n = C_n` ensures that mass cannot be created, double-spent, or inflated. An agent without layers of the required class simply cannot pay, and the conservation law — not a policy engine — prevents the call. We demonstrate this with five tool classes at five price points (API calls: 1 layer, LLM inference: 3 layers, storage: 2 layers, compute: 4 layers, admin: 1 layer), showing that budget-constrained agents are physically unable to call expensive tools, that behavioral divergence emerges from budget composition alone, and that at population scale (200 agents, 2,113 tool calls), zero conservation violations occur. No pricing protocol. No credit system. No rate limiter. The physics IS the pricing. +When an autonomous agent calls a tool through a gateway, how should it be charged? Traditional approaches — API keys with rate limits, credit-based billing, token metering — all require external enforcement: a billing service, a rate limiter, a credit ledger. We present an alternative where every tool call is a conservation-governed payment. Under Agent Mass Theory (AMT), an agent's mass consists of encrypted layers organized by key class. A tool gateway holds secrets for exactly one key class. When an agent enters the gateway, layers of that class are decrypted and consumed — this decryption IS the payment. The conservation law `C_{n+1} + S_{n+1} + L_n = C_n` ensures that no mass is created outside the factory and that every consumed byte is accounted for. (v1.0.1 erratum: it does not, by itself, prevent double-spending — a copied agent holds copyable ciphertext; see §8.2.) An agent without layers of the required class simply cannot pay, and the gateway's price check — a rule layered on the conservation law, not the law itself — refuses the call. We demonstrate this with five tool classes at five price points (API calls: 1 layer, LLM inference: 3 layers, storage: 2 layers, compute: 4 layers, admin: 1 layer), showing that budget-constrained agents are physically unable to call expensive tools, that behavioral divergence emerges from budget composition alone, and that at population scale (200 agents, 2,113 tool calls), zero conservation violations occur. No pricing protocol. No credit system. No rate limiter. The physics IS the pricing. --- @@ -46,8 +46,8 @@ A conservation law is not a protocol. It is a constraint that holds at every int If we map tool classes to key classes and require payment via layer consumption, we get a token economy where: - **Accounting** is a consequence of conservation (every byte accounted for) -- **Enforcement** is a consequence of physics (no layers = no payment = no tool call) -- **Trust** is unnecessary (the math enforces itself) +- **Enforcement** has two parts: no layers = no payment (a consequence of key affinity), and *exactly the declared price is charged* (a gateway rule, enforced in code since v1.0.1 — v1.0.0 stripped every matching layer and never consulted the price) +- **Trust** in the arithmetic is unnecessary (each party can recompute it); trust in the *executor's honesty* is assumed — see §11 and Walker 2026a §9 --- @@ -122,23 +122,26 @@ These prices are not arbitrary. They are design decisions frozen into the agent' A **tool gateway** is an AMT environment that holds exactly one secret — for its tool class's key class. When an agent enters: -1. The environment attempts to decrypt all layers matching its key class -2. Matching layers are consumed (payment extracted) -3. The conservation law is verified: `mass_before = mass_after + signal + loss` -4. If layers were stripped: the tool call succeeds -5. If no layers matched (agent has no affinity): the call fails silently -6. If the agent dies during payment: the call fails +1. The gateway counts the agent's layers of its key class against the declared price (`base_cost`) +2. Fewer layers than the price: the call is refused as `insufficient_funds` **before** any layer is consumed +3. Otherwise exactly `base_cost` matching layers are consumed (payment extracted); further layers of the class survive +4. The conservation law is verified on the consumed layers: `mass_before = mass_after + signal + loss` +5. If the agent survives payment: the tool call succeeds +6. If no layers matched (agent has no affinity): the call fails silently, nothing charged +7. If the agent dies during payment: the call fails + +*v1.0.1 erratum.* In v1.0.0 the gateway stripped **every** layer of its class and reported success whenever at least one layer was removed and the agent survived; `base_cost` was never consulted. An agent holding one beta layer was charged one layer and recorded as a successful three-layer LLM call; an agent holding twelve was charged all twelve for one call (Overmier 2026, review items 3, 5–9). The list above describes the v1.0.1 implementation. ``` Gateway("GPT-4", key_class="beta", cost=3) holds: secret_beta - strips: all beta-class layers + strips: exactly 3 beta-class layers per call (refuses if fewer than 3 are held) ignores: alpha, gamma, delta, epsilon layers ``` ### 4.2 Composition, Not Inheritance -The gateway wraps a standard AMT `Node` via composition. It delegates to `node.process()` which calls `interact()`. The conservation law is enforced inside `interact()` — the gateway doesn't even need to check it. This is critical: **the gateway cannot cheat**. Even a malicious gateway implementation cannot violate conservation because the law is enforced at the interaction level, not the gateway level. +The gateway wraps a standard AMT `Node` via composition. It delegates to `node.process(agent, max_layers=base_cost)` which calls `interact()`. The conservation law is enforced inside `interact()` — the gateway doesn't need to check it. What the gateway *does* own is the price: counting held layers against `base_cost` and bounding consumption to it. A gateway that skipped that check would still satisfy conservation and still overcharge — which is exactly what v1.0.0 did. Conservation constrains *how consumed bytes are accounted for*; it does not constrain *how many are consumed*. (v1.0.0 said here that "the gateway cannot cheat"; that was true of the arithmetic and false of the price.) ### 4.3 The Call Log @@ -219,47 +222,54 @@ We implement the token economy with: ### 7.2 Individual Agent Scenarios -**Rich Agent** (36 layers, 2,208 B): Successfully calls Config (1 epsilon), Weather (7 alpha), GPT-4 (12 beta), Database (6 gamma), and dies paying for Batch Compute (8 delta). Conservation verified at every step: 5 interactions, 0 violations. +**Rich Agent** (36 layers, 2,208 B): Successfully calls Config (1 epsilon), Weather (1 alpha), GPT-4 (3 beta), Database (2 gamma) and Batch Compute (4 delta) — exactly the declared price each time — and finishes alive at 1,372 B with budget to spare. Conservation verified at every step: 5 interactions, 0 violations. *(v1.0.0 reported this agent being drained of 7, 12, 6 and 8 layers respectively and dying at Batch Compute; that was the gateway stripping every layer of its class, not the price.)* -**Budget Agent** (6 layers, 328 B): Passes through Config (no epsilon affinity — no charge), calls Weather (4 alpha layers), partially pays for GPT-4 (1 beta layer stripped, but needs 3 — partial payment, tool still fires), dies paying for Database (1 gamma). Only 2 successful calls versus the rich agent's 4. +**Budget Agent** (6 layers, 328 B): Passes through Config (no epsilon affinity — no charge), calls Weather (1 alpha), is **refused** at GPT-4 (holds 1 beta, price 3 — `insufficient_funds`, nothing consumed), refused at Database (holds 1 gamma, price 2), and has no affinity for Batch Compute. One successful call versus the rich agent's five; it survives with its unspent layers. *(v1.0.0 recorded the one-layer GPT-4 payment as a success and then printed that the agent "couldn't afford" it — the log and the prose disagreed. Overmier 2026, item 7.)* ### 7.3 Population Scale 200 agents with Pareto-distributed budgets (reflecting real-world wealth distribution): -| Metric | Value | -|--------|-------| -| Total agents | 200 | -| Total tool calls | 2,113 | -| Successful calls | 449 | -| Survival rate | 45.5% | -| **Conservation violations** | **0** | - -**Tool usage by class:** - -| Class | Calls | Mass Consumed | -|-------|-------|---------------| -| admin | 53 | 14,196 B | -| api_call | 109 | 19,872 B | -| compute | 74 | 11,036 B | -| llm_inference | 115 | 23,436 B | -| storage_op | 98 | 17,144 B | - -**Budget-behavior correlation:** -- Rich agents (>1,000 B): average 3.8 successful calls -- Poor agents (≤500 B): average 1.8 successful calls -- Rich agents make **2.1x** more successful tool calls +| Metric | v1.0.1 reference run | v1.0.0 | +|--------|-------|-------| +| Total agents | 200 | 200 | +| Total tool calls (all outcomes) | 2,574 | 2,113 | +| Successful calls | 468 | 449 | +| Refused — insufficient funds | 728 | (not an outcome in v1.0.0) | +| No affinity | 1,373 | — | +| Died paying | 5 | — | +| Survival rate | 97.5% (195) | 45.5% (91) | +| **Conservation violations** | **0** | **0** | + +*The v1.0.1 column is deterministic across processes (hash-seed-independent neighbour order). The v1.0.0 column was one draw: the reviewer observed 2,090–2,206 calls and 100–106 survivors across three `PYTHONHASHSEED` values; the author 2,093–2,125 and 88–107. Survival rose because the gateway no longer drains every layer of its class.* + +**Tool usage by class (successful calls, v1.0.1):** + +| Class | Calls | Mass Consumed | v1.0.0 calls / mass | +|-------|-------|---------------|------| +| admin | 97 | 6,364 B | 53 / 14,196 B | +| api_call | 211 | 13,620 B | 109 / 19,872 B | +| compute | 23 | 6,096 B | 74 / 11,036 B | +| llm_inference | 60 | 11,200 B | 115 / 23,436 B | +| storage_op | 77 | 9,240 B | 98 / 17,144 B | + +**Budget-behavior correlation (v1.0.1):** +- Rich agents (>1,000 B): average 10.0 successful calls (v1.0.0: 3.8) +- Poor agents (≤500 B): average 1.0 successful calls (v1.0.0: 1.8) +- Rich agents make **9.8x** more successful tool calls (v1.0.0: 2.1x). The gap widened because refused payments leave poor agents' layers unspent and unusable, while rich agents are no longer drained to death. ### 7.4 Conservation Proof Every tool call is a conservation-governed interaction. At population scale: ``` -For all 2,113 tool calls: +For all 2,574 tool calls (v1.0.1; 2,113 in v1.0.0): C_{n+1} + S_{n+1} + L_n = C_n Verified. Zero violations. ``` +This is an accounting identity (Walker 2026 Appendix B): `loss` is defined as the consumed mass not returned as signal, so the equation cannot fail in a correct implementation. Zero violations confirms the bookkeeping, not a law of nature (*v1.0.1 wording*). + The conservation law holds regardless of: - Agent budget level - Tool class @@ -277,9 +287,13 @@ The token economy exhibits five fundamental economic properties, all emerging fr Conservation prevents mass creation. The total mass in the system can only decrease (via signal extraction and loss) or remain constant (when no interactions occur). New mass enters only through the factory — the monetary authority — which creates agents with specific layer compositions. There is no mechanism for agents, gateways, or any system component to create mass ex nihilo. -### 8.2 No Double-Spending +### 8.2 Local Destruction, Not Double-Spend Resistance + +*Rewritten in v1.0.1; the v1.0.0 heading was "No Double-Spending" and the paragraph was wrong as written.* + +When a layer is decrypted, the reference implementation removes it from the agent's layer list. Within one honest process the agent cannot present that layer again, and the plaintext becomes signal (if non-empty) or loss (if empty/overhead). That is all AES-256-GCM gives: confidentiality and integrity of the bytes. It does not give a byte string uniqueness, custody, or one-time existence. The ciphertext is ordinary data; a copy of the agent taken before decryption still holds it, and a gateway that receives the copy will accept the same payment a second time. The reference implementation demonstrates this directly (`tests/test_known_limitations.py`, `docs/reviews/2026-08-22-overmier-v1.0.0/`). Walker 2026a §9 already listed "clone the agent (copy layers before decrypting)" as a move available to a dishonest environment; this section previously claimed the opposite. -Layers are destroyed upon decryption. An AES-256-GCM ciphertext, once decrypted, ceases to exist as a layer. The plaintext becomes signal (if non-empty) or loss (if empty/overhead). The encrypted form is gone. An agent cannot present the same layer twice because after the first presentation, the layer no longer exists. +Preventing replay requires an authority outside the encryption scheme that can answer whether a specific capability is valid *now* and whether it has already been consumed — a transactional ledger, signed state, secure hardware, or a settlement protocol. That is v2 work, with its own threat model. The v1.0.x claim is narrower: **destruction is irreversible within the holder's process, and every byte consumed is accounted for.** ### 8.3 Perfect Audit @@ -294,13 +308,13 @@ No observer can determine: ### 8.4 Emergent Pricing -Per-key pricing is structural, not transactional. A 3-layer LLM call costs exactly 3 layers of the appropriate key class. This cost does not fluctuate with demand, time of day, or agent identity. +Per-key pricing is structural, not transactional. A 3-layer LLM call costs exactly 3 layers of the appropriate key class — enforced by the gateway since v1.0.1 (`ToolGateway.process_tool_call`; asserted in `tests/test_price_enforcement.py`). This cost does not fluctuate with demand, time of day, or agent identity. However, per-visit pricing is dynamic. An environment's total cost is determined by how many key classes it holds — its hazard surface. Under load, an environment can arm itself with additional keys, stripping more layer types per visit without changing the cost of any individual key class. This creates a natural throttle: fixed unit prices with dynamic aggregate cost, governed entirely by key rotation rather than rate negotiation. ### 8.5 Budget Stratification -An agent's tool access is determined entirely by its layer composition. No access control list, permission matrix, or role-based policy intervenes. An agent with zero delta layers simply cannot call compute tools. An agent with 12 beta layers has exactly 4 LLM calls. This creates natural economic tiers without any tier-management infrastructure. +An agent's tool access is determined by its layer composition and the gateway's declared price. No access control list, permission matrix, or role-based policy intervenes. An agent with zero delta layers simply cannot call compute tools. An agent with 12 beta layers has exactly 4 LLM calls (true since v1.0.1; in v1.0.0 it had one call that cost all twelve). This creates natural economic tiers without any tier-management infrastructure — but note that the price check is a gateway rule; what the conservation law contributes is that the tiers cannot be inflated from inside the agent. --- @@ -332,7 +346,7 @@ LangChain's callback system and AutoGPT's budget tracking monitor agent spending ### Key Differentiator -All related systems are **protocols** — agreements about how accounting should work. AMT's token economy is a **conservation law** — a physical constraint on how accounting *must* work. The distinction: a protocol can be violated by a sufficiently creative adversary; a conservation law cannot, because it is enforced by the mathematical structure of the interaction itself. +All related systems are **protocols** — agreements about how accounting should work. AMT's token economy rests on an **accounting identity** — `loss` is defined as `mass − signal`, so the books balance by construction (Walker 2026 Appendix B: "It holds because subtraction works"). The distinction the v1.0.0 text drew — that a protocol can be violated and a conservation law cannot — is true of the arithmetic and only the arithmetic. The identity does not prevent a copied agent from paying twice, a gateway from ignoring its price, or an executor from lying about what it consumed; those are protocol-level properties and need protocol-level (or ledger-level) enforcement. *(Rewritten in v1.0.1.)* --- @@ -374,13 +388,13 @@ The natural next step is mapping this token economy onto Ravenhelm's Bifrost too We have demonstrated that conservation-governed agent mass provides a complete token economy without any pricing protocol, credit system, or rate limiter: -- **Tool calls are interactions.** A gateway is an environment. Payment is decryption. Conservation is enforcement. -- **Layers are currency.** Each key class is a denomination. Each tool class has a price in layers. +- **Tool calls are interactions.** A gateway is an environment. Payment is decryption. Conservation is the accounting; the price check is the enforcement. +- **Layers are currency.** Each key class is a denomination. Each tool class has a price in layers, charged exactly (v1.0.1). - **Budget is mass profile.** An agent's economic identity is its layer composition. - **Behavioral divergence is emergent.** Same topology, same parameters, different budgets → different tool usage patterns. -- **Conservation is absolute.** 200 agents, 2,113 tool calls, zero violations. +- **The accounting identity held on every call.** 200 agents, 2,574 tool calls (v1.0.1 reference run; 2,113 in v1.0.0), zero violations. -The math is the market. The physics is the pricing. No protocol needed. +What this does not provide, and v1.0.0 claimed it did: double-spend resistance across copies, and price enforcement without a gateway rule. See §8.2 and the v1.0.1 errata (`paper/ERRATA-v1.0.1.md`). --- @@ -401,6 +415,36 @@ Key classes: Population: 200 agents, Pareto-distributed budgets +v1.0.1 reference run (`python3 amt_token_economy_demo.py`, any `PYTHONHASHSEED`): + +``` +Agents: 200 +Total tool calls: 2,574 +Successful calls: 468 +Agents surviving: 195 (97.5%) +Conservation violations: 0 +Calls by outcome: + died_paying 5 + insufficient_funds 728 + no_affinity 1,373 + success 468 + +Tool Usage by Class: + Class Calls Mass + admin 97 6,364 + api_call 211 13,620 + compute 23 6,096 + llm_inference 60 11,200 + storage_op 77 9,240 + +Budget-Calls Correlation: + Rich agents (>1000B): avg 10.0 successful calls + Poor agents (<=500B): avg 1.0 successful calls + Rich agents make 9.8x more tool calls. +``` + +v1.0.0 reference run, retained for the record (one process-dependent draw): + ``` Agents: 200 Total tool calls: 2,113 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2e5cf71 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +[project] +name = "agentropy" +version = "1.0.1" +description = "Agentropy / Agent Mass Theory — reference implementation of a byte-accounting conservation identity for encrypted-layer agents, with four domain models and an ablation study." +readme = "README.md" +requires-python = ">=3.10" +license = { text = "MIT" } +authors = [{ name = "Nathan Walker" }] +dependencies = ["cryptography>=3.4.8"] + +[project.optional-dependencies] +dev = ["pytest>=7.0"] + +[project.urls] +Repository = "https://github.com/nwalker85/agentropy" +"Deposited artifact (v1.0.0)" = "https://doi.org/10.5281/zenodo.20818597" + +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = "-q" diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..9573d48 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,2 @@ +-r requirements.txt +pytest>=7.0 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..be1262b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +# Runtime dependency of the reference implementation. +# Verified range: the v1.0.0 review ran on cryptography 3.4.8 (Python 3.10.12); +# v1.0.1 was verified on 3.4.8, 46.0.4 and 50.0.1 (Python 3.10, 3.12, 3.14). +cryptography>=3.4.8 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..943bd29 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,7 @@ +"""Make the repository root importable so tests can import the amt_* modules.""" +import os +import sys + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if ROOT not in sys.path: + sys.path.insert(0, ROOT) diff --git a/tests/test_ablation_controls.py b/tests/test_ablation_controls.py new file mode 100644 index 0000000..e43964c --- /dev/null +++ b/tests/test_ablation_controls.py @@ -0,0 +1,55 @@ +"""v1.0.1 ablation controls (review items 20, 21): matched depletion rate, honest signal/loss.""" +import os +import random + +from amt_core import AgentFactory, Environment, LAYER_OVERHEAD +from amt_ablation import interact_random, interact_random_matched + + +def _setup(): + secrets = {k: os.urandom(32) for k in ["alpha", "beta", "gamma"]} + return secrets, AgentFactory(secrets) + + +def test_random_matched_removes_exactly_what_control_would(): + secrets, factory = _setup() + env = Environment("alpha", {"alpha": secrets["alpha"]}) + random.seed(3) + agent = factory.build_agent([("alpha", b"a" * 10)] * 4 + [("beta", b"b" * 10)] * 6) + result = interact_random_matched(agent, env) + assert result.layers_stripped == 4 + assert agent.layer_count == 6 + + +def test_random_signal_and_loss_follow_layer_geometry(): + secrets, factory = _setup() + env = Environment("alpha", {"alpha": secrets["alpha"]}) + random.seed(5) + agent = factory.build_agent([("alpha", b"a" * 50), ("beta", b""), ("gamma", b"g" * 20)]) + before = agent.mass + result = interact_random_matched(agent, env) # removes 1 layer, class-blind + assert result.layers_stripped == 1 + removed_mass = before - agent.mass + expected_signal = max(0, removed_mass - LAYER_OVERHEAD) + assert result.total_signal == expected_signal + assert result.total_loss == removed_mass - expected_signal + assert result.mass_before == result.mass_after + result.total_signal + result.total_loss + + +def test_unmatched_random_keeps_v1_rate_of_one_layer_per_key(): + secrets, factory = _setup() + env = Environment("alpha", {"alpha": secrets["alpha"]}) + random.seed(9) + agent = factory.build_agent([("alpha", b"a")] * 8) + result = interact_random(agent, env) + assert result.layers_stripped == 1 + + +def test_random_matched_is_control_for_pure_class_agents(): + """Documented consequence: the selectivity fixture cannot separate structure from depletion.""" + secrets, factory = _setup() + env = Environment("alpha", {"alpha": secrets["alpha"]}) + random.seed(11) + agent = factory.build_agent([("alpha", b"a" * 8)] * 6) + result = interact_random_matched(agent, env) + assert result.layers_stripped == 6 and not agent.alive diff --git a/tests/test_conservation.py b/tests/test_conservation.py new file mode 100644 index 0000000..3850504 --- /dev/null +++ b/tests/test_conservation.py @@ -0,0 +1,87 @@ +"""The conservation identity and the layer geometry it rests on (paper Appendix B). + +These tests make the paper's own statement executable: the identity holds because +`loss` is defined as `mass - signal`, and every layer carries exactly LAYER_OVERHEAD +bytes of nonce + tag. +""" +import os +import random + +import pytest + +from amt_core import Agent, AgentFactory, Environment, LAYER_OVERHEAD, interact + +CLASSES = ["alpha", "beta", "gamma", "delta", "epsilon"] + + +@pytest.fixture +def secrets(): + return {k: os.urandom(32) for k in CLASSES} + + +@pytest.fixture +def factory(secrets): + return AgentFactory(secrets) + + +def test_layer_geometry_is_overhead_plus_plaintext(factory): + for n in (0, 1, 17, 256, 1024): + layer = factory.create_layer("alpha", os.urandom(n)) + assert layer.mass == LAYER_OVERHEAD + n + + +def test_conservation_identity_holds_for_random_agents(factory, secrets): + rng = random.Random(1) + for _ in range(50): + specs = [ + (rng.choice(CLASSES), os.urandom(rng.choice([0, 0, 8, 64, 300]))) + for _ in range(rng.randint(1, 20)) + ] + agent = factory.build_agent(specs) + held = rng.sample(CLASSES, rng.randint(0, 5)) + env = Environment("e", {k: secrets[k] for k in held}) + before = agent.mass + result = interact(agent, env) + assert result.mass_before == before + assert result.mass_before == result.mass_after + result.total_signal + result.total_loss + # every stripped layer was of a class the environment holds + assert all(k in held for k in result.delta_L) + # and no surviving layer is of a held class + assert all(layer.key_class not in held for layer in agent.layers) + + +def test_signal_and_loss_match_geometry(factory, secrets): + agent = factory.build_agent([("alpha", b"x" * 40), ("alpha", b""), ("beta", b"y" * 10)]) + env = Environment("alpha-only", {"alpha": secrets["alpha"]}) + result = interact(agent, env) + assert result.layers_stripped == 2 + assert result.total_signal == 40 + assert result.total_loss == 2 * LAYER_OVERHEAD + assert agent.layer_count == 1 and agent.layers[0].key_class == "beta" + + +def test_max_layers_bounds_consumption_and_preserves_identity(factory, secrets): + agent = factory.build_agent([("beta", b"p" * 16)] * 12 + [("alpha", b"ballast")]) + env = Environment("beta", {"beta": secrets["beta"]}) + before = agent.mass + result = interact(agent, env, max_layers=3) + assert result.layers_stripped == 3 + assert sum(1 for l in agent.layers if l.key_class == "beta") == 9 + assert before == result.mass_after + result.total_signal + result.total_loss + + +def test_max_layers_none_strips_everything_matching(factory, secrets): + agent = factory.build_agent([("beta", b"p")] * 12) + env = Environment("beta", {"beta": secrets["beta"]}) + result = interact(agent, env, max_layers=None) + assert result.layers_stripped == 12 + assert not agent.alive + + +def test_mass_gate_blocks_without_consuming(factory, secrets): + agent = factory.build_agent([("alpha", b"x" * 100)]) + env = Environment("tiny", {"alpha": secrets["alpha"]}, mass_threshold=(0, 10)) + before = agent.mass + result = interact(agent, env) + assert not result.agent_could_enter + assert agent.mass == before diff --git a/tests/test_determinism.py b/tests/test_determinism.py new file mode 100644 index 0000000..bc2a13b --- /dev/null +++ b/tests/test_determinism.py @@ -0,0 +1,67 @@ +"""v1.0.1: seeded runs are identical across processes with different PYTHONHASHSEED (review item 24).""" +import os +import subprocess +import sys + +import pytest + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +SCRIPT = r""" +import random, os, sys +sys.path.insert(0, __ROOT__) +from amt_core import AgentFactory +from amt_extensions import Node, Topology, AgentBehavior +secrets = {k: bytes([i]) * 32 for i, k in enumerate(["alpha", "beta", "gamma", "delta"])} +factory = AgentFactory(secrets) +topo = Topology() +topo.add_node(Node(node_id="hub", name="hub", _key_secrets={})) +for k in ["alpha", "beta", "gamma", "delta"]: + topo.add_node(Node(node_id=k, name=k, _key_secrets={k: secrets[k]})) + topo.connect("hub", k) +topo.connect("alpha", "beta"); topo.connect("beta", "gamma"); topo.connect("gamma", "delta") +random.seed(7) +visits = [] +for i in range(40): + agent = factory.build_agent([(k, bytes(8)) for k in ["alpha", "beta", "gamma", "delta"] for _ in range(3)]) + behavior = AgentBehavior(risk_baseline=0.4, desperation_curve=2.0) + behavior.observe(agent) + cur = "hub" + for step in range(15): + node = topo.nodes[cur] + node.process(agent, factory) + behavior.observe(agent) + if not agent.alive: + break + nxt = behavior.choose_node(topo.reachable_from(cur), agent) + if nxt is None: + break + cur = nxt.node_id + visits.append(cur) +print(len(visits), ",".join(visits)) +""" + + +def _run(hash_seed: str) -> str: + env = dict(os.environ, PYTHONHASHSEED=hash_seed) + out = subprocess.run( + [sys.executable, "-c", SCRIPT.replace("__ROOT__", repr(ROOT))], + capture_output=True, text=True, check=True, env=env, cwd=ROOT, + ) + return out.stdout.strip() + + +@pytest.mark.parametrize("a,b", [("0", "1"), ("1", "2"), ("0", "12345")]) +def test_behavioral_traversal_is_hash_seed_independent(a, b): + assert _run(a) == _run(b) + + +def test_reachable_from_is_sorted(): + from amt_extensions import Node, Topology + topo = Topology() + for nid in ["zeta", "alpha", "mid"]: + topo.add_node(Node(node_id=nid, name=nid, _key_secrets={})) + topo.add_node(Node(node_id="hub", name="hub", _key_secrets={})) + for nid in ["zeta", "alpha", "mid"]: + topo.connect("hub", nid) + assert [n.node_id for n in topo.reachable_from("hub")] == ["alpha", "mid", "zeta"] diff --git a/tests/test_known_limitations.py b/tests/test_known_limitations.py new file mode 100644 index 0000000..453db80 --- /dev/null +++ b/tests/test_known_limitations.py @@ -0,0 +1,81 @@ +"""KNOWN LIMITATIONS of the v1.0.x reference implementation, made executable. + +These tests PASS when the limitation is PRESENT. They exist so that the limitation +is stated in code, next to the code, and so that whoever removes it in a later +version has to change a test on purpose. None of these is a bug fixed by v1.0.1; +each is scoped out of a patch release and belongs to a v2 design record. + +Source: Overmier open technical review of v1.0.0 (2026-08-22) and +docs/reviews/2026-08-22-overmier-v1.0.0/VERIFICATION-NOTES.md. +""" +import copy +import os + +import pytest + +from amt_core import AgentFactory, Environment, interact +from amt_extensions import PublicLedger +from amt_token_economy import STANDARD_TOOL_CLASSES, ToolGateway + +CLASSES = ["alpha", "beta", "gamma", "delta", "epsilon"] + + +@pytest.fixture +def secrets(): + return {k: os.urandom(32) for k in CLASSES} + + +@pytest.fixture +def factory(secrets): + return AgentFactory(secrets) + + +def test_known_limitation_copied_agent_pays_twice(factory, secrets): + """Review item 8. Consumption is a list reassignment inside one process; ciphertext + bytes copy like any bytes. Price enforcement (v1.0.1) does not change this: a + copy that holds the full price still pays. Uniqueness across copies requires an + authority outside the scheme (ledger, settlement, hardware) — v2.""" + gw = ToolGateway("llm", "LLM", STANDARD_TOOL_CLASSES["llm_inference"], "model") + gw.initialize(secrets) + original = factory.build_agent([("beta", b"p")] * 3 + [("alpha", b"ballast")]) + clone = copy.deepcopy(original) + a = gw.process_tool_call(original) + b = gw.process_tool_call(clone) + assert a["outcome"] == "success" and b["outcome"] == "success" + assert a["layers_charged"] == b["layers_charged"] == 3 + + +def test_known_limitation_key_class_is_plaintext_metadata(factory, secrets): + """Review item 2. An environment without the beta secret can still see that beta + layers exist; only their contents are hidden. cross_org_accountability.md:94 + said otherwise in v1.0.0 (corrected in the v1.0.1 errata).""" + agent = factory.build_agent([("alpha", b"a"), ("beta", b"b")]) + alpha_only = Environment("alpha", {"alpha": secrets["alpha"]}) + visible_classes = {layer.key_class for layer in agent.layers} + assert "beta" in visible_classes + result = interact(agent, alpha_only) + assert result.layers_stripped == 1 + assert [l.key_class for l in agent.layers] == ["beta"] + + +def test_known_limitation_public_ledger_is_a_mutable_list(): + """Review item 10. The reference 'public append-only ledger' is an in-memory + Python list; commitments are unsigned and unanchored. Anchoring/signing is v2.""" + ledger = PublicLedger() + assert isinstance(ledger.commitments, list) + ledger.commitments.clear() # nothing prevents this + assert ledger.commitments == [] + + +def test_known_limitation_physical_budget_round_trip_is_lossy(factory): + """Review item 12. GCM overhead and 256-B layer quantisation make encode/decode + non-invertible. The demo now prints the error table; the paper no longer says + 'bidirectional' without qualification.""" + from amt_physical_iot import ResourceBudget + b = ResourceBudget(battery_wh=50, bandwidth_mb=100, storage_ops=500, cpu_seconds=30, sensor_reads=50) + back = ResourceBudget.interpret_agent_mass(factory.build_agent(b.to_layer_specs())) + assert round(back["battery"], 3) == 45.396 + assert back["bandwidth"] == 128.0 + assert round(back["storage"], 2) == 453.96 + assert round(back["cpu"], 3) == 26.984 + assert round(back["sensor"], 2) == 43.82 diff --git a/tests/test_marketplace_accounting.py b/tests/test_marketplace_accounting.py new file mode 100644 index 0000000..648073f --- /dev/null +++ b/tests/test_marketplace_accounting.py @@ -0,0 +1,74 @@ +"""v1.0.1 marketplace fixes: regeneration clock (item 16) and nutrient accounting (items 17, 18).""" +import os + +import pytest + +from amt_core import AgentFactory, LAYER_OVERHEAD +from amt_marketplace import MarketplaceNode, MarketplaceTopology, NutrientCycler, ResourcePool + + +def test_first_simulation_tick_regenerates(): + pool = ResourcePool(capacity=100.0, current=0.0, regeneration_rate=10.0) + node = MarketplaceNode("n", "n", {"alpha": os.urandom(32)}, pool, NutrientCycler()) + topo = MarketplaceTopology() + topo.add_node(node) + seen = [] + for _ in range(4): + topo.tick(0.5) + seen.append(pool.current) + assert seen == [5.0, 10.0, 15.0, 20.0] + + +def test_reviewers_single_tick_of_ten_seconds_regenerates_to_capacity(): + """The reviewer's harness ticked once and read 0; v1.0.1 reads 100.""" + pool = ResourcePool(capacity=100, current=0, regeneration_rate=10) + node = MarketplaceNode("n", "N", {"alpha": os.urandom(32)}, pool) + topo = MarketplaceTopology() + topo.add_node(node) + topo.tick(10) + assert pool.current == 100 + + +def test_explicit_zero_time_is_a_valid_clock_value(): + pool = ResourcePool(capacity=100.0, current=0.0, regeneration_rate=10.0, last_tick=0.0) + pool.regenerate(0.0) + assert pool.current == 0.0 and pool.last_tick == 0.0 + pool.regenerate(2.0) + assert pool.current == 20.0 + + +def test_standalone_pool_first_call_only_sets_the_basis(): + pool = ResourcePool(capacity=100.0, current=0.0, regeneration_rate=10.0) + pool.regenerate(0.5) + assert pool.current == 0.0 + pool.regenerate(1.0) + assert pool.current == 5.0 + + +@pytest.fixture +def factory(): + return AgentFactory({k: os.urandom(32) for k in ["alpha", "beta", "gamma", "delta", "epsilon"]}) + + +def test_cycled_counts_only_what_is_returned(factory): + cycler = NutrientCycler(cycle_threshold=200) + cycler.deposit(signal=1000, loss=0) # 30 % retention -> 300 B budget + assert cycler.accumulated == 300 + specs = cycler.cycle(factory, ["alpha", "beta"], max_layers=3) + used = sum(len(p) for _, p in specs) + assert len(specs) <= 3 + assert cycler._total_cycled == used + assert cycler.accumulated == 300 - used + assert cycler._total_overhead_created == LAYER_OVERHEAD * len(specs) + assert cycler.total_ciphertext_created == used + LAYER_OVERHEAD * len(specs) + + +def test_minted_ciphertext_exceeds_nutrient_budget_by_overhead(factory): + """The reviewer's 300 B -> 356 B (two classes) figure, now labelled for what it is.""" + cycler = NutrientCycler(cycle_threshold=200) + cycler.deposit(signal=1000, loss=0) + specs = cycler.cycle(factory, ["alpha", "beta"]) + layers = [factory.create_layer(k, p) for k, p in specs] + minted = sum(l.mass for l in layers) + assert minted == cycler.total_ciphertext_created + assert minted - cycler._total_cycled == LAYER_OVERHEAD * len(layers) diff --git a/tests/test_price_enforcement.py b/tests/test_price_enforcement.py new file mode 100644 index 0000000..e932dea --- /dev/null +++ b/tests/test_price_enforcement.py @@ -0,0 +1,81 @@ +"""v1.0.1: the token gateway enforces its declared price (review items 3, 5, 6, 7, 9).""" +import os + +import pytest + +from amt_core import AgentFactory +from amt_token_economy import STANDARD_TOOL_CLASSES, ToolGateway + +CLASSES = ["alpha", "beta", "gamma", "delta", "epsilon"] + + +@pytest.fixture +def secrets(): + return {k: os.urandom(32) for k in CLASSES} + + +@pytest.fixture +def factory(secrets): + return AgentFactory(secrets) + + +@pytest.fixture +def llm_gateway(secrets): + gw = ToolGateway("llm", "LLM", STANDARD_TOOL_CLASSES["llm_inference"], "model") + gw.initialize(secrets) + return gw + + +def test_declared_price_is_three_layers(llm_gateway): + assert llm_gateway.tool_class.base_cost == 3 + + +def test_underfunded_payment_is_rejected_before_any_consumption(factory, llm_gateway): + agent = factory.build_agent([("beta", b"payment"), ("alpha", b"survival")]) + before = agent.mass + call = llm_gateway.process_tool_call(agent) + assert call["outcome"] == "insufficient_funds" + assert call["success"] is False + assert call["layers_charged"] == 0 + assert call["mass_charged"] == 0 + assert agent.mass == before + assert call["layers_held"] == 1 and call["declared_cost"] == 3 + + +def test_exact_price_is_charged_and_surplus_survives(factory, llm_gateway): + agent = factory.build_agent([("beta", f"p{i}".encode()) for i in range(12)] + [("alpha", b"s")]) + call = llm_gateway.process_tool_call(agent) + assert call["outcome"] == "success" + assert call["layers_charged"] == 3 + assert sum(1 for l in agent.layers if l.key_class == "beta") == 9 + + +def test_twelve_layers_buy_exactly_four_calls(factory, llm_gateway): + """The paper's sentence 'an agent with 12 beta layers has exactly 4 LLM calls' (token_economy.md §8.5).""" + agent = factory.build_agent([("beta", b"p")] * 12 + [("alpha", b"s")]) + outcomes = [llm_gateway.process_tool_call(agent)["outcome"] for _ in range(5)] + assert outcomes == ["success"] * 4 + ["no_affinity"] + + +def test_no_affinity_charges_nothing(factory, llm_gateway): + agent = factory.build_agent([("alpha", b"only-alpha")]) + before = agent.mass + call = llm_gateway.process_tool_call(agent) + assert call["outcome"] == "no_affinity" and call["layers_charged"] == 0 + assert agent.mass == before + + +def test_paying_the_exact_price_with_nothing_else_is_died_paying(factory, llm_gateway): + agent = factory.build_agent([("beta", b"p")] * 3) + call = llm_gateway.process_tool_call(agent) + assert call["outcome"] == "died_paying" + assert call["success"] is False + assert call["layers_charged"] == 3 + assert not agent.alive + + +def test_conservation_holds_on_every_recorded_call(factory, llm_gateway): + for spec in ([("beta", b"p")] * 5 + [("alpha", b"s")], [("beta", b"p")], [("gamma", b"g")]): + agent = factory.build_agent(spec) + call = llm_gateway.process_tool_call(agent) + assert call["mass_before"] == call["mass_after"] + call["signal"] + call["loss"]