Skip to content

feat(people): add people.drifting — surface contacts gone quiet - #4541

Closed
mysma-9403 wants to merge 1 commit into
tinyhumansai:mainfrom
mysma-9403:feat/people-drifting-contacts
Closed

feat(people): add people.drifting — surface contacts gone quiet#4541
mysma-9403 wants to merge 1 commit into
tinyhumansai:mainfrom
mysma-9403:feat/people-drifting-contacts

Conversation

@mysma-9403

@mysma-9403 mysma-9403 commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Re-applied onto current main after the #5328 domain restructure — this branch
was 750 commits behind and predated it, so it was reset onto main and the
change re-applied at the new path src/openhuman/memory/people/* (was
src/openhuman/people/*).

What

Adds a read-only people.drifting controller — the "relationships going cold"
surface the scorer's ScoreComponents doc anticipates. Returns contacts whose
most recent interaction is older than a days threshold, oldest last-touch
first, excluding contacts never interacted with (nothing to drift from).

Method Inputs Output
people.drifting days? (default 30), limit? (default 100, clamped 1–500) contacts[] (oldest last-touch first) — each person_id, display_name?, primary_email?, primary_phone?, last_interaction_at, days_since_last; plus threshold_days.

Change

  • store (store.rs): list_drifting(cutoff_ts, limit) — one grouped SELECT,
    people JOIN interactions … GROUP BY p.id HAVING MAX(i.ts) < ?cutoff ORDER BY last_ts ASC LIMIT ?.
    limit is clamped 1..=500 inside the pub fn: a direct call with
    limit == 0 would emit LIMIT 0 (empty), and a usize above i64::MAX wraps
    under as i64 to a negative, which SQLite reads as no limit. Takes an
    explicit cutoff_ts (not days) so it stays deterministic/testable against a
    fixed clock. primary_phone is selected for parity with people.list.
  • rpc (rpc.rs): handle_drifting(store, days, limit) — derives the cutoff
    with a saturating u64 → i64 guard (days as i64 turns u64::MAX into -1,
    which would push the cutoff into the future and return everyone; the saturating
    form floors it to the distant past → no matches). Emits contacts[] +
    threshold_days and grep-friendly [people::rpc] drifting: diagnostics.
  • schema (schemas.rs): the people.drifting ControllerSchema + adapter,
    wired through the existing all_controller_schemas / all_registered_controllers
    vecs (no core/all.rs change — registration is data-driven).

Tests

  • list_drifting_orders_stale_and_excludes_recent_and_never (store) — ordering
    (oldest first), exclusion of fresh + never-interacted contacts, primary_phone
    passthrough, and the limit == 0 → clamped to 1 guard.
  • drifting_lists_stale_contacts_with_days_since / drifting_huge_days_threshold_does_not_overflow
    (rpc) — end-to-end payload + the u64::MAX overflow guard.
  • drifting_schema_inputs_are_optional + controller-count tests (schema).

Verified locally: cargo test --lib --features memory-git memory::people — green.
The JSON-RPC E2E smoke test is deferred (the bare build_core_http_router
harness has no built people CoreContext), same as the original PR; per-layer
unit tests cover store/rpc/schema.


Note — pushed over pre-existing main breakage

main currently fails any --no-default-features compile (memory-git is
default-OFF) due to a memory/diff cfg mismatch unrelated to this PR:

error[E0432]: unresolved import `tools`         → src/openhuman/memory/diff/mod.rs:79
error[E0432]: unresolved import `super::types`  → src/openhuman/memory/diff/stub.rs:29

So the pre-push pnpm rust:check (shell build, gates off) fails on main's bug,
and this push used --no-verify. The same Rust Feature-Gate Smoke / Rust Core Coverage lanes are red on other current-main PRs (e.g. #5486) and green on a
pre-regression base (#5344). Rust Quality (fmt, clippy) compiles this change
clean; the feature-flagged local test above covers it.

Summary by CodeRabbit

  • New Features
    • Added a people.drifting endpoint to identify contacts whose latest interaction exceeds a specified age.
    • Results include contact details and elapsed days, ordered from oldest interaction to newest.
    • Supports configurable day thresholds and result limits, with safe limit handling.
    • Contacts without interaction history are excluded.

@mysma-9403
mysma-9403 requested a review from a team July 5, 2026 05:30
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds the people.drifting controller, RPC handler, and store query. The endpoint filters contacts by latest interaction age, orders results oldest first, excludes contacts without interactions, clamps limits, and handles oversized thresholds safely.

Changes

Drifting contacts feature

Layer / File(s) Summary
Store query for drifting contacts
src/openhuman/memory/people/store.rs
Adds PeopleStore::list_drifting, which filters, orders, and limits contacts. Tests cover cutoff behavior, exclusions, ordering, phone values, and limit clamping.
RPC handler for drifting contacts
src/openhuman/memory/people/rpc.rs, src/openhuman/memory/people/README.md
Adds handle_drifting, overflow-safe cutoff handling, result formatting, logging, integration tests, and RPC documentation.
Controller schema and wiring
src/openhuman/memory/people/schemas.rs, src/openhuman/memory/people/tests.rs
Registers people.drifting, defines optional days and limit inputs, exposes structured contact fields, delegates to the RPC handler, and updates controller tests.

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

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant driftingController
  participant handle_drifting
  participant PeopleStore
  participant Database
  Client->>driftingController: Call people.drifting(days, limit)
  driftingController->>handle_drifting: Pass parsed parameters
  handle_drifting->>PeopleStore: Request contacts before cutoff
  PeopleStore->>Database: Query latest interactions
  Database-->>PeopleStore: Return ordered contact rows
  PeopleStore-->>handle_drifting: Return drifting contacts
  handle_drifting-->>Client: Return contact details and elapsed days
Loading

Suggested reviewers: senamakel

Poem

I’m a rabbit with a tidy trail,
Old contacts rise when days grow stale.
The store sorts slow, the limits bind,
Safe thresholds leave wraps behind.
drifting hops through schemas bright—
Five controllers now work just right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the new people.drifting RPC and its purpose of identifying contacts with no recent interactions.

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 added feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. labels Jul 5, 2026

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

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/openhuman/people/rpc.rs Outdated
) -> Result<RpcOutcome<Value>, String> {
let limit = limit.clamp(1, 500);
let now = Utc::now();
let cutoff_ts = now.timestamp() - (days as i64).saturating_mul(86_400);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clamp days before computing the cutoff

When a caller sends a very large days value (the schema accepts any JSON U64), days as i64 wraps for values ≥ 2^63 before saturating_mul runs. For example, u64::MAX becomes -1, making the cutoff land in the future and returning nearly every contacted person instead of none; 2^63 can also overflow this subtraction in debug builds. Clamp/convert with i64::try_from and use saturating subtraction before querying.

Useful? React with 👍 / 👎.

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.

Confirmed resolved in 5ef8ed867: the cutoff now uses i64::try_from(days).unwrap_or(i64::MAX) with saturating_sub/saturating_mul, so an out-of-range days floors the cutoff to the distant past (→ no matches) instead of wrapping into the future. Regression test drifting_huge_days_threshold_does_not_overflow covers u64::MAX. ✅

Comment thread src/openhuman/people/rpc.rs Outdated
Comment on lines +77 to +80
let rows = store
.list_drifting(cutoff_ts, limit)
.await
.map_err(|e| format!("list_drifting: {e}"))?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Add diagnostics for the drifting RPC path

The root /workspace/openhuman/AGENTS.md says new/changed flows must include verbose, grep-friendly diagnostics for entry/exit, external calls, branches, and errors, but this new people.drifting path performs the store query and returns results without any tracing/log output. Add non-PII [people::rpc]/store debug logs around the threshold, limit, query result count, and error path so failures or empty drifting surfaces are diagnosable.

Useful? React with 👍 / 👎.

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.

Confirmed resolved in 5ef8ed867: handle_drifting now emits [people::rpc] tracing on entry (days/limit/cutoff_ts), on result count, and on the list_drifting error path — grep-friendly and PII-free. ✅

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

🤖 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/openhuman/people/rpc.rs`:
- Around line 74-76: The `days` to `i64` conversion in the cutoff calculation
can overflow before `saturating_mul`, unlike the guarded `limit` value above.
Update the `cutoff_ts` logic in the `rpc` codepath to convert `days` with a
checked/saturating conversion before multiplying, so huge caller-supplied values
cannot wrap into a bogus timestamp.
🪄 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: 8593dd77-36ea-44ac-abaa-6ead5ebf7728

📥 Commits

Reviewing files that changed from the base of the PR and between 9191cd4 and c8b511c.

📒 Files selected for processing (5)
  • src/openhuman/people/README.md
  • src/openhuman/people/rpc.rs
  • src/openhuman/people/schemas.rs
  • src/openhuman/people/store.rs
  • src/openhuman/people/tests.rs

Comment thread src/openhuman/people/rpc.rs Outdated
mysma-9403 added a commit to mysma-9403/openhuman that referenced this pull request Jul 5, 2026
…rifting

Addresses review on tinyhumansai#4541 (CodeRabbit + Codex):

- `days as i64` wrapped for values above i64::MAX (u64::MAX -> -1), pushing the
  cutoff into the future and returning nearly every contact. Convert with
  i64::try_from(days).unwrap_or(i64::MAX) and subtract saturating so an absurd
  threshold floors the cutoff (→ no matches) instead of overflowing.
- Add [people::rpc] tracing on the drifting path: entry (days/limit/cutoff),
  the query error path, and the result count — per the verbose-diagnostics rule.

Test: drifting_huge_days_threshold_does_not_overflow asserts u64::MAX days
returns empty (floored cutoff) rather than everyone.
@mysma-9403

Copy link
Copy Markdown
Contributor Author

Addressed both review findings in 5ef8ed8:

  • Overflow (days as i64) — now i64::try_from(days).unwrap_or(i64::MAX) + saturating subtraction, so an out-of-range days floors the cutoff (→ no matches) instead of wrapping it into the future and returning everyone. Added drifting_huge_days_threshold_does_not_overflow covering u64::MAX.
  • Diagnostics — added [people::rpc] tracing on entry (days/limit/cutoff), the query error path, and the result count, per the verbose-diagnostics rule.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 5, 2026

@M3gA-Mind M3gA-Mind 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.

PR #4541 — feat(people): add people.drifting — surface contacts gone quiet

Walkthrough

This adds a single, self-contained read path to the people domain: a people.drifting controller that returns contacts whose most recent interaction is older than a caller-supplied days threshold, oldest last-touch first. The slice is cleanly layered — store::list_drifting (one grouped SQL query), rpc::handle_drifting (cutoff derivation + shaping), and schemas.rs (controller schema + adapter + registration) — and it follows the established people-domain conventions (rpc.rs/store.rs/schemas.rs split, registry wiring, no root-level .rs). Tests cover the store ordering/exclusion behavior, the end-to-end handler, the u64::MAX overflow guard, and the schema shape. The two earlier bot findings (the days as i64 overflow and missing diagnostics) are already fixed in 5ef8ed867. Overall this is a tight, well-tested, low-risk PR that does exactly what the title claims; the remaining notes are minor/polish.

Changes

File Summary
src/openhuman/people/store.rs New list_drifting(cutoff_ts, limit)JOIN interactions … GROUP BY p.id HAVING MAX(i.ts) < cutoff ORDER BY last_ts ASC LIMIT; excludes never-interacted contacts. Store test added.
src/openhuman/people/rpc.rs New handle_drifting(store, days, limit) — clamps limit, overflow-safe cutoff, [people::rpc] tracing, derives days_since_last, returns { contacts, threshold_days }. Handler + overflow tests.
src/openhuman/people/schemas.rs drifting schema (inputs days/limit optional, typed outputs), registration, adapter. Controller-count tests bumped to 5 + shape test.
src/openhuman/people/tests.rs Domain schema test renamed/extended to assert drifting is exposed (count 5).
src/openhuman/people/README.md Module doc lists handle_drifting.

Actionable comments (2)

💡 Refactor / suggestion

1. src/openhuman/people/rpc.rs:113-119 — Drifting output omits primary_phone

The row shape returns person_id, display_name, primary_email, last_interaction_at, days_since_last, but drops primary_phone — even though people.list returns it and the domain supports imessage handles. A "relationships going cold" surface exists to prompt the user to reach back out, and for many drifting contacts the reachable channel is a phone/iMessage, not email. Consider adding primary_phone for parity so a UI/nudge consumer doesn't need a second people.score/list round-trip to find how to reconnect. (Requires selecting p.primary_phone in list_drifting and adding the field to the drifting output schema.)

Suggested change:

// store.rs — list_drifting SELECT
// before
"SELECT p.id, p.display_name, p.primary_email, MAX(i.ts) AS last_ts \
 FROM people p JOIN interactions i ON i.person_id = p.id \
 GROUP BY p.id HAVING MAX(i.ts) < ?1 ORDER BY last_ts ASC LIMIT ?2"
// after
"SELECT p.id, p.display_name, p.primary_email, p.primary_phone, MAX(i.ts) AS last_ts \
 FROM people p JOIN interactions i ON i.person_id = p.id \
 GROUP BY p.id HAVING MAX(i.ts) < ?1 ORDER BY last_ts ASC LIMIT ?2"

// rpc.rs — contacts json!
"primary_email": primary_email,
"primary_phone": primary_phone,      // add
"last_interaction_at": last_ts,

(This is a design choice, not a defect — fine to defer to the follow-up UI PR if you'd rather keep this slice minimal, but worth a deliberate call now since the schema is the contract.)

2. src/openhuman/people/store.rs:526list_drifting trusts an unclamped limit

handle_drifting clamps limit to 1..=500 before calling, so today this is safe. But list_drifting is pub and the clamp lives only in the caller. A direct call with limit == 0 yields LIMIT 0 (empty), and a usize above i64::MAX wraps under as i64 to a negative, which SQLite treats as "no limit" (returns everything). Since the safety invariant isn't co-located with the query, a defensive clamp (or an explicit debug_assert / doc contract) inside the store method would keep it robust against future callers.

Suggested change:

// before
let rows = stmt.query_map(params![cutoff_ts, limit as i64], |r| {

// after
let limit = limit.clamp(1, 500);
let rows = stmt.query_map(params![cutoff_ts, limit as i64], |r| {

Nitpicks (2)

  • src/openhuman/people/store.rs:512list_drifting returns a 4-tuple accessed positionally in tests (drifting[0].3). A small named struct (DriftingRow { id, display_name, primary_email, last_ts }) would read better and survive field additions; low priority given the domain already leans on tuples internally.
  • src/openhuman/people/rpc.rs:123threshold_days echoes the raw days verbatim, so a pathological days: u64::MAX request returns threshold_days: 18446744073709551615 alongside an empty contacts. Harmless, but echoing the effectively-applied threshold (post-clamp) would be marginally more honest.

Questions for the author (1)

  • src/openhuman/people/rpc.rs:74 — With days: 0, the cutoff becomes now, so HAVING MAX(i.ts) < now returns essentially every contacted person (their last interaction is always in the past). Is days: 0 a meaningful input you want to accept, or should it be treated as "use the default" / floored to 1? Not a bug — just confirming the intended semantics at the boundary.

Verified / looks good

  • Overflow guard (i64::try_from(days).unwrap_or(i64::MAX) + saturating_sub/saturating_mul) correctly floors an out-of-range days to the distant past instead of wrapping into the future — the earlier Codex/CodeRabbit finding is fully addressed, with a dedicated u64::MAX regression test.
  • Diagnostics: entry (threshold/limit/cutoff), result count, and error paths all logged with the grep-friendly [people::rpc] prefix and no PII — the earlier diagnostics finding is addressed.
  • SQL is fully parameterized (params![cutoff_ts, limit as i64]); no injection surface. JOIN (not LEFT JOIN) correctly excludes never-interacted contacts, and the interactions(person_id, ts DESC) index backs the MAX(i.ts) aggregation.
  • Convention adherence: logic in rpc.rs/store.rs, schema/adapter/registration in schemas.rs, auto-wired via all_people_registered_controllers() in src/core/all.rs (no ad-hoc cli.rs/jsonrpc.rs branch), no new root-level .rs. README + module docs updated.
  • RpcOutcome<Value> error propagation matches the sibling handlers; store errors are mapped and surfaced rather than swallowed.
  • Test coverage is thorough for a change this size: store ordering/exclusion (stale/staler/recent/never), end-to-end handler with days_since_last ≈ 90, overflow, and schema shape/count.

Review is comment-only by request — approval/merge left to a human maintainer.

Comment thread src/openhuman/people/rpc.rs Outdated
"person_id": id.to_string(),
"display_name": display_name,
"primary_email": primary_email,
"last_interaction_at": last_ts,

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.

Drifting output omits primary_phone — parity with people.list.

This row shape drops primary_phone even though people.list returns it and the domain supports imessage handles. A "going cold" surface exists to prompt the user to reconnect, and for many drifting contacts the reachable channel is a phone/iMessage, not email — so a consumer would otherwise need a second people.list/score round-trip to find how to reach out.

"primary_email": primary_email,
"primary_phone": primary_phone,      // add (also select p.primary_phone in list_drifting + add to output schema)
"last_interaction_at": last_ts,

Design choice, not a defect — fine to defer to the follow-up UI PR, but worth a deliberate call since the schema is the contract.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call — took it now rather than deferring, since the schema is the contract and it's cheaper to widen before consumers depend on the shape. In 706602f list_drifting selects p.primary_phone and returns it in the row; handle_drifting emits primary_phone in each contact; and it's added to the output FieldSchema plus the people README methods table (which, I noticed, was missing a people.drifting row entirely — added that too). The row tuple is now 5 fields, so I introduced a DriftingRow type alias to keep the Vec<…> signature readable and dodge clippy::type_complexity. Passthrough is asserted in the store test (stale carries a phone; the drifting row surfaces it).

Comment thread src/openhuman/people/store.rs Outdated
HAVING MAX(i.ts) < ?1 \
ORDER BY last_ts ASC \
LIMIT ?2",
)?;

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.

list_drifting trusts an unclamped limit.

handle_drifting clamps limit to 1..=500 before calling, so this is safe today. But list_drifting is pub and the clamp lives only in the caller: a direct call with limit == 0 yields LIMIT 0 (empty), and a usize above i64::MAX wraps under as i64 to a negative, which SQLite treats as no limit (returns everything). Co-locating a defensive clamp with the query keeps it robust for future callers:

let limit = limit.clamp(1, 500);
let rows = stmt.query_map(params![cutoff_ts, limit as i64], |r| {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 706602f. Co-located the clamp inside list_driftinglet limit = limit.clamp(1, 500); at the top of the fn, so it no longer depends on handle_drifting being the only caller. A direct limit == 0 now returns the single oldest drifter instead of LIMIT 0, and a usize above i64::MAX can no longer wrap to a negative as i64 (SQLite's "no limit"). The doc comment spells out why the clamp lives here. Regression covered by a new assertion in list_drifting_orders_stale_and_excludes_recent_and_never: list_drifting(cutoff, 0) → exactly 1 row, oldest-first.

mysma-9403 added a commit to mysma-9403/openhuman that referenced this pull request Jul 9, 2026
…ist_drifting

Address review on tinyhumansai#4541:
- list_drifting now selects and returns primary_phone (parity with people.list):
  the 'going cold' surface is meant to prompt reconnecting, and many drifting
  contacts are reachable by phone/iMessage rather than email — returning it here
  saves consumers a second people.list/score round-trip. Threaded through the
  RPC row shape, the output schema, and the README methods table (which was
  also missing a people.drifting row entirely).
- Co-locate the limit clamp (1..=500) inside list_drifting, not only in
  handle_drifting: the fn is pub, and a direct call with limit==0 would emit
  LIMIT 0 (empty) while a usize > i64::MAX would wrap under 'as i64' to a
  negative, which SQLite reads as no limit (returns everything).
- Introduce a DriftingRow type alias for the now-5-field row tuple to keep the
  Vec<...> signature readable and avoid clippy::type_complexity.
- Tests: assert primary_phone passthrough and the limit==0 clamp.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 9, 2026
@Coding-Dev-Tools

Copy link
Copy Markdown

🤖 Council Merge Gate — APPROVE (merge blocked by branch protection)

The model council review gate returned APPROVE (session council-5b36a3f5-354d-4aa4-8d03-32afbfd653da), so the change is council-cleared.

However, gh pr merge was rejected by GitHub branch protection: the base branch policy prohibits the merge. This repo requires passing status checks and/or an approving review before merge, and those requirements are not currently satisfied (e.g. failing required CI checks).

Action needed (human): resolve the failing required checks / obtain the required review, then merge. A maintainer with admin rights may also merge explicitly. The PR is left OPEN.

Posted automatically by the council_gate_merge cron job.

@Coding-Dev-Tools

Copy link
Copy Markdown

🤖 Council Merge Gate — APPROVE (merge blocked by branch protection)

The model council review gate returned APPROVE (session council-bc7b3cb4-fa48-4d31-9f34-53410a878106), so the change is council-cleared.

However, gh pr merge was rejected by GitHub branch protection: the base branch policy prohibits the merge. This repo requires passing status checks and/or an approving review before merge, and those requirements are not currently satisfied (e.g. failing required CI checks).

Action needed (human): resolve the failing required checks / obtain the required review, then merge. A maintainer with admin rights may also merge explicitly. The PR is left OPEN.

Posted automatically by the council_gate_merge cron job.

@Coding-Dev-Tools

Copy link
Copy Markdown

🤖 Council Merge Gate — APPROVE (merge blocked by branch protection)

The model council review gate returned APPROVE (session council-af2adc5d-6a8c-45b9-a2cf-9a4da72a1a90), so the change is council-cleared.

However, gh pr merge was rejected by GitHub branch protection: the base branch policy prohibits the merge. This repo requires passing status checks and/or an approving review before merge, and those requirements are not currently satisfied (e.g. failing required CI checks).

Action needed (human): resolve the failing required checks / obtain the required review, then merge. A maintainer with admin rights may also merge explicitly. The PR is left OPEN.

Posted automatically by the council_gate_merge cron job.

@senamakel senamakel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Automated technical review: not approved.

Summary

The code changes are clean, well-scoped, and follow established people-domain conventions. All prior review feedback (CodeRabbit, Codex, M3gA-Mind) has been addressed: overflow guard, diagnostics, primary_phone passthrough, and store-level limit clamp are all present. Test coverage is thorough across store, RPC handler, overflow, and schema layers.

However, the "Rust Quality (fmt, clippy)" check is failing, causing the "PR CI Gate" to also fail, and the approval standard requires all status checks to be terminal and successful, neutral, or skipped.

The CI failure is pre-existing and unrelated to this PR

All 8 compilation errors are in src/openhuman/voice/dictation_listener.rs — missing ActivationMode, HotkeyEvent, and hotkey module. These are voice-domain feature-gate issues on the base branch (July 9). The PR touches only src/openhuman/people/ files and does not cause or interact with this breakage. The main-branch CI has since been fixed (multiple successful runs on July 22).

Findings

  • blocking | CI: Rust Quality (fmt, clippy) | Failing check (exit code 101) with 8 compile errors in src/openhuman/voice/dictation_listener.rs. Pre-existing and unrelated to this PR, but formal CI gate blocks approval.

Code quality (positive)

  • Store query is parameterized, uses JOIN (correctly excluding never-interacted), returns oldest-first
  • Overflow-safe: i64::try_from(days).unwrap_or(i64::MAX) + saturating_sub/saturating_mul
  • Limit clamping co-located in list_drifting (not only in the RPC handler), guarded with dedicated test
  • DriftingRow type alias avoids clippy::type_complexity
  • Tracing with stable [people::rpc] prefix on entry, errors, and result count
  • Controller wiring follows existing paradigm — auto-registered via all_people_registered_controllers()
  • Tests cover: store ordering/exclusion, RPC handler, u64::MAX overflow guard, schema shape/count

Recommended next step

Rebase onto current main and push. The base-branch voice-gate compilation error is resolved on main. No code changes needed.

@senamakel senamakel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Automated technical review: not approved.

Summary

The code changes are clean, well-scoped, and follow established people-domain conventions. All prior review feedback (CodeRabbit, Codex, M3gA-Mind) has been addressed: overflow guard, diagnostics, primary_phone passthrough, and store-level limit clamp are all present. Test coverage is thorough across store, RPC handler, overflow, and schema layers.

However, the "Rust Quality (fmt, clippy)" check is failing, causing the "PR CI Gate" to also fail, and the approval standard requires all status checks to be terminal and successful, neutral, or skipped.

The CI failure is pre-existing and unrelated to this PR

All 8 compilation errors are in src/openhuman/voice/dictation_listener.rs — missing ActivationMode, HotkeyEvent, and hotkey module. These are voice-domain feature-gate issues on the base branch (July 9). The PR touches only src/openhuman/people/ files and does not cause or interact with this breakage. The main-branch CI has since been fixed (multiple successful runs on July 22).

Findings

  • blocking | CI: Rust Quality (fmt, clippy) | Failing check (exit code 101) with 8 compile errors in src/openhuman/voice/dictation_listener.rs. Pre-existing and unrelated to this PR, but formal CI gate blocks approval.

Code quality (positive)

  • Store query is parameterized, uses JOIN (correctly excluding never-interacted), returns oldest-first
  • Overflow-safe: i64::try_from(days).unwrap_or(i64::MAX) + saturating_sub/saturating_mul
  • Limit clamping co-located in list_drifting (not only in the RPC handler), guarded with dedicated test
  • DriftingRow type alias avoids clippy::type_complexity
  • Tracing with stable [people::rpc] prefix on entry, errors, and result count
  • Controller wiring follows existing paradigm — auto-registered via all_people_registered_controllers()
  • Tests cover: store ordering/exclusion, RPC handler, u64::MAX overflow guard, schema shape/count

Recommended next step

Rebase onto current main and push. The base-branch voice-gate compilation error is resolved on main. No code changes needed.

@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown

Greptile Summary

This PR introduces people.drifting, a new read-only RPC that surfaces contacts whose most recent interaction is older than a caller-supplied threshold in days. The change is self-contained in the Rust core and deliberately defers a UI card and optional cron digest to follow-up PRs.

  • Store layer (store.rs): adds list_drifting(cutoff_ts, limit), an INNER JOIN-based grouped query returning (id, display_name, primary_email, primary_phone, last_interaction_ts) ordered oldest-first; limit is clamped to 1..=500 at both the store and RPC layers.
  • RPC layer (rpc.rs): handle_drifting converts days → cutoff, guards the u64 → i64 conversion with i64::try_from + saturating arithmetic, derives days_since_last against the same now snapshot, and the previously flagged store::get() leak has been replaced with current_people_store()? (workspace-scoped).
  • Schema / wiring (schemas.rs): controller registered with optional days/limit inputs, output shape matches RPC output; E2E coverage explicitly deferred with a tracked TODO comment.

Confidence Score: 5/5

Safe to merge — the new people.drifting endpoint is a pure read path with no mutations, the previously flagged cross-workspace store leak is resolved, and all edge cases are tested.

The change is a self-contained read-only addition. The u64 to i64 cutoff conversion is guarded with saturating arithmetic and exercised by a dedicated test. Limit is clamped at both the RPC and store layers. The workspace-isolation fix matches every sibling controller. No mutations, migrations, or shared state are touched.

Files Needing Attention: No files require special attention.

Important Files Changed

Filename Overview
src/openhuman/people/store.rs Adds list_drifting with correct INNER JOIN grouping, HAVING filter, and double-clamped limit; limit as i64 cast is safe post-clamp.
src/openhuman/people/rpc.rs Adds handle_drifting with proper u64 to i64 overflow guard, consistent now snapshot for both cutoff and days_since_last, and corrected workspace-scoped store access.
src/openhuman/people/schemas.rs Controller registered with correct schema, handler reads current_people_store() workspace-scoped, parameters mapped correctly.
tests/json_rpc_e2e.rs Adds a tracked TODO comment explaining why the HTTP-level smoke test is deferred; no test logic added or removed.
src/openhuman/people/tests.rs Controller count test updated from 4 to 5; drifting presence assertion added.
src/openhuman/people/README.md README table updated to reflect the new people.drifting controller with accurate input/output documentation.

Reviews (3): Last reviewed commit: "test(people): track deferred JSON-RPC E2..." | Re-trigger Greptile

Comment thread src/openhuman/memory/people/schemas.rs
Comment on lines +65 to +129
/// Contacts whose most recent interaction is older than `days` days — the
/// "relationships going cold" surface the scorer's `ScoreComponents` doc
/// anticipates. Oldest last-touch first; contacts never interacted with are
/// excluded (there's nothing to drift from).
pub async fn handle_drifting(
store: &PeopleStore,
days: u64,
limit: usize,
) -> Result<RpcOutcome<Value>, String> {
let limit = limit.clamp(1, 500);
let now = Utc::now();
// Guard the u64 -> i64 conversion: `days as i64` wraps for values above
// i64::MAX (u64::MAX -> -1), which would push the cutoff into the future and
// return nearly every contact. Clamp with try_from, then subtract saturating
// so an absurd threshold floors the cutoff (→ no matches) instead of
// overflowing in debug builds.
let days_i64 = i64::try_from(days).unwrap_or(i64::MAX);
let cutoff_ts = now
.timestamp()
.saturating_sub(days_i64.saturating_mul(86_400));
tracing::debug!(
domain = "people",
operation = "drifting",
days,
limit,
cutoff_ts,
"[people::rpc] drifting: querying contacts with no interaction in > {days}d"
);
let rows = store.list_drifting(cutoff_ts, limit).await.map_err(|e| {
tracing::warn!(
domain = "people",
operation = "drifting",
error = %e,
"[people::rpc] drifting: list_drifting query failed"
);
format!("list_drifting: {e}")
})?;
tracing::debug!(
domain = "people",
operation = "drifting",
count = rows.len(),
"[people::rpc] drifting: {} contact(s) past the {days}d threshold",
rows.len()
);
let contacts: Vec<Value> = rows
.into_iter()
.map(
|(id, display_name, primary_email, primary_phone, last_ts)| {
let days_since_last = (now.timestamp() - last_ts).max(0) / 86_400;
json!({
"person_id": id.to_string(),
"display_name": display_name,
"primary_email": primary_email,
"primary_phone": primary_phone,
"last_interaction_at": last_ts,
"days_since_last": days_since_last,
})
},
)
.collect();
Ok(RpcOutcome::new(
json!({ "contacts": contacts, "threshold_days": days }),
vec![],
))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 JSON-RPC E2E coverage missing

Per the CLAUDE.md feature design workflow, step 3 is "JSON-RPC E2E — extend tests/json_rpc_e2e.rs / scripts/test-rust-with-mock.sh." This PR adds solid #[cfg(test)] unit coverage at the store and rpc layers but does not extend the JSON-RPC E2E suite. The E2E layer exercises the full HTTP transport → dispatch → handler → store stack, which is where bugs in wiring (e.g., the store accessor issue on the schemas adapter) would also surface at test time.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fair on the workflow step. The store→rpc→schema path is covered by unit tests at each layer (list_drifting ordering/exclusion/limit-clamp, handle_drifting u64::MAX overflow guard + tracing, schema shape/count) — which is what the ≥80% diff-coverage gate enforces here. I kept the JSON-RPC E2E as a deliberate follow-up rather than adding it in this store-layer PR: it is most meaningful in the UI PR that actually consumes people.drifting, where the full HTTP→dispatch→store round-trip exercises the wiring. Happy to extend tests/json_rpc_e2e.rs here instead if you would prefer it gated in this PR.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The unit coverage argument is solid, and the 80% gate confirms the logic paths are exercised. The one thing unit tests at each layer can't catch is a mismatch in the adapter itself — e.g., the schemas.rs registration passing the wrong argument order or a mismatched field name to the RPC handler. That wiring is already complete in this PR, which is exactly why the E2E belongs here rather than in a follow-up: the follow-up UI PR wouldn't be adding the transport wiring, it would just be consuming an already-wired endpoint.

That said, since the endpoint is deterministic and the schema shape test already validates inputs/outputs, the residual risk is low. If you're committed to deferring, I'd suggest at minimum adding a comment in tests/json_rpc_e2e.rs like:

// TODO(people.drifting): add E2E coverage when the UI card PR lands — wiring in
// schemas.rs is already complete; this just needs an HTTP-level smoke test.

That makes the gap intentional and trackable rather than invisible to the next reviewer.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call — took your suggested compromise in 424ae5e. Added a TODO(people.drifting, #4541) anchor next to the context-scoped controller E2E tests in tests/json_rpc_e2e.rs, so the gap is intentional and trackable rather than invisible to the next reviewer.

You're right that the adapter wiring lives in this PR — the reason I deferred the HTTP smoke test rather than adding it here is mechanical, not a coverage dodge: handle_drifting resolves its store via CoreContext::current()?.people(), and the bare build_core_http_router harness these tests use never builds a DEFAULT_CONTEXT (it is only set when a full CoreContext is constructed — context.rs:158). No people.* controller has E2E coverage yet for exactly this reason. Standing up that context scaffolding belongs with the UI card PR that consumes the endpoint, where it is reused rather than one-off. The adapter itself is a 4-line delegation whose field names are pinned by the schema-shape unit test.

Comment thread src/openhuman/memory/people/schemas.rs
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 27, 2026
Re-applied onto the post-tinyhumansai#5328 tree (people → memory/people). Adds a read-only
`people.drifting` controller: contacts whose most recent interaction is older
than a `days` threshold (default 30), oldest last-touch first, excluding
contacts never interacted with.

- store: `list_drifting(cutoff_ts, limit)` — one grouped SELECT over people
  JOIN interactions, `HAVING MAX(i.ts) < cutoff`, `ORDER BY last_ts ASC`,
  `LIMIT` clamped 1..=500 inside the pub fn (a `usize` above i64::MAX wraps to a
  negative under `as i64`, which SQLite reads as "no limit").
- rpc: `handle_drifting(store, days, limit)` — derives the cutoff with a
  saturating u64→i64 guard (an absurd `days` floors the cutoff instead of
  wrapping it into the future), returns `contacts[]` + `threshold_days`.
- schema: `people.drifting` controller wired via the existing registry vecs.

Per-layer unit tests cover store ordering/exclusion/limit-clamp, the
handle_drifting u64::MAX overflow guard, and the schema shape/count. The
JSON-RPC E2E smoke test is deferred (the bare router harness has no built
people CoreContext), same as the original PR.

Claude-Session: https://claude.ai/code/session_01ACB4Ugi5pJMQqoCbZnVo6f
Copilot AI lite review requested due to automatic review settings August 11, 2026 09:38
@mysma-9403
mysma-9403 force-pushed the feat/people-drifting-contacts branch from 424ae5e to e367ad2 Compare August 11, 2026 09:38
@mysma-9403

Copy link
Copy Markdown
Contributor Author

Force-pushed — re-applied after the #5328 restructure (this branch was 750 commits behind). Reset onto current main and the change re-applied at the new path src/openhuman/memory/people/* (was src/openhuman/people/*). The red --no-default-features lanes are the pre-existing main memory/diff breakage described in the body (also red on #5486, green on the pre-regression base); Rust Quality (fmt, clippy) compiles this clean, and cargo test --lib --features memory-git memory::people is green locally.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot removed rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. feature Net-new user-facing capability or product behavior. labels Aug 11, 2026

Copilot AI 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.

Pull request overview

Adds a new read-only people.drifting controller to the Rust core’s memory/people domain, surfacing contacts whose most recent interaction is older than a caller-supplied staleness threshold (default 30 days). This extends the existing People/relationship-scoring subsystem with a “gone quiet” query suitable for UI nudges or automation.

Changes:

  • Add PeopleStore::list_drifting(cutoff_ts, limit) to query stale contacts (oldest last-touch first), excluding never-interacted contacts.
  • Add RPC + schema wiring for people.drifting, including days/limit defaults and computed days_since_last.
  • Update unit tests and domain README to cover the new controller and updated controller counts.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/openhuman/memory/people/store.rs Adds list_drifting query and a store-level test covering ordering, exclusions, and limit clamping.
src/openhuman/memory/people/rpc.rs Adds handle_drifting RPC handler + tests for payload shape and overflow-safe cutoff derivation.
src/openhuman/memory/people/schemas.rs Registers the new controller schema/handler and adds schema-level tests for optional inputs and controller counts.
src/openhuman/memory/people/tests.rs Updates schema exposure test naming and expected controller count.
src/openhuman/memory/people/README.md Documents the new people.drifting method and its inputs/outputs.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

| Method | Inputs | Output |
| --- | --- | --- |
| `people.list` | `limit?` (default 100, capped at 500) | `people[]` ranked by score desc — each with `person_id`, `display_name?`, `primary_email?`, `primary_phone?`, `handles[]`, `score`, `components`, `interaction_count`. |
| `people.drifting` | `days?` (default 30), `limit?` (default 100, clamped 1–500) | `contacts[]` (oldest last-touch first), each with `person_id`, `display_name?`, `primary_email?`, `primary_phone?`, `last_interaction_at`, `days_since_last`; plus `threshold_days`. Excludes contacts never interacted with. |
Comment on lines +402 to +404
let days = read_optional_u64(&params, "days")?.unwrap_or(30);
let limit = read_optional_u64(&params, "limit")?.unwrap_or(100) as usize;
to_json(rpc::handle_drifting(&store, days, limit).await?)

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

🤖 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/openhuman/memory/people/README.md`:
- Line 47: Update the controller count statement in the people namespace README
from four to five, leaving the controller table and all other documentation
unchanged.

In `@src/openhuman/memory/people/rpc.rs`:
- Around line 85-108: Add a non-sensitive correlation identifier to the
diagnostics emitted by handle_drifting, including both debug logs and the
list_drifting warning, so all drifting events can be correlated without secrets
or PII. Generate or obtain the ID within the handler and include the same field
in each relevant tracing event.
🪄 Autofix

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 Plus

Run ID: b137af3f-ad92-47eb-961e-645c1a1b474b

📥 Commits

Reviewing files that changed from the base of the PR and between 4748cdd and e367ad2.

📒 Files selected for processing (5)
  • src/openhuman/memory/people/README.md
  • src/openhuman/memory/people/rpc.rs
  • src/openhuman/memory/people/schemas.rs
  • src/openhuman/memory/people/store.rs
  • src/openhuman/memory/people/tests.rs

| Method | Inputs | Output |
| --- | --- | --- |
| `people.list` | `limit?` (default 100, capped at 500) | `people[]` ranked by score desc — each with `person_id`, `display_name?`, `primary_email?`, `primary_phone?`, `handles[]`, `score`, `components`, `interaction_count`. |
| `people.drifting` | `days?` (default 30), `limit?` (default 100, clamped 1–500) | `contacts[]` (oldest last-touch first), each with `person_id`, `display_name?`, `primary_email?`, `primary_phone?`, `last_interaction_at`, `days_since_last`; plus `threshold_days`. Excludes contacts never interacted with. |

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the controller count.

Line 42 says that the people namespace has four controllers. This table now lists five controllers. Change “Four controllers” to “Five controllers.”

🤖 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 `@src/openhuman/memory/people/README.md` at line 47, Update the controller
count statement in the people namespace README from four to five, leaving the
controller table and all other documentation unchanged.

Comment on lines +85 to +108
tracing::debug!(
domain = "people",
operation = "drifting",
days,
limit,
cutoff_ts,
"[people::rpc] drifting: querying contacts with no interaction in > {days}d"
);
let rows = store.list_drifting(cutoff_ts, limit).await.map_err(|e| {
tracing::warn!(
domain = "people",
operation = "drifting",
error = %e,
"[people::rpc] drifting: list_drifting query failed"
);
format!("list_drifting: {e}")
})?;
tracing::debug!(
domain = "people",
operation = "drifting",
count = rows.len(),
"[people::rpc] drifting: {} contact(s) past the {days}d threshold",
rows.len()
);

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C3 'handle_drifting|people::rpc|request_id|correlation_id|trace_id|Span::current' \
  src/openhuman src/core 2>/dev/null || true

Repository: tinyhumansai/openhuman

Length of output: 50379


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="src/openhuman/memory/people/rpc.rs"
printf '%s\n' '--- target file outline ---'
ast-grep outline "$file" 2>/dev/null || true
printf '%s\n' '--- target implementation ---'
sed -n '1,150p' "$file"
printf '%s\n' '--- direct callers and RPC dispatch context ---'
rg -n -C5 'handle_drifting|ControllerFuture|ControllerSchema|tracing::(debug|trace|warn)|Span::current|request_id|correlation_id|trace_id' "$file" src/openhuman/memory src/openhuman/rpc* src 2>/dev/null \
  | head -n 1200
printf '%s\n' '--- span/request correlation setup ---'
rg -n -C4 'tracing::(info_span|debug_span|span!)|Instrument|request_id|correlation_id|trace_id|thread_id' src --glob '*.rs' \
  | head -n 1600

Repository: tinyhumansai/openhuman

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- people module files ---'
git ls-files 'src/openhuman/memory/people/*'
printf '%s\n' '--- drifting references ---'
rg -n -C8 'handle_drifting|drifting' src/openhuman/memory/people
printf '%s\n' '--- request-boundary tracing ---'
rg -n -C6 'span\(|info_span|debug_span|trace_span|Span::current|request_id|correlation_id|trace_id' \
  src/api src/rpc.rs src/core src/openhuman/memory/people --glob '*.rs' \
  | rg -n 'request|correlation|trace|span|rpc|people|dispatch|controller' \
  | head -n 1000
printf '%s\n' '--- tracing subscriber and formatter setup ---'
rg -n -C5 'tracing_subscriber|fmt::|with_span_events|EnvFilter|set_global_default|tracing_appender' \
  src --glob '*.rs' | head -n 800

Repository: tinyhumansai/openhuman

Length of output: 43444


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- people schema handler and context access ---'
sed -n '1,80p;380,420p' src/openhuman/memory/people/schemas.rs
rg -n -C8 'current_people_store|CoreContext|invoke_method|RegisteredController|handler\(' \
  src/openhuman/memory/people src/core --glob '*.rs' \
  | head -n 1200
printf '%s\n' '--- candidate RPC transport files ---'
git ls-files 'src/**/*.rs' | rg 'rpc|server|jsonrpc|controller|transport|context' | head -n 300
printf '%s\n' '--- tracing spans in likely RPC transport files ---'
while IFS= read -r f; do
  rg -n -C6 'info_span|debug_span|trace_span|tracing::span|Span::current|request_id|correlation_id|trace_id|invoke_method|RegisteredController' "$f" || true
done < <(git ls-files 'src/**/*.rs' | rg 'rpc|server|jsonrpc|controller|transport|context')

Repository: tinyhumansai/openhuman

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- JSON-RPC request path ---'
sed -n '45,85p;449,490p' src/core/jsonrpc.rs
printf '%s\n' '--- HTTP tracing/request-id middleware ---'
rg -n -C8 'TraceLayer|MakeSpan|make_span|RequestId|request_id|trace_id|correlation_id|tower_http::trace|tracing::Span' \
  src/core/jsonrpc.rs src/api src/core --glob '*.rs' \
  | head -n 1200
printf '%s\n' '--- all direct span creation in the RPC module ---'
rg -n -C5 'span|request_id|correlation_id|trace_id' src/core/jsonrpc.rs
printf '%s\n' '--- logging configuration for span fields ---'
rg -n -C6 'tracing_subscriber|with_span_events|fmt::layer|EnvFilter|set_global_default|subscriber' \
  src/core/logging.rs src/core --glob '*.rs' | head -n 1000

Repository: tinyhumansai/openhuman

Length of output: 50380


Add a correlation field to the drifting diagnostics. The JSON-RPC handler invokes handle_drifting without a request span, so domain and operation do not correlate events. Add a safe correlation ID to lines 85–107 without logging secrets or PII.

🤖 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 `@src/openhuman/memory/people/rpc.rs` around lines 85 - 108, Add a
non-sensitive correlation identifier to the diagnostics emitted by
handle_drifting, including both debug logs and the list_drifting warning, so all
drifting events can be correlated without secrets or PII. Generate or obtain the
ID within the handler and include the same field in each relevant tracing event.

Source: Coding guidelines

@mysma-9403

Copy link
Copy Markdown
Contributor Author

Closing in the wake of the memory-subsystem extraction on main (ba088ec20 / a2bfeb38c). This PR's natural home was a store-level query (SELECT … JOIN interactions GROUP BY p.id HAVING MAX(i.ts) < ? ORDER BY last_ts ASC LIMIT ?) in memory/people/store.rs, which is now the vendored tinymemory crate (core/src/people/store.rs) — out of this repo. It is still expressible at the RPC layer (store.list() + store.batch_interactions_for(), filter/sort in Rust), but only as a degraded reimplementation of the SQL above, so I'd rather not land it mid-extraction. Happy to reopen and re-author at the RPC layer, or upstream the store query in tinymemory, if the feature is wanted.

@mysma-9403 mysma-9403 closed this Aug 12, 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.

5 participants