Skip to content

Adaptive profile extraction + confidence_score 400 fix#122

Merged
besfeng23 merged 1 commit into
mainfrom
fix/pandora-adaptive-profile-extraction
Jul 3, 2026
Merged

Adaptive profile extraction + confidence_score 400 fix#122
besfeng23 merged 1 commit into
mainfrom
fix/pandora-adaptive-profile-extraction

Conversation

@besfeng23

@besfeng23 besfeng23 commented Jul 2, 2026

Copy link
Copy Markdown
Owner

What

Makes the adaptive learning loop actually populate and surface real profile data, and fixes the get_adaptive_context 400. Follow-up #1 from the AU Memory Engine work.

Changes

  • Real profile extraction — new lib/services/adaptive-profile-extractor.ts: deterministic (no model calls / embeddings), classifies memory events into preferences / facts / decisions / open loops / risks and assigns a bounded confidence, reusing the Phase 5D classifyContent.
  • refresh_adaptive_profiles populates rowsrefreshAdaptiveProfileFromEvents reads namespace-scoped events, extracts, and upserts a populated versioned profile (previously an empty "MCP adaptive profile refresh requested." stub). MCP tool wired to it.
  • confidence_score 400 fix — hybrid retrieval selected a non-existent confidence column (the real Phase 5D column is confidence_score) → PostgREST 400 that silently emptied get_adaptive_context's event/profile data. Now selects confidence_score and maps it back so ranking still uses the stored score.
  • get_adaptive_context surfaces the profile — extracted preferences → writing_rules (au) / business_rules (real_life); decisions → decision_rules; facts → do_not_forget; plus new adaptive_profile_summary / adaptive_profile_confidence. Additive: every existing field preserved.

Schema changes

One additive migration supabase/migrations/20260702000000_adaptive_profile_versioning_columns.sql:

alter table public.memory_profiles add column if not exists supersedes_profile_id uuid;
alter table public.memory_profiles add column if not exists superseded_at timestamptz;

upsertVersionedMemoryProfile writes these supersession-chain columns, but they were never created (absent from every migration and from the deployed table) — so a non-dry profile write 400'd on re-runs. Fully additive; nothing dropped. Not auto-applied — apply via the reviewed migration step before enabling non-dry refresh in prod.

Tests — tests/unit/adaptive-profile-loop.test.ts (5/5)

Proves the full loop with an in-memory client: extract canon/preference/business-fact/decision → assign confidence → save populated profile (non-dry) → dry-run previews without writing → retrieve via get_adaptive_context → confidence_score select fix.

Verification

  • typecheck ✅ · lint ✅ (no new warnings) · build ✅
  • tests: 554 passed (5 new). The 1 failure (first-reviewed-memory-fixturespawnSync npm ENOENT) is a pre-existing sandbox-only flake in an untouched file.

Hard-rules compliance

  • No real_life master packs touched. No AU↔real_life mixing. Existing retrieval (get_memory_context / get_latest_context_pack) behavior intact (additive only). No gated features enabled — still model-free and embedding-free.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Adaptive profile data is now automatically distilled from recent memory events into structured profile updates.
    • Chat context now includes more relevant remembered preferences, facts, and decisions.
    • Profile updates now support version history for safer refreshes.
  • Bug Fixes

    • Improved memory retrieval to use confidence scores consistently.
    • Better handling of archived or duplicate memory events during profile refresh.
  • Tests

    • Added coverage for the adaptive profile refresh flow and chat context output.

Makes the AU/real_life adaptive learning loop actually work instead of
writing empty stub profiles.

- Add deterministic adaptive-profile-extractor (no model calls / embeddings):
  classifies memory events into preferences, facts, decisions, open loops,
  risks and assigns a bounded confidence.
- Wire refresh_adaptive_profiles to extract real content and upsert a
  populated versioned profile (was an empty "refresh requested" stub).
- Fix hybrid retrieval 400: select confidence_score (the real Phase 5D
  column) instead of the non-existent `confidence`; map it back so ranking
  still uses the stored score.
- Surface the extracted operating profile in get_adaptive_context
  (writing_rules/business_rules/decisions/facts + summary/confidence),
  additive so existing fields are unchanged.
- Additive migration: add supersedes_profile_id/superseded_at to
  memory_profiles so versioned profile writes succeed on re-runs.
- Tests prove the full loop: extract -> confidence -> save -> refresh ->
  retrieve via get_adaptive_context, plus the confidence_score fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a deterministic adaptive-profile extractor, a refresh function that persists extracted profiles via versioned upsert, wires the MCP refresh tool to it, updates ChatGPT context building to surface extracted preferences/facts/decisions, switches hybrid retrieval to use confidence_score, adds a DB migration, and adds unit tests.

Changes

Adaptive Profile Extraction and Refresh Loop

Layer / File(s) Summary
Adaptive profile extractor
lib/services/adaptive-profile-extractor.ts
New module with types, regex classifiers, and extractAdaptiveProfile that deduplicates/classifies events into preferences, facts, decisions, open loops, risks, and computes a deterministic confidence score.
Refresh service and versioning storage
lib/services/memory-profile-service.ts, supabase/migrations/20260702000000_adaptive_profile_versioning_columns.sql
Adds refreshAdaptiveProfileFromEvents which reads recent non-archived events, extracts a profile, and upserts it with metrics; adds additive supersedes_profile_id/superseded_at columns and index.
MCP tool wiring
lib/services/pandora-mcp-tools.ts
Switches refreshAdaptiveProfilesTool from upsertProfileFromMemoryEvents to refreshAdaptiveProfileFromEvents, dropping hard-coded summary/evidence_refs arguments.
ChatGPT context and hybrid retrieval confidence
lib/services/adaptive-chatgpt-context-service.ts, lib/services/memory-hybrid-retrieval-service.ts
Adds profileLines helper and flattens adaptive profile data into business_rules, writing_rules, decision_rules, do_not_forget; updates memory_events selection and recent_events mapping to use confidence_score.
Unit tests
tests/unit/adaptive-profile-loop.test.ts
Adds a fake DB client mock and tests covering extraction, refresh (dry-run and real), context building, and confidence_score selection.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • besfeng23/Memory#103: Extends the same adaptive-profile extraction/context building and MCP tool wiring introduced there.
  • besfeng23/Memory#107: Shares the versioned memory profile upsert pipeline used by refreshAdaptiveProfileFromEvents.
  • besfeng23/Memory#111: Also modifies getHybridMemoryContext to use confidence_score for recent events.

Suggested labels: codex

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it omits the repository's required checklist section about env vars and secrets. Add the checklist section and explicitly answer whether env vars or secrets changed, were added to Env Broker, and are server-only/public-safe.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and matches the main change: adaptive profile extraction and the confidence_score fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 fix/pandora-adaptive-profile-extraction

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed due to a network error.


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.

@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
memory Ready Ready Preview, Comment Jul 2, 2026 7:13pm

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 72908e588f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (seen.has(key)) continue;
seen.add(key);

const item: AdaptiveProfileItem = { text: clip(raw), event_id: event.id ?? null, importance: event.importance ?? null };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Drop secret-classified events before profiling

When a captured event contains a token/password (possible through direct MCP/bridge capture before candidate review), classifyContent returns secret, but the extractor still builds item from the raw event text and the switch below can persist it into patterns or risks via refreshAdaptiveProfileFromEvents. Since active memory_profiles are returned wholesale by hybrid context, a profile refresh can duplicate and expose credentials instead of honoring the existing secret-blocking contract; skip or redact category === "secret" before saving profile items.

Useful? React with 👍 / 👎.

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

🧹 Nitpick comments (1)
tests/unit/adaptive-profile-loop.test.ts (1)

137-149: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Strengthen the confidence vs confidence_score regression check.

expect(eventsSelect).not.toContain(",confidence,") (Line 148) won't catch a regression where the bare confidence column appears as the first or last entry in the select list (e.g. "confidence,id,..." or "...,id,confidence", missing one of the surrounding commas). Since this test's stated purpose is guarding against the exact 400-causing bug this PR fixes, a word-boundary regex is safer.

🐛 Proposed fix
-    expect(eventsSelect).not.toContain(",confidence,");
+    expect(eventsSelect).not.toMatch(/\bconfidence\b(?!_score)/);
🤖 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/unit/adaptive-profile-loop.test.ts` around lines 137 - 149, Strengthen
the regression assertion in the getHybridMemoryContext test so it catches any
bare confidence column, not just the middle-of-list case. Replace the current
eventsSelect containment check with a word-boundary style validation that fails
whether confidence appears at the start, middle, or end of the select list,
while still confirming confidence_score is requested. Keep the focus of the test
on the fakeClient memory_events select capture and the hybrid retrieval
behavior.
🤖 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 `@lib/services/adaptive-profile-extractor.ts`:
- Around line 74-100: The switch in adaptive profile extraction allows
`"secret"` items from `classifyContent` to fall through into `patterns`, which
can persist sensitive text. Update the logic in the extraction flow around
`classifyContent`, `RISK_RE.test`, and the category switch to explicitly skip
any `"secret"` category before any array pushes. Ensure secret/credential items
are neither added to `patterns` nor to other profile buckets, even when they do
not match `RISK_RE` or `DECISION_RE`.

---

Nitpick comments:
In `@tests/unit/adaptive-profile-loop.test.ts`:
- Around line 137-149: Strengthen the regression assertion in the
getHybridMemoryContext test so it catches any bare confidence column, not just
the middle-of-list case. Replace the current eventsSelect containment check with
a word-boundary style validation that fails whether confidence appears at the
start, middle, or end of the select list, while still confirming
confidence_score is requested. Keep the focus of the test on the fakeClient
memory_events select capture and the hybrid retrieval behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 701d035e-1591-497f-89ba-e24944ab052e

📥 Commits

Reviewing files that changed from the base of the PR and between 9115389 and 72908e5.

📒 Files selected for processing (7)
  • lib/services/adaptive-chatgpt-context-service.ts
  • lib/services/adaptive-profile-extractor.ts
  • lib/services/memory-hybrid-retrieval-service.ts
  • lib/services/memory-profile-service.ts
  • lib/services/pandora-mcp-tools.ts
  • supabase/migrations/20260702000000_adaptive_profile_versioning_columns.sql
  • tests/unit/adaptive-profile-loop.test.ts

Comment on lines +74 to +100
const category = classifyContent({
text: raw,
memory_type: event.memory_type ?? null,
importance: event.importance ?? null,
source: event.source ?? null,
});
const sensitive = event.sensitivity === "high" || event.sensitivity === "private";
const isRisk = RISK_RE.test(raw) || sensitive;
const isDecision = DECISION_RE.test(raw);

if (isRisk) risks.push(item);
if (isDecision) decisions.push(item);

switch (category) {
case "durable_preference":
preferences.push(item);
break;
case "production_fact":
facts.push(item);
break;
case "task_state":
openLoops.push(item);
break;
default:
if (!isRisk && !isDecision) patterns.push(item);
break;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Secret-classified content can leak into the stored patterns array.

classifyContent returns "secret" for text/memory_type matching credential patterns, and per its own contract Secrets/credentials always win and are blocked. Here, however, category "secret" falls through the default branch of the switch and is pushed into patterns unless the text also happens to match RISK_RE or DECISION_RE (Line 98). Since sensitivity and risk keywords are independent of secret detection, a credential/secret event can be silently stored (raw, unredacted item.text) in the profile's patterns field, which is then persisted via upsertVersionedMemoryProfile and returned as part of ctx.adaptive_profile from getHybridMemoryContext.

Add an explicit skip for the secret category before classification/push.

🔒 Proposed fix
     const category = classifyContent({
       text: raw,
       memory_type: event.memory_type ?? null,
       importance: event.importance ?? null,
       source: event.source ?? null,
     });
+    if (category === "secret") continue;
     const sensitive = event.sensitivity === "high" || event.sensitivity === "private";
📝 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
const category = classifyContent({
text: raw,
memory_type: event.memory_type ?? null,
importance: event.importance ?? null,
source: event.source ?? null,
});
const sensitive = event.sensitivity === "high" || event.sensitivity === "private";
const isRisk = RISK_RE.test(raw) || sensitive;
const isDecision = DECISION_RE.test(raw);
if (isRisk) risks.push(item);
if (isDecision) decisions.push(item);
switch (category) {
case "durable_preference":
preferences.push(item);
break;
case "production_fact":
facts.push(item);
break;
case "task_state":
openLoops.push(item);
break;
default:
if (!isRisk && !isDecision) patterns.push(item);
break;
}
const category = classifyContent({
text: raw,
memory_type: event.memory_type ?? null,
importance: event.importance ?? null,
source: event.source ?? null,
});
if (category === "secret") continue;
const sensitive = event.sensitivity === "high" || event.sensitivity === "private";
const isRisk = RISK_RE.test(raw) || sensitive;
const isDecision = DECISION_RE.test(raw);
if (isRisk) risks.push(item);
if (isDecision) decisions.push(item);
switch (category) {
case "durable_preference":
preferences.push(item);
break;
case "production_fact":
facts.push(item);
break;
case "task_state":
openLoops.push(item);
break;
default:
if (!isRisk && !isDecision) patterns.push(item);
break;
}
🤖 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 `@lib/services/adaptive-profile-extractor.ts` around lines 74 - 100, The switch
in adaptive profile extraction allows `"secret"` items from `classifyContent` to
fall through into `patterns`, which can persist sensitive text. Update the logic
in the extraction flow around `classifyContent`, `RISK_RE.test`, and the
category switch to explicitly skip any `"secret"` category before any array
pushes. Ensure secret/credential items are neither added to `patterns` nor to
other profile buckets, even when they do not match `RISK_RE` or `DECISION_RE`.

@besfeng23 besfeng23 merged commit b61c963 into main Jul 3, 2026
5 checks passed
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.

2 participants