-
Notifications
You must be signed in to change notification settings - Fork 4
docs(feature-aelf-core): spec memo for #439 #456
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,270 @@ | ||
| # Feature spec: `aelf core` CLI (#439) | ||
|
|
||
| **Status:** implementation spec | ||
| **Issue:** #439 | ||
| **Sibling commands:** `aelf locked` (`src/aelfrice/cli.py:_cmd_locked`), `aelf unlock` (`src/aelfrice/cli.py:_cmd_unlock`) | ||
| **ROADMAP slot:** v2.0.0 — *"`core` / `unlock` / `delete` / `confirm` (CLI surface)"* | ||
|
|
||
| --- | ||
|
|
||
| ## Purpose | ||
|
|
||
| Surface the **load-bearing subset** of the belief store — the beliefs that | ||
| anchor retrieval and that a human reviewer should look at first when deciding | ||
| whether the store is healthy. `aelf locked` already lists L0 ground truth; | ||
| `aelf core` widens the lens to include independently corroborated beliefs and | ||
| beliefs with strong positive posteriors, so the operator sees the full | ||
| foundation, not just the user-asserted slice. | ||
|
|
||
| --- | ||
|
|
||
| ## Definition of "core" | ||
|
|
||
| A belief is **core** if **any** of the following hold: | ||
|
|
||
| 1. **Locked** — `lock_level != 'none'` (any L0 lock tier). | ||
| 2. **Corroborated** — `corroboration_count >= MIN_CORROBORATION` (default 2). | ||
| 3. **High posterior** — `posterior_mean >= MIN_POSTERIOR` (default 2/3) **AND** | ||
| `alpha + beta >= MIN_ALPHA_BETA` (default 4). | ||
|
|
||
| Where `posterior_mean = alpha / (alpha + beta)`. | ||
|
|
||
| ### Why these three signals | ||
|
|
||
| | Signal | What it captures | Why it's load-bearing | | ||
| |---|---|---| | ||
| | Lock | User-asserted ground truth | Operator has explicitly declared this canonical (#178). | | ||
| | Corroboration ≥ 2 | Same content-hash re-ingested from ≥2 distinct sources (`belief_corroborations`, #190) | Independent confirmation — not just self-reinforcement from one source. | | ||
| | Posterior ≥ 2/3 with α+β ≥ 4 | Multi-event majority-positive Beta-Bernoulli posterior | Distinguishes "got used a lot, kept being right" from one-shot `confirm` events. | | ||
|
|
||
| ### Why these defaults | ||
|
|
||
| - **`MIN_CORROBORATION = 2`** — the prior is `corroboration_count = 0` (no | ||
| re-ingests beyond the first). `1` would mean "seen a second time from the | ||
| same source," `2` is the first independent re-confirmation. | ||
| - **`MIN_POSTERIOR = 2/3`** — the Beta(1,1) prior has mean 0.5; one positive | ||
| event Beta(2,1) is exactly 2/3. Pairing with `MIN_ALPHA_BETA >= 4` rules out | ||
| the single-event case so the threshold genuinely reflects sustained | ||
| positive feedback. | ||
| - **`MIN_ALPHA_BETA = 4`** — the prior has α+β = 2; ≥4 means at least two net | ||
| feedback events have landed. With the 2/3 posterior gate this requires at | ||
| minimum Beta(3,1), i.e., two positive events and zero harmful, or any other | ||
| multi-event majority-positive shape. | ||
|
|
||
| All three thresholds are tunable via flags so the operator can dial the lens | ||
| wider or tighter without rebuilding. | ||
|
|
||
| ### What `core` is **not** | ||
|
|
||
| - **Not "everything"** — uses thresholds; non-load-bearing beliefs are | ||
| excluded. | ||
| - **Not duplicate of `aelf locked`** — `locked` is L0 only; `core` is L0 plus | ||
| the corroboration / posterior subsets. | ||
| - **Not the same as `aelf wonder`** — `wonder` surfaces *consolidation | ||
| candidates* (beliefs that look like they should merge); `core` surfaces | ||
| *currently load-bearing beliefs* (beliefs that already anchor retrieval). | ||
|
|
||
| --- | ||
|
|
||
| ## Contract | ||
|
|
||
| ``` | ||
| aelf core [--json] | ||
| [--limit N] | ||
| [--min-corroboration N] | ||
| [--min-posterior FLOAT] | ||
| [--min-alpha-beta N] | ||
| [--locked-only] | ||
| [--no-locked] | ||
| ``` | ||
|
|
||
| | Flag | Default | Notes | | ||
| |---|---|---| | ||
| | `--json` | off | Switch text → JSON list output. | | ||
| | `--limit N` | none | Cap result count after filtering / sort. | | ||
| | `--min-corroboration N` | `2` | Threshold for the corroboration signal. `0` disables. | | ||
| | `--min-posterior FLOAT` | `0.6666...` | Threshold for posterior-mean signal. `0.0` disables. | | ||
| | `--min-alpha-beta N` | `4` | Co-gate on posterior signal — rules out single-event case. | | ||
| | `--locked-only` | off | Equivalent to setting both other signals to "disabled". | | ||
| | `--no-locked` | off | Suppress the locked subset (debug — surface only the corroboration / posterior subsets). | | ||
|
|
||
| `--locked-only` and `--no-locked` are mutually exclusive; passing both is a CLI | ||
| error (exit 2 — argparse default). | ||
|
|
||
| Exit codes: | ||
|
|
||
| - `0` — success (including empty result; "store empty" message goes to stdout | ||
| per `aelf locked` precedent). | ||
| - `1` — store I/O error (matches `aelf locked` behaviour). | ||
|
|
||
| --- | ||
|
|
||
| ## Output — text | ||
|
|
||
| One line per belief, sorted as: locked first (DESC by `locked_at`, ASC by `id` | ||
| tiebreak — same as `aelf locked`), then non-locked by `posterior_mean` DESC, | ||
| `id` ASC tiebreak. | ||
|
|
||
| ``` | ||
| <belief-id> [LOCK,CORR=3,α=4.0,β=1.0,μ=0.800]: <content one-line> | ||
| ``` | ||
|
|
||
| Tag block fields, comma-separated, omitted when not signalling: | ||
|
|
||
| - `LOCK` — present if `lock_level != 'none'`. | ||
| - `CORR=N` — `corroboration_count` value, if `>= MIN_CORROBORATION`. | ||
| - `α=F.F`, `β=F.F`, `μ=0.NNN` — present if the posterior signal triggered (so | ||
| the operator can see *why* a non-locked belief is in the list). | ||
|
|
||
| Empty store / no matches: | ||
|
|
||
| ``` | ||
| no core beliefs | ||
| ``` | ||
|
|
||
| (parallels `aelf locked`'s `no locked beliefs`). | ||
|
|
||
| ## Output — `--json` | ||
|
|
||
| ```json | ||
| [ | ||
| { | ||
| "id": "...", | ||
| "content": "...", | ||
| "lock_level": "user", | ||
| "alpha": 9.0, | ||
| "beta": 0.5, | ||
| "posterior_mean": 0.947, | ||
| "corroboration_count": 0, | ||
| "signals": ["lock"] | ||
| }, | ||
| ... | ||
| ] | ||
| ``` | ||
|
|
||
| `signals` is a sorted list drawn from `{"lock", "corroboration", "posterior"}` | ||
| — exactly the signals that put this belief in the result. Always non-empty | ||
| for any returned row. | ||
|
|
||
| --- | ||
|
|
||
| ## Implementation | ||
|
|
||
| `_cmd_core` in `src/aelfrice/cli.py`, registered alongside `_cmd_locked`. No | ||
| new store method needed — composition over existing API: | ||
|
|
||
| ```python | ||
| def _cmd_core(args, out): | ||
| store = _open_store() | ||
| try: | ||
| locked = [] if args.no_locked else store.list_locked_beliefs() | ||
| candidates = [] | ||
| if not args.locked_only: | ||
| # Walk all beliefs once and filter in Python — fine at v2.0 | ||
| # store sizes (~10^4 rows). If this becomes a hot path the | ||
| # store can grow a `list_core_candidates(min_corr, min_post, | ||
| # min_ab)` method, but defer until measurement justifies it. | ||
| for bid in store.list_belief_ids(): | ||
| b = store.get_belief(bid) | ||
| if b is None or b.lock_level != "none": | ||
| continue # locked already handled above | ||
| if _qualifies(b, args): | ||
| candidates.append(b) | ||
| finally: | ||
| store.close() | ||
| _emit(locked, candidates, args, out) | ||
| ``` | ||
|
|
||
| Where `_qualifies` checks the corroboration / posterior gates, and `_emit` | ||
| dedupes-by-id (locked subset takes precedence over candidates), applies | ||
| `--limit` after sort, and routes text vs `--json`. | ||
|
|
||
| ### Why no new store method (v2.0) | ||
|
|
||
| `list_locked_beliefs` is one query; `list_belief_ids` + `get_belief` per id is | ||
| N+1, but at v2.0 store sizes (target ~10^4 beliefs per project per #437 | ||
| benchmarks) the round-trip cost is in the low milliseconds and the simplicity | ||
| is worth more than the optimisation. If `aelf core` becomes a per-turn hot | ||
| path (it isn't — it's a research-line / operator-introspection verb), promote | ||
| to a single SQL query. | ||
|
|
||
| ### Mutual exclusion in argparse | ||
|
|
||
| `--locked-only` and `--no-locked` registered with | ||
| `subparser.add_mutually_exclusive_group()`; argparse raises exit 2 on conflict | ||
| with a clear stderr message. No custom validation. | ||
|
|
||
| ### Slash command | ||
|
|
||
| `src/aelfrice/slash_commands/core.md` mirrors `unlock.md` and `confirm.md` | ||
| (`$ARGS` passthrough). Add to `EXPECTED_COMMANDS` in | ||
| `tests/test_slash_commands.py`. | ||
|
|
||
| ### Doc update | ||
|
|
||
| Append a row to `docs/COMMANDS.md` between `locked` (line 20) and `unlock` | ||
| (line 21): | ||
|
|
||
| ``` | ||
| | `core [--json] [--limit N] [--min-corroboration N] [--min-posterior FLOAT] [--min-alpha-beta N] [--locked-only] [--no-locked]` | (v2.0+, #439) Surface load-bearing beliefs: locked ∪ {corroboration ≥ 2} ∪ {posterior ≥ 2/3 with α+β ≥ 4}. Read-only. | | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Test plan (sketch — implementation PR) | ||
|
|
||
| Unit tests in `tests/test_cli_core.py` against a fixture store with five | ||
| beliefs hand-built to exercise each branch: | ||
|
|
||
| | Fixture id | Lock | α, β | corr | Expected `core` | | ||
| |---|---|---|---|---| | ||
| | `b-locked` | user | 1, 1 | 0 | yes (LOCK) | | ||
| | `b-corr` | none | 1, 1 | 3 | yes (CORR) | | ||
| | `b-posterior` | none | 4, 1 | 0 | yes (μ=0.8, α+β=5) | | ||
| | `b-thin-posterior` | none | 2, 1 | 0 | **no** — μ=0.667 but α+β=3 | | ||
| | `b-prior` | none | 1, 1 | 0 | no | | ||
|
|
||
| Tests cover: | ||
|
|
||
| 1. Default output text — all four selection signals produce expected rows. | ||
| 2. `--json` round-trips through `json.loads`; signals list is correct. | ||
| 3. `--locked-only` filters to `b-locked` only. | ||
| 4. `--no-locked` suppresses `b-locked`. | ||
| 5. `--locked-only --no-locked` exits 2. | ||
| 6. `--limit 1` after default sort returns `b-locked` (locked-first). | ||
| 7. Empty store prints `no core beliefs` and exits 0. | ||
| 8. Threshold flags — `--min-corroboration 4` drops `b-corr`; `--min-posterior | ||
| 0.0 --min-alpha-beta 0` includes everything that isn't the prior. | ||
|
|
||
| Slash-command registration test (`tests/test_slash_commands.py`) extended with | ||
| `core` in `EXPECTED_COMMANDS`. | ||
|
|
||
| --- | ||
|
|
||
| ## Out of scope (deferred) | ||
|
|
||
| - **PageRank weighting.** The original issue body mentions "high-PageRank" as | ||
| a candidate signal. PageRank infrastructure is not in the store as of v2.0; | ||
| the closest existing primitive is `graph_spectral.py` (signed-Laplacian / | ||
| heat kernel for retrieval). Adding a graph-centrality signal to `aelf core` | ||
| is a follow-up once a PageRank pass is computed and persisted; tracked as | ||
| future work, not blocking this CLI. | ||
| - **Hibernation interaction.** Hibernated beliefs (`hibernation_score` | ||
| populated) are still candidates if they meet the lock / corroboration / | ||
| posterior gates; the spec does not introduce a hibernation-aware filter. | ||
| If the operator wants hibernation-respecting output, that's a flag to add | ||
| later. | ||
| - **`aelf core --explain <id>`.** Useful future verb to print the tag block | ||
| with the threshold values that would change membership. Out of scope for | ||
| the v2.0 ship. | ||
|
|
||
| --- | ||
|
|
||
| ## Provenance refs | ||
|
|
||
| - #439 — this issue. | ||
| - #178 — locks (`lock_level`, `aelf lock` / `aelf locked`). | ||
| - #190 — `belief_corroborations` table. | ||
| - #441 / #390 — `aelf confirm` (sibling user-affirmation verb). | ||
| - ROADMAP `docs/ROADMAP.md` v2.0.0 row — "core / unlock / delete / confirm | ||
| (CLI surface)". | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick (typo): Minor grammar nit: consider "rules out the single-event case"
Consider adding "the" before "single-event case" for smoother wording:
Co-gate on posterior signal — rules out the single-event case.