Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
9 changes: 5 additions & 4 deletions src/aelfrice/retrieval.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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(
Expand Down
29 changes: 16 additions & 13 deletions tests/test_clustering_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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(
Expand Down Expand Up @@ -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(
Expand Down
23 changes: 21 additions & 2 deletions tests/test_compression_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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
Expand All @@ -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")
Comment on lines +190 to +196

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Avoid hard-coding the clustering env var name in tests

Use the same constant the implementation relies on (e.g. ENV_INTENTIONAL_CLUSTERING) instead of the string literal. This keeps the test aligned with production behavior and ensures it fails if the env var name changes, rather than silently diverging.

s = _populate_store()
result = retrieve_v2(s, "sqlite system") # no explicit kwarg
assert len(result.compressed_beliefs) == len(result.beliefs)
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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.
Expand Down
Loading