LightScientist:三层架构的 AI 科研系统
2026.04用简洁的三层架构支撑长期科研自动化,每层只做一件事。
背景与动机
AI Agent 做科研的核心矛盾:科研任务周期长、步骤多、需要持久会话和人机协作,但单体 Agent 很难同时做好"项目管控"和"具体执行"。
现有方案的常见问题:
- Prompt 无限增长:历史日志全塞进 context,很快就撑爆上下文窗口。
- 生命周期边界模糊:取消、挂起、恢复没有清晰的状态机,实现出来是一团乱麻。
- 项目管控与执行耦合:同一个 Agent 既要管阶段流转,又要读文件跑命令,难以单独测试或替换。
LightScientist 的设计目标很明确:用确定性的第一层管项目状态机,用LLM 辅助的第二层做调度决策,用持久会话的第三层做具体执行。三层之间通过清晰的数据结构通信,不回放历史日志。
核心设计:为什么要三层?
单层 Agent 本质上是一个不断增长的 for-loop:每一轮把历史塞进去,让模型决定下一步。项目周期一长,这种方式必然失效。
三层分离的核心收益:
| 层 | 性质 | 职责 |
|---|---|---|
| 第一层 | 确定性代码 | 项目状态机、阶段流转、交付验证 |
| 第二层 | LLM 辅助 | 监督当前阶段的所有 worker、做调度决策 |
| 第三层 | 持久会话 | 具体的 DeepAgent 执行:读文件、跑命令、写产物 |
每层之间的通信数据结构是刻意设计的,不传原始日志,只传结构化更新:
- 第一层 → 第二层:任务描述(目标、技能文件路径、要求输出路径)
- 第三层 → 第二层:状态更新(状态、进度计数、最终结果)
- 第二层内部:调度事件(只把有意义的变化送给 supervisor agent)
这样第二层的 supervisor agent 每次只收到一个增量事件,而不是全部历史。需要更多上下文时,supervisor 通过工具主动查询。
第一层:研究控制器(ResearchController)
第一层是纯确定性代码,不调用任何模型 API。它只做一件事:维护项目阶段状态机。
阶段表(Stage Table)
科研流程被建模为阶段(stage)的有向图,定义在阶段定义文件中:
idea.survey → idea.generate → idea.evaluate → idea.gate
↓
idea.probe_batch → idea.probe_collect → idea.gate
↓
experiment.setup → experiment.loop → experiment.analyze → experiment.gate
↓
paper.plan → paper.figure → paper.write → paper.review → done
每个阶段包含:
- 技能描述文件路径(第二层按需读取,不内联进 prompt)
- 要求输出的文件路径(第一层验证交付物时检查)
- 默认下一阶段
- 允许的下一阶段列表
- 是否需要人工审批
第二层如何请求转阶段——层间通信的关键机制
这是整个系统层间协作的核心:
- 第三层的 worker 完成当前工作后,调用"完成当前阶段"工具,建议下一个阶段。
- 第二层的 supervisor 收到 worker 完成事件后,决策是否结束当前阶段。
- 第二层调用"完成当前阶段"工具,向第一层交付阶段结果,并建议下一阶段。
- 第一层验证建议的下一阶段是否在允许列表中。验证通过才执行状态转移。
这个机制保证了:LLM 只能"建议"下一阶段,不能"擅自"推进项目。阶段转移的终极控制权在第一层确定性代码手里。
auto / manual 模式的状态转移
项目运行时可以选两种模式,决定 gate 阶段的行为:
auto 模式:gate 阶段自动通过,不需要人工审批。整个流程可以全自动运行。
auto 模式下的完整状态转移流程:
idea.probe_batch → idea.probe_collect → idea.gate(自动通过)
→ experiment.setup → experiment.loop(可多次循环)→ experiment.analyze → experiment.gate(自动通过)
→ paper.plan → paper.figure → paper.write → paper.review(自动通过)→ done
manual 模式:gate 阶段返回等待用户,CLI 发送回复后恢复。适合需要人工把关的关键决策点。
项目级记忆:PROCESS.md
第一层维护 PROCESS.md 作为精简的长期项目记忆。每个阶段成功交付后,第一层向 PROCESS.md 追加一条摘要(约 5-10 行)。后续阶段直接读 PROCESS.md,不需要回放全部日志。
这是三层分离的核心收益之一:prompt 大小不随项目周期增长。
暴露给下层的工具
第一层只暴露两个工具给第二层:
- 完成阶段工具:正常交付当前阶段结果。
- 请求用户决策工具:需要项目级人工判断时调用。
第二层可以建议 next_stage,但第一层验证后才生效。这避免了 LLM"擅自"推进项目阶段。
第二层:运行时监督器——LLM 辅助的控制层
第二层的搭建逻辑:控制循环本身是确定性的,LLM 只做调度决策。它监督当前阶段的所有 worker,决定何时启动新 worker、何时恢复挂起的 worker、何时结束当前阶段。
Worker 记录与事件队列
每个 worker 有一条完整的运行记录,包含:运行时唯一标识、所属任务、工作目标、状态、会话标识符、挂起模式、进度计数、工作区目录。
第三层发送的状态更新首先进入第二层的原始更新队列。第二层处理时分两步:
- 更新函数:更新 worker 记录和缓存结果。
- 事件过滤函数:判断是否有意义,决定是否创建调度事件入队。
过滤规则:普通进度更新只更新记录,不入调度队列。这避免了 supervisor 被每一步日志淹没。
调度 Agent
第二层的调度 agent 本身也是一个持久会话(复用第三层的运行机制),但它的工具是运行时管理工具集:获取任务信息、列出所有 worker、获取指定 worker 记录、启动新 worker(非阻塞发射)、恢复指定 worker、取消指定 worker、预定未来直接恢复 worker。
调度 agent 的输入是增量的:每次只接收一个调度事件,而不是全部历史。如果需要更多上下文,通过工具主动查询文件、worker 状态、阶段输出。
输出约定(一行文本):完成、失败、或继续。
设计约束
调度 agent 的 prompt 里刻意加入了行为约束:优先复用已有 worker,少取消,少创建并行 worker。
如何请求第一层转阶段
这是层间协作的核心流程:
- 第三层的 worker 完成当前工作后,调用"完成当前阶段"工具,建议下一个阶段。
- 第二层的 supervisor 收到 worker 完成事件后,决策是否结束当前阶段。
- Supervisor 调用"完成阶段"工具,向第一层交付阶段结果,并建议下一阶段。
- 第一层验证建议的下一阶段是否在允许列表中。验证通过才执行状态转移。
这个机制保证了:LLM 只能"建议"下一阶段,不能"擅自"推进项目。阶段转移的终极控制权在第一层确定性代码手里。
第三层:执行运行时——持久 DeepAgent 会话
第三层的搭建逻辑:每个 worker 是有状态的会话,不是一次性函数调用。使用 LangGraph 的持久化机制实现跨轮次会话保持。
持久化会话设计
关键设计:第三层不再是无状态的"调一次模型拿结果",而是有状态的会话。每次模型调用后,LangGraph 自动 checkpoint 会话状态。只要进程活着,同一个线程 ID 可以无限次恢复,会话状态不丢失。
Worker 生命周期状态
| 状态 | 含义 | 恢复方式 |
|---|---|---|
| 运行中 | 正在执行 | 不需要恢复 |
| 等待输入 | 需要外部输入才能继续 | LangGraph 中断机制 → 恢复时传入答案 |
| 后台挂起 | 主动挂起,稍后恢复 | 同一线程 ID 发普通消息 |
| 已完成 | 任务完成 | 不可恢复 |
| 已失败 | 执行失败 | 不可恢复 |
| 已取消 | 被上层取消 | 不可恢复 |
Worker 工具
每个 worker 处于一个受限的工作区,可以看到标准工具集:文件读写、搜索、任务管理、命令执行。
此外还有生命周期工具(仅 worker 自身可调用):请求输入、主动挂起、取消时整理交付。
关键设计决策
waiting vs background:两种挂起,两种恢复
这是整个系统最重要的设计决策之一。Worker 主动挂起时有两种语义完全不同的场景:
| 等待输入 | 后台挂起 | |
|---|---|---|
| 触发方式 | 请求外部输入 | 主动挂起,稍后检查 |
| 含义 | 现在就需要答案才能继续 | 工作已委托出去,稍后检查 |
| 恢复方式 | LangGraph 中断机制 → 恢复时传入答案 | 同一线程 ID 发普通消息 |
预定恢复工具 允许 supervisor 现在做决定、写好未来要发的消息,到时间后第二层直接 resume worker,不再先问 supervisor。
事件驱动的监督
第三层的每一次状态变化都通过状态更新向上传递,但第二层不会把所有更新都送给 supervisor agent。
第三层 状态更新
→ 第二层 更新 worker 记录
→ 第二层 过滤并创建调度事件
→ Supervisor 队列 supervisor 空闲时每次只处理一个事件
普通 running → running 的进度更新只更新记录,不入 supervisor 队列。Supervisor 不会被每一步日志淹没。
协作式取消
取消从第二层流入第三层,是协作式的:
- 第二层调用取消接口
- 第三层尝试让 worker 自己调用取消完成工具整理交付
- Worker 保留有用产物(交付记录、调试日志、工作区文件)
- 如果整理超时,运行时返回兜底结果
- 如果有正在运行的子进程,终止进程组
- 会话从运行时状态中删除,后续无法恢复
Python 线程不能被强制杀掉(不安全),所以取消是协作式的:先请求 worker 整理交付,超时后才做清理。
会话持久化与 LangGraph Checkpoint
第三层 DeepAgent 会话的持久化依赖 LangGraph 的 MemorySaver:
- 每次模型调用后,LangGraph 自动 checkpoint 会话状态
- 同一个
thread_id可以跨多次start/resume调用保持会话 interrupt()是 LangGraph 的原生机制,用于实现waiting
当前实现是纯内存版:进程结束后会话丢失。磁盘持久化是已知未完成项。
文档记忆而非向量记忆
LightScientist 刻意不使用向量数据库做长期记忆。原因:
- 科研项目的上下文是结构化的(阶段输出、实验记录、论文草稿)
- 文档比向量检索更可控、更易调试
PROCESS.md作为项目级记忆,agent-run.md作为 worker 级交付记录
每层 prompt 只注入当前需要的信息,而不是无限增长的对话历史。这是三层分离的核心收益之一。
研究工作流:阶段状态机
项目级流程:idea → experiment → paper → done
每个阶段有允许的下一阶段列表。跨 phase 的转移必须经过 gate 阶段:
idea.gate → experiment.setup (需要审批或 auto 模式)
experiment.gate → paper.plan (需要审批或 auto 模式)
paper.review → done (论文评审通过后结束)
auto 模式的状态转移
auto 模式下,gate 阶段自动通过,整个流程可以全自动运行:
idea.survey → idea.generate → idea.evaluate → idea.gate(自动通过)
↓
idea.probe_batch → idea.probe_collect → idea.gate(自动通过)
↓
experiment.setup → experiment.loop(可多次循环)→ experiment.analyze → experiment.gate(自动通过)
↓
paper.plan → paper.figure → paper.write → paper.review(自动通过)→ done
Phase 内的循环是自由的:例如 experiment.loop 可以多次执行,每次 supervisor 根据实验结果决定继续 loop 还是进入 experiment.analyze。
manual 模式的状态转移
manual 模式下,gate 阶段返回等待用户,CLI 发送回复后恢复:
idea.gate → 返回 waiting_user → CLI 发送 --reply y/n → 恢复执行
第二层可以建议下一阶段,但第一层验证后才生效。这避免了 LLM"擅自"推进项目阶段。
可观测性:事件流
LightScientist 有一个侧通道事件流,用于观察 Agent 行为(不驱动控制流)。
核心组件
- 事件数据结构:包含层、类型、消息、任务ID、worker ID、阶段、数据、时间戳
- 事件总线:事件分发器
- JSONL事件写入器:写入
.lightscientist/events.jsonl - 终端事件输出器:
--watch模式终端实时输出
事件覆盖
| 层 | 事件类型 |
|---|---|
| 第一层 | stage start / finish / transition、user decision |
| 第二层 | worker created / status、supervisor event / decision、scheduled resume、stall detected |
| 第三层 | session start/end、model call/output、tool call/result、waiting/background/cancelled |
使用示例
# 实时观察 Agent 行为
PYTHONPATH=src python -m esnext run "你的工作目录是什么" --agent --watch
PYTHONPATH=src python -m esnext research "复现某篇论文" --mode auto --stage experiment.setup --watch
事件流只观测行为,不暴露隐藏推理。这符合可观测性的基本原则:能看到 Agent 做了什么,但不需要看到每一次内部思考。
当前限制与未来工作
以下限制是刻意的,当前系统优先保证清晰的层边界,而不是更多功能:
- 第三层会话纯内存:进程结束即丢失,不支持跨进程恢复。未来可接入 LangGraph SqliteSaver 或 PostgresSaver。
- 顶层 CLI 不暴露通用会话恢复管理:恢复能力只在内层验证,不作为顶层产品接口。未来可暴露
list、resume <agent_id>等命令。 - 第一层是确定性代码,不是 LangGraph 控制器:当前第一层是纯 Python 状态机。未来可将其也 LangGraph 化,支持更复杂的 stage 流转逻辑。
- 项目记忆是文档式,不是向量式:当前用
PROCESS.md做项目级记忆。对于非常大的项目,未来可引入向量检索作为补充。
总结
LightScientist 的三层架构可以概括为:
CLI / 用户
→ 第一层(ResearchController,确定性)
→ 第二层(RuntimeSupervisor,LLM 辅助)
→ 第三层(ExecutionRuntime,DeepAgent 会话)
核心设计准则:
- 关注点分离:每层只做一件事,边界清晰,可独立测试和替换。
- 增量通信:不回放历史,只传递有意义的事件和摘要,prompt 大小不随项目周期增长。
- 生命周期管理:
waiting与background刻意区分,取消是协作式的。 - 可观测性:事件流独立,侧通道设计,不影响控制流。
- 文档优先的记忆:结构化文档(
PROCESS.md、阶段输出文件)而非向量检索。
项目地址:E:/LightScientist