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
@@ -0,0 +1,81 @@
# Calendar Source Adapter v0.2(接口冻结)
| 字段 | 值 |
|------|-----|
| 状态 | **DraftV0.2** |
| 实现 | `temporal-recall-service/adapters/calendar.mjs`V0.1 stub |
---
## 1. 职责
从 MeMind 日程系统(`scheduleService` / 未来 CalDAV)读取 **event_time 权威** 的 calendar events,归一化为 `TimelineItem`
与 MeInput/Chat 的区别:
| Source | event_time 来源 |
|--------|-----------------|
| Calendar | 结构化字段(权威) |
| MeInput/Chat | 文本抽取( hypothesis |
---
## 2. Adapter 签名(Frozen
```typescript
interface CalendarSearchContext {
userId: string;
retrieval: ContextPlan['retrievals'][0];
time: ContextPlan['time'];
temporalMode: ContextPlan['temporal_mode'];
}
searchCalendar(ctx: CalendarSearchContext): Promise<TimelineItem[]>;
```
---
## 3. 数据来源(计划)
| 优先级 | 来源 | 说明 |
|--------|------|------|
| P0 | `h5_schedules` / scheduleService API | MeMind 内置日程 |
| P1 | CalDAV 订阅 | 只读 sync |
| P2 | iOS EventKit export | 经 MeInput 伴侣 App |
---
## 4. 归一化规则
```json
{
"source": "calendar",
"type": "event",
"event_time": "<schedule.start_at>",
"observed_time": "<schedule.created_at>",
"title": "<schedule.title>",
"status": "planned",
"confidence": 0.98,
"source_ref": "calendar:schedule:<id>"
}
```
---
## 5. Context Planner 权重
当 query 含 `行程|会议|约会|日历|几点|安排` 时:
```json
"sources": { "calendar": 0.85, ... }
```
V0.2 启用 retrievals 中 `source: "calendar"`V0.1 在 planner 中跳过未实现源)。
---
## 6. V0.2 验收
1. 「我今天有什么安排?」→ Calendar items 排在 MeInput 前
2. Calendar event + MeInput 同事件 → Dedupe merge
3. `PLANNED_IN` 模式仅按 `event_time` 过滤
+151
View File
@@ -0,0 +1,151 @@
# Context Planner API v1
| 字段 | 值 |
|------|-----|
| 状态 | **FrozenV0.1** |
| 建议 Base URL | MeMind Portal 同域 `/api/v1/context/*` |
| 输出契约 | `schemas/context-plan.schema.json` |
---
## 1. 职责
将用户自然语言解析为 **ContextPlan AST**,决定:
- 需要哪些上下文域(User Model / Temporal Recall / Memory V2
- 多源检索权重与 expanded queries
- 时间范围与 Temporal Mode
**不做**单选 intent 分类。
---
## 2. `POST /v1/context/plan`
### 请求
```json
{
"query": "我昨天有什么重要的事情安排吗?",
"user_id": "a70ff537-8908-486e-9b6c-042e07cc25db",
"now": "2026-09-03T22:00:00+08:00",
"session_id": "optional-session-uuid",
"locale": "zh-CN",
"planner_level": "auto"
}
```
| 字段 | 必填 | 说明 |
|------|------|------|
| `query` | 是 | 用户原句 |
| `user_id` | 是 | 当前用户 |
| `now` | 否 | 解析相对时间的锚点,默认服务端当前时间 |
| `session_id` | 否 | 用于 Chat 上下文 |
| `planner_level` | 否 | `auto` / `cheap_only` / `semantic` |
### 响应
```json
{
"plan": {
"query_type": "personal_temporal_recall",
"temporal_mode": "AMBIGUOUS",
"time": {
"mention_range": {
"start": "2026-09-02T00:00:00+08:00",
"end": "2026-09-03T00:00:00+08:00"
},
"event_range": {
"start": "2026-09-02T00:00:00+08:00",
"end": "2026-09-03T00:00:00+08:00"
},
"relative_label": "yesterday"
},
"targets": [
{ "type": "commitment", "weight": 1.0 },
{ "type": "calendar_event", "weight": 0.85 },
{ "type": "todo", "weight": 0.9 }
],
"sources": {
"calendar": 0.85,
"chat": 0.80,
"meinput": 0.75,
"memory_v2": 0.45
},
"retrievals": [
{
"source": "meinput",
"query_type": "important_mentions",
"expanded_queries": ["重要", "安排", "会议", "明天", "截止"],
"weight": 0.75
},
{
"source": "chat",
"query_type": "commitments_and_decisions",
"expanded_queries": ["安排", "确认", "跟进", "记得"],
"weight": 0.80
}
],
"filters": { "importance_min": 0.6, "status": "any" },
"output": { "group_by": "importance", "dedupe": true, "timeline": true, "wide_recall": true },
"context_needs": {
"user_snapshot": "OPTIONAL",
"temporal_recall": "REQUIRED",
"memory_retrieval": "OPTIONAL"
},
"planner_meta": {
"level": "cheap_router",
"rule_hits": ["time:yesterday", "keyword:重要", "keyword:安排"],
"confidence": 0.82
}
}
}
```
### 错误
| HTTP | code | 说明 |
|------|------|------|
| 400 | `invalid_query` | 空 query |
| 401 | `unauthenticated` | 未登录 |
| 422 | `time_unparseable` | 时间词无法解析且无 fallback |
---
## 3. 解析管线(实现约束)
```
1. deterministic time parser → mention_range / event_range / relative_label
2. keyword rule matcher → sources 权重 + rule_hits
3. target inference (rules) → targets[]
4. merge → ContextPlan
5. [v0.2] semantic planner patch → 仅 cheap_router confidence < 0.6 时
```
V0.1 **默认 `cheap_only`**`semantic` 返回 `501 not_implemented`
---
## 4. 与 Task Intent Router 边界
| 组件 | 用途 |
|------|------|
| `chat-intent-router` | 聊天 → Agent 升级、tool 路由 |
| **Context Planner** | 回答需要哪些**检索上下文** |
Context Planner **不决定**是否启动 Agent Run。
---
## 5. Runtime 集成
Agent 每轮开始前:
```
plan = POST /v1/context/plan { query: user_message }
if plan.context_needs.user_snapshot != SKIP → load snapshot
if plan.context_needs.temporal_recall != SKIP → POST /v1/temporal-recall/query { plan }
if plan.context_needs.memory_retrieval != SKIP → memory-v2 retrieve
```
V0.1 Runtime hook 可仅在 `temporal_recall = REQUIRED` 时启用。
+373
View File
@@ -0,0 +1,373 @@
# RFC: Temporal Recall & Context Planner v0.1
| 字段 | 值 |
|------|-----|
| 状态 | **FrozenV0.1** |
| 日期 | 2026-09-03 |
| 关联 Schema | `schemas/timeline-item.schema.json`, `schemas/context-plan.schema.json` |
| 关联 API | `docs/api/temporal-recall-v1.md`, `docs/api/context-planner-v1.md` |
| 前置 RFC | `docs/rfc/user-model-v0.1.md` |
---
## 1. 摘要
MeMind 需要一套独立于 User Model 与 Memory V2 的 **Temporal Recall** 能力,回答:
> **某个时间范围内,发生了什么?**
这与 User Model(「你是谁 / 你现在关注什么」)正交,是第四条一级能力域。
**V0.1 目标:** 跑通 `User Query → Context Planner → Multi-source Retrieval → Timeline → Rank → Answer`,数据源仅 **MeInput + Chat**Calendar / Memory V2 作为可选低权重源或 v0.2 接入。
**核心架构转变(Frozen):**
```
Intent Classification(单选 intent → 不再是 Temporal 问题的核心
Context Planning(多源 Query Plan → 新核心
Retrieval Robustness(宽召回 + Rank → 优于 Intent Precision
```
---
## 2. 四个能力域(Frozen
| 域 | 回答 | 典型问题 |
|----|------|----------|
| **User Model** | 我是谁、我现在关注什么 | 「帮我写代码时用什么风格?」 |
| **Memory V2** | 我过去明确发生过 / 决定过什么 | 「我们上次定的架构是什么?」 |
| **Temporal Recall** | 某个时间范围内发生了什么 | 「我这周有什么重要的事?」 |
| **Runtime Context** | 当前这一轮要用哪些上下文 | Session 内 tool / page / task 状态 |
```
User Query
Context Planner
┌─────────────┼─────────────┐
▼ ▼ ▼
User Model Temporal Recall Memory V2
「你是谁」 「发生了什么」 「曾经记住什么」
│ │ │
└─────────────┼─────────────┘
Agent Runtime
```
### 2.1 组合策略(Frozen
| 问题类型 | User Model | Temporal Recall | Memory V2 |
|----------|------------|-----------------|-----------|
| 普通聊天 | REQUIRED | SKIP | SKIP |
| 时间范围问题 | OPTIONAL | REQUIRED | OPTIONAL |
| 历史决策 | OPTIONAL | OPTIONAL | REQUIRED |
| 复杂综合 | OPTIONAL | REQUIRED | REQUIRED |
每项取值:`REQUIRED | OPTIONAL | SKIP`
---
## 3. 架构铁律(Frozen
1. **Temporal 问题不做单选 intent 分类。** 输出 Query Plan,允许多源并行检索。
2. **时间词用确定性 parser,不交给 LLM 猜。**(今天 / 昨天 / 这周 / 最近三天 …)
3. **强约束词给 source 加权,不硬路由。**(行程→Calendar 高权重;原话→MeInput 高权重)
4. **模糊语义才走 LLM semantic expansion。**(「重要的事」「需要注意的」)
5. **Timeline Item 必须区分 `event_time` 与 `observed_time`。**
6. **宽召回优于窄 intent。** 误解 20% 仍应能召回正确证据。
7. **Temporal Recall 不写入 User Model Graph。** 它是检索视图,不是画像。
8. **Temporal Recall 不默认写入 Memory V2。** 长期记忆仍走 UMS → Candidate → Memory 链。
9. **Answer 必须基于 ranked timeline items,禁止 LLM 无证据自由发挥。**
10. **Dedupe 跨源合并同一事件**Calendar + MeInput + Chat 三条 → 一条)。
---
## 4. 核心组件
### 4.1 Context PlannerPersonal Query Planner
**职责:** 决定「这句话需要哪些上下文」,而非「这句话属于哪个 intent」。
**双通道解析(Frozen):**
```
User Query
┌─────────┴──────────┐
▼ ▼
deterministic parser semantic parser
(rule / small model) (LLM, 仅复杂 query)
│ │
time / keywords / targets / ambiguity /
explicit entities implied meaning
│ │
└─────────┬──────────┘
Context Plan Merge
ContextPlan AST
```
**Level 1 — Cheap Router~80% 问题):** 无 LLM 或小模型;检测时间范围、明显 Calendar 词、明显原文请求、是否历史问题。
**Level 2 — Semantic Planner** 仅复杂 query 启用 LLM(多时间引用、未完成承诺追踪、跨源条件过滤)。
### 4.2 Temporal Recall Service
**职责:** 按 Context Plan 并行检索各 Source Adapter,归一化为 Timeline ItemDedupe + Rank,返回 Recall Bundle。
```
Context Plan
┌────────────┼─────────────┐
▼ ▼ ▼
Calendar Chat MeInput
(v0.2+) (v0.1) (v0.1)
│ │ │
└────────────┼─────────────┘
Timeline Normalizer
Dedupe
Recall Ranker
Recall Bundle
```
### 4.3 Source Adapter 边界
每个 Source **只暴露 Timeline Item 或 Evidence Envelope**Temporal Recall **不得** JOIN Source 内部表(与 UMS 铁律一致)。
| Source | V0.1 | Adapter 入口 |
|--------|------|--------------|
| MeInput | ✅ | `GET /v1/evidence/export`expression_segment |
| Chat | ✅ | Portal session messages / agent runs |
| Memory V2 | 低权重可选 | `memory-v2` time-bounded search |
| Calendar | ❌ v0.2 | 待定 |
---
## 5. ContextPlan ASTFrozen
`schemas/context-plan.schema.json`。核心字段:
```json
{
"query_type": "personal_temporal_recall",
"time": {
"mention_range": { "start": "...", "end": "..." },
"event_range": null
},
"temporal_mode": "OCCURRED_IN | MENTIONED_IN | PLANNED_IN | CREATED_IN | DUE_IN | AMBIGUOUS",
"targets": [
{ "type": "calendar_event", "weight": 0.8 },
{ "type": "commitment", "weight": 1.0 },
{ "type": "todo", "weight": 0.9 }
],
"sources": {
"calendar": 0.85,
"chat": 0.80,
"meinput": 0.75,
"memory_v2": 0.45
},
"retrievals": [
{
"source": "meinput",
"query_type": "important_mentions",
"expanded_queries": ["会议", "安排", "明天", "截止"],
"weight": 0.75
}
],
"filters": {
"importance_min": 0.6,
"status": "unresolved"
},
"output": {
"group_by": "importance",
"dedupe": true,
"timeline": true,
"wide_recall": true
},
"context_needs": {
"user_snapshot": "OPTIONAL",
"temporal_recall": "REQUIRED",
"memory_retrieval": "OPTIONAL"
}
}
```
### 5.1 六个解析槽位
| 槽位 | 含义 | 示例 |
|------|------|------|
| `time_scope` | 绝对时间范围 | yesterday → `[start, end)` |
| `target` | 要找什么类型 | event, schedule, todo, commitment |
| `operation` | 检索后操作 | retrieve / retrieve_and_summarize |
| `importance` | 重要性过滤 | important / any |
| `sources` | 多源权重(非单选) | calendar:0.85, chat:0.80 |
| `temporal_semantics` | 时间语义 | occurred_or_planned / ambiguous |
### 5.2 Temporal ModeFrozen
| Mode | 语义 | 示例问题 |
|------|------|----------|
| `OCCURRED_IN` | 事件发生在该范围 | 「我昨天做了什么?」 |
| `MENTIONED_IN` | 在该范围内被提到 | 「我昨天说过要做什么?」 |
| `PLANNED_IN` | 计划在该范围发生 | 「我今天有什么安排?」 |
| `CREATED_IN` | 在该范围内创建/记录 | 「我上周定了哪些事?」 |
| `DUE_IN` | 截止 / 应完成在该范围 | 「这周五之前要交什么?」 |
| `AMBIGUOUS` | 宽召回两种语义 | 「我昨天有什么重要的事?」 |
`AMBIGUOUS`**禁止追问为默认行为**;并行查 occurred + mentioned,分组回答。
---
## 6. Timeline Item 统一模型(Frozen
`schemas/timeline-item.schema.json`
```json
{
"timeline_item_id": "uuid",
"user_id": "uuid",
"source": "calendar | chat | meinput | memory_v2",
"type": "event | todo | decision | mention | commitment",
"event_time": "2026-09-03T15:00:00+08:00",
"observed_time": "2026-09-02T20:30:00+08:00",
"title": "与张总签合同",
"content": "明天下午三点去签合同",
"importance": 0.82,
"confidence": 0.91,
"recall_score": 0.76,
"source_ref": "meinput:segment:...",
"participants": ["张总"],
"status": "planned | completed | mentioned | unknown",
"merged_from": ["calendar:...", "chat:...", "meinput:..."]
}
```
**关键区分:**
- `event_time` — 事情何时发生 / 计划发生
- `observed_time` — 何时被记录 / 输入 / 提到
例:9 月 2 日输入「明天下午三点签合同」→ `observed_time=9/2`, `event_time=9/3 15:00`
---
## 7. Recall ScoreFrozen
```
recall_score =
source_quality
× temporal_match
× semantic_match
× importance
× extraction_confidence
```
| 分数区间 | 展示策略 |
|----------|----------|
| ≥ 0.75 | 主答案直接展示 |
| 0.50 ~ 0.75 | 次要信息 / 「可能相关」 |
| < 0.50 | 默认不展示 |
---
## 8. Query ExpansionFrozen
**禁止**用用户原句单一 embedding 搜所有库。
Planner 为每个 source 生成 **expanded_queries**
| Source | 扩展策略 |
|--------|----------|
| Calendar | 时间 filter 为主,keyword 为辅 |
| Chat | 约 / 安排 / 明天 / 会议 / 确认 / 跟进 / 截止 |
| MeInput | 任务词 / 时间表达 / action verbs |
| Memory V2 | commitment / task / decision |
---
## 9. Dedupe 规则(V0.1 简化)
同一 `event_time` ±15min + 语义相似(title/content embedding 或 keyword overlap > 0.7)→ merge。
Merge 后保留最高 `source_quality``merged_from` 记录各源。
---
## 10. 与 User Model 的关系
| | User Model | Temporal Recall |
|--|------------|-----------------|
| 时间性 | 慢变 / 半静态 | 强时间索引 |
| 问题 | 「我是谁」 | 「那时发生了什么」 |
| 存储 | Graph + Snapshot | 检索视图(不持久化画像) |
| 写入 | Evidence → Signal → Candidate | 只读各 Source |
| Session 加载 | 默认 REQUIRED | 按 Planner 按需 |
Planner 可将 User Snapshot 作为 **ranking prior**(例如已知 active_projects 加权相关 timeline items),但 Temporal Recall 不反向写 Graph。
---
## 11. V0.1 范围
### 做
- [ ] ContextPlan JSON Schema + Timeline Item Schema
- [ ] Deterministic time parserzh-CN 相对时间)
- [ ] Rule-based source weight(强约束词表)
- [ ] Cheap RouterLevel 1
- [ ] MeInput Source Adapterevidence export
- [ ] Chat Source Adaptersession messages
- [ ] Timeline Normalizer + Dedupe + Rank
- [ ] `POST /v1/temporal-recall/query` API
- [ ] Agent Runtime hook`context_needs.temporal_recall = REQUIRED` 时注入 Recall Bundle
### 不做(V0.1
- Calendar / Email / Browser / Git / Location
- LLM Semantic PlannerLevel 2)— 仅预留接口
- Memory V2 深度集成(可选低权重 stub)
- 用户追问 clarification UI(默认宽召回 + 分组)
- Timeline 持久化索引库(V0.1 实时检索;V0.2 考虑 `memind_timeline` 物化)
---
## 12. 开发顺序(建议)
1. Schema + API 契约冻结(本文档)
2. Deterministic time parser + rule keyword weights
3. MeInput adapter(复用 evidence export
4. Chat adapter
5. Normalizer + Ranker + Dedupe
6. Cheap Router → ContextPlan
7. Temporal Recall API
8. Runtime Context Planner hook
9. v0.2Calendar adapter + Semantic Planner + 物化 timeline index
---
## 13. 命名约定(Frozen
| 避免 | 使用 |
|------|------|
| Intent Layer(用于 Temporal | **Context Planner** |
| `intent: query_schedule` | `ContextPlan.sources` 多源权重 |
| 「猜测用户唯一意图」 | 「宽召回 + Rank」 |
| Personal Intent Classification | **Personal Temporal Recall** |
Task Execution Intent(聊天→Agent 升级)仍走现有 `chat-intent-router`**不与 Context Planner 混用**。
---
## 14. 验收标准(V0.1
1. 「我昨天有什么重要的事?」→ 返回 grouped timelineoccurred + mentioned),不全为空
2. 「我这周输入过什么关于 MeInput 的?」→ MeInput source 高权重命中
3. 同一事件在 Chat + MeInput 重复出现 → Dedupe 为一条
4. `event_time` / `observed_time` 过滤行为符合 Temporal Mode
5. 无 ContextPlan 时 Agent 不调用 Temporal Recall(不误触发)
+190
View File
@@ -0,0 +1,190 @@
# Temporal Recall API v1
| 字段 | 值 |
|------|-----|
| 状态 | **FrozenV0.1** |
| 建议 Base URL | MeMind Portal 同域 `/api/v1/temporal-recall/*` |
| 输入 | `ContextPlan` 或简化 query |
| 输出 | `TimelineItem[]` + 分组元数据 |
---
## 1. 职责
按 Context Plan **并行检索**多源,归一化、Dedupe、Rank,返回 Personal Timeline Recall Bundle。
**回答:** 「某个时间范围内发生了什么?」
---
## 2. `POST /v1/temporal-recall/query`
### 方式 A — 传入完整 Plan(推荐)
```json
{
"plan": { "...ContextPlan..." },
"limit": 50
}
```
### 方式 B — 快捷 query(内部先调 Planner
```json
{
"query": "我这周有什么重要的事?",
"user_id": "a70ff537-8908-486e-9b6c-042e07cc25db",
"now": "2026-09-03T22:00:00+08:00",
"limit": 50
}
```
### 响应
```json
{
"query_type": "personal_temporal_recall",
"temporal_mode": "AMBIGUOUS",
"time_range": {
"start": "2026-09-01T00:00:00+08:00",
"end": "2026-09-08T00:00:00+08:00"
},
"groups": [
{
"label": "occurred_in_range",
"items": [
{
"timeline_item_id": "...",
"source": "meinput",
"type": "mention",
"event_time": null,
"observed_time": "2026-09-02T14:30:00+08:00",
"title": "MeInput 验证上屏",
"content": "MeInput验证上屏",
"importance": 0.71,
"confidence": 0.95,
"recall_score": 0.78,
"source_ref": "meinput:segment:...",
"status": "mentioned"
}
]
},
{
"label": "mentioned_or_planned",
"items": []
}
],
"items": [],
"stats": {
"sources_queried": ["meinput", "chat"],
"raw_count": 42,
"deduped_count": 38,
"returned_count": 25,
"elapsed_ms": 320
},
"plan": { "...echo ContextPlan..." }
}
```
`groups``temporal_mode=AMBIGUOUS` 时区分「实际发生」与「提到/安排」;否则 `items` 为 flat ranked list。
---
## 3. `GET /v1/temporal-recall/info`
```json
{
"schema_version": 1,
"supported_sources": ["meinput", "chat"],
"planned_sources": ["calendar", "memory_v2", "email", "browser", "tasks"],
"temporal_modes": ["OCCURRED_IN", "MENTIONED_IN", "PLANNED_IN", "CREATED_IN", "DUE_IN", "AMBIGUOUS"],
"default_limit": 50,
"max_limit": 200
}
```
---
## 4. Source Adapter 契约
每个 adapter 实现:
```typescript
interface TemporalSourceAdapter {
source: 'meinput' | 'chat' | 'calendar' | 'memory_v2';
search(ctx: {
userId: string;
retrieval: ContextPlan['retrievals'][0];
time: ContextPlan['time'];
temporalMode: ContextPlan['temporal_mode'];
}): Promise<TimelineItem[]>;
}
```
### MeInput AdapterV0.1
- 调用 `GET /v1/evidence/export`MeInput Cloud
- 过滤 `expression_segment`,按 `occurred_at` 映射 `observed_time`
- 规则抽取 `event_time`(「明天下午三点」→ 解析为绝对时间)
- `source_ref = meinput:segment:{evidence_id}`
### Chat AdapterV0.1
-`h5_agent_runs` + session messages(用户可见范围)
- `observed_time = message.created_at`
- commitment/todo 规则抽取
---
## 5. Rank 公式
```
recall_score =
source_quality(source)
× temporal_match(item, plan.time, plan.temporal_mode)
× semantic_match(item, expanded_queries)
× importance(item)
× confidence(item)
```
| 阈值 | 展示 |
|------|------|
| ≥ 0.75 | 主答案 |
| 0.50 ~ 0.75 | 次要 |
| < 0.50 | 丢弃 |
---
## 6. Dedupe
- 时间:`|event_time_a - event_time_b| < 15min` 或同日 + 同类
- 语义:title/content keyword overlap > 0.7 或 embedding cosine > 0.85v0.2
- Merge → 保留最高 `source_quality`,填充 `merged_from`
---
## 7. 认证
| 调用方 | 认证 |
|--------|------|
| Agent Runtime | 用户 sessionToken |
| 内部 Worker | `TEMPORAL_RECALL_TOKEN`(可选) |
---
## 8. V0.1 不做
- Calendar / Email / Browser adapter
- 持久化 timeline 索引表
- LLM 答案生成(只返回 structured timelineAnswer 层在 Agent
---
## 9. 错误码
| HTTP | code | 说明 |
|------|------|------|
| 400 | `invalid_plan` | Plan schema 校验失败 |
| 401 | `unauthenticated` | 未登录 |
| 503 | `source_unavailable` | MeInput export 不可用 |
| 504 | `recall_timeout` | 并行检索超时(默认 5s |