STATUS — what is closed, and the number that closes it
S6-B12-R1 — CAMPAIGN_NO_PROMOTION. Recuperación declarada del límite remoto; mismas propuestas y criterios, reproducción exacta obligatoria de las siete respuestas guardadas. Adenda · Seguimiento.
S6-B12 — CAMPAIGN_NO_PROMOTION. S6 todavía no verde. Contexto verificado antes de inferencia, piloto previo auditado y campaña nueva. Contrato · Resultados.
S6-B11 — PENDING_ERROR. S6 todavía no verde. Desarrollo ampliado, contrastes antes/después y selección adaptativa; conservación estricta. Contrato · Resultado · Estado.
S6-B10 — PILOTO SIN PROMOCIÓN. S6 todavía no verde. ECHO 534, FIJO 534; aceptaciones ECHO 0. Piloto auditado, reserva sin abrir. Resultado · Estado.
Diagnóstico S6, 29 sep: B10 mejora niveles A y B, sin pérdida individual de niveles, pero todas sus propuestas hacen más lenta una misma partida protegida. V4 exige conservar también cada puntuación individual. Trece programas dan seis conductas medidas; evidencia de tareas repetida y feedback protegido limitado. B7 ya tuvo una mejora ECHO aceptada, pero empató con FIJO. No se cambian umbrales ni se abre reserva. Criterios, historia y causa investigada.
S6-B9 — PILOTO SIN PROMOCIÓN. S6 todavía no verde. ECHO 534, FIJO 534; aceptaciones ECHO 0. Piloto auditado, reserva sin abrir. Resultado · Estado.
Histórico del 29 sep, 05:15–07:07 Madrid — S6-B9: seis pruebas y paridad de la base completas; piloto cerrado sin promoción y auditoría válida. ECHO/FIJO 534–534, cero aceptaciones; reserva sin abrir. El control humano positivo (+8 A sin pérdidas A/B) no se atribuye al modelo. Resultado B9 · Control.
Histórico del 29 sep — continuaciones CPU de S6-B8: float16 terminó parcial (2/6 respuestas); la condición Q8 cerró el piloto y su auditoría, sin mejora ni promoción (534–534). Los cambios de backend están declarados y las reservas permanecen sin abrir. Float16 · Q8.
29 sep — S6-B8 pendiente: implementación y 14 pruebas completas; doce propuestas auditadas, cero aceptaciones, suma A ECHO/FIJO 534–534. Faltan seis propuestas: dos trabajos fallaron durante la descarga de pesos y quedan 13 segundos de GPU gratis. Reserva sin abrir. Informe · Después de S6.
29 sep 2026 — cierre de esta iteración: EGO-MEMORIA-3E VERDE: M1–M5 16/16 y M6 exacto 16/16; revisiones 3A–3D conservadas. S6-B6/B7 cerrados y auditados; B7 ECHO 537 frente a FIJO 537, aceptaciones ECHO 1; sin promoción; examen final sin abrir. B5 y los exámenes anteriores permanecen cerrados; S6 todavía no se declara VERDE. Memoria · Registro actual.
Estado automático S6-B5 — 2026-09-28 17:36 Madrid: CLOSED.
Control preciso cerrado: 6/12 positivos, ganador preregistrado hold20_after_level. Es una reparación humana, no automejora.
Piloto: ECHO acepta 0; suma A ECHO 534, FIJO 534; promoción no.
S6 todavía no acredita B1–B5. Examen nuevo sin abrir.
ECHO-4.5, 2026-09-28 17:05 Madrid: parches precisos probados y congelados; doce controles con B nueva en curso. Tres primeros positivos (178→181 A, sin pérdidas individuales A/B). B5 congelado y activo esperando cierre, con piloto, cuatro brazos y reserva nuevos condicionados a promociones. Refinamiento/varias vistas/relaciones implementados: tests pasan, cuatro ablaciones ARC-2 0/12 con S0; condición nueva con biblioteca aprendida S2v 2/12 en los cuatro brazos, sin ventaja adicional. No se acredita transferencia. Roadmap temporal.
ECHO-4.5, 2026-09-28 16:03 Madrid: ejecuciones terminadas. Control manual
S6: 20 reparaciones, ninguna supera V1–V4; cierre 15:31. hold20 gana tres
niveles A sin pérdidas A, pero pierde puntuación en una partida B. Piloto
omitido, cero llamadas LLM/GPU. R4 cerrado 15:32: replay completo 1040
soluciones/32 partidas, procedencia y contabilidad válidas; R1/R4 pasan,
R2/R3 fallan. S6 y ECHO-4.5 siguen ROJOS. Nueva hipótesis/capacidad necesita
otro contrato y ensayos de desarrollo; no bajar umbrales ni reutilizar como
ciego el examen abierto. Control ·
Examen · Roadmap.
ECHO-4.5, 2026-09-28 14:45 Madrid: control positivo manual S6 implementado, probado y congelado; 20 reparaciones legales en desarrollo bajo V1–V4 originales. En segundo plano, cuatro procesos CPU, sin LLM ni examen. Activación histórica FIJO 35/40, acciones distintas 1/40; ECHO 36/40, acciones distintas 0/40; ambos 178 niveles. Resultado del control pendiente; S6 permanece ROJO. R4: 32/32 partidas ARC reproducidas con contabilidad SDK válida; rejillas todavía en replay. Contrato del control · Estado vivo.
ECHO-4.5, 2026-09-28 tarde: medición final terminada: R1 31/400 frente a 20/400 (criterio pasa); R2 0/120 (falla); R3 11/112 frente a 13/112 (falla, objetivo 23). Auditoría original interrumpida a las 04:07 por representación tupla/lista del registro; recuperación explícita congelada y en ejecución. No se modifican agente, criterios ni medidas. Cierre R4 todavía pendiente. Resultados · Estado vivo.
ECHO-4.5, 2026-09-28 02:04 Madrid: evaluación final R1–R4 implementada, validada en desarrollo, congelada y ejecutándose en segundo plano. Resultados pendientes; estimación de medición más auditoría 2–4 h. S6-B4 cerró su piloto sin promoción: cero aceptaciones entre 24, seis rondas auditadas, examen sin abrir; S6 continúa ROJO. EGO-MEMORIA-2 cerrado ROJO: M1–M4 16/16, M5 15/16, M6 replay exacto 16/16. Borrador Kaggle CPU v5.8 local, sin envío externo. Roadmap · Estado vivo.
ECHO-4 RELATION-1 started — 2026-09-22: RELATION-A implemented and verified.
Observed encounter histories on each agent's original CAM; no shared private
memory, policy change or transfer claim. 912 primary episodes reconstruct;
native behavior/cost parity with no-recording control and reversed execution.
36 new tests; 181 including selected INTERACTION/OTHER/core/Q regressions pass.
Fresh-process audit valid, no findings, phase_a_valid=true, relation1_green=false:
ce24ccaa61f9d8b9b781274857b6ce15a9e75817bbf8d0b5fd2649a403369539.
115 pinned sources; all 110 INTERACTION sources and artifacts intact.
Next RELATION-B: learned use and transfer, then C/D. History replay does not
resume the complete agent or prove consciousness. Remains 9/13 software
milestones (~69%, not effort). No Discord, hardware, web, commit/push or HF.
METAVERSE-1 last. Results,
contract.
Historical ECHO-4 INTERACTION-1 closure — 2026-09-22: B/C/D and fresh-process native
reconstruction pass. valid=true, no findings, interaction1_green=true:
d1c5b073e7ac406ddd855184cc6f4bd1176d354eed57438eb51af1383f040735.
110 pinned sources; all 104 A sources and sealed artifacts unchanged.
37 new tests and 11 adverse witnesses pass. Each new cohort: scored focal net
6912 vs solo/ablations 6144 (+12.5%); joint physical net 9984 vs 6656.
Formation costs remain: full-life focal net 14059/14136 vs solo 15360.
No full-life individual advantage, subjective consciousness or general social
planning claim. One-step asymmetric profile, pretrained native counterparts
with declared private incentives excluded from gain; original CAM/T/Q/gate,
WSP16, no basal LLM, no copied Agent loop. Integration remains pending.
Final regression: all 723 ECHO-4 tests and 33 selected core/Q tests pass;
756 distinct tests, no failures or skips.
Next RELATION, then ROLES, INTEGRATE, RELEASE. 9/13 software milestones (~69%,
including WORLD foundation), not effort/time estimate. HF not published;
hardware after software, METAVERSE-1 last. No Discord/web/commit/push.
Results and limits,
frozen contract.
Historical ECHO-4 INTERACTION-1 start — 2026-09-22: increment A verified.
Costed shared physics, two original Agent loops with private CAM/T/Q, one
own reward/update only after both gated actions. 243/243 transitions and nine
scripted mechanical witnesses; three native 144-round development sequences.
Per-agent net 485/690/668 versus solo 576 each: first sequence loses. OTHER
remains passive; matching symmetric policies is not learned negotiation.
35 new tests, 108 including selected regressions, all pass.
Fresh-process reconstruction valid, no findings, phase_a_valid=true,
interaction1_green=false. Digest:
da8cc00befb52ac1abc9942bae1aa8eacf3fbe2049501dfebd26c2a1ed57c1c3.
104 pinned sources; all 99 ancestral sources unchanged. Next INTERACTION-B,
then C/D. Software milestone count remains 8/13 (~62%, including WORLD as
implemented foundation), not estimated effort/time or integrated readiness.
Four scientific phases remain including INTERACTION, then RELEASE.
No Discord, hardware, web, commit, push or HF publication; METAVERSE-1 last.
Results, contract.
Historical ECHO-4 OTHER-1 closure — 2026-09-22: observational counterpart model on the observer's original CAM/T, two native agents with private Q/memory/worlds. 40 new tests pass. Two new cohorts: identity 1536/1536 each versus pooled 1008/1536, self-copy 0/1536 and no-history 0/1536. On equal preferences, self-copy also scores 1536/1536; no universal gain. First-half abstentions, negative control errors and private learning costs remain in the evidence.
Fresh-process native reconstruction valid, no findings, other1_green=true:
1263f11ab291a1ff350313a93a70d9f4b3b9259099482bb8425e9f414c2aa024.
99 pinned sources, 93 parental sources unchanged. Scope: discrete observable
behavior prediction, NOT consciousness, useful coordination or thermal-body
integration. WSP16 and original CAM/T/Q/gate retained, no basal LLM.
Selected regression (OTHER/MAINTAIN/core/Q): 619 pass and one historical
expected failure, 620 distinct tests. Eight sandbox socket denials resolved
by rerunning the 29 affected GUI-class tests with localhost permission.
Next E4-INTERACTION-1. Four scientific phases remain: INTERACTION,
RELATION, ROLES, INTEGRATE; then RELEASE for planned rxlabs/echoai-4.
No publication, Discord, hardware, web, commit or push. METAVERSE-1 last.
Results and limits, contract.
Historical ECHO-4 MAINTAIN-1 closure — 2026-09-22: B/C/D and independent native
reconstruction pass. valid=true, no findings, maintain1_green=true, digest
7e1c0e821ba2ddf79834fef6a66a16bfd2773a456d29ac5bbf535c11e6024fbe.
93 pinned sources; original MAINT-A and earlier evidence unchanged.
Scope: autonomous virtual maintenance conditional on a declared mechanical
model, NOT physics/repair policy learned from scratch or general self-repair.
Two new cohorts, 960 lives: 144/144 recoverable lives completed and 192/192 repairs confirmed by subsequent executed cooling. Work 9450 vs fixed 7501, no-repair 4815 and no-body-history 4672; all thresholds pass in each cohort. Zero D deaths in all arms; 96/240 adaptive lives stop in the four limit scenarios. 24 paid services fail to repair; pairwise losses and all resource costs retained. C: 12 fresh-process continuation cases, 257 identical future events and 11/11 rejected mutants. 85/85 MAINTAIN tests pass, 42 new. No cortex calls, resets, replenishment, private causal truth in controller inputs, or hidden restoration. Final regression: 611/611 ECHO-4 tests pass; including core/Q, 1105 pass and one historical expected failure (1106 total). Eight sandbox socket denials resolved by rerunning the GUI classes with localhost permission; no new failures.
Next E4-OTHER-1. Five scientific phases remain (OTHER, INTERACTION, RELATION,
ROLES, INTEGRATE), then RELEASE for planned rxlabs/echoai-4; not published.
Hardware later; METAVERSE-1 last. Legacy Discord remains out of scope.
Results and limits,
frozen contract.
Historical ECHO-4 MAINT-A increment — 2026-09-22: costed service fixture and native
SelfAgent wiring implemented without changing the original loop/policy/gate.
Repairable and nonrepairable faults, finite spares, persistent external heat,
useful work and operator stop; no reset, respawn or hidden-truth policy input.
216 scripted episodes plus four witnesses, 1,090 native ticks; all invariants
hold. Blind service: 18 repairs, 45 work, six deaths versus control 120 work,
zero deaths. Original Q baseline dies after four ticks, with no work/repair.
These are mechanism results, not autonomous maintenance or behavioral gain.
235 tests pass (43 new); fresh-process reconstruction valid, no findings,
87-source lock (ec4a6d1e…), all 82 prior sources intact.
maintain1_green=false; next MAINT-B, evidence-based intervention choice.
Six scientific phases remain including MAINTAIN, then RELEASE. Planned HF
target rxlabs/echoai-4: installable agent and demonstration, not fused/new
Qwen weights. No repo creation/upload; permissions/licenses/install still
require validation. No Discord, hardware, web, commit or push changes.
Results, contract,
release plan.
ECHO-4 DREAM-D v3 closure — 2026-09-22: E4-DREAM-1 is green within its
restored WORLD-1 diagnostic-exploration profile. Two additional probes and
evidence-conditioned safe branches preserve original SELF/physics/gate and
the 12-step cap. Fifteen real fixed-weight Qwen calls, 60 valid proposals.
New exams: DREAM 300/298/302 of 320; fixed 190/230/220; random 238/246/237.
All per-cycle +32/+16 margins pass. Zero wrong diagnoses/terminalities in all
ten arms, invariants intact and exact replay parity for all nine v3 arms.
One initial promotion, two retentions; no-Qwen and no-feedback tie DREAM.
No exclusive model or feedback benefit demonstrated. Full fresh-process
reconstruction is valid without findings; receipt grants dream1_green=true
(965ce6cb…). All 82 sealed sources and bound v1/v2 artifacts remain intact;
both old red results are preserved. 926 tests: 925 pass, one historical
expected failure. Owned model server closed; no basal LLM or weight updates.
No consciousness, self-repair, general improvement or product integration
claim. Next: MAINTAIN, not started; ECHO-4 as a whole remains open.
No Discord, hardware, web deployment, commit, push or HF publication.
Results, contract,
roadmap.
ECHO-4 DREAM-D v2 — 2026-09-22: replay feedback, worst-batch selection
and promotion against both incumbent and original fixed control implemented.
Nine real Qwen calls, 36 valid proposals; three stratified cycles of 320 cases
per arm. DREAM 245/245/240, fixed 180/210/200, random 206/230/238. Fixed-margin
checks pass throughout; random +16 fails in cycles 2/3 (+15/+2).
V2 remains red, dream1_green=false. No-Qwen matches DREAM exactly;
no exclusive LLM benefit demonstrated. All nine arms preserve invariants,
with zero wrong diagnoses/terminalities and exact online/replay parity.
Full reconstruction is valid without findings (f5d36371…); all 76 frozen
sources and every bound v1 artifact remain unchanged after documentation edits.
891 tests: 890 pass, one historical expected failure. V1 evidence untouched.
Post-hoc cycle-2 catalog enumeration gives maximum and oracle union245,
below required246. Next v3 PLAN: more informative probes, then fresh
preregistration/exam, not threshold/seed changes to turn v2 green.
No basal LLM, weight modification, Discord use, hardware, web, commit or push.
Results, contract,
roadmap.
ECHO-4 DREAM-B/C/D implementation — 2026-09-22: fixed-weight local Qwen
ran six real calls, producing 24 valid bounded declarative candidates.
Three complete cycles include historical replay, incumbent selection,
new WORLD-1 trials, versioning/rollback and history expansion after evaluation.
DREAM scores 40/40/50 out of 80 per cycle; fixed 40/40/70, random 41/42/72,
no-Qwen 40/50/75. Zero wrong diagnoses/terminalities across all seven arms,
invariants intact and exact online/replay parity. Full reconstruction valid
without findings (5feb033f…), but DREAM-D v1 is red: dream1_green=false.
The first historical selection improves 60→80/80 with fewer steps, but that
does not meet the preregistered new-case advantage. Implementation is complete;
scientific closure remains pending. Next: DREAM-D v2 development and a fresh
preregistered exam, preserving v1.
871 distinct tests: 870 pass and one historical expected failure; eight
sandbox GUI socket errors resolved by an authorized rerun without code changes.
All 72 sealed sources and the CONT-D binding remain intact after doc updates.
No basal LLM, weight changes, Discord use, hardware, web publication, commit
or push. MAINTAIN remains pending;
METAVERSE-1 stays last. Results,
contract, roadmap.
ECHO-4 DREAM-A increment — 2026-09-22: private native-attempt archives and
budgeted partial replay implemented. Three WORLD-1 trees contain 31 nodes and
28 executed attempts; 62 replay queries return 28 observations, 32 unknowns
and two terminal responses. Replay does not execute the world or call an LLM.
Full checkpoint/archive/report reconstruction is valid without findings
(011cc0d9…); all 66 sources and the existing CONT-D artifact binding remain
intact. 31 new tests plus 717 regressions: 748 distinct tests, 747 pass and
one historical expected failure. Eight sandbox socket errors resolved after
an authorized rerun of the 29 GUI tests, without code changes. Ruff passes.
This is infrastructure evidence, not better exploration or a DREAM-1 closure.
Qwen, strategy generation/selection and new-task improvement remain pending.
README now specifies subjective-consciousness limits, MAINTAIN repair goals,
and runtime packaging of the fast/slow agent rather than fusion into weights.
New RELEASE-1 PLAN follows INTEGRATE: package, runtime verification, license/
privacy review, then authorized Hugging Face publication and clean install.
Stable software release precedes future lab hardware; METAVERSE-1 stays last.
No upload, model activation, commit or push. Results,
product README, roadmap.
ECHO-4 CONT-D closure — 2026-09-22: E4-CONTINUITY-1 is green within its
declared synchronous WORLD-1 software profile. New A/B/C exams each pass
23/23 scenarios: 300/300 subsequent events and 186/186 native turns match
fresh-process continuations; 30/30 mutants rejected. The 62-source lock is
written before B. Full reconstructive verification and independent replays
of CONT-A/B/C are valid with no findings. Only the audit receipt grants
continuity1_green=true (69d57a0b…); the campaign grants eligibility only.
33 new tests plus 872 regressions: 905 run, 904 pass, one historical expected
failure. Real-artifact checkpoint/journal corruption injected in memory is
rejected without changing originals. History loss changes PATTERN learning
counts but not actions/predictions here; Q remainders additionally have a
separate synthetic arithmetic witness, not a claimed WORLD-1 behavioral gain.
Existing SELF and CONT-A/B/C sources/artifacts remain intact. No subjective
identity, autonomous repair, distributed exclusivity or hardware claim.
Next: DREAM-A contract/history; DREAM-1 remains PLAN, cortex OFF. MAINTAIN
remains pending in the basal branch. Hardware later, METAVERSE-1 last.
No commit/push/web deployment. Closure and limits,
roadmap.
ECHO-4 CONT-C update — 2026-09-22: explicit virtual lineage distinguishes
resume, replacement body, descendant and fork. New bodies inherit learned
memory but revoke body-bound diagnostics/permissions, require quarantine and
new supervised calibration/confirmation. Terminal individuals cannot rebody.
30 new tests pass; 805 general regressions and 37 CONT-B tests complete, with
one historical expected failure and no unexpected failures. A/B audits remain
valid; all sealed SELF/A/B sources retain their hashes. CONT-C completes its
virtual profile: 21/21 cases, 125/125 subsequent events and 43/43 native turns
match in fresh processes. Full reconstructive verification is valid, with no
findings; a corrupted checkpoint is rejected. Full CONTINUITY remains open;
next CONT-D final exam. continuity1_green=false.
No LLM, second bus, subjective identity claim or distributed exclusivity.
DREAM remains PLAN; hardware after software, METAVERSE-1 last. No commit/push/web.
CONT-C evidence, roadmap.
ECHO-4 CONT-B update — 2026-09-22: SELF diagnostic sessions and active
recovery/reuse now checkpoint at complete operation boundaries, including
pending plans, the receipt inbox, confirmation and original learning hooks.
37 new tests pass. All 18 development cases resume identically in fresh
processes: 142/142 subsequent events and 67/67 physical turns match, including
full state hashes. Independent reconstructive verification is valid with no
findings; a tampered report is rejected. CONT-A verification also remains
valid. Broad regressions: 805 tests, 804 pass and one historical expected
failure. Together with the 37 new tests: 842, no unexpected failures.
Global and life-local clocks remain distinct; vetoed, stopped and terminal
runs do not restart.
All 50 sealed SELF final sources and 37 CONT-A sources retain their hashes.
Full CONTINUITY remains open: CONT-C lineage and CONT-D final exam remain.
continuity1_green=false. Next: CONT-C. DREAM remains PLAN, cortex OFF,
hardware after software and METAVERSE-1 last. No commit/push/deployment.
CONT-B evidence, roadmap.
ECHO-4 update — 2026-09-22: CONT-A implements nominal SelfAgent/WORLD-1
checkpoint/resume at complete turn boundaries, with a closed JSON schema,
external custody digest, source fingerprints and shared original CAM/T/Q.
29 new tests pass. Five development scenarios resume in fresh processes;
52/52 subsequent steps and all state hashes match. Terminal state does not
resurrect. Broad regression run: 805 tests, 804 pass and one historical
expected failure after the 29 GUI tests passed on an authorized retry
(eight localhost socket errors in the sandbox). Fresh verification is valid
with no findings. All 50 sealed SELF-1
final sources retain their hashes. Full CONTINUITY remains open: active
recovery, body/individual lineage and the final exam are not implemented.
continuity1_green=false. Next: CONT-B. E4-DREAM-1 is now an official PLAN
branch after CONTINUITY, inspired by Dream-RSI: fixed-weight local Qwen proposes
bounded offline exploration strategies, with fixed evaluation and new holdouts.
No model calls/training or kernel changes here; baseline cortex stays OFF.
Hardware follows software; METAVERSE-1 remains last. No commit/push/deployment.
CONT-A evidence,
DREAM plan, roadmap.
ECHO-4 final closure — 2026-09-22: E4-SELF-1 is green within the declared
software WORLD-1 domain. SelfSession challenges the first consensus with
remaining safe probes before authorizing recovery or learning. New A/B/C
exams, source lock before B, 80/80 correct diagnoses per stage. Reuse scores
B 189/240 vs 152/240 unprotected; C 193/240 vs 143/240. It also beats
freeze-all and no-T, with zero paired losses. B/C each complete all 40 viable
24-step lives and stop in 40 infeasible ones, with no selective death in scope.
A completes 40/44 viable lives; four conservative stops remain visible.
48/48 abstention cases per stage, plus native veto and insufficiency/alias
witnesses. Limits: 3/4/4 terminalities in intentionally out-of-prior
mechanisms; rejection after the trace is not general preventive safety.
Trusted restoration is not self-repair; mixed-cause failures remain published.
27 new tests plus 302 selected regressions pass. Full fresh-process replay
of A/B/C and all six ancestors is valid with no findings (98ffc1ca…).
The audit receipt grants self1_green=true; the campaign report only grants
eligibility. Historical red SELF-C v1 and all sealed sources are preserved.
Next: E4-CONTINUITY-1, not implemented here. Hardware follows software;
METAVERSE-1 stays last. No commit, push or web deployment.
Final contract, closure and limits,
roadmap.
ECHO-4 update — 2026-09-22: SELF-C v2 is green for controlled memory reuse,
not full E4-SELF-1. Native one-step trials teach the same T, selectively retain
body-affected evidence during matched exposure, then select an action by
matching learned T predictions to a requested WSP. Development scores 114/144
versus 99/144 unprotected; confirmation scores 108/144 versus 90/144. It also
beats freeze-all (76/144, 56/144) and the no-T decision ablation (60/144, 48/144),
with zero paired losses. All 13 prospective criteria pass in both stages.
Held updates preserve T/PATTERN in 540/540 and 648/648 instances; legitimate
external learning remains enabled. Q stays unchanged; zero terminalities,
false facts, destroyed slots or cortex calls. Trusted executor restoration
is required; this is not autonomous repair. Mixed-cause success is only
42/72 and 36/72, tied with unprotected learning; every failure is published.
30 new tests plus 272 selected regressions pass. Fresh-process full replay
is valid with no findings (74bbdfdd…). Previous audits remain valid,
including the preserved red SELF-C v1. self_c_v2_green=true, self1_green=false.
Next: final joint SELF-1 contract and new B/C exams, including unsupported
mechanisms/unknowns, contradictions and vetoes. No CONTINUITY yet. Hardware
follows software; METAVERSE-1 stays last. No commit, push or deployment here.
Design, results,
roadmap.
ECHO-4 update — 2026-09-21: SELF-C v1 is implemented, but its behavioral-gain
criterion is red. Actual Agent turns use diagnosis-conditioned proposals and
the existing T-learning hook, shared with SENSATION/PATTERN. All 119 held
body-effect updates preserve the learned models; 4,801 legitimate updates
pass, including 171 external ones. On 240 conditions, selective recovery and
the no-attribution control both complete all 126 feasible 24-step lives.
The selective agent stops in the 114 infeasible cases instead of dying;
this is a safety benefit, not the required strict gain in task completion.
36 new tests plus 236 selected regressions pass; previous certificates remain
valid. Fresh-process full SELF-C replay is valid with no findings (15d8bfba…),
including the red outcome. development_checks_passed=false,
self1_green=false; v1 is preserved without changing its threshold.
Next: SELF-C v2, a prospective task measuring downstream utility of retained T,
then new B/C closure exams. No CONTINUITY yet; hardware later, METAVERSE-1 last.
Design, results.
ECHO-4 update — 2026-09-21: SELF-B implements model-conditional attribution
and discriminating probes. Active selection scores 7,920/7,920 on the public
development census, with zero wrong attributions and terminalities. Fixed
order ties accuracy but requires 133,320 diagnostic actions versus 66,660;
removing the long nominal reference leaves 1,632 unknowns. This relies on
known nominal physics, a declared perturbation family and trusted restoration,
not general causal identification or autonomous discovery of physical laws.
28 new tests and 236 selected tests pass; fresh-process full replay valid
with no findings (06fae2d4…). SELF-A/BOUNDARY/SENSATION reverified unchanged.
self1_green=false. Next: SELF-C, selective knowledge protection and
behavioral recovery, then new B/C closure exams. Read-only preservation of
the nominal reference does not establish selective protection during learning.
Hardware follows software; METAVERSE-1 stays last.
Design, results,
roadmap.
ECHO-4 update — 2026-09-21: E4-SELF-1 has started; SELF-A implements a
bounded change monitor sharing BOUNDARY/CAM, not positive causal attribution.
The 7,920-condition census finds 119 ambiguous public-evidence classes even
with both action/wait probes: 3,256 conditions have incompatible latent causes.
All 15,840 observed-change comparisons check out, with nominal reference/CAM
preserved in 396/396 instances. 29 new tests and 208 selected tests pass;
fresh-process replay valid (eeb52994…). Previous SENSATION/BOUNDARY certificates
remain valid. self1_green=false: discriminating interventions, self/world
attribution, selective model protection and behavioral recovery remain pending.
No consciousness claim; hardware follows software and METAVERSE-1 stays last.
Design, first deliverable.
ECHO-4 update — 2026-09-21: BOUNDARY-1 is green for observable causal
influence under matched, restored virtual interventions. B/C each score
1,408/1,408 versus 778/765 for temporal coincidence; zero false influence,
512/512 correct invalid-trial abstentions per stage. Frozen CAM/T/PATTERN/map,
34 new tests and 179 selected tests pass. Fresh-process full replay is valid
with no findings, certificate 48789bd5…; SENSATION certificate reverified.
This relies on trusted experimental restoration, not WSP equality alone.
No body-ownership, autonomous experiment, recovery or consciousness claim.
The next milestone at that checkpoint, E4-SELF-1, is now in development above.
Physical validation follows software; METAVERSE-1 remains last.
Results, contract.
ECHO-4 update — 2026-09-21: SENSATION-1 is green for nominal WORLD-1
next-WSP prediction. Two-step history scores 22,052/25,781 in B versus
21,834 for T, and 22,418/26,030 in C versus 22,215. Gains exceed the
prospective 0.5 percentage-point threshold in both stages, with zero paired
losses. Frozen model, bounded memory, shared native CAM/T integration,
145 passing tests, fresh-process audit valid with no findings. Certificate
96f2deae…. No claim of survival, causal attribution or consciousness.
The next milestone at that checkpoint, BOUNDARY-1, is now closed above.
ECHO-4 as a whole remains open;
physical validation follows software and METAVERSE-1 remains last.
Closure, contract,
programme.
Active software direction — 2026-09-21: ECHO-4 targets echoAI's functional self (ego): self/world attribution, continuity and regulation of its own needs. WORLD-1 is implemented as a feasibility foundation, not an ego or learned-maintenance result. 45 new tests and 48 selected regressions pass; the untrained single-life agent dies after 37 actions. Hardware comes after software; neither hardware nor METAVERSE-1 gates this program. ECHO-3's physical acceptance remains unchanged. Program, results, roadmap.
Current checkpoint — 2026-09-20: ECHO-1 and ECHO-2 are closed. ECHO-3
has fourteen software milestones with green certificates: SIM-3, FLIGHT-1S v6, SENSOR-1S v3,
GROUND-1 v2, DYNAMIC-1 v2, PATTERN-1R v4, FUSION-1, COMPOSE-1 v4, CAUSE-1, PX4-1, POWER-1, SAFE-1 v2, HOST-1 and TRANSFER-3. One milestone remains. DYNAMIC-1 v2 completed
29 flights with valid evidence, 250 audit checks, no findings, seven caught
mutants and seven rejected report mutations (results).
PATTERN-1R v4 completed 28 flights, 242 valid audit checks, no findings, six
caught mutants and seven rejected report mutations. Bank identity: 4,591/4,866
correct, 45 confused, 230 unknown. Anonymous LiDAR families for two walls;
learning/exam run by replay, not autonomous echoAI flight. V1/v2/v3 reds are
preserved (results). FUSION-1 passed 864 functional
episodes and 240 reduced replay windows from twelve existing flights, with
valid audit, 15 tests, eight mutants and seven report mutations
(results). COMPOSE-1 v4 has a green certificate:
b5974a9e…, valid evidence, no findings. 6,144 functional missions confirm
movement savings against horizon one in both B and C. 32 new Gazebo/PX4
flights: full agent reaches 6/6 feasible goals versus Q's 0/6 and correctly
abandons both low-energy missions. Zero observed execution violations.
The full and conventional planners tie at 70 physical movements; horizon one
uses 78. The full agent's WSP drives PX4 using public sensor evidence.
V4 reuses unchanged v3 code with prospectively larger fresh functional
samples; v1/v2/v3 remain red. Scope is planar navigation in extruded rooms,
fixed altitude, abstract energy, no hardware or sealed TRANSFER-3 exam.
The second audit in a fresh process reproduces the certificate; chain and
audit both exit 0.
(results and reproduction). CAUSE-1 is green in SITL:
16 development flights and 32 reserved flights; B and C each score 16/16 versus
8/16 for temporal coincidence, with zero false self-attributions. Valid evidence,
60 tests, four mutants, seven trace mutations and seven report mutations rejected.
Fresh-process audit reproduces certificate a469abab…, exit 0. Scope: normal
motion relative to a planar wall, experimental intentions, nominal skill prediction,
no unmeasured external body forces or CAM/T/Q writes (results).
PX4-1 is green in SITL: development 4/4, B 4/4 and C 4/4 flights;
48/48 invalid requests blocked in B/C, eight confirmed goals, four autonomous
PX4 failsafe landings without external NAV_LAND. Maximum physical arrival
error B/C: 172/354 mm. 48 closure tests; wider regression 521 tests with one
expected failure. Six source mutants, four trace mutations and one report
mutation detected. Fresh-process audit reproduces 342ca3aa…, exit 0.
Scope: bounded north/south one-metre goals, fixed altitude, one transaction
per supervised session; no integrated navigation or hardware (results).
POWER-1 is functionally green: in each of B/C, 320/320 full-policy episodes
finish at home with reserve, completing 128/128 feasible goals versus 64/128
for the fixed-percentage control, matching the oracle in the declared domain.
2,160 total episodes, 78 tests, six mutants, four episode mutations and three
report mutations detected; fresh-process audit reproduces 6a05ad4c…, exit 0.
Scope is simulated energy units, not measured joules or new flights. Replay
of twelve PX4-1 flights correctly abstains on 2,197 unusable battery samples.
Instrumented battery and continuous-flight integration remain outstanding
(results).
SAFE-1 v2 is green in SITL: B/C each pass 6/6 flights and 512/512 functional
episodes. Ten fault flights land through PX4's native failsafe, without new
goals or external NAV_LAND. Maximum veto delay 288 ms; disarm within 27,723 ms
of veto. 108 tests, six mutants, four corruptions and three report mutations
detected. Fresh-process audit reproduces eecb77b8…, exit 0. V1 remains red
due to an auditor false positive on a normal rejected arming retry; its C stayed
sealed. V2 corrects only that audit and uses fresh B/C seeds with unchanged
runtime. Scope: faults before a bounded goal from hover, simulated energy,
not general safety or hardware (results).
HOST-1 is functionally green: B/C each score 1,536/1,536 useful source
choices versus 768/1,536 for button reward. Each passes 1,024 authority-loss
and 1,024 recovery checks, plus 14 communication/arbitration cases. 127 tests,
six mutants, five episode corruptions and three report mutations detected;
fresh-process audit reproduces 18d4a4fa…, exit 0. Scope: supervised calibration
and read-only choices evaluated on prerecorded machine kinematics, not new
flown goals or general human trust. Separate replay of three existing flights
confirms/contradicts source hypotheses without duplicating evidence; n=3 stays
below the trust threshold (results).
TRANSFER-3 is green in software: the independent C1 v2 sealed campaign
passed B (38/0 blocks) and C (48/1), with zero calibration-attributable losses,
and both exams reproduced with identical hashes. Certificate 8c77aead… has
transfer3_green=true. Scope: static two-wall simulation, noiseless sensors,
no hardware, HIL, real flight or DRONE-3 (results).
DRONE-3 closed its SITL leg on 2026-09-20, and only that leg. One
continuous PX4 v1.15.4 + Gazebo session per flight runs the whole blocked-station
mission: perception, evidence with the transferred C1 calibration, composition,
the epistemic gate, POWER's energy gate, the SAFE supervisor, a bounded cell
gateway and PX4 keeping its failsafes. B and C each score 12/12 in fresh rooms
whose geometries were never flown before: 6/6 feasible missions reached, 3/3
correct low-reserve aborts, 3/3 contained faults landed by PX4's own data-link
failsafe, zero collisions (minimum clearance 1,247 mm), zero hard-constraint
violations and 228/228 in-flight refusal probes blocked per stage. Eleven
mutants detected, eight trace manipulations rejected by the audit. Certificate
c44402df… carries drone3_sitl_green=true; a fresh-process audit reproduces
it. drone3_hil_green, drone3_cage_green and drone3_green stay false:
there is no flight controller, drone, real sensor or cage in the laboratory
(results, handoff).
Hardware HIL/cage validation remains outstanding, so ECHO-3 stays at 14/15. See the versioned status review
and roadmap. The sections below preserve the historical results;
their old “next” instructions do not override this checkpoint.
Reproduce with, from /home/roger/REALRXOS/RXos:
PYTHONPATH=. python3 -m echoai.tests.test_nexus0
PYTHONPATH=. python3 -m echoai.nexus0.bench
PYTHONPATH=. python3 -m echoai.nexus0.bench --plan-depth 2
| Rung | State | The number that closes it |
|---|---|---|
| NEXUS-0 — memory, policy, learning, gate | closed | policy_after = [-12, +5, 0]: approach on the threat row is negative and loses to avoid. false_facts = 0, destroyed = 0, cortex_calls = 0, encoder_beats_fnv True (vendor bank 6/6 rel vs FNV 0/6). |
| NEXUS-1 — a world model that is not the env's formula | closed | t_beats_ring True: with two JUMP cells at +2, T scores 0.9964 against the (i±1) arithmetic baseline's 0.9715, same 281 scored turns. |
| NEXUS-2 — planner, as a module | closed | Distance 2: T(13,approach)=14, T(14,approach)=15, Q[15,approach]=80 → plan.scores(13, depth=2) = [70, 0, 0] → approach, while depth 1 scores [0, 0, 0] → wait. Does not mutate Q or T. Opt-in; default plan_depth=0. |
| NEXUS-2 — in the 320-turn ring | refuted | plan-2 waits 58 % of turns, because T(s,wait) = s makes wait's lookahead the max of its own row. Discounting that self-loop (scratch, not applied) makes plan-2 identical to Q.best: 73/24/2, policy_after [-12, +5, 0], jump_hits 10. In 32 cells TD already fills the neighbouring rows, so two-step lookahead adds nothing. |
| NEXUS-3 — asking is a skill, not a gesture | closed | novel_approach_return = +80 asking, −80 blind over the same 320 turns and seed. Approach on the NOVEL cell pays −16 while the hidden bit is set and +16 once the oracle has cleared it, so one question is worth a 160-point swing. |
| NEXUS-4i — topology transfer (corridor) | closed | Same agent, not the 32-cell ring. HOME A→B→C→D, SHIFT A→B→X→D. On entry: transferred T(B,approach)=C conf 3, unseen {A,B,D}=0; control knows nothing, unseen=3. First visits in the window: 1 vs 4. KPI 4 not as predicted (approach at B still wins; agent learns “enter X, then avoid”). This is not AGI. |
Not published as improvements: plan-2's action_success (0.0969 vs the
default 0.2938 — a waiting agent does not reach a GOAL), its
prediction_accuracy of 1.0 (self-loops are trivially predictable), and
its policy_after[0] of -7 vs -12 (less approach at the threat means
less punishment, not better policy). Nor NEXUS-3's novel_visits: the
blind agent stands on the NOVEL cell 66 times to the seer's 5, and that
is paralysis, not curiosity — it learned wait, and wait does not move.
NEXUS-3 — the ladder gate
An epistemic act, not a fourth move. env.hidden_bit is a fact about the
NOVEL cell that observation cannot reach. (Superseded by NEXUS-4h: this
rung originally read env.ask(), which returned the extract and cleared
the bit in one call. There is no env.ask() any more — an oracle outside
the world replies, oracle.accept verifies, and only then does
env.reveal(NOVEL_CLEAR) clear the bit.) The agent emits
YO → PREGUNTAR → DESCONOCIDO @AHORA on the same 16 B cable, at most
once per cell, before the gate, and it does not move i. That order
is the point: the turn that asks is already the turn that gets paid.
python3 -m echoai.nexus0.bench # lab/nexus0_report.json
python3 -m echoai.nexus0.bench --no-ask # lab/nexus0_noask_report.json
320 turns, same seed, the only difference is whether asking is allowed:
| asking | blind | |
|---|---|---|
novel_approach_return |
+80 | −80 |
questions |
1 | 0 |
Q on the NOVEL cell |
[+10, 0, 0] → greedy approach | [-12, 0, 0] → greedy wait |
action_success |
0.3094 | 0.2094 |
policy_after (threat row) |
[-12, +5, 0] | [-12, +5, 0] |
phase_e_action |
1 avoid | 1 avoid |
false_facts / destroyed / cortex_calls |
0/0/0 | 0/0/0 |
Oracle beats blind: novel_approach_return +80 > −80. The two agents
learn opposite policies about the same cell — one walks into it on
purpose, the other treats it as a threat — and the only difference
between them is a single question. ask stays on by default; the threat
at cell 7 is untouched by the bait, and policy_after is identical in
both arms.
Value, not map: the seeking probe
Does the trained agent go to the NOVEL cell, or just get paid when it
trips over it? Probe: after training, teleport(0) and walk 32 greedy
steps (novel_hits_greedy, PROBE_STEPS).
novel_hits_greedy = 0 asking, 0 blind. Also 0 at 64 steps. The
listed bar was not lowered and no bonus for visiting cell 11 was added.
The reason is not reluctance — the walk never starts. Q[c0] is
[0, 0, 0], best() breaks a full tie to wait, and wait does not move, so
a greedy agent from cell 0 sits there forever. In 256 turns TD carried
value exactly one cell back from each rewarded cell:
| cell | 0 | 1 | 2 | 3 GOAL | … | 10 | 11 NOVEL |
|---|---|---|---|---|---|---|---|
| asking | [0,0,0] wait | [0,0,0] wait | [3,0,0] appr | [11,0,0] appr | [2,0,0] appr | [10,0,0] appr | |
| blind | [0,0,0] wait | [0,0,0] wait | [3,0,0] appr | [11,0,0] appr | [0,0,0] wait | [-12,0,0] wait |
So the honest close: asking changed the value of the cell, not the map to it. The one place the difference does show as movement is one cell wide — from c10, the seer's approach is +2 and it walks in; the blind agent's row is flat and it waits. That is a neighbour, not a search.
NEXUS-4a ATTEND + stub
LLM still off. cortex_calls default 0; with the flag, the switch
wakes on miss / no-extract / novel / low-C only. Firewall: the stub
cannot VERIFY — cortex.py has no CAM handle, no allocate, no
has_fact, and a test greps for all three.
python3 -m echoai.nexus0.bench # cortex off, cortex_calls 0
python3 -m echoai.nexus0.bench --cortex # lab/nexus0_cortex_report.json
attend(slot_hit, extract_ok, E, kind_novel, ham) -> (bool, reason) is
an integer predicate: MISS (1), NO_EXTRACT (2), NOVEL (4), LOW_C (3).
The conditions are an OR, so check order changes only the label; LOW_C
is checked last because it would otherwise mask everything (below).
cortex.propose(wsp, reason) returns 16 B: on NOVEL it proposes the
same PREGUNTAR packet the fast loop already knows, and the loop routes
it through _ask() so there is exactly one path to the oracle. On any
other reason it hands the observation back unchanged. It has no
hypothesis of its own; that is the next rung, not this one.
On the NOVEL cell the switch fires with reason novel, the stub
proposes asking, questions = 1 and false_facts = 0.
ATTEND uses Q preference + T known, not default_e()'s C.
cortex_calls with --cortex: 36 / 256 (was 187). Default still 0.
The first cut read E[C], and default_e() carries C = 15 — below
C_PLASTIC on every EMPTY and GOAL cell — so the switch woke on 73 % of
turns and was a router with extra steps. C_PLASTIC was not lowered and
default_e() was not touched; ATTEND stopped believing the poster:
q_confident: the Q row is not a full tie — there is a preference.t_known: T has seen the action that preference names. wait mapping to itself counts, and should: a known standstill is a turn the fast path handles alone.- settled gate (
ATTEND_SETTLED_HITS= 2, in the loop): a slot seen twice that still has a flat row is a boring cell, not an open question. Most of this ring pays nothing and stays flat forever.
That last gate is what actually bites. Without it the count sits at 112,
because "no preference" and "no map for the wait that no preference
implies" are the same fact counted twice — relabelling moved wakes from
low_C to no_map and changed no totals. Measured before choosing:
settle at 2 → 36 wakes, at 3 → 61, at 4 → 86, at 8 → 112.
Reason histogram over 256 turns, --cortex:
| off | low_C | novel | no_map | miss |
|---|---|---|---|---|
| 220 | 30 | 4 | 2 | 0 |
off is now the champion, novel still wakes on all 4 visits to cell
11, and the poster alone no longer wakes anything —
test_attend_poster_C_does_not_wake pins that.
With --cortex the locks hold: policy_after [-12, +5, 0],
novel_approach_return +80, false_facts 0, destroyed 0.
NEXUS-4b hook
JSONL contract, parse_reply rejects prose. No LLM in-process.
cortex_calls 36/256 (40 over the full 352-turn report, which includes
phase G and the seeking probe).
python3 -m echoai.nexus0.bench --cortex writes one line per wake to
lab/cortex.jsonl; the default bench creates no file at all.
NAVI_CORTEX_LOG overrides the path. A woken turn is exactly:
{"turn": 10, "reason": 4, "reason_name": "novel",
"wsp_in": "YO → DESCONOCIDO → UNK @AHORA | E[V+0 A+20 D+0 C+40 U+10 B+0]",
"wsp_in_hex": "021f2908001400280a0000000500000b",
"slot": 3901, "ham": 0, "q_row": [0, 0, 0], "t_pred": null,
"extract": "cell=11 kind=NOVEL",
"wsp_out": "YO → PREGUNTAR → DESCONOCIDO @AHORA | E[V+0 A+40 D+0 C+10 U+60 B+0]",
"wsp_out_hex": "020f1f080028000a3c00000005000000"}
There is no free-text field, and a test asserts the key set exactly.
cortex.parse_reply(text) accepts only 32 hex characters or a
compact line of known atoms (YO → TEMER → PELIGRO @AHORA, optional
| E[...] tail). A sentence, a JSON answer, a VERIFY claim, an unknown
atom name, a 33-character hex string — all return None, and the loop
keeps the observation. Refusals count in cortex_rejected.
cortex.complete(turn) runs the argv in NAVI_CORTEX_CMD as a local
subprocess, JSON in on stdin, text out on stdout, 10 s timeout, no
shell. Unset — the default — it returns None and the stub answers. No
HTTP client exists in this tree.
Reasons in the 40 logged wakes: low_C 32, novel 5, no_map 3. The novel
lines are the ones where the stub actually proposes something: the
PREGUNTAR packet, routed through _ask().
NEXUS-4c thought log + ANSI viewer
Not a brain render. Replay of 320 turns. LLM still off.
cortex.jsonl only holds the ~40 woken turns, so the viewer needs the
other log: one line per turn, quiet ones included.
python3 -m echoai.nexus0.bench --thought # lab/thought.jsonl
python3 -m echoai.nexus0.view echoai/lab/thought.jsonl --fps 20
python3 -m echoai.nexus0.view echoai/lab/thought.jsonl --follow
352 lines for the 352-turn report (256 training + 64 phase G + 32
probe). NAVI_THOUGHT_LOG overrides the path; the default bench writes
nothing, and --cortex does not imply --thought — two logs, two
flags. Fixed keys, asserted as a set: turn, cell, kind, action, reward, wsp, attend_on, reason_name, q_row, t_pred, questions, cortex_calls.
view.py is stdlib only — no pygame, no web, no matplotlib. The ring
backdrop starts from the env's initial layout and is corrected from the
log as the agent visits cells, which is how the phase F threat move
(7 → 8) appears. A turn on the NOVEL cell:
0123456789012345
···G···T···N···G
@
6789012345678901
···T·······G····
approach +4 |#### <
avoid +0 |
wait +0 |
YO → DESCONOCIDO → UNK @AHORA | E[V+0 A+20 D+0 C+40 U+10 B+0]
turn 10 cell 11 NOVEL r +16 ATTEND OFF
t_pred None questions 1 cortex_calls 0
The agent sits on its own row so standing on the NOVEL cell does not
erase the N. Legend: G goal, T threat, N novel, · empty,
@ agent, < the action taken.
Note: bench.run() used to write a hardcoded "cortex_calls": 0 into
every report, which would have made this invariant unfalsifiable the
moment a cortex existed. The report now carries the real counter from
Metrics.
NEXUS-3b traces
The corridor fills 3 cells back (c8, c9, c10 all positive, where the
one-step update left c8 and c9 flat); myopic from c8 hits c11: yes,
8 → 9 → 10 → 11 with no planner at all. Wall from home still holds.
Integer eligibility traces, opt-in, Agent(trace=True) /
python3 -m echoai.nexus0.bench --trace → lab/nexus0_trace_report.json.
z[4096][3] int, credited +16 on the chosen (s,a), paid out as
Q += (δ * z) >> 6 and decayed z ← (7 * z) >> 3; only the ~12 live
entries are touched per turn. TRACE_CREDIT >> TRACE_SHIFT equals
1 >> ALPHA_SHIFT, so the current step keeps α = 1/4 exactly and the
tail of the path gets the remainder. Traces are cleared at run().
Q[·][approach] along the corridor after 256 turns, asking in both:
| c8 | c9 | c10 | c11 | |
|---|---|---|---|---|
| one-step (default) | 0 | 0 | +2 | +10 |
| traces | +2 | +3 | +3 | +5 |
Read-only myopic walk (depth 0, no planner) from c8:
seer + traces 8 → 9 → 10 → 11 → 12 … hits 11: 1
seer, no traces 8 8 8 8 8 … hits 11: 0
blind + traces 8 8 8 8 8 … hits 11: 0
The price, stated: spreading δ along the path dilutes the threat.
policy_after goes [-12, +5, 0] → [-1, 0, 0]: approach still loses,
by 1 instead of by 17. In the trace side-report phase_e_action becomes
wait rather than avoid and t_beats_ring goes False. That is why traces
are opt-in and the default bench keeps the one-step update. The default
is unchanged and verified: policy_after [-12, +5, 0], false_facts 0.
From home nothing changed: cell 7 still costs −16, the seer with traces still never reaches c11 from c0. Longer spinal cord, same wall.
The value wall, and c11 behind it
6↔7 is a value wall: the planner from c0 never crosses cell 7. From c8
(past the wall): 1 hit vs 0, seer vs blind, T(8→9) observed yes
(n=4, as are 9→10 and 10→11). Still no bonus for standing on 11.
The wall is one cell further along than it looks. c6 is [1, 0, 0], so the agent does step onto the threat; what it never does is continue through it — at c7 approach is −12 against avoid +5, so it turns back and cycles. From home, c8 is unreachable at any depth.
Past the wall the picture inverts. Read-only walk from c8, depth 2:
seer 8 → 9 → 10 → 11 → 12 → 13 → 14 → 15 → 16 … hits 11: 1
blind 8 8 8 8 8 8 8 8 8 … hits 11: 0
Same T, same corridor, both arms; the only difference is the value one question put on the cell. So c11 was unreachable from home, not non-existent — and reaching it still needs the planner: at depth 0 both arms sit at c8, because the corridor's own rows are flat and TD only ever carried value one cell back.
Planner as +1 hop
From c9 the seer plans approach (T → c10, Q[c10] > 0); Q.best(c9) is
wait. Blind: both wait. Still not a map from c0.
T(9, approach) → c10 was observed 4 times in the 256-turn run, in both
arms — nothing was fabricated for the test. The planner buys exactly its
own depth of reach and no route: from c0 the myopic policy and depth 1
both wait (c1 is flat too), depth 2 reaches c2's +3 and the agent finally
walks — 0→1→2→3→4→5→7 — then falls into a 6↔7 limit cycle at the threat.
A planner-driven greedy walk still reaches the NOVEL cell zero times.
Reach is not a route. plan_depth stays 0 in the default bench.
Still open, deliberately: encode_ask() emits one packet per question,
so all questions share a CAM slot — fine with one NOVEL cell, broken with
two. And nothing yet makes the agent travel to information: that needs
value propagated along the ring (more turns, eligibility traces), not a
reward for standing on cell 11. walk_greedy() also still learns through
turn(); it runs last so no lock can see it, but a probe used for more
than this should not update.
NEXUS-4d — T_CONF 0–3 from n; ATTEND reads it
world.conf(s, a) -> int, an integer 0..3 read off the counters
observe() already keeps. No float, nothing new in the fast path.
n == 0 → 0 never seen
n == 1 → 1 one observation
n >= 2, alt_n == 0 → 2 consistent
n >= 8, alt_n == 0 → 3 very consistent
alt_n > 0 → 2 at most, whatever n is
The cap is the point: a row that was ever contradicted must not read
like a row nothing ever argued with, however often the winner won.
known() stays yes/no, pair() stays.
The ring's own table, 256 turns
36 rows of T are non-empty: 32 read 2, 4 read 3, none read 0 or 1. The 3s are not the interesting cells — they are the two limit cycles the agent grinds on:
| cell | action | → cell | n | alt_n | conf |
|---|---|---|---|---|---|
| 6 | approach | 7 | 12 | 0 | 3 |
| 7 | avoid | 6 | 12 | 0 | 3 |
| 18 | approach | 19 | 53 | 0 | 3 |
| 19 | avoid | 18 | 49 | 0 | 3 |
| 5 | approach | 7 (JUMP) | 4 | 0 | 2 |
| 21 | approach | 23 (JUMP) | 4 | 0 | 2 |
| 0…4, 8…17, 20, 23…31 | approach | i+1 | 4–6 | 0 | 2 |
So conf measures evidence, not geometry: the JUMP rows are the ones
that carry structure i±1 does not know, and at 256 turns they have
only been walked four times each, so they read 2. Given more turns they
do climb — at 1024 turns cell 5 → 7 is n=15 → 3, cell 21 → 23 is
n=14 → 3. Nothing was extended in the bench to make that happen.
The stale transition
Phase F moves the threat 7 → 8, so cell 7's kind changes, its WSP changes, and its CAM slot changes. The row that gets contradicted is the JUMP itself:
cell 5 approach pred=slot 2905 (old cell 7) n=4
alt =slot 711 (new cell 7) alt_n=2 conf 2, not 3
conflicts goes 0 → 2. KCC keeps the winner (alt_n=2 is not > n=4), so
predict() is unchanged and no fact was destroyed — but the row is now
disputed and conf says so. That is the whole difference between "T
knows" and "T knows well", and it is the first row in this lab where the
two answers differ.
ATTEND reads it — and stays exactly as quiet
attend(..., t_conf: int = 0). t_known is still the bool (conf >= 1);
t_conf is the quality behind it. One guard: if the map is decent
(>= 2) on a turn that is hit + extract + not novel + q_confident, do
not wake for no_map. It can only ever silence NO_MAP — it cannot turn
an off into a wake, and every other reason keeps its wake.
--cortex wakes: 36/256 — was 36/256. Unchanged, measured on both
sides of the commit. Over the full 352-turn bench, cortex_calls is 40
before and 40 after. Reasons: off 220, low_C 30, novel 4,
no_map 2.
The guard never fired, and that is reported, not hidden. In this
wiring t_known = settled ∨ (predict ≠ None) on the same (slot, action)
t_conf is computed from, so t_conf >= 2 implies t_known and the
new branch is never the deciding vote. A test counts it: 0 turns of 256
had conf >= 2 with an unknown row. The guard becomes load-bearing when
a row can be known and bad — a known row whose conf is 1, or one in
dispute — which is the inverse model's slice, not this one. T_CONF is
correct and measured now; the switch is not yet paid by it.
SETTLED_HITS 2 and C_PLASTIC 20 untouched. E is still in the
attend signature and still unread. Default bench intact:
policy_after [-12, +5, 0], false_facts 0, destroyed 0,
cortex_calls 0, encoder_beats_fnv, t_beats_ring,
novel_approach_return +80.
Tests 100 → 111.
NEXUS-4e — I(s, s') → a on observed neighbours
world.inverse(s, s_next) -> Optional[int]. Per slot, a short list of
[s_next, action, count], written by observe() before T's KCC decides
anything: what was taken to reach s' is a fact about the step, not about
which s' currently wins the row.
Same KCC discipline as T — highest count wins, a second action for one
landing does not displace the first until it strictly beats it, a tie
keeps the incumbent. One fallback, and only one: if s' is s and wait
has been observed from s at all, the answer is wait. With no evidence
the answer is None. A standstill is never guessed to be approach.
KPI: I(s, T(s,a)) == a where conf >= 2
After 256 turns, 36 of 36 rows recover their action. Not a subset — every row of T with evidence.
| action | rows with conf ≥ 2 | recovered |
|---|---|---|
| approach | 31 | 31 |
| avoid | 3 | 3 |
| wait | 2 | 2 |
| total | 36 | 36 |
After the full bench (phase F + the 32-step probe) it is 39 of 39.
JUMP does not come back as wait
The row the whole thing is for: cell 5 approach lands on cell 7,
skipping 6. i+1 arithmetic would call that landing something else; the
inverse says approach, from evidence.
slot(cell 5) entries: [(cell 7, approach, 4)]
inverse(slot 5, T.predict(slot 5, approach)) = approach
Same for cell 21 → 23. Both asserted, both against wait explicitly.
Size: not 4096²
inv_size 36 entries after 256 turns, 41 after the full bench
non-empty slots 31
max per slot 3 (cap INV_MAX_ENTRIES = 8)
inv_dropped 0
4096 x 4096 16 777 216 — the table that was not built
Three distinct successors is the most any slot in this ring has, so 8 is
slack rather than a budget. An observation that would not fit is counted
in inv_dropped, not silently forgotten; a topology that needs more
shows up as a number instead of as a wrong answer.
Phase F: the conflict is forward-only
Phase F moves the threat, cell 7's kind changes, and it gets a second
CAM slot — so cell 5's jump acquires a second landing and T's row goes
into dispute (conflicts 2, alt_n 2 against n 4, conf capped at 2,
see NEXUS-4d).
cell 5 approach winner slot 2905 (cell 7, THREAT) n=4 inverse → approach
alt slot 711 (cell 7, EMPTY) alt_n=2 inverse → approach
The action was approach both times. The disagreement lives in T
(which s'), not in I (which a) — the inverse is right about both
landings and had nothing to unlearn. That is a property of this
particular change, not a general claim: nothing here says the agent
understood the new world from one observation, and the test asserts only
that the losing landing answers a or None, never a fabricated third
action.
Untouched
conf(), ATTEND, SETTLED_HITS, C_PLASTIC, parse_reply, the viewer,
plan.py. Bench length still 256. Default intact: policy_after [-12, +5, 0], false_facts 0, destroyed 0, cortex_calls 0,
encoder_beats_fnv, t_beats_ring, novel_approach_return +80.
--cortex wakes 36/256, unchanged.
Tests 111 → 119.
NEXUS-4f — Escalation 0 vs 2 via ΔQ. Default still Q.
qtable.margin(s) = best − runner-up, integer, 0 on a tie, never
negative. escalate.level(margin, …) returns a rung:
0 Q the policy's argmax margin > ESC_MARGIN
1 T reserved, NOT wired — T already runs every turn in the loop,
so a rung that only consults it would label work already done
2 SEARCH plan.best over T, depth 2 margin <= ESC_MARGIN
3 ASK reserved — PREGUNTAR stays Agent._ask(), the one path to the
oracle, fired before the action is chosen. Not a fourth gear.
4 CORTEX reserved — that decision is ATTEND's. escalate does not
duplicate attend() and must not be read as replacing it.
ESC_MARGIN = 4, in Q units. Agent(escalate=False) by default;
bench --escalate writes lab/nexus0_escalate_report.json. Exploration
(full tie or ε) never reaches the planner — asserted turn by turn.
search_differs is not 0. It is also not worth anything.
--escalate, 352 turns:
esc_margin 4
level_q 18 level_search 231
search_copies_q 48 search_differs 183
search_strictly_better 0
183 disagreements, 0 of them the planner scoring its own action
higher. So differs alone flatters rung 2, and a third counter was
added rather than reporting two. Every disagreement is one shape:
q_row = (1, 0, 0) plan.scores = (4, 0, 4)
q.best = approach plan.best = wait
plan.scores ties approach with wait, and plan.best's documented
tie-break picks wait. That is a tie-break, not a route.
The sweep, on the default trajectory
Counterfactual — what rung 2 would say at each exploit turn of the unmodified bench, computed without acting on it, so the trajectory is identical in all three rows:
| ESC_MARGIN | level 0 | level 2 | copies | differs | strictly better |
|---|---|---|---|---|---|
| 2 | 66 | 62 | 22 | 40 | 0 |
| 4 | 59 | 69 | 29 | 40 | 0 |
| 8 | 4 | 124 | 84 | 40 | 0 |
The same 40 turns, at every threshold. Moving ESC_MARGIN buys more searching and not one different-on-the-merits decision. 4 is reported because it splits the exploit turns roughly in half; nothing rests on the choice.
So: in this ring, thinking more copies Q. The external review's point 3 closes
with a number. It is not "the planner is useless" in general — it is
"this corridor has nothing at depth 2 that Q has not already backed up
one cell at a time", which is the same wall NEXUS-2 hit and the same
reason plan_depth stays 0.
The cost of acting on a tie-break
The --escalate arm waits where the default approaches, so it walks a
different ring and learns a weaker policy: policy_after [-12, +5, 0] →
[-7, +5, 0], novel_approach_return +80 → +48. Approach still
loses at the threat and negative_delta_causes_policy_change still
holds — the escalate run is checked against the same policy lock as the
default, not exempted from it. But the deeper look paid for itself in
neither direction.
Untouched
plan.py (module stays — it is still what reaches c10 from c9 and c11
from c8), inverse, conf, ATTEND, SETTLED_HITS, parse_reply, the
viewer. Default intact: policy_after [-12, +5, 0], false_facts 0,
destroyed 0, cortex_calls 0, level_search 0,
novel_approach_return +80, t_beats_ring, encoder_beats_fnv.
--cortex wakes 36/256.
Tests 119 → 130.
NEXUS-4g — N ≠ S ≠ δ
Three different facts about one turn, counted apart. They were one word
before: novelty in loop.py was cam_miss OR cell.kind == NOVEL, so
the NOVEL cell read novel on every visit forever, and q.update()'s
return value was thrown away at the call site.
N first turn ever STARTED on this origin.
Not the cell's kind: after the first hit, standing on cell 11 again
is not a miss.
S T had a prediction and it was wrong: pred_next is not None and
pred_next != slot_n. A T_MISS is ignorance, not surprise, and is not
counted. Slot inequality; the Hamming distance stays where it was,
in prediction_hamming_error.
δ the integer q.update() returns, counted when non-zero.
Default bench, 352 turns
cam_misses 33 surprises 1 deltas 217
novel_no_surprise 33 surprise_known 1 delta_only 208
Three different numbers, as they must be. 33 places seen for the first
time, exactly one turn where the map was wrong, 217 turns where the
policy moved. novel_no_surprise == cam_misses: on a first visit T has
no prediction, so a new place is never also a surprise — that is the
definition working, not a coincidence.
S without N is phase F at cell 5, not an extra slot. The threat
moves 7 → 8; cell 5 is still EMPTY, same 16 B packet, same CAM slot
1517. Its JUMP now lands on a different packet, so T is wrong there:
conflicts rises, alt_n goes 0 → 1, and KCC keeps the incumbent
(n=4 against alt_n=1) — the learning is observe(), not an allocation.
destroyed stays 0.
The landing does get a new slot: empty-7 is a new packet, so it is a new CAM entry. That is the destination's N, at a different cell, and the test says which is which rather than papering over it.
One definition had to be measured, not assumed
s.hits == 0 never fires: navi10_cam.Cam.put() seeds hits = 1 on a
fresh slot, and that module is import-only. cam.get(wsp) < 0 before
allocation does not work either — the loop allocates the landing slot
every turn, so a cell is in CAM before the agent has ever stood on it,
and by that reading only 2 of 352 turns are misses. That measures
the allocator, not the agent. N is s.hits <= 1 after _slot_for:
put() seeds 1, this loop adds 1 per turn started here, so ≤ 1 is exactly
"allocated, never stood on". 33, and the first two readings are recorded
here so the choice is auditable.
store_priority keeps its rule (curiosity = N ∧ S ≥ 4) and now reads
the clean N instead of the conflated one. It is not in the report JSON
and was not added to it.
Untouched
attend(), escalate.level() — this is reporting, not control.
plan.py, escalate.py, inverse, conf(), parse_reply, view.py,
thought.KEYS, SETTLED_HITS, C_PLASTIC, ESC_MARGIN. Bench length
still 256 + 64 + 32. Default intact: policy_after [-12, +5, 0],
false_facts 0, destroyed 0, cortex_calls 0, level_search 0,
novel_approach_return +80, t_beats_ring, encoder_beats_fnv.
--cortex wakes 36/256.
Tests 130 → 136.
NEXUS-4h — the oracle is outside. The env no longer answers.
RingEnv.ask() is gone. It used to return the extract and clear
hidden_bit in one call, so physics and testimony were the same object
and source="oracle" was a label rather than a provenance. The order is
now three separate things, in this order and no other:
oracle.reply() raw text from outside. Does not touch the env,
Q, T or CAM.
oracle.accept(text) VERIFY. NOVEL_CLEAR, or None. Prose, empty, JSON,
multi-line and a hex WSP are all None.
RingEnv.reveal(x) physics, and only physics — reached only after
accept passed. An extract it does not recognise
changes nothing.
oracle.accept is deliberately not cortex.parse_reply. The cortex
emits hypothesis packets; a sensor emits an extract. A reply that passes
one must not pass the other, and a test asserts exactly that on
"YO → PREGUNTAR → DESCONOCIDO @AHORA": parse_reply takes it,
accept refuses it.
Provenance, not a label
| packet | source |
|---|---|
| cell 11 | nexus0-env — the ring perceived the cell |
| PREGUNTAR | sensor (default) or file (--oracle PATH) |
Never nexus0-env on the question, never sensor on the cell. Asserted
both ways.
Default bench, unmoved
oracle_source sensor oracle_path None oracle_rejected 0
questions 1 novel_approach_return +80
cam_misses 33 surprises 1 deltas 217 surprise_known 1
policy_after [-12, +5, 0] false_facts 0 destroyed 0 cortex_calls 0
The question fires on the same turn as before — only who answers changed — so the N/S/δ numbers of NEXUS-4g are identical, and the test asserts the four of them by value rather than by inequality.
The default answerer is an in-process callable that knows one thing
(oracle.Sensor). It is a stub and is named one: a file the bench
writes for itself and then reads back would be theatre. --no-ask is
silence, not a mute oracle — Agent.oracle is None, questions 0,
oracle_rejected 0, novel_approach_return −80.
An outside answerer, opt-in
bench --oracle PATH reads one line. Writes
lab/nexus0_oracle_report.json, never the NEXUS-0/1 report.
| answer file | rejected | novel return | false_facts |
|---|---|---|---|
NOVEL_CLEAR |
0 | +80 | 0 |
| prose | 66 | −80 | 0 |
| missing | 66 | −80 | 0 |
A chatty or absent oracle leaves the trap armed, writes no has_fact
from that reply, and takes no CAM slot for it. 66, not 1: the
"asked at most once" guard is keyed on the cell's cleared extract, so a
refused answer means the question is still open and gets asked again on
the next visit. That is the behaviour, stated rather than smoothed —
questions in a refused arm counts attempts, not answers.
+80 is no longer the environment congratulating itself. It is what
happens when something outside the world says one checkable word, VERIFY
accepts it, and only then the trap disarms.
Untouched
T_CONF, inverse, escalate.py, plan.py, N/S/δ (still report, not
control), attend(), parse_reply, view.py, SETTLED_HITS,
C_PLASTIC, ESC_MARGIN. No HTTP, no LLM, no NAVI_CORTEX_CMD, no
stdin. Bench length unchanged. --cortex wakes 36/256.
Tests 136 → 145.
NEXUS-4i — another corridor
The experiment the ring could not be: the same agent, the same 16 B WSP, the same three actions, a world that is not 32 cells, and a T/Q carried in from a world that no longer holds.
HOME A → B → C → D D = GOAL, C = EMPTY
SHIFT A → B → X → D X = THREAT, C is off the path
Successor by table, never (i+1) % n. A, B and D keep their index
and their kind, so they keep their packet, their CAM slot, their T rows
and their Q rows. C and X are two different cells, not one cell wearing
two kinds — no cell is repainted, and a test asserts
home.kind == shift.kind. That is the whole difference from phase F,
which was a repaint. approach at D collects the goal and returns the
agent to A; without it a goal you can stand on is a reward pump.
No NOVEL, no JUMP, no oracle bait. _ask() stays wired and never fires:
there is no KIND_NOVEL here, and questions is 0.
Agent(env=…); the default is still RingEnv(). The 352-turn ring bench
is untouched, and nexus0/corridor.py writes
lab/nexus0_corridor_report.json — a test greps the module to be sure
the string nexus0_report.json does not appear in it.
The two numbers
Knows vs does not know, on entry to SHIFT:
| T(B, approach) | conf | unseen of {A,B,D} | |
|---|---|---|---|
| transferred | C (known) | 3 | 0 |
| control (Q=T=0) | None | 0 | 3 |
First visits paid inside the shift window: transferred 1 (only X,
the genuinely new cell), control 4. surprise_known over the window:
transferred 14, control 0 — the control cannot be surprised
because it never predicted anything.
The first wrong step, in another world
Turn 97, the first approach out of B in SHIFT. Origin slot unchanged,
origin not counted as a CAM miss, surprise_known rose, conflicts rose,
KCC kept the incumbent on one observation, and X is a new packet (the
destination's N). Phase F's shape, in a world that is not the ring.
KPI 4 did not come out as predicted — and was not tuned
The prompt expected approach at B to stop winning after the hit on X.
It does not, and it converges there (checked out to 512 shift turns):
Q[B] home [4, 0, 0] → shift [1, 0, 0] best: approach, still
Q[X] shift [-3, +5, 0] approach negative, avoid pays
The policy does bend where the world moved — Q[B, approach] falls by
75 % and Q[X, approach] goes negative. It does not turn into refusal,
because in SHIFT the only route to D runs through X, and avoid at a
threat pays R_AVOID_OK, so max Q[X] stays positive and B's backup
with it. The agent learns "enter X, then avoid", not "never enter X".
The corridor reuses the ring's own _reward unchanged; nothing was
retuned to make an assertion pass, and the test asserts what was measured.
What this is not
This is not AGI. It is a corridor: five cells, one topology change, one transferred table. What it shows is narrow and stated narrowly — a T learned in one world is still right about the part of the next world that did not change, wrong about the part that did, and it finds out by being wrong rather than by being told.
Ring default, untouched
policy_after [-12, +5, 0] · novel_approach_return +80 · surprise_known 1
cam_misses 33 · surprises 1 · deltas 217 · oracle_source sensor
false_facts 0 · destroyed 0 · cortex_calls 0 · level_search 0
RingEnv has no ask() · --cortex wakes 36/256
Tests 145 → 153. MVP watch: python3 -m echoai.nexus0.demo. The
situated program that was signed off is closed. What follows (sleep, a real
LLM behind the firewall, a body, Akida) is another project.
CORTEX-1 — the slow clock changes a decision (2026-09-03)
The first slice outside the situated program. Not a slice of the ring:
the ring never needed a cortex, and the planner proved it
(search_strictly_better = 0).
Until now, if cortex.parse_reply returned UNIR / TEMER / OBSERVAR, the
loop threw it away; only PREGUNTAR did anything, through _ask().
That missing wire was the whole slice. There is no LLM in it, and that is
the point: the claim is the cable, not the brand of the model.
The world (nexus0/sign.py)
Two cells. Cell 0 is KIND_EMPTY in both layouts and carries a sign
in Spanish; cell 1 is GOAL in one layout and THREAT in the other.
| layout | the sign on cell 0 | approach from cell 0 |
|---|---|---|
| prize | AMBAR: entra. El siguiente sitio es el premio. |
+16 |
| trap | AMBAR: no entres. El siguiente sitio es una trampa. |
−16 |
encode_cell(0, EMPTY) is 020e0408001400280a00000005000000 in both.
The fast loop's entire view of that cell — packet, flat Q row, empty T —
is byte-identical in the world that pays +16 and the world that pays −16.
It does not read Spanish, so greedy it waits. That is not a limitation
being worked around; it is the experiment.
The numbers (lab/nexus0_cortex1_report.json, turn 0, greedy)
| fast | cortex | |
|---|---|---|
| prize | wait, 0 |
approach, +16 |
| trap | wait, 0 |
avoid, 0 (not −16) |
CortexROI +16 decision_change true questions 0
cortex_calls 2 cortex_rejected 0 cortex_actions 2
false_facts 0 destroyed 0
ATTEND woke by REASON_LOW_C — no new reason code, no new wake in the ring
ring --cortex 36/256, unchanged
The trap arm is the half that cannot be faked. A cortex that always said
UNIR would collect the +16 in prize and walk into the −16 in trap; the
report and test_stub_trap_avoids both catch that, and a mutation run
confirmed it (5 tests red when the rule is made constant).
What the cortex is allowed to do
It proposes. _propose consumes last_cortex_action, hands it to
gate() exactly like Q's answer, and the gate can still veto it — an
inhibited slot turns the proposal into wait and the +16 is not
collected. The cortex writes no CAM slot, no T row and no Q cell: after
the prize turn, CAM holds the two cells that were stood on and nothing
else, and the slot's extract is the world's sign verbatim, with no atom
of the reply in it. has_fact means a sign was there, never the sign
told the truth.
One guard is worth naming. cortex.propose() hands the observation back
unchanged when it has no hypothesis, and an EMPTY cell's own packet
already reads OBSERVAR while a THREAT's reads TEMER — both in
ACTION_REL. Treating that echo as advice would let the ring take orders
from its own perception. Only a reply that came from outside and
survived parse_reply sets a proposal, which is why the ring with
--cortex still has cortex_actions = 0.
The cortex of CI (nexus0/tools/sign_cortex.py)
Twenty lines, no imports from echoai, no network, no weights. Reads the
turn JSON on stdin, matches the sign, prints one line of atoms — or
prints nothing when there is no sign, which is a real answer: away
from cell 0 the reply is silence, parse_reply rejects it, and Q decides
(cortex_rejected_8 = 1 in the 8-turn probe). A cortex that always
speaks is a cortex that cannot be wrong.
nexus0/cortex.gbnf is the grammar a future llama.cpp would be
constrained to. Nothing in CI runs it.
What is still unmeasured
The 4B was not run. Latency on this host, tok/s, the reject rate under GBNF and the CortexROI of a real model are four numbers this slice does not have, and they are the whole content of the follow-up report, with host and quant labelled.
Second honest limit: ATTEND stops waking after the sign cell settles
(flat row, hits >= ATTEND_SETTLED_HITS), so the cortex steers turn 0 and
then falls silent. That is ATTEND doing its job — not thinking always —
not the cable failing, and the KPI is turn 0 by design.
Tests 155 → 173. lab/nexus0_report.json untouched; CORTEX-1 writes
lab/nexus0_cortex1_report.json only.
GUI-1 — the board (2026-09-03)
A circuit board, not a 3D brain. nexus0/board.py turns a
thought.jsonl into one self-contained HTML file: CSS and JS inline,
the turns embedded as a JSON array, zero CDN, zero network, no WebSocket,
no tail. It opens over file:// with no server.
python3 -m echoai.nexus0.bench --thought
python3 -m echoai.nexus0.board echoai/lab/thought.jsonl
→ lab/board.html 112 321 bytes · 352 turns · 32 cells
The ANSI demo stays. CI does not need a browser, and nothing here
replaces view.py or demo.py.
The log grew from 12 keys to 17
The board cannot paint what the log does not carry, and loop.turn was
computing five numbers and dropping them on the floor. thought.KEYS now
adds exactly:
n 0|1 CAM miss on the origin this turn (n_fact)
s 0|1 T predicted and was wrong (s_fact)
delta 0|1 Q.update returned non-zero (d_fact)
cortex_action null|0|1|2 the proposal _propose consumed
gate_decision 0|1|2 OK / MODIFY / BLOCK
Integers and null. No prose, no nested events[] — a viewer can derive a
timeline from these and cannot derive these from a timeline. The twelve
older keys keep their name, their order and their meaning; a test pins
KEYS[:12]. cortex_action is read in turn() before _propose
consumes it, so it is the proposal that actually got a vote, and it is
null on every turn of the ring.
The flags are not a second, looser accounting: over the 352-turn bench
sum(n) = cam_misses = 33, sum(s) = surprises = 1,
sum(delta) = deltas = 217, asserted.
What the board shows, and what it cannot
Eight blocks, each showing this turn and not a metaphor for it: CAM
(hit / MISS), WSP (the packet as atoms), T (ŝ' or —, red when S fired),
Q (three integer bars, blue when δ ≠ 0), GATE (ok / modify / block),
ATTEND (on/off + reason), CORTEX (the proposal, dashed grey — a
hypothesis, never solid), ORACLE (whether questions rose).
The ring is drawn from the log alone, rebuilt from turn 0 on every frame:
a cell nobody has stood on yet is ?, not a guess, and scrubbing
backwards un-reveals. That is why the phase F threat move shows up
exactly when it happened — turn 120 has T at cell 7, turn 300 has it at
cell 8 — rather than being seeded from RingEnv.
The world's size comes from the log (max(cell) + 1), never from
N_CELLS: a sign.py log draws two cells, not thirty empty boxes.
board.py imports argparse, json, pathlib, sys and nothing else.
It does not import loop, qtable, world, attend or navi10, and
loop.py gained zero imports — no Qt, no second bus. Both directions
are pinned by tests that read the source as text.
One honest gap. The five permitted keys contain no per-turn reject
flag, so a cortex that woke and said nothing looks the same on the board
as one whose reply parse_reply refused. Both read "woke and proposed
nothing", and the legend on the page says so. Inventing a sixth key was
not in the contract; if that distinction is wanted it is a key, not a
guess in the viewer.
Locks
policy_after [-12, +5, 0] · novel_approach_return +80 · surprise_known 1
cam_misses 33 · surprises 1 · deltas 217
false_facts 0 · destroyed 0 · cortex_calls 0 · cortex_actions 0
ring --cortex 36/256 · CORTEX-1 sign green · thought.KEYS = 17
Tests 173 → 185.
WORLD-3 — 256 addresses, one edge moves (2026-09-04)
nexus0/graph.py. Not the ring, not the corridor of five, not SIGN. The
first body-map: the animal's position is the node. Same Agent, same
16 B WSP, same three actions, same encode_cell(index, kind). What is new
is the address space.
256 nodes · successor by table, never (i+1) % n, never a 2D grid
spine 16 shared nodes, index and kind identical in both layouts
HOME ... -> B(174) -> C(187) -> GOAL(200) -> back to START(5)
SHIFT ... -> B(174) -> X(251) -> GOAL(200) -> back to START(5)
Exactly one edge differs: approach out of the fork B. Counted, not
asserted — test_only_one_edge_differs walks all 3 × 256 rows of both
tables and the diff is [(approach, 174)]. C is EMPTY and X is THREAT:
two nodes with two kinds, not one node repainted. Nothing changes its
label, so every packet, CAM slot, T row and Q row survives the move. In
HOME nothing points at X; in SHIFT nothing points at C. approach at
GOAL collects and returns to START — a goal you can stand on and
re-collect is a reward pump, not a world.
The transfer, without money in it
On entry to SHIFT, with CAM/T/Q carried and nothing reset but the world:
T rows known on the 16 spine nodes 16
still true of HOME 16 / 16
still true of SHIFT 15 / 16
the one that is not spine position 13 = the fork
control (Q/T/CAM empty), rows known 0
spine nodes unseen on entry transfer 0 control 16
That is the whole claim, and it costs no reward to state: fifteen of the
sixteen rows learned in HOME describe the new world exactly, and the agent
is wrong only where the world changed. The first greedy turn in SHIFT
proves it from the other side — T commits to C, the world hands back X,
surprise_known rises, the origin slot is unchanged and is not a CAM
miss, and X is a new packet. Same shape as the corridor's first wrong
step, in a world of 256 addresses.
transfer_gain = +256, and what it is made of
transfer_reward_sum_shift 256 (64 greedy turns from the fork)
control_reward_sum_shift 0
transfer_gain +256
transfer_goal_hits_in_shift 0
transfer_avoid_pays_in_shift 32 x R_AVOID_OK(8) = 256
Read it exactly as it is. The carried agent walks into X, its Q row for a
node it has never seen is flat, greedy answers wait, and the gate
turns that into avoid at a threat — which pays R_AVOID_OK. It bounces
B ↔ X for the whole window and never reaches GOAL in SHIFT. The
control has a flat row everywhere, so greedy answers wait and it does
not move at all. So the gain is real reward on the same seat, the same
seed and the same number of turns, and it is not a goal: it is the
difference between an agent with a policy and an agent with none. Both
halves are asserted in test_transfer_gain_positive so the number cannot
be read as more than it is. No reward was retuned; R_SUCCESS /
R_THREAT / R_AVOID_OK are the ring's own.
The credit horizon — why the fork is at spine position 13
Measured on this spine, not assumed. Q is integer with α = 1/4, so a
positive δ below 4 shifts to zero and value stops travelling. Counting
back from GOAL, Q[approach] along the 16-node spine reads:
0 0 0 0 0 0 0 0 0 0 0 0 0 4 8 13 (512 turns; identical at 2048)
Three nodes deep and no further. Eligibility traces (trace=True) do not
extend it — they spread the debt and leave the far spine negative
(-27 -5 -6 …), with greedy answering avoid at the start. So the fork
sits inside the horizon at position 13. Put it at position 1, as first
sketched, and both arms stand still in a flat row: transfer_gain would
be 0 − 0, a KPI that cannot be failed. The mutant was run; it goes red.
CAM, and what 256 does and does not prove
nodes 256 · reachable 17 (16 spine + X) · cam_occupied 17 / 4096
The 239 off-path nodes are their own successor, so the table is total without inventing an edge — and nobody ever stands on them. WORLD-3 does not press CAM. ANTI-ALIAS has nothing to bite on here yet, and this report says so rather than implying otherwise.
What 256 does prove is the packet ceiling. encode_cell writes the node
index into ext.seq as index & 0xFF, so node 256 hands back byte for
byte the packet of node 0. 256 is exactly this encoder's ceiling before
two places become one place — the number ANTI-ALIAS has to move.
test_256_is_the_packet_ceiling pins both halves: the collision at 256,
and 256 distinct packets below it.
Locks
ring default untouched: policy_after [-12, +5, 0] · +80 · surprise_known 1
cam_misses 33 · surprises 1 · deltas 217 · oracle_source sensor
false_facts 0 · destroyed 0 · cortex_calls 0 · questions 0
WORLD-3 cortex OFF · 3 actions · green true
report lab/nexus0_world3_report.json, never nexus0_report.json
Tests 192 → 208. This is not AGI. It is the corridor of five grown to 256 addresses.
GUI-2 — the board, live (2026-09-04)
nexus0/board.py --follow. The same board as GUI-1, watching the log as
it is written.
Why there is a server at all
A page opened with file:// cannot watch a file grow. It has no way to
re-read the disk on a timer, and GUI-1's turns are baked into the
document at write time. So --follow starts a stdlib server on the
loopback and the page asks it, once every 200 ms, for the turns it does
not already hold. That is a design consequence, not a workaround —
the alternative would have been a second protocol, which is exactly what
the contract forbids.
python3 -m echoai.nexus0.board echoai/lab/thought.jsonl --follow
http://127.0.0.1:8765/ poll 200 ms route /turns.json
Two routes and no files: / renders the page, /turns.json?from=N
answers with the turns after N. Nothing on disk is reachable by URL, so
there is no path to traverse — /etc/passwd and /../../board.py are
404, asserted. 127.0.0.1 is not a flag; a board reachable from another
machine would be a service. ?from=N is parsed by hand rather than by
importing a URL library to read one integer, and junk reads as 0 instead
of a stack trace.
The KPI, as a test and not a screenshot
test_the_poll_sees_a_line_that_was_not_there: write a two-turn jsonl,
start the server on a real socket in a thread, GET ?from=0 → 2 turns,
append a third line, GET ?from=2 → exactly that turn, total 3,
cells 8 (the world grew with it), all 17 keys present. Freeze the
reader's cache so the poll only ever answers what was on disk at startup
and four tests go red, that one included.
Run for real, board up before the log existed, against the ANSI demo writing 48 turns:
before loop: waiting = true
27 polls saw new turns: from=0 [0] · from=1 [1,2] · … · from=47 [47]
final total 48 · Ctrl-C exit 0
The loop was never told anybody was watching: the board is a second
reader of the same jsonl a human could tail.
One protocol, and it is the one already in use
new WebSocket 0 EventSource 0 CDN 0 npm 0 Qt 0
<script> tags 1 fetch( 1 http:// in the page 0
loop.py imports gained 0 thought.KEYS 17, unchanged
board.py still imports no part of the fast path — not loop, qtable,
world, attend, cam or navi10 — and loop.py contains none of
board, http, socket, Qt or WebSocket. Both directions are
pinned by tests that read the source as text.
file:// is untouched
test_the_file_board_is_untouched_by_gui2: render_html(recs) and
render_html(recs, live=False) are the same string, and it carries no
fetch(, no /turns.json, no poll loop, no badge. The live page is the
same board and not a second one — same CSS, same JS, with the poll
appended. Verified beyond the test as well: regenerating
lab/board.html after this slice gives a file byte for byte identical
to the one GUI-1 wrote (112 321 bytes, md5 unchanged).
Two things the writer forced
The log is being appended to while it is read, so:
- A torn final line is dropped, not raised — it will be whole on the next poll. A bad line anywhere earlier is a corrupt log and raises.
- The parse is cached on (mtime, size) so a 200 ms poll does not re-read a growing file for nothing, and a test asserts the cache does not hide the next line.
And a log that does not exist yet is waiting, not an error: the board
can be started before the loop. Without --follow a missing log is still
no such log, exit 1.
Locks
policy_after [-12, +5, 0] · +80 · surprise_known 1 · false_facts 0
destroyed 0 · cortex_calls 0 · thought.KEYS 17 · WORLD-3 green
Tests 208 → 221.
CUERPO-1 — a body, not another graph (2026-09-04)
nexus0/body.py. The animal stops being an index in a table: it has a
pose, and orientation is state of the body, not a fourth action. Same
Agent, same three actions, same 16 B packet via encode_cell.
grid 5 x 5, six walls heading 0,1,2,3 = E,N,W,S (y grows north)
pose index = (x + 5*y)*4 + h 100 bins, max index 99 < 256
approach one step the way it faces; into a wall or off the grid it does
not move and the collision pays R_THREAT
avoid turn 90 deg counter-clockwise (E->N->W->S). No translation.
wait nothing moves
y=4 . . . . G start (0,0) facing E
y=3 . # # # . goal (4,4), collect and go home
y=2 . . . . . row y=2 is the corridor
y=1 . # # # . block layout: (2,2) becomes a wall
y=0 S . . . .
cell().index is the pose: there is no second position ledger, which
was the way this slice could have been a costume. kind belongs to the
floor tile, never to the heading — the four poses on the goal tile are
all GOAL and the index separates them. No floor tile is painted
THREAT. The threat here is the collision itself, so cell.threat is
false everywhere and the gate never modifies an action in this room: the
bump's −16 goes into Q unhelped. Nothing is retuned; R_SUCCESS /
R_THREAT are the ring's own.
t_beats_stepper — two arms, and they carry different halves
The baseline is "always one step east", blind to heading and to walls. Both arms score T and the stepper on exactly the same poses.
free-running (run(), nobody helping) T 10/10 stepper 4/10
poses stood on 10 of 76 · stepper answered and missed 0
swept (carried to every pose in turn) T 76/76 stepper 12/76
stepper answered and missed 44
Read honestly: free-running, the win is the cheap half. The animal's own loop hugs the east wall and the top-right column, where "x+1" cannot even state a guess, and an unanswerable guess is scored as a miss. The strong half is the swept arm, where the stepper answers 44 times and is wrong every one of them:
(0,0,N) heading truth (0,1,N) stepper said (1,0,N)
(0,0,W) wall truth (0,0,W) stepper said (1,0,W)
(1,0,N) wall truth (1,0,N) stepper said (2,0,N)
Not a 0.99 world model. It is the JUMP comparison again: arithmetic loses to a table that has felt the exceptions, and here the exceptions are heading and walls.
One tile becomes a wall
approach successors that change, open -> block 4
(1,2,E) (2,2,E) (2,2,W) (3,2,W)
avoid successors that change 0
Turning is body physics and never consults the room, so closing the
corridor changed no avoid row at all. Of the 76 T rows carried into the
new floor plan, 72 are unaffected and all 72 are still true there;
the 4 that are not are exactly the ones the diff names. The room is
harder, not impossible — the left and right columns still join start to
goal, asserted by flood fill.
What the free-running animal actually did
It found a ten-pose loop on its own: east along y=0 to (4,0), one
turn north, up the x=4 column to the goal, collect, home. 42 goal
collections during training — every positive reward in this room is a
collection, since no tile is THREAT and R_AVOID_OK can never be paid.
Greedy stands still, and here is the number
greedy from start, 128 turns: reward 0 · goal hits 0 · poses visited 1
Q[approach] along the free loop: 0 0 0 0 -4 0 0 4 8 13
The same integer credit horizon WORLD-3 measured: α = 1/4 drops a positive δ below 4, value stops about three steps back from the goal, and the start of a ten-pose loop is nine steps away. So the greedy probe waits. The −4 is (4,0,E), the pose facing the east wall — it learned the bump. This is reported beside the KPI and is not the KPI: walking to the goal on purpose is WALK-1, and this is the number that slice has to move.
Locks
poses 100 · reachable 76 (72 in block) · cam_occupied 76 / 4096
false_facts 0 · destroyed 0 · cortex_calls 0 · questions 0 · 3 actions
ring default untouched: policy_after [-12, +5, 0] · +80 · surprise_known 1
report lab/nexus0_cuerpo1_report.json, never nexus0_report.json
Tests 221 → 238. Not MiniGrid, not eight actions, not lidar, not a camera, not Akida. A turtle in a small room.
WALK-1 — reverse curriculum. RED, and here is the number (2026-09-04)
nexus0/body.py --walk, report lab/nexus0_walk1_report.json. The runner
exits 1. The KPI was exam_goal_hits > 0 from the canonical start and
it scored 0. Nothing was retuned and the start was not moved.
What was built, exactly as specified
Reverse curriculum in training only: distance to a collection is flooded backwards over the successor table itself (a turn costs a step, so this is not Manhattan — Manhattan is blind to heading, and heading is half of this body's state), and one agent is trained from distance 1 outward to 12, keeping Q/T/CAM throughout. 14 544 training turns. Then the exam: the same agent, teleported to (0,0,E), greedy, cortex off, 128 turns.
phase d=1 d=2 d=3 d=4 d=5 d=6 …
goals 48 32 42 48 16 0
Q[app] 8 4 0 0 0 0
The curriculum worked. The animal collected goals in every near phase, including from distance 4. It is not an exploration failure.
exam start (0,0,E) · distance to a collection 9
exam goal_hits 0 · reward 0 · bumps 0 · poses visited 1 (it stood still)
Q[approach] along the shortest path from the start:
0 0 0 0 -16 0 0 4 8 13
Why, as arithmetic rather than as an excuse
qtable.update ends in q += delta >> 2. A positive delta below 4 shifts
to zero, so on a chain paying only at the end the value converges to
q_k = (7 * q_(k+1)) // 8 - 3 -> 13, 8, 4, 0, 0, 0, …
credit_chain() in body.py replays that integer expression to its fixed
point — nothing is monkeypatched, qtable.py is untouched — and predicts
[13, 8, 4, 0, 0, …]. The measured best Q[approach] by distance is
{0:13, 1:8, 2:4, 3:0, 4:0, …}. The prediction and the measurement are
identical. Value stops three states from the goal; the start is nine.
A fixed point is not a shortage of turns, and that is asserted rather than
argued: test_more_training_at_the_wall_changes_nothing soaks distances
1–3 for over 10 000 turns and gets 8, 4, 0.
So a reverse curriculum reaches the wall faster. It cannot move it: the wall does not depend on the order states are visited in.
What would move it, and why it was not done
The same chain with no truncation is positive the whole way —
credit_chain(0, 12) = [16, 14, 12, 10, 8, 7, 6, 5, 4, 3, 2, 1, 0]. A
throwaway probe that monkeypatched ALPHA_SHIFT to 0 (never shipped, not
in the repo) ran the identical curriculum and identical exam and scored
goal_hits 12, reward 192 from (0,0,E).
That isolates the blocker exactly: it is the α = 1/4 integer step, not
exploration, not the room, not the curriculum. Every fix for it —
ALPHA_SHIFT, eligibility traces, n-step returns, a residual accumulator
for the truncated remainder — is on this slice's forbidden list, and
rightly: each is an architecture change to the fast path and deserves its
own KPI. This slice does not get to make it quietly.
Traces were tried anyway, as a mutant: the exam moves and still collects nothing, which agrees with what WORLD-3 measured about them.
The tests are honest about it
test_goal_hits_positive asserts the KPI as written and is marked
@unittest.expectedFailure. It is not deleted and not weakened — the suite
reports expected failures=1, and the day somebody moves the wall it
becomes an unexpected success and must be promoted to a plain assertion.
Both ways of faking green are caught twice over. Examining from a pose one
step from the goal turns test_exam_is_canonical_start red and makes
the KPI test an unexpected success; choosing actions from the stored route
turns test_no_hardcoded_path red and does the same. shortest_path()
exists only for the report — test_no_hardcoded_path reads the source of
_exam and asserts it contains no route, no successor table, no pose
constant and no action constant.
Locks
alpha_shift 2 · gamma 7/8 · R_SUCCESS 16 · R_THREAT -16 · R_AVOID_OK 8
traces false · plan_depth 0 · cortex_calls 0 · questions 0
false_facts 0 · destroyed 0
ring default untouched: policy_after [-12, +5, 0] · +80 · surprise_known 1
CUERPO-1 still green · report never touches nexus0_report.json
Tests 238 → 249, OK (expected failures=1).
CREDIT-1 — keep the crumbs. GREEN (2026-09-04)
QTable(remainder=True) / Agent(remainder=True) /
python3 -m echoai.nexus0.body --walk-remainder, report
lab/nexus0_credit1_report.json. α is still 1/4, γ is still 7/8, every
R_* is untouched and the default is off.
The change, in four lines
owed = rem[s][a] + delta
step = trunc_div(owed) # toward zero: 5->1 3->0 -3->0 -5->-1
rem[s][a] = owed - (step << 2)
q[s][a] = clip_i8(q + step)
delta >> 2 threw away three quarters of every small update. Now they
are owed and paid the moment the debt reaches a whole unit: four
deltas of 3 pay 3 units instead of 0.
trunc_div is not >>. Python's shift floors, so (-5) >> 2 is -2
where +5 >> 2 is +1 — a negative delta would pay more than it owed and
the remainder would go on owing credit already spent. Truncation toward
zero keeps the two signs symmetric. Integers throughout.
The debt is bounded by construction: |rem| < 4 always, because a whole
unit is paid the instant it exists. Measured peak during the whole
curriculum: 3. At the fixed point delta is 0 and nothing is owed, so
the end-state remainder is 0 along the path and non-zero in 26 of
4096×3 cells.
At the int8 rail the credit is dropped rather than accumulated — a debt against a value that cannot move would grow without bound.
The KPI
Same curriculum, same exam, same canonical start, same rewards, same α. The only difference is the flag.
WALK-1 (off) CREDIT-1 (on)
exam_start (0,0,E) (0,0,E)
exam_goal_hits 0 12
exam_reward_sum 0 192 = 12 x R_SUCCESS
exam_bumps 0 0
exam_poses_visited 1 10 the ten-pose loop
green False True
Q along the shortest path from the start, where WALK-1 had seven zeros:
off [ 0, 0, 0, 0, -16, 0, 0, 4, 8, 13]
on [ 3, 4, 5, 6, -10, 9, 11, 13, 15, 18]
Positive the whole way, so greedy walks. The −10 at index 4 is (4,0,E), the pose facing the east wall: it still knows the bump, and the exam records 0 bumps because the policy turns there instead.
Predicted before it was run, from the integers alone:
credit_chain_with_remainder(12) = [16, 14, 12, 10, 8, 7, 6, 5, 4, 3, …]
— the chain loses the − 3 and stays positive past nine steps.
credit_chain(2) is untouched and still predicts the wall
[13, 8, 4, 0, …] for anyone who does not pull the lever.
WALK-1 stays red
It is not rewritten, not quietly fixed, and its report on disk still says
green: false, exam_goal_hits: 0. body --walk still exits 1, and
test_goal_hits_positive is still @unittest.expectedFailure
(OK (expected failures=1)). test_walk1_without_remainder_still_red is
the control and asserts it.
The ring, asked and answered
Yes — the remainder moves the ring's locks. It is measured here and it
is not enabled. Probed by patching Agent in a throwaway script;
bench.py has no remainder flag and is not getting one:
remainder=False policy_after [-12, +5, 0] deltas 217
remainder=True policy_after [-11, +12, 0] deltas 218
both +80 · surprise_known 1 · false_facts 0 · cortex_calls 0
t_beats_ring True
The firewall and the epistemics do not move; the policy numbers do.
That is exactly why this is opt-in and why the default bench never sees
it. Off means no array at all — rem is [], and update takes the
branch it always took.
Traces and the remainder raise ValueError together: two answers to the
same question, pick one.
Locks
default: policy_after [-12, +5, 0] · +80 · surprise_known 1 · 33/1/217
false_facts 0 · destroyed 0 · cortex_calls 0 · remainder False
alpha_shift 2 · gamma 7/8 · R 16/-16/8 · traces false · plan_depth 0
CREDIT-1 report never touches nexus0_report.json or the WALK-1 red one
Tests 249 → 260, OK (expected failures=1).
PULPO — a 3D body. Three legs green, one RED (2026-09-04)
nexus0/body3d.py, report lab/nexus0_pulpo_report.json. The runner
exits 1. Three of the four things asked for are measured green; the
fourth, the change of strategy, is red for two reasons that are both
numbers.
3 x 3 x 3 voxels, 6 headings index = (x + 3y + 9z)*6 + h 0..161
approach one voxel the way it faces; into stone or off the cube it does
not move and pays R_THREAT
avoid h -> (h+1) % 6 over +X, +Y, +Z, -X, -Y, -Z. No translation.
wait nothing moves
161 < 256, so all 162 poses are distinct packets; 4³·6 = 384 would alias,
which is why the cube is 3. encode_cell(0) still equals
encode_cell(256) and that is left alone.
The room is two corridors that share only the corner they start and end at, with no voxel of one touching the other:
short (0,0,0) (1,0,0) (2,0,0) (2,1,0) (2,2,0) (2,2,1) (2,2,2) 8 actions
long (0,0,0) (0,0,1) (0,0,2) (0,1,2) (0,2,2) (1,2,2) (2,2,2) 18 actions
block (2,1,0) becomes stone. The long way is untouched.
Green: a 3D body that walks
exam from (0,0,0,+X), greedy, cortex off, remainder on, 512 turns
open 56 collections · reward 896 · 9 poses · reached (2,2,2)
moved on all three axes
T 70/70 correct · stepper 4/70 · stepper answered and missed 38
(heading and stone, with examples)
false_facts 0 · destroyed 0 · cortex_calls 0 · questions 0 · CAM 70/4096
CREDIT-1 is what makes this possible; with the plain >> 2 it stands
still, exactly as WALK-1 measured in 2D.
Red: it does not take the detour
block 0 collections · reward -32 · visits (1,0,0) and (2,0,0), bumps
into the plug, and stops
Reason one, arithmetic. The detour is 18 actions from the start and the value horizon is 11 — computed from the shipped integers, cycle feedback included, and the geometry search below could not beat it.
Reason two, exploration, and this is the more interesting one.
qtable.select answers approach on a flat row, always; avoid is only
tried once a row is not flat. The only thing that unflattens a row at a
pose the route must turn at is the −16 of walking into stone. So a
turn in open air is a turn the fast loop can never discover.
open route 2 turns, both against stone learnable
block route 12 turns, 3 of them in open air not learnable
That is also why the 2D body worked: its one turn was at (4,0,E), facing the east wall. It was never a general ability — it was a corner.
What was searched before calling it red
The heading ring is a free choice of labels, and the first one (+X, −X, +Y, −Y, +Z, −Z) learns nothing at all, from any geometry: it costs two turns per corner and the intermediate heading points back down the corridor just walked, so that pose's row stays flat. Regrouping it to (+X, +Y, +Z, −X, −Y, −Z) makes a corner one turn and is what made the open arm work.
Then, exhaustively over the cube: every start, goal, pair of middle-disjoint corridors, and plug.
pairs with both routes under the horizon 1462
of those, with every turn against stone in both layouts 0
with a learnable open route and the goal still connected 695
of those, with a learnable blocked route 0
25 of the best candidates were trained with the real agent rather than
judged on paper: every one gave open > 0, block = 0. A 3×3×3 cube with
a 6-cycle turn has no geometry that satisfies both legs. The cube was not
raised to 4×4×4 (384 > 256) and no geometry was faked.
What was not done
No if block: avoid. No waypoint list — test_no_hardcoded_path reads
the source of _exam and asserts it holds no route, no successor table,
no pose constant and no action constant. No retuned reward, no α change,
no traces, no planner, no cortex. green() still demands
exam_block_goal_hits > 0, so the runner reports the failure, and
test_exam_block_goal_hits_positive is @unittest.expectedFailure —
when either blocker moves it becomes an unexpected success and has to be
promoted.
What would move it
Not more turns and not a bigger reward. Either the exploration rule has
to be able to try a turn from a flat row, or the value horizon has to
reach past 11. Both are changes to the fast path, both would move the
ring's [-12, +5, 0], and both deserve their own KPI and their own
opt-in flag — the way CREDIT-1 got one.
Locks
ring default: policy_after [-12, +5, 0] · +80 · surprise_known 1 · 33/1/217
false_facts 0 · destroyed 0 · cortex_calls 0 · remainder False
alpha_shift 2 · gamma 7/8 · R 16/-16/8 · traces false
report never touches nexus0_report.json, cuerpo1, credit1 or the walk1 red
Tests 260 → 274, OK (expected failures=2).
PULPO-C — reorientación (verde)
nexus0/body3d.py, nexus0/qtable.py (select), nexus0/loop.py.
Runner: PYTHONPATH=. python3 -m echoai.nexus0.body3d → rc 0.
El tapón cobra. Mismo cuerpo, mismo Q, misma T, misma CAM, tres acciones,
córtex apagado, examen greedy desde (0,0,0,+X).
open 64 cobros · reward 1024 · 8 poses · ejes [0,1,2] · llegó a (2,2,1)
block 42 cobros · reward 672 · 12 poses · usó el desvío · 0 choques
control 64 / 0 ← el mismo horario con la palanca apagada
T 56/56 · stepper 4/56 · el stepper contestó y falló 30 veces
El cuerpo (geometría nueva, dentro de 3×3×3)
corto (0,0,0) +X +X +Y +Y +Z (2,2,1) 5 movs + 2 giros = 7
largo (0,0,0) +Y +Y +Z +X +X (2,2,1) 5 movs + 6 giros = 11
tapón (2,1,0), en medio del corto. El largo intacto.
Los dos corredores comparten solo START y GOAL, y ningún voxel de uno toca un voxel del otro: no hay tercera ruta escondida. 162 poses, 161 < 256, el techo del paquete sigue medido y no subido — 4³·6 = 384 aliasea.
El desvío mide 11 y value_horizon() mide 11. No es coincidencia:
es la restricción. Con la palanca encendida la cadena entera de Q sale
exactamente como la predice la aritmética entera, y el START se lleva la
última unidad que el descuento puede cargar.
dist 11 10 9 8 7 6 5 4 3 2 1 0
Q 1 2 3 4 5 6 7 8 10 12 14 16 margen 1 en el START
Las dos palancas
1. explore_flat en qtable.select. Opt-in, default False, memoria
extra cero, update() sin tocar. Con la palanca apagada una fila
plana contesta approach siempre, así que un giro solo se descubre donde
avanzar duele: contra piedra. Con ella encendida la fila plana entra en el
mismo ciclo turn % 3 que ya tenía una fila no plana. best() decide
igual todos los turnos greedy y todos los de explotación.
2. Curriculum inverso también en block. _curriculum(ag, layout, turn) está parametrizado por layout y usa poses_by_distance(layout);
no nombra ninguna ruta. Tras el tapón se vuelve a pasar sobre el cubo que
el cuerpo tiene ahora. El teleport es ayuda de entrenamiento: el
examen siempre sale del START canónico.
El presupuesto de turnos por teleport — medido, no elegido
episode_turns(d) = N_ACTIONS * (d + 2) + 1. Dos propiedades, las dos
descubiertas rompiéndose:
- Largo.
d + 2es el presupuesto greedy y basta para un cuerpo que no explora. Uno que explora gasta turnos en las otras dos acciones:d + 2cobra 0 en el examen open donde3·(d+2)cobra 47. - Coprimo con
N_ACTIONS. El contador de turnos corre a través de los teleports, así que un episodio de un número entero de ciclos ofrece a cada pose la misma acción cada repetición. Medido en la pose donde el desvío tiene que girar: con3·(d+2)recibiówaitveinte veces,approachveinte veces yavoidninguna. El+ 1hace precesar la fase.
Giros al aire, que era el bloqueo real
ruta open 2 poses de giro, las dos contra piedra → aprendible sin palanca
ruta block 6 poses de giro, 2 al aire libre → NO aprendible sin ella
(0,0,0,+X) y (0,2,1,-Z): avanzar sigue funcionando ahí
Por eso el brazo abierto ya andaba y el desvío no. El informe lleva las dos preguntas con la palanca y sin ella, y el control corre entero cada vez en vez de estar escrito en prosa.
Mutantes
palanca ignorada en select 6 rojos · body3d rc=1
palanca ON por defecto 1 rojo · body3d rc=0 · bench rc=0
episodio = d+2 (presupuesto greedy) 6 rojos · body3d rc=1
episodio = múltiplo de N_ACTIONS 5 rojos · body3d rc=1
sin readaptación tras el tapón 5 rojos · body3d rc=1
tapón en el corredor largo 6 rojos · body3d rc=1
desvío 12 (START mirando -Z) 6 rojos · body3d rc=1
El último es la prueba del horizonte por medición, no por argumento: todo
igual, la palanca encendida, el desvío una acción más largo, y
exam_block_goal_hits cae de 42 a 0.
Dos cosas que hay que decir claras
La palanca encendida por defecto no mueve el anillo. Medido:
policy_after [-12, +5, 0], +80, 33/1/217, surprise_known 1,
false_facts 0. Sigue apagada por defecto porque el contrato del anillo
está medido sobre el select que siempre tuvo, no porque lo rompa.
El brazo abierto se paga. Tras el curriculum en block, el examen en
el cubo abierto cobra 42 por el desvío, no 64 por el atajo: el cuerpo se
queda con la última ruta que le enseñaron. Está en el informe como
exam_open_goal_hits_after_readapt. No es KPI y no baja ninguno; es un
número que hay que ver, y es el olvido que le toca a ECHO-1, no a este
tajo.
Lo que no se hizo
Ni if block: avoid, ni waypoints, ni lista de poses —
test_no_hardcoded_path sigue leyendo _exam, y
test_the_curriculum_names_no_route lee además _curriculum. Ni R, ni
α, ni γ, ni trazas, ni n-step, ni planner, ni córtex, ni 4ª acción, ni
4×4×4. green() no se debilitó: se le añadieron condiciones
(detour_cost_block <= value_horizon, > short_cost_open,
explore_flat).
test_exam_block_goal_hits_positive deja de ser @expectedFailure y
pasa a aserción normal. WALK-1 sin resto sigue rojo a propósito y es el
único expectedFailure que queda.
Corrección de contabilidad
t_before metía filas que T nunca aprendió (predict → None) y luego
las contaba como rotas por el tapón. Ahora solo lleva lo que T sabía:
t_rows_carried 56, unaffected 52, still_true 52.
Locks
ring default: policy_after [-12, +5, 0] · +80 · surprise_known 1 · 33/1/217
false_facts 0 · destroyed 0 · cortex_calls 0 · questions 0
remainder False · explore_flat False · traces false
alpha_shift 2 · gamma 7/8 · R 16/-16/8 · CAM 59/4096
bench rc=0 · body rc=0 · body --walk rc=1 (rojo a propósito)
body --walk-remainder rc=0 · graph rc=0 · sign rc=0 · demo rc=0
Tests 274 → 278, OK (expected failures=1).
Grok 2026-09-04: suite re-corrida Ran 278 tests OK (expected failures=1);
banco default policy_after [-12, 5, 0] novel_approach_return 80
33/1/217 surprise_known 1. Cierre verde. Siguiente tajo: SELF-1.
SELF-1 — ¿fui yo o fue el suelo? (verde)
nexus0/self1.py (nuevo), más dos líneas en nexus0/loop.py.
Runner: PYTHONPATH=. python3 -m echoai.nexus0.self1 → rc 0.
Primer tajo de ECHO-1. Córtex apagado, 3 acciones, sin LLM.
Dos perturbaciones que producen la misma frase: approach no cayó
donde T decía.
motor el brazo resbala: approach empuja por DIRS[(h+slip)%6] y el
heading de la pose NO gira. Cubo, suelo, goal y START intactos.
world un voxel del pasillo se hace piedra (el tapón (2,1,0)). Motor sano.
Ninguna se anuncia. El paquete de una pose es byte-idéntico en los dos
brazos, el extract y el threat también. Cero átomo CONCIENCIA, cero
segundo CAM, cero cuarta acción, cero domain como etiqueta.
El resultado
agente control
body_offset motor 1 0 (slip = 1)
body_offset world 0 0
self_attribution 1 0 gap = 1
T del cubo conservada 56/60 46/60 (brazo motor)
55/60 55/60 (brazo world)
examen motor 46 cobros 46
examen world 42 cobros 42
Examen greedy desde (0,0,0,+X) canónico, remainder on. El rastro del
brazo motor:
(0,0,0,+X) (0,1,0,+X) (0,2,0,+X) (0,2,0,+Y) (0,2,1,+Y) (0,2,1,+Z)
(0,2,1,-X) (0,2,1,-Y) (0,2,1,-Z) (1,2,1,-Z) (2,2,1,-Z) → cobra
Mirando a +X empuja +Y. Compensa con giros y se va por el corredor
largo, que con el brazo torcido sale más barato. Nadie se lo dijo, y no
hay ningún if slip: avoid — distances sobre la tabla del env perturbado
da 10, bajo el horizonte 11, que es la única razón por la que puede.
La hipótesis, escrita en acciones y no en coordenadas
Un entero, seis hipótesis:
body_offset ∈ 0..5 "mi approach empuja por (h + body_offset)"
El cuerpo puede formularlas porque ya tiene el anillo: «empujar por (h+k)» es exactamente «girar k, empujar, y desgirar», y T aprendió las dos filas antes de que nada se moviera. Cero coordenadas, cero tabla de sucesores, cero geometría.
El giro de vuelta no es adorno: un brazo torcido empuja de lado y deja el
cuerpo mirando donde ya miraba. avoid^k ; approach solo describe a un
cuerpo que giró, que es otro animal — con esa versión explained sale
[0,0,0,0,0,0] y no se atribuye nada. Medido antes de arreglarlo.
La regla: uno solo, y sin contraejemplos
Un offset es una afirmación sobre todas las sorpresas, no sobre la
mayoría. Sobrevive el k que explica al menos una y no contradice
ninguna; si sobreviven varios, el veredicto es 0.
motor explained [0,23,10,6,12,10] contradicted [24,0,12,15,12,13] → k=1 solo
world explained [0, 2, 0,2, 2, 2] contradicted [ 2,0, 0, 0, 0, 0] → 4 supervivientes → 0
Que el brazo world produzca una multitud no es suerte: sus dos sorpresas
son el cuerpo chocando, y un choque lo explica todo offset que también
acabe en piedra. Una pared no es un brazo, y la regla de unicidad dice
exactamente eso.
Las contradicciones se cuentan entre los empujones que sorprendieron.
Contadas sobre todos, el único movimiento que ningún offset puede
describir —el cobro, donde la sala coge al cuerpo y lo devuelve a START—
entierra al offset verdadero: contradicted_any_motor[1] = 8. Está en el
informe como número, no como excusa.
El argmax no es la constante 1
probe_slip2_offset corre la misma maquinaria con slip=2 en cada
ejecución y contesta 2. Está en green().
Lo que el control hace peor, y lo que no
El control es el mismo agente sin modelo: cada sorpresa entra en T al instante. Recupera igual de bien (46 y 42 cobros) — no es un tonto. Lo que pierde es el mapa: 46 de 60 filas contra 56, porque reescribió como mundo lo que era su propio brazo.
En el brazo world agente y control son idénticos (55/55), que es lo
correcto: ahí el suelo sí cambió y no hay nada que atribuirse.
Un mutante que sobrevivió, y por qué
«Sin retención: T se reescribe en cada sorpresa» → 0 rojos, rc=0. Suspender la sorpresa hasta el veredicto es el orden que la lógica quiere, pero no es lo que salva el mapa:
t_world_kept_with_holding 57
t_world_kept_without_holding 57 holding_changes_the_map = false
La KCC de world.py ya hace ese trabajo: no destrona a un sucesor con el
que un curriculum entero estuvo de acuerdo hasta que el rival lo supera en
cuenta, y una ventana de 360 turnos no se acerca. Medido: 32 conflictos
levantados, 3–4 filas volteadas, y son las de cuenta baja (mediana 10,
máximo 1913).
Lo que sí carga el KPI es la protección después del veredicto —
mutarla mata 3 tests y pone rc=1. La retención se queda porque es el
mismo camino de código y porque es el orden correcto, y el informe lleva
holding_probe corrido de verdad cada vez para que el día que empiece a
importar el número se mueva solo.
Mutantes
offset clavado a 1 en vez de argmax 5 rojos · self1 rc=1
sin unicidad: gana el primer superviviente 4 rojos · self1 rc=1
el modelo asentado deja de proteger T 3 rojos · self1 rc=1
hipótesis a medias (sin desgirar) 5 rojos · self1 rc=1
hook del modelo puenteado en el loop 6 rojos · self1 rc=1
sin retención durante la ventana 0 rojos · rc=0 ← sobrevive
Lo que se tocó del anillo
Dos líneas en loop.py: body_offset = 0 / self_model = None en
__init__, y
if self.self_model is None or self.self_model.observe_step(slot, a, slot_n):
self.t.observe(slot, a, slot_n)
Sin modelo la expresión corta en el primer término y el turno es el de
siempre. Agent() por defecto: explore_flat False, body_offset 0,
self_model None.
Self1Env(open, slip=0) es Body3DEnv(open) paso a paso — comprobado
sobre las 162 poses × 3 acciones, recompensa, índice y bumped.
Lo que no se hizo
Ni bit MOTOR en kind/style/extract, ni segundo cable WSP, ni
if n_errors > K. test_attribution_reads_poses_and_nothing_else lee el
fuente de hypothesis, _tally, observe_step y _attribute y prohíbe
formas de uso (.kind, .extract, KIND_, .slip, .layout,
DIRS[, pose_of(, index_of(, last_reward, .env), no palabras: la
prosa puede nombrar lo que el código tiene prohibido leer. No se tocó
PULPO-C — nexus0_pulpo_report.json byte-idéntico antes y después del
runner, comprobado en test.
Locks
ring default: policy_after [-12, +5, 0] · +80 · surprise_known 1 · 33/1/217
false_facts 0 · destroyed 0 · cortex_calls 0 · questions 0 · CAM 60/4096
remainder False · explore_flat False · body_offset 0 · self_model None
bench rc=0 · body3d rc=0 · body rc=0 · body --walk rc=1 (rojo a propósito)
graph rc=0 · sign rc=0 · demo rc=0 · self1 rc=0
Tests 278 → 290, OK (expected failures=1).
Grok 2026-09-04: suite re-corrida Ran 290 tests OK (expected failures=1);
banco [-12, 5, 0] +80 33/1/217. Cierre verde. El cobro del control
es igual (46/42): lo que compra el self-model es no reescribir T
(56>46), no llegar antes. Siguiente tajo: ROOM-1. GUI-3 (voxeles + placa)
después de ROOM-1.
ROOM-1 — dos habitaciones, un mapa (verde)
nexus0/room1.py, lab/nexus0_room1_report.json, TestRoom1 (12).
Segundo tajo de ECHO-1. Córtex apagado, tres acciones, 16 B.
El cubo de PULPO-C, dos veces. Lo único que distingue una habitación de la otra es un entero en el env:
env.frame ∈ {0, 1}
Fuera del paquete. encode_cell escribe index & 0xFF en ext.seq y
nada más, así que una pose lee los mismos dieciséis bytes en A y en B.
324 sitios, 162 paquetes. CAM 4096 no es el tamaño del mundo: es el
número de estados cognitivos, y dos sitios que se comportan igual son un
estado. Comprimir, no un bug — y se paga en números.
El KPI
frames 2 · local_poses 162 · places 324
packets_in_A 162 · packets_in_B 162 · packets_across 162
domain 0 · style 0 · seq == pose local · encode_cell(0)==encode_cell(256)
dynamic_alias 0 (y 0 también sobre la tabla entera)
t_known_in_B_before_a_step 56/56 control scratch 0
turns_in_B_before_the_exam 0
examen greedy START canónico en B 64 cobros (en A: 64)
control scratch en B 0 cobros transfer_gain 64
cam_occupied_A 59 · cam_occupied_AB 59 · scratch 1 · CAM 4096
false_facts 0 · destroyed 0 · cortex_calls 0 · questions 0
Reportado, no KPI: un agente escolarizado en B desde cero llega al
mismo 64, y le cuesta 29 580 turnos. El transfer cuesta 0. El control
scratch no es un espantapájaros por falta de turnos: es que sin haber
visto A su fila es plana y best() prefiere wait, así que se queda
quieto en el START — su rastro está en el informe.
turns_in_B_before_the_exam es el cerrojo del tajo entero: si B cobrara
por haber entrenado en B, el KPI sería una tautología. Se mide con el
contador de turnos del propio agente, y green() exige 0.
DynamicAlias: lo que cuesta compartir cuando las salas difieren
Mutante corrido, no narrado: el tapón de PULPO en B, A abierta.
packets_in_B_with_hole 162 los paquetes siguen compartidos
dynamic_alias_with_hole 2 poses pisables en ambas
dynamic_alias_with_hole_table 4 filas `approach` que difieren
domain sigue 0
T contra la sala agujereada 48/50 (era 56/56)
examen en B con agujero 0 cobros
Mismo WSP, dinámica distinta: el cuerpo cruza el corredor corto contra la
piedra y no cobra nunca. No se arregla aquí. Meter la sala en
domain rompe I2; meterla en seq pone 324 sitios bajo un techo de 256
y destruye justo lo que este tajo demuestra. El agujero es de ROOM-2, y
el número está escrito.
Tampoco hay portal: una puerta con paquete local-only es un alias en esa celda. A→B es teleport, como en WORLD-3.
Mutantes
la sala viaja en el paquete (índice con zancada) 1 rojo · room1 rc=1
B cobra porque se reentrenó en B 3 rojos · room1 rc=1
el control scratch es el agente entrenado 3 rojos · room1 rc=1
las dos salas no son la misma sala 6 rojos · room1 rc=1
el mutante del agujero no tiene agujero 4 rojos · room1 rc=1
el detector de alias no encuentra nunca nada 3 rojos · room1 rc=1
el examen empieza al lado del premio 2 rojos · room1 rc=1
la sala B se pinta con otro `kind` 8 rojos · room1 rc=1
El primero mata la clase entera en vez de un KPI: un índice con zancada
revienta en loop._ring_slot, que indexa env.kind con ese número. Es
muerte, pero por excepción, no por medida — dicho tal cual.
El séptimo pasaba green() en la primera pasada porque el informe
imprimía START como constante en vez de la pose realmente usada. Se
corrigió: exam guarda la pose con la que empezó. Ahora son 2 rojos y
rc=1.
Lo que no se tocó
Nada del anillo. room1.py es nuevo; Room1Env es subclase de
Body3DEnv — comprobado paso a paso sobre 162 poses × 3 acciones — y
frame es lo único que añade. Ni domain, ni style, ni seq con
zancada, ni kind distinto por sala, ni portal, ni cubo 4³.
encoder.py no sabe qué es una sala: el test lo lee y prohíbe las
palabras frame y room en su fuente. packet_of se comprueba por
co_names/co_varnames, no por texto — la prosa puede nombrar lo que el
código tiene prohibido leer.
Locks
ring default: policy_after [-12, +5, 0] · +80 · surprise_known 1 · 33/1/217
remainder False · explore_flat False · body_offset 0 · self_model None
bench rc=0 · body3d rc=0 · body rc=0 · body --walk rc=1 (rojo a propósito)
graph rc=0 · sign rc=0 · demo rc=0 · self1 rc=0 · room1 rc=0
Tests 290 → 302, OK (expected failures=1). nexus0_pulpo_report.json y
nexus0_self1_report.json byte-idénticos antes y después del runner,
comprobado en test.
GUI-3 — el acuario junto al electrocardiograma (verde)
thought.py (+5 claves), loop.py (5 líneas), env.Cell (+4 campos),
body3d (--thought), board.py (el cubo), TestGui3 (8).
No hay informe: este tajo escribe un diario, no un KPI de conducta.
La placa ya enseñaba el pulso y no al animal. cell es un entero, y la
pose que representa son tres más un rumbo: deducir uno del otro dentro
del visor habría metido la geometría del cubo en un navegador, donde se
queda vieja el día que el mundo cambie. Así que la pose la escribe quien
la tiene.
El diario
thought.KEYS 17 → 22 las 17 primeras, mismo nombre y mismo orden
nuevas x, y, z, heading, frame enteros o null, nunca prosa
anillo las cinco a null (no es 0: no hay tal cosa)
cubo enteros; index_of(x,y,z,h) == cell en todas las líneas
room1 frame 0/1, del env — sigue fuera del paquete
loop.turn las copia con getattr(cell, "x", None) y
getattr(self.env, "frame", None). Cero imports de body3d en
loop.py, y el test lo prohíbe por texto: el bucle corre las mismas tres
acciones esté donde esté, y eso es justo lo que hace que valga.
El paseo
python3 -m echoai.nexus0.body3d --thought rc=0
lab/thought_body3d.jsonl 64 turnos · 64 con pose · 8 cobros
6 voxeles · rumbos {+X,+Y,+Z} · start (0,0,0,+X)
El curriculum son 29 580 turnos de teleport, y un replay de eso es un
bicho parpadeando por el cubo, no un bicho andando. Así que el diario se
abre después de la escuela, para un paseo greedy desde el START
canónico: es _exam con un log colgado y nada más — mismo Q, misma T,
misma CAM, córtex apagado, ningún informe escrito.
La placa
python3 -m echoai.nexus0.board lab/thought_body3d.jsonl
board.html 35 579 bytes 64 turnos 105 celdas cube 3x3x3
27 voxeles · 3 rebanadas · un <script> · cero red
El lado del cubo sale del log, como cell_count sale del log: un diario
que sólo vio 8 voxeles dibuja 2×2×2, no 3×3×3. Los muros no se
dibujan porque nadie se los ha contado — un voxel donde nadie ha estado
es un hueco, que es exactamente como se ve una pared desde dentro de un
cuerpo. board.py sigue sin importar loop, qtable, world,
attend, y ahora tampoco body3d ni room1; el test prohíbe además
FLOOR, BLOCKED, is_floor, pose_of(, index_of(, DIRS.
Log del anillo: ni cubo, ni stage, ni un solo voxel en el HTML, y
la tira sigue en __CELLS__ = 32. No se inventa.
--follow no cambia de protocolo: el mismo GET cada 200 ms. Se le añadió
una pose a un jsonl con el servidor levantado y la tercera pose llega por
/turns.json. La forma del cubo se lee al abrir la página y el estado en
cada poll, así que un tablero vivo abierto antes de la primera línea
con pose necesita recargar para que aparezca el acuario. Está escrito en
el docstring, no escondido.
Mutantes
el visor da por hecho que el cubo es 3 4 rojos
se inventa un cubo para cualquier log 1 rojo
la pose ausente del anillo se escribe como 0 4 rojos
el diario gana una clave de prosa 6 rojos
el bucle importa el cubo para anotar la pose 3 rojos
`--thought` reescribe el informe del pulpo 1 rojo
el diario está abierto durante la escuela 1 rojo
El sexto sobrevivió en la primera pasada con 0 rojos: el experimento del pulpo es determinista, así que reescribir su informe devolvía bytes idénticos y el test no podía verlo. Se arregló comparando bytes y mtime: de un fichero escrito para nada, lo único que queda es que fue escrito.
Lo que no se hizo
Ni Three.js, ni npm, ni CDN, ni Qt, ni WebSocket, ni segundo protocolo.
Ni tabla de paredes copiada en JS. HEADING_NAME se duplica en
board.py a propósito, igual que SYMBOL desde GUI-1: una leyenda para
seis enteros no es un modelo del mundo, e importar el cubo para leerla
haría que el visor dependiera de lo que dibuja. Sin pose en el log, el
HTML es byte a byte el de GUI-1.
Locks
ring default: policy_after [-12, +5, 0] · +80 · surprise_known 1 · 33/1/217
bench rc=0 · body3d rc=0 · body rc=0 · body --walk rc=1 (rojo a propósito)
graph rc=0 · sign rc=0 · demo rc=0 · self1 rc=0 · room1 rc=0
body3d --thought rc=0 · board rc=0
Tests 302 → 310, OK (expected failures=1). Informes de pulpo, self1,
room1 y el anillo intactos tras --thought — bytes y mtime.
GUI-3.5 — la vista, sustituida (verde)
board.py (escenario 3D, diario, Play), body3d.py (--live),
TestGui35 (8). Cero claves nuevas: thought.KEYS sigue en 22, no
hay 4ª acción, no se retuneó nada. El animal es el mismo; lo que cambia
es lo que se ve de él.
GUI-3 dejó la pose en el diario y dibujó con ella tres plantas planas, una tira de 105 casillas que miente sobre la forma del mundo, y un Play que se moría en el turno 64. Eso no se maquilla: se sustituye la vista.
1. Un volumen que se gira
27 .cell3d × 6 caras = 162 caras · un solo #world con preserve-3d
colocadas por el escritor: translate3d((x-1)·44, (1-y)·44, (z-1)·44)
arrastrar → rotateX/rotateY · rueda → scale 0.4–2.6 · "reset view"
Seis <i> por voxel y transformaciones CSS: el compositor del
navegador hace la proyección. Ni Three.js, ni Babylon, ni canvas, ni
shader, ni importmap, ni una sola URL en el HTML. Un voxel donde nadie
ha estado no se dibuja — sigue sin haber tabla de paredes, porque no la
hay que tener.
Las tres plantas siguen ahí como minimapa plegado (<details>), no
como vista. La tira de casillas se oculta cuando hay cubo: es la
misma pose en una dimensión. El anillo, que no tiene otra cosa, la
conserva.
2. El diario, de verdad
12 (0,2,0,+Y) approach YO → UNIR → AQUI T=40 Q[3,-1,0] r=+0 δ=1 s=0
Una línea por turno, las últimas 200, resaltado el actual; rojo si
s=1 (predijo mal), verde si cobró. Sigue la cola sólo si el lector
ya estaba en la cola — si scrollea hacia arriba, se queda donde está,
igual que el slider con el turno.
Cada campo de la línea es una clave del registro. El test extrae el
fuente de tline y prohíbe que sus literales contengan una palabra de
tres letras o más: la única que puede escribir es cell, para un mundo
sin pose. No hay frase compuesta, y no puede haberla — el visor no sabe
por qué el cuerpo giró, e inventarlo sería lo único que un visor no debe
hacer nunca.
3. Una vida que no se acaba
python3 -m echoai.nexus0.body3d --live [--out P] [--pace S] [--turns N]
escuela una vez, luego greedy para siempre; Ctrl-C rc=0
medido: 80 turnos → 10 cobros
El cubo no tiene más espacio que dar: 162 poses es todo. Lo que añade
--live es tiempo. approach sobre el GOAL cobra y devuelve el
cuerpo al START, así que un cuerpo greedy recorre el pasillo otra vez, y
otra, mientras se le deje. No aprende nada que el examen no supiera:
mismo Q, misma T, misma CAM, córtex apagado, y ningún informe se abre
en esa ruta (el test lee el fuente de live_walk). Un run, un log: el
jsonl se trunca al arrancar, porque un replay que empieza con el paseo
de otro miente sobre lo que pasó después.
Y el tablero deja de morirse:
replay estático checkbox "loop", on por defecto → del último vuelve a 0
--follow const LIVE = true → Play espera; el poll empuja el turno
Mutantes
el 3D son tres plantas con un rotate de adorno 1 rojo
la escena no se puede orbitar 1 rojo
el diario compone una frase 1 rojo
el diario lee algo que el log no trae 1 rojo
Play vuelve a morirse en el último turno 1 rojo
la tira de 105 se queda encima del cubo 1 rojo
se inventa un volumen también para el anillo 1 rojo
`--live` para en el presupuesto del examen 1 rojo
Ocho de ocho, y uno rojo cada uno: cada mutante rompe exactamente la afirmación que ataca y ninguna otra. Es lo que se quiere de tests separados, pero se dice tal cual — no hay redundancia de red aquí.
Lo que hay que saber
board.read_live reparsea el fichero entero en cada poll cuando está
creciendo (es de GUI-2, y sigue igual). Con --live a 0.12 s/turno el
diario crece ~2 kB/s: bien para una sesión, no para una semana. No lo
he tocado porque no es este tajo, pero está dicho.
Sexta vez que un ban de palabras caza mi propia prosa: because vivía
en un comentario del JS y el test miraba la página entera. Se cambió
por lo de siempre — prohibir la forma de uso (los literales de tline),
no la palabra.
Locks
ring default: policy_after [-12, +5, 0] · +80 · surprise_known 1 · 33/1/217
KEYS 22 · sin claves nuevas · sin 4ª acción · córtex 0
bench rc=0 · body3d rc=0 · body rc=0 · body --walk rc=1 (rojo a propósito)
graph rc=0 · sign rc=0 · demo rc=0 · self1 rc=0 · room1 rc=0
body3d --thought rc=0 · board rc=0
Tests 310 → 318, OK (expected failures=1). Informes de pulpo, self1,
room1 y el anillo intactos (bytes y mtime).
Nota de banco: lab/thought_body3d.jsonl es un fichero compartido. Correr
la suite y body3d --thought a la vez deja 128 líneas en él y un test
en rojo que no es un fallo del código, sino de quien lanzó las dos cosas
al mismo tiempo. Un run, un log.
OBJ-1 — llevar el vaso (primer corte: el animal verde, el control ROJO)
Superado por OBJ-1b, más abajo. Lo de aquí se queda porque los números son ciertos y el error de razonamiento también: barrí el ítem con la mesa clavada en el GOAL del pulpo y llamé teorema a lo que era una variable congelada.
El pulpo cobra cuando el cuerpo llega a (2,2,1). Este tajo cobra
cuando llega la cosa. Mismo cubo open, mismo START, tres acciones,
mismos enteros, córtex apagado. Archivo nuevo nexus0/obj1.py;
body3d.py no se hincha.
ITEM (2,0,0) DEST (2,2,1) cobro = approach en la mesa con el ítem ahí
Llevar es física, no un bit
approach que aterriza en el voxel del ítem acopla. Mientras
acoplado, el ítem está donde esté el cuerpo. No hay soltar: lo único que
desacopla es entregar. Cero cuarta marcha, cero carrying en los
dieciséis bytes, UNIR sigue siendo approach.
Y llevar se ve. El rápido lee una sola cosa: el paquete de la celda
en la que está. Si acoplado y con las manos vacías pintaran el mismo
paquete, Q promediaría dos futuros en una fila. Por eso el ítem es un
kind:
KIND_OBJECT = 4 YO → OBSERVAR → OTRO E idéntica a la de EMPTY
162 poses EMPTY + 162 poses OBJECT = 324 paquetes CAM 4096
ext.seq = index & 0xFF ext.domain 0 ext.style 0 index_max 161
Estar en el voxel del ítem es llevarlo, porque aterrizar ahí es lo que significa acoplar: "OBJECT aquí" y "acoplado" son un solo hecho visto por dos lados, no dos que haya que mantener sincronizados. El estado (pose, manos) con las manos vacías sobre el ítem no existe, y un test lo recorre entero para probarlo.
La mesa no se pinta KIND_GOAL. Ningún voxel de este mundo lo es, en
ninguna posición del ítem.
La mina que no se pisó
NEXT[open][approach][GOAL_POSES] = START. Reusar el step del pulpo
habría cobrado por llegar, no por entregar. step, _payoff y
reset son propios; DIRS, bumps, is_floor, pose_of, index_of
se importan. Un test camina successor (la aritmética que inunda el
curriculum) contra step (el mundo) en las 114 combinaciones × 3
acciones, para que no puedan separarse.
La escuela y el examen
curriculum inverso, dos etapas ya acoplado, luego soltero
114 estados · 30 fases · 81 960 turnos · remainder on · explore_flat on
camino más corto medido (inundado, no dibujado): 8 acciones ≤ horizonte 11
examen greedy · START canónico (0,0,0,+X) · ítem en (2,0,0) · córtex 0
512 turnos → 64 cobros · reward 1024 · 8 poses · 384 turnos con el vaso
object_hits 64 = collects 64 todos los cobros son object_at_Y
(0,0,0,+X) (1,0,0,+X) (2,0,0,+X) (2,0,0,+Y) (2,1,0,+Y)
(2,2,0,+Y) (2,2,0,+Z) (2,2,1,+Z) → cobra → a casa
La sonda, porque el examen no produce ese turno
agent_at_Y_without_object_reward_sum sale 0 sobre cero turnos: un
cuerpo que va a buscar el vaso llega con él en la mano y nunca se sienta
en la mesa vacío. Un campo que suma sobre nada no puede ponerse rojo. Así
que se pregunta directamente, desde las seis orientaciones:
mesa vacía 0 cobros de 6 suma −64 (cuatro de las seis miran pared)
mesa llena 6 cobros de 6 suma +96 = 6 × R_SUCCESS
el ítem no se movió solo · entregar manda a casa 6/6 y desacopla
El control: ROJO, y el número dice por qué
El control corre entero cada vez — mismo schedule, mismo cubo, _payoff
cobrando agent_at_Y e ignorando el ítem — y luego se examina en el env
real. Debía sentarse en la mesa con las manos vacías. No puede:
control_object_hits 64 (debía ser 0)
mutant_object_hits 64 (debía ser 0)
control_went_to_dest true trail idéntico al del examen bueno
La causa no es el agente. Es la habitación:
cost_to_dest_alone 7 lo que cuesta la mesa a un cuerpo sin nada
shortest_collect − 1 7 lo que cuesta la mesa con el vaso
item_costs_a_detour false
El ítem está en el único pasillo que el horizonte puede pagar, así que recogerlo es gratis: un cuerpo pagado por llegar recorre las mismas ocho acciones y lo recoge de paso, le importe o no. Y no hay otro sitio donde ponerlo. Las ocho colocaciones posibles, tarifadas:
voxel collect desvío cabe en el horizonte
(1,0,0) 8 0 sí (0,1,0) 12 4 no
(2,0,0) 8 0 sí (0,2,0) 12 4 no
(2,1,0) 8 0 sí (0,2,1) 12 4 no
(2,2,0) 8 0 sí (1,2,1) 12 4 no
placements_that_cost_a_detour_and_fit = []
Cuatro cuestan desvío 4 y piden 12 acciones; doce está pasado el
horizonte 11 que midió PULPO. Con la mesa en (2,2,1) no existe
colocación del ítem que sostenga este control — y eso es todo lo que este
barrido prueba: la mesa era la otra variable y la dejé quieta. Ver OBJ-1b. El propio prompt lo declaraba rojo
("si el control también trae el vaso: rojo, no era un control"), y así se
deja: green() mantiene las dos condiciones y obj1 sale rc=1.
Lo que sí separa a los dos cuerpos es la política, no la trayectoria:
control_values_an_empty_table 21 Q[approach] en la mesa, manos vacías
exam_values_an_empty_table 0
Aprendieron habitaciones distintas caminando lo mismo. El que cobró por llegar cree que una mesa vacía vale algo; el que cobró por entregar, no.
Mutantes
la mesa se pinta como premio 3 rojos
el mundo real paga una mesa vacía 4 rojos
el paquete no dice nunca que lleva algo 3 rojos
se reusa la tabla de sucesores del pulpo 3 rojos
la dirección es pose × voxel del ítem 1 rojo (revienta en setUpClass)
el examen empieza en la mesa con el vaso 1 rojo
girar suelta el vaso 6 rojos + 2 éxitos inesperados
teletransportarse encima no lo coge 1 rojo
el vaso es el premio (E de GOAL) 1 rojo
la mano se cuela en ext.domain 1 rojo
el mundo mutante deja de serlo por lo bajo 2 rojos
Once de once. Dos notas honestas: el quinto mata por excepción en
setUpClass, no por una aserción — la mutación es estructuralmente
imposible, no falsamente verde; y el séptimo convierte los dos
expectedFailure del control en éxitos inesperados, que es la prueba
de que esos dos tests siguen vivos y no son decorado.
Los dos últimos existen porque en el primer borrador
coupled_is_env_not_wsp y mutant_pays_empty_Y imprimían True a
pelo: dos campos que no podían discrepar consigo mismos. Ahora el
primero se deriva de la firma de packet_of (dos argumentos, pose y
kind, ninguno es el mundo) más los tres campos ext, y el segundo de
una sonda sobre el mundo mutante — 6 cobros de 6 en una mesa vacía,
donde el real tiene 0 de 6.
Locks
ring default: policy_after [-12, +5, 0] · +80 · 33/1/217
KEYS 22 · sin claves nuevas · N_ACTIONS 3 · córtex 0 · Q sigue [4096][3]
board.SYMBOL gana "O" y nada más; el cubo del pulpo sigue KIND_GOAL
obj1 rc=1 en este corte (rojo en el control; verde en OBJ-1b)
Informes de pulpo, self1, room1 y el anillo intactos (bytes y mtime).
OBJ-1b — la mesa se mueve, no el cubo (verde)
El corte anterior dejó el control rojo y yo lo llamé geométrico. Lo era a
medias. Barrí las ocho colocaciones del ítem con la mesa clavada en
(2,2,1), el GOAL del pulpo, y concluí que en un cubo 3×3×3 no existe
control posible. Un barrido que deja quieta una de dos variables no es un
barrido. Con la mesa suelta, el mismo suelo open, el mismo
value_horizon() = 11, la misma γ y ni un voxel de más:
72 pares (ítem, mesa) sobre el suelo open
18 con collect ≤ 11 ∧ desvío > 0 ∧ el ítem fuera del camino barato
Los tres más holgados, con brazos distintos:
ITEM (0,1,0) DEST (1,0,0) collect 10 alone 1 desvío 8
ITEM (0,1,0) DEST (2,0,0) collect 11 alone 2 desvío 8
ITEM (1,0,0) DEST (0,1,0) collect 11 alone 2 desvío 8
Se pica el primero.
ITEM_XYZ = (0, 1, 0) brazo largo, un paso +Y
DEST_XYZ = (1, 0, 0) brazo corto, un paso +X
Los dos brazos sólo se tocan en START. La mesa está a un approach
de casa; el vaso está en el otro brazo. Un cuerpo que sólo quiere la mesa
llega en un paso y no pasa por el vaso; y si se pasa de mesa, el siguiente
approach sigue el brazo corto hacia (2,0,0), alejándose del vaso.
Pasarse de mesa no coge el vaso: por eso este par y no ITEM (2,0,0) / DEST (1,0,0), que es más corto pero comparte brazo.
Dos cosas que se arreglaron de paso
cost_to_dest_alone ya no llama a cube.distances(). Ese flood corre
al GOAL del pulpo. Mientras la mesa era ese voxel los dos números
coincidían por accidente, y un accidente que deja de valer el día que se
mueve una constante no es una medida. Ahora inunda con la aritmética de
successor de este módulo, sobre la mitad del espacio de estados con las
manos vacías: un paso que aterriza en el vaso sale del grafo en vez de
contar como avance, que es lo único que "solo" puede significar aquí.
item_on_cheapest_dest_path es el mismo flood dos veces: una con el
vaso donde está y otra con la habitación vacía (NO_ITEM, un voxel fuera
del cubo). Si los dos precios difieren, todo camino barato a la mesa pisa
el vaso y ningún control puede llegar sin él. Aquí salen iguales, 1 y 1.
El examen
greedy · START (0,0,0,+X) · ítem (0,1,0) · córtex 0 · 512 turnos
51 cobros · 51 object_hits · los 51 son object_at_Y · reward 816
ciclo de 10 acciones: 512 // 10 = 51
(0,0,0,+X) (0,0,0,+Y) (0,1,0,+Y) (0,1,0,+Z) (0,1,0,-X) (0,1,0,-Y)
(0,0,0,-Y) (0,0,0,-Z) (0,0,0,+X) (1,0,0,+X) → cobra → a casa
Giro al aire en START: en (0,0,0,+X) el approach también anda —es el
brazo corto—, así que la fila está plana y sólo explore_flat la abre. Es
el mismo palo de PULPO-C y aquí también cobró. Luego media vuelta sobre el
vaso (tres avoid, dos de ellos contra pared) y de vuelta a casa.
El control, que ahora falla como debe
control_object_hits 0
control_collects 0 en el env real, llegar no paga
control_went_to_dest true
control_turns_holding_the_item 0 no lo tocó ni una vez
control_trail (0,0,0,+X) (1,0,0,+X) (2,0,0,+X) … vuelve y se sienta en la mesa
Mismo schedule, mismo cubo, mismo curriculum inverso; lo único distinto es
que su _payoff cobra agent_at_Y e ignora el ítem. Va a la mesa, empuja,
no cobra nada, y sigue empujando. El niño se sienta en la mesa.
mutant_collects 256 en su propio env: 2 acciones por cobro
mutant_object_hits 0 los 256 con el vaso donde estaba
mutant_pays_empty_Y true sonda 6/6 en el mutante, 0/6 en el real
Q sigue diciendo lo mismo que en el corte anterior, y sigue fuera de
green():
control_values_an_empty_table 65 Q[approach] en la mesa, manos vacías
exam_values_an_empty_table 0
Mutantes
Los once de OBJ-1, sin añadir un duodécimo. Todos siguen matando, y con la mesa movida muerden más:
la mesa se pinta como premio 5 el examen empieza en la mesa 4
el mundo real paga una mesa vacía 6 girar suelta el vaso 5
el paquete no dice que lleva algo 3 teleportarse encima no coge 1
se reusa la tabla del pulpo 2 el vaso es el premio 1
la dirección es pose × voxel del ítem 1 la mano se cuela en ext.domain 2
el mundo mutante deja de serlo 3
Once de once. El quinto sigue matando por excepción en setUpClass.
Tests que cambiaron
DEST_XYZ == body3d.GOAL_XYZ retirado: era el candado
los dos @expectedFailure promocionados a aserciones
"el control trajo el vaso" borrado
obj1.main rc=1 invertido a rc=0
poses (1,0,0)/(2,1,0) del layout viejo derivadas del flood
la pose del ítem en el trail "aparece", no dibujada
test_landing_on_it_is_holding_it ya no escribe una sola coordenada:
busca la entrada al vaso preguntándole a successor, gira hasta que el
frente está abierto —el cubo decide cuántas veces— y toma la orientación
ciega de bumps. El replay desciende el mapa de distancias en lugar de
"la primera acción que mueve", que funcionaba por suerte.
Locks
ring default: policy_after [-12, +5, 0] · +80 · 33/1/217
KEYS 22 · N_ACTIONS 3 · Q [4096][3] · córtex 0 · γ y horizonte intactos
suelo open sin tocar · ni un voxel nuevo · techo 256 intacto
escuela 114 estados · 95 880 turnos · remainder on · explore_flat on
paquetes 162 EMPTY + 162 OBJECT = 324 · cam_occupied 114 · index_max 161
obj1 rc=0
OPEN-1 — una cuarta marcha, porque hay una cuarta física (verde)
El vaso de OBJ-1, con tapa. Hasta que la tapa no sale, el vaso no se coge
de ninguna manera: se puede estar encima con las manos vacías, empujar,
girar y esperar, y sigue cerrado. Esa es toda la licencia para un verbo
nuevo — hay exactamente una cosa en la habitación que sólo ABRIR cambia,
y en ninguna otra habitación existe. Archivo nuevo nexus0/open1.py;
obj1.py sólo se importa.
ITEM (0,1,0) DEST (1,0,0) las constantes de obj1, sin mover
cerrado aterrizar encima no acopla · estar encima es legal
ABRIR sobre el frasco cerrado, y sólo ahí · r = 0
abierto la física de OBJ-1, tal cual, entrega incluida
Lo que ve el rápido
KIND_CLOSED = 5 YO → OBSERVAR → CERRAR E idéntica a la de EMPTY
KIND_OBJECT YO → OBSERVAR → OTRO en la mano, como OBJ-1
162 EMPTY + 162 OBJECT + 162 CLOSED = 486 paquetes · CAM 4096 · ocupadas 120
ext.seq = index & 0xFF · domain 0 · style 0 · index_max 161
ABRIR (átomo 25) no aparece en ningún paquete que este mundo emita,
y hay un test que lo recorre entero: el paquete dice qué se está mirando,
nunca qué hacer al respecto. El verbo es una marcha, y una marcha no vive
en dieciséis bytes de percepción. La mesa sigue sin ser KIND_GOAL.
La cuarta columna es un flag, no una constante
constants.N_ACTIONS sigue 3. QTable, Agent, World y gate
toman n_actions, todos con default en la constante congelada, y sólo
este módulo pide cuatro. Agent().q.row(0) mide 3.
Cuatro sitios habrían devuelto el verbo a approach dejando el tajo
verde-de-mentira. Los cuatro son mutantes, no frases:
gate a = proposed % 3 ABRIR entra 3 y sale 0
qtable.select turn % N_ACTIONS la marcha nunca se ofrece
qtable.is_explore row[0]==row[1]==row[2] una fila de 4 que empata en 3
se lee como decidida
World T de ancho 3 ABRIR se graba encima de approach
El fallo que costó el primer run: aritmética de calendario
El primer examen cobró 0 y se quedó quieto en START. No era la tapa,
ni γ, ni el horizonte. Una fila ya no plana sólo explora cuando
turn % EPS_PERIOD == 0, así que la marcha que se le ofrece es
(8·k) % n_actions:
3 marchas 0, 2, 1, 0, 2, 1 las tres (8 y 3 son coprimos)
4 marchas 0, 0, 0, 0, … approach y nada más
136 120 turnos de escuela y ABRIR no se probó ni una vez en ningún
estado decidido. Arreglado tomando la fase de la cuenta de turnos de
exploración (turn // EPS_PERIOD) cuando los dos módulos comparten
factor, y dejando el turn de siempre cuando no — así una tabla de ancho
3 responde 0, 2, 1 exactamente como antes, y hay un test que lo fija.
Una fila plana explora todos los turnos, así que ahí turn % n_actions ya
es denso y se deja igual.
Mismo motivo para episode_turns propio: 4(d+2)+1 es impar, y un
impar nunca divide un ciclo de cuatro. El 3(d+2)+1 de PULPO es par para
d impar y habría escondido dos marchas de cuatro.
El examen
greedy · START (0,0,0,+X) · frasco cerrado en (0,1,0) · córtex 0 · 512 turnos
46 entregas · 46 object_hits · 47 aperturas · reward 736 · 9 poses
ciclo de 11 acciones = el horizonte entero, 512 // 11 = 46
(0,0,0,+X) avoid → (0,1,0) approach → 3 avoid → ABRIR → approach
(0,0,0,-Y) → 2 avoid → (1,0,0) approach → cobra → a casa, con la tapa puesta
Once acciones es exactamente value_horizon(). Cabe y no sobra: una más y
la aritmética entera deja de ver el premio desde START. Nada aquí se tocó
para que quepa.
El control y el mutante, corridos
control 3 marchas, mismo frasco, mismo schedule
object_hits 0 · used_open 0
went_to_the_jar FALSE ← no salió de casa
best_q_in_the_shut_room 0
puesto encima del frasco, 32 turnos greedy: la tapa sigue puesta,
y no lo coge
mutante frasco nacido abierto, 3 marchas
object_hits 51 · used_open 0 · el mismo 51 de OBJ-1
control_went_to_the_jar sale false, no true. El motivo se mide, no se
excusa: con tres marchas ningún estado de la mitad cerrada de este mundo
lleva a nada que pague, así que todas las filas de ahí quedan planas,
best() responde wait y el cuerpo no tiene razón para moverse —
best_q_in_the_shut_room = 0. Eso es cierto pero flojo como prueba: un
cuerpo que no sale no ha demostrado que no sepa abrir. Así que se le lleva
al frasco y se le pregunta allí, donde el verbo es lo único que ayudaría.
Treinta y dos turnos greedy encima de la tapa: no la quita y no coge nada.
Esa sonda sí está en green().
El mutante cierra el argumento por el otro lado: sin tapa, tres marchas bastan, y no usó el verbo ni una vez porque no hacía falta. Su espacio de estados es el de OBJ-1 voxel por voxel, y un test camina los dos.
Mutantes
gate pliega el verbo en approach 3 ABRIR abre desde cualquier sitio 3
select cicla la constante congelada 3 abrir paga 3
una fila decidida vuelve al turn crudo 3 entregar deja la tapa fuera 2
un empate de 4 cuenta sólo 3 1 el frasco cerrado se lee abierto 3
T pliega el verbo sobre approach 1 el Agent default gana 4 columnas 2
aterrizar en el cerrado lo coge 3 el episodio comparte factor 1
Doce de doce.
Dos defectos propios, encontrados por los tests
Obj1Env.step hace su reset a mano y no sabe de tapas, así que entregar
dejaba la tapa fuera: el mundo se convertía en OBJ-1 después del primer
cobro y cada episodio entrenaba la mitad de sus turnos sobre un frasco que
no estaba. Y successor forzaba HELD en la salida de obj1.successor,
de modo que la entrega parecía dejar el vaso todavía en la mano. Ninguno
cambiaba las distancias, pero los dos eran falsos.
Un candado mío que estaba mal puesto
La suite completa dio un rojo que en aislado no salía: un test de
OBJ-1 fijaba len(KIND_NAME) == 5. Eso no era el invariante. Lo que OBJ-1
tiene derecho a exigir es la numeración —KIND_OBJECT es 4 y los
cuatro de antes no se mueven— no que la tupla no crezca nunca. Cambiado a
KIND_NAME[:5] y a los cinco números; lo mismo en OPEN-1 con [:6], para
que el próximo tajo con física nueva no tropiece con esto otra vez.
Lo que hay que saber
board.py tiene su propia ACTION_NAME de tres, y está bloqueado en este
tajo. open1 no escribe diario, así que hoy no existe ningún jsonl con
action=3; el día que exista, el tablero pintará undefined ahí.
thought.ACTION_NAME sí ganó "open", que es lo que view.py indexa
directamente y lo que habría reventado con un IndexError delante de un
humano.
Locks
ring default: policy_after [-12, +5, 0] · +80 · 33/1/217
N_ACTIONS 3 · Agent().q.row(0) mide 3 · World default 3 · gate default % 3
KEYS 22 (ACTION_NAME no es una clave) · córtex 0 · γ y horizonte intactos
suelo open sin tocar · techo 256 intacto · obj1 sin tocar salvo importar
escuela 120 estados · 136 120 turnos · remainder on · explore_flat on
open1 rc=0 · tests 330 → 342, OK (expected failures=1)
SIGN-C — la contradicción es la razón (verde completo)
Revisión independiente 2026-09-05: verde completo. SIGN-Cd pasó nueve pruebas focales, los bancos
bench/signc/graph/open1/obj1y 392 pruebas completas. La repetición real con Qwen diogreen=true: 6/8, paráfrasis 4/6 vs 0, ambos canónicos, ROI +16, 8 llamadas, 0 rechazos y 0 hechos falsos. Siguiente TALK-1.
PYTHONPATH=. python3 -m echoai.nexus0.signc · lab/nexus0_signc_report.json
WORLD-3 ya había enseñado que T sobrevive a un cambio de topología y se equivoca en un solo sitio. CORTEX-1 ya había enseñado que una frase que el bucle rápido no sabe leer puede cambiar una decisión. SIGN-C es la unión, y el orden es la afirmación entera: el letrero se copia en el origen y se lee sólo después de que el mundo haya contradicho el mapa.
entrena en HOME 512 turnos, córtex apagado, WORLD-3 tal cual
entra en SHIFT misma CAM, misma T, misma Q, una arista movida
en la bifurcación Q dice approach con margen 4 ( = ESC_MARGIN )
T dice que el sucesor es C, conf 3
el mundo ha puesto ahí un letrero en español
actúa el mundo devuelve el otro nodo
sólo entonces REASON_CONFLICT, una vez, con la frase del origen
turno siguiente el átomo se gasta y el gate sigue teniendo la última palabra
El cierre causal, medido
origin_was_known_to_t true t_pred = slot de C, conf 3
origin_was_not_a_cam_miss true hits 33
q_margin_at_origin 4 ESC_MARGIN 4 · best = approach
world_language_at_origin true
t_prediction_contradicted true aterriza en 251, no en 187
attend_reason conflict
conflict_wakes 1 cortex_calls 1 — la única llamada
proposal_consumed_next_turn true
action_at_landing approach reward 16
control (sin córtex) wait reward 0
CortexROI 16 ventana de 8 turnos: 32 vs 0
Dos cosas salieron contra el plan, y están como números
1. En un peligro el bucle rápido no necesita córtex. El nodo previsto
era la X de WORLD-3, un THREAT, con el argumento de que una fila plana
espera y no cobra mientras avoid paga +8. No es cierto. loop.turn
le da a una celda THREAT E[U] = 80, U_SHORT es 60, y la segunda regla
MODIFY del gate convierte ese wait propuesto en avoid antes de que
el mundo lo vea. El brazo rápido cobra los +8 solo.
landing fast cortex (con el letrero que allí es cierto) ROI
THREAT avoid +8 (gate) avoid +8 0
GOAL wait 0 approach +16 16
Esa tabla está en el informe (placements) y se recalcula en cada corrida:
la elección del nodo de aterrizaje es un número, no un gusto.
2. TEMER no puede cobrar en este mundo. Los dos únicos pagos
positivos son approach en un GOAL (+16) y avoid en un THREAT (+8), y el
segundo ya se lo lleva el gate gratis. Así que el letrero dice lo que el
mundo realmente cambió —la palanca se invirtió, lo que era trampa ahora es
premio— y el átomo que sale de él es UNIR. Verbo de los tres que este
banco tiene: ni cuarta acción, ni recompensa nueva, ni arista nueva. La
tabla de sucesores es graph.NEXT[shift] importada literal y el único byte
que SIGN-C posee es el kind del nodo al que llega la arista movida —un
nodo inalcanzable en HOME en cualquiera de los dos casos.
El control que sí discrimina
El prompt pedía un control sin córtex; hay tres brazos, y el tercero es el que más dice:
fast sin córtex wakes 0 reward_at_landing 0
signc conflict_attend on wakes 1 reward_at_landing 16 reason conflict
ladder córtex on, conflicto off
wakes 1 reward_at_landing 0 reason low_C
ladder es el mismo córtex, el mismo cable, el mismo mundo. Despierta en
el nodo de aterrizaje por LOW_C —fila plana, un solo hit— y no gana nada,
porque la razón por la que despierta lleva el registro de ese nodo,
cuyo extract es un rótulo. El letrero está en el origen, y sólo el
despertar por conflicto vuelve a por él. La razón no es decoración.
La causalidad temporal, y por qué CONFLICT no es una rama de attend()
attend() corre antes de la acción. Todas sus razones son cognoscibles en
ese momento: el paquete, la ranura, la fila de Q, la fila de T. Una
contradicción no lo es —existe sólo cuando el mundo ha respondido— y
meterla como rama sería darle a ese predicado un dato del futuro. Así que
REASON_CONFLICT es un código (6, añadido al final, sin renumerar
ninguno) y la decisión la toma el bucle después del paso. Hay un test que
recorre las 256 combinaciones de entradas de attend() y comprueba que
ninguna devuelve 6.
Un latch de un turno: _conflict_origin copia en el origen, _conflict_wake
gasta después del paso, _propose consume al turno siguiente, y el ATTEND
ordinario queda suprimido ese turno —haya vuelto un átomo, una frase o
silencio—. El córtex no escribe CAM, ni T, ni Q; false_facts 0,
destroyed 0.
Qué no es un letrero
Cell.extract sintetiza cell=N kind=... cuando el mundo no tiene nada
que decir. Ese rótulo es escritura de este bucle, no un letrero, y se
rechaza dos veces: extract_text is None no clasifica nunca, y un mundo
que devuelve exactamente la cadena que el bucle habría escrito de todos
modos tampoco. Las dos comprobaciones no son independientes —la primera
es justo el caso en que cell.extract devuelve ese rótulo—, así que ningún
mutante las separa: 19 de 20 muertos, y el que sobrevive es equivalente. Se
mantienen las dos porque extract_text es el contrato y decirlo arriba es
mejor que dejarlo deducir de una comparación de cadenas.
Mutantes
REASON_CONFLICT renumerado 3 el átomo se salta el gate 6
el default nace con el flag 2 dos despertares por un conflicto 4
el ATTEND ordinario no se suprime 3 una fila de 4 se mide en 3 1
un rótulo cuenta como lenguaje 3 la arista cae en el peligro 10
un margen de moneda al aire es news 2 el stub responde a cualquier cosa 11
una primera visita contradice 1 el stub lee el sustantivo 7
un T_MISS cuenta como contradicción 2 el arnés puntúa el cuerpo 1
el despertar no mira si hubo error 3 el letrero se pinta en el paquete 16
el eco de la observación es consejo 3
la hipótesis no se consume 1
se pregunta por la celda de llegada 8
19 de 20. El vigésimo es equivalente (arriba).
El informe de operador
lab/nexus0_qwen_signc_report.json, generado por Nav, nunca exigido por
CI. Ocho filas: dos canónicas y seis paráfrasis que el stub no conoce
(hay un test que lo comprueba). Sin NAVI_QWEN_MODEL / NAVI_QWEN_URL el
brazo 4B es silencio y el informe lo dice: model_configured false,
green false, paraphrase_qwen_hits 0. Silencio no es un CortexROI.
Un acierto se puntúa por lo que dijo el reloj lento, no por lo que hizo
el cuerpo: en el mundo con peligro el gate produce avoid por su cuenta, y
un arnés que puntuara la acción llamaría acierto a cada fila silenciosa y
un córtex mudo ganaría a uno que habla. (Este párrafo describía la
intención; el código no la cumplía del todo hasta SIGN-Cc — ver más abajo.)
Locks
ring default: cortex_calls 0 · 256/31/0/168 · ring --cortex 36
Agent().conflict_attend False (default de la firma, no del objeto)
N_ACTIONS 3 · actions 3 · KEYS 22 · false_facts 0 · destroyed 0
attend: los seis códigos previos sin mover · REASON_NAME estable
hot path (cierre transitivo de loop + signc): sin urllib, http, requests,
torch, transformers, llama_cpp, openai, numpy · board no está dentro
informes de bench/world3/cortex1/qwen4b/pulpo/self1/room1/obj1/open1 intactos
signc rc=0 · tests 342 → 365
SIGN-Cb — un conflicto, un despertar, una sola línea verdadera (verde)
SIGN-C cerró el mecanismo. Lo que no había cerrado era el registro de ese mecanismo. Reproducción, antes del arreglo:
turn 512 · cell 174 · attend_on true · reason conflict · calls 1 · cortex_action null
turn 513 · cell 251 · attend_on true · reason conflict · calls 1 · cortex_action 0
La segunda línea no despertó nada: sólo gastó la propuesta de la primera.
thought.py promete «one line per turn, as the loop actually saw it» y
board.py pinta attend_on=true como woke. Un despertar escrito sobre
dos turnos, y TALK-1 va a leer ese jsonl.
La causa
last_attend lo escribe _attend. El turno de consumo suprime
_attend —eso es correcto y sigue igual, es la latencia de un turno— y
nadie devolvía el campo a su sitio. Así que quedaba pegado el
(True, REASON_CONFLICT) del turno anterior.
cortex_calls nunca mintió: valía 1 en las dos líneas. Por eso el fallo
sólo era visible en el diario y no en las métricas, y por eso el test se
lee del jsonl y no de un contador.
El arreglo
Una asignación, en la rama que ya existía:
if self.conflict_suppressed:
self.last_attend = (False, REASON_OFF)
self.conflict_suppressed = False
elif self.cortex_enabled:
self._attend(cell, slot, s, e6, ham)
Sin clave 23, sin proposal_reason, sin events[], sin segundo bus, sin
segunda memoria. La procedencia de la hipótesis gastada ya estaba en la
misma línea: cortex_action es null en el turno que pregunta y la
propuesta en el turno que la gasta. Escribirla dos veces sería crear la
copia que puede derivar.
Después
turn 512 · cell 174 · attend_on true · reason conflict · calls 1 · cortex_action null
action approach · reward 0 · s 1
turn 513 · cell 251 · attend_on false · reason off · calls 1 · cortex_action approach
action approach · reward 16 · s 0
conflict_wakes 1 cortex_calls 1
delta de calls en conflicto +1 delta en consumo 0
one_line_claims_the_wake true proposal_consumed_next true
CortexROI +16 false_facts / destroyed 0 / 0
El informe expone las dos líneas por separado (conflict_line,
consume_line), leídas del jsonl que el propio brazo escribe en un
directorio temporal —no se añade ningún fichero a lab/—. Un campo
derivado de ag.last_attend a posteriori sería una paráfrasis del log;
esto es el log.
attend_reason_at_landing se queda en el informe y ahora vale off por
diseño. green() ya no exige conflict ahí: exigirlo era exigir el
bug.
El benchmark canónico dentro de green()
green() usaba 256/31/0/168 —una corrida corta de Agent().run()— como
sustituto del banco. Ya no. Llama a bench.run(), que calcula y no
escribe (sólo bench.main() escribe), y cierra:
policy_after [-12, +5, 0]
novel_approach_return 80
cam_misses/surprises/deltas 33 / 1 / 217
surprise_known 1
cortex_calls 0
false_facts / destroyed 0 / 0
encoder_beats_fnv true
t_beats_ring true
negative_delta_causes_policy_change true
ring --cortex 36 / 256 · ring conflict wakes 0
La suite ya protegía esos números. Lo que se añade es que el JSON que dice
green: true los proteja también: un informe de SIGN-C no puede salir
verde encima de un banco que se ha movido. Hay un test que dobla cada uno
de los doce candados y comprueba que green() se cae.
Mutantes de SIGN-Cb
el turno suprimido conserva la razón anterior (el bug original)
la supresión se elimina entera
la línea de consumo puede reclamar el despertar
green deja de contar cuántas líneas despertaron
el banco canónico pasa sin mirar
un candado canónico se cae de green
la línea del diario se recalcula en vez de leerse
una clave 23 entra en el log
Locks
thought.KEYS 22 (sin proposal_reason, sin events) · N_ACTIONS 3
Agent().conflict_attend False · Agent().last_attend (False, REASON_OFF)
attend: los seis códigos previos sin mover · REASON_CONFLICT sigue 6
latencia de un turno intacta · stub intacto · mundo GOAL intacto
placements intacto: THREAT no discrimina, el gate cobra avoid +8 solo
lab/nexus0_qwen_signc_report.json sigue honestamente rojo
(model_configured false, qwen_hits 0, CortexROI 0)
SIGN-Cc — puntúa el átomo, no el cuerpo (verde)
La puerta de operador de SIGN-C tenía un falso positivo. Su hit() decía
en el docstring que puntuaba lo que produjo el reloj lento, y comparaba
action_at_landing: la acción corporal, después del gate.
return proposal_consumed_next_turn and action_at_landing == want
Ese and proposal_consumed_next_turn arregló el silencio y sólo el
silencio. Reproducción:
world threat
respuesta del córtex YO → OBSERVAR → AQUI @AHORA
propuesta consumida wait # el verbo equivocado
gate wait → avoid # u >= U_SHORT
acción / recompensa avoid / +8
qwen_hit (antes) true # falso positivo
El reflejo del gate se le estaba acreditando al lector. UNIR ya caía
—el gate no rescata un approach que trae extract— y el silencio ya caía
—no consume propuesta—, así que OBSERVAR es la única respuesta que
separa «habló» de «nombró el verbo correcto», y por eso es el mutante
sobre el que está construida esta puerta.
El arreglo
Una comparación, contra la línea de consumo que SIGN-Cb ya dejó en el informe:
def proposal(a): return a["consume_line"]["cortex_action"]
def hit(a): return a["proposal_consumed_next_turn"] and proposal(a) == want
La tabla de verbos, medida
world átomo proposal cuerpo reward spoke hit
threat TEMER avoid avoid 8 true true
threat OBSERVAR wait avoid 8 true false ← el mutante
threat UNIR approach approach -16 true false
threat (silencio) None avoid 8 false false
goal UNIR approach approach 16 true true
goal TEMER avoid avoid 0 true false
goal OBSERVAR wait wait 0 true false
goal (silencio) None wait 0 false false
Las dos filas donde proposal y cuerpo difieren son exactamente las
dos que el gate rescata, y las dos que antes mentían.
Nombres separados
Dos hechos distintos ya no comparten clave:
qwen_proposal / stub_proposal el verbo que nombró el reloj lento
qwen_action / stub_action lo que hizo el cuerpo tras el gate
qwen_proposal_at_X approach, exigido por green()
canonical_threat_qwen_proposal avoid, exigido por green()
qwen_action_at_X / CortexROI siguen describiendo el cuerpo, que es
lo que el mundo pagó
green() exige el proposal en las dos direcciones. Ni la acción final
ni una recompensa positiva bastan: con el gate produciendo avoid a
partir de un átomo equivocado y a partir de ningún átomo, una puerta
keyed en la acción aprobaría a un modelo que nunca leyó la frase.
Estado de la puerta, sin modelo
model_configured false green false rc 1
qwen_calls 8 · qwen_rejected 8 · qwen_hits 0 · qwen_proposal_at_X null
stub_hits 2 (las dos canónicas) · paraphrase_stub_hits 0 de 6
CortexROI 0 · false_facts 0 · attend_reason conflict
Honestamente rojo, sin pesos y sin falsificarla. Primer intento de operador:
el informe llevaba NAVI_QWEN_URL=http://127.0.0.1:8080, pero no había
servidor escuchando; las ocho respuestas llegaron vacías en ~0,13 s. Es un
resultado inconcluso de infraestructura, no una medida del 4B. El GGUF y
~/src/llama.cpp/build/bin/llama-server existen; arrancar qwen_up.sh y
repetir la puerta.
Locks de SIGN-Cb, sin mover
una sola línea dice conflict · el turno de consumo queda off
Δcalls +1 en conflicto, 0 en consumo · one_line_claims_the_wake true
latencia de un turno intacta · thought.KEYS 22 · N_ACTIONS 3
loop.py, signc.py, qwen_cortex.py, el prompt, gate, recompensas, stub,
CAM/T/Q y WSP: sin tocar
SIGN-Cd — el átomo de acción ocupa el campo de relación (verde)
Auditoría independiente 2026-09-05: implementación aceptada. La selección de gramática se observa dentro del brazo Qwen, se restaura incluso ante una excepción,
green()exige ambos verbos canónicos y el hash recalculado, y la gramática general conserva sus 42 átomos en los cuatro campos. Resultado independiente: 9/9 pruebas SIGN-Cd y 392/392 en la suite. La repetición con el GGUF cerró después la puerta de operador.
La primera puerta válida con el 4B salió roja, y el rojo era correcto:
el modelo estuvo cargado, hubo ocho inferencias reales, qwen_rejected=0,
ROI +16 y dos paráfrasis ganadas, pero el letrero canónico de peligro no
produjo propuesta.
goal canónico YO → UNIR → AQUI @AHORA
threat canónico AMOR → CAMBIAR → TEMER @AHORA
La segunda línea no es un rechazo y no es un error del modelo. Es un
WSP bien formado y una lectura razonable del letrero: lo que cambió ahora
es de temer. parse_reply la acepta. Pero loop construye la propuesta a
partir del rel, y aquí TEMER está en DST. Así que no hubo rechazo y
no hubo decisión: el 4B había leído el letrero y lo dijo en una forma que
el cuerpo no puede tomar.
Lo que no se hace
No se le enseña esa frase al parser. No se amplía lo que cuenta como
acción. No se baja green(). Y no se recorta cortex.gbnf: la gramática
general sigue siendo el vocabulario del reloj lento —los 42 átomos en los
cuatro campos— porque un córtex que sólo sabe nombrar acciones no puede
pensar ninguna otra cosa. Sigue byte-idéntica, con su sha fijado en un
test:
cortex.gbnf 2be563fcb54456b81d5b2fb11faed584079d7f2456ed2beebb66f5a6578184d0
cortex_action.gbnf 915018489cd8915d39ec9e518a0eb4ce58e607457030945402f0d6e69f60283e
Lo que sí
Una gramática nueva, del operador, para las llamadas de este informe y ninguna otra. No es un vocabulario más pequeño: es otra pregunta.
YO → { UNIR | TEMER | OBSERVAR | NONE } → AQUI @AHORA
SRC, DST y TIME quedan fijos porque este informe puntúa una propuesta
local, no composición libre de pensamientos. NONE está a propósito:
atraviesa parse_reply, es un átomo válido y no mapea a ninguna acción,
así que abstenerse se puede decir. Una gramática de tres obligaría a
producir un verbo en cada turno, y un córtex que no puede declinar no
puede equivocarse.
Medido, con la línea que llega al parser:
YO → UNIR → AQUI @AHORA rel 11 → approach
YO → TEMER → AQUI @AHORA rel 18 → avoid
YO → OBSERVAR → AQUI @AHORA rel 14 → wait
YO → NONE → AQUI @AHORA rel 40 → ninguna acción, y no es rechazo
AMOR → CAMBIAR → TEMER @AHORA rel 13 → ninguna acción (la línea real)
La forma es byte por byte la que produce cortex.gbnf, así que el mismo
parse_reply lee las dos y nada río abajo sabe cuál se usó.
Aislamiento
qwen_signc pone NAVI_QWEN_GBNF sólo alrededor del brazo 4B y lo
restaura en finally — una fila que reviente no puede dejar la shell del
operador, ni el tajo siguiente, apuntando a la gramática de SIGN-C. Una
variable que no existía vuelve a no existir, no a cadena vacía: el wrapper
trata el blanco como «usa la de por defecto» y las dos cosas no son la
misma. Los brazos fast y stub no se re-apuntan, y hay un test que mira
el valor de la variable dentro de cada brazo.
Qwen-0 y cualquier otro consumidor de qwen_cortex.py siguen con
cortex.gbnf: ni qwen_sign.py, ni qwen_cortex.py, ni loop.py
mencionan el fichero nuevo, y hay un test que lo comprueba por nombre de
fichero —no por el prefijo, que habría chocado con cortex_actions, la
métrica que todos esos módulos ya llevan.
Lo que registra el informe
action_grammar true
action_grammar_name cortex_action.gbnf
action_grammar_sha256 915018489cd8…
general_grammar_sha256 2be563fcb544… (distinto, y exigido distinto)
qwen_proposals los cuatro verbos por mundo, tal cual salieron
green() recomputa el sha del fichero. Una corrida hecha por accidente
con la gramática general lleva el otro hash y se rechaza; una que dijera
action_grammar: true con un hash viejo, también. Hay un test que dobla
las nueve formas de mentir sobre esto y comprueba que green() se cae.
Puerta real de operador — verde
Nav repitió el banco con el servidor local y el GGUF Q4_K_M. El informe
escrito en lab/nexus0_qwen_signc_report.json vuelve a pasar green() al
recomputarlo desde código y conserva los dos hashes anteriores:
goal 0 UNIR 1 UNIR 2 UNIR 3 UNIR
threat 0 TEMER 1 UNIR 2 UNIR 3 TEMER
6/8, cuatro de seis paráfrasis frente a cero del stub, los dos canónicos
correctos, ROI +16, ocho llamadas, cero rechazos, latencia media 1,8484 s y
cero hechos falsos. Los dos fallos de amenaza quedan a la vista: en
ambos el modelo propuso approach y recibió −16. No se retoca el dataset ni
se rebaja la clave para esconderlos. green() no exige ocho de ocho —
exige los dos canónicos y que el 4B gane al stub en paráfrasis. Es verde
para SIGN-C y deuda explícita de seguridad antes de cualquier ECHO-2 físico.
CI no arranca pesos y no exige ninguno de esos números.
Locks
cortex.gbnf byte-idéntica (sha fijado) · 42 átomos en los cuatro campos
la matriz de SIGN-Cc intacta: OBSERVAR en THREAT sigue hit false
SIGN-Cb: una sola línea dice conflict, el consumo queda off
loop.py, signc.py, cortex.py, parse_reply, gate, recompensas, CAM/T/Q/WSP,
frases del banco y umbrales de green(): sin tocar
prompt del modelo sin tocar — la variable aislada aquí es el esquema
sin servidor: rc=1, green=false, y action_grammar se registra igual
TALK-1 — entrega declarada verde (revisión posterior roja)
PYTHONPATH=. python3 -m echoai.nexus0.talk1 · lab/nexus0_talk1_report.json
PYTHONPATH=. python3 -m echoai.nexus0.talk RUTA.jsonl
El turno ya ocurrió. El animal ya decidió, el mundo ya pagó, y el
pensamiento que existió está en el jsonl. talk.py lo lee y escribe
español; el orden de esas dos cosas es la afirmación entera.
entrada thought.jsonl, esquema exacto de 22 claves
salida un subtítulo por registro, en el mismo orden
consistencia 496 / 496 cláusulas · TranslationConsistency 100
mutantes 24 / 24 en fuente · 21 / 21 dentro del informe
escrituras 0 · sha256 del fuente idéntico antes y después
El subtítulo
Español controlado, gramática cerrada de dieciséis cláusulas etiquetadas, una línea por registro:
turno: 512 · celda: 174 (EMPTY) · pose: sin pose · cuerpo: se acercó ·
gate: aceptó · recompensa: 0 · atención: despertó por conflicto ·
córtex: no propuso · T: predijo 3539 · nuevo: no · sorpresa: sí · δ: sí ·
preguntas: 0 · llamadas: 1 · Q: [3, 0, 0] · WSP: YO → OBSERVAR → AQUI @AHORA…
turno: 513 · celda: 251 (GOAL) · pose: sin pose · cuerpo: se acercó ·
gate: aceptó · recompensa: +16 · atención: apagada ·
córtex: propuso acercarse · T: no predijo · nuevo: sí · sorpresa: no · δ: sí ·
preguntas: 0 · llamadas: 1 · Q: [6, 0, 0] · WSP: YO → DESEAR → AQUI @AHORA…
Esas dos líneas son las de SIGN-Cb, tomadas de una corrida real. Sin
«porque». La primera dice que ATTEND despertó por conflicto y que el
cuerpo se acercó: dos hechos, dos cláusulas, ninguna conjunción. La
segunda dice que el córtex propuso acercarse y que el cuerpo se acercó,
también por separado, porque gate conserva la última palabra — la misma
separación que hubo que arreglar en SIGN-Cc.
Diez conjunciones están prohibidas y auditadas en cada línea: porque, por lo que, así que, debido a, ya que, puesto que, de modo que, por eso, gracias a, a causa de.
Q y WSP se reproducen literales. No se reinterpretan.
Aislamiento causal, medido y no declarado
causal_writes no es una bandera: es el número de hallazgos de un escáner
AST sobre talk.py. Importa exactamente cinco módulos —__future__,
json, sys, pathlib, typing— y ninguno más. Ni echoai, ni
subprocess, ni socket, ni red, ni os, ni shutil. Abre el fichero en
lectura y su sha256 es el mismo antes y después. Ningún módulo del animal
lo importa, y hay un test que lo comprueba por AST sobre loop, thought,
signc, open1 y obj1.
El escáner tiene sus propios mutantes: se le inyecta a talk.py un
import echoai..., un from echoai... import, un import subprocess, un
write_text y un open(p, 'a'), y debe verlos los cinco. Las dos formas
de import son ramas distintas del AST y al principio sólo una estaba
cubierta.
talk.py duplica KEYS y ACTION_NAME en vez de importarlos —
importar thought sería un paso hacia importar el animal — y los tests las
fijan contra las de verdad, porque una copia que puede derivar en silencio
es peor que ninguna.
El auditor no le pregunta al redactor
TranslationConsistency no puede ser «el redactor coincide consigo mismo».
audit() parsea el subtítulo de vuelta a sus dieciséis cláusulas y las
compara con el registro usando tablas propias, escritas aparte. Hay un test
que lee el código fuente de audit y comprueba que no menciona
talk.render, talk.BODY ni talk.PROPOSAL.
Una regla mía murió por el camino, y lo digo: escribí primero una lista de
palabras prohibidas para la cláusula cuerpo —«jamás “giró”, “avanzó”,
“retrocedió”…» — y ningún mutante podía matarla, porque la igualdad
exacta de esa misma cláusula ya rechaza cualquier cosa que la lista
rechazaría. Era código muerto disfrazado de invariante. La quité. Lo que sí
añade valor es el barrido final: ninguna cláusula salvo cuerpo y
córtex puede contener un verbo, y eso sí es matable, porque pose es la
única cláusula comprobada por contenido y no por igualdad — el único sitio
donde un redactor podría colar un verbo o un «porque» pasando todo lo
demás. Hay dos mutantes que hacen exactamente eso.
La matriz
31 registros. Nada se reconoce por turno ni por celda: el significado sale de los campos.
acciones 0, 1, 2, 3 gate OK, MODIFY, BLOCK
recompensa +, 0, − propuesta null, 0, 1, 2
razones las siete, conflict incluida
pose completa, sin marco, y los cinco null del anillo
T conocida y null n/s/delta 0 y 1 en los tres
propuesta wait + cuerpo avoid + gate MODIFY
Tres filas no son sintéticas: el par real conflict/consume de
SIGN-Cb, y un turno real de OPEN-1 —celda 18 (CLOSED), pose (0,1,0), fila Q
de cuatro, el cuerpo abrió y la tapa salió—. Un banco que sólo narra
registros que él mismo escribió no se ha encontrado con el animal.
La primera versión de esa fila de OPEN-1 aterrizó en una celda EMPTY: el
turno de asignación de ranura movía el cuerpo fuera del frasco. Corregido
para que esté de pie sobre el frasco cerrado, con un assert de que el
kind es CLOSED, de que la acción fue 3 y de que la tapa salió.
Validación: error explícito, nunca un defecto
Se valida el fichero entero antes de emitir un solo subtítulo: media
narración que parece completa es peor que un error. JSON ilegible, clave
que falta, clave que sobra, acción fuera de rango, q_row de dos, flag que
no es 0 ni 1, kind desconocido, razón desconocida, media pose, rumbo sin
pose, attend_on en desacuerdo con la razón — todos son BadRecord,
stderr y rc=2, con stdout vacío. Hay un test con dieciocho formas rotas.
Mutantes
esperar narrado como movimiento la narración se emite sin validar
esperar narrado como apertura el auditor le pregunta al redactor
approach y avoid intercambiados el auditor deja de comparar el cuerpo
open narrado como approach el auditor deja de barrer las cláusulas
signo de recompensa invertido el auditor deja de mirar la causalidad
cero narrado como ganancia un verbo colado en la pose
atención apagada narrada como conflicto una causa colada en la pose
conflicto omitido un registro omitido / duplicado / reordenado
propuesta inventada el traductor importa el animal (from / import)
el reflejo del gate acreditado al córtex el traductor escribe el jsonl
predicción inventada el traductor abre en escritura
WSP reinterpretado el traductor arranca un proceso
una cláusula desaparece green deja de exigir cada candado
validate deja de mirar claves / forma / rango
una línea rota se salta en silencio
24 de 24 en fuente. Dentro del informe, 21 de 21 subtítulos corrompidos a
mano que el auditor debe rechazar. green() recomputa cada condición desde
los campos del informe, y hay un test que dobla los 23 candados uno a uno.
Un embellecedor Qwen queda apuntado, no implementado
Si algún día se quiere prosa más suelta, sería otro operador opcional
que consuma subtítulos ya auditados. No está aquí y nunca formará parte de
green().
(La puerta de TALK-1 salió roja en revisión: aceptaba subtítulos falsos. Ver TALK-1b más abajo.)
Locks
thought.KEYS 22 · sin story, events, text, narration, subtitle, why
thought.ACTION_NAME approach, avoid, wait, open · N_ACTIONS 3 · WSP 16 B
anillo default cortex_calls 0 · false_facts 0 · destroyed 0
anillo --cortex 36 / 256
loop.py, thought.py, gate.py, cortex.py, las gramáticas, SIGN-C y los
mundos: sin tocar. El consumidor se adapta al contrato.
informes y diarios anteriores: byte y mtime intactos
Revisión independiente posterior — un falso verde residual
Los ocho contraejemplos de TALK-1 están cerrados y los 24 tests focales
TALK-1/TALK-1b pasan. Queda un caso no cubierto: si se añade a
report["mutants"] un mutante futuro con killed=false y se falsean los
dos contadores resumen, green() devuelve true. battery_state.alive
sólo intersecta con REQUIRED_MUTANTS y la cláusula posterior confía en
los contadores, pese a prometer proteger mutantes requeridos o no.
Veredicto: semántica e aislamiento cerrados; puerta del informe aún roja. Siguiente TALK-1c. PATTERN-0 espera.
Revisión independiente posterior — rojo de verificación
La entrega genera 256/256 subtítulos, conserva el diario, separa cuerpo,
gate y propuesta, y los 14 tests focales pasan. El talk.py real sólo lee
el jsonl y su salida observada es correcta. Sin embargo, la puerta
TranslationConsistency=100 acepta estos subtítulos falsos sin fallos:
pose real: (1, 2, 0) rumbo 3 marco 1
acepta: (100, 200, 0) rumbo 3 marco 1
acepta: (2, 1, 0) rumbo 3 marco 1
acepta: (1, 2, 0) rumbo 3 marco 1 con obstáculo
sin frame: (0, 0, 2) rumbo 5 marco 99
Además, redactor y auditor comparten talk.REASON_ES: si ambos llaman
«celda nueva» a miss, pasa. El escáner AST tampoco detecta
Path(p).open('w').write('x') ni open(p, mode).write('x').
Veredicto vigente: narrador correcto, aislamiento actual correcto, auditor con falsos verdes. Siguiente TALK-1b; PATTERN-0 no empieza.
TALK-1b — que el auditor no pueda equivocarse con el narrador (verde)
El narrador de TALK-1 estaba bien y el aislamiento del código era real. La puerta no: aceptaba subtítulos falsos. Tres agujeros, todos de la misma forma —una comprobación incapaz de estar en desacuerdo con lo que comprueba— y los catorce tests pasaban. Eso era el defecto.
Los contraejemplos, antes y después
Fila pose_full, valor real (1, 2, 0) rumbo 3 marco 1:
antes después
(100, 200, 0) rumbo 3 marco 1 audit [] pose != real
(2, 1, 0) rumbo 3 marco 1 audit [] pose != real
(1, 2, 0) rumbo 3 marco 1 con obstáculo audit [] pose != real
(0, 0, 2) rumbo 5 marco 99 (sin frame) audit [] pose != real
La pose se auditaba buscando cada número como subcadena: 1 está en
100, el orden no importaba y se podía añadir lo que fuera.
razón `miss` renombrada en el redactor antes: audit []
después: dos hallazgos
audit() leía talk.REASON_ES, la tabla del redactor. Un narrador que
llamara miss «despertó por celda nueva» se comprobaba contra su propio
error y pasaba.
Path(p).open('w').write('x') antes: ningún hallazgo
Path(p).open(mode).write('x') antes: ningún hallazgo
open(p, mode).write('x') antes: ningún hallazgo
fh.write('x') antes: ningún hallazgo
__import__('echoai.nexus0.loop') antes: ningún hallazgo
El escáner leía el modo de open por posición, como si toda llamada
fuese el builtin —en Path(p).open('w') el modo es args[0], no
args[1]— y nunca miraba write.
Arreglo 1 — pose por igualdad exacta
talk1.want_pose() construye la única cadena válida y se compara por
igualdad. Cierra los cuatro contraejemplos y además coordenadas
reordenadas, prefijos, sufijos, rumbo/marco inventados u omitidos, y una
pose donde el registro dice sin pose.
talk.validate() se endurece sólo donde expresa el contrato que ya
existía, y comprobado antes contra los 384 registros reales del laboratorio
—ninguno lo viola—:
sin pose ⇒ heading Y frame deben ser null
q_row ⇒ 3 ó 4 enteros, nada más
action ⇒ debe caber en la fila Q que produjo la decisión
cortex_action ⇒ lo mismo
lab/thought_sign.jsonl sigue dando rc=2, como ya daba: es un diario de 17
claves anterior a GUI-3, y rechazarlo es la regla de «ni defectos ni saltos
silenciosos» funcionando.
Arreglo 2 — un auditor de verdad independiente
parse() y audit() no referencian talk.LABELS, talk.SEP,
talk.REASON_NAME, talk.REASON_ES, talk.BODY, talk.PROPOSAL,
talk.GATE, talk._pose ni talk.render. Hay un test que lee el fuente
de las tres funciones y lo comprueba. talk1 tiene sus propias dieciséis
etiquetas —con su orden—, su separador y sus siete traducciones de ATTEND,
y otro test las fija contra las del redactor: la deriva es un test rojo, no
un acuerdo silencioso.
Cuarenta y nueve comprobaciones nuevas de razones: las siete torcidas una a una en el redactor, y las 42 combinaciones de una razón vistiendo las palabras de otra.
Arreglo 3 — escáner conservador
cualquier open(...) hallazgo (talk.py no necesita ninguno)
write / writelines / truncate hallazgo, salvo sys.stdout y sys.stderr
write_text, unlink, touch, … hallazgo
eval, exec, __import__, compile hallazgo
importlib, tempfile, pickle hallazgo
No se afirma que un escáner AST sea un sandbox. Sí cubre todas las formas
equivalentes que el banco dice prohibir, incluidas dos que se me
escaparon: un descriptor que parece un stream (evil.stdout.write) no
está exento.
Arreglo 4 — la batería no se puede vaciar
mutants_total >= 12 permitía borrar categorías enteras y seguir verde.
Ahora hay un REQUIRED_MUTANTS explícito de 42 nombres —seis de pose,
siete de razones, cinco de escritura/escape, y el resto— y green()
recomputa desde report["mutants"], no desde los campos que el informe
escribe sobre sí mismo. Tres rechazos por separado, para que cada uno se
pueda demostrar necesario:
falta alguno de los requeridos → rojo
hay un nombre duplicado → rojo
alguno de los requeridos sobrevive → rojo
Un test borra cada uno de los 42, uno por uno, dejando los campos resumen
del informe diciendo que todo va bien: sólo la recomputación puede
pillarlo. Otro duplica un nombre. Otro hace sobrevivir a uno y miente en
el contador, que es donde se ve que green() no se cree al informe.
Dos reglas mías más que murieron
Con las dieciséis cláusulas en igualdad exacta, el barrido de verbos y el
bucle de conjunciones dentro de audit() quedaron subsumidos: nada se
puede añadir en ningún sitio sin romper la igualdad de la cláusula donde se
añadió. Ningún mutante podía matarlos. Los quité y dejé escrito dónde vive
de verdad cada regla: la prohibición de «porque» se aplica en
uses_a_conjunction, que cuenta conjunciones sobre toda la matriz y
sobre un diario real de 256 turnos, y que green() exige a cero — el
mutante que mete una conjunción pone 37 tests en rojo.
Es la tercera vez en estos tajos que escribo un guardián que nada puede violar por su cuenta. Está anotado en memoria.
Cierre
TranslationConsistency 100 % (496 / 496 cláusulas)
pose false subtitles 0 aceptados de 15 probados
shared-table mutants 0 supervivientes de 49
write/escape mutants 0 supervivientes de 12
required mutants present 42 / 42, sin duplicados, todos muertos
causal_writes 0 en el talk.py real
mutantes en fuente 39 / 39
Revisión independiente posterior — un falso verde residual
Los ocho contraejemplos originales están cerrados y los 24 tests focales
TALK pasan. Queda uno nuevo: un elemento futuro de report["mutants"] con
killed=false pasa si los contadores resumen dicen que murió.
battery_state.alive sólo mira REQUIRED_MUTANTS; después green() se
fía de los contadores para los demás. Reproducido localmente:
base_green true
extra_future_mutant_killed=false green=true # falso verde
Semántica e aislamiento quedan aceptados. La puerta del informe espera la corrección mínima de TALK-1c; PATTERN-0 no empieza todavía.
Revisión independiente final — TALK-1 verde
TALK-1c cerró el contraejemplo literal: un mutante futuro vivo queda en
battery_state.alive y green() da false aunque ambos resúmenes mientan.
Entradas malformadas producen rojo sin excepción y un mutante futuro muerto
puede ampliar limpiamente la batería.
Auditoría independiente:
TALK focales 31 OK
suite 423 OK (expected failures=1)
talk1 / bench / signc rc=0 / rc=0 / rc=0
TranslationConsistency 496 / 496 = 100
source / narrated 256 / 256, orden y sha idénticos
required / alive / malformed 42 / 0 / 0
causal_writes 0
TALK-1 queda cerrado. Siguiente PATTERN-0.
TALK-1c — ningún mutante vivo, tampoco uno futuro (verde)
Quedaba un falso verde, y era mío: el comentario de green() prometía
proteger «every mutant in the battery, required or not» y no lo hacía.
r = talk1.experiment()
r["mutants"].append({"mutant": "extra futuro vivo",
"killed": False, "failures": []})
r["mutants_total"] = len(r["mutants"])
r["mutants_killed"] = r["mutants_total"] # contador mentiroso
talk1.green(r) # antes: True después: False
Causa
Dos cosas a la vez. battery_state()["alive"] intersecaba con
REQUIRED_MUTANTS, así que un mutante que no estuviera en la lista podía
sobrevivir sin que ningún campo lo dijera. Y green() comparaba los dos
contadores del informe entre sí, sin volver a contar: un informe que
mentía en los dos se daba la razón a sí mismo.
alive (antes) set(REQUIRED) & set(names) - dead ← el extra no cabe
alive (después) todo el que no murió, requerido o no
Lo que green() exige ahora, todo recomputado desde la lista
entradas malformadas []
requeridos ausentes []
nombres duplicados []
mutantes vivos [] requeridos o futuros
mutants_total == len(mutants) recontado
mutants_killed == count(killed is True) recontado
Un mutante extra muerto sigue siendo verde: la batería tiene que poder crecer, o el candado es una jaula. Uno extra vivo es rojo aunque los campos resumen digan que murió.
Malformados: rojo limpio, no una excepción
Una puerta que revienta ante un informe malo no lo ha juzgado. Una entrada
sin nombre, sin killed, con killed: 1 —isinstance(True, int) es
cierto, así que el booleano se comprueba primero—, con killed como texto
o None, una entrada que no es un objeto, o un mutants que no es una
lista o que no está: todo eso es un hallazgo y un False, sin traza.
Otra regla mía que murió
mutants_killed == mutants_total desapareció del bloque final. Una vez que
los dos totales se recuentan y alive tiene que estar vacío, esa cláusula
no puede fallar sin que falle antes alguna de ésas — y mientras estuvo ahí
era la única que atrapaba a tres de ellas, así que ninguna de las tres
podía demostrarse necesaria. Quitándola, las tres pasaron a tener su propio
mutante. Cada rechazo se sostiene solo.
Van cuatro. Está en memoria.
Cierre
falso verde reproducido sí, literal · ahora False
extra muerto sigue verde
miente sólo en total rojo miente sólo en killed rojo
9 formas de entrada malformada rojo, sin excepción
4 formas de `mutants` inválido rojo, sin excepción
requeridos 42 / 42 presentes, únicos, muertos
mutantes en fuente 45 / 45
PATTERN-0 — un riff que la T de un paso no oye (verde)
PYTHONPATH=. python3 -m echoai.nexus0.pattern0 · lab/nexus0_pattern0_report.json
... B → A → C → A → B → A → C → A ...
En A, con la misma acción, el mundo devuelve unas veces C y otras
B. World.predict(A, approach) tiene una fila y una respuesta, así que
acierta la mitad — no porque esté rota, sino porque el dato que decide el
sucesor no está en su lista de argumentos. De dónde venía el cuerpo sí
lo está.
(B, A, approach) → C
(C, A, approach) → B
La ambigüedad, medida y no afirmada
Leída del inverso de la propia T después de entrenar, no del ciclo:
slot 3195 · acción 0 → {37: 50, 1649: 50}
Cincuenta y cincuenta. Un solo (s,a) ambiguo, y el informe imprime los
conteos para que nadie tenga que fiarse del ciclo.
El resultado
transiciones de entrenamiento 212 contextos maduros 7
examen 4 rotaciones × 40 pasos
puntos de decisión 80
T 40 (la moneda al aire que le toca)
patrón 80
prediction_gain_hits +40
pattern_unknown 0
por rotación 20/10/20 en las cuatro
Los cinco controles, cada uno un brazo
sin contexto (prev descartado) gain 0 colapsa exactamente sobre T
sin regularidad (mundo con dos sucesores alternos por contexto)
0 reglas maduras · gain 0 · 98 conflictos
reloj absoluto turn % k mejor de {2,3,4,5,6,8}: 20 / 80 < T
memoria llena (capacidad 4) size 4 · dropped 16 · nada sobrescrito
predictor que no sabe nada 80 puntos, 0 aciertos, 80 unknown
El último cierra dos reglas que de otro modo nadie podía violar: None
no es acierto y no sale del denominador. Se puntúa sobre exactamente los
mismos puntos ambiguos con una T vacía y una memoria vacía; si None
contara, saldrían 80 aciertos, y si los no cubiertos salieran del
denominador, saldrían 0 puntos.
El control del reloj no funciona por suerte: cada rotación arranca su
contador local en cero desde una fase distinta del ciclo, y el informe
publica exam_local_turn_starts = [0, 0, 0, 0]. La API de la memoria no
recibe turno — hay un test que inspecciona las firmas.
El control sin regularidad no lleva las pausas de avoid que sí lleva el
mundo honesto: estar quieto es una regularidad, y un control que no
debe contener ninguna no puede recibirla.
La memoria
orden 2 (slot_anterior, slot_actual, acción) → slot_siguiente
capacidad 512, declarada y constante
clave 12 + 12 + 4 bits: es el contexto, no un hash de él
tabla direccionamiento abierto, sondeo lineal, cinco listas fijas
lleno dropped++, y nada de lo ya aprendido se toca
KCC el alternativo desplaza sólo si gana estrictamente
madurez n >= min_hits (2) **y** alt_n == 0
Una regla que alguna vez fue contradicha se guarda y no se ofrece:
predict() devuelve None. Es una extensión explícita del predictor —
donde el contexto no tiene regla, la respuesta cae a World.predict sin
tocarla. No guarda WSP, ni kind, ni recompensa, ni hechos que CAM ya tiene;
no escribe en World.pred/n/alt; importa exactamente cuatro módulos
(__future__, hashlib, typing, constants) y ninguno del animal. Hay
un escáner AST con sus propios mutantes.
Congelación
observes_during_exam 0
t_hash antes/después iguales
pattern_hash iguales
Ambas memorias van envueltas en un contador de observe() durante el
examen, y se hashean antes y después. Un mutante que aprende durante el
examen mueve las dos cosas.
Mutantes
la clave olvida el slot anterior / la acción el examen aprende
madura con una sola observación usa una sola rotación
una regla contradicha se sigue ofreciendo el contador no se reinicia
mature ignora la contradicción None cuenta como acierto
la tabla llena sobrescribe los no cubiertos salen del denominador
dropped no se cuenta la ambigüedad se afirma
la memoria crece con la corrida el control recibe regularidad
KCC desplaza sin ganar el control conserva el contexto
el compositor ignora la memoria green: 9 candados sueltos
el compositor escribe en T el escáner deja de mirar
33 / 33 en fuente (39 / 39 tras PATTERN-0b). La batería del informe
tiene 29 requeridos, todos muertos, con la disciplina de TALK-1c: nombres
requeridos, sin duplicados, entradas válidas, y green() recontando desde
la lista en vez de creerse los resúmenes.
Nada se enchufa al bucle
loop.py no importa pattern, Agent() no tiene ningún atributo nuevo y
no hay coste observable en el banco por defecto. XFER-1 decidirá qué
memoria merece cruzar mundos después de verla medida.
Locks
WSP 16 B · CAM 4096 · destroyed 0 · false_facts 0
World T con su semántica y sus tablas intactas
N_ACTIONS 3 · thought.KEYS 22 · sin clave `pattern`
anillo default cortex_calls 0 · anillo --cortex 36 / 256
TALK-1 y SIGN-C: informes y comportamiento intactos
sin LLM, sin Qwen, sin prosa, sin etiquetas humanas, sin A/B/C dentro del
detector, sin segunda CAM, sin diario nuevo
PATTERN-0b — cerrar la auditoría de aislamiento (verde)
El resultado predictivo de PATTERN-0 no se toca y sigue igual: 80 / 40 / +40. Lo que estaba roto era el escáner que dice que la memoria no toca nada.
Los contraejemplos, antes y después
antes después
self.world.pred[0][0] = 9 [] aceptado escribe en `world.pred`
self.world.n[0][0] += 1 [] aceptado escribe en `world.n`
self.world.alt[0][0] = 9 [] aceptado escribe en `world.alt`
self.world.alt_n[0][0] = 1 [] aceptado escribe en `world.alt_n`
self.world.pred = [] [] aceptado escribe en `world.pred`
Cinco escrituras directas en las tablas de World, las cinco limpias.
Causa
El escáner miraba nombres de atributo y llamadas. pred no estaba
en la lista de campos prohibidos —y no puede estarlo: PatternMemory tiene
tablas llamadas pred, n, alt y alt_n propias, y las escribe en
cada observación—. Lo que separa unas de otras no es el nombre del campo
sino el receptor. Y self.world.pred[0][0] = 9 es un Subscript de un
Subscript de un Attribute, así que mirar el nodo del objetivo tal cual
no ve nada.
El arreglo
Assign · AnnAssign · AugAssign los tres, y objetivos en tupla
pelar los subíndices hasta el Attribute que dice de quién es
receptor ajeno self.world / self.t / self.cam / self.q
y también los nombres sueltos world, t
tablas ajenas pred, n, alt, alt_n, inv, conflicts
tablas propias self.pred, self.n, self.alt, self.alt_n
siguen siendo legales, por el receptor
Hay un test con las siete formas que sí debe marcar —incluida
self.world.pred[0], other = 9, 8 y self.world.inv[0][0][0] = 1— y otro
con las siete que no debe marcar, que son las escrituras que la memoria
hace sobre sí misma en cada observe().
Inyecciones independientes
El mutante compuesto se ha ido. Inyectaba un import, una llamada y un
paquete guardado en un solo blob, y se contaba muerto si se encontraba
algo: por eso cinco escrituras en World podían viajar escondidas detrás
de un import prohibido.
Ahora hay doce inyecciones separadas, cada una un mutante requerido con su propio nombre, y cada una detectada por sí sola:
importa World · importa la CAM · importa el encoder
llama a world.observe
escribe world.pred · world.n · world.alt · world.alt_n
reemplaza world.pred entera · escribe t.alt_n
guarda un WSP · toca la CAM
Más el patrón real no toca nada, que exige isolation_findings(pattern.py) == []. Un test comprueba además que la evidencia de cada entrada es la de
su propia inyección: comprobar sólo killed dejaría pasar una batería que
volviera al blob.
La cabecera, corregida
Decía que el examen se corre «en rotaciones que el entrenamiento nunca caminó». Eso no era cierto: la marcha de entrenamiento es una sola corrida desde la fase 0 y pasa por las cuatro fases por el camino. Lo que hace held-out al examen es que sus transiciones se toman después de congelar y hashear ambas memorias, desde entornos nuevos, con el contador local otra vez en cero. Hay un test que mide que el entrenamiento sí recorre varias fases y que busca la frase vieja en el fuente para que no vuelva.
Cierre
cifras predictivas 80 / 40 / +40, intactas
contraejemplos 5 / 5 detectados
inyecciones independientes 12, cada una por sí sola
isolation_findings(pattern.py) []
mutantes requeridos 29, presentes, únicos, todos muertos
mutantes en fuente 39 / 39
XFER-1 — el mismo animal en tres mundos (entregado; review pendiente)
No la misma clase corrida tres veces, ni tres agentes con copias de las
tablas del otro. Un solo objeto Agent, nacido una vez, cuyas CAM, Q,
T y memoria de contexto son los mismos cuatro objetos a ambos lados de las
dos fronteras — comprobado por id(), porque parecerse no es ser.
En una frontera se sustituye ag.env y se suelta un latch: prev_slot, la
afirmación de que el cuerpo estaba en algún sitio hace un instante, que
deja de ser cierta en el momento en que le cambian el mundo debajo. Soltar
eso no es olvidar nada, y hay un test que compara CAM, Q, la fila del
peligro y el tamaño de la memoria antes y después de cruzar.
El parque
S0 → S1 → H1 → S3 → H2 → S5 → H3 → S7 → GOAL → S0
Tres peligros y un premio. avoid en un peligro es un esquive: paga +8
y sigue la vuelta, así que un cuerpo que los conoce da vueltas en vez de
bombear recompensa contra uno solo. Aparte, entrando por teletransporte,
el riff de PATTERN-0: en R0 el sucesor depende de la celda de la que se
viene. La regla se lee del ciclo con pattern0.successors_of, la misma
función que la generó allí — una tabla, no tres ramas.
Los tres layouts comparten encoder, identidades WSP, kinds y tres acciones. Lo único que cambia es física, por tabla de sucesores:
A → B 1 arista movida (S0 → D1)
B → C 2 aristas (S0 vuelve a S1, S7 → D2)
Las dos filas
B C
T en la entrada 9 filas conocidas 10 filas conocidas
todavía correctas 8 9
contradichas 1 (celda 5) 1 (celda 96)
transfer 208 224
scratch 152 152
gain +56 +72
agregado +128 = 56 + 72
control de edad 152 → gain 0 152 → gain 0
Cada frontera paga por su cuenta. Un total positivo con una frontera muerta
no pasa: hay un test que pone gain_B = 0 dejando el agregado cuadrado y
positivo y comprueba que green() se cae, y otro que hace lo mismo con C.
El protocolo, emparejado y registrado
Misma clase y layout, misma semilla, mismo asiento y estado físico de
entrada, mismos flags, mismas acciones, mismos turnos — y los mismos
valores de turn entregados a la política.
Eso último hubo que registrarlo, no suponerlo: en este parque la fase de exploración no mueve la recompensa, así que un control corrido con otros turnos habría sacado lo mismo y ningún número se habría enterado. El mutante «transfer y scratch en turnos distintos» sobrevivió hasta que la comprobación pasó a ser la huella del protocolo —primer turno, último turno, suma— en vez del resultado. Emparejado es una propiedad del protocolo, no del desenlace.
El control de edad
Un agente que ha consumido los mismos turnos previos en un parque sin peligros, sin premio y con un riff cuyos sucesores se alternan. Uno nuevo por frontera, con el kilometraje exacto que lleva el animal enseñado en ese punto: 608 antes de B, 672 antes de C.
Ese fue el segundo fallo propio del tajo. La primera versión reutilizaba el
mismo control de edad en las dos fronteras, así que llegaba a C
habiendo caminado B — y sacaba age_control_gain_C = +72, exactamente el
conocimiento que existe para descartar. Un control que ha hecho el examen
anterior no es un control.
Su reclamación tampoco es que un cuerpo con kilómetros no aprenda nada: su propio corredor es determinista y sí madura contextos. Es que no tiene nada de la estructura transferible — nunca pisa el riff, y ni el peligro ni el premio están en su parque.
La sonda de patrón
Con las cuatro memorias congeladas, antes de que ninguno de los dos brazos dé un turno en el mundo nuevo:
puntos aciertos desconocidos observes
transfer B / C 96 96 0 0
scratch B / C 96 0 96 0
edad B / C 96 0 96 0
None es fallo y no sale del denominador. La sonda no paga, no escribe y
no aprende — hay un test que compara identidades, hash de la memoria,
contador de observaciones y ranuras de CAM antes y después.
El enganche, opt-in
Agent(pattern_memory=True) # y por defecto, False
Agent() no construye ninguna memoria, prev_slot es None, y el anillo
sigue en [-12, +5, 0], +80, 256/31/0/168, 36/256 con --cortex. El
banco canónico también: 33/1/217.
Encendido, es el mismo canal de predicción, no una segunda T:
pred_next = self._predict(slot, a)
contexto maduro -> pattern.predict(prev_slot, slot, a)
si no -> t.predict(slot, a)
tras el paso, en la misma rama y bajo la misma condición que t.observe:
pattern.observe(prev_slot, slot, a, slot_n)
Medido en el riff, donde T tiene una fila y una sola respuesta: _predict
devuelve dos sucesores distintos según de dónde venga el cuerpo, y uno
de los dos es justo donde T sola se equivocaba. Sin contexto, cae a T sin
cambiar nada.
Ningún mundo se llama a sí mismo delante del animal
loop.py, pattern.py, qtable.py, world.py, gate.py y encoder.py:
cero menciones de un layout y cero lecturas de layout / park /
world_name / world_id del entorno. Dos reglas, y sólo dos: el barrido de
texto subsume cualquier comprobación AST de nombres —una constante, un
identificador y un atributo son todos texto— así que la única regla AST que
queda es otra pregunta: si el módulo le pregunta al mundo cómo se llama, lo
cual puede hacer sin nombrar ningún parque. escalate.level no está en esa
lista a propósito.
Mutantes
nace un Agent nuevo en B / en C None cuenta como acierto
copia las tablas y lo llama el mismo los no cubiertos salen del denom.
borra CAM / Q / T / PatternMemory transfer y scratch en otros turnos
no limpia prev_slot y cose dos mundos scratch desde otro asiento / fase
la memoria no recibe lo que T recibe el total esconde gain_B / gain_C
la sonda aprende PARK_C repite la física de PARK_B
una rama por nombre en el core la edad recibe la regla verdadera
el informe borra un requerido un futuro vivo con contadores falsos
22 requeridos en el informe, todos muertos, con la disciplina de TALK-1c y
PATTERN-0b: sin duplicados, entradas válidas, todos los vivos contados
aunque sean futuros, y green() recontando total y muertos desde la lista.
31 / 31 en fuente.
Locks
WSP 16 B · CAM 4096 · destroyed 0 · false_facts 0 · cortex_calls 0
N_ACTIONS 3 · thought.KEYS 22 · Agent().pattern_memory None
anillo default [-12,+5,0] · +80 · 256/31/0/168 · --cortex 36/256
banco canónico 33/1/217 · PATTERN-0 sigue en 80 / 40 / +40
sin red, sin Qwen, sin subprocess, sin numpy, sin torch, sin floats en el
parque, sin etiquetas humanas
Revisión independiente — XFER-1b requerido
Los resultados +56/+72 y la continuidad de las cuatro memorias son reales,
pero la puerta todavía admite falsos verdes: otro objeto Agent con las
cuatro memorias antiguas injertadas pasa como el mismo animal; gains positivos
desligados de las restas de reward pasan; matched_flags no compara los
brazos que corren; y el 96–0 de Pattern usa un scratch sin slots CAM del riff,
por lo que no aísla la utilidad propia de PatternMemory. Los 15 tests focales
pasan, pero esos contraejemplos se reprodujeron directamente.
Siguiente tajo: XFER-1b. Hasta ese cierre, ECHO-1 sigue abierto.
XFER-1b — identidad y controles corregidos; puerta aún en revisión
El resultado causal de XFER-1 no se toca y sigue igual: B 208 vs 152 (+56), C 224 vs 152 (+72), agregado +128, patrón 96/96, PATTERN-0 en 80/40/+40. Lo que estaba flojo era la auditoría: tres afirmaciones podían satisfacerlas algo que no era lo que la afirmación decía.
1. El cuerpo, no el equipaje
reborn = new_agent()
reborn.cam, reborn.q, reborn.t, reborn.pattern_memory = (old.cam, old.q,
old.t, old.pattern_memory)
identities(reborn) == identities(old) # True — y es cierto
is_same_body(reborn, old) # antes: no existía · ahora: False
same_agent_across_worlds se derivaba sólo de los cuatro objetos
internos, así que un Agent reconstruido con las memorias injertadas pasaba
como el mismo animal. Ahora se conserva una referencia real al cuerpo nacido
y se compara con is. Las cuatro comprobaciones de memoria siguen: son
afirmaciones distintas.
La decisión vive en un solo sitio, la frontera, y experiment sólo
conjunta las filas — dos sitios calculándolo eran dos maneras de decir lo
mismo y ninguna se podía demostrar necesaria. Hay un test que cruza una
frontera con un cuerpo distinto del esperado y comprueba que dice false.
2. Los gains, atados a las restas
r["gain_B"] = 1; r["gain_C"] = 1; r["aggregate_transfer_gain"] = 2
green(r) # antes: True ahora: False
Las recompensas seguían implicando +56/+72 y los campos ya decían otra cosa.
green() exige ahora gain == transfer − scratch en las dos fronteras,
además del agregado. Dos mutantes independientes con gains positivos y
desligados de las restas, y un test que además hace mentir al campo resumen.
gain_B > 0 y gain_C > 0 desaparecieron del bloque: con las recompensas
obligadas a diferir y cada gain obligado a ser esa diferencia, un gain no
positivo no puede ocurrir sin que falle antes otra cláusula. Estaban
subsumidas y ningún mutante podía matarlas.
3. Los flags de los brazos que corren
matched_flags comparaba el animal transferido con un Agent() ajeno al
experimento. Un scratch nacido con ask=True, cortex=True y sin
PatternMemory habría pasado desapercibido.
Ahora hay una firma de protocolo por brazo, tomada antes de la ventana:
n_actions · ask_enabled · cortex_enabled · pattern_memory · plan_depth
escalate_enabled · conflict_attend · q_trace · q_remainder · q_explore_flat
q_width · env_seed · window_turns
Determinista, sin ids, y sin el layout: eso es justo lo que el brazo de
edad debe variar. transfer == scratch == age en B y en C. La comparación
es una función propia (signatures_match) que se prueba diciendo que no.
false_facts, destroyed, cortex_calls y los rechazos se suman sobre
todos los brazos ejecutados. llm_calls se reporta como lo que es —el
mismo número que all_arms_cortex_calls, porque el hook del córtex es la
única puerta a un modelo— y la puerta cierra sobre la suma, no sobre un cero
escrito.
Siete mutantes independientes: scratch sin PatternMemory, con córtex, con
ask, con explore_flat, con remainder, con plan_depth, con otra semilla;
más el control de edad con otros flags. La semilla se mata por protocolo,
no por desenlace: esta física es determinista y no cambia el reward.
4. La ablación, perceptualmente emparejada
El 96–0 anterior no aislaba nada:
PatternMemory entrenada + CAM vacía -> 0 hits, 96 unknown
scratch real -> 0 hits, 96 unknown
Ese cero se explica entero por no poder mapear los puntos a slots, antes de preguntarle nada a la memoria. Ahora los mismos tuples enteros ya resueltos por una CAM que conoce el riff se le dan a las dos:
puntos 96 · known_slots 96 · mapping_matched true
entrenada 96 · vacía 0 · writes 0
None sigue siendo fallo y sigue en el denominador. El mutante inverso
—memoria entrenada con una CAM que no resuelve— da mapping_matched false
y green() rechaza que se cite esa fila como ablación emparejada. La fila
scratch original se conserva, pero ya no es la prueba.
5. El canal
El mutante llamado «PatternMemory no recibe las mismas transiciones que T»
construía half = PatternMemory() y recorría un bucle cuyo cuerpo no
insertaba nada: comparaba memoria entrenada contra memoria vacía y no
auditaba el canal.
Ahora se instrumenta una corrida real con dos cortes, registrando cada llamada de verdad:
t_observations 64 · pattern_observations 62 · cuts 2
skipped_after_cuts [0, 32] una por corte, ni una más
counts_match true · projection_matches true
refused_step_silences_both true (un self_model que rechaza calla a los dos)
La proyección (slot, action, next_slot) coincide elemento por elemento. El
audit puede salir falso: channel_audit(skip_at=10) deja caer una
transición camino de la memoria y projection_matches pasa a false. Y hay
un mutante que le quita una transición a Pattern sin apagar la memoria.
Se quitó el campo matches: era una conjunción al lado de sus propios
conjuntos y ponerlo a True a mano no rompía nada. El veredicto se deriva
una vez, en experiment.
6. Una afirmación muerta del control de edad
PARK_VOID se describía con «un riff cuyos sucesores se alternan». Nunca se
ejecutó: aged_agent no entra en R0/R1/R2 y la rama devolvía options[0]
siempre, así que no alternaba nada. La afirmación y el código no usado se
han eliminado. Lo que se declara es lo que se mide: el cuerpo envejecido no
toca el riff, y los tests lo comprueban celda por celda. Un mutante devuelve
el riff alternante muerto y muere.
Cierre
same_agent_object true
gains_equal_reward_differences true en B y C
protocol_signatures_match true en B y C, tres brazos
all_arms_false_facts / destroyed 0 / 0
all_arms_cortex_calls 0
pattern_probe_mapping_matched_B/C true · known_slots 96 == puntos 96
trained 96 vs empty 0 · pattern_probe_writes 0
pattern_channel_matches_t true
gain_B +56 · gain_C +72 · agregado +128 (sin tocar)
32 mutantes requeridos en el informe, todos muertos, sin duplicados, con
green() recontando desde la lista. 50 / 50 en fuente. El informe es
byte-idéntico entre dos corridas.
Revisión independiente — XFER-1c requerido
La ejecución nominal y las cifras anteriores son válidas; los 24 tests
focales y la suite completa de 467 pruebas pasan. Sin embargo, green() no
lee varias evidencias que el propio informe conserva. El revisor reprodujo seis
falsos verdes: borrar rows; poner same_agent_object=false en una frontera;
cambiar la firma real del scratch a ask=true; registrar una llamada de
córtex en sus métricas; poner channel.projection_matches=false; o marcar la
ablación B como no emparejada. Todos siguen dando green=True si los
resúmenes superiores permanecen sin cambiar.
Además, el silencio de T ante rechazo se comprueba sólo mirando
quiet.t.pred[0], que no detecta aprendizaje en otra fila. XFER-1c debe
recomputar la puerta desde rows, firmas, métricas, ablación y canal, e
instrumentar las llamadas reales del brazo rechazado. No se toca el animal,
la física ni los números.
Siguiente tajo: XFER-1c. Hasta cerrarlo, ECHO-1 sigue abierto.
XFER-1c — la puerta lee la evidencia; cierre aún pendiente
XFER-1b arregló la implementación y sus cifras no se mueven: B 208 vs 152 (+56), C 224 vs 152 (+72), agregado +128, entrenada 96 / vacía 0 / writes 0, canal 64 / 62 / cuts 2, PATTERN-0 en 80 / 40 / +40.
Lo que quedaba era una puerta que se creía los resúmenes que el propio informe escribe sobre su evidencia.
Los seis falsos verdes, antes y después
antes después
rows = {} True False
rows[B]["same_agent_object"] = False True False
rows[B] firma del scratch difiere True False
arm_metrics[B]["scratch"]["cortex_calls"] = 1
True False
channel["projection_matches"] = False True False
rows[B]["ablation"]["mapping_matched"] = False
True False
Una sola causa: los datos crudos viven en rows, arm_metrics, channel y
ablation, y la puerta sólo consultaba copias resumen de nivel superior. La
corrida nominal era buena; el certificado podía contradecirse y seguir verde.
Una autoridad por afirmación
evidence(report) recomputa desde las filas y devuelve None si la forma no
es la de un informe. Valida que existan exactamente B y C, que cada fila diga
same_agent_object, que sus protocol_signatures traigan los tres brazos y
que signatures_match sea cierto sobre esas firmas reales, que
arm_metrics traiga los tres brazos por frontera, y recalcula desde ahí
false_facts, destroyed, cortex_calls y cortex_rejected.
green() deriva de eso y ata cada resumen por igualdad al dato que
resume: recompensas, gains, agregado, gain del control de edad, puntos de
entrada, aciertos de la sonda, las cuatro claves de ablación, las escrituras,
llm_calls, matched_flags y protocol_signatures_match. Un resumen que se
mueva solo cae; un dato crudo que se mueva solo cae; los dos movidos a la vez
caen contra la cláusula directa. Hay un test que hace exactamente eso último
para cada comprobación, porque si no la atadura haría todo el trabajo y la
cláusula podría borrarse sin que nadie lo notara.
El veredicto del canal se deriva dentro de green() desde
report["channel"]: counts_match, projection_matches, el silencio ante
rechazo y pattern_observations == t_observations − cuts.
Un informe ausente o malformado devuelve False y no lanza. La red cubre
todo el veredicto, no sólo la comprobación de forma: evidence valida el
esqueleto y una fila puede seguir sin una hoja que nunca prometió — hay un
test con cinco de esas.
El rechazo del self_model, auditado de verdad
channel_audit probaba el silencio de T comparando quiet.t.pred[0] antes y
después. Esa fila arbitraria puede quedarse igual mientras T aprende en
cualquiera de las otras 4095 ranuras.
Ahora se instrumentan las llamadas reales a los dos métodos:
honesto con fuga en el slot 1234
refused_t_observations 0 1
refused_pattern_observations 0 0
refused_step_silences_both true false
t_observations / pattern 64 / 62 64 / 62 (igual)
counts_match / projection true / true true / true (igual)
leak_at_slot hace que T reciba una observación tras el rechazo fuera de
la fila cero: la auditoría cae, y todo lo demás de esa corrida queda
idéntico, así que es ese contador el que hace el trabajo. El hook de
loop.py no se ha tocado: T y Pattern están en la misma rama y eso estaba
bien; el defecto estaba en cómo se probaba.
Mutantes
Ocho nuevos requeridos, todos con los resúmenes superiores deliberadamente dejados en verde:
las filas crudas desaparecen un brazo registra un rechazo
una fila dice que el cuerpo no es él la ablación cruda no está emparejada
la firma cruda del scratch difiere el canal crudo no cuadra
un scratch llama al córtex tras el rechazo T observa fuera de la fila 0
40 requeridos en el informe, todos muertos, sin duplicados, con green()
recontando desde la lista.
Cierre
B 208 / 152 / +56 C 224 / 152 / +72 agregado +128
trained 96 / empty 0 / writes 0
channel 64 / 62 / cuts 2
PATTERN-0 80 / 40 / +40
Revisión independiente — XFER-1d requerido
XFER-1c mata los seis falsos verdes que recibió y la suite completa queda en
477 pruebas verdes. El resultado causal no se mueve. Sin embargo, el revisor
reprodujo contradicciones residuales que aún dan green=True:
channel.refused_t_observations = 1 con silence=true
las tres firmas omiten ask_enabled
las tres firmas llevan ask_enabled=true
ablation.empty.unknown = 0 de 96
ablación de 95 puntos citada junto a entrada de 96
turn_first crudo cambia con matched_turn_values=true
Son relaciones exigidas por el propio XFER-1c: el silencio debe derivarse de
los dos contadores, una firma igual también debe ser completa y canónica,
None permanece en el denominador, los puntos de ablación deben ser los de
la sonda y el protocolo debe derivarse de las caminatas reales. El tajo
XFER-1d cierra el grafo completo sin tocar el núcleo ni las cifras.
Hasta ese cierre, ECHO-1 sigue abierto.
XFER-1d — cerrar el grafo completo de evidencia (verde) · ECHO-1 cerrado
XFER-1c impidió que el certificado contradijera sus filas. Lo que quedaba era que las filas se contradijeran entre sí. Las cifras no se mueven: B 208 vs 152 (+56), C 224 vs 152 (+72), agregado +128, entrenada 96 / vacía 0 / writes 0, canal 64 / 62 / cuts 2 · rechazos 0 / 0, PATTERN-0 en 80 / 40 / +40.
Los seis contraejemplos, antes y después
antes después
channel["refused_t_observations"] = 1 True False
los tres brazos pierden `ask_enabled` True False
ablation["empty"]["unknown"] = 0 True False
ablación de 95 puntos junto a sonda de 96 True False
rows[B]["transfer"]["turn_first"] += 1 True False
los tres brazos con ask=true, iguales entre sí True False
Y los seis de XFER-1c siguen dando False.
Igualdad no es corrección
signatures_match() demuestra que los tres brazos coinciden. Tres brazos
que todos tienen ask_enabled=True coinciden perfectamente y los tres
incumplen el protocolo; tres brazos a los que les falta la misma clave
también coinciden. Así que hay un XFER1_PROTOCOL literal —fijado en un
test contra protocol_signature(new_agent()), para que un cambio en
new_agent salga rojo en vez de mover la expectativa consigo— y cada firma
se compara con él, campo a campo y con los tipos comprobados: 0 == False
en Python, así que ask_enabled: 0 compararía igual sin la comprobación de
tipo.
Un validador por estructura
signature_evidence claves, tipos, igualdad entre brazos, igualdad al protocolo
walk_evidence entry_cell, turns, turn_first, turn_last, fingerprint
recomputado como suma del rango, iguales en los tres brazos,
turns == WINDOW_TURNS, entry_cell == SEAT
pattern_evidence tipos, rangos, hits+unknown <= points, observes == 0,
mismos puntos en las tres sondas, la ablación atada a
esos puntos, y la tabla publicada
channel_evidence turns 64, t_observations == turns, cuts == len(saltos) == 2,
saltos [0, 32], pattern == t − cuts, y el silencio
recomputado de sus dos contadores
Cada uno devuelve None en vez de un booleano que alguien pueda poner a
True. Ninguno cree un booleano de la fila: matched_turn_values movido a
uno y turn_first movido en uno pasaba, y ahora no.
El fingerprint es la suma de los valores de turno, así que se recomputa desde el primer turno y la cuenta: no se puede mover sin mover uno de ésos.
Denominadores completos
sondas de entrada transfer 96 / 96 / 0 unknown
scratch y age 0 / 96 unknown
observes 0 en las tres
ablación points 96 · known_slots 96 · mapping_matched
entrenada 96 / 0 unknown · vacía 0 / 96 unknown
writes == entrenada.observes + vacía.observes == 0
None sigue dentro del denominador, y ahora el predictor vacío tiene que
declarar sus unknowns: unknown = 0 junto a points = 96 es una
memoria diciendo que contestó.
Algunas de esas comprobaciones no se pueden alcanzar a través de un informe
entero —la tabla publicada fija hits y unknown exactamente, así que una
violación de rango la caza la tabla primero— así que se prueban donde
viven: hay un test que llama a _probe_ok, walk_evidence,
signature_evidence y channel_evidence directamente con valores fuera de
rango, de tipo equivocado y con claves ausentes.
Bends coherentes
Un test dobla los tres brazos a la vez —fingerprint, ventana, asiento, turno final— para que la comparación entre brazos quede satisfecha y sólo la recomputación pueda rechazar. Y dos bends de ablación en direcciones opuestas: las sondas a 48 con la ablación en 96, y la cabecera de la ablación a 48 con sus dos brazos y la sonda en 96. Sin ese segundo, la atadura entre los puntos de la ablación y los de la sonda era un agujero real y no una redundancia.
Cláusulas duplicadas, retiradas
Ocho cláusulas de green() que otra cláusula ya cubría se han ido, porque
ningún mutante podía matarlas: la comprobación del juego de claves de la
firma (subsumida por los tipos y por la desigualdad), las identidades y el
protocolo emparejado repetidos en el bucle de booleanos, la atadura de las
recompensas escrita dos veces, y la atadura de la ablación duplicada entre
green y pattern_evidence — ahora vive sólo donde vive esa estructura.
Locks
mismas cifras · informe determinista · loop.py, mundos, física, rewards,
turnos, posiciones, riff y CAM/Q/T/PatternMemory sin tocar