Add User Model Service and Temporal Recall for MeMind V0.1.

Introduce UMS ingest/snapshot pipeline, Context Planner with multi-source recall, runtime context injection, canonical user mapping, and session snapshot loading on auth/me.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-09-03 23:25:18 +08:00
parent bf66fdf493
commit 212ff3ff80
47 changed files with 4534 additions and 6 deletions
+175
View File
@@ -0,0 +1,175 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://memind.local/schemas/context-plan.v1.schema.json",
"title": "ContextPlan",
"description": "Context Planner 输出 — 决定需要哪些上下文及多源检索计划",
"type": "object",
"required": ["query_type", "time", "temporal_mode", "targets", "sources", "retrievals", "context_needs"],
"additionalProperties": false,
"properties": {
"query_type": {
"type": "string",
"enum": [
"personal_temporal_recall",
"memory_lookup",
"profile_only",
"general_chat",
"task_execution"
]
},
"time": {
"type": "object",
"required": ["mention_range"],
"additionalProperties": false,
"properties": {
"mention_range": {
"$ref": "#/$defs/timeRange",
"description": "按 observed_time / 提及时间过滤"
},
"event_range": {
"oneOf": [{ "$ref": "#/$defs/timeRange" }, { "type": "null" }],
"description": "按 event_time / 计划发生时间过滤"
},
"relative_label": {
"type": ["string", "null"],
"enum": [
"today",
"yesterday",
"day_before_yesterday",
"this_week",
"last_week",
"this_month",
"last_month",
"recent_days",
null
]
}
}
},
"temporal_mode": {
"type": "string",
"enum": ["OCCURRED_IN", "MENTIONED_IN", "PLANNED_IN", "CREATED_IN", "DUE_IN", "AMBIGUOUS"]
},
"targets": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["type", "weight"],
"additionalProperties": false,
"properties": {
"type": {
"type": "string",
"enum": [
"calendar_event",
"event",
"schedule",
"todo",
"commitment",
"decision",
"mention",
"important_event"
]
},
"weight": { "type": "number", "minimum": 0, "maximum": 1 }
}
}
},
"sources": {
"type": "object",
"additionalProperties": { "type": "number", "minimum": 0, "maximum": 1 },
"propertyNames": {
"enum": ["calendar", "chat", "meinput", "memory_v2", "email", "browser", "tasks", "git"]
}
},
"retrievals": {
"type": "array",
"items": {
"type": "object",
"required": ["source", "query_type", "weight"],
"additionalProperties": false,
"properties": {
"source": {
"type": "string",
"enum": ["calendar", "chat", "meinput", "memory_v2"]
},
"query_type": {
"type": "string",
"description": "源内检索策略,如 important_mentions | commitments_and_decisions | events"
},
"expanded_queries": {
"type": "array",
"items": { "type": "string", "maxLength": 128 }
},
"weight": { "type": "number", "minimum": 0, "maximum": 1 },
"skip_below": {
"type": "number",
"minimum": 0,
"maximum": 1,
"default": 0.3,
"description": "低于此权重的 source 跳过检索"
}
}
}
},
"filters": {
"type": "object",
"additionalProperties": false,
"properties": {
"importance_min": { "type": "number", "minimum": 0, "maximum": 1 },
"status": {
"type": "string",
"enum": ["any", "unresolved", "planned", "completed"]
},
"participants": {
"type": "array",
"items": { "type": "string" }
}
}
},
"output": {
"type": "object",
"additionalProperties": false,
"properties": {
"group_by": {
"type": "string",
"enum": ["importance", "time", "source", "type"]
},
"dedupe": { "type": "boolean", "default": true },
"timeline": { "type": "boolean", "default": true },
"wide_recall": { "type": "boolean", "default": true },
"max_items": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 }
}
},
"context_needs": {
"type": "object",
"required": ["user_snapshot", "temporal_recall", "memory_retrieval"],
"additionalProperties": false,
"properties": {
"user_snapshot": { "type": "string", "enum": ["REQUIRED", "OPTIONAL", "SKIP"] },
"temporal_recall": { "type": "string", "enum": ["REQUIRED", "OPTIONAL", "SKIP"] },
"memory_retrieval": { "type": "string", "enum": ["REQUIRED", "OPTIONAL", "SKIP"] }
}
},
"planner_meta": {
"type": "object",
"additionalProperties": false,
"properties": {
"level": { "type": "string", "enum": ["cheap_router", "semantic_planner"] },
"rule_hits": { "type": "array", "items": { "type": "string" } },
"confidence": { "type": "number", "minimum": 0, "maximum": 1 }
}
}
},
"$defs": {
"timeRange": {
"type": "object",
"required": ["start", "end"],
"additionalProperties": false,
"properties": {
"start": { "type": "string", "format": "date-time" },
"end": { "type": "string", "format": "date-time" }
}
}
}
}
+146
View File
@@ -0,0 +1,146 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://memind.local/schemas/evidence-envelope.v1.schema.json",
"title": "EvidenceEnvelope",
"description": "通用 Evidence Contract v1。所有 SourceMeInput、Chat、Calendar、Browser、Git…)统一产出此结构;UMS 只消费 Envelope,不依赖各 Source 内部表结构。",
"type": "object",
"required": [
"evidence_id",
"user_id",
"source_type",
"source_ref",
"occurred_at",
"evidence_type",
"payload",
"content_hash",
"schema_version"
],
"properties": {
"evidence_id": {
"type": "string",
"format": "uuid",
"description": "Evidence 全局唯一 ID(由 Source 或 Export 层生成)"
},
"user_id": {
"type": "string",
"format": "uuid",
"description": "MeMind h5_users.id"
},
"source_type": {
"type": "string",
"enum": ["meinput", "chat", "calendar", "browser", "git", "mail", "filesystem", "voice", "location"],
"description": "Evidence Source 类型,与 MeMind Source 体系对齐"
},
"source_ref": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"description": "Source 内稳定引用,如 segment_id / message_id / commit_sha"
},
"occurred_at": {
"type": "string",
"format": "date-time",
"description": "Evidence 发生时间(用户行为时间,非入库时间)"
},
"received_at": {
"type": "string",
"format": "date-time",
"description": "Source 侧接收/导出时间,可选"
},
"evidence_type": {
"type": "string",
"enum": [
"expression_segment",
"expression_commit",
"expression_session",
"chat_message",
"calendar_event",
"browser_visit",
"git_commit",
"aggregate_daily",
"timeline_report"
],
"description": "Evidence 语义类型,决定 payload 子 schema"
},
"payload": {
"type": "object",
"description": "类型化载荷,见 $defs"
},
"content_hash": {
"type": "string",
"pattern": "^[a-f0-9]{64}$",
"description": "SHA-256(canonical_json(payload)+evidence_type+source_ref),用于幂等与去重"
},
"schema_version": {
"type": "integer",
"const": 1
},
"privacy_level": {
"type": "string",
"enum": ["normal", "restricted", "secure_skip"],
"default": "normal",
"description": "secure_skip 的 Evidence 不应离开 SourceExport 层必须过滤"
},
"locale": {
"type": "string",
"description": "如 zh-CN,可选"
}
},
"additionalProperties": false,
"$defs": {
"ExpressionSegmentPayload": {
"type": "object",
"required": ["text", "start_at", "end_at", "event_ids"],
"properties": {
"text": { "type": "string", "minLength": 1 },
"start_at": { "type": "string", "format": "date-time" },
"end_at": { "type": "string", "format": "date-time" },
"event_ids": { "type": "array", "items": { "type": "string", "format": "uuid" }, "minItems": 1 },
"event_count": { "type": "integer", "minimum": 1 },
"context": {
"type": "object",
"properties": {
"app": { "type": ["string", "null"] },
"app_bundle_id": { "type": ["string", "null"] },
"device_id": { "type": "string" },
"scene": { "type": "string" },
"input_method": { "type": ["string", "null"] },
"language": { "type": ["string", "null"] }
}
}
},
"additionalProperties": false
},
"AggregateDailyPayload": {
"type": "object",
"required": ["day", "stats"],
"properties": {
"day": { "type": "string", "format": "date" },
"stats": {
"type": "object",
"properties": {
"events": { "type": "integer", "minimum": 0 },
"segments": { "type": "integer", "minimum": 0 },
"chars": { "type": "integer", "minimum": 0 },
"peak_hour": { "type": "integer", "minimum": 0, "maximum": 23 },
"top_apps": { "type": "array", "items": { "type": "string" } },
"top_terms": { "type": "array", "items": { "type": "string" } }
}
}
},
"additionalProperties": false
},
"TimelineReportPayload": {
"type": "object",
"required": ["report_type", "period_key", "edition", "status", "content"],
"properties": {
"report_type": { "type": "string", "enum": ["daily_close", "weekly_review"] },
"period_key": { "type": "string" },
"edition": { "type": "integer", "minimum": 1 },
"status": { "type": "string", "enum": ["published", "revoked"] },
"content": { "type": "object" }
},
"additionalProperties": false
}
}
}
+187
View File
@@ -0,0 +1,187 @@
-- memind_user_model v0 — User Model Service (UMS)
-- 库:memind_user_model(与 meinput、goose 并列,独立 RDS 库或 schema
-- UMS 只 ingest Evidence Envelope v1,不读 meinput.mi_* 表。
-- ── L0: 原始 Evidenceimmutable append)────────────────────────────────────
CREATE TABLE IF NOT EXISTS um_evidence (
evidence_id CHAR(36) PRIMARY KEY,
user_id CHAR(36) NOT NULL,
source_type VARCHAR(32) NOT NULL,
source_ref VARCHAR(512) NOT NULL,
evidence_type VARCHAR(64) NOT NULL,
occurred_at DATETIME(3) NOT NULL,
received_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
content_hash CHAR(64) NOT NULL,
schema_version INT NOT NULL DEFAULT 1,
privacy_level VARCHAR(32) NOT NULL DEFAULT 'normal',
payload_json JSON NOT NULL,
UNIQUE KEY uq_um_evidence_content (user_id, content_hash),
KEY idx_um_evidence_user_time (user_id, occurred_at DESC),
KEY idx_um_evidence_source (user_id, source_type, occurred_at DESC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ── L1: Signal(观察到了什么 — 确定性,可复算)────────────────────────────
CREATE TABLE IF NOT EXISTS um_signals (
signal_id CHAR(36) PRIMARY KEY,
user_id CHAR(36) NOT NULL,
signal_type VARCHAR(64) NOT NULL,
-- 例:term_frequency | app_usage | entity_mention | rhythm_hour | segment_count
dimension_key VARCHAR(128) NOT NULL,
-- 例:term:MeInput | app:com.tencent.xin | hour:15
window_start DATETIME(3) NOT NULL,
window_end DATETIME(3) NOT NULL,
value_json JSON NOT NULL,
-- 例:{"count":126,"chars":8400,"distinct_days":7}
evidence_ids JSON NOT NULL,
-- 指向 um_evidence.evidence_id 数组
computed_at BIGINT NOT NULL,
content_hash CHAR(64) NOT NULL,
UNIQUE KEY uq_um_signal (user_id, signal_type, dimension_key, window_start, window_end, content_hash),
KEY idx_um_signals_user_window (user_id, window_end DESC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ── L2: Candidate(系统推断了什么 — hypothesis)────────────────────────────
CREATE TABLE IF NOT EXISTS um_candidates (
candidate_id CHAR(36) PRIMARY KEY,
user_id CHAR(36) NOT NULL,
candidate_type VARCHAR(64) NOT NULL,
-- project | interest | preference | relation | routine | focus
hypothesis_json JSON NOT NULL,
-- 例:{"type":"project","name":"MeInput","role":"active"}
status ENUM(
'observed',
'open',
'accepted',
'rejected',
'conflicted',
'revoked',
'memory_eligible',
'memory_promoted'
) NOT NULL DEFAULT 'observed',
promotion_score DECIMAL(6,4) NOT NULL DEFAULT 0,
confidence DECIMAL(6,4) NOT NULL DEFAULT 0,
signal_ids JSON NOT NULL,
evidence_ids JSON NOT NULL,
first_seen_at DATETIME(3) NOT NULL,
last_seen_at DATETIME(3) NOT NULL,
version INT NOT NULL DEFAULT 1,
parent_candidate_id CHAR(36) NULL,
conflict_with_id CHAR(36) NULL,
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL,
KEY idx_um_candidates_user_status (user_id, status, last_seen_at DESC),
KEY idx_um_candidates_user_type (user_id, candidate_type, promotion_score DESC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ── L3: User Model Graph ───────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS um_entities (
entity_id CHAR(36) PRIMARY KEY,
user_id CHAR(36) NOT NULL,
entity_type VARCHAR(32) NOT NULL,
-- person | project | topic | tool | preference_dim | role
canonical_name VARCHAR(256) NOT NULL,
aliases_json JSON NULL,
status ENUM('active', 'archived', 'revoked') NOT NULL DEFAULT 'active',
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL,
UNIQUE KEY uq_um_entity (user_id, entity_type, canonical_name(128))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS um_attributes (
attribute_id CHAR(36) PRIMARY KEY,
user_id CHAR(36) NOT NULL,
entity_id CHAR(36) NULL,
attr_key VARCHAR(128) NOT NULL,
value_json JSON NOT NULL,
confidence DECIMAL(6,4) NOT NULL,
decay_halflife_days INT NOT NULL DEFAULT 90,
effective_weight DECIMAL(6,4) NOT NULL DEFAULT 1,
evidence_ids JSON NOT NULL,
candidate_id CHAR(36) NULL,
first_seen_at DATETIME(3) NOT NULL,
last_seen_at DATETIME(3) NOT NULL,
status ENUM('active', 'superseded', 'revoked') NOT NULL DEFAULT 'active',
version INT NOT NULL DEFAULT 1,
content_hash CHAR(64) NOT NULL,
KEY idx_um_attr_user_key (user_id, attr_key, status, effective_weight DESC),
CONSTRAINT fk_um_attr_entity FOREIGN KEY (entity_id) REFERENCES um_entities(entity_id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS um_relations (
relation_id CHAR(36) PRIMARY KEY,
user_id CHAR(36) NOT NULL,
from_entity_id CHAR(36) NOT NULL,
rel_type VARCHAR(64) NOT NULL,
to_entity_id CHAR(36) NOT NULL,
confidence DECIMAL(6,4) NOT NULL,
evidence_ids JSON NOT NULL,
candidate_id CHAR(36) NULL,
first_seen_at DATETIME(3) NOT NULL,
last_seen_at DATETIME(3) NOT NULL,
status ENUM('active', 'superseded', 'revoked') NOT NULL DEFAULT 'active',
KEY idx_um_rel_user (user_id, from_entity_id, rel_type),
CONSTRAINT fk_um_rel_from FOREIGN KEY (from_entity_id) REFERENCES um_entities(entity_id) ON DELETE CASCADE,
CONSTRAINT fk_um_rel_to FOREIGN KEY (to_entity_id) REFERENCES um_entities(entity_id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ── L4: Profile ProjectionGraph 的结构化投影,非 narrative)──────────────
CREATE TABLE IF NOT EXISTS um_profile_versions (
profile_version INT NOT NULL,
user_id CHAR(36) NOT NULL,
structured_json JSON NOT NULL,
content_hash CHAR(64) NOT NULL,
parent_version INT NULL,
materialize_reason VARCHAR(64) NOT NULL,
-- daily_consolidation | candidate_accepted | threshold | manual
created_at BIGINT NOT NULL,
status ENUM('active', 'superseded', 'revoked') NOT NULL DEFAULT 'active',
PRIMARY KEY (user_id, profile_version)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ── L5: Agent SnapshotProfile Projection 的渲染视图,Session 加载)────────
CREATE TABLE IF NOT EXISTS um_profile_snapshots (
snapshot_id CHAR(36) PRIMARY KEY,
user_id CHAR(36) NOT NULL,
projection VARCHAR(32) NOT NULL DEFAULT 'default',
-- default | coding | schedule
profile_version INT NOT NULL,
fast_revision INT NOT NULL DEFAULT 0,
snapshot_json JSON NOT NULL,
byte_size INT NOT NULL,
content_hash CHAR(64) NOT NULL,
created_at BIGINT NOT NULL,
expires_at BIGINT NULL,
status ENUM('active', 'superseded', 'revoked') NOT NULL DEFAULT 'active',
KEY idx_um_snapshot_user_proj (user_id, projection, status, created_at DESC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ── 辅助:ingest 游标(按 user + source_type)────────────────────────────────
CREATE TABLE IF NOT EXISTS um_ingest_cursors (
user_id CHAR(36) NOT NULL,
source_type VARCHAR(32) NOT NULL,
cursor_value VARCHAR(256) NOT NULL,
updated_at BIGINT NOT NULL,
PRIMARY KEY (user_id, source_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ── v0.1 预留:Memory lineage(暂不写入,schema 先冻结)────────────────────
CREATE TABLE IF NOT EXISTS um_memory_promotions (
promotion_id CHAR(36) PRIMARY KEY,
user_id CHAR(36) NOT NULL,
candidate_id CHAR(36) NOT NULL,
attribute_id CHAR(36) NULL,
memind_candidate_id VARCHAR(64) NULL,
content_hash CHAR(64) NOT NULL,
promoted_at BIGINT NOT NULL,
revoked_at BIGINT NULL,
status ENUM('promoted', 'revoked') NOT NULL DEFAULT 'promoted',
KEY idx_um_promo_user (user_id, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+105
View File
@@ -0,0 +1,105 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://memind.local/schemas/timeline-item.v1.schema.json",
"title": "TimelineItem",
"description": "Temporal Recall 统一时间线条目 — 跨 Source 归一化模型",
"type": "object",
"required": [
"timeline_item_id",
"user_id",
"source",
"type",
"observed_time",
"title",
"content",
"importance",
"confidence",
"source_ref",
"status"
],
"additionalProperties": false,
"properties": {
"timeline_item_id": {
"type": "string",
"format": "uuid",
"description": "归一化后的 timeline 条目 ID"
},
"user_id": {
"type": "string",
"format": "uuid"
},
"source": {
"type": "string",
"enum": ["calendar", "chat", "meinput", "memory_v2", "email", "browser", "tasks", "git", "location", "notes"]
},
"type": {
"type": "string",
"enum": ["event", "todo", "decision", "mention", "commitment"]
},
"event_time": {
"type": ["string", "null"],
"format": "date-time",
"description": "事件计划/实际发生时间;未知时为 null"
},
"observed_time": {
"type": "string",
"format": "date-time",
"description": "首次被记录/输入/提到的时间"
},
"title": {
"type": "string",
"maxLength": 512
},
"content": {
"type": "string",
"maxLength": 8192
},
"importance": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "抽取/归一化置信度"
},
"recall_score": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "最终召回排序分"
},
"source_ref": {
"type": "string",
"maxLength": 512,
"description": "源系统引用,如 meinput:segment:<id>"
},
"participants": {
"type": "array",
"items": { "type": "string", "maxLength": 128 },
"default": []
},
"status": {
"type": "string",
"enum": ["planned", "completed", "mentioned", "unknown"]
},
"merged_from": {
"type": "array",
"items": { "type": "string", "maxLength": 512 },
"description": "Dedupe 合并前的 source_ref 列表"
},
"app_bundle_id": {
"type": ["string", "null"],
"description": "MeInput 来源 App"
},
"app_name": {
"type": ["string", "null"]
},
"metadata": {
"type": "object",
"additionalProperties": true
}
}
}