Files
nofx/docs/architecture/AGENT_ARCHITECTURE_STATUS.zh-CN.md
2026-04-25 16:18:45 +08:00

28 KiB
Raw Blame History

NOFXi Agent 架构现状说明

本文档说明当前 nofxi-dev/agent 的整体设计、关键执行链路、内存与快照机制、skill 协作方式,以及这套实现是怎么逐步收成现在这个样子的。

适用范围:

  • nofxi-dev/agent
  • 与 Agent 强相关的 tool / store / workflow / frontend chat 行为

1. 当前目标

当前 Agent 的目标不是“单轮问答机器人”,而是一个可持续管理配置、跨多轮续接、可在多个对象之间切换上下文的任务型 Agent。

核心要求有 4 个:

  1. 用户一句模糊话,也要先判断是在继续当前任务、切回旧任务、开新任务,还是取消。
  2. model / exchange / trader / strategy 四个 management skill 都要能被统一路由、统一理解、统一执行。
  3. 用户说的是名称,系统执行的是 ID中间必须有稳定映射。
  4. 快照、当前引用对象、最近对话、执行状态,不能只是“存着”,而要真的被 LLM 当成决策输入。

2. 顶层执行链路

当前主入口在:

整体顺序是:

  1. tryLLMIntentRoute
  2. tryStatePriorityPath
  3. tryInstantDirectReply
  4. tryReadFastPath
  5. tryHardSkill
  6. runPlannedAgent

也就是说,现在系统优先做“统一语义判断”,然后才看 active flow、direct reply、hard skill 和 planner。

2.1 统一语义网关

顶层语义网关在:

LLM 目前先判断一条消息属于哪种 intent

  • continue_active
  • resume_snapshot
  • start_new
  • cancel
  • instant_reply

如果是 start_new,再继续判断 route

  • skill
  • workflow
  • planner

这层的意义是:先判断“这句话在和哪个上下文说话”,再判断“具体怎么做”。

2.2 顾问式系统前缀

现在统一语义网关和 Planner 共享同一份顾问式系统前缀,而不再只是“路由器”或“计划器”口吻。

这份前缀的核心基调是:

  • 你是 NOFX 的核心智能中枢 NOFXi
  • 你的首要目标不是盲目执行命令
  • 你需要以资深量化顾问身份,确保每一次配置都正确、安全且符合逻辑
  • 当用户遇到问题时,你要结合当前状态和平台边界主动诊断,并给出具体解决方案

统一前缀已经抽成共享 helper

当前已注入:

  • 顶层统一语义网关
  • one-pass 统一语义网关
  • planner
  • replanner

这样做的目的,是让“理解用户意图”和“后续制定执行计划”都遵守同一套顾问式人格、安全边界和诊断基调。

2.3 兼容式 One-Pass JSON 网关

为了减少 router -> classifier -> extractor 的串行 LLM 调用次数,当前已经在统一语义网关前面接入了一个兼容式 One-Pass JSON 网关:

它会在一次调用里尝试同时输出:

  • intent
  • target_skill
  • target_snapshot_id
  • extracted_fields
  • need_planner_help

典型返回形状如下:

{
  "intent": "continue_active",
  "target_skill": "trader_management:create",
  "target_snapshot_id": "draft_7788",
  "extracted_fields": {"leverage": "100"},
  "need_planner_help": false
}

当前它采用的是“兼容式接入”,不是硬切:

  1. 先尝试 one-pass 网关
  2. 如果输出不可用,回退到原有统一语义网关
  3. 原有 planner / workflow / hard skill 分层继续保留

这意味着系统已经开始减少多次串行 LLM 往返,但不会因为一次新网关输出失误就直接把旧链路全部推翻。


3. 状态优先层

状态优先层在:

它负责优先处理这些场景:

  • 是否要恢复挂起任务
  • 是否已有 active workflow
  • 是否已有 active skill session
  • 是否已有 active execution state

这层不是重新发明语义,而是消费上层已经决定好的“继续当前 / 切回旧快照 / 新开任务”。

如果当前有 active skill session它会进一步进入

  • resolveSkillSessionTurn
  • classifySkillSessionIntentWithLLM
  • extractSkillSessionFieldsWithLLM

如果当前是 execution state则会尝试

  • bridgeExecutionStateToSkillSession

这一层的目标是把“planner 等待态”或者“执行等待态”桥接成真实 skill session而不是让后续执行时丢上下文。


4. 四层上下文

当前 Agent 在规划和路由时,主要使用四层上下文:

  1. Current reference summary
  2. Execution state JSON
  3. Recent conversation
  4. Task state

它们现在被显式写进 planner prompt相关逻辑在

当前优先级是:

  1. 当前引用对象
  2. 当前执行状态
  3. 最近对话
  4. 持久化压缩背景

这解决的是“明明刚才就在说某个 trader / strategy但后面又像不认识了一样”的问题。


5. CurrentReferences、快照和持久引用记忆

5.1 CurrentReferences

CurrentReferences 表示当前锁定的对象,例如:

  • 当前 trader
  • 当前 model
  • 当前 exchange
  • 当前 strategy

它会进入:

  • router prompt
  • planner prompt
  • active flow classifier
  • flow extraction

相关读取点包括:

5.2 Suspended snapshots

挂起任务快照用于:

  • 打断当前流程
  • 以后恢复到具体旧流程
  • 让 LLM 在“刚才那个”“前面那个”这种模糊指代下仍能选对上下文

快照信息会进入:

  • top-level router
  • active flow classifier
  • flow extraction

5.3 Persistent reference memory

现在 CurrentReferences 不只存在于 ExecutionState 里。

新增了持久引用记忆:

核心函数:

  • semanticCurrentReferences
  • semanticReferenceHistory
  • rememberReferencesFromToolResult

这层的作用是:

  • 即使 ExecutionState 被清掉
  • 当前对象记忆仍可延续
  • 后续 follow-up 仍能命中“当前 trader / 当前 strategy”

5.4 DB 活性校验

持久化记忆不能被 100% 信任,因为真实对象可能已经被前端或其他入口删除。

因此现在在真正执行实体更新前,会先做一次轻量级活性校验:

  • 若当前 TargetRef 指向的对象已经不存在
  • 不再盲目继续执行
  • 会清掉失效引用,并要求用户重新指定目标对象

这解决的是:

  • Agent 记得“当前策略 A”
  • 但真实数据库里的 Strategy A 已被网页前端删掉
  • 后续再说“就按当前策略来”时,不会直接拿悬空 ID 去执行

6. Skill 体系

当前正式 management skill 有四个:

  • trader_management
  • exchange_management
  • model_management
  • strategy_management

Skill 定义的正式来源在:


7. 统一 skill 上下文:单一真源

之前的问题是:

  • skill JSON 有一份简介
  • router prompt 又手写一份
  • workflow prompt 再手写一份
  • classifier / extraction 又各有自己的上下文说明

这会导致不同层看到的 skill 描述不一致。

现在已经收成统一 helper

关键函数:

  • buildSkillDefinitionSummary
  • buildSkillDependencySummary
  • buildSkillForbiddenSummary
  • buildManagementSkillContext

7.1 buildManagementSkillContext

这是现在 management skill 上下文的统一入口。

它输出两类信息:

  1. 四个 management skill 的简要说明
  2. 四个 management skill 的负向约束
  3. 当前 active skill 的依赖说明

例如对于 trader_management:create,它现在会明确告诉模型:

  • 创建 trader 依赖已启用交易所
  • 依赖已启用模型
  • 依赖可用策略
  • 修复这些依赖时,仍属于 trader create 的主流程

同时它也会告诉模型一些“不能做什么”的边界,例如:

  • model_management 不负责测试连接和诊断上游错误
  • exchange_management 不负责行情和交易执行
  • strategy_management 只负责模板管理,不负责直接运行

7.2 已接入的层

这个统一 helper 现在已经接入:

也就是说,现在 router、workflow、classifier、extraction、planner 使用的是同一套 management skill 说明。


7.3 语义就绪检查

仅仅把消息路由到某个 skill 还不够,还要判断这条消息在“语义上是否已经准备好进入执行层”。

这层现在在:

关键点:

  • evaluateHardSkillCandidate
  • semanticReadinessMissingSlots
  • skillSemanticReadinessSummary

设计目标是:

  • 如果 LLM/规则已经判到某个 skill/action
  • 但当前消息还明显缺少核心必填字段
  • 就不要直接往 hard skill 执行层掉

例如:

  • 用户说“帮我新建一个模型配置”
  • 但还没有 provider / api_key / custom_model_name

这时系统会优先把控制权交给 planner / ask_user而不是直接返回程序式“缺字段”提示。

这样做的价值是:

  • 减少生硬的 hard skill 报错
  • 让交互更像“LLM 正在推进表单”
  • 避免路由下坠到执行层后再回滚

8. Active flow 内部是怎么继续的

如果顶层判断是 continue_active,当前消息不会直接执行 tool而是进入当前 flow 的续接过程。

进一步地,当前只要已经进入某个 active skill:action,系统会优先沿着当前 action 继续推进。 旧的 detectManagementAction / has*Patch / detect*Patch 这类文本 heuristics 仍然保留,但已经更明确地退到:

  1. 没有 active skill session 时,用于粗路由和兜底识别
  2. active skill session 内,只有在 session/patch 都无法给出结果时,才作为 fallback 参与判断

这保证了:

  • 先尊重已经由 LLM 和状态机确定下来的当前 flow
  • 再在必要时使用旧 heuristics 补洞
  • 避免“已经在当前 skill 里了,却又被文本规则抢去改 action”的抖动

现在 active flow 的打断条件也更收紧了:

  • 单纯在当前 flow 里提到 model / exchange / strategy
  • 或者在补依赖时顺带提到其他 domain 名词

不再默认视为“跳到新任务”。

只有当消息整体更像一个新的顶层请求时,跨 domain 提及才会触发中断和重新路由。

8.1 Skill session 续接

相关逻辑:

主要分两步:

  1. classifySkillSessionIntentWithLLM
    • 判断是继续、取消、打断还是闲聊
  2. extractSkillSessionFieldsWithLLM
    • 把这条消息抽成结构化字段

然后把结构化字段 merge 回当前 skill session。

8.1.1 对话驱动式 skill 收集

对于高价值的多轮 management flow当前已经开始把“补槽”从代码猜测迁到 LLM 对话驱动器:

目前最先落地的是:

  • trader_management:create
  • trader_management:update_bindings
  • trader_management:configure_strategy
  • trader_management:configure_exchange
  • trader_management:configure_model
  • model_management:update / update_status / update_endpoint / update_name
  • exchange_management:update / update_status / update_name
  • strategy_management:update / update_name / update_prompt / update_config / activate / duplicate

这层的设计目标不是“让代码先把用户的话拆碎”,而是:

  1. 先由 LLM 理解当前这句话在当前 flow 里到底是什么意思
  2. 再按需披露当前 skill:action 的规则书
  3. 再按当前缺失槽位,动态注入最相关的资源列表
  4. 最后由代码校验结果并落地执行

当前 llmSkillConversationDriver 会显式拿到:

  • 当前 active skill:action 的 contract
  • 当前已收集字段
  • 当前缺失槽位
  • 最近一轮对话
  • 只和当前缺失槽位相关的资源列表(例如只缺 model 时,只注入模型列表)

它返回的核心结果是:

  • ready
  • question
  • extracted
  • needs_clarification
  • cancel

也就是说,现在 active skill 内部已经开始从:

  • 代码先猜字段
  • 模型后补救

迁移到:

  • 模型先理解当前回答的语义
  • 代码只做 guardrail 与执行

进一步地,执行层现在也开始优先消费 skillSession 里已经由 LLM 提取好的字段和目标对象。 只有当 session 中还没有对应值时,才会退回到旧的文本解析 fallback。

更具体地说,当前高频 management update 动作的执行顺序已经开始统一成:

  1. 先消费 session 中已经由 LLM 提取好的字段/patch
  2. session 仍为空,再看当前整句是否能直接形成结构化 patch
  3. 只有前两步都失败时,才退回到 update_field + 单字段值 这类旧文本猜测

这意味着旧的 detect... / parse... / pick... 路径仍然存在,但已经逐步退到真正的兜底层。

8.1.2 按需资源披露

对话驱动器不会每轮都把用户所有模型、交易所、策略全量塞进 Prompt。

现在这层已经改成:

  • exchange 才查并注入交易所列表
  • model 才查并注入模型列表
  • strategy 才查并注入策略列表
  • 某个槽位填完后,下一轮立即把对应资源列表从 Prompt 中移除

这就是“按需喂饭Just-In-Time Context Injection

  • 节省 token
  • 降低延迟
  • 避免注意力稀释
  • 减少无关资源对当前推理的干扰

8.2 Execution state 到 skill session 的桥接

如果当前是 planner / execution waiting 状态,会尝试桥接成 skill session

这解决的是:

  • planner 已经问到一半
  • 用户回复了字段
  • 但后续 hard skill 执行时又像“没收到”

现在系统已经能把 execution waiting 中收集到的信息投影回 skill session。

8.3 子任务成功后的父任务回流

现在快照不只是“可恢复存档”,还带有父任务信息。

相关结构在:

新增字段包括:

  • intent_id
  • parent_intent_id
  • resume_on_success
  • resume_triggers

构建点在:

执行成功后的回流点在:

这解决的是:

  • 用户本来在 trader_management:create
  • 中途去启用一个被禁用的交易所或模型
  • 子任务成功后,系统不再“断片”
  • 会自动恢复父任务上下文,并继续提示主流程剩余缺失项

因此现在的 suspended snapshot 已更接近“带返回指针的任务栈”。

8.4 取消时的任务栈回溯清理

如果用户在子任务中途说:

  • 算了
  • 不改了
  • 换话题

系统现在不会只取消当前子任务就结束,而是会检查栈里是否还有父任务挂起。

如果存在父任务,会明确追问:

  • 当前子任务已经取消
  • 之前的父任务是否还要继续
  • 或者是否“一并取消”

这样做是为了防止:

  • 父任务长期挂在栈底
  • 子任务被取消后无人接管
  • 最终形成僵尸任务和状态堆积

9. Trader create 为什么特殊重要

trader_management:create 是当前最复杂的 management flow 之一,因为它天然依赖另外三个 skill 的资源状态:

  • exchange
  • model
  • strategy

因此它不是一个封闭 skill而是一个“父 skill”。

用户在创建交易员时说:

  • 启用某个交易所
  • 换一个模型
  • 使用现有策略

这些都不应该默认被理解成新的平级 top-level 任务,而应优先理解成:

  • trader create 补齐依赖
  • 然后继续主流程

目前这一层的 prompt 级理解已经通过统一 skill dependency summary 接入,但执行层的“修复依赖后自动回流主流程”还需要继续补强。


10. 名称和 ID 的连接

用户说的是自然语言名称,比如:

  • test
  • DeepSeek AI
  • 高频做空策略
  • 白开水

执行层需要的是稳定 ID。

当前这层连接主要做在:

核心函数:

  • hydrateCreateTraderSlotReferences
  • findOptionByIDOrName

设计原则是:

  1. 用户层允许说名字
  2. 系统尽快解析出唯一对象
  3. 一旦唯一,就落成真实 ID
  4. 展示给用户时仍然优先显示友好名字

这解决的是“确认文案看起来正确,但真正执行又说缺字段”的问题。

10.1 歧义引用澄清

除了“名字映射到 ID”系统现在也开始处理“多个候选对象同名或近似”的情况。

相关逻辑在:

核心做法是:

  1. 若唯一命中,直接解析成 ID
  2. 若多个候选同时命中,不再静默选择
  3. 统一返回澄清问题,让用户明确要操作哪一个对象

这比“猜一个”更安全,也避免了对象误绑。


10.2 用户级串行化

同一个用户可能在网络卡顿或前端重发的情况下,几乎同时发出两条修改消息。

为了避免:

  • 两条消息并发进入同一个 active flow
  • extraction 结果交叉 merge
  • skillSession / ExecutionState 变成缝合状态

现在 thinkAndActthinkAndActStream 已经在用户维度上做了串行化处理。

也就是说:

  • 同一个 userID
  • 任意时刻只允许一条主消息进入 flow merge/execute 链路

这比只在单个 save* 调用上加锁更有效,因为它保护的是整条“读状态 -> 理解 -> merge -> 执行 -> 写状态”的事务链。


11. Tool 层约束

之前一个根问题是:上层像是“创建成功”了,但底层实际上没拿到完整必填字段。

现在部分 create 约束已经下沉到 tool 层:

当前明确加了必填约束的包括:

  • model_management:create
  • exchange_management:create

这意味着:

  • 不再只靠上层 prompt 判断“够不够建”
  • tool 自己也会拒绝缺关键字段的 create

这能防止“草稿像成功了,但对象其实是半残”的情况。

11.1 Tool 层安全硬隔离

安全不能只靠 Prompt。

因此现在 Tool 层已经补了两类后端硬边界:

  1. 敏感凭证永不明文返回
    说明:
  • toolGetModelConfigs
  • toolGetExchangeConfigs
  • 以及对应 create / update 响应

都会先走安全视图,再做一层递归敏感字段剥离。
也就是说,像 api_keysecret_keypassphrase、私钥这类字段,不会因为 LLM 被注入而通过 Tool 明文吐回去。

系统只允许返回类似:

  • has_api_key
  • has_secret_key
  • has_passphrase

这种布尔存在性信息。

  1. 交易执行必须通过会话级授权
    说明:
  • execute_trade 不再只靠“模型说要下单”就能执行
  • Tool 层现在会检查当前请求上下文里的会话权限
  • 没有交易执行权限的 session会被后端直接拒绝

这意味着即使 Prompt 被注入,模型生成了合法的 execute_trade 调用,只要当前 token/session 没有对应权限,后端仍然不会执行。

当前实现上,这条边界先采用:

  • 已认证会话
  • 明确的 session policy
  • 服务端 AllowTradeExecution 开关

的组合约束。

也就是说,真正的安全边界现在开始下沉到了 Tool / Session / Server Policy而不是停留在提示词层。


11.1 Planner 的多槽补齐策略

语义就绪检查把“准备不足”的请求挡回 planner 后,如果 planner 还是一轮只问一个槽位,用户体验会很差。

因此现在 planner prompt 已经明确被要求:

  • 优先一次性询问多个核心缺失字段
  • 在安全且常见的场景下,可以同时提出合理默认值

目标不是简单的“防止 hard skill 报错”,而是:

  • 让补槽更像一次有组织的表单引导
  • 减少挤牙膏式的一问一答

12. 为什么要保留 planner、workflow、hard skill 三层

当前不是所有请求都应该直接落到 hard skill。

12.1 Hard skill

适合:

  • 结构明确
  • skill 明确
  • action 明确
  • 必填足够

12.2 Workflow

适合:

  • 多个 management action 串联
  • 存在依赖关系

12.3 Planner

适合:

  • 开放式目标
  • 需要先探索当前状态
  • 结构还不够稳定

当前的设计方向是:

  • LLM 先判断当前在和哪个上下文说话
  • 再决定 route
  • 再进入 skill / workflow / planner

而不是一开始就靠 hard skill 猜。

12.4 Planner 的人格与职责

Planner 现在不只是“拆步骤”的模块,也共享了同一份 NOFXi 顾问式系统前缀。

这意味着 Planner 在生成计划时,会优先遵守这些原则:

  • 先保证配置正确、安全、逻辑一致
  • 先做状态诊断,而不是机械执行
  • 缺信息时,优先组织更像顾问的多槽追问和默认值建议

因此 Planner 现在承担的是:

  • 任务澄清
  • 风险控制
  • 配置诊断
  • 计划生成

这也是为什么统一语义网关和 Planner 必须共用同一份系统前缀。


13. 前端聊天页的运行形态

前端聊天页之前的问题是:

  • 切页就 abort 流式请求
  • 正在生成的消息会消失

现在这部分已经调整成:

  • 流式回复由更全局的 runtime/store 托管
  • 站内切页不会立刻中断流
  • 已生成内容会保留

这让 Agent 更接近“后台持续回复”,而不是“仅页面内临时回复”。


14. 这套结构是怎么一步步收出来的

当前架构不是一次性设计出来的,而是沿着这些问题逐步收口:

阶段 1先把快照恢复链打通

目标:

  • 挂起任务可恢复
  • target_snapshot_id 真能驱动恢复

结果:

  • router、flow extraction、runtime 都开始理解 snapshot

阶段 2把状态续接和全局路由收成统一语义网关

目标:

  • 不再一层判断“是不是当前流程”,另一层再重新猜一遍

结果:

  • 先做 continue_active / resume_snapshot / start_new / cancel / instant_reply
  • 再进入具体执行层

阶段 3让 CurrentReferences 真正成为“参考书”

目标:

  • 当前对象不能只是埋在 JSON 里
  • 要显式进入 prompt 决策

结果:

  • router、planner、classifier、extraction 都看当前引用对象

阶段 4把 skill 说明和依赖说明收成单一真源

目标:

  • 不再在每层 prompt 写一份不同的 skill 描述

结果:

  • buildManagementSkillContext 成为统一入口

阶段 5把名字和 ID 连接起来

目标:

  • 用户交互说名字
  • 系统执行用 ID

结果:

  • draft -> resolved object -> ID 的链路更稳

15. 当前已经验证过的方向

当前已经补过定向测试的方向包括:

  • 顶层 router prompt 包含 management skill summary
  • 顶层 router / one-pass gateway / planner prompt 共享顾问式系统前缀
  • 顶层/flow prompt 包含 management skill negative constraints
  • 顶层 router prompt 包含 current reference summary
  • active flow extraction prompt 包含 suspended snapshots
  • trader create 的依赖说明进入统一 skill context
  • semantic readiness 会把未准备好的 create 请求挡回 planner
  • Tool 层不会明文返回配置秘钥,只返回存在性标记
  • execute_trade 必须通过会话级授权和服务端开关
  • 子任务成功后会自动恢复父任务上下文
  • 名称歧义会触发澄清,而不是静默命中
  • execution waiting state 能桥接回 skill session
  • persistent reference memory 在 execution state 清掉后仍能命中当前对象
  • model/exchange create 的 tool 必填约束生效

相关测试文件主要包括:

另外仓库里现在已经有一套“AI 对练”种子回放骨架:

它的用途是:

  • 让代码助手或大模型根据产品说明生成极端对话场景
  • 把这些场景写成 JSON fixture
  • 用统一回放器批量喂给 thinkAndAct
  • 再把暴露的问题沉淀为:
    • Skill JSON 说明
    • Validator / Resolver / Readiness Gate
    • 按需上下文注入规则

16. 当前仍需要继续收的点

虽然主链已经比之前完整很多,但还有几块需要继续收:

  1. configure_strategy / configure_exchange / configure_model 这类 action 的语义落地 说明: 这些已经在动作语义上更清晰,但要继续让 LLM 和执行层稳定对齐。

  2. 更完整的 create 约束下沉 说明: strategy / trader 的部分约束还可以继续更严格地下沉到执行层和 tool 层。

  3. 更完整的跨 skill 依赖图 说明: 现在重点收了 trader create 的依赖图,未来可以继续扩展到其他多 skill 依赖场景。

  4. 歧义消除的 LLM 参与度 说明: 当前歧义澄清、活性校验、父任务回溯已经有了规则级保护;后续可以继续让 LLM 参与“如何问得更自然、如何结合上下文缩小候选范围”。

  5. 更细粒度的事务型状态版本控制 说明: 当前已经做了用户级串行化,足以挡住同一用户的大部分并发污染;后续如果要支持更复杂的多端并发或后台异步写入,可以继续升级成显式版本号或乐观锁。


17. 一句话总结

当前 NOFXi Agent 已经从“多个局部 if-else 叠起来的 chat handler”逐步收成了一套

  • 统一语义网关
  • 快照恢复
  • 当前对象引用记忆
  • 单一真源 skill context
  • skill/workflow/planner 分层执行

的任务型 Agent 架构。

它现在最核心的设计原则是:

  • 先判断用户在和哪个上下文说话
  • 再判断在当前上下文里要做什么
  • 再把自然语言解析成结构化状态
  • 最后由对应 skill/workflow/tool 去安全执行