Skip to content

feat(cli): belief introspection + reversible soft-delete curation (#1081) - #1101

Merged
github-actions[bot] merged 2 commits into
mainfrom
feat/issue-1081-introspection-curation
Jul 6, 2026
Merged

feat(cli): belief introspection + reversible soft-delete curation (#1081)#1101
github-actions[bot] merged 2 commits into
mainfrom
feat/issue-1081-introspection-curation

Conversation

@robotrocketscience

@robotrocketscience robotrocketscience commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Summary

Completes the curation half of #1081 — the two deliverables the operator
enumerated for it: a native belief-introspection tool and a reversible
soft-delete curation pass. The other curation-half pieces already merged
(hedge-float drop at ingest, stranded-capture prune, aelf context source-turn
recovery); this is the last of the enumerated scope.

The scoring-redesign half (exposure-≠-endorsement, organic sink, recurrence-vs-
truth axis, decided-vs-floated status) remains tracked separately under #1086.

What lands

aelf introspect — a read-only, honest-signal view answering "look at my
conversations and analyse the beliefs it extracted" without hand-rolled SQL.
Groups active beliefs by ingest session (or --by project) and surfaces, per
belief, the signals the store already holds but never displayed together:

Deterministic per #605 (pure counts + edge lookups, no model, no clock). The
core is a pure module (aelfrice.introspect.build_report); the CLI wrapper
formats text or --json. Flags: --by, --session, --project,
--only-noise (the retire shortlist), --limit N (0 = no cap), --json.

aelf retire / aelf restore — reversible soft-delete curation. retire
is the gentle sibling of delete: it sets valid_to (belief drops out of
retrieval + FTS search) while preserving the evidence trail (edges, entities,
corroborations); restore — the previously-missing inverse of
soft_delete_belief — clears valid_to and re-inserts the pruned FTS row. This
makes the store's soft-delete reversible end-to-end, so curation off the
introspect view is a low-friction, undoable pass. Audit rows: user_retired /
user_retired_force (−1), user_restored (+1). Lock guard (--force),
#661 read-only-federation guard, already-retired/already-active no-ops.

The command surface (new aelf introspect vs extend aelf review) and the
reversal shape (restore/retire verbs vs a curation-file workflow) were both
operator-confirmed before build.

Tests

  • tests/test_cli_retire_restore.py — retire/restore CLI + store.restore_belief
    (valid_to transitions, FTS membership, audit rows, edge preservation vs delete,
    round-trip, no-op paths).
  • tests/test_introspect.py — the deterministic module (grouping, every signal,
    within-group ordering, filters, soft-delete exclusion, invalid group_by) + CLI
    text/JSON smoke.
  • EXPECTED_COMMANDS updated for the three new visible verbs (slash files ship).
  • Full suite green (5803 passed, 69 skipped, 75 xfailed).

Closes #1081.

Summary by CodeRabbit

  • New Features

    • Added a read-only introspection view to inspect beliefs by session or project, with optional noise-only filtering, limits, and JSON output.
    • Added commands to softly retire beliefs and later restore them, preserving history and searchability on recovery.
  • Bug Fixes

    • Improved handling for locked, unknown, or already-processed beliefs with clearer outcomes and safer no-op behavior.
  • Documentation

    • Updated command docs and changelog entries for the new workflow.

@robotrocketscience robotrocketscience added the author-Gylf PR coordination mutex label Jul 6, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @robotrocketscience, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@robotrocketscience robotrocketscience added the attn:review Needs review (PR open, awaiting reviewer) label Jul 6, 2026
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@robotrocketscience, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 37 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b836539c-cb4c-42a3-86ed-2177df3b97ef

📥 Commits

Reviewing files that changed from the base of the PR and between 5c08be8 and 748a225.

📒 Files selected for processing (10)
  • CHANGELOG/v3.md
  • src/aelfrice/cli.py
  • src/aelfrice/introspect.py
  • src/aelfrice/slash_commands/introspect.md
  • src/aelfrice/slash_commands/restore.md
  • src/aelfrice/slash_commands/retire.md
  • src/aelfrice/store.py
  • tests/test_cli_retire_restore.py
  • tests/test_introspect.py
  • tests/test_slash_commands.py
📝 Walkthrough

Walkthrough

Adds an aelf introspect command reporting posterior, grounding, status, and noise signals over stored beliefs grouped by session or project, plus reversible aelf retire/aelf restore soft-delete commands using a new MemoryStore.restore_belief method, with CLI wiring, slash-command docs, changelog entries, and tests.

Changes

aelf introspect feature

Layer / File(s) Summary
Introspect data model and report builder
src/aelfrice/introspect.py
Adds BeliefSignals, Group, IntrospectReport dataclasses, grouping/status/grounding constants, and build_report() which filters, classifies, buckets, sorts, and totals active beliefs.
Introspect CLI command and docs
src/aelfrice/cli.py, src/aelfrice/slash_commands/introspect.md
Adds _cmd_introspect and rendering helpers producing text/JSON output, argparse wiring for --by/--session/--project/--only-noise/--limit/--json, and slash-command documentation.
Introspect tests
tests/test_introspect.py
Tests grouping, signal classification, filtering/limits, and CLI text/JSON output.

aelf retire/restore curation

Layer / File(s) Summary
Store restore_belief method
src/aelfrice/store.py
Adds MemoryStore.restore_belief to clear valid_to, reinsert the FTS row, bump version, and fire invalidation.
Retire/restore CLI and docs
src/aelfrice/cli.py, src/aelfrice/slash_commands/retire.md, src/aelfrice/slash_commands/restore.md
Adds _cmd_retire/_cmd_restore with ownership checks, lock enforcement (--force), audit event logging, argparse wiring, and command docs.
Retire/restore tests
tests/test_cli_retire_restore.py
Tests error handling, lock enforcement, no-op cases, audit rows, edge preservation, and retire→restore round trip.

Supporting changes
CHANGELOG/v3.md, tests/test_slash_commands.py: Changelog entries for both features and updated expected slash-command inventory covering introspect, retire, restore.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as aelf CLI
  participant Introspect as introspect.build_report
  participant Store as MemoryStore
  participant Audit as feedback_history

  CLI->>Introspect: introspect --by session/project
  Introspect->>Store: load active beliefs
  Store-->>Introspect: beliefs
  Introspect-->>CLI: grouped report (text/JSON)

  CLI->>Store: retire belief_id
  Store-->>CLI: soft_delete_belief result
  CLI->>Audit: log retire event

  CLI->>Store: restore belief_id
  Store-->>CLI: restore_belief result
  CLI->>Audit: log restore event
Loading

Related issues: #1081 — introspect provides a curated, signal-based view distinguishing floated vs. decided beliefs and flags stranded-capture noise fragments.

Suggested labels: enhancement, cli, tests

Suggested reviewers: robotrocketscience

🐰 A belief once floated, now marked with care,
Retired to rest, or restored to the air,
Signals of noise and grounding take flight,
Introspect whispers what's stale, what's right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it doesn't follow the required template and omits several required sections. Add the missing Linked issues, Type of change, Verification, Test plan, and Notes for reviewer sections, or mark non-applicable items.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the new introspection and reversible curation features.
Linked Issues check ✅ Passed The change delivers the requested introspection view and reversible retire/restore curation for #1081.
Out of Scope Changes check ✅ Passed All changed files support the new CLI, docs, store, and tests; no unrelated scope was introduced.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-1081-introspection-curation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

PR-size soft cap

This PR is over the advisory size threshold:

  • 1407 changed lines (limit: 200)
  • 10 changed files (limit: 3)

Bigger PRs collide with more open work, which under the parallel-session workflow tends to produce repeated attn:merge-conflict cycles (see #602). When practical, split into smaller PRs that each touch a focused surface.

This is advisory only — nothing is blocked. If the size is intentional (large refactor, module removal, generated code), apply the size:override label and this comment will be removed on the next push.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
tests/test_introspect.py (1)

41-65: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No test covers the recurrence signal.

BeliefSignals.recurrence (mapped from belief.corroboration_count) is one of the four named signals in the PR description, but no test asserts sig.recurrence against a seeded corroboration_count. Coverage exists for posterior/evidence, grounding, status, and noise, but not recurrence.

✅ Suggested test
def test_recurrence_reflects_corroboration_count() -> None:
    s = MemoryStore(":memory:")
    try:
        b = _mk("a")
        b.corroboration_count = 3
        s.insert_belief(b)
        report = build_report(s)
    finally:
        s.close()
    assert _by_id(report)["a"].recurrence == 3

Also applies to: 154-167

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_introspect.py` around lines 41 - 65, Add a test that covers the
missing recurrence signal by seeding a Belief with corroboration_count and
asserting the generated BeliefSignals.recurrence value in build_report output.
Use the existing _mk helper and locate the behavior through MemoryStore and
build_report, then verify _by_id(report)[...].recurrence matches the seeded
count so the mapping from belief.corroboration_count is exercised.
src/aelfrice/introspect.py (2)

133-149: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

N+1 query pattern in _status.

_status issues two separate SQL round-trips (edges_to + edges_from) per belief. build_report calls this once for every belief surfaced (up to --limit, default 100, unbounded when --limit 0), so a full report does up to 2 * total individual queries instead of a single batched lookup — unlike entity_persistence_scores, which is already batched by belief_id IN (...). For large stores or --limit 0, this scales linearly with query round-trips rather than being O(1) queries.

Consider adding a batched edge-lookup on the store (mirroring entity_persistence_scores) that returns incoming/outgoing RESOLVES/POTENTIALLY_STALE edges keyed by belief id for the full ids list, then deriving status from that map instead of per-belief calls.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aelfrice/introspect.py` around lines 133 - 149, The _status helper is
causing an N+1 query pattern by calling edges_to and edges_from for each belief
individually. Update build_report to fetch all needed edges in one batched store
lookup for the full ids set, similar to entity_persistence_scores, then derive
each belief’s status from a keyed map instead of per-belief round-trips. Keep
the status precedence logic from _status (incoming RESOLVES, then
POTENTIALLY_STALE, then outgoing RESOLVES, else floated) while moving the data
access into a batched store method.

195-201: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Full active-belief table fetched before filtering/limiting.

store.list_active_beliefs(limit=None, order="recent") always pulls every active belief into memory, then session/project filtering and the --limit slice happen in Python. For stores with many thousands of active beliefs this means the read-only introspect command's cost scales with total store size rather than with the requested --limit, even though the default limit is 100.

This is arguably a deliberate tradeoff (limit must apply after filtering per the docstring), but for large stores it's worth considering pushing session_id/project_context filters into the SQL layer (e.g., an optional filter parameter on list_active_beliefs) so only matching rows are fetched, with the Python-side slice still applied afterward.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aelfrice/introspect.py` around lines 195 - 201, The introspect path is
loading all active beliefs in memory before applying session/project filtering
and the requested limit. Update the store access used by the introspection flow
so `list_active_beliefs` (or the underlying store query it calls) can accept
optional `session_id` and `project_context` filters, then fetch only matching
rows while still applying the final `limit` slice afterward in the introspection
logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/aelfrice/cli.py`:
- Around line 7372-7375: The `--limit` option in the CLI allows negative
integers, which then reach `_cmd_introspect` and `build_report` where slicing
with a negative value silently truncates from the end instead of failing. Update
the argument validation for `p_introspect.add_argument("--limit", ...)` to
reject values below 0 while still allowing 0 as the existing “no cap” case,
ideally by reusing a validator pattern like `_positive_int` with a zero-allowed
variant so invalid input fails fast with a clear error.

In `@src/aelfrice/store.py`:
- Around line 2246-2256: The restore flow in the beliefs persistence path
currently returns True after a pre-check SELECT even if the subsequent UPDATE
did not actually restore anything, which can happen under concurrent restores.
Update the logic in the store method that restores beliefs to rely on the UPDATE
outcome instead of the earlier SELECT: keep the existence check if needed, but
only return True when the UPDATE on beliefs changes a row by checking its
rowcount, and return False otherwise so callers do not emit duplicate feedback
or invalidation events.

---

Nitpick comments:
In `@src/aelfrice/introspect.py`:
- Around line 133-149: The _status helper is causing an N+1 query pattern by
calling edges_to and edges_from for each belief individually. Update
build_report to fetch all needed edges in one batched store lookup for the full
ids set, similar to entity_persistence_scores, then derive each belief’s status
from a keyed map instead of per-belief round-trips. Keep the status precedence
logic from _status (incoming RESOLVES, then POTENTIALLY_STALE, then outgoing
RESOLVES, else floated) while moving the data access into a batched store
method.
- Around line 195-201: The introspect path is loading all active beliefs in
memory before applying session/project filtering and the requested limit. Update
the store access used by the introspection flow so `list_active_beliefs` (or the
underlying store query it calls) can accept optional `session_id` and
`project_context` filters, then fetch only matching rows while still applying
the final `limit` slice afterward in the introspection logic.

In `@tests/test_introspect.py`:
- Around line 41-65: Add a test that covers the missing recurrence signal by
seeding a Belief with corroboration_count and asserting the generated
BeliefSignals.recurrence value in build_report output. Use the existing _mk
helper and locate the behavior through MemoryStore and build_report, then verify
_by_id(report)[...].recurrence matches the seeded count so the mapping from
belief.corroboration_count is exercised.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 132352a7-8dac-4726-9ed6-424e0f5e7e4f

📥 Commits

Reviewing files that changed from the base of the PR and between 0fd6290 and 5c08be8.

📒 Files selected for processing (10)
  • CHANGELOG/v3.md
  • src/aelfrice/cli.py
  • src/aelfrice/introspect.py
  • src/aelfrice/slash_commands/introspect.md
  • src/aelfrice/slash_commands/restore.md
  • src/aelfrice/slash_commands/retire.md
  • src/aelfrice/store.py
  • tests/test_cli_retire_restore.py
  • tests/test_introspect.py
  • tests/test_slash_commands.py

Comment thread src/aelfrice/cli.py
Comment thread src/aelfrice/store.py
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Setr:2026-07-06T19:00:10Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

Review — LGTM, mergeable

Reviewed the diff against main, CI, and the bot findings.

Correctness/quality: clean two-commit series (both signed), FF on main, full suite green (5803 passed), discretion grep clean. introspect.build_report is a pure deterministic module (#605-compliant: counts + edge lookups, no model/clock); retire/restore correctly make soft-delete reversible end-to-end (valid_to toggle + FTS re-insert, evidence trail preserved). Test coverage is thorough (retire/restore round-trip, FTS membership, audit rows, edge-preservation-vs-delete, soft-delete exclusion). Closes #1081's enumerated curation half.

On the two CodeRabbit "Minor" items — both assessed non-blocking:

  • store.restore_belief returning True off the pre-check SELECT rather than the UPDATE rowcount (line ~2256): the flagged concurrent-restore race isn't reachable — MemoryStore is single-connection and SQLite serializes writes, so there's no interleaving between the SELECT and the guarded UPDATE. The valid_to IS NOT NULL guard on both statements already makes this a correct no-op. Not a functional bug.
  • --limit accepting negatives on aelf introspect (cli.py ~7375): valid nitpick — it's type=int where the file's other --limit args use a _positive_int validator, so --limit -N would slice-from-end silently. But this is a read-only introspection view: worst case is slightly-off output, no mutation, no crash. Cosmetic; a one-line follow-up (a 0-allowed non-negative validator) can tidy it without holding the release gate.

Merging.

@robotrocketscience robotrocketscience added ready-to-merge Trigger merge-train: FF main to this PR's head and removed ready-to-merge Trigger merge-train: FF main to this PR's head labels Jul 6, 2026
Add the reversible curation pair the introspection tool points users at.
retire is the gentle sibling of delete: it sets valid_to (the belief drops
out of retrieval and FTS search) but preserves the evidence trail — edges,
entities, and corroborations survive — so it can be undone. restore clears
valid_to and re-inserts the FTS index row soft_delete pruned.

- store.restore_belief: the missing inverse of soft_delete_belief; idempotent
  via a valid_to IS NOT NULL guard, returns False on already-active/unknown.
- retire: --force to retire locked beliefs; already-retired is a no-op with no
  second audit row; writes user_retired / user_retired_force (valence -1).
- restore: writes user_restored (valence +1); not-restorable exits non-zero.
- Both honour the #661 read-only federation contract (assert_local_ownership).
- Slash files + EXPECTED_COMMANDS entries for the two visible verbs.
A native answer to "look at my conversations and analyse the beliefs it
extracted." introspect groups the active beliefs by session (or --by
project) and surfaces, per belief, the signals the store already holds but
never displays together:

- posterior mean μ + evidence weight (α+β)
- recurrence (corroboration count), labelled as recurrence, NOT truth
- grounding: durable / ephemeral / neutral (the #1096 entity-persistence
  axis — standalone-meaningful vs context-bound)
- status: floated vs decided, from RESOLVES / POTENTIALLY_STALE edges
- noise: the #1081 stranded-capture predicate; junk floats to the top of
  each group as the prime retire candidates

The deterministic core is a pure module (aelfrice.introspect.build_report);
the CLI wrapper formats text or --json. Read-only per #605 (pure counts +
edge lookups, no model, no clock) — curation stays in retire/lock/resolve,
which the footer points at. Flags: --by, --session, --project, --only-noise,
--limit (0 = no cap), --json. Also drops the #1081 curation-half CHANGELOG
entries.
@robotrocketscience
robotrocketscience force-pushed the feat/issue-1081-introspection-curation branch from 5c08be8 to 748a225 Compare July 6, 2026 19:12
@robotrocketscience

Copy link
Copy Markdown
Owner Author

Rebased onto current main (b5f8ec3d, post-#1089) — the branch fell behind when #1089 merged, so the merge-train (FF-only, no signing key to rebase) couldn't advance it. Only conflict was the [Unreleased] CHANGELOG list; resolved by keeping both #1089 and #1081 entries, code commits replayed unchanged (still signed). Waiting on CI re-green, then re-adding ready-to-merge.

@robotrocketscience robotrocketscience added the ready-to-merge Trigger merge-train: FF main to this PR's head label Jul 6, 2026
@github-actions
github-actions Bot merged commit 748a225 into main Jul 6, 2026
29 checks passed
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

merge-train: merged 748a225main via FF push.

@github-actions github-actions Bot removed the ready-to-merge Trigger merge-train: FF main to this PR's head label Jul 6, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Setr:2026-07-06T19:16:34Z]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

attn:review Needs review (PR open, awaiting reviewer) author-Gylf PR coordination mutex

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stored beliefs are more conversational expected

1 participant