Skip to content

feat(cli): aelf reason + aelf wonder — Track B v2.0 graph-walk surfaces (#389) - #409

Merged
robotrocketscience merged 5 commits into
mainfrom
feat/issue-389-reason-wonder-cli
May 5, 2026
Merged

feat(cli): aelf reason + aelf wonder — Track B v2.0 graph-walk surfaces (#389)#409
robotrocketscience merged 5 commits into
mainfrom
feat/issue-389-reason-wonder-cli

Conversation

@yoshi280

@yoshi280 yoshi280 commented May 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #389 — Track B v2.0 sub-issue: re-introduces aelf reason and aelf wonder CLI surfaces, shipping together per umbrella ratification ("both or neither").

Implementation matches the operator-ratified defaults in #389 comment 4372792969 and amendment 9 (phantom-belief scaffolding additive).

What ships

  • aelf reason <query> — BM25 top-3 seeds (or --seed-id), expand_bfs walk with terminal-tight defaults (depth=2, budget=10, fanout=8). Indented hop-tree output by default; --json for tooling. Read-only.
  • aelf wonder — highest-degree non-locked seed (id-asc tiebreak) or --seed. Combined BFS path-score × wonder_consolidation.score ranking. Suggested actions in {merge, supersede, contradict, relate} from edge-type heuristic. --top N controls list length; --emit-phantoms emits Phantom JSON for offline review; --json toggles output format.
  • Phantom-belief scaffoldingORIGIN_SPECULATIVE constant + Phantom dataclass + amendment-9 store-write integration TODO marker. NOT in ORIGINS validation set yet — phantom-store integration ships in a follow-up issue under the v2.x [v2.0] Phantom promotion-trigger rule — three rejected naive triggers, need a benchmarked rule #229 lane.
  • Slash commands/aelf:reason and /aelf:wonder.
  • Bench-gate harnesstests/bench_gate/test_reason.py + test_wonder_online.py, both skip-on-no-corpus. Skipped on public CI; lab-side run with AELFRICE_CORPUS_ROOT set produces ship-decision evidence.
  • Corpus schemareasoning/ and wonder_online/ registered in tests/test_corpus_schema.py and tests/corpus/v2_0/README.md. Public tree carries .gitkeep placeholders only.
  • Docsdocs/COMMANDS.md and docs/SLASH_COMMANDS.md updated.

Bench gates (must clear before ship-decision can flip to merge)

Gate Threshold Where
aelf reason chain hit@k uplift over baseline ≥+3pp tests/bench_gate/test_reason.py
aelf wonder row-recall@10 (≥1 expected candidate per row) ≥60% of rows tests/bench_gate/test_wonder_online.py

This PR opens with bench-gated flagged. Lab-side corpus rows under ~/projects/aelfrice-lab/tests/corpus/v2_0/{reasoning,wonder_online}/ will run the gates and produce the ship-or-skip evidence; numbers will be posted as a follow-up comment on this PR.

If either gate misses: per #389, the surface does not ship; commits revert via git revert.

Out of scope

Test plan

  • uv run pytest --ignore=tests/bench_gate passes (2413 passed, 18 skipped).
  • uv run aelf reason --help and uv run aelf wonder --help parse and document the flags.
  • New tests in tests/test_cli_reason_wonder.py (9 tests) cover seed selection, JSON output, unknown-id error path, empty-store handling, deterministic seed pick, --emit-phantoms, and suggested-action vocabulary.
  • Slash-command parity test (tests/test_slash_commands.py) green with reason/wonder registered.
  • Schema validator (tests/test_corpus_schema.py) green with new module specs.
  • Lab-side bench-gate run against populated corpus — pending.

Summary by Sourcery

Introduce new graph-walk reasoning and consolidation surfaces in the CLI and slash commands, backed by phantom-belief scaffolding and bench-gated corpus modules for Track B v2.0.

New Features:

  • Add aelf reason CLI and /aelf:reason slash command to surface reasoning chains over the belief graph with JSON output support.
  • Add aelf wonder CLI and /aelf:wonder slash command to surface consolidation candidates and emit Phantom JSON for offline review.
  • Introduce Phantom speculative-belief scaffolding with a Phantom dataclass and ORIGIN_SPECULATIVE origin constant for future store integration.

Enhancements:

  • Extend corpus schema and v2.0 corpus layout with reasoning and wonder_online modules for graph-walk evaluation.
  • Add bench-gate tests for aelf reason hit@k uplift and aelf wonder online recall to gate shipping of the new surfaces.
  • Add CLI documentation updates describing the new reason and wonder commands and their behavior.
  • Extend slash command tests to cover the new reason and wonder entries.

Tests:

  • Add smoke tests for aelf reason and aelf wonder CLI behavior, including JSON output, error paths, deterministic seed selection, and phantom emission.

Summary by CodeRabbit

New Features

  • Added reason command to surface reasoning chains over belief graphs with configurable depth and seed selection
  • Added wonder command to identify consolidation candidates with optional speculative belief suggestions

Documentation

  • Updated command catalogs and added slash-command definitions for the two new graph-query commands

Tests

  • Added test coverage for reasoning chain and consolidation candidate discovery features

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR introduces two new CLI commands (aelf reason and aelf wonder) to surface reasoning chains and consolidation candidates over the belief graph. The implementation includes command handlers, a new Phantom dataclass, bench-gate tests against corpus fixtures, smoke tests, and updated documentation across CLI and slash-command surfaces.

Changes

Aelf Reason & Wonder CLI Commands

Layer / File(s) Summary
Data Models
src/aelfrice/models.py
Introduces ORIGIN_SPECULATIVE constant and new frozen dataclass Phantom with fields for constituent belief IDs, generator, content, and score.
Core Command Logic
src/aelfrice/cli.py (lines 59–72, 746–989)
Implements _cmd_reason to expand seeds via BM25/explicit IDs, traverse outbound edges with expand_bfs, and output indented chains or JSON. Implements _cmd_wonder with deterministic seed selection (highest-degree belief), BFS expansion, wonder-consolidation scoring, and optional Phantom JSON emission. Adds edge-type→action mapping and ranking helpers.
CLI Registration
src/aelfrice/cli.py (lines 3116–3171)
Registers reason and wonder subcommands in the argument parser with their respective flags (--seed-id, --json, --emit-phantoms, etc.) and wires them to command handlers.
Slash Command Definitions
src/aelfrice/slash_commands/reason.md, wonder.md
Defines metadata, objectives, and execution instructions for /aelf:reason and /aelf:wonder slash-command surfaces.
Smoke Tests
tests/test_cli_reason_wonder.py
Validates CLI behavior with a 3-belief synthetic graph: reason seed-walk and JSON output, unknown --seed-id exit codes, wonder deterministic seeding, phantom JSON shape, and empty-store messaging.
Bench-gate Tests
tests/bench_gate/test_reason.py, test_wonder_online.py
Measures uplift of aelf reason (BFS chain vs. baseline search) and recall of aelf wonder (top-k candidate hit fraction) against labeled corpus fixtures; skips cleanly if corpus modules are absent.
Test Infrastructure
tests/test_slash_commands.py, test_corpus_schema.py
Extends expected slash-command set to include reason and wonder; adds schema validators for new corpus modules (reasoning, wonder_online) with their required fields.
Documentation
docs/COMMANDS.md, docs/SLASH_COMMANDS.md, tests/corpus/v2_0/README.md
Updates subcommand count (26→28), documents reason and wonder in command reference, and specifies corpus module structure and per-row field requirements.

Sequence Diagrams

sequenceDiagram
    actor User
    participant CLI as aelf CLI
    participant Store as MemoryStore
    participant BFS as expand_bfs
    participant Output as Output Formatter

    User->>CLI: aelf reason --seed-id bid1 [--json]
    CLI->>Store: load store
    CLI->>Store: search_beliefs(query) or validate seed
    Store-->>CLI: seed belief IDs
    CLI->>BFS: expand_bfs(seeds, depth, fanout, budget)
    BFS-->>CLI: hop tree {belief_id, edges, hops}
    CLI->>Output: format chain or JSON
    Output-->>User: indented tree or JSON payload
Loading
sequenceDiagram
    actor User
    participant CLI as aelf CLI
    participant Store as MemoryStore
    participant BFS as expand_bfs
    participant Scoring as wonder_consolidation
    participant Output as Output Formatter

    User->>CLI: aelf wonder [--seed id] [--emit-phantoms] [--json]
    CLI->>Store: load store
    CLI->>CLI: pick_seed (deterministic: max degree, tie-break by id)
    CLI->>BFS: expand_bfs(seed, top, fanout, budget)
    BFS-->>CLI: hop neighbors {belief_id, edges, hop_score}
    CLI->>Scoring: score(belief) for each hop
    Scoring-->>CLI: consolidation score per candidate
    CLI->>CLI: rank by combined score (hop + consolidation)
    CLI->>Output: format candidates + optional Phantom rows
    Output-->>User: human-readable rows, JSON, or Phantom JSON
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

  • robotrocketscience/aelfrice#397 — Implements the wonder package (Phantom dataclass, wonder_consolidation, strategies, simulator) that is imported and used by the CLI commands in this PR.
  • robotrocketscience/aelfrice#320 — Introduces bench-gate harness and corpus fixture infrastructure that this PR's new bench-gate tests (test_reason.py, test_wonder_online.py) build upon.

Suggested labels

attn:review

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.04% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: introducing two new CLI commands (aelf reason and aelf wonder) for Track B v2.0 graph-walk surfaces, clearly related to the linked issue #389.
Description check ✅ Passed The description comprehensively covers Summary, Linked Issues, Type of Change, Verification steps, and Test Plan sections as per template. All required sections are present and well-documented with implementation details and bench-gate thresholds.
Linked Issues check ✅ Passed The PR successfully implements both required CLI commands (aelf reason and aelf wonder) with BFS walks, seed selection, JSON output, slash commands, corpus schema updates, bench-gate harnesses meeting thresholds (≥3pp uplift for reason, ≥60% recall for wonder), and proper documentation in COMMANDS.md and SLASH_COMMANDS.md.
Out of Scope Changes check ✅ Passed All changes are appropriately scoped to the PR objectives: CLI implementation, documentation, tests, corpus schema, and bench-gate harnesses. Phantom store-writes, edge writes, and public corpus shipping are explicitly deferred to follow-up work, not included in this PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-389-reason-wonder-cli

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 and usage tips.

@sourcery-ai

sourcery-ai Bot commented May 4, 2026

Copy link
Copy Markdown

Reviewer's Guide

Implements two new graph-walk CLI surfaces (aelf reason and aelf wonder) plus their slash-command wrappers, adds speculative Phantom-belief scaffolding, and wires in corpus schema and bench-gate tests for Track B v2.0 ship decisions.

Sequence diagram for the new aelf reason CLI graph-walk

sequenceDiagram
    actor User
    participant aelf_cli
    participant _cmd_reason
    participant MemoryStore
    participant expand_bfs

    User->>aelf_cli: run `aelf reason <query> [flags]`
    aelf_cli->>_cmd_reason: dispatch with args, out

    _cmd_reason->>MemoryStore: _open_store()
    activate MemoryStore

    alt has_seed_id
        _cmd_reason->>MemoryStore: get_belief(seed_id)
        MemoryStore-->>_cmd_reason: Belief or None
        alt seed_missing
            _cmd_reason-->>User: print error seed-id not found
            _cmd_reason->>MemoryStore: close()
            _cmd_reason-->>aelf_cli: exit code 2
        end
    else bm25_seed_search
        _cmd_reason->>MemoryStore: search_beliefs(query, limit=k)
        MemoryStore-->>_cmd_reason: seeds list
    end

    alt no_seeds
        _cmd_reason-->>User: print no seeds message
        _cmd_reason->>MemoryStore: close()
        _cmd_reason-->>aelf_cli: exit code 0
    else have_seeds
        _cmd_reason->>expand_bfs: expand_bfs(seeds, store, depth, fanout, budget)
        expand_bfs-->>_cmd_reason: hops
        _cmd_reason->>MemoryStore: close()
    end
    deactivate MemoryStore

    alt json_output
        _cmd_reason-->>User: print JSON payload {query,seeds,hops}
    else tree_output
        _cmd_reason-->>User: print seeds and hop tree
    end

    _cmd_reason-->>aelf_cli: exit code 0
Loading

Class diagram for Phantom scaffolding and belief relationships

classDiagram
    class Belief {
        +str id
        +str content
        +str origin
    }

    class Phantom {
        +tuple~str~ constituent_belief_ids
        +str generator
        +str content
        +float score
    }

    class OriginConstants {
        +str ORIGIN_USER_STATED
        +str ORIGIN_AGENT_INFERRED
        +str ORIGIN_DOCUMENT_RECENT
        +str ORIGIN_AGENT_REMEMBERED
        +str ORIGIN_UNKNOWN
        +str ORIGIN_SPECULATIVE
        +frozenset~str~ ORIGINS
    }

    class WonderCLI {
        +int _cmd_wonder(args, out)
        +object _wonder_pick_seed(store)
        +str _suggested_action_for(path)
    }

    class ReasonCLI {
        +int _cmd_reason(args, out)
    }

    class MemoryStore {
        +Belief get_belief(belief_id)
        +list~Belief~ search_beliefs(query, limit)
        +list~str~ list_belief_ids()
        +list edges_from(belief_id)
        +void close()
    }

    class WonderConsolidation {
        +float score(seed_belief, candidate_belief)
    }

    Phantom --> Belief : constituent_belief_ids
    WonderCLI --> Phantom : creates
    WonderCLI --> Belief : reads
    ReasonCLI --> Belief : reads
    WonderCLI --> MemoryStore : uses
    ReasonCLI --> MemoryStore : uses
    WonderCLI --> WonderConsolidation : uses
    OriginConstants --> Belief : origin_field
    OriginConstants --> Phantom : planned_origin_speculative
Loading

File-Level Changes

Change Details Files
Add aelf reason CLI subcommand that walks the belief graph from BM25- or id-selected seeds and emits either an indented hop tree or JSON.
  • Introduce _cmd_reason to open the store, resolve seeds via --seed-id or BM25 search_beliefs, run expand_bfs with depth/budget/fanout controls, and handle empty/unknown-seed cases with user-facing messages.
  • Add JSON output mode that serializes query, seeds, and BFS hops (id, content, score, depth, path) for tooling consumption.
  • Register the reason subparser in build_parser with flags for query, seed override, BM25 fanout, BFS parameters, and --json toggle, and hook it into the CLI dispatch table.
src/aelfrice/cli.py
docs/COMMANDS.md
tests/test_cli_reason_wonder.py
Add aelf wonder CLI subcommand to surface consolidation candidates, compute suggested actions, and optionally emit Phantom JSON without store writes.
  • Define _WONDER_ACTION_BY_EDGE and _suggested_action_for to map edge-type paths to suggested actions in {merge,supersede,contradict,relate} with priority ordering.
  • Implement _wonder_pick_seed deterministic seed selection (highest outbound-degree non-locked belief, id-asc tiebreak) to ensure stable outputs.
  • Implement _cmd_wonder to run BFS from the seed, score candidates using wonder_consolidation.score combined with path scores, sort and truncate to --top, construct Phantom objects, and support --emit-phantoms and --json output modes while remaining read-only.
  • Register the wonder subparser and flags (--seed, --top, --emit-phantoms, --json) and integrate with slash-command wrappers and docs.
src/aelfrice/cli.py
src/aelfrice/models.py
src/aelfrice/slash_commands/wonder.md
docs/COMMANDS.md
docs/SLASH_COMMANDS.md
tests/test_cli_reason_wonder.py
Introduce Phantom-belief scaffolding and speculative origin constant for future store integration.
  • Add ORIGIN_SPECULATIVE string constant to models but intentionally exclude it from the ORIGINS validation set pending later integration.
  • Define an immutable Phantom dataclass capturing constituent belief ids, generator, content, and score as the wire format for aelf wonder --emit-phantoms and downstream tools.
  • Use Phantom within _cmd_wonder to build in-memory speculative candidates and serialize them as JSON when requested.
src/aelfrice/models.py
src/aelfrice/cli.py
Add corpus schema entries and bench-gate tests to guard shipping of aelf reason and aelf wonder.
  • Extend tests/test_corpus_schema.py and tests/corpus/v2_0/README.md to register reasoning/ and wonder_online/ corpus modules with their graded-row schemas.
  • Add tests/bench_gate/test_reason.py to construct per-row stores, run BM25+expand_bfs chains, compare hit@k versus baseline_search_only_top_k, and assert ≥+3pp uplift, skipping when corpus or rows are insufficient.
  • Add tests/bench_gate/test_wonder_online.py to reconstruct labeled graphs, mirror _cmd_wonder scoring, and assert that ≥60% of rows have at least one expected candidate in the top-10, with sensible skip conditions.
tests/test_corpus_schema.py
tests/corpus/v2_0/README.md
tests/bench_gate/test_reason.py
tests/bench_gate/test_wonder_online.py
Wire new CLI surfaces into slash-command ecosystem and regression tests.
  • Add /aelf:reason and /aelf:wonder markdown definitions that shell out to the corresponding CLI commands, with clear objectives and usage hints.
  • Update slash-command documentation to account for the expanded command count and describe the new Track B surfaces and their constraints.
  • Extend tests/test_slash_commands.py to assert that reason and wonder are present in the registered slash-command list.
src/aelfrice/slash_commands/reason.md
src/aelfrice/slash_commands/wonder.md
docs/SLASH_COMMANDS.md
tests/test_slash_commands.py

Assessment against linked issues

Issue Objective Addressed Explanation
#389 Reintroduce the aelf reason <query> and aelf wonder CLI commands (and corresponding slash commands) using the belief-graph BFS/wonder primitives, shipping together as a coherent, read-only graph-walk surface.
#389 Document both aelf reason and aelf wonder in the user-facing docs, including COMMANDS.md and SLASH_COMMANDS.md (if exposed as slash commands).
#389 Add test coverage and bench-gate harnesses for both commands tied to v2.0 corpora (reasoning and wonder_online), and include bench results in the PR body demonstrating that the gates are met. The PR adds CLI smoke tests, bench-gate tests (tests/bench_gate/test_reason.py, test_wonder_online.py), corpus schema entries, and README updates for the reasoning and wonder_online modules, so the harness and test wiring are in place. However, the PR body explicitly marks the lab-side bench-gate run as pending and does not yet include actual bench numbers, which are required by the issue’s acceptance criteria.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Comment thread src/aelfrice/cli.py
store.close()

if args.json:
import json
Comment thread src/aelfrice/cli.py
]

if args.emit_phantoms:
import json
Comment thread src/aelfrice/cli.py
return 0

if args.json:
import json
@github-actions github-actions Bot added the attn:merge-conflict PR branch needs rebase label May 4, 2026
@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown

This PR is now behind main. Rebase locally so your commit signatures stay intact:

git fetch origin && git checkout 'feat/issue-389-reason-wonder-cli' && git rebase origin/main
# resolve conflicts if any, then
git push --force-with-lease

Auto-rebase was removed because the bot has no signing key; rebasing as the bot strips author signatures and the required_signatures rule on main then blocks the merge. See #341.

@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.

Hey - I've found 1 issue, and left some high level feedback:

  • In the _wonder_pick_seed / _cmd_wonder flow, the use of object | None and repeated # type: ignore[union-attr] suggests the types could be tightened to Belief | None (and the return type of _wonder_pick_seed updated accordingly) so that downstream attribute access is type-safe without ignores.
  • The bench-gate helpers _build_store in test_reason.py and test_wonder_online.py duplicate Belief/Edge construction logic; consider extracting a shared helper (or reusing existing test fixtures) so future changes to the Belief schema or defaults stay consistent across gates.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the `_wonder_pick_seed` / `_cmd_wonder` flow, the use of `object | None` and repeated `# type: ignore[union-attr]` suggests the types could be tightened to `Belief | None` (and the return type of `_wonder_pick_seed` updated accordingly) so that downstream attribute access is type-safe without ignores.
- The bench-gate helpers `_build_store` in `test_reason.py` and `test_wonder_online.py` duplicate Belief/Edge construction logic; consider extracting a shared helper (or reusing existing test fixtures) so future changes to the Belief schema or defaults stay consistent across gates.

## Individual Comments

### Comment 1
<location path="src/aelfrice/cli.py" line_range="842-849" />
<code_context>
+}
+
+
+def _suggested_action_for(path: list[str]) -> str:
+    """Map a BFS edge-type path to a one-word suggested action.
+
+    Picks the highest-priority edge type seen on the path, with
+    fall-through to "relate" when none match. Priority order matches
+    `_WONDER_ACTION_BY_EDGE` insertion order.
+    """
+    for edge_type in path:
+        if edge_type in _WONDER_ACTION_BY_EDGE:
+            return _WONDER_ACTION_BY_EDGE[edge_type]
</code_context>
<issue_to_address>
**issue (bug_risk):** Suggested-action priority does not match the docstring description.

The docstring describes choosing the highest-priority edge type based on `_WONDER_ACTION_BY_EDGE` insertion order, but the loop instead returns the first matching edge in `path`. If `path` can contain multiple edge types, this prefers path order over the defined priority. Please either iterate over `_WONDER_ACTION_BY_EDGE` and check membership in `path`, or update the docstring to match the actual behavior.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/aelfrice/cli.py
Comment on lines +842 to +849
def _suggested_action_for(path: list[str]) -> str:
"""Map a BFS edge-type path to a one-word suggested action.

Picks the highest-priority edge type seen on the path, with
fall-through to "relate" when none match. Priority order matches
`_WONDER_ACTION_BY_EDGE` insertion order.
"""
for edge_type in path:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): Suggested-action priority does not match the docstring description.

The docstring describes choosing the highest-priority edge type based on _WONDER_ACTION_BY_EDGE insertion order, but the loop instead returns the first matching edge in path. If path can contain multiple edge types, this prefers path order over the defined priority. Please either iterate over _WONDER_ACTION_BY_EDGE and check membership in path, or update the docstring to match the actual behavior.

…lding)

Adds the wire-format constant and dataclass for the wonder-generated
phantom-belief mechanic. ORIGIN_SPECULATIVE is intentionally NOT yet
in the ORIGINS frozenset — store-write integration is deferred to a
follow-up issue (#229 promotion-trigger lane). Constant + dataclass
land now so 'aelf wonder' (this issue) can produce phantom candidates
in-memory and the future integration sub-issue has the scaffolding
to wire into.
Two new CLI subcommands shipping together per issue #389 ratification
("both or neither" — no partial-ship surface).

aelf reason <query>:
  BM25 top-k seeds (or --seed-id), expand_bfs walk with terminal-tight
  defaults (depth=2, budget=10, fanout=8). Indented hop tree by default;
  --json for machine consumption. Read-only over the graph.

aelf wonder:
  Highest-degree non-locked belief as seed (id-asc tiebreak) or
  --seed override. expand_bfs + wonder_consolidation.score combined
  scoring; suggested_action {merge,supersede,contradict,relate}
  derived from edge-type heuristic on path. --top N controls list
  length; --emit-phantoms emits Phantom JSON for offline use.
  Phantom-belief STORE-write integration deferred to v2.x #229 lane
  per operator amendment 9 — TODO marker in source flags the hookpoint.

Implements operator-ratified defaults from issue #389 comment 4372792969
plus amendment 9 (phantom scaffolding additive).
tests/test_cli_reason_wonder.py: 9 in-process atomic tests against
a 3-belief synthetic graph. Covers seed selection (BM25 + --seed-id),
JSON output shape, unknown-id error path, empty-store handling,
deterministic seed pick, --emit-phantoms output, and suggested-action
vocabulary.

src/aelfrice/slash_commands/{reason,wonder}.md: aelf:reason +
aelf:wonder slash command files mirroring the existing search.md
template. Both surfaces are now invokable via slash command.

tests/test_slash_commands.py: registers reason/wonder in
EXPECTED_COMMANDS so the visible-CLI ↔ slash-dir parity test passes.
Full suite green: 2413 passed, 16 skipped.
)

Two new bench-gate tests mirroring the test_bfs_multihop_relates_to.py
pattern. Both skip cleanly when AELFRICE_CORPUS_ROOT is unset (public
CI), when their module dir is empty, or when fewer than MIN_ROWS=20
non-seed rows are present.

reasoning/ gate: chain hit@k uplift over baseline_search_only_top_k
≥+3pp (#389 decision-ask 4).

wonder_online/ gate: ≥1 expected candidate in top-10 across ≥60% of
rows (#389 decision-ask 8).

Schema validator (test_corpus_schema.py) registers both new modules
with their per-row field specs. Lab corpus content lives under
~/projects/aelfrice-lab/tests/corpus/v2_0/{reasoning,wonder_online}/
per directory-of-origin rules — public tree carries .gitkeep
placeholders only.
COMMANDS.md: bump count 26→28 and add Memory-operations rows for
both commands. Notes that aelf wonder's phantom-store integration is
deferred to v2.x #229 lane.

SLASH_COMMANDS.md: bump count 15→17 and add reference rows for
/aelf:reason and /aelf:wonder.
@robotrocketscience
robotrocketscience force-pushed the feat/issue-389-reason-wonder-cli branch from bf6e199 to cf00de3 Compare May 4, 2026 23:30
@yoshi280
yoshi280 enabled auto-merge (rebase) May 4, 2026 23:30

@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: 4

♻️ Duplicate comments (1)
src/aelfrice/cli.py (1)

789-790: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove the three redundant local import json statements.

json is already imported at module level on line 35. The local re-imports inside _cmd_reason (line 790) and _cmd_wonder (lines 947, 961) shadow the module import without adding value. CodeQL flagged all three, and the same finding was raised on prior commits.

♻️ Proposed fix
@@  in _cmd_reason
-    if args.json:
-        import json
-        payload = {
+    if args.json:
+        payload = {
@@  in _cmd_wonder (--emit-phantoms branch)
-    if args.emit_phantoms:
-        import json
-        payload = [
+    if args.emit_phantoms:
+        payload = [
@@  in _cmd_wonder (--json branch)
-    if args.json:
-        import json
-        payload2 = {
+    if args.json:
+        payload2 = {

Also applies to: 946-947, 960-961

🤖 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/cli.py` around lines 789 - 790, Remove the redundant local
"import json" statements that shadow the module-level import; specifically
delete the conditional local imports inside the functions/methods handling the
commands (the "if args.json: import json" lines found in _cmd_reason and
_cmd_wonder) so the code uses the json symbol already imported at module scope
(no other changes needed).
🧹 Nitpick comments (6)
src/aelfrice/models.py (1)

133-138: 💤 Low value

Track the ORIGIN_SPECULATIVEORIGINS reconciliation explicitly.

The comment notes this is deferred to #229, which is correct. To prevent the constant from drifting silently, consider either (a) adding a # noqa: future-#229 style marker that a follow-up PR can grep for, or (b) leaving a brief TODO(#229) next to ORIGINS itself so anyone editing that frozenset sees the link. Today, only this side documents the connection — a future contributor adding a new origin to ORIGINS would have no signal that ORIGIN_SPECULATIVE is intentionally missing.

🤖 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/models.py` around lines 133 - 138, Add an explicit
reconciliation marker so the ORIGIN_SPECULATIVE ↔ ORIGINS relationship is
discoverable: either annotate the ORIGIN_SPECULATIVE constant with a searchable
tag (e.g., "# TODO(`#229`)" or "# noqa: future-#229") and/or add a brief
"TODO(`#229`)" comment next to the ORIGINS frozenset declaration so future editors
see the linkage; reference ORIGIN_SPECULATIVE and ORIGINS when adding the
comment to ensure both places are easily grep-able for follow-up PR `#229`.
src/aelfrice/cli.py (2)

855-882: ⚖️ Poor tradeoff

_wonder_pick_seed is O(N) get_belief round-trips on every wonder invocation.

For each belief id, get_belief(bid) is called once, then edges_from(bid) again — that's 2N SQLite queries on top of list_belief_ids(). On a project-scale store (10k+ beliefs) this turns a "wonder" into seconds of latency.

If MemoryStore has a count_outbound_edges_per_belief() or similar aggregate query (or you can run SELECT b.id, COUNT(e.src) FROM beliefs b LEFT JOIN edges e ON ... WHERE b.lock_level != 'user' GROUP BY b.id), prefer that. Alternatively, add a single SQL helper to do this in one round-trip. Not a blocker for the 10–100 belief test corpora, but worth flagging before this hits a real project.

🤖 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/cli.py` around lines 855 - 882, _wonder_pick_seed currently
calls store.get_belief(bid) and store.edges_from(bid) for every belief id,
causing 2N DB round-trips; replace that hot loop with a single aggregated query
or helper on MemoryStore that returns non-locked belief ids and their outbound
edge counts in one call (for example count_outbound_edges_per_belief or a method
that returns (id, degree) pairs), then pick the max-degree/lowest-id tie from
that result and finally call get_belief only once for the chosen id (or return
the pre-fetched belief object if the helper returns it) to reduce round-trips to
one.

915-915: ⚡ Quick win

total_budget=args.top * 2 may surface fewer than --top candidates.

expand_bfs caps total expanded nodes at total_budget; for --top 10 that's 20 nodes across all hops, which can easily under-deliver on dense graphs (e.g., when fanout produces many depth-1 hits with low path-scores that displace the depth-2 hits the user is asking for) and under-deliver on sparse graphs (visited-set + cycle pruning eats the budget). The user expects --top N to actually return up to N rows; the implementation can silently truncate.

Consider scaling more generously (e.g., total_budget=max(args.top * 4, 32)), or honoring the same --budget flag the reason command exposes so operators can tune. At minimum, add a unit test that pins the "we get back ≤ args.top, but typically ≈ args.top on a normal graph" expectation.

🤖 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/cli.py` at line 915, The current call to expand_bfs(seed_b,
store, max_depth=2, total_budget=args.top * 2) can under-deliver results; change
the total_budget calculation to a more generous or configurable value (for
example total_budget=max(args.top * 4, 32) or wire it to the same
--budget/args.budget flag used by the reason command) so expand_bfs can return
up to args.top candidates reliably; update the call site where hops =
expand_bfs([seed_b], store, max_depth=2, total_budget=...) and add a unit test
that simulates dense and sparse graphs asserting the command returns ≤ args.top
but typically ≈ args.top (and verify the new budget prevents silent truncation).
tests/corpus/v2_0/README.md (1)

85-86: ⚡ Quick win

Missing ship-gate documentation sections for reasoning and wonder_online.

Every other module documented in this README has a dedicated subsection explaining the ship gate, threshold, per-row shape, and aggregation rule (e.g., ### tests_edge ship gate (#384)). The new reasoning and wonder_online modules add only the schema-table rows at lines 85-86 — readers don't see:

  • The bench-gate thresholds (per the PR: ≥+3pp hit@k uplift for aelf reason; ≥60% row-recall@10 for aelf wonder).
  • The aggregation formula (baseline_search_only_top_k is a comparison set — its semantics aren't defined anywhere).
  • Skip-on-empty-corpus behavior for the bench-gate harnesses at tests/bench_gate/test_reason.py and tests/bench_gate/test_wonder_online.py.

Add subsections analogous to the existing ones so the corpus contract is self-contained.

🤖 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/corpus/v2_0/README.md` around lines 85 - 86, Add missing ship-gate
subsections for the new modules reasoning and wonder_online: create two
subsections (e.g., "### tests_reasoning ship gate" and "### tests_wonder_online
ship gate") that document the bench-gate thresholds (reasoning: ≥+3pp hit@k
uplift for "aelf reason"; wonder_online: ≥60% row-recall@10 for "aelf wonder"),
the per-row input/output shape (fields: reasoning uses query, beliefs, edges,
expected_hit_ids, baseline_search_only_top_k, k; wonder_online uses beliefs,
edges, seed_id, expected_candidate_ids), the aggregation rule clarifying
baseline_search_only_top_k semantics (it's a comparison set used to compute
uplift against baseline_search_only_top_k), and the skip-on-empty-corpus
behavior consistent with tests/bench_gate/test_reason.py and
tests/bench_gate/test_wonder_online.py so the corpus README is self-contained.
tests/bench_gate/test_wonder_online.py (1)

58-72: 🏗️ Heavy lift

Prefer a shared wonder-ranking helper over re-implementing scoring in the gate.

Line 58-Line 72 duplicates the _cmd_wonder ranking math in test code. Centralizing the ranking path avoids future divergence between ship behavior and bench-gate measurement.

🤖 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/bench_gate/test_wonder_online.py` around lines 58 - 72, The test
duplicates the ranking math from _cmd_wonder in _row_top_k_candidates; extract
the combined scoring + ranking into a single shared helper (e.g.,
aelfrice.wonder_consolidation.rank_or_get_top_k) and have _row_top_k_candidates
call that helper instead of re-implementing the logic; update callers
(_cmd_wonder and this test) to import and use the new helper so scoring uses
wonder_consolidation.score and expand_bfs only in one place, returning the same
set[str] top-k ids.
tests/bench_gate/test_reason.py (1)

61-63: 🏗️ Heavy lift

Reduce gate/CLI drift by sharing reason-surface defaults and ranking path.

Line 61-Line 63 hardcode seed-limit/expansion behavior locally. If CLI defaults evolve, this gate can silently validate a different algorithm than the shipped command. Consider routing both CLI and bench gate through one shared helper/config source.

🤖 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/bench_gate/test_reason.py` around lines 61 - 63, The test hardcodes
seed/expansion parameters (calls to store.search_beliefs and expand_bfs and
subsequent surfaced_ids assembly) which can drift from the CLI; refactor the
test to consume the shared reason-surface defaults and ranking path used by the
CLI (e.g., a single helper or config provider) instead of literal
limit/expansion behavior — replace direct calls to store.search_beliefs(...) and
expand_bfs(...) with calls to that shared helper (or import the CLI's
defaults/ranking function) so the test uses the same parameters and
surface-ordering logic as the shipped command.
🤖 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 922-927: The blending formula currently computes combined =
h.score * (0.5 + 0.5 * relatedness) so relatedness can only attenuate h.score;
update the code to either (A) explicitly document this as a "path-score with
relatedness penalty" in the surrounding docstring/comment near combined and keep
the formula, or (B) change the blending to actually let both signals contribute
(e.g., use an additive mix like 0.5*h.score + 0.5*relatedness or a geometric
mean like math.sqrt(h.score * relatedness)) so relatedness can boost candidates;
locate the combined calculation and _suggested_action_for(...) call and apply
the chosen change, and ensure candidates.append still receives (combined, h,
action, relatedness).
- Around line 842-852: The docstring for _suggested_action_for incorrectly
states that priority follows _WONDER_ACTION_BY_EDGE insertion order while the
implementation actually uses path order; either update the docstring to state
"first matching edge in the path wins" (e.g., "priority is path-order: the first
decisional edge encountered") or change the implementation to respect dict
insertion priority by iterating _WONDER_ACTION_BY_EDGE keys and returning the
first edge_type present in the path; reference the function
_suggested_action_for and the mapping _WONDER_ACTION_BY_EDGE when making the
change.
- Around line 982-987: The loop unpacks four values from candidates into
(combined, h, action, relatedness) but never uses relatedness, triggering Ruff
B007; update the unpacking in the for loop that iterates over candidates to use
an unused-name (e.g., _relatedness or _) instead of relatedness so the intent is
clear and the linter is satisfied — modify the for statement that currently
reads for combined, h, action, relatedness in candidates: to use the unused
variable name.
- Around line 746-787: The seed-id error prints to the success output stream;
change the error prints in the seed-not-found branches to write to sys.stderr
instead of using the out parameter so error messages are kept off stdout/JSON;
update the seed-id failure handling in _cmd_reason (the loop that calls
store.get_belief and prints "aelf reason: seed-id not found: {sid}") to print to
sys.stderr and keep the return code 2, and make the same adjustment in the
analogous seed-not-found branch in _cmd_wonder so both handlers consistently
emit errors on stderr.

---

Duplicate comments:
In `@src/aelfrice/cli.py`:
- Around line 789-790: Remove the redundant local "import json" statements that
shadow the module-level import; specifically delete the conditional local
imports inside the functions/methods handling the commands (the "if args.json:
import json" lines found in _cmd_reason and _cmd_wonder) so the code uses the
json symbol already imported at module scope (no other changes needed).

---

Nitpick comments:
In `@src/aelfrice/cli.py`:
- Around line 855-882: _wonder_pick_seed currently calls store.get_belief(bid)
and store.edges_from(bid) for every belief id, causing 2N DB round-trips;
replace that hot loop with a single aggregated query or helper on MemoryStore
that returns non-locked belief ids and their outbound edge counts in one call
(for example count_outbound_edges_per_belief or a method that returns (id,
degree) pairs), then pick the max-degree/lowest-id tie from that result and
finally call get_belief only once for the chosen id (or return the pre-fetched
belief object if the helper returns it) to reduce round-trips to one.
- Line 915: The current call to expand_bfs(seed_b, store, max_depth=2,
total_budget=args.top * 2) can under-deliver results; change the total_budget
calculation to a more generous or configurable value (for example
total_budget=max(args.top * 4, 32) or wire it to the same --budget/args.budget
flag used by the reason command) so expand_bfs can return up to args.top
candidates reliably; update the call site where hops = expand_bfs([seed_b],
store, max_depth=2, total_budget=...) and add a unit test that simulates dense
and sparse graphs asserting the command returns ≤ args.top but typically ≈
args.top (and verify the new budget prevents silent truncation).

In `@src/aelfrice/models.py`:
- Around line 133-138: Add an explicit reconciliation marker so the
ORIGIN_SPECULATIVE ↔ ORIGINS relationship is discoverable: either annotate the
ORIGIN_SPECULATIVE constant with a searchable tag (e.g., "# TODO(`#229`)" or "#
noqa: future-#229") and/or add a brief "TODO(`#229`)" comment next to the ORIGINS
frozenset declaration so future editors see the linkage; reference
ORIGIN_SPECULATIVE and ORIGINS when adding the comment to ensure both places are
easily grep-able for follow-up PR `#229`.

In `@tests/bench_gate/test_reason.py`:
- Around line 61-63: The test hardcodes seed/expansion parameters (calls to
store.search_beliefs and expand_bfs and subsequent surfaced_ids assembly) which
can drift from the CLI; refactor the test to consume the shared reason-surface
defaults and ranking path used by the CLI (e.g., a single helper or config
provider) instead of literal limit/expansion behavior — replace direct calls to
store.search_beliefs(...) and expand_bfs(...) with calls to that shared helper
(or import the CLI's defaults/ranking function) so the test uses the same
parameters and surface-ordering logic as the shipped command.

In `@tests/bench_gate/test_wonder_online.py`:
- Around line 58-72: The test duplicates the ranking math from _cmd_wonder in
_row_top_k_candidates; extract the combined scoring + ranking into a single
shared helper (e.g., aelfrice.wonder_consolidation.rank_or_get_top_k) and have
_row_top_k_candidates call that helper instead of re-implementing the logic;
update callers (_cmd_wonder and this test) to import and use the new helper so
scoring uses wonder_consolidation.score and expand_bfs only in one place,
returning the same set[str] top-k ids.

In `@tests/corpus/v2_0/README.md`:
- Around line 85-86: Add missing ship-gate subsections for the new modules
reasoning and wonder_online: create two subsections (e.g., "### tests_reasoning
ship gate" and "### tests_wonder_online ship gate") that document the bench-gate
thresholds (reasoning: ≥+3pp hit@k uplift for "aelf reason"; wonder_online: ≥60%
row-recall@10 for "aelf wonder"), the per-row input/output shape (fields:
reasoning uses query, beliefs, edges, expected_hit_ids,
baseline_search_only_top_k, k; wonder_online uses beliefs, edges, seed_id,
expected_candidate_ids), the aggregation rule clarifying
baseline_search_only_top_k semantics (it's a comparison set used to compute
uplift against baseline_search_only_top_k), and the skip-on-empty-corpus
behavior consistent with tests/bench_gate/test_reason.py and
tests/bench_gate/test_wonder_online.py so the corpus README is self-contained.
🪄 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: 1dcb0bdc-bf21-460c-b668-d90ee5f886ee

📥 Commits

Reviewing files that changed from the base of the PR and between d1d28d5 and cf00de3.

📒 Files selected for processing (14)
  • docs/COMMANDS.md
  • docs/SLASH_COMMANDS.md
  • src/aelfrice/cli.py
  • src/aelfrice/models.py
  • src/aelfrice/slash_commands/reason.md
  • src/aelfrice/slash_commands/wonder.md
  • tests/bench_gate/test_reason.py
  • tests/bench_gate/test_wonder_online.py
  • tests/corpus/v2_0/README.md
  • tests/corpus/v2_0/reasoning/.gitkeep
  • tests/corpus/v2_0/wonder_online/.gitkeep
  • tests/test_cli_reason_wonder.py
  • tests/test_corpus_schema.py
  • tests/test_slash_commands.py

Comment thread src/aelfrice/cli.py
Comment on lines +746 to +787
def _cmd_reason(args: argparse.Namespace, out: object) -> int:
"""Surface a reasoning chain over the belief graph for a query.

Seeds: explicit `--seed-id` (repeatable) wins; otherwise top-k
`search_beliefs` BM25 hits over `args.query`. Walks `expand_bfs`
from those seeds with terminal-tight defaults and prints either
an indented hop tree (default) or JSON when `--json`.

Read-only: never writes to the store.
"""
store = _open_store()
try:
seeds: list = []
if args.seed_id:
for sid in args.seed_id:
b = store.get_belief(sid)
if b is None:
print(
f"aelf reason: seed-id not found: {sid}",
file=out, # type: ignore[arg-type]
)
return 2
seeds.append(b)
else:
seeds = store.search_beliefs(args.query, limit=args.k)
if not seeds:
print(
"aelf reason: no seeds (empty store, or query didn't "
"match any indexed belief). Try --seed-id <id> to "
"force a starting point.",
file=out, # type: ignore[arg-type]
)
return 0
hops = expand_bfs(
seeds,
store,
max_depth=args.depth,
nodes_per_hop=args.fanout,
total_budget=args.budget,
)
finally:
store.close()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reason handler logic LGTM, with one robustness nit on --seed-id exit semantics.

--seed-id failure on line 762-767 prints to out (stdout) but returns exit 2. Other CLI handlers in this file (e.g., _cmd_demote line 1113, _cmd_uninstall line 1834) emit error messages on sys.stderr and reserve out for successful payloads. Inconsistency means a JSON-consuming caller using --json could get an error string mixed into stdout when one seed-id is bogus.

♻️ Proposed fix
-                    print(
-                        f"aelf reason: seed-id not found: {sid}",
-                        file=out,  # type: ignore[arg-type]
-                    )
+                    print(
+                        f"aelf reason: seed-id not found: {sid}",
+                        file=sys.stderr,
+                    )
                     return 2

Same fix applies to the _cmd_wonder "seed not found" branch at line 901-904.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _cmd_reason(args: argparse.Namespace, out: object) -> int:
"""Surface a reasoning chain over the belief graph for a query.
Seeds: explicit `--seed-id` (repeatable) wins; otherwise top-k
`search_beliefs` BM25 hits over `args.query`. Walks `expand_bfs`
from those seeds with terminal-tight defaults and prints either
an indented hop tree (default) or JSON when `--json`.
Read-only: never writes to the store.
"""
store = _open_store()
try:
seeds: list = []
if args.seed_id:
for sid in args.seed_id:
b = store.get_belief(sid)
if b is None:
print(
f"aelf reason: seed-id not found: {sid}",
file=out, # type: ignore[arg-type]
)
return 2
seeds.append(b)
else:
seeds = store.search_beliefs(args.query, limit=args.k)
if not seeds:
print(
"aelf reason: no seeds (empty store, or query didn't "
"match any indexed belief). Try --seed-id <id> to "
"force a starting point.",
file=out, # type: ignore[arg-type]
)
return 0
hops = expand_bfs(
seeds,
store,
max_depth=args.depth,
nodes_per_hop=args.fanout,
total_budget=args.budget,
)
finally:
store.close()
def _cmd_reason(args: argparse.Namespace, out: object) -> int:
"""Surface a reasoning chain over the belief graph for a query.
Seeds: explicit `--seed-id` (repeatable) wins; otherwise top-k
`search_beliefs` BM25 hits over `args.query`. Walks `expand_bfs`
from those seeds with terminal-tight defaults and prints either
an indented hop tree (default) or JSON when `--json`.
Read-only: never writes to the store.
"""
store = _open_store()
try:
seeds: list = []
if args.seed_id:
for sid in args.seed_id:
b = store.get_belief(sid)
if b is None:
print(
f"aelf reason: seed-id not found: {sid}",
file=sys.stderr,
)
return 2
seeds.append(b)
else:
seeds = store.search_beliefs(args.query, limit=args.k)
if not seeds:
print(
"aelf reason: no seeds (empty store, or query didn't "
"match any indexed belief). Try --seed-id <id> to "
"force a starting point.",
file=out, # type: ignore[arg-type]
)
return 0
hops = expand_bfs(
seeds,
store,
max_depth=args.depth,
nodes_per_hop=args.fanout,
total_budget=args.budget,
)
finally:
store.close()
🤖 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/cli.py` around lines 746 - 787, The seed-id error prints to the
success output stream; change the error prints in the seed-not-found branches to
write to sys.stderr instead of using the out parameter so error messages are
kept off stdout/JSON; update the seed-id failure handling in _cmd_reason (the
loop that calls store.get_belief and prints "aelf reason: seed-id not found:
{sid}") to print to sys.stderr and keep the return code 2, and make the same
adjustment in the analogous seed-not-found branch in _cmd_wonder so both
handlers consistently emit errors on stderr.

Comment thread src/aelfrice/cli.py
Comment on lines +842 to +852
def _suggested_action_for(path: list[str]) -> str:
"""Map a BFS edge-type path to a one-word suggested action.

Picks the highest-priority edge type seen on the path, with
fall-through to "relate" when none match. Priority order matches
`_WONDER_ACTION_BY_EDGE` insertion order.
"""
for edge_type in path:
if edge_type in _WONDER_ACTION_BY_EDGE:
return _WONDER_ACTION_BY_EDGE[edge_type]
return "relate"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

_suggested_action_for priority is path-order, not the documented dict-insertion order.

The docstring claims "Picks the highest-priority edge type seen on the path, with fall-through to 'relate'. Priority order matches _WONDER_ACTION_BY_EDGE insertion order." But the loop returns on the first edge type in path that's in the dict — so for a path [EDGE_SUPPORTS, EDGE_SUPERSEDES] the result is "merge" (path-order), even though EDGE_SUPERSEDES has higher dict-insertion priority.

If path-order is intended (e.g., "the first decisional edge encountered while walking out from the seed wins"), reword the docstring. If dict-priority was intended, walk the dict instead:

♻️ Proposed fix (dict-priority semantics)
 def _suggested_action_for(path: list[str]) -> str:
-    """Map a BFS edge-type path to a one-word suggested action.
-
-    Picks the highest-priority edge type seen on the path, with
-    fall-through to "relate" when none match. Priority order matches
-    `_WONDER_ACTION_BY_EDGE` insertion order.
-    """
-    for edge_type in path:
-        if edge_type in _WONDER_ACTION_BY_EDGE:
-            return _WONDER_ACTION_BY_EDGE[edge_type]
-    return "relate"
+    """Map a BFS edge-type path to a one-word suggested action.
+
+    Picks the highest-priority edge type present on the path, with
+    fall-through to "relate". Priority follows `_WONDER_ACTION_BY_EDGE`
+    insertion order (SUPERSEDES > CONTRADICTS > SUPPORTS).
+    """
+    seen = set(path)
+    for edge_type, action in _WONDER_ACTION_BY_EDGE.items():
+        if edge_type in seen:
+            return action
+    return "relate"
🤖 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/cli.py` around lines 842 - 852, The docstring for
_suggested_action_for incorrectly states that priority follows
_WONDER_ACTION_BY_EDGE insertion order while the implementation actually uses
path order; either update the docstring to state "first matching edge in the
path wins" (e.g., "priority is path-order: the first decisional edge
encountered") or change the implementation to respect dict insertion priority by
iterating _WONDER_ACTION_BY_EDGE keys and returning the first edge_type present
in the path; reference the function _suggested_action_for and the mapping
_WONDER_ACTION_BY_EDGE when making the change.

Comment thread src/aelfrice/cli.py
Comment on lines +922 to +927
# Combine BFS path-score with token-overlap relatedness.
# Multiplicative so both signals must be non-trivial for a
# candidate to rank high.
combined = h.score * (0.5 + 0.5 * relatedness)
action = _suggested_action_for(h.path)
candidates.append((combined, h, action, relatedness))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Relatedness can only attenuate combined, never boost it — clarify the docstring or rebalance the formula.

combined = h.score * (0.5 + 0.5 * relatedness) produces a multiplier in [0.5, 1.0], so a high token-overlap relatedness yields at best h.score, while a low relatedness halves the score. The accompanying comment ("Multiplicative so both signals must be non-trivial for a candidate to rank high") reads as if both signals are co-equal, but in practice BFS path-score dominates and relatedness only acts as a discount.

If the intent was to actually require both, an additive blend (0.5*h.score + 0.5*relatedness) or a geometric mean (sqrt(h.score * relatedness)) would let either signal dominate. If the intent really is "BFS path-score with a relatedness penalty", the docstring should say that explicitly so future tuners don't try to "fix" what looks like a bug.

This is also relevant to the wonder bench gate (≥60% row-recall@10) — if the corpus run misses the threshold, this formula is the first knob to revisit.

🤖 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/cli.py` around lines 922 - 927, The blending formula currently
computes combined = h.score * (0.5 + 0.5 * relatedness) so relatedness can only
attenuate h.score; update the code to either (A) explicitly document this as a
"path-score with relatedness penalty" in the surrounding docstring/comment near
combined and keep the formula, or (B) change the blending to actually let both
signals contribute (e.g., use an additive mix like 0.5*h.score + 0.5*relatedness
or a geometric mean like math.sqrt(h.score * relatedness)) so relatedness can
boost candidates; locate the combined calculation and _suggested_action_for(...)
call and apply the chosen change, and ensure candidates.append still receives
(combined, h, action, relatedness).

Comment thread src/aelfrice/cli.py
Comment on lines +982 to +987
print(f"top {len(candidates)} consolidation candidate(s):", file=out) # type: ignore[arg-type]
for combined, h, action, relatedness in candidates:
print(
f" [{combined:.3f}] ({action}) {h.belief.id}: {h.belief.content}",
file=out, # type: ignore[arg-type]
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Unused loop variable relatedness (Ruff B007).

The unpacking on line 983 binds relatedness, but it's not referenced inside the loop body — only combined, h, and action are printed. Rename to _relatedness (or _) to silence Ruff and signal intent.

♻️ Proposed fix
-    for combined, h, action, relatedness in candidates:
+    for combined, h, action, _relatedness in candidates:
         print(
             f"  [{combined:.3f}] ({action}) {h.belief.id}: {h.belief.content}",
             file=out,  # type: ignore[arg-type]
         )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
print(f"top {len(candidates)} consolidation candidate(s):", file=out) # type: ignore[arg-type]
for combined, h, action, relatedness in candidates:
print(
f" [{combined:.3f}] ({action}) {h.belief.id}: {h.belief.content}",
file=out, # type: ignore[arg-type]
)
print(f"top {len(candidates)} consolidation candidate(s):", file=out) # type: ignore[arg-type]
for combined, h, action, _relatedness in candidates:
print(
f" [{combined:.3f}] ({action}) {h.belief.id}: {h.belief.content}",
file=out, # type: ignore[arg-type]
)
🧰 Tools
🪛 Ruff (0.15.12)

[warning] 983-983: Loop control variable relatedness not used within loop body

(B007)

🤖 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/cli.py` around lines 982 - 987, The loop unpacks four values
from candidates into (combined, h, action, relatedness) but never uses
relatedness, triggering Ruff B007; update the unpacking in the for loop that
iterates over candidates to use an unused-name (e.g., _relatedness or _) instead
of relatedness so the intent is clear and the linter is satisfied — modify the
for statement that currently reads for combined, h, action, relatedness in
candidates: to use the unused variable name.

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

Labels

attn:merge-conflict PR branch needs rebase

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[v2.0 / Track B] re-introduce aelf reason + aelf wonder CLI (ship together, bench-gated)

3 participants