From 83bbc6f0f564a37d9944b8aa510f728e37833122 Mon Sep 17 00:00:00 2001 From: gdemonc <1502689916@qq.com> Date: Sun, 17 May 2026 13:54:14 +0800 Subject: [PATCH 1/5] docs(retrospective): add project growth writeup --- project-growth-retrospective.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 project-growth-retrospective.md diff --git a/project-growth-retrospective.md b/project-growth-retrospective.md new file mode 100644 index 00000000..e3fe12bd --- /dev/null +++ b/project-growth-retrospective.md @@ -0,0 +1,19 @@ +# 项目实践经历与个人成长复盘 + +如果让我总结自己在这个项目中的成长,我觉得最大的变化是,我从一开始主要站在前端实现的角度参与项目,慢慢转变成了一个会主动思考产品方向、需求收敛和团队协作方式的人。 + +项目最开始的时候,我们其实并没有特别完整地去考虑市场、需求和产品定位,而是想先做一个类 OpenClaw 的通用 Agent,把我们自己的想法实现出来。那个阶段大家的出发点更多是“先把东西做出来”,所以我们一直在往里面加功能,希望让它变得更强、覆盖更多场景。但回过头看,这种推进方式虽然很有冲劲,却也很容易失去边界。因为一旦没有明确的目标和取舍标准,项目就会变成“想到什么做什么”,功能越来越多,但主线反而会越来越模糊。这也是我后来真正理解“少就是多”的开始。 + +我的一个比较明显的转折点是在第二周。那个时候我开始强烈地感觉到,项目不能继续只靠功能堆叠往前推了,所以我主动去推动团队做市场调研,重新确认我们的产品定位,并把调研文档、市场资料、技术栈和前期的功能设想重新整理了一遍。更重要的是,这个过程不是简单地把资料收集起来,而是开始对最初的功能想法做筛选和删减,把真正符合产品方向的内容保留下来,把不必要的部分拿掉。对我来说,这是一次很重要的成长,因为我开始意识到,一个项目不是功能越多越好,而是定位越清晰越好,重点不是“能做什么都做”,而是“到底最该做什么”。 + +在这个过程中,我也逐渐认识到,产品定义文档、PRD 和协议文档并不是形式性的材料,而是整个项目能否稳定推进的核心工具。尤其是在我们这种前后端协同、多人并行开发的项目里,如果前期没有把这些内容整理清楚,后期的代价会非常大。我们后来能够比较顺利地往下推进,很大程度上就是因为先把产品定义、市场调研和需求文档整理出来了。有了这些之后,团队就不再是“走一步想一步”,而是可以围绕相对明确的目标去继续推进开发。 + +但与此同时,这个项目也让我吃到了很多“前期对齐不够”的亏。其中最典型的就是 API 协议文档。现在回头看,我会觉得协议文档其实是整个文档体系里最重要的一部分之一,因为它决定了前后端之间到底如何协作、对象如何流转、功能如何真正落地。我们当时的问题在于,这份协议文档主要交给了一个前端和一个后端去推进,而其他同学都在忙各自的事情,导致它并没有完整覆盖所有人的理解和想法。这个结果就是,后面开发深入之后,我们不断发现有些协议没定义、有些字段不完整、有些交互场景根本没有提前考虑到,最后就只能不断补协议、改协议。这个过程给我们带来了很大的返工成本,也让我非常深刻地意识到,协议文档绝对不能只是少数几个人的工作,它应该在 PRD 完成之后、正式进入开发之前,由团队共同参与完善和确认。 + +除了协议文档,PRD 这件事本身也让我有了新的理解。以前我可能会觉得,PRD 更像是一个开发前要先写完的规范文档,但在这次项目里我发现,PRD 虽然非常重要,但它并不是一份一成不变的东西,而是需要随着项目推进不断被修正和补全的。尤其是在我们第一次做这种项目、对很多问题想得还不够全面的情况下,很多最初看起来合理的设计,到了后面才发现并不成立,或者至少不够完整。这些问题一旦没有及时回写到 PRD 或其他真源文档里,就会给后面的开发带来很多麻烦。 + +同时,导师提出的一个想法也让我印象很深,就是在 AI Coding 的模式下,可以先快速做出原型,再反过来推 PRD。我觉得这个思路对我们很有启发,因为它让我们没有完全照着传统产品的路径走,而是做出了一个和当时其他产品不太一样的形态。比如我们没有采用很常见的聊天框式交互,而是把很多操作收在悬浮球上,希望把 Agent 做成一种更贴近操作系统、更自然融入用户环境的存在。虽然最后这个方向我们没有实现到特别理想的程度,但这个尝试本身让我开始意识到,产品设计不一定只是跟随现有范式,也可以通过原型先试出一种新的交互形态,再回过头去定义需求和产品逻辑。这种思考方式对我来说也是一个很大的进步。 + +在正式进入开发之后,我们也是一边做架构,一边推进功能。按照导师的要求,理想状态应该是在开发过程中不断优化架构,让架构随着真实需求逐步调整和演进。但从我们的实际情况来看,我觉得我们并没有真正做到这一点。我们的整体架构从一开始确定了 task 总线这样的主思路之后,后面其实没有发生特别本质的变化。我们也一直在面对一个问题,就是不知道该怎么继续拆模块、怎么判断哪些边界应该前置抽象、哪些又应该先保持简单。这个部分其实也让我认识到,架构优化不是一句口号,而是一种很具体的能力,它要求你不仅会实现功能,还要能从不断出现的问题里总结出更合理的模块边界和组织方式。虽然我们在这方面做得还不够好,但至少我开始清楚地看到自己的不足,也知道以后如果再做从 0 到 1 的项目,不能只盯着功能是否完成,还要更早地去关注结构是否健康、协作边界是否清晰。 + +所以如果要概括我的成长,我会觉得,这个项目让我完成了一次很重要的转变:我不再只是一个关注前端页面、交互和功能实现的人,而是开始学会从产品定位、需求取舍、文档真源、协议对齐和团队协作的角度去理解一个项目是怎么从 0 到 1 推进起来的。对我来说,这种成长比学会某个具体框架或者单独完成某个功能更重要,因为它让我真正开始理解,做项目不是单点突破,而是要让方向、需求、协作和实现一起成立。 From 03f3fe8969cc89f9a6462f8dbf41e9dbb4a3372b Mon Sep 17 00:00:00 2001 From: gdemonc <1502689916@qq.com> Date: Sun, 17 May 2026 17:22:07 +0800 Subject: [PATCH 2/5] docs(retrospective): add architecture overview notes --- project-architecture-overview.md | 486 +++++++++++++++++++++++++++++++ 1 file changed, 486 insertions(+) create mode 100644 project-architecture-overview.md diff --git a/project-architecture-overview.md b/project-architecture-overview.md new file mode 100644 index 00000000..995bc6b8 --- /dev/null +++ b/project-architecture-overview.md @@ -0,0 +1,486 @@ +# 项目架构理解总览 + +## 1. 我为什么要整理这份文档 + +这份文档主要是用来整理我对这个项目整体架构的理解,尤其是站在前端同学补后端认知的角度,去弄清楚项目主链路到底是怎么跑的。 + +这份文档目前主要想回答三个问题: + +1. 这个项目后端的主流程是什么? +2. 为什么这个项目会采用一种偏 `task-centric` 的总线式架构? +3. 为什么大家会觉得我们现在的模块拆分还不够干净,以及为什么后续优化会这么困难? + +## 2. 我当前对整体架构的理解 + +从现在的理解来看,这个项目并不是一个简单的聊天应用后端,而是一个以任务为中心的本地 Agent 系统。 + +它的大致主链路可以先粗略理解成: + +```text +前端交互 +-> JSON-RPC 请求 +-> RPC handler +-> orchestrator +-> runengine / executor / governance +-> delivery +-> 前端结果承接 +``` + +如果再具体一点,可以理解成: + +```text +输入 / 文本选中 / 文件拖拽 / 仪表盘动作 +-> 前端 RPC client 发请求 +-> local-service 收到 JSON-RPC method +-> handler 按方法名承接请求 +-> orchestrator 做主要业务决策 +-> runengine 维护 task/run 状态 +-> executor 在需要时执行模型或工具调用 +-> governance 处理授权 / 审计 / 恢复边界 +-> delivery 组装正式结果对象 +-> 前端收到 task / bubble_message / delivery_result +``` + +在这套架构里,不同层的职责大致可以这样理解: + +- `handler`:RPC 方法入口函数,负责把某个 method 的请求接住,再交给 orchestrator。 +- `orchestrator`:后端的主要业务中枢,负责判断请求怎么进入 task 主链,要不要确认,要不要授权,能不能直接执行,以及最后结果怎么返回。 +- `runengine`:运行态状态机,负责维护 `task` 和 `run` 的状态、时间线推进、通知发送等。 +- `delivery`:结果组装层,负责把运行时结果变成前端能消费的正式对象,比如 `bubble_message`、`delivery_result`、`artifact`。 + +## 3. 为什么我觉得这是一个 task-centric 的总线式架构 + +这个项目不是让每个功能各走一套完全独立的执行路径,而是尽量把正式动作都拉回到同一个主对象上,也就是 `task`。 + +也就是说,它想维持的主线其实更接近这样: + +```text +input -> task -> execution / governance -> delivery -> frontend +``` + +虽然在后端内部,仍然保留了执行兼容对象,比如: + +- `run` +- `step` +- `event` +- `tool_call` + +但是对前端、对正式主对象来说,核心锚点还是 `task`。 + +所以我这里说的“task 总线”,意思不是某个非常严格的中间件定义,而是说项目里很多不同来源的输入、能力和执行流程,最终都会被路由进同一条以 `task` 为中心的主链,而不是各自维护互相平行的功能流水线。 + +## 4. task 总线这种设计的优点 + +### 4.1 主链路很清晰 + +这个设计最大的优点之一,就是不同类型的输入最后都还能进入同一条正式任务链路,比如: + +- 悬浮球输入 +- 文本选中 +- 文件拖拽 +- 错误信息 +- 仪表盘动作 + +这样做的好处是,不容易演变成很多套彼此平行的功能状态系统。 + +### 4.2 它更适合 Agent 产品,而不只是一个聊天应用 + +这个项目不是只返回一句回答那么简单,它还涉及: + +- 多阶段执行 +- 工具调用 +- 确认流程 +- 授权流程 +- 正式交付 +- 审计与恢复 +- 记忆回写 + +在这种情况下,用 `task` 作为对外主对象是合理的,因为它能给整个系统提供一个统一的稳定锚点。 + +### 4.3 对前端承接更友好 + +从前端视角来看,task-centric 的模型更容易消费,因为前端主要围绕这些内容组织页面和交互: + +- task 列表 +- task 详情 +- task 状态 +- task 结果 + +前端不需要直接拥有所有底层执行对象的复杂语义。 + +### 4.4 更容易把治理能力内建到主链里 + +因为正式动作都会经过同一条 task 主链,所以高风险行为更容易统一接进治理链路里,比如: + +- 风险评估 +- approval request +- authorization record +- audit record +- recovery point + +如果没有统一的 task 主线,而是让很多功能各走各的路径,那么这些治理能力就很容易做得不一致。 + +### 4.5 更方便追踪和复盘 + +因为 task 成了统一锚点,所以更容易回答一个动作是怎么从输入走到最终交付的。 + +这对于下面这些事情都很有帮助: + +- 排查问题 +- 做任务详情页 +- 回看执行过程 +- 面试时讲清楚项目闭环 + +## 5. task 总线这种设计的缺点 + +### 5.1 orchestrator 很容易变重 + +这是我目前觉得最明显的风险。 + +因为很多流程最后都汇进同一条主链,所以 orchestrator 很容易逐渐承担越来越多的职责,比如: + +- 输入适配 +- 意图建议 +- 确认流程 +- 授权流程 +- 执行入口 +- 结果分流 +- delivery 和 memory 的后处理交接 + +如果这些职责持续往里面堆,orchestrator 就会变成一个很重的中心大脑。表面上看架构仍然统一,但内部模块就会越来越难拆干净。 + +### 5.2 `task` 对象本身容易承载过多语义 + +理论上 `task` 作为对外主对象是对的,但如果什么都往 task 上挂,它就可能逐渐同时承担: + +- 产品展示状态 +- 执行状态 +- 治理状态 +- 交付状态 +- 展示层状态 +- 兼容层字段 + +当一个对象承载的语义过多时,边界就会开始变模糊。 + +### 5.3 主链可能对轻量场景来说太重 + +统一主链是有价值的,但它也可能让系统变重,尤其是当所有交互都被强行往正式 task 流里塞的时候。 + +有些场景可能只需要轻量反馈,有些场景才真的需要完整的 `task -> governance -> delivery` 流程。如果这两类场景的区分不够清晰,架构就会把一些本来可以轻一点的交互也做重。 + +### 5.4 调试成本会更高 + +在简单系统里,一个请求可能就是调用一个函数然后返回结果。 + +但在这个项目里,一条正式链路可能会经过: + +- RPC handler +- orchestrator +- runengine +- executor +- governance +- delivery +- storage +- notifications + +这说明它更工程化,但也说明它的理解和调试成本更高。 + +## 6. 为什么我们会觉得模块拆分还不够干净 + +我现在的感觉是,问题并不只是“架构方向错了”,而更像是 task-centric 这条路本身是合理的,但后面围绕它的模块精细化拆分没有完全跟上。 + +### 6.1 中心很明确,但围绕中心的边界还比较模糊 + +我们大概都知道 `task` 是中心,但它周围很多职责仍然高度耦合在同一条核心流程里。 + +比如 orchestrator 在一条请求路径里,往往会同时碰到很多事情: + +- context capture +- intent handling +- task creation +- confirmation logic +- governance precheck +- execution start +- result adaptation +- post-delivery handoff + +即使这些事情已经拆成了一些 helper,它们的主流程控制权仍然高度集中在一起。 + +### 6.2 主链是有了,但缺少足够强的次级边界层 + +task 总线很擅长给出一条统一主链,但项目一旦变复杂,它还需要围绕主链继续长出更清晰的次级边界,比如: + +- 入口适配边界 +- 意图决策边界 +- 治理边界 +- 执行边界 +- 交付边界 +- 持久化边界 + +如果这些边界只是“概念上知道有”,但没有在代码结构和团队认知里都变得足够稳定,那大家就会觉得模块“不够干净”,哪怕系统本身是能跑的。 + +### 6.3 orchestrator 逐渐变成“所有故事都要在那里讲完”的地方 + +我觉得这其实是最难的地方。 + +一旦 orchestrator 变成必须把完整故事串起来的地方,那么后续做架构优化就会变得困难,因为每一次想拆出去,都要回答这些问题: + +- 哪些逻辑是真正属于 orchestration 的,应该留在中心? +- 哪些逻辑其实是领域策略,应该往外围拆? +- 哪些逻辑更偏展示适配,不该放在 orchestration 里? +- 哪些逻辑是执行细节,应该更靠近 executor 或 runtime? + +如果团队对这些问题还没有稳定答案,那么大家就会同时有两种感受: + +- 都觉得代码变重了 +- 但又都不太敢确定应该从哪里切 + +### 6.4 协议和跨角色对齐会直接影响模块是否清晰 + +模块是否干净,不只是代码问题,也是共享认知问题。 + +如果团队对下面这些东西没有完全对齐: + +- 正式的 task 对外应该暴露什么 +- 哪些东西属于内部执行兼容对象 +- 哪些对象应该协议化 +- 哪些状态其实只是本地 UI 状态 + +那么代码里的边界自然就会开始漂移。这样一来,即使架构大方向是对的,落到实现上也还是会越来越难收口。 + +这也和我们之前踩过的坑有关:协议没有充分对齐,后面很容易扩大成架构层面的混乱。 + +## 7. 为什么后续优化会这么困难 + +从我现在的角度看,优化困难主要有几个原因。 + +### 7.1 我们能看见“重”,但还看不清“切点” + +感觉架构越来越重,这件事通常比较容易感受到。 + +但真正难的是,有没有信心明确说: + +- 这个逻辑应该从 orchestrator 拆出去 +- 这个逻辑应该继续留在 orchestration +- 这个逻辑应该变成独立领域服务 +- 这个逻辑只是 builder 或 mapper 层 + +所以会出现一种很典型的情况:问题先被感知到了,但拆分策略还没有形成。 + +### 7.2 这套架构承载的是产品复杂度,不能随便简化 + +这不是一个很轻的 demo 架构,它本身要承载: + +- task 连续性 +- approvals +- audit +- recovery +- tool execution +- delivery results +- memory handoff + +所以优化不只是“把文件拆小一点”这么简单。如果抽错了,反而可能把主链拆得更混乱。 + +### 7.3 团队可能还没有形成完全稳定的模块语言 + +要做好架构优化,团队通常需要一套比较强的共享术语,用来讨论边界。 + +比如: + +- 什么叫 orchestration +- 什么叫 runtime state +- 什么叫 delivery assembly +- 什么叫 governance policy +- 什么叫 query hydration 和 formal state 的区别 + +如果这些词大家都只是“差不多理解”,但还没有形成稳定共识,那优化讨论就很容易停留在“感觉有点乱”,但很难真正变成明确可执行的拆分方案。 + +### 7.4 项目前期更关注能力堆出来,而不是长期结构 + +这个项目最开始是很强的想法驱动和功能驱动,这种推进方式本身很有价值,但它也意味着:能力增长速度,可能快过了结构沉淀速度。 + +这就很容易导致一种典型结果: + +- 系统方向是有意思的 +- 能力也确实做出来了 +- 主链也确实存在 +- 但内部模块清晰度没有同步跟上功能增长 + +## 8. 我当前的结论 + +我现在的看法是,task-centric 的总线方向本身不是问题。相反,对于一个有确认、工具执行、风险治理和正式交付的本地 Agent 系统来说,这个方向其实是很合理的。 + +更深层的挑战在于,一旦所有重要流程都汇聚到同一条 task 主线上,项目就必须继续补上这些能力: + +- 更清楚的领域边界 +- 更稳定的协议对齐 +- 更明确的职责拆分 +- 更成熟的模块语言 + +如果这些后续工作没有持续跟上,那么架构就会出现一种情况:方向是对的,但实现会越来越重,模块看起来也越来越不够干净。 + +## 9. 我在项目中期的一次重构设想,以及后来的反思 + +在项目中期,我其实有过一个比较明确的重构想法。 + +当时因为我们一直觉得总线型架构不太好拆,orchestrator 也有越来越重的趋势,所以我当时想,既然直接沿着 task 总线往下拆很困难,那不如先从更大的产品模块入手,把整个系统先拆成三个大模块,然后再在每个大模块里面继续细拆。 + +我当时设想的三个大模块是: + +- 悬浮球 +- 仪表盘 +- 控制面板 + +而且我当时的思路并不是只拆前端页面,而是想把前后端都尽量按这三个大模块去收口,也就是不再强行按照传统意义上的前后端边界去组织,而是让每个大模块既包含前端入口,也包含对应的后端能力。 + +我当时对这三个模块的理解大概是: + +- 悬浮球:负责上下文采集、输入承接和轻量输出 +- 仪表盘:不再保留过多分散模块,而是主要保留任务和便签,突出后端编排能力的承接和展示 +- 控制面板:把镜子、安全这些内容都收进去,更像是对系统数据、状态和治理能力的整理与配置中心 + +从当时的角度来看,这个方案有一个很明显的吸引力,就是它更贴近产品形态,也更贴近用户真正看到的三个大表面模块。这样一来,系统在讲述和理解上会更直观:悬浮球负责近场交互,仪表盘负责任务编排承接,控制面板负责系统整理和治理展示。 + +如果只是从“讲清楚产品结构”或者“提升表层可读性”的角度看,这个思路其实是顺的。 + +但是后来我慢慢意识到,这种拆法虽然直观,却不一定真的适合后端架构本身。 + +原因在于,这三个模块更像是产品表面的入口和展示形态,而不是后端最稳定的能力边界。因为一旦真的按这三个模块去切后端,很多底层能力还是会横着在模块之间流动。 + +比如: + +- 上下文采集不只会被悬浮球使用,其他入口也可能会复用 +- task 编排虽然主要体现在仪表盘,但它并不只属于仪表盘 +- 安全和授权虽然可以展示在控制面板里,但它们实际会直接影响悬浮球触发的执行链路 +- 镜子和记忆虽然可以被收进控制面板展示,但它们本身也会反向参与任务上下文和结果整理 +- delivery 的结果既可能通过悬浮球轻反馈返回,也可能进入仪表盘任务详情中被正式承接 + +这就说明一个问题:如果按“悬浮球 / 仪表盘 / 控制面板”去拆后端,大量真正重要的底层能力并不会因为这样拆就消失,反而会继续在模块之间横向穿透。 + +也就是说,这种拆法虽然让系统在表面上更整齐,但未必真的减少复杂度,反而可能把原本集中在中枢里的复杂度,变成很多跨模块来回流动的数据和依赖。 + +后来我进一步意识到,这其实是一种很典型的架构矛盾: + +- 按产品表面模块拆,通常更直观、可读性更好 +- 按后端稳定能力边界拆,通常更抽象,但长期更稳 + +而我当时那个方案,本质上更偏第一种。它确实解决了一部分“系统看起来怎么讲更顺”的问题,但不一定真正解决“底层怎么解耦更合理”的问题。 + +所以我后来会觉得,如果真的完全按这三个产品模块去切前后端整体架构,数据流通路径可能会变得更多,跨模块依赖也会增加。某种意义上说,那更像是在用表层可读性换底层结构复杂度。 + +现在回过头看,我觉得这个想法并不是没有价值。因为它至少说明我当时已经不满足于只看局部页面和功能,而是在试图用更大的模块视角去理解系统,去寻找一种让架构更容易拆开的方式。 + +只是后来我逐渐意识到,前端模块和后端模块不一定应该完全一一对应。 + +更合理的方式可能是: + +- 前端可以继续按产品表面模块组织,比如悬浮球、仪表盘、控制面板 +- 后端则更适合按能力边界组织,比如输入承接、上下文捕获、任务编排、运行态、治理、交付、记忆和查询装配 + +这样做的好处是,既保留了产品层面的可读性,也不至于让后端为了迎合页面结构而失去稳定边界。 + +所以这段中期的重构设想,对我来说最大的价值,不是它本身一定正确,而是它让我进一步意识到:架构拆分不能只看“页面怎么分”,还要看“真正稳定的职责边界在哪里”。 + +## 10. 为什么后期对 orchestrator 的拆分不等于真正的架构重构 + +回到原本的架构设计上看,项目后期其实已经对 orchestrator 做过一次明显的整理。原本可能更接近单一的大文件,后来逐步拆成了很多按功能命名的文件,比如: + +- `task_start.go` +- `task_confirm.go` +- `task_query.go` +- `task_delivery.go` +- `task_control.go` +- `task_queue.go` +- `settings.go` +- `social_chat.go` + +从表面上看,这当然已经算是一种“拆分”了,而且它也不是没有价值。 + +我觉得这次拆分带来的收益主要体现在: + +- 单文件没有那么巨大了 +- 某个功能路径更容易定位代码 +- 局部阅读体验比之前更好 +- 团队在改某一类逻辑时,至少不用总是进入同一个超级大文件 + +但是从我现在的理解来看,这更像是一次“文件级拆分”或者“功能级拆分”,而不是一次真正意义上的“架构级重构”。 + +因为真正的架构重构,通常不只是把代码从一个大文件分散到多个小文件,而是要进一步回答这些问题: + +- 职责边界有没有重新定义? +- 模块之间的依赖方向有没有更清晰? +- 哪些逻辑属于 orchestrator,哪些逻辑不属于,是否真的迁走了? +- orchestrator 是不是还在承担几乎所有核心流程的控制权? +- 系统有没有从“中心统管一切”变成“中心编排 + 外围能力自治”? + +而从现在这个目录状态来看,虽然文件变多了,但很多核心流程控制权其实还是集中在同一个 `orchestrator.Service` 上。 + +也就是说,原来是: + +- 一个很大的 `orchestrator.go` + +后来变成: + +- 一组按功能切开的 `task_start.go`、`task_confirm.go`、`task_query.go` 等文件 + +但这些文件本质上还是围绕同一个中心对象、同一个 orchestrator 包、同一套主流程控制逻辑在工作。 + +所以我会觉得,这种拆法虽然改善了可读性和局部维护性,但没有真正改变系统层面的结构关系。 + +换句话说,它更像是: + +- 还是同一个大脑 +- 只是把不同功能分别写到了不同纸上 + +而不是真正把不同的器官拆出来,让它们各自负责更稳定的能力边界。 + +我现在觉得,这种“像拆了但又没完全拆”的感觉,恰好说明了一件事:这次拆分的主要维度仍然是“按功能切片”,而不是“按领域边界重构”。 + +比如: + +- `task_start.go` +- `task_confirm.go` +- `task_control.go` + +这些更多是在按流程动作拆文件。 + +但是更深层的问题并没有因此自然消失,比如: + +- 哪些逻辑属于输入适配 +- 哪些逻辑属于任务状态推进 +- 哪些逻辑属于治理策略 +- 哪些逻辑属于结果展示适配 +- 哪些逻辑属于 post-delivery handoff +- 哪些逻辑属于 protocol-facing response assembly + +如果这些边界没有真正迁移到更清晰的能力层里,那么即使文件拆开了,orchestrator 依然会是那个知道太多、负责太多的中心。 + +所以我现在对这次拆分的判断是:它当然有价值,但它解决的主要是“单文件过大、代码难找”的问题,而没有根本解决“orchestrator 是否过重、职责边界是否足够清晰”这些更深层的架构问题。 + +我觉得这类拆分更适合被理解成: + +- 代码整理 +- 文件整理 +- 功能分文件 + +而不应该直接等同于: + +- 架构已经重构完成 + +如果要说我心里更接近“真正重构”的目标,那至少应该做到下面这些方向中的一部分: + +- orchestrator 更像主流程编排层,而不是几乎所有细节都亲自处理 +- 输入承接、确认、治理、交付这些能力边界更加独立 +- response、bubble、delivery 这类展示适配不要长期混在编排逻辑里 +- task 状态推进和业务决策不要过度缠在一起 +- protocol-facing 组装、runtime state、domain policy 之间有更明确的分层 + +也就是说,真正想追求的不是“把 `orchestrator.go` 拆成更多文件”,而是“让 orchestrator 不再需要理解和控制所有细节”。 + +从这个角度看,我现在会认为,后期对 orchestrator 的调整更像是给后续真正架构重构打基础,而不是重构本身已经完成。 + +## 11. 一个适合面试表达的简短总结 + +如果后面我要简短概括这套架构,我现在会这样说: + +> 我们项目采用的是一种 task-centric 的后端主链设计。它的优点是能把多入口输入、执行、治理和正式交付统一围绕 task 收口,这一点很适合 Agent 产品;但它的挑战也很明显,就是一旦 task 主线成为所有事情的中心,orchestrator 和周边边界就会变得难拆,如果协议、职责和模块语言没有持续对齐,架构实现就会越来越重。 From 5898826e5333677c4b90099fb609016fd6d6ab74 Mon Sep 17 00:00:00 2001 From: gdemonc <1502689916@qq.com> Date: Sun, 17 May 2026 18:07:45 +0800 Subject: [PATCH 3/5] docs(retrospective): add core object model notes --- project-core-object-model.md | 537 +++++++++++++++++++++++++++++++++++ 1 file changed, 537 insertions(+) create mode 100644 project-core-object-model.md diff --git a/project-core-object-model.md b/project-core-object-model.md new file mode 100644 index 00000000..21648ec9 --- /dev/null +++ b/project-core-object-model.md @@ -0,0 +1,537 @@ +# 项目核心对象模型理解 + +## 1. 我为什么要整理这份文档 + +在继续理解这个项目后端的时候,我发现光理解模块名还不够,更重要的是要先理解系统里几个最核心的对象到底各自负责什么。 + +因为这个项目虽然对外一直强调 `task`,但后端内部实际上还同时存在: + +- `run` +- `step` +- `event` +- `tool_call` +- `delivery_result` +- `artifact` + +如果这些对象的分工没有搞清楚,就很容易出现两种问题: + +1. 看代码时觉得什么都混在一起 +2. 面试或者复盘时,只能模糊地说“有任务、有执行、有结果”,但说不清它们为什么要分开 + +所以这份文档主要是为了回答: + +1. 这些核心对象分别是什么? +2. 它们为什么不能混成一个对象? +3. 它们之间在主链路里是怎么配合的? + +## 2. 一个最重要的总理解 + +我现在对这个项目核心对象关系的理解可以先概括成一句话: + +> 对外主对象是 `task`,对内执行兼容对象是 `run / step / event / tool_call`,正式结果出口是 `delivery_result / artifact`。 + +这句话非常重要,因为它说明这个系统不是围绕“聊天消息”组织的,也不是所有信息都塞进一个大对象里,而是有明确的对象分层。 + +可以先粗略理解成这样: + +```text +task +-> 负责对外承接“这是一个什么任务” + +run +-> 负责对内承接“这个任务的一次执行尝试” + +step +-> 负责描述任务执行过程中的阶段 + +event +-> 负责记录执行过程中发生了什么事情 + +tool_call +-> 负责记录具体某次工具调用 + +delivery_result +-> 负责正式结果怎么交付 + +artifact +-> 负责正式产物本身落成为什么文件或对象 +``` + +## 3. `task` 是什么 + +### 3.1 `task` 是系统对外的主对象 + +`task` 可以理解成: + +- 用户发起的一件正式事情 +- 前端列表、详情页、结果页都围绕它组织 +- 用户能感知到的核心任务单元 + +比如从前端角度看,用户更容易理解的是: + +- 我发起了一个任务 +- 这个任务现在在处理中 +- 这个任务等待授权 +- 这个任务已经完成 +- 这个任务产出了结果 + +这些表达本质上都是 `task` 视角。 + +### 3.2 为什么 `task` 不能等于所有东西 + +虽然 `task` 是对外主对象,但它不能简单等于内部所有执行细节。 + +因为如果把执行过程、工具细节、运行事件、结果交付、恢复信息全都直接平铺在 `task` 上,那么 `task` 就会越来越胖,越来越难维护。 + +所以更合理的做法是: + +- `task` 负责对外主语义 +- 其他对象负责补充它的内部执行和正式交付细节 + +### 3.3 可以怎么理解 `task` + +我现在更愿意把 `task` 理解成: + +> 一个“用户看得见、系统可追踪、前后端都能围绕它组织信息”的正式任务锚点。 + +## 4. `run` 是什么 + +### 4.1 `run` 是任务的一次执行尝试 + +`run` 不是另一个对外任务,而更像是: + +- 一个 task 的某一次运行实例 +- 系统内部真正执行时对应的一次 attempt + +这意味着: + +- 一个 task 通常会映射到一个当前 run +- 如果任务重试、恢复、重新启动,可能会有新的 run + +所以 `task` 和 `run` 的关系可以理解成: + +```text +task = 这件事本身 +run = 这件事的一次具体执行 +``` + +### 4.2 为什么要单独有 `run` + +因为对外用户关心的是“任务完成了没有”,但对内系统还要关心: + +- 这是第几次执行 +- 这次执行有没有中断 +- 有没有重新启动 +- 有没有恢复后重跑 + +这些信息如果没有 `run` 这个层次,就会很难清晰表达。 + +### 4.3 可以怎么理解 `run` + +我现在会把 `run` 理解成: + +> `task` 的内部执行实例,是后端为了编排、重试、恢复和排障而保留的运行态对象。 + +## 5. `step` 是什么 + +### 5.1 `step` 是任务过程中的阶段 + +`step` 不是一个完整任务,也不是一个事件,它更像是任务时间线中的一个阶段。 + +比如一个任务可能会经历: + +- 输入已承接 +- 意图确认 +- 等待授权 +- 生成输出 +- 返回结果 + +这些东西更适合作为 step 来表达。 + +### 5.2 为什么要有 `step` + +因为如果只有 task 状态,比如只有: + +- processing +- waiting_auth +- completed + +那信息还是太粗。 + +而 `step` 可以让任务详情、时间线、状态推进变得更细。 + +所以 `step` 负责的是: + +- 阶段感 +- 时间线感 +- 执行过程的可视化 + +### 5.3 可以怎么理解 `step` + +我现在会把 `step` 理解成: + +> task 在某次执行过程中所经过的阶段化节点,用来表达时间线和过程推进。 + +## 6. `event` 是什么 + +### 6.1 `event` 是执行过程中发生的事情 + +如果说 `step` 更像阶段,那么 `event` 更像“在执行过程中发生的一次具体变化或记录”。 + +例如: + +- task.updated +- delivery.ready +- tool_call.completed +- approval.pending + +这些都更接近 event。 + +### 6.2 为什么 `event` 不能直接等于 `step` + +因为阶段和事件不是一回事。 + +- `step` 更稳定,更像流程中的阶段 +- `event` 更细粒度,更像运行过程中实际发生的一次事情 + +比如“等待授权”可以是一个 step, +但“生成了 approval.pending 通知”则更像一个 event。 + +### 6.3 可以怎么理解 `event` + +我现在会把 `event` 理解成: + +> 任务运行过程中产生的细粒度事实记录,用来支撑通知、回放、追踪和排障。 + +## 7. `tool_call` 是什么 + +### 7.1 `tool_call` 是一次具体工具调用 + +`tool_call` 比 `event` 还要更具体,它只关心: + +- 某次具体工具被调用了什么 +- 输入是什么 +- 输出是什么 +- 成功还是失败 +- 耗时多久 + +比如: + +- 读文件 +- 写文件 +- 执行命令 +- 网页读取 +- 浏览器交互 + +这些具体动作都可以落成 `tool_call`。 + +### 7.2 为什么 `tool_call` 要单独存在 + +因为工具调用是 Agent 系统里非常关键的一类信息。 + +系统后面可能需要知道: + +- 具体调用过哪些工具 +- 哪次工具调用失败了 +- 哪次调用需要授权 +- 哪次调用产出了结果证据 + +如果没有 `tool_call` 这个对象,很多执行细节就只能混在 event 或 task 里,后面追踪会很痛苦。 + +### 7.3 可以怎么理解 `tool_call` + +我现在会把 `tool_call` 理解成: + +> Agent 执行过程中一次具体的能力调用记录,是连接执行细节、风险治理和结果证据的重要对象。 + +## 8. `delivery_result` 是什么 + +### 8.1 `delivery_result` 是正式结果出口 + +`delivery_result` 关注的不是“系统内部怎么执行”,而是“结果最后怎么正式交付给前端或用户”。 + +比如结果可能是: + +- 气泡短结果 +- workspace document +- result page +- 打开某个文件 +- 跳到任务详情 + +这些都不是 run 或 tool_call 的职责,而是 delivery_result 的职责。 + +### 8.2 为什么要单独有 `delivery_result` + +因为执行结果不等于交付方式。 + +同一份结果,可能有不同交付方式。 + +所以这个对象是为了把: + +- 执行过程 +- 正式交付 + +明确分开。 + +### 8.3 可以怎么理解 `delivery_result` + +我现在会把 `delivery_result` 理解成: + +> 后端正式结果的统一出口对象,用来描述结果应该以什么形式被前端承接。 + +## 9. `artifact` 是什么 + +### 9.1 `artifact` 是正式产物本身 + +如果说 `delivery_result` 更像“怎么交付”,那么 `artifact` 更像“交付的产物是什么”。 + +例如: + +- 生成的 markdown 文档 +- 某个导出文件 +- 某个截图 +- 某个被正式保存的结果文件 + +这些更适合作为 artifact。 + +### 9.2 为什么不能只靠 `delivery_result` + +因为 `delivery_result` 更偏向交付承接动作, +而 `artifact` 更偏向真正的产物实体。 + +简单说: + +- `delivery_result` 回答“怎么给你” +- `artifact` 回答“给你的东西是什么” + +### 9.3 可以怎么理解 `artifact` + +我现在会把 `artifact` 理解成: + +> 任务执行后形成的正式产物实体,是可打开、可定位、可持久化的结果对象。 + +## 10. 这些对象之间的关系怎么理解 + +我现在觉得最适合记忆的方式是: + +```text +task +-> 对外正式任务锚点 + +run +-> task 的一次内部执行实例 + +step +-> 这次任务执行过程中经历的阶段 + +event +-> 执行过程中发生的细粒度事实 + +tool_call +-> 执行过程中某次具体工具调用 + +delivery_result +-> 最终结果如何正式交付 + +artifact +-> 最终正式产物本身 +``` + +也可以换成一句更口语的话: + +- `task` 负责“这件事是什么” +- `run` 负责“这件事这次怎么跑” +- `step` 负责“跑到了哪个阶段” +- `event` 负责“过程中发生了什么” +- `tool_call` 负责“具体调了什么工具” +- `delivery_result` 负责“结果怎么交出去” +- `artifact` 负责“真正交付出的东西是什么” + +## 11. 为什么这些对象不能随便混写 + +我现在越来越能理解,为什么项目文档一直强调这些对象要分层。 + +因为一旦混写,就会出现很多问题: + +- 把长期业务对象和短期执行对象混在一起 +- 把用户看到的状态和内部兼容状态混在一起 +- 把执行细节和最终交付混在一起 +- 把工具原始结果直接冒充正式结果 + +短期看好像方便,长期一定会让: + +- 协议越来越乱 +- 前后端越来越难对齐 +- 任务详情越来越难做 +- 排障和追踪越来越困难 + +## 12. 我当前的理解总结 + +我现在对这套对象模型的理解是: + +这个项目并不是只靠一个 `task` 包打天下,而是把“任务本身”“执行过程”“运行事件”“工具细节”“正式交付”拆成了不同层次的对象。 + +其中: + +- `task` 是对外主对象 +- `run / step / event / tool_call` 是对内执行兼容链 +- `delivery_result / artifact` 是正式结果出口 + +这种设计虽然让系统看起来更复杂,但它也让整个架构更适合做: + +- 多阶段执行 +- 工具调用追踪 +- 风险治理 +- 结果交付 +- 任务详情可视化 +- 恢复与复盘 + +所以从长远看,这种对象分层是有价值的。它不是为了把系统搞复杂,而是为了不让所有语义都挤在同一个对象里。 + +## 13. 前端到底怎么承接这些结果 + +理解这些对象之后,我现在觉得还必须补上一件事,就是前端到底怎么承接后端返回的结果。不然很容易只看到后端对象分层,却不知道这些分层最后怎么落到页面和交互上。 + +我现在的理解是,前端承接结果时,主要不是直接去消费 `run / step / event / tool_call` 这些内部执行对象,而是优先围绕下面几个对象来承接: + +- `task` +- `bubble_message` +- `delivery_result` +- `artifact` + +也就是说,前端更关心“怎么展示、怎么跳转、怎么打开、怎么追踪”,而不是直接接管所有执行细节。 + +### 13.1 `task`:前端承接正式任务状态的主锚点 + +前端最核心的承接对象仍然是 `task`。 + +因为对前端来说,最重要的问题通常是: + +- 这是哪个任务? +- 这个任务现在是什么状态? +- 它是在处理中、等待确认、等待授权,还是已经完成? +- 用户应该去哪里继续看它? + +所以 task 负责承接的是: + +- 列表视图 +- 详情页入口 +- 当前状态展示 +- 与其他正式对象的关联锚点 + +简单说,前端是靠 `task` 来知道“这件事整体怎么样了”。 + +### 13.2 `bubble_message`:前端承接轻量反馈和近场结果 + +如果说 `task` 负责正式锚点,那么 `bubble_message` 更像前端承接近场反馈的方式。 + +比如: + +- 轻量确认提示 +- 等待授权提示 +- 短结果直接展示 +- 处理中状态提醒 + +这些都不一定需要用户立刻进入任务详情页,所以更适合通过 bubble 去承接。 + +也就是说,`bubble_message` 更像回答: + +> 用户在当前这个近场入口里,马上应该看到什么反馈? + +从交互上说,它更适合悬浮球、轻量气泡、即时提示这种低打扰承接。 + +### 13.3 `delivery_result`:前端承接正式结果动作 + +`delivery_result` 是我觉得最容易被误解的对象之一。 + +它不是单纯的“结果文本”,而是前端应该如何正式承接这个结果的说明。 + +比如结果可能是: + +- 直接在气泡里看 +- 打开任务详情 +- 打开文档 +- 打开结果页 +- 跳到某个文件 + +也就是说,`delivery_result` 更像在告诉前端: + +> 这次任务的结果,不只是“有了”,而且“应该怎么被接住”。 + +所以前端看到 `delivery_result` 后,重点不是只把它显示出来,而是根据它的类型决定: + +- 是留在轻量反馈里 +- 还是跳到正式详情 +- 还是打开文件 / 文档 / 页面 + +### 13.4 `artifact`:前端承接正式产物实体 + +`artifact` 对前端来说,更多是“产物本体”的承接。 + +比如: + +- 一个 markdown 文件 +- 一个导出结果 +- 一个被保存的正式文档 +- 一个可以定位和打开的文件对象 + +这说明前端不只是接“结果”,还要接“结果落成了什么具体产物”。 + +所以当用户在任务详情里点开成果区、打开文档、定位文件时,前端实际上更接近是在消费 `artifact` 这一层。 + +### 13.5 `event` 和 `tool_call`:前端通常不是主承接,而是辅助展示 + +`event` 和 `tool_call` 这些对象并不是前端主承接的第一层对象。 + +它们更适合: + +- 调试视图 +- 任务详情里的运行轨迹 +- 工具调用记录 +- 诊断和排障信息 + +也就是说,前端不是完全不看它们,而是通常不会拿它们当主入口,而是拿来补充任务执行细节。 + +所以可以理解为: + +- `task` 负责主任务视图 +- `bubble_message` 负责轻反馈 +- `delivery_result` 负责正式承接动作 +- `artifact` 负责正式产物实体 +- `event / tool_call` 负责过程型补充信息 + +## 14. 我现在对“前端承接结果”的总结 + +我现在觉得,这个项目里前端承接后端结果,不是简单地“拿到一段文本然后显示”,而是一个分层承接过程。 + +可以大致理解成: + +```text +task +-> 告诉前端:这件正式任务是什么、现在处于什么状态 + +bubble_message +-> 告诉前端:当前近场入口应该立刻展示什么轻量反馈 + +delivery_result +-> 告诉前端:正式结果应该以什么方式被承接 + +artifact +-> 告诉前端:真正交付出来的产物是什么 + +event / tool_call +-> 告诉前端:执行过程中发生了哪些更细粒度的事情 +``` + +这样理解之后,我会觉得这个项目前端真正承接的不是“后端所有对象”,而是: + +- 以 `task` 为主锚点 +- 以 `bubble_message` 承接近场反馈 +- 以 `delivery_result` 决定正式结果动作 +- 以 `artifact` 支撑真实产物打开和定位 +- 按需再用 `event / tool_call` 去补足运行细节 + +这也说明,前端和后端虽然都围绕同一个任务系统在工作,但它们关注的重点并不完全一样: + +- 后端更关注执行链路和对象分层 +- 前端更关注结果最终怎么被人看见、打开、追踪和继续操作 From 242ad79dd406ae668c1ef6593848c244bbcd3b0e Mon Sep 17 00:00:00 2001 From: gdemonc <1502689916@qq.com> Date: Mon, 18 May 2026 22:44:39 +0800 Subject: [PATCH 4/5] docs(retrospective): add backend learning notes --- project-backend-learning-notes.md | 390 ++++++++++++++++++++++++++++++ 1 file changed, 390 insertions(+) create mode 100644 project-backend-learning-notes.md diff --git a/project-backend-learning-notes.md b/project-backend-learning-notes.md new file mode 100644 index 00000000..8aeae133 --- /dev/null +++ b/project-backend-learning-notes.md @@ -0,0 +1,390 @@ +# 项目后端学习笔记 + +## 1. 我为什么要写这份笔记 + +这份文档和前面的架构总览、对象模型不太一样。前面的文档更偏整理后的理解,而这份文档更偏我自己学习后端的过程记录。 + +它主要记录三类东西: + +1. 我一开始是怎么理解后端的 +2. 我和 AI 一起分析之后,哪些地方被逐渐理清了 +3. 我现在还没完全吃透,但已经知道该怎么继续学的地方是什么 + +对我来说,这份文档的重点不是“写得多完整”,而是把我的理解过程留下来,方便后面继续接着学。 + +## 2. 我一开始面对后端时的真实感受 + +我一开始看这个项目后端的时候,最大的感觉其实不是“难在某一段代码”,而是“东西太多,不知道该从哪里开始”。 + +比如一打开 `services/local-service/internal`,会看到很多目录: + +- `rpc` +- `orchestrator` +- `execution` +- `model` +- `agentloop` +- `runengine` +- `delivery` +- `storage` +- `risk` +- `audit` +- `checkpoint` + +当时我的直觉是: + +- 看具体实现太难 +- Go 语法也不熟 +- 后端层和层之间关系也不清楚 +- 不知道哪些是核心,哪些是辅助 + +所以我后面逐渐意识到,自己不能一上来就看某个复杂函数,而是得先建立一张后端地图。 + +## 3. 我后来发现,先理解“串起来的逻辑”更重要 + +后面慢慢聊下来,我觉得对我最有帮助的一点,是先不要纠结所有细节,而是先理解主链路是怎么串起来的。 + +我现在最先记住的是这条线: + +```text +前端请求 +-> RPC / handler +-> orchestrator +-> execution +-> model / tool / agentloop +-> delivery +-> 前端承接结果 +``` + +这条线一旦立住,我再去看具体代码的时候,就不会完全迷路。 + +我现在觉得,对我这种主要做前端、再回头补后端理解的人来说,最重要的不是先会写 Go,而是先知道: + +- 请求怎么进来 +- 任务怎么开始 +- 执行在哪发生 +- 模型在哪接入 +- 结果怎么被正式交付 + +## 4. 我最开始学到的第一个关键点:handler 是入口,不是大脑 + +我一开始看到 `rpc/handlers.go` 时,最先不理解的是: + +- `handler` 是什么 +- `registerHandlers` 是什么 +- 为什么后端要先注册 method + +后来我慢慢理解,`handler` 不是最终业务逻辑,而是 RPC 方法的入口处理函数。 + +也就是说: + +- 前端发的是 JSON-RPC 请求 +- 请求里带着 `method` +- 后端根据 `method` 找对应的 `handler` +- `handler` 再把请求交给 `orchestrator` + +所以我现在对这一层的理解是: + +- `method` 是前端想调用的能力名 +- `handler` 是这个能力在后端的入口函数 +- `orchestrator` 才是真正开始做业务处理的地方 + +这一点帮我把“协议入口”和“业务大脑”分开了。 + +## 5. 我后来理解到:orchestrator 是主脑,但不是后端的全部 + +一开始我容易把 orchestrator 理解成“后端本体”,但后来逐渐意识到这不完整。 + +现在我更愿意把它理解成: + +> orchestrator 是任务主链的编排中枢,但不是所有能力本身。 + +它最重要的职责是: + +- 决定一个请求怎么进入 task 主链 +- 要不要确认 +- 要不要授权 +- 什么时候开始执行 +- 什么时候把结果交给 delivery + +但它不应该等于: + +- 模型本身 +- 状态机本身 +- 工具本身 +- 存储本身 + +这也让我更能理解为什么后期只是把 `orchestrator.go` 拆成很多文件,并不算真正的架构重构。因为只要核心控制权还是高度集中在 `orchestrator.Service` 上,那么文件拆了,边界也不一定真的清楚。 + +## 6. 我对 runengine 的理解,是后来才慢慢清楚的 + +我一开始不是很明白,为什么已经有 task 了,还要有 runengine。 + +后来慢慢理清之后,我现在觉得: + +- `task` 是对外的正式任务对象 +- `runengine` 是对内维护 task/run 运行态状态机的地方 + +它负责的不是“决定业务要做什么”,而是: + +- task 状态怎么推进 +- timeline 怎么更新 +- 什么时候发 `task.updated` +- 什么时候发 `delivery.ready` +- 等待授权、恢复执行这些状态怎么收口 + +这让我后面能把: + +- 编排 +- 执行 +- 状态推进 + +这三件事区分开。 + +## 7. 我后面才明白:真正执行不是在 orchestrator,而是在 execution + +这一点对我来说也很关键。 + +我以前更容易把后端理解成: + +- 请求进来 +- orchestrator 处理 +- 就结束了 + +但后来我才发现: + +- orchestrator 决定“这件事怎么走” +- execution 决定“这件事具体怎么执行” + +也就是说,真正碰到: + +- 模型生成 +- Agent Loop +- 工具执行 +- 结果初步生成 + +这些事情的地方,其实是在 `execution` 里。 + +这个理解特别重要,因为它把“主流程编排”和“具体执行调度”真正分开了。 + +## 8. 我一开始也没搞清楚大模型到底在框架哪里 + +这个问题我后面才慢慢想清楚。 + +我最开始更容易只问: + +- 哪个函数调用了模型? + +但后来我发现,更重要的问题其实是: + +- 模型在整个框架分层里属于哪里? + +我现在的理解是: + +- `model` 是模型适配边界 +- `execution` 是模型真正参与任务执行的地方 +- `agentloop` 是当任务进入循环模式后,模型一轮一轮参与推理和工具选择的地方 + +所以不能简单说“大模型在 orchestrator 里”,而应该说: + +> orchestrator 决定任务是否进入执行,execution 决定执行方式,model 负责真正对接 provider,agentloop 则在符合条件时把模型带进多轮循环。 + +## 9. 我现在理解了单轮生成和 Agent Loop 是两条不同路径 + +这一点我觉得很重要。 + +这个项目里,不是所有任务都会自动进入循环。 + +现在我理解的两条路径是: + +### 9.1 单轮生成路径 + +如果当前任务不走 `agent_loop`,或者模型不支持 tool calling,那么执行会更接近单轮生成: + +- 构造 prompt +- 调一次模型 +- 返回结果 + +### 9.2 Agent Loop 路径 + +如果当前 intent 是 `agent_loop`,并且模型支持 tool calling,那么 execution 会进入 `agentloop.Runtime.Run(...)`,让模型走多轮: + +- 推理 +- 决定是否调工具 +- 执行工具 +- 观察结果 +- 再推理 +- 不断逼近最终结果 + +这让我真正理解了: + +> 我们不是只有“大模型调用”,而是有“单轮调用”和“循环式调用”两种执行方式。 + +## 10. 我后来逐渐理解 Harness 工程的意义 + +我觉得这是我最近理解得比较深的一点。 + +一开始如果只把 AI 工程理解成“写一个 prompt 然后调模型”,那其实很难理解为什么后端要长出这么多层和对象。 + +但后来我慢慢意识到,Harness 工程最重要的地方就在于: + +- LLM 的输出本身带有不确定性 +- 人不可能只靠一次完美 prompt 就让它永远返回理想答案 +- 所以工程系统不能只依赖一次调用 + +这就要求系统把一次不稳定的单轮调用,变成一个逐步收敛的过程: + +- 推理 +- 执行 +- 观察结果 +- 观察错误 +- 再推理 +- 再执行 + +也就是说,Harness 做的不是消灭不确定性,而是把不确定性放进一个可控、可追踪、可治理的系统里。 + +这让我更能理解为什么项目需要: + +- task +- agent loop +- tool_call +- event +- approval +- delivery_result + +这些东西本质上都在服务同一件事: + +> 把一次概率性的模型输出,逐步收敛成一个人可以接受、系统可以治理、产品可以交付的结果。 + +## 11. 我现在对 sidecar、tool、plugin、worker 的感觉是:先知道位置,不急着吃透细节 + +我后来也意识到,自己一开始太容易想把所有词一下子搞懂,比如: + +- sidecar +- plugin +- worker +- tool + +但后面发现,这样学很容易把自己压垮。 + +我现在更现实的理解是: + +- `tool` 是系统统一暴露的能力接口 +- `plugin` 更偏扩展能力来源 +- `worker / sidecar` 更偏某些能力背后的运行载体 + +现阶段对我来说,先知道这些词在架构图上的位置已经够了,不必要求自己一次就把底层实现全吃透。 + +## 12. 我现在学后端最有帮助的方法,不是背代码,而是记地图 + +我后面越来越明显地感觉到,开发不是把所有东西背下来,而是先在脑子里建立一张地图。 + +到现在为止,对我最有帮助的不是记很多函数,而是先记住这几个锚点: + +- `rpc/`:请求入口 +- `orchestrator/`:任务主链编排 +- `execution/`:执行调度 +- `model/`:模型边界 +- `agentloop/`:循环执行 +- `runengine/`:状态机 +- `delivery/`:结果交付 +- `storage/`:持久化 + +有了这几个点之后,我再去看任何一个问题,至少知道该先往哪一层找。 + +我现在觉得,后端学习里真正有价值的,不是“全记住”,而是: + +- 知道系统主链怎么走 +- 知道每层大概负责什么 +- 知道当前问题属于哪一层 +- 需要时再回代码里找细节 + +## 13. 我对“怎么学后端”这件事的理解也被修正了 + +一开始我会觉得,学后端是不是意味着: + +- 要先把 Go 学完整 +- 要先把每个目录都看懂 +- 要先把所有逻辑都串熟 + +但后来我发现这样想太重了。 + +对我现在来说,更适合的学习方式是: + +1. 先理解主链 +2. 再理解核心对象 +3. 再理解几层最重要的职责边界 +4. 一次只学一个具体问题 +5. 在问题里顺着调用链往下看 + +比如: + +- 请求怎么进来? +- task 怎么创建? +- 为什么会 waiting_auth? +- delivery_result 怎么来的? +- 大模型从哪一步进入 loop? + +这种按问题学习的方式,对我来说比硬啃整棵后端知识树有效得多。 + +## 14. 我现在还没有完全吃透,但已经知道该怎么继续学的地方 + +虽然这几轮下来,我已经把主链和主要模块大概理清了一遍,但我很清楚,自己还没有完全吃透很多地方。 + +我现在还想继续补的主要是这些: + +### 14.1 具体一条真实主链怎么完整跑完 + +比如: + +- `agent.task.start` +- 从前端发出 +- 到 handler +- 到 orchestrator +- 到 execution +- 到 model / agent loop +- 到 delivery +- 再回前端 + +我已经理解了总图,但还需要继续把这条线再走细一点。 + +### 14.2 orchestrator 到底还有哪些不该混的东西 + +虽然我已经能感觉到 orchestrator 很重,也知道后期拆文件不等于真正重构,但如果要继续深入,我还需要更明确地判断: + +- 哪些逻辑真属于编排 +- 哪些其实应该下沉出去 + +### 14.3 runengine 和 orchestrator 的边界 + +我现在已经知道一个负责编排、一个负责状态,但还想继续练到能更稳定地判断: + +- 某段代码应该放 orchestrator 还是 runengine + +### 14.4 前端和后端围绕 task 的承接边界 + +我已经开始理解: + +- 后端更多关心执行链路 +- 前端更多关心结果怎么被承接 + +但这一层后面还值得继续整理,因为它对我自己的前端经验也很重要。 + +## 15. 我当前的学习结论 + +到目前为止,我对“怎么学这个项目后端”的理解已经发生了变化。 + +我现在觉得,对我来说最重要的不是一下子变成会写复杂 Go 的后端开发,而是先做到下面这些: + +- 能看懂后端主链 +- 能说清主要模块各自做什么 +- 能理解 task-centric 这套系统为什么会这样长出来 +- 能知道大模型、loop、治理、交付分别在框架的哪里 +- 能判断一个问题应该去哪个层找 + +只要这些能力慢慢建立起来,后面再继续补具体实现、补 Go 语法、补更深的设计细节,就会自然很多。 + +所以我现在不再要求自己“全部记住”,而是要求自己: + +> 先把主链和地图记住,先知道为什么这样分层,先知道问题发生时该往哪找。 + +我觉得这才是我现阶段真正合理的后端学习方式。 From 370e1a56705af1750f519f579915883f447a3a2b Mon Sep 17 00:00:00 2001 From: gdemonc <1502689916@qq.com> Date: Sat, 23 May 2026 14:49:01 +0800 Subject: [PATCH 5/5] docs(retrospective): add sharing speech draft --- agent-project-sharing-speech.md | 134 ++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 agent-project-sharing-speech.md diff --git a/agent-project-sharing-speech.md b/agent-project-sharing-speech.md new file mode 100644 index 00000000..76aacd5b --- /dev/null +++ b/agent-project-sharing-speech.md @@ -0,0 +1,134 @@ +# Agent 项目经验分享演讲稿 + +## 第 1 页:标题页 + +大家好,今天我想分享的是我们第一次做 Agent 项目时的一些真实经历。 +我想讲的不是我们项目做得有多成功,而是我们从 0 到 1 推这个项目的时候,踩过哪些坑、做对了哪些事情,以及这些经历让我自己学到了什么。 + +因为我觉得,第一次做这种项目,最有价值的往往不是最后做出来多少功能,而是中间那些会反复出错、但也最能让人成长的地方。 + +我自己在这个项目里主要负责前端相关工作,但做到后面我发现,如果只盯着前端实现,其实很难真正把项目看明白。所以这次分享其实也是我自己从主要关注前端,到慢慢开始理解产品、协作和架构的一个过程。 + +## 第 2 页:我们做的是什么项目 + +在正式开始分享之前,我先简单介绍一下我们做的项目。 + +我们做的是一个前后端分离的本地 Agent 桌面协作系统。 +前端主要负责悬浮球、仪表盘、控制面板这些交互入口和结果承接;后端主要负责任务编排、模型与工具执行、风险治理和正式结果交付。 + +之所以要做前后端分离,是因为这个项目不只是一个页面应用,它同时还涉及本地任务执行、状态管理和治理链路,所以前后端职责需要分开。 +前端分离出去,不只是因为页面要单独开发,更重要的是后端承载的是任务编排和执行主链,它需要作为一个相对独立的本地服务存在。 + +同时,它和普通聊天框式 AI 不太一样。我们希望它围绕桌面现场去承接任务,比如用户可以通过悬浮球、文本选中、文件拖拽这些方式发起任务。 +简单来说,我们想做的不是一个只会聊天的 AI,而是一个能真正协助用户完成任务的 Agent。 + +## 第 3 页:项目从 0 到 1 的基本流程 + +在正式进入项目细节之前,我想先用一页简单说一下,一个项目从 0 到 1 大致会经历哪些阶段。 + +一般来说,首先是需求确认和立项,然后进入整体设计和技术调研,再进入正式开发。开发过程中不会是完全闷头写代码,而是会不断和上下游做阶段性对齐。后面再进入测试和联调,确认整体功能和接口都能跑通之后,再部署上线。上线之后,其实也不是结束,而是进入持续迭代和优化。 + +我觉得这次项目也让我更明显地意识到,开发只是其中一个阶段,前面的设计和后面的联调、测试、部署,其实都很重要。 + +## 第 4 页:最开始,我们只是想先把 Agent 搓出来 + +我们最开始做这个项目的时候,其实目标很简单,就是想先做一个类 OpenClaw 的通用 Agent,把我们觉得有意思、觉得能做的功能都先搓出来。 + +那个时候我们没有特别完整地去想市场、产品定位或者需求边界,更多是一种“先做出来再说”的状态。大家的推进方式也比较直接,就是想到什么就加什么,希望让这个 Agent 变得更强、覆盖更多场景。 + +现在回过头看,这种阶段其实很正常,因为一开始大家通常都是靠激情和想法在推项目。但问题也很明显,就是如果没有一个比较清晰的主线,功能越加越多之后,团队很容易就会慢慢失去边界,最后变成一直很忙,但不一定越来越接近真正想做的东西。 + +## 第 5 页:第一个转折,先停下来做市场和产品定位 + +我觉得第一个比较关键的转折点是在第二周。 + +那个时候我自己已经开始很明显地感觉到,项目不能继续只靠堆功能往前走了,所以我主动去推动大家做市场调研,重新确认我们的产品定位,并把竞品、市场资料、技术栈这些内容整理出来。 + +更重要的是,我们开始重新看最开始提的那些功能想法,不再默认“都要做”,而是开始做筛选和删减,把真正符合产品方向的部分留下来,把一些不必要或者不够核心的先拿掉。 + +这个过程让我第一次真正理解了“少就是多”这句话。以前会觉得做得越多越厉害,但后来我发现,一个项目真正重要的,不是功能堆得多满,而是主线是不是清楚,重点是不是明确。 + +## 第 6 页:前期文档让项目开始稳定推进 + +后面我越来越明显地感受到,真正让项目开始稳定推进下来的,其实不是某一个具体功能,而是前期文档慢慢补起来了。 + +这里面包括产品定义文档、市场调研文档、PRD,还有技术栈整理。 +因为在这些文档没有成型之前,团队其实很容易陷入一种“走一步想一步”的状态。今天想到一个方向就做,明天发现另一个问题再补,整个推进方式会比较散。 + +但是有了这些文档之后,团队至少开始有了一个相对一致的目标,也有了比较明确的推进依据。对我来说,这也是我第一次真正体会到,文档不是形式,而是协作和推进的基础工具。 + +## 第 7 页:一个重要启发,先做原型,再反推 PRD + +不过在这个过程中,我还有一个印象特别深的收获,就是导师给我们的一个方法上的启发。 + +导师提出的一个想法是,在 AI Coding 的模式下,不一定非要完全按照传统产品流程,先把 PRD 写得很完整,再开始做产品。 +有时候也可以先快速做出原型,再反过来推 PRD。 + +我觉得这个思路对我们很有启发,因为它让我们没有完全照着当时已有产品的路径走,而是敢于尝试一种不同的形态。 + +比如我们后来没有采用很常见的聊天框式交互,而是把很多操作收在悬浮球上,希望把 Agent 做成一种更贴近操作系统、更自然融入用户环境的存在。 + +虽然最后这个方向我们没有实现到特别理想的程度,但这个尝试本身让我开始意识到,产品设计不一定只是跟随现有范式,也可以通过原型先试出一种新的交互形态,再回过头去定义需求和产品逻辑。 + +对我来说,这种思考方式其实也是一个很大的进步。 + +## 第 8 页:踩得最深的坑,前后端对齐不充分 + +如果说这次项目里我觉得踩得最深的坑是什么,那一定是前后端对齐不充分。 + +我们当时的一个问题是,很多协议和交互理解主要集中在少数前后端同学那里推进,其他同学并没有充分参与。结果就是,虽然文档写出来了,但它并没有完整覆盖所有人的理解和需求。 + +这个问题在正式开发以后就慢慢暴露出来了。大家开始不断发现,有些字段不完整,有些交互场景根本没提前考虑到,于是后面就开始不断补文档、改逻辑、返工。 + +这件事给我的感受特别深,因为它让我非常明确地意识到,前后端协作里最重要的不是“各自把自己的部分做完”,而是要尽早把语义和边界对齐。 +因为一旦前期理解没有统一,后面返工成本会非常高。 + +## 第 9 页:开发阶段,我们逐渐形成了 GitHub 协作方式 + +到正式开发阶段之后,我们后面也逐渐形成了一套更工程化的 GitHub 协作方式。 + +最开始我们其实用过飞书,但后来发现,在开发阶段大家已经不只是需要共同编辑文档了,更需要一种能把任务、问题和代码修改真正关联起来的协作方式,所以 GitHub 对我们来说会更合适。 + +我们后面的方式大致是这样:先在 Project 里看整体任务推进,再把发现的问题或者需求拆成 Issue,然后针对具体 Issue 建分支开发,开发完成之后提 PR,在 PR 里看 diff、讨论实现、做 review,最后再合并继续推进。 + +这种方式带来的好处很明显。第一,任务和问题更可追踪;第二,分工更清晰;第三,后面回头看时,能知道某个问题是怎么被提出、怎么被修改、怎么被解决的。 + +同时我也越来越能理解,开发过程中不能闷头写代码,每完成一部分都应该和上下游继续对齐方向,发现问题及时调整。 + +## 第 10 页:架构和开发现实 + +这一页我最想讲的是,开发不是“边做边优化架构”这么简单。 + +现实里往往是主链要先跑起来,很多复杂度是在开发后期才真正暴露出来的。 +我们项目后期虽然也拆过 orchestrator,把原来的大文件拆成了很多小文件,但我现在回头看,会觉得这种拆分更多是缓解了可读性问题,而不等于真正完成了架构重构。 + +因为真正难的不是把文件拆小,而是明确职责边界,也就是哪些逻辑应该留在编排层,哪些应该下沉到治理层、运行态层或者交付层。 + +所以我后来会觉得,架构不能完全按照我们一开始想象的那种方式,一边沿着主链开发、一边自然扩展出来。 +更现实的做法其实是,先把主链和主要扩展方向尽量走出来,把大的职责边界先看清楚,再回到具体开发里,根据真正暴露出来的细节问题继续调整。 + +也就是说,架构优化不是边写边顺手就能完成的,它往往需要先把主链跑通、把边界看清,再在后续开发中不断修正细节。 + +## 第 11 页:我自己的成长,从前端执行到开始理解全局 + +如果要说我自己在这个项目里最大的成长,我觉得不是我写了多少功能,而是我开始慢慢从一个主要关注前端实现的人,走向了一个开始理解项目全局的人。 + +一开始我主要盯的是页面、交互和功能怎么接起来,但做到后面,我开始越来越关心另外一些问题,比如产品定位是不是清晰,需求有没有收敛,文档有没有对齐,前后端理解是不是一致,架构的主链到底是怎么跑的。 + +我后来也越来越明显地感觉到,在 AI Coding 这种项目里,不能只把自己看成一个“只做前端的人”。因为很多时候,真正决定项目能不能推进的,不只是某一层写得好不好,而是整条主链是不是成立,协作是不是对齐。 + +所以这次项目对我来说最大的成长,就是我开始主动从实现层往上看,去理解产品、协作和架构,而不是只停留在页面和功能本身。 + +## 第 12 页:最后的结论 + +最后如果要总结这次经历,我觉得我最想说的有五点。 + +第一,功能堆叠不等于产品成立。 +第二,文档不是形式,而是协作基础。 +第三,前后端对齐会直接决定后期返工成本。 +第四,架构优化的关键,不是先拆文件,而是先明确职责边界,再围绕主链逐步拆分模块。 +第五,做 AI 项目时,必须慢慢从局部实现走向主链理解。 + +对我个人来说,这次项目带给我的最重要的收获,不只是学会了某个功能怎么做,而是我开始理解,一个项目是怎么从想法、定位、文档、协作,到真正开发和落地,一步一步推进起来的。 + +我觉得这也是这次分享里最想和大家交流的部分。