diff --git a/CHANGELOG.md b/CHANGELOG.md index e4b1fb84..4bff211d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,10 @@ installable release; see the roadmap in [README.md](README.md). - **Research-agent dispatch wonder surface** ([#551](https://github.com/robotrocketscience/aelfrice/issues/551), umbrella [#542](https://github.com/robotrocketscience/aelfrice/issues/542) track E). New `aelfrice.wonder.dispatch` module exposes `analyze_gaps()` and `generate_research_axes()`. `analyze_gaps(store, query, ...)` returns a `GapAnalysis` dataclass with known beliefs, high-uncertainty beliefs (normalized 0..1 variance proxy > 0.7), unresolved CONTRADICTS pairs (CONTRADICTS minus matching SUPERSEDES), query-term coverage, and named gaps. `generate_research_axes(gap, agent_count)` produces 2–6 orthogonal `ResearchAxis` records: always-on `domain_research` + `internal_gap_analysis`, conditional `contradiction_resolution` / `uncertainty_deep_dive` / `coverage_extension`. Surfaced via the new `aelf_wonder` MCP tool (13th tool) and `aelf wonder --axes QUERY` CLI flag — both return the same JSON shape for downstream skill-layer consumption (E4, separate sub-issue). +### Changed + +- **`use_intentional_clustering` flips to default-on** ([#436](https://github.com/robotrocketscience/aelfrice/issues/436)). Substrate landed in v2.0/v2.1 ([PR #496](https://github.com/robotrocketscience/aelfrice/pull/496) module + corpus, [PR #498](https://github.com/robotrocketscience/aelfrice/pull/498) `retrieve_v2` wiring, [PR #504](https://github.com/robotrocketscience/aelfrice/pull/504) bench-gate scorer). Bench gate cleared on the production multi-store sweep (#436 R6: 60/60 PASS, p99 0.328ms — ~15-30× margin under the 5ms A4 latency budget). Per the `resolve_use_intentional_clustering()` docstring contract ("the bench gate flips the default after lab-side benchmark evidence clears"), the default is unblocked. Precedence (env > kwarg > TOML > default) is unchanged; only the default value flips `False` → `True`. Reversible via `[retrieval] use_intentional_clustering = false` in `.aelfrice.toml` (or `AELFRICE_INTENTIONAL_CLUSTERING=0`) for v2.0.x parity. The lab-finding that raising `DEFAULT_CLUSTER_EDGE_FLOOR` from 0.4 → 0.6 triples uplift (by excluding `EDGE_CITES` inter-cluster edges) is queued as a separate follow-up so this change stays minimal and reviewable. + ### Documentation - **README documents default-on auto-capture** ([#557](https://github.com/robotrocketscience/aelfrice/issues/557)). Adds a passive-capture row to the `What it remembers` table and a `Passive capture` bullet to `What you get for free` so the headline narrative reflects the v2.1 default flip (#529). Pre-v2.1 the table listed only manual inputs, so a model reading the README inferred aelfrice was for user-locked rules only — the symptom in #557. diff --git a/src/aelfrice/retrieval.py b/src/aelfrice/retrieval.py index dd89a506..090e5cf2 100644 --- a/src/aelfrice/retrieval.py +++ b/src/aelfrice/retrieval.py @@ -908,9 +908,10 @@ def resolve_use_intentional_clustering( 1. AELFRICE_INTENTIONAL_CLUSTERING env var (truthy / falsy normalised). 2. Explicit `explicit` kwarg from the caller. 3. `[retrieval] use_intentional_clustering` in `.aelfrice.toml`. - 4. Default: False — ships behind the flag at v2.0.0; the bench gate - (A2 in docs/feature-intentional-clustering.md) flips the default - after lab-side benchmark evidence clears. + 4. Default: True — flipped from False after the A4 latency bench + gate cleared on the multi-store production sweep (#436 R6, 60/60 + PASS at p99 0.328ms ~ 15-30x margin under the 5ms budget). See + docs/feature-intentional-clustering.md A2 + A4. """ env = _env_intentional_clustering_override() if env is not None: @@ -920,7 +921,7 @@ def resolve_use_intentional_clustering( toml_value = _read_toml_flag_for(INTENTIONAL_CLUSTERING_FLAG, start) if toml_value is not None: return toml_value - return False + return True def resolve_use_vocab_bridge( diff --git a/tests/test_clustering_integration.py b/tests/test_clustering_integration.py index 95a670b8..a82abbf4 100644 --- a/tests/test_clustering_integration.py +++ b/tests/test_clustering_integration.py @@ -70,8 +70,8 @@ def _isolated_cwd(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Path: # --- Flag resolution --------------------------------------------------- -def test_default_is_off(_no_env_override: None, _isolated_cwd: Path) -> None: - assert resolve_use_intentional_clustering() is False +def test_default_is_on(_no_env_override: None, _isolated_cwd: Path) -> None: + assert resolve_use_intentional_clustering() is True def test_explicit_kwarg_overrides_default( @@ -94,8 +94,8 @@ def test_env_garbage_falls_through( monkeypatch: pytest.MonkeyPatch, _isolated_cwd: Path ) -> None: monkeypatch.setenv(ENV_INTENTIONAL_CLUSTERING, "maybe") - assert resolve_use_intentional_clustering() is False - assert resolve_use_intentional_clustering(True) is True + assert resolve_use_intentional_clustering() is True + assert resolve_use_intentional_clustering(False) is False def test_toml_resolves_when_kwarg_and_env_unset( @@ -146,27 +146,30 @@ def _populate_clustered_store() -> MemoryStore: return s -def test_default_call_byte_identical_to_explicit_off( +def test_default_call_byte_identical_to_explicit_on( _no_env_override: None, _isolated_cwd: Path ) -> None: - """Default and explicit-OFF must agree on the merged belief id list. + """Default and explicit-ON must agree on the merged belief id list. - This is the OFF-byte-identity invariant that makes the wiring safe to - land default-OFF — pre-#436-Phase-2 callers see no behavior change.""" + Post-#436 default-flip: callers that don't pass the kwarg get the + same selection as callers that pass `use_intentional_clustering=True`. + Operators wanting v2.0.x parity opt out via + `[retrieval] use_intentional_clustering = false` or + `AELFRICE_INTENTIONAL_CLUSTERING=0`.""" s = _populate_clustered_store() - explicit_off = retrieve_v2( + explicit_on = retrieve_v2( s, "deploy sqlite", budget=2400, use_entity_index=False, - use_intentional_clustering=False, + use_intentional_clustering=True, ) - default_off = retrieve_v2( + default_on = retrieve_v2( s, "deploy sqlite", budget=2400, use_entity_index=False, ) - assert [b.id for b in explicit_off.beliefs] \ - == [b.id for b in default_off.beliefs] + assert [b.id for b in explicit_on.beliefs] \ + == [b.id for b in default_on.beliefs] def test_clustering_changes_selection_at_tight_budget( diff --git a/tests/test_compression_integration.py b/tests/test_compression_integration.py index 373c3f6c..8441df55 100644 --- a/tests/test_compression_integration.py +++ b/tests/test_compression_integration.py @@ -153,7 +153,13 @@ def test_compressed_beliefs_populated_when_flag_on( _no_env_override: None, _isolated_cwd: Path ) -> None: s = _populate_store() - result = retrieve_v2(s, "sqlite system", use_type_aware_compression=True) + # use_intentional_clustering=False explicit: post-#436 default-flip, + # the clustering flag is default-on and would mutex with compression. + result = retrieve_v2( + s, "sqlite system", + use_type_aware_compression=True, + use_intentional_clustering=False, + ) assert len(result.compressed_beliefs) == len(result.beliefs) # Same order, same belief ids. for b, cb in zip(result.beliefs, result.compressed_beliefs, strict=True): @@ -164,7 +170,11 @@ def test_compression_strategy_dispatches_by_retention_class( _no_env_override: None, _isolated_cwd: Path ) -> None: s = _populate_store() - result = retrieve_v2(s, "sqlite system", use_type_aware_compression=True) + result = retrieve_v2( + s, "sqlite system", + use_type_aware_compression=True, + use_intentional_clustering=False, + ) by_id = {cb.belief.id: cb for cb in result.compressed_beliefs} assert by_id["F1"].strategy == STRATEGY_VERBATIM assert by_id["S1"].strategy == STRATEGY_HEADLINE @@ -177,6 +187,13 @@ def test_env_var_alone_enables_compression( monkeypatch: pytest.MonkeyPatch, _isolated_cwd: Path ) -> None: monkeypatch.setenv(ENV_TYPE_AWARE_COMPRESSION, "1") + # Post-#436 default-flip, AELFRICE_INTENTIONAL_CLUSTERING must also + # be disabled in this scope to satisfy the v2.0.0 mutex (the cluster + # pack accounts in raw tokens; composing it with compressed cost is + # tracked as a v2.x follow-up). The test still meaningfully exercises + # "env var alone enables compression" — it just makes the clustering + # env-disable explicit instead of relying on the (now-flipped) default. + monkeypatch.setenv("AELFRICE_INTENTIONAL_CLUSTERING", "0") s = _populate_store() result = retrieve_v2(s, "sqlite system") # no explicit kwarg assert len(result.compressed_beliefs) == len(result.beliefs) @@ -228,6 +245,7 @@ def test_pack_widens_when_flag_on( budget=80, use_entity_index=False, use_type_aware_compression=True, + use_intentional_clustering=False, ) assert len(on.beliefs) > len(off.beliefs) @@ -278,6 +296,7 @@ def test_pack_locked_unchanged_when_flag_on( s, "sqlite", use_entity_index=False, use_type_aware_compression=True, + use_intentional_clustering=False, ) assert [b.id for b in off.beliefs] == [b.id for b in on.beliefs] # Locked render is verbatim under compression.