一个你能掌控的本地智能体 - Figo Agent

用 AI 处理开发任务时,我越来越在意几个问题:它到底读了哪些材料?工具真的执行了吗?一次提问调用了多少次模型?对话变长之后,哪些内容还留在上下文里?
如果只能看到最后一段回答,很多问题就只能靠猜。
所以,我从零做了 Figo Agent:一个能在本地处理开发任务,也能检查模型请求、工具执行、上下文和 token 用量的智能体。
这篇文章把它的产品界面、核心架构、执行闭环和上下文机制完整展开。你既可以把它当成一件工具来体验,也可以沿着图表和源码,研究一个 Agent 是怎样运行起来的。
🧭 先用两张图认识 Figo Agent
① 工作台:配置模型、打开项目、发起任务、编辑文件与复盘过程。

Figo Agent 工作台新手引导:五步认识主要功能
② Trace Inspector:像抓包一样,检查模型请求、响应、工具、上下文与压缩。

Trace Inspector 新手引导:按任务和时间线检查模型交互
- 🔍 看得清: 上下文来源、执行过程与 token 用量可查,出了问题有据可循。
- 🎛️ 能干预: 选择模型、组织输入、约束工具操作,必要时停止运行。
- 🧩 可改造: 小内核、模块化设计,可以按需替换模型、扩展工具与上下文来源。
👥 适合哪些人
想学习或自己搭建 Agent 的开发者。 可以从一个可运行的小内核理解模型调用、上下文构造、工具授权、消息级压缩与事实落盘,再按模块替换模型、增加工具或扩展上下文来源。项目同时保留设计、实现与验证过程,适合对照代码研究取舍。
希望掌握本地 Coding Agent 执行过程的使用者。 在本项目中执行开发任务时,可以检查文件是否真正进入请求、工具实际返回了什么、哪次调用增加了 token,以及失败或压缩发生在哪个环节,适合需要排查问题和复盘任务的人。
关注 AI 工作方式的产品、测试、技术写作者与学习者。 在已经启动的工作台中,可以通过中文标签、来源记录和请求详情理解上下文与用量变化,无需先阅读源码或手工查询数据库。涉及权限、文件写入和命令执行的操作仍需要相应的技术判断。
观察运行与自行部署的门槛不同。 自行部署需要准备 Node.js、npm 和模型 API 配置,二次开发需要 TypeScript 与基本工程经验。项目面向可信本机的个人使用与研究,目前不适合作为多租户企业平台、跨平台成熟桌面产品或无人值守的全自主开发系统。
📦 Git 仓库
Gitee 仓库地址:https://gitee.com/hfqf1234/figo-agent
🚦 当前已实现功能与未来计划
✅ 已实现功能
| 状态 | 功能 | 已交付范围 |
|---|---|---|
| ✅ | 🧠 小内核与 CLI | CLI / REPL、Session / Turn、Kernel / Loop、静态插件注册与能力契约 |
| ✅ | 🗂️ Task 多轮任务 | 创建、切换、暂停、恢复、完成、取消与任务摘要;同 Task 历史连续,Task 不替代目标验收 |
| ✅ | 🔄 Tool Calling Loop | 模型动作解析、工具结果回填、继续调用、失败与停止处理、无进展保护 |
| ✅ | 🛠️ 工具与权限策略 | 文件读写、Shell、Git、网络与浏览器工具,参数校验、Policy、超时与执行留痕 |
| ✅ | 🤖 模型与提示词配置 | MiniMax、DeepSeek、GPT 官方与中转入口,模型列表与默认模型切换,自定义系统及持久用户指令 |
| ✅ | 📚 上下文构造 | 来源收集、条目取舍、显式文件引用、任务历史、实际消息快照与来源追踪 |
| ✅ | 🗜️ Pi 消息级压缩 | 窗口减预留量触发、安全切点、近期原文保留、大模型滚动摘要、手动压缩与一次超限恢复;容量与净收益另行验证 |
| ✅ | 📊 TokenUsage | 请求级实际用量、未知状态、规模估算、历史回填与 Session / Task / Turn 汇总;不含价格成本报表 |
| ✅ | 🧩 Memory 与 Skill 基础 | 稳定记忆读取、内置 Skill 与用户显式选择;不含自动长期记忆治理或动态市场 |
| ✅ | 💾 存储、Trace 与报告 | SQLite 状态源、请求响应抓包、结构化事件、JSONL 审计与中文 Markdown 报告 |
| ✅ | 🖥️ 本地 Web 工作台 | 工作区切换、文件浏览编辑、任务目录与对话、拖动布局、增量输出、停止、历史回放、Debug Console 与 Terminal |
| ✅ | 🔬 Trace Inspector | 七标签检查、上下文来源流转、请求响应、工具、压缩、原始 JSON、分页、只读查询与脱敏导出 |
| ✅ | 🍎 macOS arm64 内部包 | Electron 复用工作台与内核;最终 ZIP 解压应用已有核心链路内部自测,未签名、不公开发行 |
| ✅ | 🪟 Windows x64 安装器构建 | 已生成 NSIS 安装器,通过资源扫描与载荷校验;目标 Windows 安装和运行验收尚未完成 |
| ✅ | 🧭 产品基础体验 | 统一版本号、中文资源、联系作者入口、工作台与检查器首次引导及重看 |
❌ 未来计划与未完成项
| 状态 | 阶段 / 方向 | 待完成内容 |
|---|---|---|
| ❌ | 🎯 V2:任务级闭环 | Goal 状态与暂停恢复、Verifier 测试 / 构建验证,区分运行结束与目标验收通过 |
| ❌ | 🛡️ V2:执行治理 | 权限 profile、工具 dry-run、风险评估与失败回放 |
| ❌ | 📝 V2:记忆与方法 | 记忆候选、人工确认入库与第二批 Skill |
| ❌ | 💰 V2:成本分析 | 价格成本报表、可验证的单 Tool / Skill 实际用量归因 |
| ❌ | 🔌 V3:插件生态 | 动态插件、本地插件包与共享 Skill |
| ❌ | 🤝 V3:能力协作 | 多 Skill 编排与多 Agent 协作 |
| ❌ | 🧠 V3:长期记忆 | 去重、过期治理、向量或混合检索 |
| ❌ | 🌐 V3:外部扩展 | MCP、外部知识库与基于真实运行事实的持续改进 |
| ❌ | 📦 桌面正式发行 | 签名 / 公证、升级 / 回滚、异常退出恢复与原生交互等未完成验收 |
| ❌ | 🪟 Windows 实机验收 | 安装、运行、SQLite、Shell / Git、升级和卸载数据保留等平台门槛 |
| ❌ | 💻 其他桌面平台 | macOS Intel、Linux 的独立构建与安装验收 |
| ❌ | 🧪 压缩长期验证 | 实际大窗口容量、通用语义质量与长期净收益仍未完整证明,不自动续做实验 |
📌 当前安排:先实际使用,再按反馈优化。 压缩阈值学习、候选策略发布与自动调参仅保留为历史研究方向,不属于当前默认实施计划。新研发任务先明确范围,再形成设计、实施与验收记录。
🧭 一、概述
为什么要从零做一个 Coding Agent
让模型回答问题只是起点。一个 Coding Agent 还需要决定读取什么、保留多少历史、调用哪些工具、如何处理失败,以及什么时候结束任务。这些运行机制共同构成 Agent Harness,也就是模型之外的执行框架。
Figo Agent 的由来,是希望从零理解并掌握这套框架,让模型选择、上下文组织、工具边界和执行机制都成为可以检查与调整的部分。它既服务本地开发任务,也用于研究上下文工程、工具调用、记忆设计与执行闭环,并沉淀可复盘的工程资料。
Figo Agent 是一个面向个人开发与研究的本地智能体。你可以看清它读了什么、执行了什么、消耗了多少,也可以选择模型、组织上下文、约束工具操作,并通过修改代码扩展它的运行机制。
任务结束后,最终答案能说明结果,却不总能解释过程。一个文件明明打开过,为什么模型仍不了解它?对话越聊越长,哪些内容还在上下文里?多次工具调用增加了多少输入?历史被压缩后,模型保留了什么,又省略了什么?
Figo Agent 将文件来源、上下文条目、请求消息、工具结果、压缩摘要和每次调用的 token 用量关联起来。看清这些事实后,使用者可以决定引用哪些材料、切换哪个模型、是否停止当前运行;开发者还可以沿模块边界调整策略与实现。

工作台:文件编辑、任务对话与执行记录
工作台将文件编辑、任务对话与运行检查放在一起,让执行、观察与必要的人工干预处于同一个工作环境。
界面截图使用隔离演示数据;真实模型实验标注测试日期、环境与结果。
📚 看清上下文:模型到底读到了什么
上下文可以简单理解为"这一次回答时,模型能拿到的材料"。它可能包含当前问题、前面的对话、显式引用的文件、工具返回和历史摘要,但不是电脑里所有文件的集合。
Figo Agent 将材料从哪里来、为什么纳入、是否被截断或摘要,以及最终进入了哪一次请求连接起来。不熟悉数据库和接口协议,也可以从来源、消息和状态查看材料的去向。

上下文流:文件与 SQLite、构建快照、摘要和模型请求
例如,点击文件只是人的阅读,显式引用并经过校验与取舍后才进入模型输入;一次工具返回可以加入下一次请求;较早的对话可能被摘要覆盖,而近期消息仍保留原文。界面将这些状态分开,避免把"浏览过""准备过"和"实际发送过"混为一谈。
📊 看清 token:一次请求与整项任务分别消耗多少
Token 是模型处理文本的计量单位,与汉字数或账单金额并不相同。观察 token,既要看单次请求输入和输出,也要看整个任务经过多少次调用。
工作台保留每次模型请求的输入、输出和总 token,并按任务与会话汇总。模型没有提供用量时显示未知;上下文规模估算与接口返回的实际用量分开标注。

模型响应标签验收截图
随着任务推进,引用文件和工具结果可能让后续输入增大,历史摘要则可能缩短某次请求。单次上下文大小与累计消耗是两个不同的指标:请求变短,不代表之前的调用与摘要开销消失。
这让使用者可以检查一次长任务的消耗来源,而不只看到一个没有出处的总数。当前统计展示 token,不提供价格换算或单个工具的实际费用分摊。
🗜️ 看清压缩:哪些原文变成了摘要
长对话接近模型一次可处理内容的上限时,系统需要缩减后续请求携带的历史。Figo Agent 采用消息级压缩:较早内容生成摘要,近期原文按安全边界保留,原始记录继续保存。

压缩检查:摘要来源、保留边界与估算对照
压缩检查器展示触发原因、摘要内容、覆盖的来源消息、保留边界和前后规模,也区分"摘要已返回""条目已保存""下一次请求已采用"。因此可以直接核对压缩发生了什么,而不必从模型后来的回答倒推。
压缩首先解决容量管理。摘要本身会消耗 token,内容也可能有损;是否更快、更省,需要把摘要和后续调用一起测量。
✨ 项目特点
- 看得清。 文件、消息、工具、摘要与用量在同一工作台关联展示,不必先翻查原始日志或数据库记录才能理解运行过程;未知用量与上下文估算分别标注。
- 能选择、能干预。 使用者选择模型、编辑提示词、显式引用文件、切换任务,并可在执行中请求停止;工具操作受参数与 Policy 约束,文件浏览不自动入模。
- 可改造。 小内核依赖明确契约,模型、上下文来源、工具、记忆、Skill 与报告分别实现。开发者可以通过代码和配置调整能力,不必将所有变化写进主循环。
- 可复盘。 请求、响应、工具、错误与来源持续落盘,最终答复之外保留可复核的事实链,为排障和修改机制提供依据。
- 本地运行、共享内核。 CLI、Web 与 Electron 复用同一套运行逻辑,任务状态与运行记录保存在工作区,入口变化不复制执行机制。
🏗️ 二、项目架构
共享内核,不复制执行逻辑

共享内核、静态能力装配与只读观测
CLI / Web / Electron → AgentKernel → AgentLoop → PluginHost / Registry。入口负责交互,Kernel 负责装配与生命周期,Loop 负责单轮编排;模型、上下文来源、工具、记忆、Skill、报告与 Policy 通过明确契约协作。
| 模块 | 职责与边界 |
|---|---|
| CLI / REPL | 输入输出与控制命令分流,不承载业务编排 |
| Task | 连续工作主题与上下文归属;不做完成验收 |
| AgentKernel | 启动装配、Session / Task / Turn 生命周期 |
| AgentLoop | 上下文构造、模型与工具循环、失败与停止 |
| PluginHost | 静态注册、生命周期、能力索引,隔离具体实现 |
| Context | 来源采集、取舍、上下文包与消息级压缩 |
| Memory | 稳定信息读取与证据,不自动写入长期记忆 |
| Skill | 可复用任务方法、输入约束与输出结构,用户显式选择才执行 |
| Tool | 原子动作、参数校验、结构化结果与执行记录 |
| LLM / Policy | Provider 适配与权限决策分离,模型输出不是授权 |
| Storage | SQLite、审计流水与产物落盘,不编排业务 |
| Trace | 结构化事实及关联,不创造因果或模型思维链 |
| Reporter | 从已有事实生成中文 Markdown,不作为状态源 |
| TokenUsage | 请求级实际用量与估算观测,不决定压缩策略 |
| Web / Electron | 同源 HTTP / SSE 与界面适配,不直接读写 SQLite 或调用 Tool |
"小内核"以职责集中为目标,Context、Memory、Trace 等基础能力仍有独立边界。单一职责、依赖倒置、接口隔离与开闭原则体现在这些边界上:新增 Provider、Tool 或 ContextSource 应扩展能力实现,不把具体细节写进主循环。
项目从 CLI 小内核逐步扩展到 Task、受控工具、多轮调用、TokenUsage、本地 Web 与检查器,桌面端复用同一应用层。
运行对象与职责
Session 管理一次运行会话,Task 管理连续工作主题,Turn 对应一次普通输入,Run 记录 Web 异步执行状态。一个 Turn 可以包含多次 LLMCall,每次调用都有独立请求、响应与用量。
上下文链路按 Session → Task → Turn → ContextPack → LLMCall 组织;工具、消息、摘要与 Trace 通过真实来源引用关联。Task 界定历史范围,Run 描述执行状态,两者不替代 Goal 的目标验收。
一次任务的数据怎样流转

上下文与抓包数据流 GIF
动效与静态图描述同一条逻辑数据流,动画速度与实际执行耗时无关。

上下文数据流静态完整图
| 数据阶段 | 输入与输出 | 谁负责 |
|---|---|---|
| 人的输入 | 问题、显式文件引用、用户提示词 | Web / CLI 适配器与应用层校验 |
| 上下文构造 | 来源候选 → 条目与取舍 → ContextPack | ContextSource / ContextBuilder |
| 运行时消息 | 当前消息、工具反馈、摘要与近期原文投影 | AgentLoop 与消息/压缩存储 |
| 外部调用 | 模型 HTTP 请求/响应;Policy 允许的真实工具动作 | LLMProvider / ToolRegistry |
| 事实落盘 | 抓包、用量、工具、Trace 与来源 ID | 各模块 Store / Storage |
| 人的观察 | 查询投影、事件更新、报告与检查器 | 本地 API / SSE / Reporter |
Web 通过同源 HTTP 提交命令与查询,通过有序 SSE 接收更新;刷新和重连读取已持久化事实,不应重复执行有副作用 Tool。浏览器不直接持有数据库连接、运行配置原件或模型密钥。
执行、存储与观察的分层
执行层由 AgentKernel、AgentLoop 和 PluginHost 协作:Kernel 管生命周期,Loop 编排单轮,插件提供模型、上下文、工具与策略能力。
数据层以 SQLite 为状态源,保存任务、消息、摘要、抓包与用量;JSONL 保存顺序审计,Markdown 报告面向阅读。
交互与观察层由 CLI、Web 与 Electron 提供入口。本地 API 负责命令、受限查询和事件推送,检查器读取已有事实,不参与工具执行,也不修改压缩决策。
单一职责、接口隔离与依赖倒置贯穿这些边界。新增模型或工具通过契约与注册接入,运行流程不依赖具体厂商、页面组件或开发文档。
🧩 三、子模块设计与实现
3.1 AgentKernel 与 CLI:启动、装配与生命周期
Kernel、Loop 与入口的依赖顺序
启动先解析配置和工作区,再初始化 Storage、Trace 与会话/任务管理,创建 Session 和默认 Task,静态装配插件,最后把能力契约交给 Loop。Trace 依赖可用存储,插件 setup 需要运行上下文,Loop 需要完成装配的能力索引;启动依赖集中由 Kernel 管理。
Kernel 对入口返回结构化 TurnResult / CommandResult,入口负责显示;Loop 不创建新 Session,不初始化插件,也不直接拼 Markdown。存储或必需能力不可用时拒绝继续,并返回对应初始化错误。
CLI 是同步交互适配器,Web 通过 RunCoordinator 异步调用同一执行边界,Electron 再封装同一 Web。入口形态不同,不意味着存在三套上下文策略或三份工具循环。
CLI 负责输入输出、普通文本与控制命令分流。Kernel 返回结构化结果,CLI 展示答复、错误摘要和产物路径,完整细节由存储与 Trace 承担。Web 使用异步适配器,但仍进入同一 Kernel 执行边界。
3.2 Task:任务边界与多轮历史
Session、Task、Turn、Run 各管什么
- Session 是一次运行会话,保存运行归属与生命周期。
- Task 是连续工作主题,界定多轮历史进入上下文的范围。
- Turn 是用户一次普通输入,一轮处理中可以发生多次模型调用和工具调用。
- Run 是 Web 异步执行索引,连接 HTTP 接受、真实开始和最终完成或中断。
- LLMCall 是一次真实模型请求,是抓包和实际用量的基本单位。
Task 与 Goal 不同。前者回答"这些对话属于什么主题",后者才需要任务级计划、验收、暂停恢复与完成闭环;当前未实现 Goal;Task 的 completed 仅表示任务状态结束,代码正确性仍由测试和验收结果判断。
CLI 中已实现的任务控制如下,控制命令本身不创建普通 Turn:
| 命令 | 行为 |
|---|---|
/task new 名称 |
新建并选中 Task |
/task current、/task list |
查询当前或可运行任务 |
/task switch ID |
切换指定任务,遵守会话与状态边界 |
/task pause、/task resume ID |
暂停当前任务或恢复指定任务 |
/task finish |
完成当前任务并生成 task-summary.md |
/task cancel 原因 |
取消任务并记录原因 |
/compact 可选关注点 |
手动压缩当前任务,不新建普通 Turn |
active 可接收普通输入,paused 需恢复,completed 与 cancelled 不能继续当作当前活动任务写入。archived 是预留的归档状态,当前 Kernel 尚未提供 /task archive 命令。
跨会话任务目录

任务目录:最近任务、搜索与跨 Session 历史
任务列表覆盖当前工作区的多个 Session,支持最近任务、全部任务、搜索和分页。Task 标题优先采用该 Task 第一条用户消息,Session 标题取该 Session 第一条用户消息,不调用模型生成标题,也不改写历史原文。
- 打开 Task 后在同一面板进入对话,返回列表不结束任务,已打开文件保持原样。
- 历史 Session 可以查看,不因在列表中可见就获得继续写入权限;旧版未归属 Task 的 Turn 通过只读兼容入口展示。
- 当前 Task 可进行新建、切换、暂停、恢复、完成或取消等操作;Task 负责上下文归属,不替代 Goal 的任务验收。
- 刷新恢复视图时先校验真实 Session/Task 归属;浏览器偏好以工作区隔离,不改变数据库关系。
任务目录是只读查询投影。搜索和分页使用同一筛选条件计算总数,并区分总任务数与当前页条数;前端通过请求代次隔离迟到响应。
3.3 AgentLoop:模型与工具的执行闭环
AgentLoop 如何把意图变成动作

单轮主路径、工具授权与结果回填
- 接收普通输入,关联 Session / Task / Turn,构造可解释的 ContextPack。
- 组装提示词、历史、文件引用与工具定义;按策略检查压缩后调用所选模型。
- 区分最终答复、工具批次与协议错误;原生 tool_calls 可附带过程正文,正文不替代工具执行。
- 工具批次统一经 ToolRegistry 参数校验与 ControlledToolPolicy,再执行并记录真实结果。
- 有界工具反馈进入下一次请求,继续循环;协议修复、重复无进展、失败与用户停止各有明确处理。
- 正常结束后保存答复与报告;失败、中断保留证据,不用固定成功文案覆盖模型的真实结论。
文本 JSON 工具协议与原生工具协议分别解析,随后汇合到同一受控执行路径。循环不以固定工具轮次上限代替完成判断,但保留协议修复、无进展检测、超时和停止等保护。
图中模型与响应节点合并了条件压缩、协议判定及限次恢复,异常与停止集中表示;它是主路径总览。这些分支分别对应成功、失败、拒绝、无进展与中断状态。
循环状态与失败处理
单轮状态从构造上下文进入模型调用,随后分为最终答复、工具批次或协议错误。工具反馈回到下一次模型调用;最终答复进入产物生成;失败与中断分别终结,不把异常路径落到正常完成。
| 分支 | 处理方式 |
|---|---|
| 模型配置或 Provider 不可用 | 明确报错,不静默切换其他连接 |
| 参数或工具协议错误 | 记录解析错误,在协议允许次数内请求修复 |
| 原生正文与 tool_calls 同时出现 | 保留过程说明,按结构化调用执行,正文非空不触发格式修复 |
| 工具不存在、参数非法、Policy 拒绝 | 保留失败或拒绝结果,绝不执行被拒动作 |
| 工具异常、超时、输出超限 | 保留真实错误、退出码与截断标记,反馈有预算上限 |
| 相同动作和结果反复出现 | 无进展检测提示回答或说明证据缺口,持续重复则失败 |
| 用户停止或服务中断 | 传播停止到可中断调用,保留事实与中断态,不重放已有副作用 |
| 报告写入失败 | 分别保留报告错误与已落库的工具执行事实 |
协议解析、动作执行与消息反馈也分开:ToolCallingRuntime 组织批次,Registry 查找工具并经过 Policy,消息构造器处理反馈预算,Provider 只负责通信。各层错误保留请求、动作或来源关联,便于定位具体阶段。
执行时间线与模型增量
右侧时间线来自实际运行事件和模型增量。原生模式分别处理结构化动作与过程正文;文本模式在确认最终正文后释放增量,未确认时等待完整响应。工具协议 JSON 和隐藏思考块不进入最终答复区域。
Provider 保留完整返回,同时可向 Web 提供文本增量。RunCoordinator 合批记录增量,携带 offset 与 sourceLength;页面按游标去重、按原始偏移合并,显示长度与原始偏移分开处理,以适应脱敏后的长度变化。Turn 最终 output 才是完成态规范答案。
碎片化 SSE、原始 wire、重建响应和错误分别保存。流结束判断兼容 DONE,也兼容服务正常关闭且已有非空 finish_reason;两者都缺失按截断处理。普通 JSON 返回可走完整响应回退。解析器已覆盖模拟 SSE 分片与回退场景;早期真实中转测试曾发生首段等待超时,该端点的及时推送能力仍缺少通过记录。
历史活动查询目前最多返回最近 5000 条事件。页面仅在滚动位置接近底部时跟随新内容,阅读旧消息时保持当前位置。刷新重新读取事实,不重新提交消息。
3.4 PluginHost:能力注册与依赖隔离
PluginHost:把变化点从内核移出去
插件由 Manifest 声明 id、名称、版本、描述与能力;setup 返回能力实例,PluginHost 按类型和名称建立索引。重复插件 ID、重复能力键、缺少必需能力都明确报错。
当前生命周期是启动时静态注册、setup、收集能力、建立索引、标记 ready;注册、能力和 ready 事件进入 Trace。模型、上下文来源、MemoryStore、Skill、Tool、Reporter 与 Policy 都从统一入口取得,Loop 不直接实例化具体实现。
模型设置即时生效采用静态插件内部的连接配置注册表,更新 Provider 快照,不等于动态下载插件、热插拔插件或插件市场。动态插件、MCP 连接和在线安装仍属于后续能力。
这也是 SOLID 在本项目中的具体落点:入口、Loop、Store、Reporter 各守单一职责;内核依赖契约;不同能力分开接口;新增实现扩展注册,不把厂商和文件来源写死进编排层。没有明确变化来源的地方不提前增加抽象。
3.5 Context:来源收集与上下文构造
准备了什么,实际发送了什么
上下文不是把所有文件和历史一次性塞进 Prompt。当前 Turn、同 Task 历史、显式文件引用、被选中的 Memory 和工具结果按各自来源进入运行链路,保留来源、进入原因、优先级、预算估算与取舍记录。
| 对象 | 回答的问题 | 不等同于 |
|---|---|---|
| ContextItem / ContextDecision | 哪份内容为何纳入、排除、摘要或截断 | 磁盘上所有文件 |
| ContextPack | 这一 Turn 构建时准备了什么 | 轮内所有请求始终相同的消息列表 |
| context_messages | 运行时实际追加了哪些消息与工具反馈 | 经过脱敏的审计正文 |
| LLM HTTP Capture | 这一次实际发了什么、收到了什么 | 模型隐藏推理过程 |
| Memory | 哪些稳定信息可跨轮、跨任务复用 | 自动进入每次请求的全部历史 |
其他 Task 的明细不默认进入当前上下文;源工程开发文档和可选个人知识库也不是用户项目的默认上下文。自定义系统指令与持久用户指令由运行时组装,并以提示词版本指纹留痕。
ContextBuilder 的输入、输出与取舍
ContextSource 只收集来源候选,ContextBuilder 负责标准化、排序与取舍,AgentLoop 消费结果;文件读取逻辑不写进 Builder,来源插件也不决定整个循环。
当前主要来源为当前输入、同 Task 历史、显式文件引用、Memory,以及历史或本轮工具观察。当前输入必须保留;历史限定同 Task,文件引用经过版本校验,Memory 是否启用由配置控制。系统指令和持久用户指令由运行时组装,不借"项目资料"路径默认加载 Agent 源工程文档。
每个条目记录内容、类型、sourceRef、纳入原因、优先级、估算及取舍动作。ContextDecision 表达 include / exclude / summarize / truncate;排除、缺失与截断均记录原因;实际采集数量与候选全集区分统计。候选、实际纳入条目和最终发送消息是三种不同统计口径。
构造完成后保存 ContextPack 与条目,生成可读 context.md;工具调用后还会继续追加反馈、生成摘要或重建模型消息。模型的实际输入由具体 requestId 对应的抓包确认,初始构建包提供构造阶段的来源说明。
消息原文为什么必须先落库
压缩如果按消息中间切分,完整 Turn 就太粗。一个很长的 Turn 内可能有多次助手工具动作与工具反馈,只保留"前五轮/后五轮"无法表达安全切点。
context_messages 追加保存运行时实际消息、角色、usage 作用域、工具调用 ID、停止原因和投影标记。用户消息、助手响应、工具结果各有稳定 sourceRef;文本协议反馈、原生 tool 消息和合成失败结果同样保存。两段文字相同不代表同一来源,同一旧消息重复出现在多个请求也不重复导入。
这避免一种常见断裂:工具返回后用户取消,没有下一条抓包可反推反馈;或者后来用另一套预览预算重建,消息长度已经与当时不同。Trace 只记引用,脱敏后的审计正文不是模型原文源。
context_compactions 保存 Task 内递增 seq、baseEntryId、首个保留来源、覆盖来源、Markdown、文件操作、tokensBefore 与全部摘要请求。条目追加前在事务中复核基础版本,避免两个压缩结果覆盖同一基线。
3.6 Compaction:消息级压缩与恢复
压缩过程可见,原始历史保留
当前采用固定版本 Pi 的消息级压缩算法移植,不引入整个 Pi 运行时。Agent 内部策略决定何时压缩,所选模型负责生成有损 Markdown 摘要;不是让模型厂商适配器自行决定压缩流程。
| 环节 | 当前机制 |
|---|---|
| 触发时机 | 新用户输入追加前检查历史;工具结果追加且确需下一次请求时再检查;最终答复后不提前压缩 |
| 触发判断 | 严格比较 contextTokens > contextWindow - reserveTokens;等于阈值不触发,Task 累计用量不参与控制 |
| 当前规模估计 | 有有效 usage 锚点时结合新增消息估算;模型、系统或工具定义变化使旧锚点失效,无有效锚点时估算当前投影 |
| 保留边界 | 按 token 预算保留近期原文,选择安全切点,避免拆坏工具调用与反馈关系 |
| 摘要生成 | 同一选中模型生成结构化 Markdown,滚动更新;来源关系保存在存储元数据,不要求摘要复述长 ID |
| 默认参数 | reserveTokens=16384、keepRecentTokens=20000;运行参数统一在 JSON,可按 Provider / Model 覆盖 |
| 失败与恢复 | 明确超限及符合判定的最终长度失败最多压缩重试一次;取消、摘要失败或基线冲突不写成功条目 |
| 历史保留 | 消息、抓包、失败请求和成本记录继续保存;只改变后续投影,不删除原始事实 |
压缩标签把"检查过""发起摘要""摘要返回""条目提交""后续请求采用"分开呈现。前后 token 估算反映投影规模变化;整体开销还包含摘要请求、后续工具与模型调用,语义损失和净收益需要分别测量。
阶段状态: Pi 算法与三模型测试窗口配对功能验收已完成,进入用户实际使用与反馈阶段;实际大窗口容量和普遍净收益未完整验证。中转端点配置的官方容量参考不是端点容量实测结论,旧的固定 Turn / 字符阈值与自优化计划不代表当前策略。
Pi 算法的移植边界
核心算法固定参考 Pi 提交 6a1f799b675c8cb98320b71ff7ee44f160fe8d66,保留版权、MIT 许可证、来源与适配差异,不引入整个 Pi 运行时。估算、触发、安全切点、摘要序列化和滚动更新来自固定上游,消息契约、Provider、SQLite 与 Trace 属于宿主适配。
算法行为差异通过适配记录与符合性验证管理。移植范围不含分支树导航、阈值学习、候选策略发布或自优化平台。
判断只使用当前有效上下文,不使用 Session/Task 累计消耗。有效 usage 锚点必须对应当前模型、系统指令和工具定义;这些发生变化就重新估算。窗口未知保持未知,不按相似模型猜值;手动压缩与普通聊天不因此被禁止。
切点、摘要、滚动更新与重建
从消息尾部按 keepRecentTokens 预算寻找保留区,并避开会破坏工具动作与反馈配对的切点。没有可总结的前缀时记录原因并跳过摘要。
摘要输入将旧消息序列化,移除思考块,并对工具结果使用默认 2000 字符的摘要预览、标明截断;这只影响摘要请求,SQLite 中的原始工具结果不被改写。输出使用结构化 Markdown,组织目标、约束、进度、关键决策、后续步骤、关键上下文及读写文件信息。
来源 ID 与覆盖边界保存在元数据,摘要正文只负责语义内容。新 Markdown 不通过旧 JSON 的来源字段校验;存量 JSON 检查点继续按原格式读取,并可作为后续滚动摘要的已有摘要。
再次压缩时接续旧摘要与新进入覆盖区的消息,不每次把所有原始历史重发一遍。后续投影由系统指令、摘要、近期原文与本轮追加构成;工具定义独立随所选普通请求提交,摘要请求不授予工具执行能力。
普通历史摘要的输出上限为:
lua
min(floor(0.8 * reserveTokens), model.maxTokens)
Turn 前缀辅助摘要按对应分支使用:
lua
min(floor(0.5 * reserveTokens), model.maxTokens)
默认预留 16384 时,两者在模型最大输出未进一步限制的情况下分别为 13107 和 8192 token。maxTokens 是输出能力上限,不是每次必然生成的长度,也不是自动压缩触发阈值。
自动、手动与恢复共用同一机制
自动压缩在安全时点检查;手动 /compact 可带关注点,从同一准备、摘要与提交路径运行,成功后更新投影并返回命令结果,不额外创建普通 Turn 或自动继续一轮助手调用。
符合判定的超限或最终长度失败才进入一次恢复,不把普通截断或带未完成工具动作的响应全部当成超限。截断的工具调用被拒绝执行,已完成的工具在恢复时保持原结果。
恢复保留原失败抓包,用 recoveryOfRequestId 关联重试。失败尝试可从后续投影省略,但原文、Trace、抓包与成本仍在。摘要失败、用户取消或基线冲突不能推进成功条目;旧投影保留供恢复使用;后续调用仍超限时,返回实际失败结果。
压缩配置与模型容量
压缩参数集中保存在统一 JSON 中:
json
{
"context": {
"compaction": {
"enabled": true,
"reserveTokens": 16384,
"keepRecentTokens": 20000,
"summaryToolResultMaxChars": 2000,
"modelOverrides": {}
}
}
}
modelOverrides 用 providerId/modelId 精确匹配,只覆盖预留量与近期保留量,未覆盖项继承全局。非法参数明确报错,运行时仅使用这套 token 预算规则。
模型条目分别保存 contextWindow、maxTokens、maxTokensField 和 metadataSource。2026-10-16 的收尾配置快照为 MiniMax 窗口 1000000、GPT 中转参考窗口 1050000、DeepSeek 窗口 1048576;默认预留下的严格触发边界分别为 983616、1033616、1032192。这些数值是该日期的配置快照,服务能力与运行参数分别核验。
GPT 中转采用同名官方参数作为配置参考,端点满窗口容量仍未验证。默认模型与容量取实际配置及 Run 提交时的快照,历史记录保留原模型归属。
3.7 TokenUsage:请求级用量与规模估算
TokenUsage:实测、估算与未知分开
| 统计项 | 口径 |
|---|---|
| 实际输入、输出和总 token | 按一次 LLM HTTP 请求统计,只采用 Provider 返回的有效 usage;总量不再与输入、输出重复相加 |
| 用量完整性 | complete / partial / unavailable 分开;缺字段保持未知,不能补零 |
| 上下文估算 | ContextItem / ContextPack 与完整请求分别估算,二者不能直接相加,也不是计费值 |
| 请求用途 | 普通对话与压缩请求都进入统计,保留用途、模型和抓包引用 |
| 聚合范围 | Session / Task / Turn 汇总已知值,同时保留覆盖次数与缺失请求数 |
| 不提供的归因 | 不从总用量猜测单个 Tool / Skill 的实际消耗;价格成本与普遍压缩收益仍未实现或证实 |
CLI 可用 /usage 查看 Task 与 Session 汇总;report.md 和 task-summary.md 提供对应复盘。手动压缩不创建普通 Turn,其用量仍归 Task / Session。
请求归属、覆盖率与回填
普通请求和轮内压缩归当前 Turn,自动 Task 压缩归触发它的 Turn;手动压缩不创建 Turn,只进入 Task / Session 汇总。原始抓包 Turn 锚点与用于统计的 usage_turn_id 分开,旧抓包没有触发轮次时保持未知,不猜测归属。
解析接受通用的 prompt_tokens / completion_tokens,也兼容 input_tokens / output_tokens。0 是有效值,负数、小数、字符串或超安全整数无效;同一请求的 total 不与输入输出再相加。reasoning、cache 等厂商细项留在原抓包,不未经核对就重复加入总量。
新抓包与其 token_usage 在同一事务保存。旧抓包按缺失 requestId 幂等回填,不改原始响应,重启不重复计数。聚合只累加明确返回的字段,并显示每字段覆盖次数和缺失请求数。
ContextPack 估算与每次完整请求的 estimatedInputTokens 分开保存;多轮工具交互每次重新估算,不能把两者相加。一个任务的全部实际消耗可能很大,但它不是当前上下文窗口大小。价格表、汇率、缓存折扣与单个工具实际成本归因当前没有可靠实现,不显示推测值。
3.8 LLM 与配置:模型协议、连接更新和提示词
模型协议与厂商名称分离
llm 是连接条目数组,id 唯一,enabled 控制注册,activeModelId 指向默认启用条目。provider 表示调用协议,目前使用 openai-compatible,不以厂商名称决定编排、压缩或授权。
当前选择缺失、ID 重复、条目禁用或协议未知时返回配置错误。多条启用不等于同时调用多个模型,也没有自动故障回退。
toolCallingMode 为 text 或 native。text 对完整工具 JSON 与最终正文做排他分类;native 接受结构化 tool_calls,可与过程 content 共存,并按 tool_call_id 回填。普通与压缩使用同一选中连接,压缩请求不下发工具定义;输出字段差异通过 maxTokensField 映射,不以模型名写死。
模型目录由服务端获取,带超时、地址校验和响应上限;读取失败时保留当前连接的默认模型,并显示目录获取错误。地址变化不复用旧凭据;上游错误不原样回显敏感响应。
模型保存的事务边界
浏览器只提交草稿和配置版本;服务端先做身份、CSRF、活动 Run 与写互斥检查,再校验条目和版本。Kernel 经 PluginHost 的模型注册表准备新 Provider,准备失败不写文件。
临时文件原子替换成功后,同步提交 Provider、默认选择、容量元数据与脱敏集合,再发布仅含 ID、模型和版本的 settings.models.updated。写文件失败不能激活新连接,旧版本提交不能覆盖新配置。
设置页、默认切换、工作区切换和新 Turn 准入通过现有锁协调;读取请求体期间也不能被另一次写入穿插。其他已连接页面经 SSE 更新,前端使用请求代次拒绝旧 bootstrap 或旧模型目录响应覆盖新状态。
配置轮换、删除后的旧密钥,在本进程仍保留于历史脱敏集合中;重叠密钥先匹配长值,避免短前缀替换泄漏后缀。事件和响应只返回 hasApiKey,不回填密钥正文。
提示词、中文资源与安全策略
自定义系统指令进入初始 system 消息,持久用户指令置于本次输入之前,二者是用户本机数据。每个新 Turn 读最新版本,抓包记录实际发送内容,Trace 记录指纹,不把提示词内容当配置阈值。
中文资源承载按钮、说明、错误模板和固定 Agent 提示。当前浏览器文案已集中,部分后端错误和工具描述仍在 TypeScript,文案迁移尚未覆盖全工程;HTTP 路由、SQL 名称、状态枚举、危险操作保护与硬上限也不属于翻译或可随意编辑的文案。
3.9 Tool 与 Policy:动作能力和授权边界
工具能力与权限
| 能力 | 当前工具 |
|---|---|
| 本机与路径观察 | system_info、path_stat、workspace_summary |
| 工作区读取 | list_files、read_file、read_many_files、search_text |
| 工作区写入 | write_file、apply_patch |
| 命令执行 | run_command,限制工作目录、超时和输出大小 |
| Git | git_status、git_diff、git_log、git_add、git_commit |
| 网络与浏览器 | network_fetch、browser_open、browser_snapshot |
所有 Agent Tool 都经过 Policy,记录参数摘要、结果、耗时、错误和必要的退出码。工作区路径、危险操作、命令超时、输出截断和敏感字段脱敏是执行边界,不由模型自行解除。
list_files.depth=0 仅当前层,正整数表示允许进入的子目录层数,省略表示递归;write_file 需要创建父目录时显式传 createDirs: true。这些语义通过工具 Schema 告诉模型,不由开发文档控制。
3.10 Memory:稳定信息与当前上下文的边界
Memory:稳定信息,而不是另一份聊天记录
Memory 的设计区分 project、user、agent、knowledge 四类作用域,分别承载项目事实、用户偏好、运行经验和可复用方法。它与 Context、Trace、Artifact 的关系是:稳定信息可被检索,只有当轮被选中才进入 Context,运行过程仍记 Trace,可读产物只是表达。
当前运行实现以 SQLite memory_records 的按 scope 检索为主,记录 key、content、source 与时间等信息;是否注入由上下文配置与取舍决定。自动分类、候选记忆确认、向量检索、长期治理和完整写入/Markdown 同步流程尚未实现。
长期治理涉及证据、去重、过期、冲突与人工确认。模型总结与报告只是候选信息,稳定记忆需要独立的确认与存储;Memory 缺失或检索失败会留下对应记录。
3.11 Skill:显式选择的任务方法
Skill:方法与动作分开
Tool 负责读文件、执行命令等原子动作,Skill 描述任务怎么开展:需要哪些上下文、允许哪些工具、按什么步骤收集证据、输出什么结构、如何判定失败。
当前内置 project-understanding-skill 承载项目理解方法。只有用户显式选择才运行;"分析当前项目"这样的普通意图不按关键词自动激活。明确选择后进行预检并收集证据,工具仍经同一 Registry 与 Policy,Skill 不直接绕过工具读写文件。
激活、跳过、执行、失败与完成都是过程事实。最终答复由模型基于实际证据生成,Skill 返回的预检状态与固定过程文案不覆盖模型答复。
尚未实现多 Skill 编排、动态 Skill 包管理和自动 Memory 写入。当前实现侧重单个显式 Skill 的执行与留痕。
3.12 Storage:状态、审计与产物落盘
SQLite、JSONL 与 Markdown 分工
| 存储 | 记录内容 | 用途 |
|---|---|---|
| SQLite | Session、Task、Turn、Context、消息、压缩、抓包、用量、工具和事件等结构化状态 | 查询、关联、恢复与检查器事实来源 |
| JSONL | TraceEvent 追加流水 | 审计与按顺序复盘,不替代复杂查询 |
| Markdown | context.md、report.md、task-summary.md 与可读记忆产物 |
给人阅读,不反向控制程序或替代状态源 |
| 关键表 / 记录 | 能回答的问题 |
|---|---|
sessions / tasks / turns |
哪次运行、哪个工作主题、哪一轮输入 |
context_packs / context_items |
构造了哪些候选与最终上下文 |
context_messages / context_compactions |
哪些实际消息被保留、摘要覆盖哪些来源 |
llm_http_captures / token_usage |
每次请求实际传了什么,已知用量是多少 |
tool_calls / trace_events |
真实执行了哪些动作,发生了什么错误 |
工作区运行数据位于 .figo-agent/,主数据库为 .figo-agent/figo-agent.db;报告与审计产物放在对应 Artifact 目录。SQLite 时间列统一采用上海本地时间文本 YYYY-MM-DD HH:mm:ss.SSS,旧数据迁移必须可回滚且不重复偏移。
Storage 负责保存,Trace 负责事实,Reporter 负责表达:context.md 解释上下文准备,report.md 解释执行,Task 完成时的 task-summary.md 汇总任务证据。全文抓包与本地运行数据仍需按敏感资料管理。
SQLite 初始化、时间与写入竞争
Storage 初始化目录、数据库、Schema 和迁移,提供连接及 Artifact 路径与原子写入,不选择模型、不制定压缩策略。SQLite 初始化失败时终止启动并返回错误。
时间列统一为上海时间的毫秒文本;旧 UTC 或带时区文本在一次性事务中迁移,记录版本,失败回滚,版本标记防止重启时重复偏移。外部请求与响应 JSON 中的原始时间不随数据库列迁移被篡改。
多进程打开同一库时,通过 sqliteBusyTimeoutMs 等待短暂写锁,默认 5000 毫秒、允许 100--30000;超过等待仍上报真实故障。锁等待只解决短暂 SQLite 竞争,不取代"同一工作区只能有受控写入者"的运行租约,也不证明任意多进程并发执行安全。
3.13 Trace:结构化运行事实
Trace 的字段与事件边界
TraceEvent 至少包含事件 ID、上海时间、type、source、sessionId,以及适用时的 taskId、turnId 和结构化 payload。payload 保存来源、参数摘要、结果、状态、错误与关联。大段正文由对应存储保存,Trace 以结构化字段连接这些事实。
关键事件覆盖用户输入与输出、Session/Task/Turn 生命周期、Context 构造与取舍、Memory 检索、Skill 状态、Tool 与 Policy、LLM 请求响应、压缩与 Artifact。失败事件必须保留直接错误、触发输入摘要、相关来源与处理结果。
TraceRecorder 先写 SQLite,再追加 JSONL,并把允许公开的字段通知观察者。两者分别提供可查询事实与顺序审计,写入并非跨介质原子事务,当前也没有完整的自动补偿机制。UI 观察者只接收安全投影,不控制业务执行。
当前观测范围是关键链路结构化事件与只读检查器,尚未引入 OpenTelemetry、分布式 Span、自动异常归因或完整日志平台。模型隐藏推理不属于采集范围。
3.14 Reporter:从事实生成可读报告
Reporter 如何组织人类可读产物
Reporter 读取已有 Context、Turn、抓包、用量与 Artifact 索引,按中文模板组织报告并由 Storage 写入。它不调用模型创造运行事实,不改 Trace、不改 Memory,也不反向决定上下文或工具授权。
当前产物分别承担不同问题:
- context.md 列出本 Turn 已纳入条目的来源、摘要、原因与估算,实际请求全文保存在对应抓包中。
- report.md 组织本 Turn 输入、真实答复、上下文与请求级用量摘要。
- task-summary.md 在任务完成时聚合 Task 信息、Turn 时间线、模型抓包索引、用量和产物入口。
- trace.jsonl 追加事件流水,不是最终报告,也不作为模型原文重建源。
当前 Turn 产物按 Session / Task / Turn 分层,例如:
python
.figo-agent/
figo-agent.db
artifacts/
sessions/
<session-id>/
trace.jsonl
tasks/
<task-id>/
task-summary.md
<turn-id>/
context.md
report.md
Task Summary 是可读聚合,不等于 Goal 验收报告;报告正常生成,也不证明任务的测试、构建或业务目标已经通过。反过来,报告写入失败不能抹去已经落库的真实动作。
3.15 Web 工作台:文件、任务与人工操作
文件、对话与终端
工作台采用左侧文件树、中间编辑器、右侧 Task 列表或对话的一体布局。底部 Debug Console / Terminal 与文件编辑位于同一工作区域;分析面板按需打开,桌面分隔线可拖动,布局宽度只保存在浏览器本地。
| 区域 | 当前提供 | 需要注意 |
|---|---|---|
| 文件区 | 目录浏览、搜索、文本编辑、显式保存、文件或行引用 | 未保存草稿不能引用;版本冲突拒绝覆盖,搜索达到上限明确提示未查完 |
| 对话区 | Task 列表与对话原位切换、固定输入框、Turn 执行时间线 | 过程说明、工具结果和最终答复分开,工具协议 JSON 不作为普通正文展示 |
| 分析区 | 请求/响应、上下文、压缩、工具与 Trace 来源定位 | 查询已落库事实,标注缺失、截断与未知状态 |
| Terminal | 人工输入的本机逐条 Shell 命令与输出 | 不是 PTY/TUI;人工命令不经过 Agent Tool Policy,需自行判断风险 |
文件浏览与编辑独立于模型上下文。只有显式引用并通过 ContextBuilder 取舍后,文件内容才进入当前请求;停止和进程中断分别记录为对应运行状态。
界面使用浅色工作底面、细分隔线和紧凑排版。青绿色表示选中和主要动作,绿色表示成功,琥珀色表示警告,红色表示错误;状态同时有文字,不只靠颜色。路径和 ID 可换行或查看完整值,图标按钮保留中文名称与提示。
文件保存与显式引用怎样保证一致
文件接口与模型读文件工具是两条不同的入口。人点击文件只获得受限文本和 SHA-256 版本;人编辑后显式保存,服务端再次校验同源、CSRF、路径、大小、工作区写入租约与旧版本,才通过同目录临时文件原子替换。
如果磁盘文件已经改变,返回 409,不覆盖别人的新内容;保存失败保留草稿。关闭、切换文件或离开页面时提示未保存修改。当前编辑器只保存已有、可预览的文本,不提供文件创建、自动保存、分页读取或图片预览。
服务端通过路径规范化、lstat 与 realpath 限定工作区,过滤凭据文件、运行配置、隐藏项、.git、node_modules 和构建目录。符号链接、二进制、非 UTF-8 与超过限制的大文本不能绕过预览保护;当前文件编辑上限为 1 MB。
引用包含相对路径、起止行和已保存版本。发送前的引用只保存在浏览器草稿中;提交 Turn 时服务端重读并核对版本、行数与大小,再交给上下文构造。文件版本改变时拒绝旧引用,未发送的选择不进入模型上下文。
工作区、配置与布局是三个作用域
切换工作区不只是换一棵文件树,而是切换 Kernel、Session、SQLite、Task、文件服务、Terminal 与事件流。活动 Run、终端命令或写入会阻止目录切换;有冲突时返回 409,保留原工作区。
新目录经 realpath 确认存在且为目录后,服务为它建立 Session 和相应服务,关闭旧事件流与数据库,再让页面重载。旧引用、草稿和事件游标随切换清理;返回旧目录会建立新 Session,旧 Session 作为历史读取。
最近目录保存在配置根目录下的 .figo-agent/workspaces.db,只记录路径与访问顺序,不是路径授权表。启动时恢复仍可访问的最近目录,失效时按启动规则回退。模型配置、任务数据与浏览器布局分别在各自作用域保存和恢复。
桌面视图的文件树、Task、编辑器与分析区有最小宽度限制;分隔条支持拖动、键盘微调和双击复位。宽度不足 900px 时切换文件/任务工作区并隐藏列分隔条;布局缓存损坏时回退默认值,不改变 Agent 配置。
Debug Console 与人工 Terminal
Debug Console 回放安全的 UI 事件并经 SSE 更新,不是完整 HTTP 抓包,也不是隐藏思维链。Terminal 是用户直接输入的本机逐条 Shell 执行器,支持查看输出、状态、退出码、停止及 cd;不支持 PTY、交互式 TUI 或后台会话管理。
当前缺省限制为一次一条命令、120 秒超时、128 KiB 输出与最近 30 条内存记录,运行参数以实际 JSON 为准。服务重启后内存输出清空。持久事件记录命令指纹、目录、退出码和截断标志,不保存命令正文与输出。
Terminal 不经过 Agent 的 ControlledToolPolicy,也不自动把输出注入模型。它仍受本地 API 身份、Origin、CSRF 和工作目录边界约束,但不提供操作系统级隔离。人工命令由用户承担授权判断,密钥不适合通过终端输入。
3.16 本地运行协议:异步提交、幂等与事件重放
HTTP 命令与只读查询
LocalApi 处理身份、输入校验、DTO 与响应;RunCoordinator 负责 Web Run 和并发;QueryService 只读查询;UiEventStore 提供可重放通知。浏览器只导入前端代码和 JSON 契约,不导入 Node、SQLite、Policy 或配置原件。
本地接口按命令、查询与事件通道划分:
| 接口 | 行为与约束 |
|---|---|
| GET /api/v1/bootstrap | 获取工作区、当前 Session、活动模型、能力与事件游标,不返回密钥 |
| GET /api/v1/sessions | 当前工作区历史 Session 与 Turn 数,只读 |
| GET /api/v1/tasks | 工作区跨 Session 任务目录、搜索与分页 |
| GET /api/v1/sessions/:id/tasks | 会话任务及旧版只读历史入口 |
| GET /api/v1/tasks/:id/turns | 读取任务 Turn,不能借此恢复旧会话写入权限 |
| POST /api/v1/tasks | 新建 Task,不创建普通 Turn |
| POST /api/v1/tasks/:id/turns | 提交文本、幂等键与显式引用,返回 Run / Turn 标识 |
| POST /api/v1/tasks/:id/actions | 类型化任务控制,不把任意命令字符串直接当接口 |
| GET /api/v1/events?after=seq | 有序事件重放与持续 SSE |
| GET /api/v1/tasks/:id/activity | Task 过程与文本历史,当前存在返回数量上限 |
| GET /api/v1/files、/file、/files/search | 受限目录、文件与搜索,不创建模型工具调用 |
| PUT /api/v1/file | 校验版本后显式保存已有文件 |
| GET /api/v1/turns/:id/context、/captures、/trace | 读取本轮上下文、抓包与事件的安全投影 |
| GET /api/v1/tasks/:id/trace-inspector/events | 检查器统一列表,支持筛选与分页 |
| GET /api/v1/trace-inspector/events/:key | 读取对象详情,验证真实归属 |
| GET /api/v1/trace-inspector/events/:key/field | 按白名单与偏移继续读取脱敏字段 |
| GET /api/v1/trace-inspector/file | 独立脱敏当前文件预览,不覆盖执行时快照 |
| GET/PUT /api/v1/settings/models、/settings/prompts | 模型或提示词设置,经版本校验和受限写入 |
| POST /api/v1/settings/models/discover | 由服务端读取模型目录,不把密钥发回浏览器 |
| PUT /api/v1/models/default | 保存并更新默认模型,已提交 Run 快照不变 |
| GET/POST /api/v1/terminal、POST /terminal/stop | 人工终端查询、执行与停止 |
只读资源的参数与大小限制由服务端实施。分页能力按接口分别提供,Turn 历史与较早的 Trace 接口仍有查询范围限制。
一次提交如何避免执行两遍
页面发送 clientRequestId。服务端核对 Task、输入、引用和显式模型选择的指纹;同一键同一请求返回已有 Run / Turn,不重新调用模型或工具,同一键用于其他内容则拒绝。
服务端在事务中创建一次 Turn、一次 ui_runs 和 accepted 事件,提交成功后发布通知,再异步进入 Kernel。网络重试沿用原幂等键,复用已有 Run 与 Turn。
Run 状态是 accepted → running → completed / failed / interrupted。accepted 表示已持久化接受,不表示模型已完成;running 来自真实执行;interrupted 表示不能证明正常结束,不等同失败工具自动回滚。
每个服务实例只允许单活动 Run,工作区运行租约协调 CLI 与 Web 的副作用执行。冲突返回错误,当前不提供隐式排队。文件写入、目录切换和模型配置更新还有各自互斥约束。
SSE 断线、刷新与进程重启
ui_events 的 seq 是顺序游标,不能用时间戳或随机 Trace ID 替代。事件包含 session/task/turn/run 归属、kind、subjectId、安全 payload 与创建时间。需要成对提交的状态与通知在事务内保存,数据库成功后才推送。
页面先读状态与游标,再请求缺失事件并持续订阅。断线时显示失联,以最后确认 seq 重连;同 event_id 幂等应用。状态与抓包仍以 SQLite 查询为准,SSE 是及时更新通道,不是另一份状态源。
刷新只恢复当前仍运行服务的视图,不重新发送 Turn;服务重启会建立新的运行 Session,旧 Session 只读。启动时识别已无运行进程的未终结 Run,标 interrupted,保留工具和请求,不自动重复 Shell、Git、写文件或网络副作用。
本地身份与浏览器安全
服务只绑定 127.0.0.1,校验 Host 与 Origin,不开放任意跨域。启用访问码时,用启动码换取 HttpOnly、SameSite=Strict 会话 Cookie;写请求还需 CSRF。认证凭据不进入 URL、前端持久存储或 Trace。
免访问码模式面向可信单机研究,仍保留 Origin、CSRF、路径、请求体和输出预算校验。模型设置、SSE、抓包、文件预览、复制与下载必须沿同一脱敏边界;服务端配置不直接投影到前端。
查询使用受限参数与白名单,不接受任意 SQL、文件路径或 JSON 字段。敏感原文揭示与交互式批准执行尚未开放。
3.17 Trace Inspector:运行事实的只读投影
像抓包一样检查模型交互

Trace Inspector:统一事件列表与模型请求
检查器采用 Fiddler 风格的高密度布局:左侧统一事实表,右侧按标签检查选中记录。请求、响应、工具、上下文与压缩不是分散的日志页面,而是同一 Task / Turn 下相互关联的证据。
| 右侧标签 | 可以检查什么 | 事实边界 |
|---|---|---|
| 总览 | 选中事实的类型、状态、归属和关联入口 | 不按时间接近猜测因果 |
| 上下文流 | 本地来源、SQLite 消息、构建快照、摘要与请求之间的关系 | 构建时快照不等于每次实际发送内容 |
| 模型请求 | 模型、请求消息、工具定义、Headers 与请求体 | 凭据脱敏,实际发送内容以对应抓包为准 |
| 模型响应 | HTTP 状态、正文、原生工具调用、实际 usage 与错误 | 原生正文可与 tool_calls 共存,不因此认定已完成任务 |
| 工具调用 | 参数、执行结果、状态、错误和相关来源 | 工具结果是执行事实,不是模型自己描述的动作 |
| 压缩 | 检查、摘要请求、来源消息、保留边界、前后对照和元数据 | 响应成功、条目提交、下一请求采用是不同事实 |
| 原始 JSON | 脱敏后的结构化记录与关联字段 | 不提供未脱敏数据库导出,不展示隐藏思维链 |
支持筛选、分页、请求切换、Live 暂停、来源定位、复制与下载。切换 Task、工作区或选中记录时隔离异步结果;缺少消息快照或历史关联时明确降级,达到查询限制时提示未查完。文件来源优先展示执行时快照,再区分当前磁盘版本。
工具调用与原始记录
工具调用与原始记录视图,截图采用 2026-10-16 的隔离测试数据。

工具调用标签验收截图

原始 JSON 标签验收截图
检查器是独立的只读观察入口,查询不创建 Turn、不执行工具,也不修改上下文。
总览:对象与关联范围

总览标签的隔离验收截图
总览列出事件类型、状态、来源、时间,以及真实存在的 Session、Task、Turn、Run、ContextPack、requestId、toolCallId 和摘要条目引用。缺少直接引用的旧记录仅按 Task / Turn 范围归组,时间相邻不作为直接关联依据。
事件表展示序号、上海时间、事件类型、状态、Task、Turn、来源、名称及有依据的耗时和 token。实际零值保留;瞬时事件显示"不适用",进行中、未记录和模型未提供分别显示对应状态。
上下文流:构建快照与请求投影
第一层是 ContextBuilder 的构建快照,展示当时准备的条目、来源、理由、估算与取舍。第二层是所选 requestId 的实际消息:运行时可能已追加工具反馈、执行压缩或省略失败尝试,同一 ContextPack 并不保证所有请求发送相同内容。
来源表区分用户输入、显式文件引用、Task 历史、纳入时的 Memory、消息原文、新 Markdown 摘要与旧 JSON 检查点。请求消息表按实际顺序展示 role、sourceRef、所属 Turn、工具调用 ID、状态和正文。
一轮对话可以同时包含已被摘要覆盖的前缀与保留原文的后缀,压缩状态因此记录到消息级边界。两个工具结果即使正文相同,只要来源不同就仍是两条事实。
来源定位先看执行时快照,当前磁盘文件或当前 Memory 是独立观察,不覆盖历史。旧库缺少消息快照时,标记为兼容重建与预览预算未知;完整工具输出和当时模型收到的裁剪结果分别处理。
模型请求:选中一次真正的调用
请求页展示 provider、model、purpose、requestId、上下文关联、URL、method、脱敏 Headers、消息与工具定义。消息可按角色查看;工具定义展示当时提交的 Schema,不拿当前注册表反推历史。
同一 Turn 的每次调用独立保留。一次压缩可能产生历史摘要与 Turn 前缀摘要,页面通过全部 requestIds 关联主请求、辅助摘要与各自开销。请求输出上限字段按实际请求显示,未记录的值不套当前配置。
请求默认选择顺序为已有手选、事件明确引用、当前详情范围内第一条已记录抓包。最后一种仅方便浏览,不新增因果关系;明确引用的抓包缺失时保留缺失状态。切换请求同步刷新消息、投影、响应和选择器,并丢弃不属于新选择的迟到响应。
模型响应与工具:看到描述不等于执行过
响应页把状态、耗时、finish_reason、正文、原生 tool_calls、usage 和原始流 wire 分开。长 wire 通过独立字段读取,不占用结构化正文的展示空间;HTTP 失败、长度终止与被恢复的失败尝试分别保留记录。
原生正文可以是过程说明,有 tool_calls 时仍要经验证、Policy 和执行器,收到真实工具结果后再继续。文本协议仍对完整响应做排他校验,两种协议使用各自的分类规则。
工具页按轮次、意图、工具名称、状态、输入输出、耗时与错误展开。Policy 拒绝显示 DENIED 与拒绝原因;耗时来自工具记录的开始结束时间,模型实际 token 不分摊到单个工具。
Artifact 只展示真实 Task / Turn 归属。某次摘要请求与 report.md 处在同一 Turn,不等于该请求生成了报告。
压缩:过程、摘要、来源、对比与元数据
过程分别显示检查、准备切点、摘要调用、成功条目提交及后续请求采用。跳过、失败、取消和提交冲突有独立分支,没有事件的内部步骤不补造时间戳。

压缩过程的历史隔离验收截图
摘要展示 Markdown 正文、条目 ID、seq、baseEntryId 和来源元数据。条目已存在但完成事件缺失时,显示"已保存,完成事件缺失",不抹掉任一事实。

压缩摘要的历史隔离验收截图
来源消息展示覆盖前缀、首个保留 sourceRef、近期原文、工具配对与省略状态。新 Markdown 条目和旧 JSON 检查点分别解释,旧记录没有消息级边界就保持兼容状态。

压缩来源消息的历史隔离验收截图
前后对比展示当次 tokensBefore / tokensAfter、摘要字符量和保留消息数,说明是估算、usage 加新增估算或未知。仅在 before 大于零且两边均有效时计算 1 - after / before,负值表示投影增长。

压缩前后对比的历史隔离验收截图
元数据展示模式 auto / manual / recovery、触发原因、模型窗口、预留量、近期保留量、全部摘要请求、恢复关系与后续使用引用。历史参数按当次记录解释,不套用最新运行配置。

压缩元数据的历史隔离验收截图
只有明确 requestId、compactionAttemptId、compactionEntryId、sourceRef 或 recoveryOfRequestId 等引用才画直接关联。仅同 Task / Turn 的记录标记范围关联;没有依据就显示未知。当前 omitted 标记不能倒推早先请求当时也省略了消息。
原始 JSON、长正文与导出
原始视图保留脱敏后的 Trace、抓包、工具、ContextPack、消息与新旧摘要结构,Markdown 正文作为字符串保存,阅读预览与原始对象分开。
长 JSON 保留对象、角色、数组与来源结构,长字符串转换为可继续读取的字段引用。字段接口验证事件归属、字段白名单、JSON 路径、offset 和页大小,先对完整字段脱敏,再按脱敏后的内容分页,避免密钥跨边界泄漏。
默认护栏包括 200 项详情集合、16384 字符正文预览、900000 字符完整响应预算、8388608 字符单字段硬上限和 32768 字符字段分页;单页最大 131072 字符、预览节点 20000。安全协议另限制 JSON 深度和字段路径层数。它们是只读展示护栏,不是模型上下文或 Pi 压缩阈值。
字段预览、集合截断、硬读取上限和关联缺失是不同状态。复制与下载标注本视图、当前页和已加载字段的范围,导出完整性与分页位置分别记录。
分页、Live 与只读交互
普通模式默认每页 200 条,显示总数、当前页、总页数和本页范围,支持首页、上一页、下一页与末页。筛选先进入 SQL 再统一计数和取页,空结果各计数为 0。翻页替换当前表,不累计加载;筛选改变回到第一页,越界夹紧后同步真实页码。
Live 读取最近 500 条窗口,显示总数与窗口条数;实时窗口与普通页码分开,列表更新保留已有事件和请求选择。页码不能与 cursor 或 latest 混用。整页超预算时明确拒绝并提示缩小筛选,不少返几条后跳页漏记录。
默认列表只看业务事实,传输事件保留独立入口。交互验证覆盖键盘选行、标签切换、搜索、拖动、来源定位、Live 暂停、返回草稿、复制与下载,以及七个一级标签、五个压缩子标签和桌面/窄屏状态。浏览不会创建 Turn、调用模型或重放工具。
3.18 Electron:共享工作台的桌面适配器
Electron 的启动、数据与退出
Electron 主进程管理窗口、原生目录选择和服务生命周期;utilityProcess 运行已有 Node 本地服务。服务使用动态回环端口,主进程通过私有消息取得地址与一次性访问码,完成认证后给桌面专属分区设置会话 Cookie。
渲染窗口设置 nodeIntegration=false、contextIsolation=true、sandbox=true,拒绝任意外部导航和通用 Shell/文件系统 IPC。首次设置页只暴露模型保存与用户主动选目录等受限动作,不把 Agent 内核搬进 React。
配置位于 userData/data,提示词和最近目录随程序用户数据保存;工作区 .figo-agent 仍在项目中,不搬进安装目录。安装资源不包含真实配置、密钥、工作区数据库、测试数据或开发文档。
模型没配置或最近目录不可用时先停在设置页,用户主动浏览并打开工作区后才启动服务,不创建假工作区 Session。取消目录选择后继续停留在设置页。桌面始终启用会话鉴权,不因 Web 的免访问码设置而开放 API。
退出先停止接收新操作并请求服务关闭。有活动 Run 时需明确等待或强制退出,等待有界;强退后的未完成 Run 记录为 interrupted。新启动换认证 Cookie,旧 Cookie 不能复用到新服务;服务崩溃时窗口显示失联状态。
🚀 四、使用与开发
从任务到复盘

工作台典型操作路径
| 步骤 | 在工作台里做什么 | 对应边界 |
|---|---|---|
| 打开工作区 | 查看文件树、最近目录和本工作区任务 | 数据跟随工作区,不混入其他项目 |
| 阅读与编辑 | 打开文件、修改并显式保存 | 浏览文件不创建 Turn,也不自动进入模型上下文 |
| 提交任务 | 在固定输入框发送问题,按需附文件或已保存行引用 | 服务端校验路径、版本和行区间后构造上下文 |
| 执行与反馈 | 查看模型轮次、工具动作、错误和最终答复,必要时停止 | 模型提出动作,Policy 决定工具是否执行 |
| 检查证据 | 按需打开 Trace Inspector,定位请求、响应和来源 | 检查器只读,不触发新的模型调用 |
| 复盘与继续 | 阅读报告、追问同一 Task,或开始新任务 | 同 Task 保持连续上下文,不默认混合其他 Task 历史 |
文件阅读、任务执行与证据检查可以独立进行。检查器按需打开,不影响对话与工具执行流程。
🖥️ 本地 Web 工作台
准备可运行本项目 node:sqlite 的 Node.js 环境、npm 和可用的模型服务配置;项目使用 TypeScript,具体依赖与脚本以 code/package.json 和锁文件为准。
在仓库根目录执行:
css
npm --prefix code install
npm --prefix code run web:dev
用 Chrome 打开终端实际输出的地址,默认 http://127.0.0.1:4317。端口占用时可通过 FIGO_WEB_PORT 指定其他端口,以当前进程输出的端口为准。启用访问码时按终端提示认证;免访问码配置仅用于可信本机研究。
web:dev 会先构建前端再启动本地服务,不提供 HMR。前端源码变更需要重新构建与部署;模型设置保存则直接更新运行实例。
🤖 模型与提示词设置
- 打开工作台设置,选择服务连接,填写 API 地址、模型和密钥;可以逐个配置,不必一次填完所有模板。
- 保存成功后,当前进程的后续请求立即使用新配置,Session / Task 与草稿不重建,不需要因保存模型设置而重启服务。
- 活动 Run 期间不能保存;版本冲突、地址改变或配置错误会明确拒绝,失败不激活新连接。
- 聊天输入区可切换默认模型;已提交的 Run 使用提交时的模型快照,不随全局默认值改变。
- 提示词页分别编辑自定义系统指令和持久用户指令,新 Turn 读取最新版本,并记录提示词指纹。
设置响应不回显已保存密钥;留空并保持连接地址不变时保留原值,清除密钥需要显式操作。地址改变时不把旧密钥自动发送到新地址。
| 内容 | 位置与更新方式 |
|---|---|
| 模型、压缩、工具与工作台运行参数 | code/figo-agent.config.json;模型设置页提供经校验的即时更新,其余手工参数修改通常在启动时加载 |
| 用户自定义提示词 | code/.figo-agent/prompts.json,属于本机数据,新 Turn 读取 |
| 固定中文文案与提示模板 | code/locales/zh-CN.json,不承载权限、协议或安全上限 |
| 工作区任务与运行事实 | 工作区 .figo-agent/,不因模型设置保存而搬迁 |
| 桌面配置 | Electron 用户数据目录,与源码配置目录分离 |
写在最后:把掌控权留在人手里
对我来说,Figo Agent 的价值,不只是让 AI 多做几件事,而是让人能够看清过程、核对结果,并决定下一步怎么走。
模型负责提供能力,工程负责把能力连接成可靠的流程,而人保留检查与选择的权利。
我希望这个项目既能承载实际任务,也能沉淀一份可运行、可拆解、可复盘的 Agent 工程实践。
如果你正在学习 Agent、尝试构建自己的 AI 工具,或者只是想弄清楚"AI 到底是怎么帮我干活的",欢迎从一个真实的小任务开始体验 Figo Agent。
📦 项目地址:gitee.com/hfqf1234/fi...
除了本地代码安装,我还提供mac和windows的安装包,可以私聊获取。
💬 欢迎通过仓库 Issue 交流,也可以在工作台点击"联系作者",或添加微信 hfqf123。反馈问题时,请先脱敏,再提供复现步骤与相关记录。
不只看 AI 给出的答案,也看看这个答案是怎样形成的。