Skip to content

Add a scheduled people sweep that keeps curated attributes current from the archive #628

Description

@salmonumbrella

The situation

A person in msgvault is pegged to a stream of conversations that never stops: email, chats, meetings, and now the attachments they send. The profile, by contrast, only changes when someone edits it. So the archive knows that a contact changed jobs, moved cities, got a dog, or stopped mentioning a partner months before the profile does, and "last time we talked" is whatever the user remembers.

The typed attribute layer (#535) and the structured primitives (#565) were built so that facts about a person are typed, historized, and provenance-aware. What is missing is the thing that keeps them current: a scheduled sweep that looks at what happened with a person since the last pass, updates the fields it can update safely, proposes the rest, and keeps the old values as history. Nobody breaks up with a past partner in a database; the name should still be there, closed with a date.

This issue proposes that sweep, plus the attribute catalog it maintains, so a personal-CRM profile like the one below stays populated without anyone typing:

  • Contact: email, phone, social profiles, chat handles
  • Profile: role, company, location, born in, birthday, address, membership, religion, politics, personality
  • Family: partner, pets, kids
  • Fun now, fun growing up, favorite food, favorite place, how we met, ask me about
  • Last contacted, contact frequency, primary channel
  • Last time we talked

What works today, and why it is not enough

Three pieces are in place, and one of them already does part of this without any model:

What does not exist: any scheduled process that reads that activity on a person's behalf, a candidate/evidence model for extracted facts, a policy that says which facts may be written unattended and which must be reviewed, a consented inference provider, or a "last time we talked" brief. #534's constraints already say that new profile facts are typed and provenance-aware, that mutable facts retain history, and that enrichment writes candidates rather than curated facts. All of that stays true here: the model proposes; msgvault decides, per attribute class, whether a proposal may be applied without a human, and every application is reversible with its evidence attached.

Ask

Add a scheduled people sweep that keeps curated person attributes current from the archive, in focused increments.

1. Attribute catalog and extraction classes

Seed the catalog above so it is usable by hand on day one, mapped onto the primitives that already exist rather than duplicated as free-text attributes:

Field Where it lives Extraction class
Email, phone, social, chat handles person_contact_points (#565) observed via existing importers, never extracted
Role, company organizations and employment associations (#534 step 5) auto-eligible
Location, born in attributes location, birthplace auto-eligible
Birthday person_dates auto-eligible
Address person_addresses review-only
Membership attribute membership (multi-value) auto-eligible
Religion, politics, personality attributes, sensitive seeded; never extracted by default; a config flag opts them into review-only extraction; never on the auto-apply allowlist; never projected to CardDAV
Partner, kids typed person relationships (#534 step 6) review-only for changes; auto-eligible for first observation of a name
Pets attribute family.pets (multi-value) auto-eligible
Fun now, fun growing up, favorite food, favorite place attributes interests.fun_now, interests.fun_growing_up, favorites.food, favorites.place (multi-value) auto-eligible
How we met, ask me about relationship-scoped attributes relationship.how_we_met, relationship.ask_me_about how we met review-only; ask me about auto-eligible
Tags person_categories review-only
Last contacted, primary channel, contact frequency derived from person_contact_state; written into the seeded attributes deterministic, everyone, no model
Last time we talked dated versioned brief, see 5 generated per sweep

Every seed carries a subject scope: about the person, about the relationship with the user, or about a related person. A statement about a partner or child is never filed as a fact about the person.

2. Who gets swept

Deterministic fields update for every durable person; they cost nothing. Extraction runs only for persons marked as tracked (msgvault person track <id>, bulk by tag or list; publishing to an address book can imply tracking). This bounds model spend to people the user actually maintains.

3. Evidence, retrieval-first

Per tracked person, take only activity past that person's sweep watermark. For each attribute family, run a semantic probe over the person's new conversation-window, meeting, and visual-attachment embeddings ("changed jobs", "moved to", "my partner", "our dog", "birthday", "favorite restaurant"...) plus a lexical pass, and keep only windows above a similarity floor. Visual embeddings are used to find moments; the model receives the surrounding text and any stored caption or OCR, never the image bytes, in this version. Assemble a bounded evidence pack under a per-person token budget: verbatim excerpts with exact message, conversation-window, meeting-span, or attachment-occurrence pointers and dates.

4. Extraction, validation, and the two-tier write

One structured-output call per person returns candidates (subject, key, value, observed date, change date if the evidence names one, confidence, evidence indices), a brief (see 5), and uncertainties. Then, in msgvault, not in the prompt:

  • Alignment: each quoted excerpt must fuzzy-match its pointed source; a candidate whose evidence does not align is dropped and counted as a hallucination signal.
  • Subject boundary and sensitive-key checks per the catalog.
  • Rejection memory: a hash of (person, key, normalized value) that the user rejected is never re-proposed. Rejections are writes, never deletes.
  • Dedup against open candidates and current values.
  • Routing: a candidate is auto-applied only if its key is on the configured allowlist, confidence is at or above the configured threshold, and it does not contradict a current value whose source is declared (user, carddav_import, vcard_import). Everything else enters the review queue with pending, accepted, rejected, unsure, superseded, auto_accepted states.
  • Writes are bitemporal supersedes, never deletes. A new partner value closes the previous one with active_until = the change date the evidence gave, else the observed date; the old row stays queryable with its evidence. source=extraction, source_ref=candidate, actor=sweep. msgvault person attributes revert <value-id> undoes any auto-write and records the rejection.

Candidates and evidence persist as person_fact_candidates and person_fact_evidence, alongside person_sweep_state (tracked, per-lane watermarks, last run, spend) and a sweep run log. Contradictions stay visible; the sweep never resolves them by last-write-wins.

5. Last time we talked

The timestamp stays deterministic from #599. The sweep additionally writes a short structured brief per tracked person: highlights the person shared, open threads, follow-up questions, appreciation the user actually expressed, each with evidence pointers and dates, rendered to a compact paragraph for the person page and API/MCP. Each regeneration is a new dated version with its input boundary, provider, model, and program version; it never rewrites what the user previously saw. The brief can carry possible_attributes, which enter the same candidate flow.

6. Provider and consent

One inference interface with JSON-schema output, prompt/program version, and a recorded consent (provider, model, retention and training posture, allowed sources and date range) before any message text leaves the machine, in the shape #616 uses for Mistral. First adapter: ChatGPT/Codex sign-in (OAuth device login), because that is the account many users already pay for. Stated risk: it is a consumer login flow used for unattended calls, so acceptance depends on the provider's terms and on the maintainer; the OpenAI-compatible API-key adapter (OpenAI, OpenRouter, local servers) is the fallback that keeps the feature independent of that decision, and the existing Ollama [chat] server is the fully local option. Nothing here changes what the archive stores; only the derived candidates, values, and briefs are written.

7. Surfaces

  • [people.sweep] config: schedule (daily deterministic, weekly extraction by default), per-person and per-run token budgets, auto-apply allowlist and threshold, sensitive opt-in, provider selection.
  • CLI: person track|untrack, person sweep <id> [--dry-run], person candidates list|accept|reject|unsure, person attributes revert, person brief <id>.
  • API and generated clients for candidates, decisions, briefs, and sweep state; MCP read tools for candidates and briefs.
  • Web review queue and brief on the person page when the person workspace lands.

Evaluation

Frozen synthetic fixtures with expected facts, expected omissions, subject-attribution traps, and stale-fact cases; regression metrics for factual support, alignment rate, attribution errors, and no re-proposal after rejection, run in the same harness proposed for retrieval quality in #534. Sweeps must be idempotent: a second run over the same watermark produces no new candidates.

Delivery boundary

Increments, each its own PR: (a) catalog seeds, tracking flag, deterministic sweep, no model; (b) provider interface, consent, first adapter; (c) candidates, evidence, review CLI/API; (d) retrieval-first extraction and tiered auto-apply; (e) briefs; (f) web review UI. Out of scope: external enrichment providers (#534 step 15), image uploads to a vision model, free-text notes on the profile, and any change to what the archive ingests.

Refs #534, #535, #565, #589, #599, #611.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions