NEXO 7.6.0 — enforcement parity: Brain catches up, after_tool goes per-instance, defaults hardened

Published 2026-04-22. Minor release over v7.5.0.

v7.5.0 handed Brain the canonical authority for session-end. v7.6.0 follows up on the surface the operator audit flagged next: the enforcement engines themselves were not actually honouring every rule type the map declared. This release closes that drift.

The drift before v7.6

tool-enforcement-map.json v2.1 declared eight rule types. Only five of them were dispatched by src/enforcement_engine.py::_build_indexes:

Desktop (nexo-desktop/enforcement-engine.js) did dispatch before_tool and on_event — so the split engines disagreed about what the contract guaranteed. Nobody dispatched conditional. A Brain user reading the map saw obligations the runtime never checked.

What v7.6 changes

  1. Dispatch parity. Brain's _build_indexes now has branches for before_tool, on_event, and conditional. Desktop gains conditional support. Both engines now cover all eight types declared by fase2_schema.supported_rule_types_v2_0.
  2. New public API on Brain. Three new methods:
    • on_tool_call_before(raw_name, tool_input) — fires before_tool rules BEFORE the destructive tool lands. Skips the nexo_guard_check → Edit/Write case because R13 already covers that path in richer context.
    • raise_event(event_name, context=None) — hooks / the engine call this when a semantic event fires. Supported events in v7.6: pre_compaction, post_compaction, factual_answer_with_high_stakes, user_correction_without_learning, multi_step_task_detected, done_claimed_with_open_task.
    • reset_task_cycle(tool) — rearms conditional counters after a matching close tool. Without this, the first task_open of a session satisfied the rule forever — the exact "satisfied-by-once" defect the checklist flagged.
  3. after_tool satisfaction is per-instance, not per-session. Old code: if target not in self.tools_called:. Once the target was called a single time, every subsequent trigger was silently satisfied forever. v7.6 compares a monotonic per-instance counter (target_last_instance > trigger_instance). Desktop mirrors the fix (toolLastInstance / toolInstanceCounter in session state).
  4. Map v2.2 tightenings.
    • nexo_learning_add on_event rule: grace_messages: 3 → 0. Corrections now produce a learning in the same turn, not three messages later.
    • nexo_task_open conditional rule: threshold: 10 → 4, level should → must, plus an explicit inject_prompt so the dispatcher has concrete text to emit. _check_conditional halves the threshold when the recent window shows Edit / Write / Task signals, so edit-heavy flows surface the obligation even earlier.
  5. guardian_default.json v1.4.0. Five rule-mode bumps where false-positive telemetry was low:
    • R15_project_context: soft → hard
    • R17_promise_debt: soft → hard
    • R22_personal_script: soft → hard
    • R_CATALOG_before_artifact_create: soft → hard
    • R34_identity_coherence: shadow → soft (identity denials without shared-brain lookup now surface a visible reminder instead of silent telemetry).
    Desktop's mirror defaults (enforcement-engine.js top-of-file) and the corresponding test (tests/guardian-runtime-overrides.test.js) are updated in lock-step.

New contract parity test

tests/test_v76_map_parity.py pins six invariants:

Tests

Brain: pytest 2056 passing. Ten unrelated pre-existing failures in test_cli_scripts, test_client_sync, test_doctor, test_evolution persist (TTY / client-selection edge cases, confirmed against clean main and tracked separately). Desktop: 673/673 tests passing after adjusting the defaults parity assertion for R_CATALOG + R34.

Honest delta vs the checklist

The constructor-guardian-90 checklist asks for ~50 individual hardenings. v7.6 lands the nuclear ones — the drifts the map itself exposed, the per-instance satisfaction bug, the five default-mode bumps, and the new contract test. Remaining items (richer e2e coverage per critical rail, ambient detector for multi_step_task_detected, and a stronger task_close trigger vocabulary) will follow in subsequent point releases. The release notes for those will stay proportional — what ships is what ships.

Related: full v7.6.0 changelog · v7.5.0 release notes · source on GitHub