Skip to content

事实与事件设计

Fact Layer 是四大真源之一,负责统一记录"发生了什么";本文同时涵盖外部事件如何统一抽象并进入 Fact Layer。 它不解释事实、不判断优先级、不维护议程,只保证每一条事实可追溯、不可变、保留原始上下文。 如本文与《产品与架构设计》冲突,以后者为准。 最后更新:2026-03-18


一、架构定位

在统一认知架构的六层体系中,Fact Layer 的位置如下:

text
Interaction Layer(接收消息、主动触达)

Fact Layer ─── 统一记录事实输入与动作结果

Person Layer ── 维护人物真源
Cognitive Core ─ 解释事实、统一决策

Concern Layer ── 持续事项状态机

Action Layer ── 渠道发送、工具执行

动作结果回写 Fact Layer

一句话定义:

Fact Layer 是系统对"世界发生了什么"的唯一记录层。所有判断、议程、人物更新都必须能追溯到某条 Fact。


二、与现有实现的映射

当前事实记录分散在三个集合中,迁移策略如下:

现有集合当前职责迁移方向
events (scripts/memory/event_store.py)通用事件流,含消息、审批、日程等升级为 Fact 主存储,字段对齐 Fact 数据模型
person_events (scripts/person/store.py)按人索引的事件(task_signal、concern_signal、dialog_signal)保留为按人索引视图,person_refs 指向 Fact 主记录
decision_log (scripts/concern/decision_log.py)系统决策记录(discovery、action)保留,归类为 action_result 类 Fact

关键变化

  • events 集合新增 fact_idfact_typeperson_refsraw_context 字段
  • person_events 不再独立存储事实本体,改为引用 fact_id
  • decision_log 的每条记录同步写入一条 action_result 类型的 Fact
  • log_event() 继续作为 Fact 写入 facade,内部直接对齐新模型

三、Fact 数据模型

统一结构

python
@dataclass
class Fact:
    fact_id: str            # 全局唯一,格式 fact_{date}_{seq}
    fact_type: str          # 见第四节分类
    source: str             # 来源标识(feishu / telegram / gitlab / system / scheduler)
    person_refs: list[str]  # 关联人物 person_id 列表
    content: dict           # 结构化事实内容
    raw_context: dict       # 原始上下文(保留完整 payload)
    timestamp: str          # 事实发生时间(ISO 8601)
    metadata: dict          # 扩展字段(trace_id、channel、session_type 等)

JSON 示例

json
{
  "fact_id": "fact_20260318_001",
  "fact_type": "dialog",
  "source": "feishu",
  "person_refs": ["p_zhangsan"],
  "content": {
    "direction": "in",
    "text": "支付接口联调卡住了,第三方那边没给回调地址",
    "msg_type": "text"
  },
  "raw_context": {
    "channel": "feishu",
    "channel_user_id": "ou_xxx",
    "msg_id": "om_xxx",
    "chat_id": "oc_xxx"
  },
  "timestamp": "2026-03-18T10:30:00+08:00",
  "metadata": {
    "trace_id": "tr_abc123",
    "session_type": "avatar"
  }
}

字段约束

字段必填说明
fact_id原子自增,格式 fact_{YYYYMMDD}_{seq:03d}
fact_type枚举值,见第四节
source事实来源渠道或系统标识
person_refs允许为空(如系统级定时触发可能无关联人)
content结构化内容,按 fact_type 不同有不同 schema
raw_context原始 payload,用于审计和回溯
timestamp事实发生时间,不是写入时间
metadata扩展字段,不影响核心逻辑

四、fact_type 分类

fact_type定义典型场景
dialog对话事实,含用户消息和系统回复飞书/TG 收到消息、系统发出回复
external_event外部系统事件飞书审批状态变更、GitLab merge、任务状态变更、日程变化
action_result系统动作结果concern 发送消息、升级通知、工具查询结果、回流报告
schedule_trigger定时触发记录定时 concern 到点、心跳扫描触发、日报生成
absence_detection缺席检测结果预期提测未发生、日报超时未提交、审批超 SLA 未处理

各类型 content 示例

dialog

json
{
  "direction": "in",
  "text": "帮我催一下张三提测",
  "msg_type": "text"
}

external_event

json
{
  "event_type": "task_status_change",
  "task_id": "task_001",
  "old_status": "开发中",
  "new_status": "已提测",
  "operator": "p_zhangsan"
}

action_result

json
{
  "action": "send_message",
  "target_person_id": "p_zhangsan",
  "channel": "feishu",
  "message_preview": "老板想了解提测什么时候能给?",
  "concern_id": "con_20260318_001"
}

schedule_trigger

json
{
  "schedule_id": "sch_daily_report",
  "schedule_name": "每日简报",
  "attempt": 1
}

absence_detection

json
{
  "expected_event": "gitlab_merge",
  "expected_by": "2026-03-18T18:00:00+08:00",
  "watch_id": "watch_001",
  "description": "张三承诺今日提测,截至 18:00 未检测到 merge 事件"
}

五、写入规则

谁写

写入方写什么示例
渠道入口(TG/飞书 Bot)dialog用户消息、系统回复
事件适配器(EventAdapter)external_event飞书 webhook、GitLab webhook
动作执行器(Action Layer)action_resultconcern 发消息、升级、回流
调度器(Scheduler)schedule_trigger定时 concern 触发
认知中枢(缺席检测)absence_detection预期事件未到达

什么时候写

  • 即时写入:消息收到时、事件到达时、动作执行完成时
  • 不延迟:Fact 写入不应等待后续判断完成
  • 先写 Fact,再做判断:这是整个链路的硬前提

不可变原则

Fact 一旦写入,不允许修改 contentraw_context

允许的操作:

  • 补充 metadata(如后续关联的 concern_id)
  • 标记 processed: true

禁止的操作:

  • 修改事实内容
  • 删除已写入的 Fact
  • 用后续判断结论反写事实字段

原始上下文保留

raw_context 必须保留足够信息用于回溯:

  • 渠道原始 payload(或关键字段)
  • 原始消息 ID
  • 原始用户标识
  • 触发链路标识(trace_id)

六、查询接口

核心查询维度

查询方式接口签名用途
按人查询query_facts(person_ref=, limit=)查某人相关的所有事实
按时间范围query_facts(since=, before=, limit=)查某段时间内的事实
按类型query_facts(fact_type=, limit=)查某类事实(如所有缺席检测)
按关联 concernquery_facts(concern_id=, limit=)查某个 concern 相关的所有事实
组合查询query_facts(person_ref=, fact_type=, since=)多条件组合
单条查询get_fact(fact_id)精确查询

接口约束

  • 默认按 timestamp 倒序
  • 返回结果不含 MongoDB _id,统一用 fact_id 标识
  • raw_context 默认不返回,需显式请求(减少传输量)
  • 聊天历史查询继续通过 get_chat_history() facade 提供

七、与其他层的关系

text
Fact Layer
  ├─→ Perception(感知层输入)
  │     事实到达 → 感知层理解 → 形成 Signal

  ├─→ Cognitive Core(判断依据)
  │     Signal + Fact + 当前状态 → 统一裁决

  ├─→ Person Layer(更新证据)
  │     对话/事件中的客观状态 → 更新 work_state 的事实依据

  ├─→ Concern Layer(推进证据)
  │     回复、业务数据变化 → advance_concern 的证据来源

  └─→ Memory(提炼来源)
        值得长期保留的事实 → 记忆提炼链路 → 经验/规则/偏好

各层与 Fact 的关系

读 Fact写 Fact说明
Interaction消息到达即写入 dialog
Perception读取事实用于理解,不改写事实
Cognitive Core读取事实用于判断,决策记录由 Action 回写
Person读取事实作为状态更新依据
Concern读取事实作为推进证据
Action动作结果回写 action_result
Memory读取事实用于长期提炼

八、存储策略

MongoDB 集合设计

主集合:facts(从现有 events 集合升级)

json
{
  "fact_id": "fact_20260318_001",
  "fact_type": "dialog",
  "source": "feishu",
  "person_refs": ["p_zhangsan"],
  "content": { ... },
  "raw_context": { ... },
  "timestamp": "2026-03-18T10:30:00+08:00",
  "metadata": { ... },
  "created_at": "2026-03-18T10:30:01+08:00"
}

索引建议

索引字段用途
idx_facts_idfact_id (unique)精确查询
idx_facts_timestamptimestamp DESC时间范围查询
idx_facts_type_tsfact_type + timestamp DESC按类型查询
idx_facts_person_tsperson_refs + timestamp DESC按人查询
idx_facts_source_tssource + timestamp DESC按来源查询
idx_facts_concernmetadata.concern_id按关联 concern 查询
idx_facts_tracemetadata.trace_id链路追踪

TTL / 归档策略

策略规则说明
热数据最近 30 天保留完整 content + raw_context
温数据30-90 天保留 contentraw_context 压缩归档
冷数据90 天以上仅保留 fact_id / fact_type / person_refs / timestamp 索引记录,全文归档到对象存储
dialog 类特殊处理与 owner 的对话永不归档老板对话是长期决策依据

归档操作由离线任务执行,不影响在线写入。


九、事件接入

外部系统事件如何统一抽象、路由,并最终进入 Fact Layer,供认知中枢进一步判断。 事件系统只负责事实进入系统,不直接拥有议程决策权。

9.1 目标

事件系统负责把外部变化转成系统内部可处理的统一事件。

典型来源:

  • 飞书 webhook
  • GitLab webhook
  • 业务系统推送

统一事件进入后,再由认知链路决定:

  • 更新人物状态
  • 写入记忆
  • 进入议程并创建 concern
  • 直接通知

9.2 统一结构

python
UnifiedEvent(
    source="feishu" | "gitlab" | "...",
    event_type="...",
    target_person_id="p_xxx" | None,
    payload={...},
    timestamp=...,
    event_id="...",
)

统一事件的关键不是平台字段,而是:

  • 事件是什么
  • 影响了谁
  • 系统要如何继续处理

9.3 处理流程

text
Webhook / 事件推送

EventAdapter
    ├─ 验签
    ├─ 去重
    └─ 转为 UnifiedEvent

EventRouter

Handler
    ├─ 补详情
    ├─ 找到相关 person_id
    ├─ 写入 Fact Layer(事实记录)
    └─ 由调用方显式 dispatch 到 awareness → 认知中枢链路

9.4 与人物档案的关系

人员相关事件进入后,统一写入人物体系:

  • 自动补齐或创建 people
  • 更新 person_work_state
  • 写入 person_events

这保证事件处理和对话识别人使用的是同一份人物主档。

9.5 与 Concern 的关系

以下事件通常会形成"值得进入议程"的强信号:

  • 任务逾期
  • 审批长时间无进展
  • 明确的阻塞、异常、升级信号

原则应收口为:

  • 事件只负责把事实送进系统
  • 是否持续跟进,交给统一认知中枢判断
  • 一旦正式进入议程,再由 Concern 承接持续推进

9.6 事件接入原则

  1. 外部事件统一转成 UnifiedEvent
  2. 事件影响到人时,先定位 person_id
  3. 需要持续推进的事件先进入议程判断,再由 Concern 承接
  4. 需要长期回忆的结论进入 Memory
  5. 需要实时响应但不持续跟进的事件直接通知或静默处理

十、当前原则

  1. 先写 Fact,再做判断 -- 所有输入先进入 Fact Layer,不跳过
  2. 事实不可变 -- 写入后不修改 contentraw_context
  3. 保留原始上下文 -- raw_context 保留足够信息用于回溯和审计
  4. 每条事实可追溯 -- 有明确的 sourcetimestamptrace_id
  5. 不解释、不判断 -- Fact Layer 只记录发生了什么,不决定它意味着什么
  6. 统一模型 -- 所有类型的事实用同一个 Fact 结构,通过 fact_type 区分
  7. 统一入口 -- log_event() / query_events() / get_chat_history() 只是 Fact Layer facade,不再代表独立旧模型
  8. 按人可查 -- person_refs 确保任何事实都能按人检索

Fact Layer 的职责可以概括成一句话:

让系统的每一个判断都能追溯到"到底发生了什么"。

Boss-AGI · 超级 AI 企业助理