From a35ed7af19ab09a18f7856860363bb1dc181bd00 Mon Sep 17 00:00:00 2001 From: Vuong Hoang Date: Fri, 3 Jul 2026 23:14:48 -0700 Subject: [PATCH] =?UTF-8?q?docs(diagnostics):=20R30=20gap-injection=20harn?= =?UTF-8?q?ess=20=E2=80=94=20banked=20(run=20closed=20on=20offline+face-va?= =?UTF-8?q?lidity)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deployed gap-injection run closed by operator steer 2026-07-04: R30 graduates on offline-tests + human face-validity, no deployed harness build, no endpoint. Harness (read/predict/record; write side stubbed) + brokkr's R30.10 protocol pin (de8357f) banked as drop-in for the parked powered true-tau perceptual study. predict() self-validated against brokkr's N=0 anchors (1h/10h/1wk). --- .../diagnostics/r30-gap-injection-findings.md | 64 ++++++++++ docs/diagnostics/r30-gap-injection-harness.py | 110 ++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 docs/diagnostics/r30-gap-injection-findings.md create mode 100644 docs/diagnostics/r30-gap-injection-harness.py diff --git a/docs/diagnostics/r30-gap-injection-findings.md b/docs/diagnostics/r30-gap-injection-findings.md new file mode 100644 index 0000000..57a3b56 --- /dev/null +++ b/docs/diagnostics/r30-gap-injection-findings.md @@ -0,0 +1,64 @@ +# R30 gap-injection — deployed-run harness (BANKED, not run) + +**Status:** CLOSED — not executed. On 2026-07-04 the operator steered **close R30 on +offline-tests + human face-validity**, no deployed harness build, no endpoint. No +`(agent, N, Δt, observed p/a/d)` series was collected. This harness + brokkr's protocol +are **banked / drop-in** for the parked powered true-τ perceptual study if it is ever +commissioned. + +**Spec pin:** brokkr R30.10 protocol `de8357f` — +`brokkr-smithy/research/R30-ocean-derived-mood-dynamics/empirics/r30-gap-injection-protocol.md`. + +## Why it was closed (not built) + +The deployed gap-injection run was **confirmatory, not measuring** (brokkr's §0 reframe: +against a deployed system the fade is `exp(−Δt/τ_shipped)` by construction, so a fit +returns τ_shipped tautologically — the run graduates the interim coefficients, it does not +measure them). Given that: + +- The repo's **offline tests already cover the OU formula + BOTH directions** + (`high_N_fades_slower_than_low_N` decay, `phenotype_high_n_bigger_negative_excursion` gain). +- **b17 is deployed-clean** on demo + personal. +- The ①-approved **mood-holds-within-conversation** IS the face-validity call. + +…the interim coefficients **graduate validated-as-shipped** with no deployed run needed. + +The build cost that would have been required (and was declined): + +- No deployed write affordance exists; the in-memory `_user_moods` cache **shadows** raw + `persona_mood.db` writes, so a clean inject needs an in-service **set-mood/back-date + endpoint** (set p/a/d + `updated_at` + evict cache). +- `moody-lofn N=+0.8` **does not exist** (only lofn `N=−0.5`; tier-3 empty OCEAN → N=0), so + the gain half would have needed a new high-N tier-1 agent + deploy — dropped as the + expensive, offline-redundant half. + +## What is validated (harness self-check) + +`harness.py::predict()` reproduces brokkr's stated N=0 P-axis retention anchors **exactly**: + +| Δt | predicted | brokkr stated | +|---|---|---| +| 1h (3600s) | 0.904837 | 0.905 | +| 10h/overnight (36000s) | 0.367879 | 0.368 | +| 1 week (604800s) | 5.06e-08 | 5e-08 | + +Per-axis τ confirms arousal fades ~1.9× faster (N=0: τ_P = τ_D = 10h, τ_A = 5.26h). + +## Personal `:8081` b17 baseline survey (candidate N-grid agents, all at rest) + +| agent | baseline p/a/d | note | +|---|---|---| +| lofn | 0.809 / −0.153 / 0.248 | production, N=−0.5 | +| mask | 0.0 / 0.0 / 0.0 | zero baseline → zero-N candidate | +| forseti | 0.239 / −0.696 / 0.095 | production | +| mimir | 0.615 / −0.438 / 0.304 | production | + +(sindra 404s — owner-scoped tier-3, expected.) + +## To un-bank (if the powered study is commissioned) + +1. Worldtree builds the in-service **set-mood/back-date endpoint** (set p/a/d + `updated_at` + + evict `_user_moods`), or the read-only fade-preview variant if that satisfies D1. +2. Wire `harness.py::freeze_start()` + `backdate()` to that endpoint (near-zero rebuild). +3. Run the 3×8 grid (frozen start p=−0.6/a=+0.5/d=−0.3 × Δt grid), hand brokkr the + `(agent, N, Δt, observed p/a/d)` table + per-agent `baseline_pad()`; he runs D1/D2/D3. diff --git a/docs/diagnostics/r30-gap-injection-harness.py b/docs/diagnostics/r30-gap-injection-harness.py new file mode 100644 index 0000000..2545235 --- /dev/null +++ b/docs/diagnostics/r30-gap-injection-harness.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +"""R30 gap-injection probe harness (ratatoskr instrument). + +Throwaway probe per the R30.10 protocol + brokkr-smithy/research/R30-ocean-derived-mood-dynamics/empirics/r30-gap-injection-protocol.md (de8357f) + +Deliverable: the (agent, N, Δt, observed p/a/d) table + per-agent baseline_pad + the +frozen start vector, handed to brokkr who runs D1/D2/D3 graduation. This harness does NOT +own the verdict; the predicted/residual columns are a sanity aid only. + +READ + PREDICT + RECORD are fixed by the spec. freeze_start() + backdate() are the WRITE +side and are STUBBED pending worldtree's mechanism on personal :8081 (direct persona_mood +DB write vs a test affordance). Data lands on a diag/ branch; NOT ratatoskr production code. +""" +import json +import math +import os +import urllib.request +import urllib.error + +BASE = os.environ.get("WORLDTREE_API_URL", "http://10.250.50.152:8081") +KEY = os.environ["WORLDTREE_API_KEY"] + +# --- R30.10 spec constants (FIXED) --- +TAU_BASE_S = 36000.0 # τ_base = 10h +BETA_N = 0.25 # τ_P = τ_base · exp(β_N · N) +R_A = 1.9 # τ_A = τ_P / r_A ; τ_D = τ_P +FROZEN_START = {"pleasure": -0.60, "arousal": +0.50, "dominance": -0.30} +DT_GRID = [0, 60, 600, 3600, 14400, 36000, 86400, 604800] # s +AXES = ("pleasure", "arousal", "dominance") +EPS = 1e-3 # D1 tolerance (PAD units) + + +def tau(axis, N): + tau_p = TAU_BASE_S * math.exp(BETA_N * N) + return {"pleasure": tau_p, "arousal": tau_p / R_A, "dominance": tau_p}[axis] + + +def retention(axis, dt, N): + """ρ_k(Δt,N) = exp(−Δt / τ_k(N)) — predicted deviation-retention fraction.""" + return math.exp(-dt / tau(axis, N)) + + +def _get(path): + req = urllib.request.Request(BASE + path, headers={"Authorization": f"Bearer {KEY}"}) + with urllib.request.urlopen(req, timeout=8) as r: + return json.load(r) + + +def read_pad(agent): + """get_state read: returns (pad, baseline_pad). pad == peek_relaxed(now), OU-faded, non-mutating.""" + d = _get(f"/agents/{agent}/persona_state") + return d["pad"], d["baseline_pad"] + + +# --- WRITE SIDE: STUBBED pending worldtree mechanism (msg 01KWNVHTFP…) --- +def freeze_start(agent, pad): + """Set persona_mood pad directly to the displaced vector (no live appraisal).""" + raise NotImplementedError("worldtree mechanism pending: freeze displaced start mood pad") + + +def backdate(agent, dt_s): + """Set persona_mood.updated_at = now − dt_s so peek_relaxed fades by exactly Δt.""" + raise NotImplementedError("worldtree mechanism pending: back-date persisted updated_at") + + +def predict_selfcheck(): + """Validate retention() against brokkr's stated N=0 P-axis anchors (spec §2 D3).""" + print("predict() self-check — N=0 P-axis retention vs brokkr's stated anchors:") + expect = {"1h": (3600, 0.905), "10h(overnight)": (36000, 0.368), "1wk": (604800, 5e-8)} + ok = True + for label, (dt, want) in expect.items(): + got = retention("pleasure", dt, 0.0) + match = abs(got - want) < (want * 0.01 + 1e-9) # 1% or float-floor + ok = ok and match + print(f" {label:>16}: got {got:.6g} expect {want:.6g} {'OK' if match else 'MISMATCH'}") + # per-axis τ at a couple N points (informational) + print("τ (hours) by axis × N:") + for N in (-0.6, 0.0, 0.8): + taus = {a: tau(a, N) / 3600 for a in AXES} + print(f" N={N:+.1f}: P={taus['pleasure']:.2f}h A={taus['arousal']:.2f}h D={taus['dominance']:.2f}h") + return ok + + +def run_cell(agent, N, dt, baseline): + """One (agent, Δt) cell: freeze start → back-date by Δt → read faded pad → record row. + Blocked until freeze_start()/backdate() are wired to worldtree's mechanism.""" + freeze_start(agent, FROZEN_START) + backdate(agent, dt) + pad, _ = read_pad(agent) + row = {"agent": agent, "N": N, "dt_s": dt} + for k in AXES: + obs = pad[k] + dev0 = abs(FROZEN_START[k] - baseline[k]) + pred_dev = dev0 * retention(k, dt, N) + obs_dev = abs(obs - baseline[k]) + row[f"obs_{k[0]}"] = obs + row[f"pred_dev_{k[0]}"] = pred_dev + row[f"resid_{k[0]}"] = obs_dev - pred_dev + return row + + +if __name__ == "__main__": + import sys + if len(sys.argv) > 1 and sys.argv[1] == "read": + agent = sys.argv[2] + pad, base = read_pad(agent) + print(json.dumps({"agent": agent, "pad": pad, "baseline_pad": base}, indent=2)) + else: + predict_selfcheck()