fix(user-prompt): inject prompt-matched conclusions instead of stalest representation lines#63
Conversation
…t representation lines The UserPromptSubmit hook ran a semantic search via peer.context() but then injected the first 5 lines of the representation string. The representation merges search hits with frequent/recent conclusions into one timestamp-ascending list, so the injected lines were always the oldest conclusions — the search results never surfaced, and the same stale facts repeated regardless of what the user asked. - Query matched conclusions explicitly (conclusionScope.query) in parallel with peer.context() and put them first in the injection - Fill remaining slots with the newest representation lines (timestamp-desc) instead of the oldest, deduped by normalized text - Drop the English-only stopword fallback in extractTopics; fall back to the raw prompt (truncated), which embeds better for semantic search and keeps non-English prompts working Measured before/after on a live workspace (358 conclusions): before, 4 of 5 injected lines were identical with and without searchQuery and unrelated to the prompt; after, all 5 lines match the prompt topic for both English and Italian prompts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
WalkthroughThis PR refines the context search and formatting pipeline in the user-prompt hook. Topic extraction is simplified to return only high-signal terms; search operations are parallelized with matched conclusions stored for prioritized formatting; and context assembly now prioritizes prompt-matched conclusions with timestamp-aware deduplication and ordering. ChangesContext Search and Formatting Improvements
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
…ogic Export extractTopics/stripConclusionLine/formatCachedContext and cover them with bun test: search-matched-first ordering, newest-not-oldest fill (regression for the head-of-representation bug), normalized dedup, 5-slot cap, header filtering, no-timestamp order preservation, JSON cache round-trip, and the empty-topics return that drives the raw-prompt fallback for non-English prompts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Brings the fork up to upstream 15d07cd (0.2.5): memory statusLine (plastic-labs#55), async sessionStart (plastic-labs#43), skip `cd` in tool logging (#21a5c8a), host-level apiKey, version-update nag, analyze-usage script. Reconciled with our carried deltas (all preserved): - plastic-labs#40 per-session dedup + plastic-labs#63 relevance ordering (still open upstream) - retrieval config + relevance-fresh fetch, skip-patterns - query_conclusions MCP tool (#2), 60s dialectic timeout (#1) - tool-call uploads stay DISABLED Conflicts resolved in user-prompt.ts (combined relevance-fresh logging with upstream's setMemoryState("recalling"); kept both node:fs/path and getRetrievalConfig imports) and CHANGELOG.md (query_conclusions under [Unreleased], upstream's [0.2.5] below). Other overlapping files (config.ts, post-tool-use.ts, pre-compact.ts, session-start.ts, mcp/server.ts) auto-merged and were audited semantically correct. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HwfqcAc2kujJNmQG9UvPqa
…facts The hook ran a semantic search over the prompt but then injected the first 5 lines of the representation — which is ordered oldest-first, so the search hits never surfaced and every turn re-showed the same stale facts. - Query matched conclusions explicitly (conclusionScope.query) in parallel with the context call and inject them first (cached as searchMatched so cache-served turns keep them). - Fill remaining slots with the NEWEST representation lines (timestamp-desc), deduped by normalized text, still honoring the per-session `seen` set. - Drop extractTopics' English-only stopword fallback; fall back to the raw prompt (truncated 300 chars), which embeds better and keeps non-English prompts working. Adapts upstream plastic-labs#63 to this fork's cross-turn `seen`/`newConclusions` dedup layer. Adds conclusion-scope stubs to the test mocks so the search path exercises end-to-end.
Turn-path reliability and memory quality since 0.1.0: - Non-blocking Stop hook + workspace-scoped outbox drain (#15) - Strip pasted non-prose from user messages; tag tool actions (plastic-labs#34) - Inject prompt-matched conclusions instead of the stalest facts (plastic-labs#63)
Problem
The
UserPromptSubmithook runs a semantic search over the user's prompt (peer.context({ searchQuery, ... })), but thenformatCachedContext()injects the first 5 lines of the representation string. The representation merges search hits with frequent/recent conclusions into a single timestamp-ascending list, so the injected lines are always the oldest conclusions in the window — the search results never surface.Measured on a live workspace (358 conclusions): with and without
searchQuery, 4 of 5 injected lines were identical and unrelated to the prompt. In practice every turn re-injects the same stale facts regardless of what the user asks — closely related to the repetition reported in #39 (includeMostFrequentpinning is only half the story; head-of-representation selection is the other half).A second, compounding issue:
extractTopics()falls back to an English-stopword word filter, so non-English prompts produce low-signal queries ("come siamo messi" → word salad).Fix
conclusionScope.query(searchQuery, 5)) in parallel withpeer.context()and put them first in the injected block (cached alongside the context, so cache-served turns keep them too)[timestamp]prefix) instead of the oldest, deduped by normalized textNo new API surface:
conclusionScope.queryalready exists in the SDK; failures degrade gracefully to the previous behavior (.catch(() => [])).Before / after (live workspace, same prompts)
Prompt (Italian): "come va il deriver di honcho su kubernetes? flux ha applicato la modifica ai worker?"
Before — 5 conclusions about an unrelated design discussion from a previous day, identical to what every other prompt received.
After — all 5 conclusions about the user's Kubernetes/k3s work from the same day.
Prompt (English): "help me create the new jira tickets for the REORG board" → after: all 5 conclusions about the user's recent Jira board work.
Test plan
tsc --noEmitcleanbun test— 16 cases covering search-matched-first ordering, newest-not-oldest fill (regression for the head-of-representation bug), normalized dedup, the 5-slot cap, and the empty-topics return that drives the raw-prompt fallback (src/hooks/user-prompt.test.ts)conclusionScope.queryrejection falls back to representation-only selectionsearchMatchedsurvives the context cache round-trip (plain JSON property)🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes