Skip to content

feat: retrieve applicable experience by text - #6

Merged
fabbrik merged 2 commits into
mainfrom
feat/2-2-retrieve-by-text
Sep 22, 2026
Merged

fabbrik merged 2 commits into
mainfrom
feat/2-2-retrieve-by-text

Conversation

@fabbrik

@fabbrik fabbrik commented Sep 21, 2026

Copy link
Copy Markdown
Owner

Summary

Story 2.2. Stored records become findable: text-matched, eligibility-filtered and ranked.

  • New port. IExperienceCandidateSource in Abstractions, with a PostgreSQL implementation. IExperienceRecordStore is untouched.
  • Search in SQL. Migration 0003 adds a generated tsvector column over task ID, task summary and reflection lesson, with a GIN index. Scope, eligible status and the confidence threshold are applied in the same statement; relevance comes from normalized ts_rank_cd.
  • Eligibility in Core. Only Validated and Reinforced are eligible. Expiry and environment compatibility are decided over the returned candidates, and each exclusion says which check removed it.
  • Ranking. Relevance 0.35, confidence 0.25, recency 0.15, status 0.15, environment 0.10 by default — nonnegative, summing to 1, validated at construction. Every returned record exposes its normalized components and effective weights; ties break on ExperienceId. A golden fixture pins the documented order.
  • Bounded. A 500 ms default timeout returns an empty result carrying a timeout signal and correlation ID rather than throwing; caller cancellation stays distinct. Authorization mismatches, source failures and unreadable candidates all fail closed to an empty result.

Decisions recorded in the spec

Expiry and required environment attributes were undefined in planning, so this story defines them: MaxAge against UpdatedAt, and required attributes as key/value pairs where a missing key excludes the record and an empty set marks the result unrestricted. Recency decays by half every 30 days, which keeps the axis defined when no expiry is configured.

Review

Three reviewers raised 21 findings, all fixed. The substantive one: ranking only saw the strongest text matches, so a high-confidence recent record with a weaker match could never surface while the docs claimed every eligible record was returned. Truncation is now reported through Truncated and the ceiling is documented. Also fixed: the timeout path could exceed its own bound by cancelling Npgsql synchronously on the caller's thread, a token-source leak, unbounded task text and timeouts, and a relevance assertion that could not fail.

Test plan

  • dotnet build --configuration Release: 0 warnings, 0 errors
  • dotnet test --configuration Release: 523 of 523 pass (Core 307, Postgres 123). Locally with TESTCONTAINERS_RYUK_DISABLED=true.
  • Container tests for the SQL: text match, scope isolation, status and confidence filtering, relevance normalization, and 0003 applied over an existing hand-migrated database.
  • CI green

🤖 Generated with Claude Code

fabbrik and others added 2 commits September 18, 2026 21:17
Add ExperienceFinalizationService: one call that loads a completed captured
run, evaluates its own closed verification round, checks host authorization and
the host's storage decision, reflects, creates the record as Candidate, and
commits its initial lifecycle event to Validated or Quarantined. Record,
reflection and event IDs derive from the run, so a retry re-derives them and
converges instead of duplicating.

Required checks can now name the evaluator kind that may satisfy them, and Core
and Storage.Postgres each expose a service-registration extension. The MAF
adapter finalizes a fully captured run through the host's resolver.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add IExperienceCandidateSource with a PostgreSQL implementation: scope, status
and confidence filtering plus a full-text match over a generated tsvector
column added by migration 0003. Core's ExperienceRetrievalService applies
expiry and environment eligibility, then ranks candidates on relevance,
confidence, recency, status and environment compatibility with configurable
validated weights, exposing every normalized component and effective weight.

Retrieval is bounded by a timeout that returns an empty result with a timeout
signal rather than throwing, caller cancellation stays distinct, and a capped
candidate pool is reported through Truncated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@fabbrik
fabbrik merged commit 57f0495 into main Sep 22, 2026
1 check passed
@fabbrik
fabbrik deleted the feat/2-2-retrieve-by-text branch September 22, 2026 21:37
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