feat(v3.0): wire API-based summarizer from config (supersedes #26)#27
Merged
Merged
Conversation
- Read provider/model/api_key_env from SummarizerSettings config - Construct ApiSummarizer (OpenAI or Anthropic) when API key available - Fallback to MockSummarizer with warning when no API key found - Add build_summarizer() function for testable summarizer construction - Add unit tests for provider selection and env var lookup - Update planning docs for v3.0
PR #26 added a "v3.0 — Real Summarization" section to ROADMAP.md and a v3.0 in-progress block to STATE.md, both numbering this work as Phase 51. The local main branch (not yet on origin/main) already has "v3.0 Competitive Parity & Benchmarks" with Phase 51 = Retrieval Orchestrator, Phase 52 = Simple CLI API, Phase 53 = Benchmark Suite. Carrying PR #26's planning edits forward would create a phase-numbering collision when local main is pushed. Slot the summarizer wiring into the existing v3.0 plan in a follow-up (likely a new Phase 54 "Real Summarization", or fold into Phase 51's LLM reranker work).
Addresses code-review findings on PR #26 (closing #26 in favor of this PR). All four pr-precheck gates pass: fmt, clippy --all-targets --all-features -D warnings, full workspace test (~825 tests), doc with RUSTDOCFLAGS=-D warnings. Decision logic split (functional core / imperative shell): - Extract pure pick_summarizer_kind(settings, key_available) -> SummarizerKind so tests assert on the branch taken instead of only verifying non-panic. Five of seven original tests ended with `let _ = summarizer;` which couldn't detect a regression that, e.g., always returned Mock. API hygiene: - Drop dead 2-tuple from resolve_api_key. The second element was always discarded by the only caller and misleadingly returned the *default* env var name even when api_key_env override was active, so any future caller using it would log the wrong var on a cache miss. - Downgrade pub -> pub(crate) on build_summarizer, env_var_for_provider, and pick_summarizer_kind. memory-daemon has both lib.rs and a [[bin]] target, so pub leaked test helpers as part of the library's public API. Helpers + duplication: - Extract is_anthropic / is_openai / default_env_var_for_provider using eq_ignore_ascii_case (was: 3 sites of `.to_lowercase() == "anthropic"` with per-call allocations). - Warn on unknown providers before fail-open to OpenAI (was: silent fall-through that would have routed an OPENAI_API_KEY to api.openai.com for typos like "openi" or genuinely different providers like "google"). Tests (7 weak -> 16 strong): - 4 env_var_for_provider mapping tests (kept as-is) - 6 pick_summarizer_kind decision tests (NEW: assert exact SummarizerKind for openai / anthropic / mixed-case / unknown-provider / no-key paths) - 5 resolve_api_key tests (NEW: regression guard for the precedence ordering api_key > api_key_env > provider default; explicit override vs custom env var; missing custom env var) - 1 build_summarizer smoke test (verifies wiring + Arc allocation) Closes #26
CI runs `dtolnay/rust-toolchain@stable` which now ships clippy from Rust 1.95, where `clippy::unnecessary_sort_by` is enforced under `-D warnings`. The pre-existing `sort_by(|a, b| b.1.cmp(&a.1))` on stale_filter.rs:163 fires the lint and blocks CI for any branch. Replace with the canonical `sort_by_key(|b| Reverse(b.1))` form suggested by clippy. Semantically equivalent (descending order by .1). Local toolchain (1.93) does not have this lint yet, which is why this slipped through `task pr-precheck` here. Fixed in this PR to unblock CI; benefits #25 and any other open branch.
Round 2 of the Rust 1.95 stable upgrade adaptation. Previous commit fixed stale_filter.rs:163 which let CI advance past memory-retrieval, where it then surfaced the same lint in three more sites. Workspace grep for the simple `cmp(&)` form (which the lint flags) covers all remaining cases: - memory-storage/src/db.rs:462 (sort_by_key on start_time, ascending) - memory-storage/src/db.rs:481 (same in get_child_nodes) - memory-service/src/agents.rs:85 (Reverse for descending last_seen_ms) - memory-toc/src/summarizer/mock.rs:122 (Reverse for descending count) The remaining sort_by sites in the workspace use multi-line closures or partial_cmp (Option-returning) which the lint cannot auto-simplify; they pass clippy as-is. All semantically equivalent. Tests on the three modified crates remain green (110 + 42 + 70 + doctests).
RichardHightower
added a commit
that referenced
this pull request
Apr 28, 2026
…al phase stack Sync planning files with origin/main reality after PR #27 merged as `3a73582`: - ROADMAP.md: insert Phase 51.5 (API Summarizer Wiring) between Phases 51 and 52 as a decimal out-of-band phase. Marks complete (merged 2026-04-28). - STATE.md: bump status to in_progress, total_phases 3 -> 4, completed 0 -> 1. Add Out-of-band Work section flagging: - PR #25 (cross-project memory) phase-numbering conflict with local Phase 51 - Local-only gsd/phase-{51..58} stack (~80 commits, no PRs yet) Phase 51.5 supersedes closed PR #26 with stronger tests (16 vs 7) and a pick_summarizer_kind decision split. Inserted out-of-band because the summarizer wiring shipped before Phase 51 (Retrieval Orchestrator) itself. No code changes. pr-precheck (fmt + clippy + test + doc) passes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Replaces #26 with the same feature (wire
ApiSummarizerinto the daemon from config + env vars, replacing the hardcodedMockSummarizerTODO) plus the review feedback from #26 applied. All fourtask pr-precheckgates pass: fmt, clippy--all-targets --all-features -D warnings, full workspace test (~825 tests), and doc withRUSTDOCFLAGS=-D warnings.What's the same as #26
crates/memory-daemon/src/commands.rsbuild_summarizer()reads provider/model from config, resolves API key from env var or config, constructsApiSummarizeror falls back toMockSummarizerwith warning.crates/memory-types/src/config.rsapi_key_env: Option<String>added toSummarizerSettingswithserde(default)for backward compat.What's improved over #26 (review-driven)
Decision logic split (functional core / imperative shell)
Five of #26's seven new tests ended with
let _ = summarizer;— they only provedbuild_summarizer()didn't panic. They couldn't detect a regression that always returnedMockor always returnedApi. This PR extracts a pure decision function:Tests now assert on the exact
SummarizerKindreturned for every provider × key-availability combination.API hygiene
resolve_api_key. Wasfn resolve_api_key(...) -> (Option<String>, &'static str)but the only caller didlet (api_key, _) = .... The discarded second element was also misleadingly the default env var name even whenapi_key_envoverride was active, so any future caller using it would log the wrong var on a cache miss. Nowfn resolve_api_key(...) -> Option<String>.pub→pub(crate)onbuild_summarizer,env_var_for_provider,pick_summarizer_kind.memory-daemonhas bothlib.rsand a[[bin]]target, sopubwas leaking test helpers as part of the library's public API.Helpers + duplication
is_anthropic/is_openai/default_env_var_for_providerusingeq_ignore_ascii_case(was: 3 sites of.to_lowercase() == "anthropic"with per-call allocations).warn!on unknown providers before fail-open to OpenAI (was: silent fall-through — typos like"openi"or different vendors like"google"would silently route toapi.openai.com).Tests: 7 weak → 16 strong
env_var_for_providermappingpick_summarizer_kinddecisionSummarizerKindfor openai / anthropic / mixed-case / unknown / no-key pathsresolve_api_keyresolutionapi_keywins overapi_key_enveven when env var is set with a different value)build_summarizersmokeArc::strong_count == 1The precedence test is the one #26's description claimed but didn't actually have.
Why a new PR instead of force-pushing #26
#26 also added a "v3.0 — Real Summarization / Phase 51: API Summarizer Wiring" section to ROADMAP.md and STATE.md. Local main (not yet on origin/main) has v3.0 Competitive Parity & Benchmarks with Phase 51 = Retrieval Orchestrator, Phase 52 = Simple CLI API, Phase 53 = Benchmark Suite — so #26's ROADMAP edits would collide on phase numbering once local main is pushed. This PR drops the planning edits in a separate revert commit; the summarizer work will be slotted into the existing v3.0 plan in a follow-up (likely a new Phase 54 "Real Summarization", or folded into Phase 51's LLM reranker work).
CI expectations
All checks should mirror #26 (which was fully green): Format, Clippy, Test (ubuntu+macos), Build (ubuntu+macos), Documentation, E2E Tests, and the full E2E CLI matrix across claude-code / gemini / opencode / copilot / codex on both ubuntu-24.04 and macos-latest.
Closes #26