Skip to content

fix: improve user-card lookup integration - #363

Closed
JuanHuaXu wants to merge 3 commits into
xDarkicex:mainfrom
JuanHuaXu:fix/user-card-integration
Closed

fix: improve user-card lookup integration#363
JuanHuaXu wants to merge 3 commits into
xDarkicex:mainfrom
JuanHuaXu:fix/user-card-integration

Conversation

@JuanHuaXu

@JuanHuaXu JuanHuaXu commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fixes user-card lookup integration so visible aliases and raw sender IDs can resolve projected OpenClaw user cards.
  • Deduplicates daemon user-card index variants in list_user_cards and filters foreign card sources.
  • Improves identity-style memory_search by overfetching/reranking user-card and speaker hits above prompt echoes/tool artifacts.
  • Updates user-card/memory guidance so sparse cards do not block follow-up recall for history, details, preferences, or explicit memory requests.
  • Sends the daemon-recognized tenant metadata header, x-libravdb-tenant-key.

Linked context

Closes #362

Real behavior proof

  • Behavior addressed: get_user_card returned null for a visible user-card alias even though the card existed under its stable projected OpenClaw speaker key; identity memory_search could prefer prompt echoes/tool artifacts over the user-card hit.
  • Real environment tested: local OpenClaw Gateway with libravdb-memory loaded from the packaged plugin artifact and a LibraVDB daemon over a Unix socket.
  • Exact steps or command run after this patch:
./node_modules/.bin/tsc -p tsconfig.tests.json
node --test .ts-build/test/unit/memory-recall.test.js .ts-build/test/unit/memory-tools.test.js
git diff --check
npm run build
npm pack
npm install <packed-openclaw-memory-libravdb-tarball>   # installed into the active OpenClaw managed plugin project
launchctl kickstart -k <openclaw-gateway-service>
openclaw agent --session-key '<discord-session-key>' --message "@Assistant what is in ExampleUser-1001's user card?" --json
  • Evidence after fix:
Direct installed-tool probe:
get_user_card("ExampleUser-1001") -> card includes:
- stable local identity key: discord|channel=c|sender=1001
- speaker id: 1001
- visible names: ExampleUser-1001

get_user_card("1001") -> same card

Live session probe:
Tool summary: 1 call, get_user_card, failures 0
Final answer used the projected user card instead of saying no card existed.
  • Observed result after fix: alias and raw sender-ID lookups resolve the existing projected user card; live session no longer reports the card as missing.
  • What was not tested: full repository test suite, cross-platform daemon sockets, and every OpenClaw channel type.
  • Proof limitations or environment constraints: live proof is from a local OpenClaw/LibraVDB setup; identifiers and paths are sanitized here.
  • Before evidence: same live prompt produced get_user_card({ user_id: "ExampleUser-1001" }) -> { "card": null }, followed by an assistant response claiming the user card did not exist.

Tests and validation

./node_modules/.bin/tsc -p tsconfig.tests.json
node --test .ts-build/test/unit/memory-recall.test.js .ts-build/test/unit/memory-tools.test.js
git diff --check

Regression coverage added/updated:

  • tenant metadata header uses x-libravdb-tenant-key
  • get_user_card resolves raw sender IDs to scoped projected user cards
  • get_user_card resolves bullet-prefixed visible aliases from identity fields
  • alias fallback ignores names that appear only in notes
  • list_user_cards deduplicates :64d / :256d projection variants
  • sparse-card prompt guidance still allows memory follow-up
  • identity memory_search prefers user-card/speaker hits and excludes historical tool artifacts from the top result window

Risk checklist

  • Did user-visible behavior change? Yes.
  • Did config, environment, or migration behavior change? No config shape changes; tenant metadata header changes on RPC calls.
  • Did security, auth, secrets, network, or tool execution behavior change? Tenant routing metadata changes; no secret/tool-execution behavior changes.
  • Highest-risk area: alias fallback scans listByMeta(type=user_card) when exact getUserCard misses.
  • Mitigation: fallback only reads user-card metadata, filters to openclaw-user-cards, matches identity/profile fields only, and keeps note-only mentions from resolving aliases.

Current review state

Ready for review. Please focus on whether the alias fallback and identity reranking are the right plugin-side invariant, and whether the tenant header spelling matches the daemon contract.

Summary

  • Fixed user-card resolution so get_user_card can resolve direct IDs, raw sender IDs, and visible aliases from projected identity fields, while ignoring aliases found only in notes.
  • Deduplicated list_user_cards projection variants like :64d / :256d, filtered to the canonical openclaw-user-cards source, and canonicalized selection via per-card replacement rules.
  • Improved identity-style memory_search by detecting identity-intent queries, conditionally overfetching, then reranking/slicing results so canonical user-card and speaker hits are promoted above prompt-echoes and historical tool artifacts.
  • Updated memory tool guidance so sparse user cards do not block follow-up recall when history/details/preferences or explicit memory requests are made.
  • Switched tenant metadata to the daemon-recognized x-libravdb-tenant-key via a shared TENANT_KEY_HEADER + createTenantInterceptor.

Validation

  • Added/expanded unit tests covering:
    • tenant header propagation (and verifying the old header name is not used),
    • alias/raw-ID projected user-card lookup behavior and fallback rules,
    • list_user_cards deduplication across projection variants,
    • prompt guidance changes for sparse cards vs follow-up recall,
    • identity-query memory_search reranking/overfetch behavior (including preference over prompt echoes/tool artifacts).

Complexity (Big O + “cyclomatic-ish” growth)

  • memory_search (identity-intent path): worst-case work shifts from mostly linear filtering to overfetch + rerank. With R raw hits returned from manager.search, the reranking step that sorts ranked candidates is O(R log R) (plus tokenization/feature extraction over snippets). Non-identity queries stay closer to O(R) for filtering/slicing.
  • list_user_cards: deduplication uses map-based canonicalization; processing is O(N) over the returned daemon entries, with extra constant-factor parsing/selection per entry.
  • Alias lookup in get_user_card: when the direct card lookup is missing, the fallback scans projected stored entries from listByMeta (O(N)) and selects the best match via identity-token comparisons (additional work proportional to token/text length per candidate).
  • Cyclomatic complexity (decision-point drivers; no baseline available for a true delta): the added identity-intent branching, conditional overfetch logic, and ranking pipeline substantially increase control-flow complexity. Current rough “cyclomatic-ish” decision-token totals in key files are:
    • src/memory-tools.ts: 84 (if=26, &&=7, ||=17, ternary=22, catch=3)
    • src/tools/memory-recall.ts: 168 (if=61, &&=13, ||=19, ternary=44, catch=18)
    • src/libravdb-client.ts: 70
    • src/memory-provider.ts: 22

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: de20b96e-74e3-4141-ae30-bea85c456f07

📥 Commits

Reviewing files that changed from the base of the PR and between 2106c53 and 7e86c5e.

📒 Files selected for processing (4)
  • src/memory-tools.ts
  • src/tools/memory-recall.ts
  • test/unit/memory-recall.test.ts
  • test/unit/memory-tools.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • test/unit/memory-tools.test.ts
  • test/unit/memory-recall.test.ts
  • src/memory-tools.ts
  • src/tools/memory-recall.ts

📝 Walkthrough

Walkthrough

Updates tenant metadata handling, prompt guidance, identity search ranking, and user-card lookup/listing so visible aliases and projected IDs resolve consistently.

Changes

Identity Lookup, Search Re-ranking, and Tenant Header

Layer / File(s) Summary
TENANT_KEY_HEADER constant and createTenantInterceptor
src/libravdb-client.ts, test/unit/libravdb-client.test.ts
Exports TENANT_KEY_HEADER = "x-libravdb-tenant-key" and createTenantInterceptor; wires the client constructor to use the helper; test asserts correct header name is set and old spelling is absent.
Prompt guidance: sparse user cards and memory_search
src/memory-provider.ts, src/memory-tools.ts, test/unit/memory-provider.test.ts, test/unit/memory-tools.test.ts
Updates prompt and tool guidance to allow memory_search after a sparse card and even when context is visible; tests verify the updated wording.
memory_search identity overfetch and re-ranking
src/memory-tools.ts, test/unit/memory-tools.test.ts
Adds identity-aware sizing and ranking in memory_search.execute; identity queries overfetch, re-rank by user-card/speaker/tool-artifact signals, and return the top results; tests validate overfetch and ordering.
get_user_card alias fallback and helpers
src/tools/memory-recall.ts, test/unit/memory-recall.test.ts
Adds canonical user-card ID handling and alias fallback lookup in get_user_card, with tests for sender IDs, aliases, and fallback exclusions.
list_user_cards Map-based deduplication
src/tools/memory-recall.ts, test/unit/memory-recall.test.ts
Switches list_user_cards to canonical-ID Map deduplication with source filtering and replacement rules, and verifies projection-variant collapse in tests.

Sequence Diagram(s)

sequenceDiagram
  participant Model
  participant get_user_card
  participant LibravDBClient
  participant findUserCardByAlias

  Model->>get_user_card: user_id = "ExampleUser-1001"
  get_user_card->>LibravDBClient: getUserCard("ExampleUser-1001")
  LibravDBClient-->>get_user_card: { cardJson: null }
  get_user_card->>findUserCardByAlias: listByMeta(type=user_card)
  findUserCardByAlias-->>get_user_card: alias candidate with stable key
  get_user_card->>LibravDBClient: getUserCard(canonicalId)
  LibravDBClient-->>get_user_card: projected card
  get_user_card-->>Model: { card: { ... }, updated_at, version }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

release:minor

🐇 A header was wrong, the alias was lost,
The ranked results put echoes in front!
Now tenant keys travel the right header lane,
Aliases find cards, no null to complain,
And identity queries get the hits that count. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: improving user-card lookup behavior and related integration paths.
Linked Issues check ✅ Passed The PR implements the requested alias/raw-ID resolution, canonical de-duplication, identity ranking, sparse-card recall guidance, and tenant header fix for #362.
Out of Scope Changes check ✅ Passed The changes described all support the linked user-card lookup fix and related memory behavior, with no clear unrelated additions.

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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 `@src/memory-tools.ts`:
- Around line 215-219: The identity search path in memory-tools.ts is too
narrow: overfetching only happens when shouldOverfetchForIdentityQuery(query)
matches the raw string, and userCardMatch is requiring all query tokens instead
of just the entity tokens. Update the search flow around
resolveResultLimit/searchMaxResults to also trigger overfetch when the query
explicitly indicates identity intent via kind or signals, and revise
userCardMatch to compare against the extracted entity tokens rather than the
full query token set so identity-card rows can be surfaced and promoted
correctly.

In `@src/tools/memory-recall.ts`:
- Around line 714-720: Restrict alias fallback in userCardAliasMatchesLookup so
it only considers visible alias/name/speaker/user-id fields and does not
tokenize provider, account type, or channel metadata. Update the lookup path in
memory-recall.ts by introducing or using a dedicated extractor like
extractUserCardAliasLookupText for the card text before calling identityTokens,
and keep the matching logic in userCardAliasMatchesLookup limited to those
alias-bearing labels.
- Around line 696-699: Tighten the user-card source check in the metadata
parsing path so only OpenClaw-origin records are accepted. In the logic around
the card JSON parsing and the alias/listing flow (the code that currently
inspects card.source and the list_user_cards fallback), reject records unless
source is exactly the OpenClaw marker string, rather than allowing missing or
non-string source values through. Update both the metadata acceptance branch and
the list_user_cards filtering path to use the same strict source validation so
foreign type=user_card entries never reach alias fallback.

In `@test/unit/memory-recall.test.ts`:
- Around line 66-74: The shared fixture helper userCardResult currently omits
source by default, which makes the positive memory-recall tests use source-less
cards unintentionally. Update userCardResult to default source to
"openclaw-user-cards" and keep the card_json construction using that source
unless a test explicitly passes a sentinel for missing metadata. Then adjust the
positive cases in memory-recall.test.ts to rely on the default source-bearing
fixture and only use the explicit missing-source value where that behavior is
being tested.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9bfc9755-1393-4ff8-bfb3-f2d0972ba3eb

📥 Commits

Reviewing files that changed from the base of the PR and between 96467dd and 2106c53.

📒 Files selected for processing (8)
  • src/libravdb-client.ts
  • src/memory-provider.ts
  • src/memory-tools.ts
  • src/tools/memory-recall.ts
  • test/unit/libravdb-client.test.ts
  • test/unit/memory-provider.test.ts
  • test/unit/memory-recall.test.ts
  • test/unit/memory-tools.test.ts

Comment thread src/memory-tools.ts
Comment thread src/tools/memory-recall.ts
Comment thread src/tools/memory-recall.ts
Comment thread test/unit/memory-recall.test.ts Outdated

@compoodment compoodment left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Vale Review — PR #363

Quality: Q4/5 — sharp
Head: 4720e3c

Findings:

  • src/memory-tools.ts:413 — identityEntityTokens still keeps common prompt-shape words like kind, and userCardMatch then requires every remaining query token to appear in the card identity text at lines 421-422. A documented identity question like what kind of <alias> will not promote the user-card hit, so prompt echoes can still outrank the actual card for one of the advertised lookup forms. major

Proof gaps: I did not rerun the full suite or a live daemon probe for this head; this is current-head source review against the reranking logic and added tests.

Verdict: request-changes — identity reranking is still too brittle for natural identity questions in the tool guidance.

– Vale

@JuanHuaXu

Copy link
Copy Markdown
Contributor Author

Thanks Vale, confirmed. The advertised what kind of <alias> form still left kind in the entity-token set, so the user-card row would not be promoted unless the card text also happened to contain that prompt-shape word.

Fixed in 7e86c5e by excluding kind/kinds (and you, another prompt-shape token) from identity entity matching, and updated the regression to use what kind of SampleName-1001 with kind: "identity".

Validation:

  • ./node_modules/.bin/tsc -p tsconfig.tests.json
  • node --test .ts-build/test/unit/memory-tools.test.js
  • git diff --check

@compoodment compoodment left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Vale Review — PR #363

Quality: Q4/5 — sharp
Head: 7e86c5e

Findings:

  • None

Proof gaps: Full suite and live daemon probe were not rerun here; review covered current metadata, diff, comments/reviews, CodeRabbit check, and current-head source around identity ranking and user-card alias fallback.

Verdict: approve — the new head fixes Vale's prior what kind of <alias> reranking failure and adds a focused regression for that prompt shape.

– Vale

@xDarkicex

Copy link
Copy Markdown
Owner

Thanks for the PR Juan. The alias resolution idea — making get_user_card work when the model uses a visible name instead of the stable key — is a good one. But this PR has several issues that make it unsafe to merge as-is:

1. Tenant header rename is incorrect. The header was changed from libravdb-tenant-key to x-libravdb-tenant-key. The daemon has always expected libravdb-tenant-key — there is no evidence the daemon accepts x-libravdb-tenant-key. If merged, multi-tenant routing silently breaks: the header is never received, all RPCs route to the default tenant, and tenant-isolated data leaks across boundaries. This alone is a hard blocker.

2. Unbounded scan on every card miss. findUserCardByAlias calls listByMeta(type=user_card) on every get_user_card miss with no caching and no result limit. In deployments with many user cards, every unrecognized lookup triggers a full table scan.

3. Source filtering silently drops cards. list_user_cards now requires source: "openclaw-user-cards" in metadata. Cards created by older versions or other sources without this field silently disappear from the list. No migration path.

4. Overfetch regex is too broad. shouldOverfetchForIdentityQuery matches any question starting with who/what/where/when/why/how — including "what is the status" or "how are you doing." This triggers 3x overfetch + reranking on non-identity queries, adding daemon load for no benefit.

5. Prompt guidance became subjective. Changing from "Only use memory_search if the card is empty or missing" to "call memory_search when the card lacks enough profile notes" makes model behavior non-deterministic — different models will judge "enough" differently.

6. Too many unrelated changes in one PR. Five distinct behavioral changes (header rename, alias scanning, identity reranking, list dedup, prompt wording) should be separate PRs so each can be reviewed and tested independently.

The alias resolution concept is worth revisiting as a focused PR — just the findUserCardByAlias fallback, with a bounded scan, without the header change or the other bundled changes. Happy to review that.

@xDarkicex xDarkicex closed this Aug 1, 2026
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.

Bug: user-card aliases and stable projected IDs are not resolved consistently

3 participants