Ted Nelson 在 1960 年代提出 Xanadu 时,想象的不是今天这种靠 URL 跳转、页面会消失的 Web,而是一个永不删除、永不覆盖、每段内容都能追溯来源与归属的文档宇宙。这个愿景影响深远,却始终没有成为日常软件基础设施。Zed 创始人 Nathan Sobo 的判断是:Xanadu 长期缺少的不是存储能力,而是能够持续理解、组织、比较和维护信息关系的执行者;AI Agent 让这个条件第一次变得现实。
Xanadu 的难题,不只是“保存所有版本”
Git、数据库审计日志、对象存储的版本控制,早已能保存历史。真正困难的是让历史保持可用。
一份设计文档经过几十次修改后,系统至少需要回答几类问题:
- 当前结论来自哪些原始讨论、代码提交或外部资料?
- 某个段落为何被改写,谁做出了这个判断?
- 新任务与过去哪个决策、哪个失败实验有关?
- 当原文发生变化时,哪些派生结论需要重新检查?
传统系统通常把这些工作交给人:写链接、补注释、维护 wiki、在 PR 中解释上下文。人能做,但成本高,而且在项目赶进度时最先被牺牲。于是大量上下文虽然“存在”,却无法被可靠检索和理解。
Xanadu 所设想的 transclusion(内容引用而非复制)也指向同一个问题:信息不应因为被搬运、摘要或重写,就失去与原始出处的联系。仅有不可变数据还不够,系统还需要持续维护这些关联。
Agent 为什么改变了这个前提
AI Agent 的关键不在于它会生成文本,而在于它可以围绕一个目标调用工具、读取上下文、产出结构化结果,并在后续任务中继续利用这些结果。
放到知识系统里,Agent 可以承担过去难以规模化的“维护劳动”:
- 导入资料时抽取实体、主题、时间和出处;
- 为摘要、代码修改、设计决定建立可追溯的证据链;
- 发现新资料与旧结论冲突时,标记需要复核的派生内容;
- 在用户提问时,不只给出答案,还返回答案依赖的原文片段;
- 根据权限和项目边界,决定哪些资料可读、可引用、可执行。
这并不等于 Agent 自动成为事实机器。模型会误读、幻觉,也可能把相关性误当成因果关系。它的价值更适合被定义为:降低维护知识关联的边际成本,并把不确定性、来源和中间推理暴露给人审查。
从“聊天记录”升级为可验证的工作记录
许多团队已经在用 Agent,但对话结束后,过程往往只剩一段难以复用的聊天文本。要朝 Xanadu 的方向实践,应当把 Agent 的每一次重要产出视为一个带来源的工件,而不是一条孤立消息。
一个最小工件至少包含:
content:生成的结论、摘要或修改说明;sources:支撑结论的文件、提交、网页或段落标识;parent_ids:它基于哪些已有工件产生;agent和model:由谁、用什么模型生成;created_at:生成时间;status:草稿、已审阅、已废弃或需复核。
注意,“永不删除”不意味着把所有内容都暴露给所有人。生产系统仍然要处理隐私删除、访问控制、保留周期和密钥轮换。更现实的原则是:对允许保留的数据使用追加式记录;对必须删除的数据保留最小化、合规的删除证明,而不是继续保留敏感原文。
可以这样实践:给 Agent 输出加上来源账本
下面的示例没有调用特定模型 API,而是实现一个可运行的本地“来源账本”。它把 Agent 生成的工件及其来源记录到 SQLite,并能递归查看一条结论依赖的来源链。
将以下内容保存为 provenance.py 后运行 python provenance.py。实际接入 Agent 时,只需要用模型输出替换 content,并把检索命中的文件、提交哈希或文档片段填入 sources。
import json
import sqlite3
from datetime import datetime, timezone
DB = "agent_provenance.db"
def connect():
conn = sqlite3.connect(DB)
conn.execute(
"""
CREATE TABLE IF NOT EXISTS artifacts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content TEXT NOT NULL,
sources_json TEXT NOT NULL,
parent_ids_json TEXT NOT NULL,
agent TEXT NOT NULL,
model TEXT NOT NULL,
status TEXT NOT NULL,
created_at TEXT NOT NULL
)
"""
)
return conn
def add_artifact(conn, content, sources, parent_ids=None,
agent="research-agent", model="example-model", status="draft"):
cursor = conn.execute(
"""
INSERT INTO artifacts
(content, sources_json, parent_ids_json, agent, model, status, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
""",
(
content,
json.dumps(sources, ensure_ascii=False),
json.dumps(parent_ids or []),
agent,
model,
status,
datetime.now(timezone.utc).isoformat(),
),
)
conn.commit()
return cursor.lastrowid
def show_lineage(conn, artifact_id, depth=0, seen=None):
seen = seen or set()
if artifact_id in seen:
print(" " * depth + f"- artifact:{artifact_id} (cycle detected)")
return
seen.add(artifact_id)
row = conn.execute(
"SELECT content, sources_json, parent_ids_json, status FROM artifacts WHERE id = ?",
(artifact_id,),
).fetchone()
if row is None:
print(" " * depth + f"- artifact:{artifact_id} (missing)")
return
content, sources_json, parent_ids_json, status = row
print(" " * depth + f"- artifact:{artifact_id} [{status}] {content}")
for source in json.loads(sources_json):
print(" " * (depth + 1) + f"source: {source}")
for parent_id in json.loads(parent_ids_json):
show_lineage(conn, parent_id, depth + 1, seen)
if __name__ == "__main__":
conn = connect()
evidence_id = add_artifact(
conn,
"项目决策记录:配置文件采用追加式变更日志。",
sources=["docs/adr/004-append-only-log.md#decision"],
agent="human-authored",
model="n/a",
status="reviewed",
)
summary_id = add_artifact(
conn,
"Agent 摘要:追加式记录便于审计,但需要配套访问控制和保留策略。",
sources=["docs/adr/004-append-only-log.md#decision"],
parent_ids=[evidence_id],
agent="architecture-agent",
model="your-model-name",
)
show_lineage(conn, summary_id)
这个例子刻意把“内容”和“证据”分开存放。后续可以继续增加:来源内容哈希、文档版本号、引用片段范围、人工审批人,以及当来源更新时自动将下游工件标记为 needs_review 的任务队列。
采用时该把边界画清楚
把 Agent 接入知识和代码工作流时,建议先从低风险、可审阅的环节开始,例如会议纪要关联、变更说明草拟、内部文档问答和设计决策索引。每一项自动结论都应能回到具体来源,而不是只显示“模型认为”。
一份实用检查清单如下:
- Agent 输出是否保存了具体来源,而非只有链接标题?
- 来源变更后,系统能否找到受影响的摘要和决策?
- 人工是否能修正、否决或标记过时的关联?
- 权限检查是否发生在检索阶段,而不只是最终展示阶段?
- 是否区分了模型生成的推测、原始事实和人工确认的结论?
- 是否为删除请求、敏感信息和审计要求设计了独立流程?
Xanadu 的理想不是让机器替人记住一切,而是让信息在被引用、修改和再解释时仍然保有来处。Agent 提供了持续维护这种关系的执行能力,但能否形成可信系统,取决于产品是否把来源、版本、权限和人工判断当作一等数据,而不是生成答案后的装饰。