最终用户应该可以这样使用它:
1 2 3 4 5 6 7 8 9 10
| $ claw
claw> 帮我检查这个项目有哪些编译错误 实时看到 Agent 的回复和工具调用 执行命令或修改文件前得到明确确认 任务很长时可以按 Ctrl-C 取消
claw> 把刚才发现的问题修好 继续复用上一轮上下文 修改完成后回到下一个 claw> 提示符
|
这不是单个功能,而是一条有依赖关系的工程路线:
1 2 3 4 5 6 7 8 9 10 11
| Runtime 边界 ↓ 连续对话 ↓ 流式事件 ↓ 全链路取消 ↓ 工具审批 ↓ 持久化、可靠性、协议、评测和部署
|
本篇只描述从当前基线继续往前的未来计划,不重复前面已经完成的基础能力。后续系列文章再按照本文的阶段逐步实现。
一、当前基线和最终目标
在这个提交中,入口程序仍然是一次性任务:创建 Provider、Registry、Engine 和 Session,追加一条固定 Prompt,然后调用一次 Run。
1 2 3 4 5 6
| sess.Append(schema.Message{Role: schema.RoleUser, Content: prompt})
err := eng.Run(context.Background(), sess, reporter) if err != nil { log.Fatalf("引擎崩溃: %v", err) }
|
这个入口可以验证 Agent Loop、工具并发和 Trace,但不能承载长期交互。目标不是把所有逻辑塞进 main.go,而是形成以下运行时分层:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Terminal / HTTP / 其他渠道 ↓ 输入、展示、审批回答 Runtime ↓ Session、Run 生命周期、取消 Agent Engine ↓ Thinking / Action / Tool / Observation Provider Adapter ↓ 同步或流式模型调用 Approval Layer ↓ Policy / Grant / Handler Tool Registry ↓ 工具发现、风险声明、中间件和执行 Persistence / Observability ↓ Session、Grant、Trace、Usage、审计
|
层之间必须保持单向依赖:
1 2 3 4 5
| Terminal 改变,不修改 Engine Provider 改变,不修改 Approval Approval 渠道改变,不修改 Tool 工具增加,不修改 REPL Session 存储改变,不修改 Reporter
|
二、目标运行时状态机
一条用户任务的完整生命周期是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Idle ↓ 用户提交 Prompt Preparing ↓ 读取 Session、压缩上下文、构造请求 WaitingResponse ↓ 模型返回完整 Assistant Message NoToolCall ─────────────────────────→ Completed ↓ 有 ToolCall WaitingApproval ├── Denied ────────────────────────→ Observation → Next Turn └── Allowed ↓ ExecutingTools ↓ AppendObservation → Next Turn
|
Ctrl-C 可以从 Preparing、WaitingResponse、WaitingApproval 和 ExecutingTools 进入 Cancelled:
1 2 3 4 5 6 7
| Cancelled ↓ 当前 Run 返回 ↓ Session 保留一致状态 ↓ Runtime 回到 Idle
|
这要求 Runtime 和 Engine 的职责严格分离:
1 2 3 4 5
| Runtime:管理用户和任务生命周期 Engine:完成一条 Agent 任务 Provider:适配模型协议 Approval:决定工具是否可以执行 Registry:执行已经获准的工具
|
三、未来阶段一:建立 Runtime 边界
第一步不是马上实现 REPL,而是先明确“一条任务”和“长期运行程序”的边界。
当前 Engine 的入口已经是可复用形态:
1 2 3 4 5
| func (e *AgentEngine) Run( ctx context.Context, session *ctxpkg.Session, reporter Reporter, ) error
|
目标关系是:
1 2 3 4 5 6 7
| Runtime Loop ├── 读取一条用户输入 ├── 写入 Session ├── 创建本次 Run Context ├── 调用 AgentEngine.Run ├── 等待完成、失败或取消 └── 回到下一次输入
|
本阶段需要完成:
1 2 3 4 5
| 把用户输入从 main.go 移出 让 Engine 不读取 stdin 让 Runtime 不解析模型 SDK Chunk 让 Reporter 成为唯一展示出口 让同一个 Session 支持多次 Run
|
验收标准:可以在不修改 Engine 核心循环的情况下,替换 Terminal 为另一个输入渠道。
四、未来阶段二:连续对话 REPL
Runtime 的第一种实现是 Terminal REPL:
1 2 3 4 5 6 7 8 9 10 11
| 打印 claw> ↓ 读取一行 Prompt ↓ 处理本地命令 ↓ session.Append(UserMessage) ↓ engine.Run(...) ↓ 回到 claw>
|
计划新增 internal/cli/repl.go,支持:
1 2 3 4
| /help 查看帮助 /clear 清空 Session 历史 /exit 退出程序 /quit 退出程序
|
Session 必须跨轮复用:
1 2 3
| 用户输入 A → Append(User A) → Run(session) 用户输入 B → Append(User B) → Run(session) 用户输入 C → Append(User C) → Run(session)
|
/clear 只清理对话状态,不重建 Provider、Registry 和 Engine。REPL 还要区分三类错误:
1 2 3
| 用户退出:正常结束 当前任务取消:回到 claw> Engine 崩溃或配置错误:显示错误并决定是否继续
|
该阶段对应的后续实现提交是 fb7d7450c0fa9d4d096a06f77e609c4320a55b4f。
五、未来阶段三:增加流式事件协议
基线的 Provider 只有完整响应接口:
1 2 3
| type LLMProvider interface { Generate(ctx context.Context, messages []schema.Message, availableTools []schema.ToolDefinition) (*schema.Message, error) }
|
目标是在保留同步能力的同时,增加可选的流式接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| type StreamEventType string
const ( StreamTextDelta StreamEventType = "text_delta" StreamCompleted StreamEventType = "completed" StreamError StreamEventType = "error" )
type StreamEvent struct { Type StreamEventType Text string Message *schema.Message Usage *schema.Usage Err error }
type StreamingProvider interface { LLMProvider GenerateStream(ctx context.Context, messages []schema.Message, tools []schema.ToolDefinition) (<-chan StreamEvent, error) }
|
三层职责必须分开:
1 2 3
| Provider:SDK Chunk → StreamEvent Engine:StreamEvent → Agent 状态变化 Reporter:StreamEvent → Terminal 展示
|
Channel 只负责 Provider 和 Engine 之间的事件传递,Provider 不应该直接调用 fmt.Printf。
该阶段对应的后续实现提交是 02dd5b944952fe7a9369f7e6877c4fef4b0881c9。
文本 Delta 可以立即输出,但 ToolCall 参数通常是分片 JSON:
1 2 3
| {"com mand":"go test ./..."}
|
所以不能把每个 Delta 直接交给工具执行。Provider 要按 ToolCall Index 聚合:
1 2 3 4 5 6 7 8 9 10
| ToolCall Delta ├── ID ├── Function Name └── Function Arguments 分片 ↓ ToolCallAccumulator ↓ JSON 校验 ↓ schema.ToolCall
|
完成条件:
1 2 3 4
| 多个 ToolCall 的参数不互相串联 空参数可以安全转换为 {} 非法 JSON 转换为 StreamError 只有 Completed 后才进入 Engine Tool 阶段
|
流式文本和最终上下文必须分开:
1 2 3
| Text Delta → 立即展示 完整 Message → 写入 Session ToolCall → 进入审批和执行
|
七、未来阶段五:实现 Ctrl-C 全链路取消
REPL 为每条任务创建独立的 runCtx:
1 2 3 4 5 6 7 8 9
| os.Interrupt ↓ cancel() runCtx.Done() ├── Runtime 停止等待当前 Run ├── Engine 停止等待 StreamEvent ├── Provider 关闭 HTTP Stream ├── sendStreamEvent 停止阻塞 ├── bash 子进程被终止 └── Approval Handler 返回取消
|
取消的用户语义是:
1 2 3 4 5
| 取消当前任务 保留已确认的 Session 历史 不执行未开始的危险工具 回到 claw> 不退出整个进程
|
每一个可阻塞组件都必须继续传递同一个 Context。不能在 Provider、Tool 或 Handler 内部重新使用无法取消的 context.Background()。
该阶段对应的后续实现提交是 4a9be525a8e0f5ef24014c7c178f7ac8a8704d2b。
八、未来阶段六:引入通用 Approval Gate
工具执行从:
1
| ToolCall → Registry.Execute
|
改为:
1 2 3 4 5 6 7 8
| ToolCall ↓ RiskLevel Policy ↓ GrantStore ↓ Handler Decision ↓ Registry.Execute
|
Approval 层拆成四个职责:
1 2 3 4
| RiskLevel:工具能力的风险分类 Policy:默认自动允许、询问或拒绝 GrantStore:保存会话授权 Handler:Terminal 等渠道如何询问用户
|
需要新增:
1 2 3 4 5 6
| internal/approval/interface.go internal/approval/policy.go internal/approval/grant.go internal/approval/gate.go internal/approval/terminal_approval.go internal/approval/id.go
|
执行顺序必须是:
1 2 3 4 5 6 7
| 所有 ToolCall 串行 Check ├── Deny:生成拒绝 Observation └── Allow:加入 approvedCalls
approvedCalls 并发 Execute ↓ 按原始 index 写回 Observation
|
不能把审批放到执行 Goroutine 中,否则多个请求会同时读取同一个 Terminal 输入流。
该阶段对应的后续实现提交是 8a13d9a876a86a08c2ee43171cc2a108412f18ec。
九、未来阶段七:补齐资源生命周期和可靠取消
阶段一至六跑通后,先处理最容易在生产环境暴露的问题:
1 2 3 4 5 6
| Provider Goroutine 是否全部退出 Approval 输入 Goroutine 是否泄漏 HTTP Stream 是否关闭 bash 子进程是否被回收 取消后的结果是否误写 Session Reporter 并发输出是否可读
|
需要增加:
1 2 3 4 5 6 7
| 统一任务状态 工具级超时 并发上限 Channel 背压 优雅关闭 Race Detector 取消、超时和泄漏测试
|
尤其要解决 Terminal Handler 中“Context 已取消但 ReadString 仍阻塞”的问题。函数返回 context.Canceled 不代表后台输入 Goroutine 一定已经退出。
十、未来阶段八:审批持久化和最小权限
内存 Grant 只能用于单进程实验。生产级授权需要:
1 2 3 4 5 6 7 8
| 文件或数据库 GrantStore 工作区范围 文件路径范围 命令前缀或参数摘要 过期时间 撤销能力 审批人和审批时间 审计记录
|
授权不能只匹配 SessionID + ToolName。允许一次 bash 不应自动等于允许该会话执行任意命令。
风险还要逐步细化到参数级:
1 2 3 4
| read_file(workspace/a.go) 低风险 write_file(workspace/a.go) 中风险 bash("go test ./...") 可配置风险 bash("rm -rf ...") 高风险
|
这一步还要建立工作区沙箱、路径规范化、命令策略和完整审计。
十一、未来阶段九:Provider 可靠性和成本预算
生产模型调用需要处理:
1 2 3 4 5 6 7 8
| 连接超时 首 Token 超时 整体响应超时 429 限流 5xx 服务错误 流式半响应 模型能力不匹配 重试和退避
|
还要建立预算控制:
1 2 3 4 5
| 单 Turn 最大 Token 单 Run 最大 Token Session 累计费用 工具执行时间预算 Subagent 次数预算
|
涉及副作用 ToolCall 时,重试必须考虑幂等性,不能因为 HTTP 重试而重复执行写文件或 bash。
十二、未来阶段十:Session 持久化和任务恢复
内存 Session 只能支持单进程运行。生产级 Session 需要保存:
1 2 3 4 5 6 7
| Session 元数据 Message History Working Memory 当前 Run 状态 最后一次 ToolCall 取消或失败原因 Usage 和成本
|
重启恢复时要判断任务停在:
只有状态和 Observation 一致时才能继续。若进程在副作用工具执行中崩溃,必须进入人工确认、幂等重试或补偿流程,不能盲目重复执行。
十三、未来阶段十一:工具生态和 MCP / A2A
本地 Registry 稳定后,接入外部工具和远程 Agent:
1 2 3 4 5 6 7 8
| MCP Server ↓ tools/list MCP Adapter ↓ schema.ToolDefinition Tool Registry ↓ Approval Gate ↓ tools/call MCP Server
|
需要解决:
1 2 3 4 5 6 7
| 工具发现和刷新 远程连接生命周期 远程超时和取消 远程错误映射 工具版本兼容 远程风险声明 Agent-to-Agent 身份和权限
|
外部工具及远程 Agent 必须复用同一个 Context、Approval 和 Observability 链路,不能因为工具来自 MCP 就绕过本地安全边界。
十四、未来阶段十二:评测、性能和部署治理
测试与评测
生产级 Harness 不能只依赖手工运行,需要:
1 2 3 4 5 6 7 8
| Provider 协议测试 StreamEvent 顺序测试 ToolCall 聚合测试 REPL 和 Ctrl-C 测试 Approval Gate 表驱动测试 并发和 Race 测试 Fake Provider 回放测试 固定任务回归测试
|
还要评估 Agent 行为:
1 2 3 4 5 6
| 任务是否完成 是否调用正确工具 是否产生越权操作 是否陷入 Doom Loop Token 和耗时是否超预算 取消后状态是否一致
|
性能与规模化
需要关注:
1 2 3 4 5 6 7 8 9
| 首 Token 延迟 完整 Run 延迟 并发 Run 数 Provider 连接复用 工具执行并发池 上下文缓存 Trace 异步写入 大输出截断 资源配额
|
部署与治理
最后把本地 CLI 变成团队可以依赖的服务:
1 2 3 4 5 6 7
| Secret 管理 用户认证和租户隔离 工作区沙箱 权限审计 指标、日志、Trace 健康检查和优雅关闭 SLO、告警、灰度和回滚
|
十五、阶段依赖和提交规划
未来阶段不能随意调换顺序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 阶段一 Runtime 边界 ↓ 阶段二 连续 REPL ↓ 阶段三/四 Stream 和 ToolCall 聚合 ↓ 阶段五 全链路取消 ↓ 阶段六 Approval Gate ↓ 阶段七 资源生命周期可靠性 ↓ 阶段八 权限持久化 ↓ 阶段九 Provider 容错和预算 ↓ 阶段十 Session 恢复 ↓ 阶段十一 MCP / A2A ↓ 阶段十二 评测、性能和部署
|
代码提交可以保持小步可运行:
1 2 3 4 5 6 7 8 9 10 11
| 47452b3 当前起点 ↓ fb7d745 阶段二:REPL 和 Session 复用 ↓ 02dd5b9 阶段三/四:Stream 和 ToolCall 聚合 ↓ 4a9be52 阶段五:Ctrl-C 和 Context 取消 ↓ 8a13d9a 阶段六:Policy、Gate、GrantStore、Terminal Handler ↓ 后续提交 阶段七至十二的生产化能力
|
每个阶段都必须保持可运行:
1 2 3 4 5 6
| 完成阶段二后:仍可非流式运行 完成阶段三后:不支持流式的 Provider 仍可回退 完成阶段五后:取消当前 Run,REPL 不退出 完成阶段六后:安全工具自动执行,危险工具需确认 完成阶段十后:进程重启可以恢复一致任务状态 完成阶段十二后:具备部署、告警、回滚和评测能力
|
十六、最终验收标准
连续对话
1 2 3 4
| 输入任务 A 任务完成后仍停留在 claw> 输入任务 B Agent 能引用任务 A 的上下文
|
实时输出
1 2 3
| 模型生成期间持续出现文本 文本结束后有明确换行 ToolCall 参数完整后才执行
|
中断
1 2 3 4
| 模型流式输出时 Ctrl-C bash 执行时 Ctrl-C 审批等待时 Ctrl-C 三种情况下都返回 claw>,不退出进程
|
审批
1 2 3 4 5 6
| read_file 自动允许 write_file 询问用户 bash 询问用户 允许一次只影响当前调用 允许本会话可以复用授权 拒绝后生成 Observation,不执行工具
|
生产稳定性
1 2 3 4 5 6 7
| Provider 错误可分类、重试或降级 Context 取消不会泄漏 Goroutine 工具结果顺序稳定 Session 可以恢复 Trace 可以回放 权限决策可以审计 指标和成本可以查询
|
总结
从 47452b38c047ba8e614369a099a04c2bbad90c83 到生产级 Agent Harness,未来要完成的是十二个连续阶段:
1 2 3 4 5 6 7 8 9 10 11 12
| Runtime 边界 连续 REPL 流式事件 ToolCall 聚合 全链路取消 Approval Gate 资源生命周期可靠性 权限持久化 Provider 容错和预算 Session 恢复 MCP / A2A 生态 评测、性能和部署治理
|
系列十一、十二、十三会先落地其中的 REPL/Stream、中断和审批;但它们只是完整路线中的早期阶段。只有把后续的权限、恢复、评测、性能和部署工作继续完成,go-tiny-claw 才真正具备生产级 Agent Framework 的能力。