Skip to content
46 changes: 23 additions & 23 deletions CHANGELOG/v1.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CHANGELOG/v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Security

- **Bump transitive `authlib` 1.7.0 → 1.7.2 to address CVE-2026-44681** ([GHSA-r95x-qfjj-fjj2](https://github.com/advisories/GHSA-r95x-qfjj-fjj2), [Dependabot #4](https://github.com/robotrocketscience/aelfrice/security/dependabot/4)). Medium-severity (CVSS 6.1) open-redirect in Authlib's OIDC server flows (`OpenIDImplicitGrant` / `OpenIDHybridGrant`) — `redirect_uri` validation runs after scope validation, so a malformed authorization request with `response_type=id_token` and `openid` omitted from scope can cause a `302` to an attacker-controlled URL. aelfrice's exposure: effectively zero — `authlib` is pulled transitively via `fastmcp` (the optional `[mcp]` extra) and the aelfrice MCP server does not implement an OIDC authorization endpoint or register the affected grant types. Fix shipped because it's free; runtime impact is one transitive bump. Lockfile-only change; `pyproject.toml` unaffected. 61 MCP-server tests pass against `authlib==1.7.2`.
- **Bump transitive `authlib` 1.7.0 → 1.7.2 to address CVE-2026-44681** ([GHSA-r95x-qfjj-fjj2](https://github.com/advisories/GHSA-r95x-qfjj-fjj2)). Medium-severity (CVSS 6.1) open-redirect in Authlib's OIDC server flows (`OpenIDImplicitGrant` / `OpenIDHybridGrant`) — `redirect_uri` validation runs after scope validation, so a malformed authorization request with `response_type=id_token` and `openid` omitted from scope can cause a `302` to an attacker-controlled URL. aelfrice's exposure: effectively zero — `authlib` is pulled transitively via `fastmcp` (the optional `[mcp]` extra) and the aelfrice MCP server does not implement an OIDC authorization endpoint or register the affected grant types. Fix shipped because it's free; runtime impact is one transitive bump. Lockfile-only change; `pyproject.toml` unaffected. 61 MCP-server tests pass against `authlib==1.7.2`.

### Added

Expand Down
8 changes: 4 additions & 4 deletions benchmarks/context-rebuilder/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# context-rebuilder eval harness

Eval harness that backs [`docs/context_rebuilder.md`](../../docs/context_rebuilder.md).
Eval harness that backs [`docs/context_rebuilder.md`](../../docs/design/context_rebuilder.md).
Two layers ship in this directory:

1. **v1.2.0 skeleton** at [`eval_harness.py`](eval_harness.py). Locks
Expand Down Expand Up @@ -93,7 +93,7 @@ can correlate the per-task-type fidelity numbers with the failure
modes the spec calls out
([`docs/context_rebuilder.md` § Failure modes][spec]).

[spec]: ../../docs/context_rebuilder.md
[spec]: ../../docs/design/context_rebuilder.md

### Why LLM-judge is parked

Expand Down Expand Up @@ -253,7 +253,7 @@ skeleton when the integration points fill in.

Synthetic fixtures (this repo) and captured fixtures (lab repo)
follow the policy decided in
[`docs/eval_fixture_policy.md`](../../docs/eval_fixture_policy.md):
[`docs/eval_fixture_policy.md`](../../docs/design/eval_fixture_policy.md):

- **Public repo (`benchmarks/context-rebuilder/fixtures/`).**
Synthetic, generator-built fixtures. Tracked in git. CI runs
Expand Down Expand Up @@ -377,7 +377,7 @@ separation. Letting the judge see the rebuilt block would allow
it to patch the candidate using context the candidate did not in
fact produce, inflating fidelity.

[b]: ../../docs/BENCHMARKS.md
[b]: ../../docs/concepts/BENCHMARKS.md

### Operator flow

Expand Down
4 changes: 2 additions & 2 deletions benchmarks/context-rebuilder/fixtures/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ backs the v1.4.0 context-rebuilder eval harness ([#136][i136]).

Per [`docs/eval_fixture_policy.md`][p] (decided at [#142][i142]):

[p]: ../../../docs/eval_fixture_policy.md
[p]: ../../../docs/design/eval_fixture_policy.md
[i142]: https://github.com/robotrocketscience/aelfrice/issues/142

- **Public repo (this directory).** Synthetic, generator-built
Expand Down Expand Up @@ -42,7 +42,7 @@ Each fixture is a `turns.jsonl` file matching the v1.2.0
[transcript-ingest schema][ts]. Per-line shape, lifted from
`aelfrice.transcript_logger._build_turn_line`:

[ts]: ../../../docs/transcript_ingest.md
[ts]: ../../../docs/design/transcript_ingest.md

```json
{
Expand Down
6 changes: 3 additions & 3 deletions docs/concepts/BENCHMARKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ aelfrice ships two benchmark surfaces with different purposes and cadences.

The synthetic harness is a measurement instrument. It is **not** a proof of the central feedback claim — through v1.2 the posterior didn't drive ranking; v1.3 added partial Bayesian re-rank, v1.6 the eval harness + heat-kernel composition wiring, v1.7 BM25F default-on, and v2.1 the use_heat_kernel + use_hrr_structural default-flips. See [LIMITATIONS](../user/LIMITATIONS.md).

The academic suite is the reproducibility deliverable. Most adapters scaffold against MAB, LoCoMo, LongMemEval, StructMemEval, and AMA-Bench but are inert at v1.0; they activate as their feature dependencies port forward. Per-adapter status: [`benchmarks/README.md`](../benchmarks/README.md).
The academic suite is the reproducibility deliverable. Most adapters scaffold against MAB, LoCoMo, LongMemEval, StructMemEval, and AMA-Bench but are inert at v1.0; they activate as their feature dependencies port forward. Per-adapter status: [`benchmarks/README.md`](../../benchmarks/README.md).

## Run the synthetic harness

Expand Down Expand Up @@ -259,6 +259,6 @@ Required for the run to count. Runs without an audit record do not enter `benchm

## See also

- [`src/aelfrice/benchmark.py`](../src/aelfrice/benchmark.py) — synthetic harness source.
- [`benchmarks/README.md`](../benchmarks/README.md) — per-adapter activation status.
- [`src/aelfrice/benchmark.py`](../../src/aelfrice/benchmark.py) — synthetic harness source.
- [`benchmarks/README.md`](../../benchmarks/README.md) — per-adapter activation status.
- [ROADMAP § v2.0.0](ROADMAP.md) — when the academic suite reproduces every headline number.
2 changes: 1 addition & 1 deletion docs/concepts/PHILOSOPHY.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ Two recovery angles fall out of the same substrate:

- **Session recovery, not just write durability.** SQLite WAL guarantees that every acknowledged write survives a crash. That is the storage-engine claim. The product-level claim is that the *working context* of an interrupted session is reconstructable on restart — not from a snapshot file, but from the same belief store the next session retrieves against. Re-open the terminal next week, ask "where were we?", and the locks plus recent retrieval-relevant beliefs are still there.
- **Confidence does not auto-flag.** A belief whose posterior drifts below 0.5 is not surfaced as a warning at v1.x. Only locked-belief demotion-pressure (≥5 contradictions → auto-demote) produces a visible state change. If you want to know which beliefs are losing the feedback loop, you ask `aelf stats`; the system does not interrupt to tell you.
- **The append-only substrate at v1 is `feedback_history`, not observations.** The research line had a separate `observations` table that was insert-only — every observation that produced a belief was permanently recorded. aelfrice v1 does not have that table. Beliefs are the substrate, and beliefs *are* mutated (decay adjusts age weighting; feedback updates `(α, β)`). What aelfrice v1 *does* keep append-only is `feedback_history`: every `apply_feedback` event writes a row, and rows are never updated. That is the immutable substrate at v1, and it is sufficient for "did the user actually correct this?" audit. Full ingest-log immutability — recording every observation that produced or refreshed a belief, not only every feedback event — is the v2.0 contract; see [`design/write-log-as-truth.md`](design/write-log-as-truth.md) for the proposed table and the migration story.
- **The append-only substrate at v1 is `feedback_history`, not observations.** The research line had a separate `observations` table that was insert-only — every observation that produced a belief was permanently recorded. aelfrice v1 does not have that table. Beliefs are the substrate, and beliefs *are* mutated (decay adjusts age weighting; feedback updates `(α, β)`). What aelfrice v1 *does* keep append-only is `feedback_history`: every `apply_feedback` event writes a row, and rows are never updated. That is the immutable substrate at v1, and it is sufficient for "did the user actually correct this?" audit. Full ingest-log immutability — recording every observation that produced or refreshed a belief, not only every feedback event — is the v2.0 contract; see [`design/write-log-as-truth.md`](../design/write-log-as-truth.md) for the proposed table and the migration story.

## What this design buys

Expand Down
8 changes: 4 additions & 4 deletions docs/concepts/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Release history and forward-looking design cuts. As of v3.0 the v1.0→v2.0
parity arc is complete and v3.0 has shipped; see the per-version rows below.

Per-issue tracking: [LIMITATIONS.md](../user/LIMITATIONS.md). Release log: [CHANGELOG.md](../CHANGELOG.md).
Per-issue tracking: [LIMITATIONS.md](../user/LIMITATIONS.md). Release log: [CHANGELOG.md](../../CHANGELOG.md).

## Origin

Expand Down Expand Up @@ -144,7 +144,7 @@ After v2.0.0, `benchmarks/` reproduces every published headline number on a fres

### v3.0.0 — completion + design cut (shipped 2026-05-13)

v3.0 closed the wonder-lifecycle wave, shipped HRR persistence with split-format on-disk migration, shipped type-aware compression at the A2 recall@k bench gate, shipped read-only federation, and ratified four v3-level design decisions. Replaced the prior v2.2 row, whose three referenced issues turned out to be stale (#197 WONTFIX; #193 evaluation shipped without hook successor; #194 was `ingest_turn(bulk=)`, also shipped). Per-entry detail: [CHANGELOG.md § 3.0.0](../CHANGELOG.md). Milestone tracker: [#608](https://github.com/robotrocketscience/aelfrice/issues/608).
v3.0 closed the wonder-lifecycle wave, shipped HRR persistence with split-format on-disk migration, shipped type-aware compression at the A2 recall@k bench gate, shipped read-only federation, and ratified four v3-level design decisions. Replaced the prior v2.2 row, whose three referenced issues turned out to be stale (#197 WONTFIX; #193 evaluation shipped without hook successor; #194 was `ingest_turn(bulk=)`, also shipped). Per-entry detail: [CHANGELOG.md § 3.0.0](../../CHANGELOG.md). Milestone tracker: [#608](https://github.com/robotrocketscience/aelfrice/issues/608).

Substrate completion (all shipped):

Expand All @@ -160,11 +160,11 @@ Design ratifications (all closed, doc-only follow-through):
- **NL-relatedness philosophy** ([#605](https://github.com/robotrocketscience/aelfrice/issues/605), ratified 2026-05-10). Option 1 — stay deterministic, narrow surface. Dedup, contradiction, and relatedness gates live in the consuming agent, not aelfrice. Memo: [`docs/design/v3_relatedness_philosophy.md`](../design/v3_relatedness_philosophy.md).
- **Sentiment-feedback hook production wire-up** ([#606](https://github.com/robotrocketscience/aelfrice/issues/606), ratified 2026-05-10). `UserPromptSubmit` lane, default-off opt-in, most-recent-window decay policy. Shipped behind `[feedback] sentiment_from_prose = true` / `AELFRICE_FEEDBACK_SENTIMENT_FROM_PROSE=1`.
- **Multimodel scope** ([#607](https://github.com/robotrocketscience/aelfrice/issues/607), deferred 2026-05-11). No maintainer validation path for third-party LLM CLIs; the wonder-dispatch lane (#542/#551) covers the in-tree story.
- **Federation write model** ([#661](https://github.com/robotrocketscience/aelfrice/issues/661), ratified 2026-05-11). Option B — read-only federation. Per-project DB is sole writer; peers open foreign DBs read-only and UNION FTS5 results. Mutation tools reject foreign belief IDs at the API surface. CRDT-primitives sub-issues (#651-#654) closed WONTFIX. See [`docs/design/federation-primitives.md`](design/federation-primitives.md) §1 for the forward-compat version-vector substrate; §2-§5 are flagged as deferred multi-writer extension.
- **Federation write model** ([#661](https://github.com/robotrocketscience/aelfrice/issues/661), ratified 2026-05-11). Option B — read-only federation. Per-project DB is sole writer; peers open foreign DBs read-only and UNION FTS5 results. Mutation tools reject foreign belief IDs at the API surface. CRDT-primitives sub-issues (#651-#654) closed WONTFIX. See [`docs/design/federation-primitives.md`](../design/federation-primitives.md) §1 for the forward-compat version-vector substrate; §2-§5 are flagged as deferred multi-writer extension.

### v3.0.1 — install-surface collapse + default-on agent-side retrieval (shipped 2026-05-13)

Patch release on top of v3.0.0. No public API change; the user-facing surface narrows on the install side and broadens on the retrieval side. Per-entry detail: [CHANGELOG.md § 3.0.1](../CHANGELOG.md).
Patch release on top of v3.0.0. No public API change; the user-facing surface narrows on the install side and broadens on the retrieval side. Per-entry detail: [CHANGELOG.md § 3.0.1](../../CHANGELOG.md).

- **Install / upgrade surface collapsed to `uv tool` only** ([#730](https://github.com/robotrocketscience/aelfrice/issues/730)). `aelf upgrade-cmd` (and `/aelf:upgrade`) emit a single in-place form (`uv tool upgrade aelfrice`) for uv-managed installs and a migration chain (`pipx uninstall aelfrice && uv tool install aelfrice`, or the pip equivalent) for any other installer. `UpgradeAdvice.context` collapses `uv_tool` / `pipx` / `venv` / `system` → `uv_tool` / `non_uv`. README and `docs/user/INSTALL.md` rewritten around `uv tool install`; the pipx/venv/system helpers in `lifecycle.py` remain internal but no longer surface as supported channels.
- **Auto-migrate non-uv installs on first 3.0.1 setup** ([#733](https://github.com/robotrocketscience/aelfrice/issues/733), follow-up [#774](https://github.com/robotrocketscience/aelfrice/issues/774) for the uv-not-found runnable one-liner). `aelf setup` runs `lifecycle.maybe_migrate_to_uv()` before hook reconciliation; on a pipx/pip install with `uv` on `$PATH`, runs `uv tool install --force aelfrice` (120s timeout) once per machine behind a `~/.aelfrice/migrated-to-uv` sentinel. The fresh uv-tool shim overwrites the existing `~/.local/bin/aelf` so subsequent invocations resolve through the uv-tool venv. A pipx-only user without `uv` now sees a copy-pasteable installer hint (`curl -LsSf https://astral.sh/uv/install.sh | sh` and `brew install uv` on macOS), not just a docs URL.
Expand Down
8 changes: 4 additions & 4 deletions docs/design/bayesian_ranking.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ score(b) = log(bm25_score(b)) + posterior_weight * f(b)

Two implementation notes follow from existing code:

1. **Use the existing `scoring.posterior_mean(α, β)`.** Per [`src/aelfrice/scoring.py`](../src/aelfrice/scoring.py), `posterior_mean(α, β) = α / (α + β)` already returns 0.5 for unobserved beliefs (which start at `(α, β) = (0.5, 0.5)`, the Jeffreys prior, per the existing decay target). **Do not switch to the Laplace form `(α + 1) / (α + β + 2)`** that issue #151 sketches: aelfrice's prior is Jeffreys, not Laplace, and conditioning the existing posterior on a different prior at the ranking layer would silently disagree with `aelf stats`, the MCP, and `decay()`. Cold-belief neutrality holds either way (both forms read 0.5 at the prior); use the form that matches the rest of the codebase.
1. **Use the existing `scoring.posterior_mean(α, β)`.** Per [`src/aelfrice/scoring.py`](../../src/aelfrice/scoring.py), `posterior_mean(α, β) = α / (α + β)` already returns 0.5 for unobserved beliefs (which start at `(α, β) = (0.5, 0.5)`, the Jeffreys prior, per the existing decay target). **Do not switch to the Laplace form `(α + 1) / (α + β + 2)`** that issue #151 sketches: aelfrice's prior is Jeffreys, not Laplace, and conditioning the existing posterior on a different prior at the ranking layer would silently disagree with `aelf stats`, the MCP, and `decay()`. Cold-belief neutrality holds either way (both forms read 0.5 at the prior); use the form that matches the rest of the codebase.

2. **Numerical safety.** `α` and `β` are `float`. Both start at `0.5` and only grow under feedback; `decay()` shrinks the deltas-from-prior factor toward zero, so post-decay `(α, β)` asymptote at `(0.5, 0.5)` from above. `posterior_mean` is therefore in the open interval `(0, 1)` for any observable belief, and `log(posterior_mean(b))` is finite. No clamp needed; assert `posterior_mean(b) > 0` in dev builds.

Expand Down Expand Up @@ -103,7 +103,7 @@ Three options were considered:

**Recommended: rely on the existing invalidation path. No new hook needed.**

The store already exposes `add_invalidation_callback`, and `RetrievalCache.__init__` already subscribes. Every store mutation that changes a belief — `insert_belief`, `update_belief`, `delete_belief`, the three callable mutators in `store.py` — calls `_fire_invalidation()`, which clears the cache. `apply_feedback` writes the new posterior via `store.update_belief(b)` (see [`src/aelfrice/feedback.py:150`](../src/aelfrice/feedback.py)), which triggers the wipe.
The store already exposes `add_invalidation_callback`, and `RetrievalCache.__init__` already subscribes. Every store mutation that changes a belief — `insert_belief`, `update_belief`, `delete_belief`, the three callable mutators in `store.py` — calls `_fire_invalidation()`, which clears the cache. `apply_feedback` writes the new posterior via `store.update_belief(b)` (see [`src/aelfrice/feedback.py:150`](../../src/aelfrice/feedback.py)), which triggers the wipe.

In other words: option (a) is what already happens, just one indirection deeper. Option (b) is unnecessary because the existing wipe is finer-grained than a serial counter would be (it also invalidates on edge mutations, lock changes, demotion-pressure increments, and content updates — anything that could reorder retrieval). Option (c) is wrong on its own because it permits stale results within the TTL window.

Expand All @@ -115,7 +115,7 @@ If two threads share a `RetrievalCache` and one calls `apply_feedback` while the

## Calibration on synthetic harness

`aelf bench` (per [`src/aelfrice/benchmark.py`](../src/aelfrice/benchmark.py)) currently runs 16 queries against 16 beliefs, BM25-only. The v1.3 implementation extends it as follows:
`aelf bench` (per [`src/aelfrice/benchmark.py`](../../src/aelfrice/benchmark.py)) currently runs 16 queries against 16 beliefs, BM25-only. The v1.3 implementation extends it as follows:

- **Phase A** (`bench --baseline`): no feedback applied. Posterior weight = 0.0 (v1.0.x ordering). Reports hit@1 / hit@3 / hit@5 / MRR — current floor `hit_at_5 >= 0.75` preserved.
- **Phase B** (`bench --partial-uplift`): one round of feedback applied. For each query, the harness applies `apply_feedback(expected_belief, valence=+1.0, source="bench-synthetic")` once before re-running retrieval at the v1.3 default `posterior_weight = 0.5`. Reports the same metrics, plus the delta from Phase A.
Expand Down Expand Up @@ -242,6 +242,6 @@ In each case the rerank reverts to `partial_bayesian_score(bm25, alpha, beta, po
- Issue [#151](https://github.com/robotrocketscience/aelfrice/issues/151) — full log-additive Beta-Bernoulli, no milestone (proposed: re-scope to v2.0.0 follow-up).
- [`docs/concepts/ROADMAP.md`](../concepts/ROADMAP.md) § v1.3.0 and § v2.0.0.
- [`docs/user/LIMITATIONS.md`](../user/LIMITATIONS.md) § "The big one: feedback doesn't drive ranking".
- [`src/aelfrice/retrieval.py`](../src/aelfrice/retrieval.py), [`src/aelfrice/scoring.py`](../src/aelfrice/scoring.py), [`src/aelfrice/feedback.py`](../src/aelfrice/feedback.py), [`src/aelfrice/store.py`](../src/aelfrice/store.py).
- [`src/aelfrice/retrieval.py`](../../src/aelfrice/retrieval.py), [`src/aelfrice/scoring.py`](../../src/aelfrice/scoring.py), [`src/aelfrice/feedback.py`](../../src/aelfrice/feedback.py), [`src/aelfrice/store.py`](../../src/aelfrice/store.py).
- Robertson 1977, *The Probability Ranking Principle in IR* — canonical reference for mixing IR scores with prior probabilities log-additively.
- Croft & Lafferty (eds.) 2003, *Language Modeling for Information Retrieval* — Bayesian smoothing background.
Loading
Loading