Remove local-only agent artifacts

This commit is contained in:
lky-spec
2026-04-27 10:51:09 +08:00
parent e8eafce1e0
commit d481b3d88c
11 changed files with 4 additions and 3286 deletions

4
.gitignore vendored
View File

@@ -44,6 +44,7 @@ decision_logs/
nofx_test
# Node.js
web/node_modules
web/node_modules/
node_modules/
web/dist/
@@ -52,6 +53,9 @@ web/.vite/
# ESLint 临时报告文件(调试时生成,不纳入版本控制)
eslint-*.json
# 本地 Agent QA seed个人调试用不纳入版本控制
docs/qa/fixtures/agent_self_play_seed.zh-CN.json
# VS code
.vscode

View File

@@ -1,167 +0,0 @@
# AGENT Implementation Spec
## Goal
重构 Agent 的主循环逻辑:将意图识别、快照管理、字段提取完全交给 LLM 驱动。建立“任务栈”管理机制,确保跨 Skill、中途换话题、回填参数等复杂场景下的状态一致性并由 DAG 自动化完成执行逻辑。
## 1. Core Principles
### 1.1 必须遵守
- 大脑前置:每一轮用户输入必须先经过意图识别,禁止规则层先于模型层截断复合语义。
- 快照优先LLM 必须感知当前及历史快照Snapshot/Reference优先判断输入是否属于对已有任务的续接。
- DAG 驱动执行Skill Handler 仅负责原子操作,复杂的逻辑依赖、顺序、确认由 LLM 生成的 DAG 任务包驱动执行。
### 1.2 明确禁止
- 禁止静默失败:参数越界或逻辑冲突时,必须通过 LLM 反馈给用户原因,禁止直接丢弃输入。
- 禁止单向路由:禁止在单次意图识别中只认一个 Action必须支持多 Action 列表化输出。
## 2. Intent Handling
### 2.1 总入口
所有用户输入都必须先经过统一意图识别:
- [x]
统一意图识别需要判断以下结果:
- [x] `continue`
说明:续接当前快照。
- [x] `switch`
说明:开启新快照或切换到历史快照。
- [x] `cancel`
说明:明确取消当前任务。
- [x] `instant_reply`
说明:直接回答,无需进入快照流转。
### 2.2 由谁判断
意图识别主要由以下方式负责:
- [x] 大模型
### 2.3 当前 Flow 中的输入
`continue`
- 用户提供了缺失字段。
- 用户对配置表示确认例如“OK”“建吧”。
- 用户对已有配置进行微调。
`switch`
- 用户输入了与当前快照领域无关的新需求。
- 例如正在调策略时突然要查余额。
- 例如正在配置一个模型时突然要求配置另一个模型。
`cancel`
- 用户表达了明确的负面意图。
- 例如“算了”“不要了”“重来”。
## 3. Slot Extraction / Field Extraction
### 3.1 抽取方式
字段抽取主要由以下方式负责:
- [x] 大模型
如果由大模型负责,抽取时必须输入以下上下文:
- [x] 当前 `skill/action`
- [x] 当前 `draft/session`
- [x] 当前缺失字段(来自 DAG 定义)
- [x] 历史对话(近 3 轮)
- [x] 快照 / 当前引用
### 3.2 输出格式
期望大模型返回如下结构化 JSON
```json
{
"intent": "continue | switch | cancel",
"target_snapshot_id": "uuid-xxxx",
"tasks": [
{
"skill": "strategy",
"action": "create",
"fields": {
"leverage": 20,
"name": "my_strat"
}
}
],
"reason": "用户提供了杠杆倍数,继续策略创建流程"
}
```
### 3.3 合并策略
- 补全模式:新抽取的字段 merge 到原快照中。
- 覆盖模式:若用户明确修改已存在的值,以最新输入为准,但必须经过 Validator 重新校验。
## 4. Flow / State Machine
### 4.1 统一状态机
所有 flow 必须统一走同一个 orchestrator
- [x]
Flow 状态至少包含:
- [x] `collecting`
说明:字段收集中。
- [x] `waiting_confirmation`
说明:待用户确认。
- [x] `ready`
说明:校验通过。
- [x] `executing`
说明DAG 执行中。
- [x] `suspended`
说明:被新任务压栈挂起。
### 4.2 Switch / Suspend / Resume
用户切换话题时,当前任务应该:
- [x] 压栈
说明:放入 History Snapshots 栈,支持后续唤回。
## 5. Skill Scope
### 5.1 适用范围
这套方法模式适用于:
- [x] 全部
说明:实现全架构的语义编排。
### 5.2 不允许单独补丁
是否禁止只针对单个 skill / 单句子打补丁:
- [x]
补充说明:
- 必须保证所有 Skill 共享同一套 Router 和快照机制。
## 6. Risk Control / Validation
### 6.1 校验层
字段抽取后必须统一进入 validator
- [x]
validator 需要覆盖:
- [x] `strategy` 数值限制Clamp
- [x] `model` 配置合法性
- [x] `exchange` 凭证合法性
- [x] `trader` 绑定关系合法性
### 6.2 错误提示
- 提示原则LLM 解析校验失败结果,用自然语言告知用户安全范围。
- 示例:`杠杆最高 20 倍,已为您设为 20是否接受`
## 7. Performance
### 7.1 调用策略
- 流式响应:意图识别确定后,第一时间返回“正在处理[某意图]...”,减少用户感知延迟。
- LLM Cache对高频重复意图进行缓存。
### 7.2 快路径
允许不用大模型直接返回的场景:
- 简单打招呼,例如 `Hi``Hello`
- 完全匹配的单词退出指令,例如 `exit``quit`
## 8. Testing / Acceptance
### 8.1 必测场景
- 意图切换:正在创建策略时,询问“比特币价格”,查完后回答“继续创建刚才的策略吗”并成功恢复快照。
- 多动作合并:一句话同时完成“创建策略 A”和“配置交易所 B”。
- 纠错重填:用户输入了错误的杠杆倍数,系统提示纠正后,用户补填正确数值,系统能正确合并到原快照。
### 8.2 验收标准
- 无静默吞咽:任何有效信息必须体现在快照更新或回复中。
- 快照一致性:`CurrentReferences` 必须能精准映射到用户口中的“它”或“那个策略”。
## 9. Notes
- 快照快照还是快照:代码底层必须实现一个 `SnapshotManager`,支持 `Save/Load/List` 动作,供 LLM 通过特定的内部 Tool 进行调用。
- DAG 是约束而非死板流程DAG 告诉 LLM 缺什么,但 LLM 决定如何通过对话向用户要到这些。

159
agents.md
View File

@@ -1,159 +0,0 @@
# NOFXi Agent 实现状态文档
> 更新时间2026-04-23
---
## 一、架构概述
ReAct 模式LLM 做意图识别和路由,代码执行具体 skill action。
### 主要流程
```
用户消息
→ tryOnePassSemanticGateway快速单次 LLM 路由)
命中 → 直接执行
未命中 → tryLLMIntentRoute完整 LLM 路由)
→ skill单个结构化操作
→ workflow多步骤跨 skill 操作)
→ planner复杂/模糊请求)
```
### Intent 类型
| Intent | 含义 |
|--------|------|
| `continue_active` | 继续当前激活流程 |
| `resume_snapshot` | 恢复某个挂起快照 |
| `start_new` | 开启新的顶层请求 |
| `cancel` | 取消当前流程 |
| `instant_reply` | 纯聊天,不触发任务 |
---
## 二、Skill 体系
### 4 个 Management Skills
#### trader_management
- **触发:** 用户提到"交易员/trader/agent" + 操作动词
- **创建必填:** name、exchange_id、ai_model_id、strategy_id
- **字段约束:**
- `name`:最多 50 字符
- `scan_interval_minutes`360 分钟,超出自动收敛
- `initial_balance`:最低 100超出自动收敛
- `is_cross_margin`bool默认 true全仓
- `show_in_competition`bool默认 false
- `auto_start`bool默认 false
- **支持操作:** create、update、update_name、update_bindings、configure_strategy、configure_exchange、configure_model、start需确认、stop需确认、delete需确认、query_list、query_running、query_detail
#### exchange_management
- **触发:** 用户提到交易所名称 + 操作动词
- **创建必填(按交易所):**
| 交易所 | 必填字段 |
|--------|---------|
| binance / bybit / gate | api_key、secret_key |
| okx / kucoin | api_key、secret_key、passphrase |
| hyperliquid | hyperliquid_wallet_addr |
| aster | aster_user、aster_signer、aster_private_key |
| lighter | lighter_wallet_addr、lighter_api_key_private_key |
- **其他约束:**
- `api_key`:至少 8 位字母数字
- `secret_key`:至少 8 位字母数字或十六进制
- `lighter_api_key_index`0255超出自动收敛
- `testnet`bool默认 false
- **支持操作:** create、update、update_name、update_status、delete需确认、query_list、query_detail
#### model_management
- **触发:** 用户提到模型/provider 名称 + 操作动词
- **支持 provider** openai、deepseek、claude、gemini、qwen、kimi、grok、minimax、claw402、blockrun-base
- **字段约束:**
- `api_key`OpenAI 必须以 `sk-` 开头
- `custom_api_url`:必须是合法 HTTPS 地址
- `enabled=true` 前必须填写 api_key 和 custom_model_name
- **支持操作:** create、update、update_status、update_endpoint、update_name、delete需确认、query_list、query_detail
#### strategy_management
- **触发:** 用户提到"策略/strategy" + 操作动词
- **字段约束:**
- `btceth_max_leverage`120超出自动收敛
- `altcoin_max_leverage`120超出自动收敛
- `min_confidence`0100超出自动收敛
- `grid_count`:最小 2
- `lower_price` 必须小于 `upper_price`
- 策略模板**不能直接启动**,只有绑定了该策略的交易员才能启动
- **支持操作:** create、update、update_name、update_prompt、update_config、activate、duplicate、delete需确认、query_list、query_detail
### 4 个 Diagnosis Skills
- `trader_diagnosis`:交易员启动失败、未下单、收益异常等诊断
- `exchange_diagnosis`invalid signature、timestamp、权限不足等诊断
- `model_diagnosis`:模型调用失败、接口不兼容、鉴权错误等诊断
- `strategy_diagnosis`:策略不生效、参数不一致等诊断
---
## 三、LLM 收到的 Prompt 内容
路由阶段 LLM 收到:
1. Skill 摘要(名称、描述、创建必填)
2. Skill 禁止规则(各 skill 不能做什么)
3. 近期对话上下文
4. 当前任务状态
5. 当前激活流程摘要 + JSON
6. 当前引用摘要
7. 执行状态 JSON
8. 挂起快照 JSON
> 注意:`buildManagementSkillContext(lang, nil)` 传 nilactive skill 的 dynamic_rules 不会注入路由 LLM。
---
## 四、Snapshot / Resume 机制
- `SnapshotManager.Save(task)` 压栈挂起任务
- `task.ResumeOnSuccess = true` + `task.ResumeTriggers` 控制子任务完成后自动回流
- `maybeResumeParentTaskAfterSuccessfulSkill` 在子任务成功后检查栈,自动恢复父任务
---
## 五、本次改动记录2026-04-23
### 1. 4 个 skill JSON 补全字段约束
文件:`agent/skills/*.json`
- `trader_management.json`:补全 field_constraints、validation_rules、完整 actions
- `exchange_management.json`补全各交易所必填字段、per_exchange_required_fields
- `model_management.json`:补全 provider 枚举、API key 格式、HTTPS 校验
- `strategy_management.json`:补全杠杆/置信度/网格约束,修复中文弯引号 JSON 错误
### 2. 路由层加 inline_sub_intent
文件:`agent/llm_skill_router.go`
- `llmSkillRouteDecision``onePassGatewayDecision``InlineSubIntent` 字段
- 两个 gateway 的 JSON shape 加 `inline_sub_intent`
- Rules 加configure_strategy/exchange/model 流程里用户想新建依赖资源时,判断 `continue_active` + `inline_sub_intent=create_sub_resource`
- `continue_active` 分支把 `inline_sub_intent` 写入 session fields
### 3. 执行层消费 inline_sub_intent
文件:`agent/skill_execution_handlers.go`
- `configure_strategy/exchange/model` 分支检测到 `inline_sub_intent=create_sub_resource` 时:
1. 压栈当前 session`ResumeOnSuccess=true`
2. 切换到对应子任务strategy/exchange/model create
3. 子任务完成后自动回流父任务
---
## 六、已知问题
| 问题 | 状态 |
|------|------|
| 创建交易员时直接用现有配置,未询问用户确认 | 未修复 |
| 路由 LLM 缺 active session contextbuildManagementSkillContext 传 nil | 未修复 |

View File

@@ -1,203 +0,0 @@
# NOFXi 诊断与配置 Skills第一批
这份文档用于沉淀交易智能助手的第一批高频诊断与配置 skill。
目标不是让模型“更会想”,而是让它面对常见问题时,优先走稳定、可复用的排查路径。
## 设计原则
- 优先按 skill 回答,不要对高频问题重复自由规划
- 先归类问题,再给出原因、检查项和修复建议
- 能通过工具验证当前状态时,先查再下结论
- 敏感信息只指导填写,不完整回显
- 对结论不确定时,要明确标注为“更可能”或“优先怀疑”
## skill_model_api_setup
### 适用场景
- 用户问某个大模型的 API key 去哪里申请
- 用户问 base URL 怎么填
- 用户问 model name 怎么填
- 用户问 OpenAI / Claude / Gemini / DeepSeek / Qwen / Kimi / Grok / MiniMax 怎么接入
### 处理策略
1. 先确认用户要配置哪个 provider
2. 告诉用户需要准备的最少字段:
- provider
- API key
- custom_api_url
- custom_model_name
3. 如果系统已有默认地址和默认模型名,优先给推荐值
4. 回答按步骤组织,不要泛泛解释概念
### 已知实现事实
- 系统内置 provider 默认运行配置,见 `agent.resolveModelRuntimeConfig(...)`
- 常见 provider 已有默认 URL 和默认 model name
## skill_model_config_diagnosis
### 适用场景
- 模型保存成功但 agent 仍然不可用
- 提示 AI unavailable
- 提示模型没启用
- 提示 custom_api_url 不合法
- 配置后 trader 不生效
### 优先排查
1. 是否存在已启用模型
2. API key 是否为空
3. custom_api_url 是否为合法 HTTPS 地址
4. custom_model_name 是否为空或不匹配
5. 当前 trader 是否绑定了这个模型
6. 更新模型后是否已触发 trader reload
### 已知实现事实
- 非 HTTPS 的 `custom_api_url` 会被后端拒绝,见 `api/handler_ai_model.go`
- 已启用模型如果缺少 API Key 或 URL会导致 agent 无法就绪,见 `agent.ensureAIClientForStoreUser(...)`
- 更新模型配置后,系统会尝试移除并重载相关 trader使新配置立即生效
### 输出格式
- 现象
- 更可能原因
- 先检查什么
- 下一步怎么修复
## skill_exchange_api_setup
### 适用场景
- 用户要新建交易所 API
- 用户不知道交易所需要哪些权限
- 用户问 API key / secret / passphrase 分别填什么
### 通用处理策略
1. 先确认交易所类型
2. 告知必须权限与禁止权限
3. 告知是否需要额外字段
4. 强调 IP 白名单与权限配置
5. 引导用户回到系统内完成绑定
### 特殊规则
- OKX 除 API Key 和 Secret 外,还需要 passphrase
- Bybit 永续/合约交易需要合约权限
- 不建议开启提现权限
### 参考文档
- `docs/getting-started/okx-api.md`
- `docs/getting-started/bybit-api.md`
## skill_exchange_api_diagnosis
### 适用场景
- `invalid signature`
- `timestamp` 错误
- `IP not allowed`
- `permission denied`
- 交易所连接不上
### 优先排查
1. 系统时间是否同步
2. API Key / Secret 是否正确
3. 是否遗漏额外字段,如 OKX passphrase
4. IP 白名单是否包含当前服务器
5. 是否启用了交易或合约权限
6. 密钥是否过期或已重建
### 已知实现事实
- 时间不同步是 `invalid signature` / `timestamp` 的高频根因,见 `docs/guides/TROUBLESHOOTING.zh-CN.md`
- OKX 的 passphrase 缺失会导致签名相关问题,见 `docs/getting-started/okx-api.md`
### 输出格式
- 报错现象
- 最常见根因
- 优先检查顺序
- 修复步骤
## skill_trader_start_diagnosis
### 适用场景
- trader 启动不了
- trader 启动了但没开始交易
- 页面显示已启动但一直没有动作
- 用户怀疑 strategy / model / exchange 绑定有问题
### 优先排查
1. 是否有已启用的模型配置
2. 是否有已启用的交易所配置
3. trader 是否绑定了 exchange_id / strategy_id / ai_model_id
4. 交易所余额和权限是否满足下单条件
5. AI 最近的决策到底是 wait、hold 还是下单失败
### 回答原则
- 要区分“没启动”“启动了但 AI 选择不交易”“尝试下单但失败”这三类
- 不要把“没开仓”直接等同于“系统故障”
## skill_order_execution_diagnosis
### 适用场景
- 下单失败
- 只开空不开户 / 只开单边
- 杠杆报错
- position side mismatch
### 优先排查
1. 账户模式是否匹配,例如 Binance 是否为 Hedge Mode
2. 是否为子账户杠杆限制
3. 合约权限是否开启
4. 余额、保证金、可交易 symbol 是否满足条件
### 已知实现事实
- Binance 在 One-way Mode 下,可能出现 `position side mismatch` 或单边行为
- 某些子账户杠杆上限较低,超过限制会直接失败
- 这些问题在 `docs/guides/TROUBLESHOOTING.md` 已有明确说明
## skill_strategy_diagnosis
### 适用场景
- 用户说策略没生效
- 用户说 prompt 预览和实际不一致
- 用户说修改策略后 trader 行为没有变化
### 优先排查
1. 当前编辑的是策略模板,还是 trader 的 custom prompt
2. 策略是否真的保存成功
3. 是否需要重新读取当前配置做对比
4. 用户说的“没生效”是指未保存、未绑定,还是运行结果与预期不一致
### 回答原则
- 先明确“对象”再排查strategy template / trader / prompt override
- 如果能读取当前保存值,就不要凭印象判断
## 后续扩展方向
下一批可以继续补:
- `skill_balance_and_position_diagnosis`
- `skill_market_data_diagnosis`
- `skill_prompt_generation_diagnosis`
- `skill_strategy_test_run_diagnosis`
- `skill_exchange_specific_setup_<exchange>`
- `skill_model_provider_setup_<provider>`

View File

@@ -1,926 +0,0 @@
# 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. 顶层执行链路
当前主入口在:
- [planner_runtime.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/planner_runtime.go:820)
整体顺序是:
1. `tryLLMIntentRoute`
2. `tryStatePriorityPath`
3. `tryInstantDirectReply`
4. `tryReadFastPath`
5. `tryHardSkill`
6. `runPlannedAgent`
也就是说,现在系统优先做“统一语义判断”,然后才看 active flow、direct reply、hard skill 和 planner。
### 2.1 统一语义网关
顶层语义网关在:
- [llm_skill_router.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/llm_skill_router.go:25)
LLM 目前先判断一条消息属于哪种 intent
- `continue_active`
- `resume_snapshot`
- `start_new`
- `cancel`
- `instant_reply`
如果是 `start_new`,再继续判断 route
- `skill`
- `workflow`
- `planner`
这层的意义是:先判断“这句话在和哪个上下文说话”,再判断“具体怎么做”。
### 2.2 顾问式系统前缀
现在统一语义网关和 Planner 共享同一份顾问式系统前缀,而不再只是“路由器”或“计划器”口吻。
这份前缀的核心基调是:
- 你是 NOFX 的核心智能中枢 `NOFXi`
- 你的首要目标不是盲目执行命令
- 你需要以资深量化顾问身份,确保每一次配置都正确、安全且符合逻辑
- 当用户遇到问题时,你要结合当前状态和平台边界主动诊断,并给出具体解决方案
统一前缀已经抽成共享 helper
- [prompt_persona.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/prompt_persona.go)
当前已注入:
- 顶层统一语义网关
- `one-pass` 统一语义网关
- planner
- replanner
这样做的目的,是让“理解用户意图”和“后续制定执行计划”都遵守同一套顾问式人格、安全边界和诊断基调。
### 2.3 兼容式 One-Pass JSON 网关
为了减少 `router -> classifier -> extractor` 的串行 LLM 调用次数,当前已经在统一语义网关前面接入了一个兼容式 `One-Pass JSON` 网关:
- [llm_skill_router.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/llm_skill_router.go)
它会在一次调用里尝试同时输出:
- `intent`
- `target_skill`
- `target_snapshot_id`
- `extracted_fields`
- `need_planner_help`
典型返回形状如下:
```json
{
"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. 状态优先层
状态优先层在:
- [planner_runtime.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/planner_runtime.go:954)
它负责优先处理这些场景:
- 是否要恢复挂起任务
- 是否已有 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相关逻辑在
- [planner_runtime.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/planner_runtime.go:2950)
当前优先级是:
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
相关读取点包括:
- [llm_skill_router.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/llm_skill_router.go:38)
- [planner_runtime.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/planner_runtime.go:2950)
- [llm_flow_extractor.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/llm_flow_extractor.go:73)
### 5.2 Suspended snapshots
挂起任务快照用于:
- 打断当前流程
- 以后恢复到具体旧流程
- 让 LLM 在“刚才那个”“前面那个”这种模糊指代下仍能选对上下文
快照信息会进入:
- top-level router
- active flow classifier
- flow extraction
### 5.3 Persistent reference memory
现在 `CurrentReferences` 不只存在于 `ExecutionState` 里。
新增了持久引用记忆:
- [reference_memory.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/reference_memory.go)
核心函数:
- `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 定义的正式来源在:
- [agent/skills/trader_management.json](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/skills/trader_management.json)
- [agent/skills/exchange_management.json](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/skills/exchange_management.json)
- [agent/skills/model_management.json](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/skills/model_management.json)
- [agent/skills/strategy_management.json](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/skills/strategy_management.json)
---
## 7. 统一 skill 上下文:单一真源
之前的问题是:
- skill JSON 有一份简介
- router prompt 又手写一份
- workflow prompt 再手写一份
- classifier / extraction 又各有自己的上下文说明
这会导致不同层看到的 skill 描述不一致。
现在已经收成统一 helper
- [skill_registry.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/skill_registry.go)
关键函数:
- `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 现在已经接入:
- [llm_skill_router.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/llm_skill_router.go:39)
- [workflow.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/workflow.go:569)
- [llm_flow_extractor.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/llm_flow_extractor.go:73)
- [planner_runtime.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/planner_runtime.go:2010)
- [planner_runtime.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/planner_runtime.go:2951)
也就是说,现在 router、workflow、classifier、extraction、planner 使用的是同一套 management skill 说明。
---
## 7.3 语义就绪检查
仅仅把消息路由到某个 skill 还不够,还要判断这条消息在“语义上是否已经准备好进入执行层”。
这层现在在:
- [skill_semantic_gate.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/skill_semantic_gate.go)
关键点:
- `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 续接
相关逻辑:
- [planner_runtime.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/planner_runtime.go:1583)
- [llm_flow_extractor.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/llm_flow_extractor.go:61)
主要分两步:
1. `classifySkillSessionIntentWithLLM`
- 判断是继续、取消、打断还是闲聊
2. `extractSkillSessionFieldsWithLLM`
- 把这条消息抽成结构化字段
然后把结构化字段 merge 回当前 skill session。
### 8.1.1 对话驱动式 skill 收集
对于高价值的多轮 management flow当前已经开始把“补槽”从代码猜测迁到 LLM 对话驱动器:
- [llm_skill_conversation.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/llm_skill_conversation.go)
目前最先落地的是:
- `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_runtime.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/planner_runtime.go:1211)
这解决的是:
- planner 已经问到一半
- 用户回复了字段
- 但后续 hard skill 执行时又像“没收到”
现在系统已经能把 execution waiting 中收集到的信息投影回 skill session。
### 8.3 子任务成功后的父任务回流
现在快照不只是“可恢复存档”,还带有父任务信息。
相关结构在:
- [execution_state.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/execution_state.go)
新增字段包括:
- `intent_id`
- `parent_intent_id`
- `resume_on_success`
- `resume_triggers`
构建点在:
- [planner_runtime.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/planner_runtime.go:2214)
执行成功后的回流点在:
- [skill_dispatcher.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/skill_dispatcher.go)
这解决的是:
- 用户本来在 `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。
当前这层连接主要做在:
- [skill_dispatcher.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/skill_dispatcher.go)
核心函数:
- `hydrateCreateTraderSlotReferences`
- `findOptionByIDOrName`
设计原则是:
1. 用户层允许说名字
2. 系统尽快解析出唯一对象
3. 一旦唯一,就落成真实 ID
4. 展示给用户时仍然优先显示友好名字
这解决的是“确认文案看起来正确,但真正执行又说缺字段”的问题。
### 10.1 歧义引用澄清
除了“名字映射到 ID”系统现在也开始处理“多个候选对象同名或近似”的情况。
相关逻辑在:
- [skill_dispatcher.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/skill_dispatcher.go)
- [skill_management_handlers.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/skill_management_handlers.go)
核心做法是:
1. 若唯一命中,直接解析成 ID
2. 若多个候选同时命中,不再静默选择
3. 统一返回澄清问题,让用户明确要操作哪一个对象
这比“猜一个”更安全,也避免了对象误绑。
---
## 10.2 用户级串行化
同一个用户可能在网络卡顿或前端重发的情况下,几乎同时发出两条修改消息。
为了避免:
- 两条消息并发进入同一个 active flow
- extraction 结果交叉 merge
- `skillSession` / `ExecutionState` 变成缝合状态
现在 `thinkAndAct``thinkAndActStream` 已经在用户维度上做了串行化处理。
也就是说:
- 同一个 `userID`
- 任意时刻只允许一条主消息进入 flow merge/execute 链路
这比只在单个 `save*` 调用上加锁更有效,因为它保护的是整条“读状态 -> 理解 -> merge -> 执行 -> 写状态”的事务链。
---
## 11. Tool 层约束
之前一个根问题是:上层像是“创建成功”了,但底层实际上没拿到完整必填字段。
现在部分 create 约束已经下沉到 tool 层:
- [tools.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/tools.go)
当前明确加了必填约束的包括:
- `model_management:create`
- `exchange_management:create`
这意味着:
- 不再只靠上层 prompt 判断“够不够建”
- tool 自己也会拒绝缺关键字段的 create
这能防止“草稿像成功了,但对象其实是半残”的情况。
### 11.1 Tool 层安全硬隔离
安全不能只靠 Prompt。
因此现在 Tool 层已经补了两类后端硬边界:
1. 敏感凭证永不明文返回
说明:
- `toolGetModelConfigs`
- `toolGetExchangeConfigs`
- 以及对应 create / update 响应
都会先走安全视图,再做一层递归敏感字段剥离。
也就是说,像 `api_key``secret_key``passphrase`、私钥这类字段,不会因为 LLM 被注入而通过 Tool 明文吐回去。
系统只允许返回类似:
- `has_api_key`
- `has_secret_key`
- `has_passphrase`
这种布尔存在性信息。
2. 交易执行必须通过会话级授权
说明:
- `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 必填约束生效
相关测试文件主要包括:
- [llm_intent_router_test.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/llm_intent_router_test.go)
- [skill_dispatcher_test.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/skill_dispatcher_test.go)
- [skill_registry_test.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/skill_registry_test.go)
- [config_tools_test.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/config_tools_test.go)
另外仓库里现在已经有一套“AI 对练”种子回放骨架:
- [self_play_replay_test.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/self_play_replay_test.go)
- [agent_self_play_seed.zh-CN.json](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/docs/qa/fixtures/agent_self_play_seed.zh-CN.json)
- [AGENT_AI_SELF_PLAY.zh-CN.md](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/docs/qa/AGENT_AI_SELF_PLAY.zh-CN.md)
它的用途是:
- 让代码助手或大模型根据产品说明生成极端对话场景
- 把这些场景写成 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 去安全执行

View File

@@ -1,613 +0,0 @@
# NOFXi Agent 当前设计说明
## 目的
本文描述当前 NOFXi Agent 的实际设计,而不是早期版本的理想设计。重点回答这些问题:
- 用户消息从哪里进入
- 什么请求会进入 planner
- 当前有哪些记忆层
- planner 如何生成与执行 plan
- tool 现在是怎么设计的
- 动态快照和当前引用分别解决什么问题
- 为什么某些问题会出现“看起来有历史,但模型还是会追问”
本文对应的主要实现文件:
- `agent/agent.go`
- `agent/web.go`
- `api/agent_routes.go`
- `agent/planner_runtime.go`
- `agent/execution_state.go`
- `agent/memory.go`
- `agent/history.go`
- `agent/tools.go`
## 一句话总览
当前 Agent 的运行模型可以概括为:
1. 前端把消息发到 `/api/agent/chat/stream`
2. 后端把登录用户身份放进 context
3. Agent 除 `/clear``/status` 外,其他消息全部进入 planner
4. planner 结合多层记忆、动态快照和 tool schema 生成 plan
5. 执行 plan 中的 `tool / reason / ask_user / respond`
6. 在执行过程中持续更新执行态、短期原话、长期摘要和当前对象引用
## 请求入口
### 前端入口
前端 Agent 页面在:
- `web/src/pages/AgentChatPage.tsx`
当前聊天使用:
- `POST /api/agent/chat/stream`
请求体里会传:
- `message`
- `lang`
- `user_key`
### 后端路由入口
路由注册在:
- `api/agent_routes.go`
这里会:
1. 经过 `authMiddleware`
2. 从登录态里取出 `user_id`
3. 通过 `agent.WithStoreUserID(...)` 写入 request context
### Agent Web Handler
真正的 HTTP handler 在:
- `agent/web.go`
主要入口:
- `HandleChat(...)`
- `HandleChatStream(...)`
再往下进入:
- `HandleMessageForStoreUser(...)`
- `HandleMessageStreamForStoreUser(...)`
## 最外层分流
当前外层分流已经被收口。
`agent/agent.go` 中,除了这两个命令之外,其他输入全部交给 planner
- `/clear`
- `/status`
也就是说,现在这些都不再在外层直接处理:
- setup flow
- trade confirmation
- direct trade regex
- 自然语言配置流程
- 自然语言策略创建
这些都统一进入 planner。
这是当前设计里一个很重要的原则:
- 外层分流越少,行为边界越清晰
- 自然语言理解尽量统一交给 planner + tool
## 当前的 5 层记忆
当前不是 3 层,也不是 4 层,而是 5 层:
1. `chatHistory`
2. `TaskState`
3. `ExecutionState`
4. `CurrentReferences`
5. `Persistent Preferences`
### 1. chatHistory
定义位置:
- `agent/history.go`
作用:
- 保存最近几轮用户 / assistant 原始消息
- 给模型保留最近原话上下文
- 为后续摘要成 `TaskState` 提供原始素材
特点:
- 只保留短期原话
- 内存态
- `/clear` 时清空
适合存:
- 最近几轮对话原文
- 用户的最新措辞
- 刚刚的自然语言上下文
不适合存:
- 长期真相
- 当前外部系统状态
- 当前流程精确执行位置
### 2. TaskState
定义位置:
- `agent/memory.go`
作用:
- 保存跨轮次仍然有意义的高层摘要
- 注入 planner / reasoning / final response
持久化 key
- `agent_task_state_<userID>`
字段:
- `CurrentGoal`
- `ActiveFlow`
- `OpenLoops`
- `ImportantFacts`
- `LastDecision`
- `UpdatedAt`
适合存:
- 当前高层目标
- 跨轮次仍然成立的未闭环事项
- 关键事实
- 最近一次重要决策及其原因
不适合存:
- step 级待办
- “下一步调用哪个 tool”
- 动态余额、持仓、配置存在性
- 任何可以通过 tool 重新读取的实时状态
### 3. ExecutionState
定义位置:
- `agent/execution_state.go`
作用:
- 保存当前 plan 的执行态
- 支持 `ask_user` 之后继续执行
- 保存 plan、当前步骤、执行日志、等待状态等
持久化 key
- `agent_execution_state_<userID>`
当前关键字段:
- `SessionID`
- `Goal`
- `Status`
- `PlanID`
- `Steps`
- `CurrentStepID`
- `DynamicSnapshots`
- `ExecutionLog`
- `SummaryNotes`
- `Waiting`
- `CurrentReferences`
- `FinalAnswer`
- `LastError`
### 4. CurrentReferences
定义位置:
- `agent/execution_state.go`
作用:
- 记录当前对话里“这个 / 那个 / 刚才那个”到底指的是谁
当前支持的引用对象:
- `strategy`
- `trader`
- `model`
- `exchange`
这是为了解决一种常见问题:
- 用户明明前一轮刚说过“激进策略”
- 下一轮说“改一下这个策略”
- 如果没有结构化引用,模型虽然有聊天历史,也容易重新追问
`CurrentReferences` 不是系统状态快照,而是:
- 当前对话焦点对象
- 当前代词绑定对象
### 5. Persistent Preferences
对应工具:
- `get_preferences`
- `manage_preferences`
作用:
- 保存用户长期偏好
适合存:
- 默认中文回复
- 偏好激进风格
- 更关注 BTC / ETH
- 不喜欢高频
- 每天固定时间简报
它和 `TaskState` 的区别是:
- `TaskState` 偏向当前任务摘要
- `Persistent Preferences` 偏向长期用户画像
## DynamicSnapshots 是什么
`DynamicSnapshots` 是当前真实系统状态的快照。
它不是历史,也不是长期记忆,而是 planner 在规划前或执行中插入的“当前事实”。
当前会进入快照的典型信息包括:
- 当前模型配置列表
- 当前交易所配置列表
- 当前策略列表
- 当前 trader 列表
- 当前余额
- 当前持仓
- 最近交易历史
作用:
- 防止 planner 盲信旧结论
- 避免“之前没配置,现在其实已经配好了却还说没有”
- 避免“之前余额是 A现在拿旧 observation 继续回答”
一句话:
- `DynamicSnapshots` = 当前世界里真实有什么
## CurrentReferences 和 DynamicSnapshots 的区别
这两个容易混淆,但职责完全不同。
`DynamicSnapshots`
- 当前系统状态快照
- 是候选集合 / 当前事实
- 例如当前有两个策略:`激进``新策略`
`CurrentReferences`
- 当前对话焦点对象
- 是“这个”到底指谁
- 例如用户现在说的“这个策略”就是 `激进`
可以这样理解:
- `DynamicSnapshots` 是地图
- `CurrentReferences` 是你手指现在指着地图上的哪个点
## Planner 的输入
planner 主逻辑在:
- `agent/planner_runtime.go`
生成计划时,当前会把这些东西一起送给模型:
- 当前用户请求
- tool schema
- `Persistent Preferences`
- `TaskState`
- `ExecutionState`
- `Resume context`
- `Structured waiting state`
- `Observation context`
其中 observation context 不是旧版单数组,而是分层后的:
- `dynamic_snapshots`
- `execution_log`
- `summary_notes`
## Plan 的结构
当前 planner 只允许这 4 类 step
- `tool`
- `reason`
- `ask_user`
- `respond`
这意味着现在的 Agent 不是一个“自由发挥的回复器”,而是:
- 先规划
- 再执行步骤
- 必要时重规划
## 步骤执行流程
`executePlan(...)` 的核心逻辑是:
1. 找下一个 pending step
2. 标记 step 为 running
3. 执行对应类型
4. 写回 `ExecutionState`
5. 必要时触发 replanning
不同 step 类型行为如下:
### tool
- 调内部 tool
- 把结果写入 `ExecutionLog`
- 根据结果更新 `CurrentReferences`
- 必要时触发 replanner
### reason
- 发起一次短 reasoning 调用
- 生成一段简短中间推理
- 写入 `ExecutionLog`
### ask_user
- 进入 `waiting_user`
- 保存 `WaitingState`
- 把问题直接回给用户
### respond
- 生成最终回答
- 标记当前执行完成
## WaitingState 是什么
`WaitingState` 用来解决:
- 用户回复 `是`
- 用户回复 `继续`
- 用户回复 `那个就行`
这类短回复如果没有结构化等待状态,很容易丢上下文。
当前字段包括:
- `Question`
- `Intent`
- `PendingFields`
- `ConfirmationTarget`
- `CreatedAt`
它的作用是:
- 告诉 planner 上一轮到底在等什么
- 让这轮短回复更容易被理解成“对上一问的回答”
## CurrentReferences 如何更新
当前是双路径更新:
### 1. 用户消息命中对象名时更新
如果用户说:
- `修改激进策略`
- `停止 lky`
- `用 DeepSeek`
系统会去当前用户的策略 / trader / model / exchange 列表里尝试匹配名称或 ID。
匹配成功后,更新 `CurrentReferences`
### 2. tool 成功返回对象时更新
比如:
- `manage_strategy(create/update/activate)`
- `manage_trader(create/update)`
- `manage_model_config(update)`
- `manage_exchange_config(update)`
只要 tool 返回了具体对象,系统就会把对应 ID / name 写回当前引用。
## Tool 设计
当前 tool 是“资源型 tool”设计不是“页面动作型 tool”。
### 当前主要工具
配置资源:
- `get_exchange_configs`
- `manage_exchange_config`
- `get_model_configs`
- `manage_model_config`
策略资源:
- `get_strategies`
- `manage_strategy`
trader 资源:
- `manage_trader`
交易 / 查询资源:
- `search_stock`
- `execute_trade`
- `get_positions`
- `get_balance`
- `get_market_price`
- `get_trade_history`
### 为什么这么设计
优点:
- tool schema 稳定
- 行为边界清晰
- planner 更容易学会
- 资源增删改查统一
当前 `manage_strategy` 支持:
- `list`
- `get_default_config`
- `create`
- `update`
- `delete`
- `activate`
- `duplicate`
当前 `manage_trader` 支持:
- `list`
- `create`
- `update`
- `delete`
- `start`
- `stop`
## 为什么“创建策略”不该默认依赖交易所和模型
当前设计里,策略模板应该是独立资源:
- `strategy`
而运行态对象是:
- `trader`
更合理的边界是:
- 创建策略模板:用 `manage_strategy`
- 把策略跑起来:用 `manage_trader`
也就是说:
- 策略不默认依赖交易所和模型
- 只有当用户要求“运行 / 部署 / 创建 trader”时才需要进一步关联 exchange / model / trader
## 当前一个完整例子
用户输入:
`帮我创建一个新的激进策略模板,名字就叫激进。创建完后,再把这个策略绑定到 trader lky。`
当前大致流程:
1. 前端请求 `/api/agent/chat/stream`
2. 后端注入 `store_user_id`
3. Agent 进入 planner
4. planner 刷新动态快照:
- 当前策略
- 当前 trader
5. 生成 plan例如
- `get_strategies`
- `manage_strategy(create)`
- `manage_trader(update)`
- `respond`
6. 执行 `manage_strategy(create)` 后:
- 写入 `ExecutionLog`
- 更新 `CurrentReferences.strategy`
7. 执行 `manage_trader(update)` 时:
- 直接使用刚创建策略的 ID
8. 输出最终回复
如果此后用户继续说:
`把这个策略的 prompt 改激进一点`
系统会优先从 `CurrentReferences.strategy` 理解“这个策略”。
## 为什么看起来“有历史”,模型还是会追问
因为“有聊天历史”不等于“有结构化对象绑定”。
如果没有 `CurrentReferences`
- 模型只能依赖原话文本推断“这个策略”是谁
- 一旦中间插入多条消息,或者有多个候选策略
- 就容易重新追问
所以当前设计里,`CurrentReferences` 是补齐这一块的关键。
## 当前已知限制
### 1. 外层虽然已经大幅收口,但仍然不是纯 graph runtime
现在比之前更统一,但整体仍然是:
- Agent 主入口
- Planner
- Tool 执行
而不是完整 node-graph 引擎。
### 2. ExecutionState 仍然是按 userID 单槽位
这意味着:
- 同一用户的多个并行任务仍然可能相互影响
更彻底的方向应该是:
- 按 thread / session 多实例存储
### 3. CurrentReferences 目前还是轻量实现
当前只覆盖:
- strategy
- trader
- model
- exchange
后面如果要更强,需要考虑:
- 多候选冲突消解
- 昵称映射
- 跨更长会话的稳定实体绑定
## 当前设计的核心思想
一句话总结:
- `chatHistory` 记原话
- `Persistent Preferences` 记长期偏好
- `TaskState` 记高层摘要
- `ExecutionState` 记当前流程
- `DynamicSnapshots` 记当前事实
- `CurrentReferences` 记当前指代对象
- `planner` 决定步骤
- `tools` 执行落地动作
这就是当前 NOFXi Agent 的实际运行设计。

View File

@@ -1,454 +0,0 @@
# NOFXi Agent Memory And Planning Design
## Purpose
This document explains how the current NOFXi agent handles:
- short-term conversation memory
- durable task memory
- durable execution / planning state
- planner execution and replanning
- state reset and resume behavior
The implementation described here is primarily in:
- `agent/history.go`
- `agent/memory.go`
- `agent/execution_state.go`
- `agent/planner_runtime.go`
- `agent/agent.go`
## High-Level Model
The current agent uses three different layers of state:
1. `chatHistory`
Recent in-memory user/assistant turns for the live conversation.
2. `TaskState`
Durable summarized context that should survive beyond recent turns.
3. `ExecutionState`
Durable workflow state for the currently running or recently blocked plan.
These three layers serve different purposes and should not be treated as the same thing.
## State Layers
### 1. `chatHistory`
Defined in `agent/history.go`.
Role:
- stores recent `user` / `assistant` messages in memory
- keyed by `userID`
- used as short-term conversational context
- acts as the source material for later compression into `TaskState`
Characteristics:
- in-memory only
- capped by `maxTurns`
- cleared by `/clear`
- not suitable as durable truth
Typical contents:
- the last few user questions
- the last few assistant replies
- temporary conversational wording
### 2. `TaskState`
Defined in `agent/memory.go`.
Role:
- stores durable, structured, non-derivable context
- persisted through `system_config`
- injected into planning and reasoning prompts
Storage key:
- `agent_task_state_<userID>`
Fields:
- `CurrentGoal`
- `ActiveFlow`
- `OpenLoops`
- `ImportantFacts`
- `LastDecision`
- `UpdatedAt`
Intended contents:
- user goal that still matters across turns
- high-level unresolved issues that still matter across turns
- facts that tools cannot cheaply re-fetch
- latest important decision summary
Explicitly not intended for:
- step-level pending items such as "wait for API key"
- execution actions such as "call get_exchange_configs"
- live balances
- current positions
- current market prices
- mutable configuration availability
Those should be checked from tools at planning time instead of being trusted from old summaries.
### 3. `ExecutionState`
Defined in `agent/execution_state.go`.
Role:
- stores the current execution workflow
- allows the agent to resume after `ask_user`
- persists plan steps, observations, and completion status
Storage key:
- `agent_execution_state_<userID>`
Fields:
- `SessionID`
- `UserID`
- `Goal`
- `Status`
- `PlanID`
- `Steps`
- `CurrentStepID`
- `Observations`
- `FinalAnswer`
- `LastError`
- `UpdatedAt`
This is the planner's working state, not a general memory store.
## Data Flow
### Request Entry
Entry points:
- `HandleMessage(...)`
- `HandleMessageStream(...)`
Flow:
1. user message enters `agent`
2. slash commands and explicit direct branches are handled first
3. all other requests go into planner flow via `thinkAndAct(...)` / `thinkAndActStream(...)`
### Planner Flow
The planner pipeline in `agent/planner_runtime.go` is:
1. append user message into `chatHistory`
2. emit `planning` SSE event
3. load `ExecutionState`
4. optionally reset stale `ExecutionState`
5. optionally refresh dynamic configuration snapshots
6. create a fresh execution plan with the LLM
7. execute steps one by one
8. persist `ExecutionState` after important transitions
9. append assistant answer into `chatHistory`
10. maybe compress old conversation into `TaskState`
## Short-Term vs Durable Memory
### What lives in `chatHistory`
Good fits:
- raw recent messages
- conversational wording
- latest assistant phrasing
Bad fits:
- long-lived truths
- current external system state
### What lives in `TaskState`
Good fits:
- durable goal
- high-level unfinished work that remains relevant across turns
- important facts the user stated
- previous decisions and why they were made
Bad fits:
- pending steps inside the current plan
- execution-level reminders such as "wait for a field" or "call a tool"
- old conclusions about whether tools exist
- old conclusions about whether model/exchange config is present
- live operational state that can change outside the chat
### What lives in `ExecutionState`
Good fits:
- current plan steps
- observations from tool calls
- blocked-on-user-input status
- exact current workflow state
- step-level pending work and block reasons
Bad fits:
- evergreen user profile
- long-term semantic memory
## Planning Logic
### Plan Creation
`createExecutionPlan(...)` sends the following into the planner model:
- available tool definitions
- persistent preferences
- `TaskState` context
- `ExecutionState` JSON
- current user request
The planner must return JSON only with step types:
- `tool`
- `reason`
- `ask_user`
- `respond`
### Step Execution
`executePlan(...)` executes the plan loop:
- `tool`
call tool and append observation
- `reason`
run reasoning sub-call and append observation
- `ask_user`
save `waiting_user` state and return question
- `respond`
generate final answer and mark completed
After each completed step, `replanAfterStep(...)` may:
- continue
- replace remaining steps
- ask user
- finish
## Resume Behavior
When `ExecutionState.Status == waiting_user`, the next user turn is treated as a reply to the pending question.
Current safeguards:
- latest asked question is extracted from the stored plan
- the user reply is appended as a `user_reply` observation
- planner prompt receives explicit `Resume context`
This prevents short replies like `是` from being misread as unrelated fresh intents as often as before.
## Dynamic State Refresh
Configuration and trader management requests are dynamic by nature. Their truth can change outside the current chat, for example:
- user configures exchange in the UI
- user adds model in another tab
- user creates trader elsewhere
Because of that, configuration/trader requests should not trust stale model conclusions.
Current protection in `planner_runtime.go`:
- detects config / trader intent with `isConfigOrTraderIntent(...)`
- clears `TaskState` context from the planner prompt for these requests
- refreshes `ExecutionState.Observations` with fresh snapshots from:
- `toolGetModelConfigs(...)`
- `toolGetExchangeConfigs(...)`
- `toolListTraders(...)`
This makes the planner rely more on current system state and less on older narrative memory.
## Reset Strategy
The system currently resets or weakens stale execution state when:
- user says retry-like phrases such as `再试`, `继续`, `try again`, `continue`
- request is config / trader related and old execution state is failed / completed / waiting
Reset scope:
- `ExecutionState` may be cleared
- `TaskState` is not globally deleted, but it is intentionally ignored for config/trader planning
Manual reset:
- `/clear`
This clears:
- short-term chat history
- task state
- execution state
## Compression Design
`maybeCompressHistory(...)` moves older short-term chat content into `TaskState` when:
- recent message count exceeds the configured window
- estimated token count exceeds the threshold
Compression strategy:
1. keep recent conversation in `chatHistory`
2. summarize older turns into structured `TaskState`
3. persist new `TaskState`
4. replace `chatHistory` with recent slice
Important design rule:
- `TaskState` should keep durable context only
- it should not become a stale copy of mutable operational state
## Current Architecture Diagram
```mermaid
flowchart TD
U[User Message] --> A[HandleMessage / HandleMessageStream]
A --> B{Direct command?}
B -->|Yes| C[Direct branch or slash command]
B -->|No| D[thinkAndAct / thinkAndActStream]
D --> E[Append user turn to chatHistory]
D --> F[Load ExecutionState]
F --> G{waiting_user?}
G -->|Yes| H[Attach user_reply observation]
G -->|No| I[Create fresh ExecutionState]
H --> J[Refresh dynamic snapshots if config/trader intent]
I --> J
J --> K[createExecutionPlan via LLM]
K --> L[Execution plan]
L --> M[executePlan loop]
M --> N[tool step]
M --> O[reason step]
M --> P[ask_user step]
M --> Q[respond step]
N --> R[Append Observation]
O --> R
R --> S[replanAfterStep]
S --> M
P --> T[Persist waiting_user ExecutionState]
T --> UQ[Return question to user]
Q --> V[Persist completed ExecutionState]
V --> W[Append assistant turn to chatHistory]
W --> X[maybeCompressHistory]
X --> Y[Persist TaskState]
Y --> Z[Final response]
```
## Memory Relationship Diagram
```mermaid
flowchart LR
CH[chatHistory\nin-memory\nrecent turns]
TS[TaskState\npersisted summary\nsystem_config]
ES[ExecutionState\npersisted workflow\nsystem_config]
PL[Planner Prompt]
CH -->|recent raw turns| PL
ES -->|current workflow JSON| PL
TS -->|durable structured context| PL
CH -->|old turns compressed| TS
PL -->|plan / observations / status| ES
```
## State Transition Diagram
```mermaid
stateDiagram-v2
[*] --> planning
planning --> running: plan created
running --> waiting_user: ask_user step
waiting_user --> planning: user replies
running --> completed: respond step finished
running --> failed: step error
failed --> planning: retry / continue / config-trader reset
completed --> planning: new relevant request or retry flow
```
## Known Design Tradeoffs
### Strengths
- separates short-term chat from durable task summary
- allows blocked flows to resume
- supports replanning after every meaningful step
- can recover from stale assumptions better for dynamic config/trader requests
### Weaknesses
- `TaskState` is still summary-driven, so summarization quality matters
- planner still depends on model compliance for some transitions
- `ExecutionState` is single-track per user, not multiple concurrent workflows
- config/trader intent detection is heuristic and keyword-based
## Practical Guidance
### When to trust `TaskState`
Trust it for:
- user intent continuity
- open loops
- durable facts
Do not trust it for:
- whether current exchange/model/trader config exists now
- whether a specific operational action is currently possible
### When to trust `ExecutionState`
Trust it for:
- current plan continuity
- exact blocked step
- latest observation chain
Do not trust it blindly when:
- user has changed configuration outside the chat
- the system capabilities changed after deployment
### When to fetch live state again
Always prefer fresh tool snapshots before answering about:
- existing model configs
- existing exchange configs
- existing traders
- whether trader creation can proceed
## Suggested Future Improvements
- add workflow versioning so capability changes invalidate stale `ExecutionState`
- separate `waiting_user_confirmation` from generic `waiting_user`
- introduce code-level handling for short confirmations such as `是`, `好`, `继续`
- move dynamic state refresh from heuristic to explicit planner preflight stage
- support multiple concurrent execution sessions per user if needed

View File

@@ -1,453 +0,0 @@
# NOFXi Agent 记忆与规划设计
## 目的
本文说明当前 NOFXi agent 是如何处理以下能力的:
- 短期对话记忆
- 持久化任务记忆
- 持久化执行态 / 规划态
- planner 的执行与重规划
- 状态重置与恢复
本文主要对应以下实现文件:
- `agent/history.go`
- `agent/memory.go`
- `agent/execution_state.go`
- `agent/planner_runtime.go`
- `agent/agent.go`
## 总体模型
当前 agent 使用三层不同的状态:
1. `chatHistory`
用于保存当前会话最近几轮的原始用户/助手对话,驻留内存。
2. `TaskState`
用于保存跨轮次仍然有价值的结构化摘要,持久化存储。
3. `ExecutionState`
用于保存当前规划流程的执行态,支持流程中断后的继续执行。
这三层职责不同,不能混为一谈。
## 三层状态
### 1. `chatHistory`
定义位置:`agent/history.go`
作用:
-`userID` 保存最近的 `user` / `assistant` 消息
- 作为短期对话上下文
- 作为后续压缩进 `TaskState` 的原始素材
特性:
- 仅在内存中存在
-`maxTurns` 上限
- `/clear` 时会清空
- 不适合作为长期真相来源
典型内容:
- 最近几轮用户问题
- 最近几轮助手回答
- 临时措辞与上下文表达
### 2. `TaskState`
定义位置:`agent/memory.go`
作用:
- 保存持久化、结构化、不可轻易从工具重新推导出的上下文
- 通过 `system_config` 持久化
- 注入到 planner / reasoning prompt 中
存储 key
- `agent_task_state_<userID>`
字段:
- `CurrentGoal`
- `ActiveFlow`
- `OpenLoops`
- `ImportantFacts`
- `LastDecision`
- `UpdatedAt`
适合存放:
- 当前仍有效的用户目标
- 跨轮次仍然成立的高层未闭环问题
- 无法简单通过工具重新读取的重要事实
- 最近一次关键决策及原因
不适合存放:
- “等用户提供 API Key” 这类 step 级待办
- “调用 get_exchange_configs” 这类执行动作
- 实时余额
- 当前持仓
- 当前行情价格
- 是否存在某个配置这类会变化的状态
这些动态信息应该在规划阶段通过工具重新检查,而不是相信旧摘要。
### 3. `ExecutionState`
定义位置:`agent/execution_state.go`
作用:
- 保存当前执行中的工作流状态
- 支持 `ask_user` 之后恢复执行
- 持久化保存计划步骤、观察结果和最终状态
存储 key
- `agent_execution_state_<userID>`
字段:
- `SessionID`
- `UserID`
- `Goal`
- `Status`
- `PlanID`
- `Steps`
- `CurrentStepID`
- `Observations`
- `FinalAnswer`
- `LastError`
- `UpdatedAt`
它是 planner 的“工作态”,不是通用记忆仓库。
## 数据流
### 请求入口
入口函数:
- `HandleMessage(...)`
- `HandleMessageStream(...)`
流程:
1. 用户消息进入 `agent`
2. 优先处理 slash command 和显式直达分支
3. 其余请求进入 planner 流程:`thinkAndAct(...)` / `thinkAndActStream(...)`
### Planner 主流程
`agent/planner_runtime.go` 中的 planner 管线如下:
1. 把用户消息加入 `chatHistory`
2. 发出 `planning` SSE 事件
3. 加载 `ExecutionState`
4. 视情况重置过期的 `ExecutionState`
5. 视情况刷新动态配置快照
6. 调用 LLM 生成新的执行计划
7. 按步骤执行计划
8. 在关键状态变化后持久化 `ExecutionState`
9. 把助手回答加入 `chatHistory`
10. 视情况把旧对话压缩进 `TaskState`
## 短期记忆 vs 持久记忆
### `chatHistory` 里应该放什么
适合:
- 最近原始消息
- 对话措辞
- 最近一轮助手的表达方式
不适合:
- 长期真相
- 外部系统当前状态
### `TaskState` 里应该放什么
适合:
- 持续目标
- 跨轮次仍有意义的高层未闭环事项
- 用户明确讲过的重要事实
- 历史关键决策和原因
不适合:
- 当前 plan 中尚未执行的步骤
- “等待某个字段”“调用某个 tool” 这类执行级待办
- “系统有没有这个工具” 这种过时结论
- “当前有没有模型/交易所配置” 这种可变化状态
- 可以通过工具重新查询到的动态状态
### `ExecutionState` 里应该放什么
适合:
- 当前计划步骤
- 工具调用观察结果
- 当前是否卡在等用户补充信息
- 当前工作流的精确执行位置
- step 级待办和阻塞原因
不适合:
- 长期用户画像
- 通用长期语义记忆
## 规划逻辑
### 计划生成
`createExecutionPlan(...)` 会把以下信息送给 planner 模型:
- 当前可用 tool 定义
- 持久化用户偏好
- `TaskState` 上下文
- `ExecutionState` JSON
- 当前用户请求
planner 必须返回 JSON且步骤类型只能是
- `tool`
- `reason`
- `ask_user`
- `respond`
### 步骤执行
`executePlan(...)` 的执行循环如下:
- `tool`
调用工具并写入 observation
- `reason`
发起 reasoning 子调用并写入 observation
- `ask_user`
保存 `waiting_user` 状态并把问题返回给用户
- `respond`
生成最终回答并标记完成
每个步骤结束后,`replanAfterStep(...)` 还可以决定:
- continue
- replace_remaining
- ask_user
- finish
## 恢复执行
`ExecutionState.Status == waiting_user` 时,下一条用户消息会被视为对上一轮追问的回复。
当前保护机制:
- 从已有 plan 中提取最近一次追问内容
- 将用户回复作为 `user_reply` observation 追加
- 在 planner prompt 中注入显式的 `Resume context`
这样可以减少用户只回复 `是` 这类短消息时,被错误理解成全新意图的情况。
## 动态状态刷新
配置类与 trader 管理类请求本质上是动态请求,它们的真相可能在聊天之外发生变化,例如:
- 用户在 Web UI 中配置了交易所
- 用户在另一个页面新增了模型
- 用户在别处创建了 trader
因此,这类请求不能依赖旧的模型结论。
当前在 `planner_runtime.go` 中的保护措施:
- 通过 `isConfigOrTraderIntent(...)` 检测配置 / trader 意图
- 这类请求在 planner prompt 中不再注入旧 `TaskState`
- 同时刷新 `ExecutionState.Observations` 中的实时快照:
- `toolGetModelConfigs(...)`
- `toolGetExchangeConfigs(...)`
- `toolListTraders(...)`
这样 planner 会更多依赖当前系统状态,而不是依赖旧记忆中的描述。
## 重置策略
当前系统在以下场景会重置或弱化旧执行态:
- 用户说了类似 `再试``继续``try again``continue`
- 当前请求是配置 / trader 相关,并且旧 `ExecutionState` 已经失败 / 完成 / 正在等待用户
重置范围:
- `ExecutionState` 可能会被清空
- `TaskState` 不会整体删除,但在配置 / trader 请求中会被主动忽略
手动清理:
- `/clear`
这条命令会清掉:
- 短期 chat history
- task state
- execution state
## 压缩设计
`maybeCompressHistory(...)` 会在以下条件满足时把旧的短期对话压缩进 `TaskState`
- 最近消息数超过窗口
- 估算 token 数超过阈值
压缩流程:
1. 保留最近若干轮对话在 `chatHistory`
2. 把更早的内容总结成结构化 `TaskState`
3. 持久化新的 `TaskState`
4. 用最近消息切片替换 `chatHistory`
重要设计原则:
- `TaskState` 只保留长期有效上下文
- 不能把它变成动态运营状态的陈旧副本
## 当前架构图
```mermaid
flowchart TD
U[用户消息] --> A[HandleMessage / HandleMessageStream]
A --> B{是否命中直达分支?}
B -->|是| C[直接处理 slash command 或快捷分支]
B -->|否| D[thinkAndAct / thinkAndActStream]
D --> E[写入 chatHistory]
D --> F[加载 ExecutionState]
F --> G{是否 waiting_user?}
G -->|是| H[追加 user_reply observation]
G -->|否| I[创建新的 ExecutionState]
H --> J[若为配置或 trader 请求则刷新动态快照]
I --> J
J --> K[createExecutionPlan 调用 LLM]
K --> L[得到 execution plan]
L --> M[executePlan 循环执行]
M --> N[tool step]
M --> O[reason step]
M --> P[ask_user step]
M --> Q[respond step]
N --> R[写入 Observation]
O --> R
R --> S[replanAfterStep]
S --> M
P --> T[持久化 waiting_user ExecutionState]
T --> UQ[向用户返回追问]
Q --> V[持久化 completed ExecutionState]
V --> W[把 assistant 回复写入 chatHistory]
W --> X[maybeCompressHistory]
X --> Y[持久化 TaskState]
Y --> Z[返回最终回答]
```
## 记忆关系图
```mermaid
flowchart LR
CH[chatHistory\n内存态\n最近对话]
TS[TaskState\n持久化摘要\nsystem_config]
ES[ExecutionState\n持久化执行态\nsystem_config]
PL[Planner Prompt]
CH -->|最近原始对话| PL
ES -->|当前工作流 JSON| PL
TS -->|长期结构化上下文| PL
CH -->|旧消息压缩| TS
PL -->|计划 / 观察 / 状态| ES
```
## 状态转换图
```mermaid
stateDiagram-v2
[*] --> planning
planning --> running: plan created
running --> waiting_user: ask_user step
waiting_user --> planning: user replies
running --> completed: respond step finished
running --> failed: step error
failed --> planning: retry / continue / config-trader reset
completed --> planning: new relevant request or retry flow
```
## 当前设计的取舍
### 优点
- 将短期对话与长期摘要分离
- 支持在 `ask_user` 之后恢复执行
- 每个关键步骤后都支持重规划
- 对配置 / 创建 trader 这类动态请求,已经能更好抵抗旧结论污染
### 缺点
- `TaskState` 的质量仍然依赖总结效果
- 某些恢复逻辑仍依赖模型是否听话
- 每个用户当前只有一条 `ExecutionState`,不支持多个并发工作流
- 配置 / trader 意图识别目前仍是关键词启发式
## 实践建议
### 什么时候该相信 `TaskState`
应该相信它用于:
- 延续用户目标
- 跟踪未完成事项
- 保留长期有效事实
不应该相信它用于:
- 当前是否存在模型 / 交易所 / trader 配置
- 当前是否能够执行某个操作
### 什么时候该相信 `ExecutionState`
应该相信它用于:
- 当前工作流是否仍然连续
- 当前阻塞在哪一步
- 最近的 observation 链条
不应该盲信它用于:
- 用户在聊天外已经修改过配置的场景
- 系统能力或工具集发生变化后的旧结论
### 什么时候必须重新获取实时状态
以下场景应该优先重新通过工具获取:
- 当前模型配置
- 当前交易所配置
- 当前 trader 列表
- 当前是否满足 trader 创建条件
## 后续建议
-`ExecutionState` 增加版本号或能力签名,能力变化时自动失效
-`waiting_user_confirmation` 与通用 `waiting_user` 分开
-`是``好``继续` 这类短确认增加代码级识别
- 将动态快照刷新从启发式升级为显式 planner 预检查阶段
- 如果后续需要,支持一个用户多条并发执行会话

View File

@@ -1,272 +0,0 @@
# Agent 4 Skill 验收清单
本文档用于验收 Agent 对 4 个管理类 skill 的字段认知、工具调用和用户可见行为是否与页面编辑能力对齐。
当前范围:
- `model_management`
- `exchange_management`
- `trader_management`
- `strategy_management`
验收目标:
- 页面上能手动改的核心字段Agent 也能稳定改
- Agent 能回答页面上可见的字段与选项
- 模糊请求不会被硬塞进错误 skill
- 多字段一句话更新时,不会被窄动作截断
## 0. 前置条件
- 已完成登录
- 后端已启动
- 至少准备 1 条可编辑的模型、交易所、交易员、策略数据
- 测试前如果有旧上下文,先在 Agent 会话里执行 `/clear`
建议先跑自动化回归:
```bash
go test ./agent -run 'Test(ManageModelToolSchemaExposesEditableFields|ManageExchangeToolSchemaExposesEditableFields|ManageTraderToolSchemaExposesAdvancedFields|ManageStrategyToolSchemaExposesFieldLevelConfig|ModelManagementManualEditableFieldsAreCoveredByAgent|ExchangeManagementManualEditableFieldsAreCoveredByAgent|TraderManagementManualEditableFieldsAreCoveredByAgent|StrategyManagementManualEditableFieldsAreCoveredByAgent|ExchangeManagementUpdateSupportsManualFields|ModelManagementThinkAndActSupportsCompositeFieldUpdates|TraderManagementUpdateSupportsAdvancedManualFields|StrategyManagementThinkAndActSupportsGridAndRiskFields)'
```
对应测试主要在:
- [skill_dispatcher_test.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/skill_dispatcher_test.go)
- [config_tools_test.go](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/agent/config_tools_test.go)
## 1. 自动化覆盖基线
通过以下检查后,才进入手工验收:
- [ ] 4 个 skill 的 tool schema 已暴露字段级参数
- [ ] 4 个 skill 的 manual editable field 集合都被 agent 字段目录覆盖
- [ ] `model` 支持一句话同时改 `enabled + custom_api_url + custom_model_name`
- [ ] `exchange` 支持一句话同时改 `account_name + hyperliquid_wallet_addr + testnet`
- [ ] `trader` 支持高级字段更新
- [ ] `strategy` 支持 grid/risk 多字段更新
## 2. Model Skill
页面参考:
- [ModelConfigModal.tsx](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/web/src/components/trader/ModelConfigModal.tsx)
核心字段:
- `provider`
- `name`
- `api_key`
- `custom_api_url`
- `custom_model_name`
- `enabled`
手工验收:
- [ ] 说“列出我的模型配置”时,能列出当前模型
- [ ] 说“这个模型的接口地址改成 xxx模型名称改成 yyy并且禁用”时能一次成功更新
- [ ] 说“这个模型有哪些字段能改”时,回答至少覆盖 `API Key / 接口地址 / 模型名称 / 启用状态`
- [ ] 说“把这个模型启用”时,不会误触发重命名流程
- [ ] 说“把这个模型改成最好的”这类抽象诉求时,不应硬造字段值;应该解释或引导
通过标准:
- 回复文本明确说明已更新模型配置
- 页面刷新后字段真实变化
- 不出现“我还需要你明确要操作哪个对象”这种错误兜底
## 3. Exchange Skill
页面参考:
- [ExchangeConfigModal.tsx](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/web/src/components/trader/ExchangeConfigModal.tsx)
核心字段:
- 公共字段:
- `exchange_type`
- `account_name`
- `enabled`
- `testnet`
- CEX:
- `api_key`
- `secret_key`
- `passphrase`
- Hyperliquid:
- `api_key`
- `hyperliquid_wallet_addr`
- Aster:
- `aster_user`
- `aster_signer`
- `aster_private_key`
- Lighter:
- `lighter_wallet_addr`
- `lighter_api_key_private_key`
- `lighter_api_key_index`
手工验收:
- [ ] 说“把 Dex 的账户名改成 Dex ProHyperliquid 钱包改成 0xabctestnet 打开”时,能一次成功更新
- [ ] 说“这个交易所有哪些字段能改”时,能按当前交易所类型回答差异字段
- [ ] 说“把这个交易所禁用”时,不会误进入改名分支
- [ ] 说“列出我的交易所配置”时,能读出当前配置
- [ ] 对缺少必填凭证的创建请求,会明确指出缺哪一项,而不是模糊失败
通过标准:
- 回复文本明确说明已更新交易所配置
- 页面刷新后对应字段真实变化
- 不因为对象解析失败而掉到“请明确对象”
## 4. Trader Skill
页面参考:
- [TraderConfigModal.tsx](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/web/src/components/trader/TraderConfigModal.tsx)
页面核心字段:
- `name`
- `ai_model_id`
- `exchange_id`
- `strategy_id`
- `is_cross_margin`
- `show_in_competition`
- `scan_interval_minutes`
- `initial_balance`
Agent 扩展字段:
- `btc_eth_leverage`
- `altcoin_leverage`
- `trading_symbols`
- `custom_prompt`
- `override_base_prompt`
- `system_prompt_template`
- `use_ai500`
- `use_oi_top`
手工验收:
- [ ] 说“把交易员 A 切换到策略 B扫描间隔改成 8 分钟,全仓关闭,竞技场显示关闭”时,能一次成功更新
- [ ] 说“把高级交易员的 BTC/ETH 杠杆改成 8山寨币杠杆改成 4交易对改成 BTC、ETH自定义 prompt 改成 xxx启用 AI500”时能成功更新
- [ ] 说“这个交易员有哪些字段能改”时,至少能回答页面核心字段和 Agent 扩展字段
- [ ] 说“启动这个交易员”时,仍会保留高风险确认链路
- [ ] 说“为什么我的交易员不交易”时,仍能走诊断 skill不会被错误识别成 update
通过标准:
- 回复文本明确说明更新了交易员配置或绑定
- 页面刷新或查询结果能看到真实变化
- `交易对` 提取不会误吞后半句自然语言
## 5. Strategy Skill
页面参考:
- [StrategyStudioPage.tsx](/Users/zheweifang/Desktop/Nofx2/nofxi-dev/web/src/pages/StrategyStudioPage.tsx)
编辑器模块:
- `grid_config`
- `coin_source`
- `indicators`
- `risk_control`
- `prompt_sections`
- `custom_prompt`
- `publish_settings`
重点字段:
- 元信息:
- `name`
- `description`
- `strategy_type`
- `is_public`
- `config_visible`
- Grid:
- `symbol`
- `grid_count`
- `total_investment`
- `upper_price`
- `lower_price`
- `use_atr_bounds`
- `atr_multiplier`
- `distribution`
- `enable_direction_adjust`
- `direction_bias_ratio`
- `max_drawdown_pct`
- `stop_loss_pct`
- `daily_loss_limit_pct`
- `use_maker_only`
- Coin source:
- `source_type`
- `static_coins`
- `excluded_coins`
- `use_ai500`
- `ai500_limit`
- `use_oi_top`
- `oi_top_limit`
- `use_oi_low`
- `oi_low_limit`
- Risk:
- `max_positions`
- `min_confidence`
- `min_risk_reward_ratio`
- `btceth_max_leverage`
- `altcoin_max_leverage`
- `btceth_max_position_value_ratio`
- `altcoin_max_position_value_ratio`
- `max_margin_usage`
- `min_position_size`
- Indicators / timeframe:
- `primary_timeframe`
- `primary_count`
- `selected_timeframes`
- `ema_periods`
- `rsi_periods`
- `atr_periods`
- `boll_periods`
- `enable_ema`
- `enable_macd`
- `enable_rsi`
- `enable_atr`
- `enable_boll`
- `enable_volume`
- `enable_oi`
- `enable_funding_rate`
- Prompt:
- `role_definition`
- `trading_frequency`
- `entry_standards`
- `decision_process`
- `custom_prompt`
手工验收:
- [ ] 说“把策略 A 改成网格策略,网格数量改成 14ATR 倍数改成 2.5,最大保证金使用率改成 0.6”时,能一次成功更新
- [ ] 说“把选币来源改成静态,静态币改成 BTC、ETH排除 DOGEAI500 关闭”时,能成功更新
- [ ] 说“选币来源有哪些”时,能回答当前面板的来源类型与相关选项,而不是重复草稿摘要
- [ ] 说“这个策略里面的参数和 prompt 分别是什么样的”时,能走 explain/detail不会误更新
- [ ] 说“帮我创建一个不亏钱的策略”这类抽象请求时,不应直接强绑到字段创建;应该回退 planner 或引导细化
通过标准:
- 回复文本明确说明已更新策略参数或进入合理引导
- Strategy Studio 刷新后真实反映更新
- 不会把开放式目标误当作已可执行的精确配置
## 6. 跨 Skill 语义验收
- [ ] 模糊输入先过统一语义网关,再决定 `continue_active / resume_snapshot / start_new`
- [ ] 一个 skill 进行中时,问页面字段选项,优先走 explain不要硬落 execute
- [ ] 开放式目标型请求在参数不足时,优先回 planner不要强行进 hard skill
- [ ] 同一句话改多个字段时,不会只改其中一个窄字段
- [ ] `/clear` 后,旧的 skill session / workflow / execution state / snapshots 都被清空
- [ ] 切回旧话题时snapshot restore 能恢复到正确对象,而不是凭 heuristics 误接
## 7. 回归记录模板
每次验收建议记录:
- 日期:
- 提交版本:
- 后端 PID
- 前端地址:
- 本轮执行人:
逐项记录:
- 用例:
- 用户原话:
- 预期:
- 实际:
- 是否通过:
- 备注:
## 8. 当前结论口径
当本文档第 1 节自动化基线和第 2-6 节核心手工项全部通过后,才建议对外宣称:
“Agent 对 4 个 skill 已基本对齐当前页面可编辑能力,并具备稳定的 explain / execute / planner fallback 行为。”

View File

@@ -1,38 +0,0 @@
{
"scenarios": [
{
"name": "trader_create_reports_all_missing_prereqs",
"desc": "空白环境下创建交易员,应一次性报告所有必填槽位和依赖缺口。",
"turns": [
{
"user": "帮我创建一个交易员",
"want_all": ["名称", "交易所", "模型", "策略", "当前还没有可用交易所配置", "当前还没有可用模型配置", "当前还没有可用策略"]
}
]
},
{
"name": "strategy_update_risk_control_clamp_requires_acceptance",
"desc": "策略参数越界时,应先给出风控收敛说明,再等待确认应用。",
"setup": [
{
"tool": "manage_strategy",
"args": {
"action": "create",
"name": "风险策略",
"lang": "zh"
}
}
],
"turns": [
{
"user": "把风险策略的杠杆改成100",
"want_any": ["手动面板允许的范围", "按风控范围收敛"]
},
{
"user": "确认应用",
"want_any": ["确认应用", "风控范围", "已更新策略参数"]
}
]
}
]
}

View File

@@ -1 +0,0 @@
/Users/zheweifang/Desktop/Nofx2/nofxi/web/node_modules