Skip to content

feat(v3.0): wire API-based summarizer from config#26

Closed
RichardHightower wants to merge 2 commits into
mainfrom
feature/v3.0-api-summarizer-wiring
Closed

feat(v3.0): wire API-based summarizer from config#26
RichardHightower wants to merge 2 commits into
mainfrom
feature/v3.0-api-summarizer-wiring

Conversation

@RichardHightower

Copy link
Copy Markdown
Contributor

Summary

Wires the existing ApiSummarizer (OpenAI + Anthropic) into the daemon from config and environment variables. Previously hardcoded to MockSummarizer with a TODO comment.

What changed

File Change
crates/memory-daemon/src/commands.rs build_summarizer() function: reads provider/model from config, resolves API key from env var or config, constructs ApiSummarizer or falls back to MockSummarizer with warning. 7 unit tests.
crates/memory-types/src/config.rs Added api_key_env: Option<String> to SummarizerSettings for custom env var override (serde(default) for backward compat)
.planning/STATE.md, .planning/ROADMAP.md v3.0 progress updated

How it works

# ~/.config/agent-memory/config.toml
[summarizer]
provider = "openai"          # or "anthropic"
model = "gpt-4o-mini"        # or "claude-3-haiku-20240307"
# api_key_env = "MY_KEY"     # optional override, defaults to OPENAI_API_KEY / ANTHROPIC_API_KEY

Set the corresponding env var (OPENAI_API_KEY or ANTHROPIC_API_KEY) and the daemon picks it up automatically. Without an API key, it falls back to MockSummarizer with a warning log.

Design decisions

  • Env-var-first for secrets — API keys stay out of config files. Config can optionally override which env var to read via api_key_env.
  • Backward compatible — new field is Option<String> with serde(default), existing configs unchanged.
  • Fail-open — missing API key logs a warning and falls back to mock, never blocks startup.
  • Case-insensitive provider matchingOpenAI, openai, OPENAI all work.

Tests

7 unit tests covering:

  • Provider → env var mapping (openai, anthropic, custom)
  • Case-insensitive provider matching
  • Explicit key in config takes precedence over env var
  • Env var lookup for both providers
  • Fallback to mock when no key available

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

Copy link
Copy Markdown
Contributor Author

Closing in favor of #27, which carries the same feature plus the review feedback applied:

  • Pure pick_summarizer_kind(settings, key_available) -> SummarizerKind decision function so tests assert on the branch taken (was: only verified non-panic with let _ = summarizer;).
  • resolve_api_key simplified from (Option<String>, &'static str) to Option<String> (the second element was always discarded by the only caller).
  • pubpub(crate) on build_summarizer, env_var_for_provider, pick_summarizer_kind (memory-daemon is lib+bin, so pub was leaking test helpers).
  • is_anthropic / is_openai / default_env_var_for_provider helpers using eq_ignore_ascii_case (was: 3 sites of .to_lowercase()).
  • warn! on unknown providers before fail-open to OpenAI (was: silent).
  • Tests: 7 → 16, including the precedence regression guard the PR description claimed but didn't actually have.

The ROADMAP/STATE edits from this PR are reverted in #27 because they collide with the unpushed local v3.0 plan (Phase 51 = Retrieval Orchestrator, not Summarizer Wiring). The summarizer wiring will get its own slot in v3.0 via a follow-up (likely Phase 54).

RichardHightower added a commit that referenced this pull request Apr 28, 2026
* feat(v3.0): wire API-based summarizer from config

- 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

* fix(docs): use full path for Summarizer doc link in commands.rs

* revert(planning): drop ROADMAP/STATE edits from PR #26

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

* refactor(summarizer): apply PR #26 review feedback

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

* fix(retrieval): satisfy clippy::unnecessary_sort_by (Rust 1.95+)

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.

* fix(workspace): fix remaining clippy::unnecessary_sort_by sites

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>
@RichardHightower RichardHightower deleted the feature/v3.0-api-summarizer-wiring branch May 12, 2026 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant