Skip to main content
扣瓦的 agent 不在渲染进程里。它跑在一个独立的本地 sidecar 进程中 (apps/sidecar/pi-agent,Bun + TypeScript), 通过 stdin/stdout 的 NDJSON 协议与前端通信。 这样做的直接好处是:长任务不会拖垮界面,界面崩溃不会打断正在跑的活儿。

一次回合的生命周期

理解 agent 的行为,从这一个循环开始比读任何单个模块都清楚:
1

组装上下文

取当前会话历史,注入长期记忆与已装载技能,附上 @ 提及指向的文件内容 与附件。是否需要压缩,取决于当前上下文占用了多少。
2

调用模型

把组装好的上下文交给当前选中的模型,拿到增量文本或一组工具调用。 换模型只影响这一步。
3

逐个判定工具调用

每个调用先过会话模式(这一档允许写吗),再过权限档位 (这一档要不要问)。两者都放行才继续。
4

需要审批就挂起

挂起不是丢弃:sidecar 保留回合状态,推一条审批事件给前端, 你的选择回来之后从同一步继续。被拒绝的调用会作为 blocked 结果回给模型, 让它换一条路,而不是卡死。
5

执行并回灌结果

工具在 sidecar 内执行,结果作为 tool 消息回灌进上下文,循环回到第二步。 直到模型不再要求工具,这一回合才结束。
6

落库

转录、工具调用与检查点写入会话存储;长会话触发上下文压缩, 把早期回合折成摘要。
这个循环里只有第二步是模型的,其余全是本地确定性的逻辑。 这就是为什么换模型不会改变权限行为——审批、压缩、子代理调度都在模型之外。
这个循环模型内部跑了多少轮、每轮想干什么、为什么停,默认看不见。 想看清它,见循环视图。

内置工具

sidecar 注册的工具大致分为这几类: Question 工具值得单说:它是 agent 主动向用户提问的通道, 在需要澄清需求或确认关键决策时挂起回合,而不是猜一个然后继续。

会话模式:能不能改

agent

完整工具集。默认模式,可以读写文件、跑命令。

plan

结构性只读。只能读和规划,通过 plan_exit 拿到批准后回到 agent 实施。

ask

纯只读子集,没有 bash。适合”就问问,别动手”。

goal

完整工具集 + 跨轮自治。面向”把这个目标做完”的长任务。
plan 模式的设计意图是让规划与实施在权限上分开: 读代码阶段拿到的是只读权限,写代码阶段才拿回写权限。

权限档位:问不问

ask

write / edit / bash / 配置类工具,每次都确认。

workspace-write

工作区内 + 可写根清单内的目录免确认;bash 仍确认,但支持”允许并记住这类命令”。

auto-edit

写与编辑全部免确认,含工作区外;bash 仍确认。

auto

全免确认。
这四档就是全部(档位是枚举,不是可调参数;Shift+Tab 在它们之间循环)。

审批卡的三个按钮

“允许并记住”生成的是前缀规则,不是精确匹配: (不带 * 的逐字相等规则仍然有效——那是手工写进清单的形态,UI 只产出前缀规则。) 解释器类与破坏性命令(node / bash / npx / pnpm dlx / sudo / rm / cp / find)不记——它们的规则第一词之后就是”要执行的代码”或”任意路径”,一次点击 等于永久免问。这类命令的卡上没有第三个按钮,只说明”这一次放行”。
这些规则必须配三道守卫:逐段校验(git log && rm -rf ~ 的第二段要自己命中 规则)、反藏写(重定向、命令替换、反引号、$'…' 一律不命中;重定向的识别按 shell 的词法走,echo a${IFS}>f 也是写)、转义与引号按 shell 解析(解析不可信 就不放行也不记规则)。另加一层可写根清单限制”能落到哪些目录”——项目文件只能 提议、不能自行授权。也就是说,“记住这类命令”永远不会让 agent 获得你手动批准之外 的新目录写权限。同时要坦白一点:workspace-write 这档管不住 bash。 命令可以通过 shell 写到工作区之外的任何位置。这一点在 docs/permission-modes.md 里有专门章节说明,不是设计疏漏而是取舍。

可写根清单(工作区外也要免确认的目录)

工作区内免确认、工作区外要确认——但真实工作常跨项目(前后端两个目录、monorepo 外的 共享库),每次都弹确认就成了纯噪声。清单解决这个:列进去的目录按”工作区内”对待。 三层文件取并集: 审批卡上点「允许并记住」,写进去的是本机那份(local)。也就是说:唯一能授权的 文件都在你自己机器上,结构上不存在可以被仓库伪造的信任状态——项目层声明的根只会 出现在审批卡上那句「这个项目请求放行 X」里。

两个维度是分开的

模式决定”能不能改”,档位决定”要不要问”。它们是独立的下拉,组合起来才是最终行为。 把这两件事混为一谈是关于扣瓦最常见的误解。

上下文压缩

长会话总会撞上窗口上限。sidecar 的 compaction 会把较早的对话 压缩成摘要,保留关键决策、已产出的文件与未完成的目标, 让任务能继续跑下去而不丢上下文。

长期记忆

除了会话内的压缩,扣瓦还有一层跨会话的长期记忆: 在设置 → 记忆里可以查看与编辑历史条目。 记忆分两层存放:全局层在 ~/.kova/memory(跨会话、跨工作区共享), 工作区层在 <工作区>/.kova/memory。会话本身(转录与轨迹)不在这里, 而在应用数据目录下的 sessions/ 里。

子代理

子代理让 agent 把大任务拆开并行处理,而不是在一个上下文里串行做完。
1

Task

派发一个子代理,带独立的上下文与工具集。
2

TaskWait

等待某个子代理完成并取回结果。
3

TaskList

列出当前存活的子代理及其状态。
4

TaskStop

中止一个正在跑的子代理。
子代理定义的本机层在 ~/.kova/subagents,工作区层在 <工作区>/.kova/subagents (可随仓库提交、与团队共享),都可在设置 → 子代理里管理。 发现机制分三层:内置定义、用户自定义、以及插件提供的定义。 子代理活动会被持久化,长任务中途重启也能接上 (见 docs/subagent-activity-persistence-design.md)。

定时任务与无人值守

自动化(automations)用 croner 调度。 定时任务跑在专门的自动化权限档下——没人盯着的时候, 危险档位显然不能默认放开。任务完成还可以通过 Webhook 推送到任意服务。

下一步

工具之外,还有能直接打开来用的工作台。