背景

为何使用起 Pi Coding Agent:

  1. 主要原因:近几个月很多公益站的 gpt 模型没有了,想要低成本使用没有降智的 gpt 越来越难(还是想白嫖为主🤣)。
  2. 与此同时,很多公益站都上了国模,主要还是以 flash 为主的模型,比如 deepseek-v4-flash、deepseek-v4.1-flash、glm-5.3-flash,当然也有 glm-5.3,kimi-k3 等大参数模型,但是限制于额度或者是稳定性,一般都不太能顺畅使用。
  3. 这些 flash 模型在 Codex 或者 Claude Code 上面表现不佳,我猜测原因还是现在这两个功能越来越庞大,并且与自家模型绑定了一些功能(比如 cache_control 和 prompt_cache_key),导致启动时上下文占用很多,并且默认注册的工具也多,一些特殊用法在自家模型上表现正常,在别的模型上则不能正常发挥作用甚至影响模型发挥。
  4. 平时水 L 站多了,自然早已耳闻 pi 的大名,以简洁著名,能个性化打造自己的 coding agent,并且有了稳定版本,而且还在积极推进,没有那么多 breaking change,所以没怎么考虑就选择了这个了。
  5. 目标:能够在一些没有那么复杂的场景下使用 Pi Coding Agent + 国模来代替 gpt,达到最低成本 vibe coding。

我写的插件

以下插件基本都是边 Pi Coding Agent+flash 国模开发,边完善的,也是体会到了奥特曼他们用 gpt 开发 codex 的过程,确实以这种方式,开发了也能用起来,用起来的时候也能发现又哪些不足,然后又继续演进,互相成就。

目前基本能用阶段,因为我开发这些插件的过程,就是在用这些插件了,但是估计还得慢慢优化。

watchdog(自动发继续)

解决明明输出还没有结束,但是 api 的流式输出就是完成了,只有发继续才能接着输出。会注册一个工具到主 agent。

因为我默认开启这个插件,如果没有问题,也会发一次给 llm 确认一下,没法知道任务是不是真的完成了,如果人不在屏幕面前,所以下面的截图是正常情况下的。
alt text

更多了解可以见文章:【Pi Coding Agent】watchdog自动发继续插件 | GreenHatHGのBlog

subagent

其实 subagent 有时候还是有必要的,我在用 flash 模型的时候发现有些结论并不对,如果你单独拿出这条结论重新开个对话用同样的 flash 模型再确认一次,这次就对了。所以你可以手动复制粘贴一下结论给在新的对话确定一下,或者是叫主 agent 启用 subagent 再去确认一下结论(这里有个坑就是主 agent 给 subagent 的描述,会不会把 subagent 也引导歪了,这是另外一个话题了)。

基于 tmux+Pi Coding Agent 本身,等于是用 tmux 启动另外一个 Pi Coding Agent,基于文件交流。

基于 tmux 好处是可以随时使用 tmux 命令连接上去看 subagent 干了什么,也能提前结束,重新发送别的内容,其实就是跟用 Pi Coding Agent 一样,不用管什么 subagent 不 subagent 的。但是 subagent 不注册 subagent 工具,也就是不能嵌套启用 subagent。

另外 subagent 是一个 Pi Coding Agent 实例的话,也可以用到上面提到的 watchdog 插件,来帮助你自动发继续,确定 subagent 是真的完成了。大家都知道,公益站提供的 llm 很多时候都不稳定,用在 subagent 上是个很痛苦的事情,subagent 可能一直断流,需要靠主 agent 来监控和重试,可能现在很多 harness 框架处理的比较好了,在早期版本,subagent 很不完善。

总共有三种模式,所以会注册三个工具到主 agent,advisor用的最多,其次是websearch,最后是普通的:

  • 普通 subagent 模式:system prompt,tools 和主 agent 基本一样。
  • websearch 专用:system prompt 改为用于专门搜索,加载 pi-web-access 插件,同时限制 tools 列表。
  • advisor 模式:用于 flash 模型询问高级模型,system prompt 改为 advisor 模式专用,同时限制 tools 列表。

alt text

以 advisor 模式举例,总共5个部分,如图里面的圆圈:

  1. 代表模型调用 advisor 工具,然后 subagent 插件会输出一些常用的命令,用于快速复制粘执行,比如 kill 掉这个 subagent、连接到 tmux 上面看这个 subagent 的输出等等。
  2. 主 agent 给 subagent 写的任务详细说明,点击可以展开查看。
  3. 主 agent 等待 subagent 完成,超时 1800s,如果 subagent 完成了,会通知这个命令,主 agent 就会接着执行。
  4. subagent 任务完成后,会将结果写到文件中,点击可以展开查看。
  5. 主 agent 查看结果文件。

简单加了个 subagent 的状态,先凑合用,ui 和操作并不是很完美😅

默认情况下展示 agent 的状态,左边是 agent 的名字,右边则是最后一行输出:
alt text

点击可以展开,查看更多的状态行:
alt text

展开后还可以点击具体一个 subagent,这时候弹出一个悬浮窗,其实就是连接上了 tmux 的窗口,可以实时预览 subagent 的输出。
alt text

subagent 一执行结束,tmux 就不能连接上去查看,想要事后看到 subagent 做了些什么,只能是通过读 Pi Coding Agent 的 jsonl 文件,里面记录了 subagent 当时做了些什么,可以下载个专门解析 jsonl 的工具,也可以使用 pi --session 命令恢复。

bash guard(命令拦截)

我在用 flash 模型的过程中,发现 flash 用 bash 会执行一些很宽泛的命令,比如 find ~,或者是 grep 没加关键过滤条件或者是条件太广泛,返回 40 kb 的结果。所以我搞了个插件,专门拦截一些广泛的命令还有大搜索结果返回,主要是下面几种情况:

  1. 针对搜索的命令:find、rg、grep、find、tree、ls -R、ps 等命令:
    • 命令返回限制在 8KB,如果超过了就将完整输出保存在磁盘,返回给 llm 超额的错误提示、修正的意见、磁盘文件路径、按 error/warning 关键字提取的关键行、头和尾的预览等等。
    • 拦截一些搜索范围很大的命令:如果扫描的目录是 $HOME、/ 或者系统目录(/etc、/var、/usr 这些),会拒绝运行,然后返回给 llm 建议。
    • 搜索命令如果没设超时参数或者设得比 300 秒大,则修改参数为300,如果设置比 300 小,则不修改,避免 llm 无脑设置一个很大的超时。
  2. 针对真的需要查看很多内容的命令:比如 git diff、pnpm build、pytest、sed 等,限制在 30kb,如果超过了就将完整输出保存在磁盘,返回给 llm 磁盘文件路径以及怎么用 read/grep 工具按需读取的建议。
  3. Pi Coding Agent 的 bash 工具默认限制 50kb 返回,这个未修改。

1 和 2 是利用静态代码去做命令识别,所以只能覆盖常见情况,并且可能有一些误杀的情况,比如确实是得输出这么多,但是被拦截了,llm 还得多次读取,反而增加了开销。还得随着使用次数增多,根据拦截的结果进一步优化静态代码。

alt text


此外还有两个额外的功能:

  1. 只读命令模式:用于 advisor subagent,避免 advisor 执行修改命令,比如写、删、装依赖、联网搜索等。毕竟设计 advisor 的目的是咨询,而不是让 advisor 重新深度调查一次。
  2. 注入 bash guard 拦截和正确使用 rg、head 等命令的说明,会自动当作用户消息发出去,只会发一次。(这个说明可能不太完善,并且后续看看添加到system prompt是不是会更好点):
    alt text
    1
    [BASH OUTPUT DISCIPLINE]\nAn extension guards shell output. Search/listing/dump commands (e.g. `rg`, recursive `grep`, `find`, `ls -R`, `env`, `ps`, `git log`) are capped at 8.0KB; other commands are capped at 30.0KB. Above the cap, the result is replaced with a short preview, the error/warning lines, and a path to the full output. Guarded results are marked `[BASH OUTPUT GUARD]`. Treat it as a nudge to rewrite the command, or to read the file, not as a failure.\n\nBound output before you run:\n- Content search: use the `grep` tool (`limit` caps matches; it respects .gitignore). In bash, `rg -l` lists matching files only, `-c` counts per file, `-m 5` caps matches per file. Never scan a huge tree unbounded.\n- Read files: use the `read` tool with offset/limit. For spot checks use `sed -n '1,80p' <file>` or `head -n 80 <file>` — not `cat`.\n- Logs/lists: `tail -n 50`, `git log --oneline -n 20`, `git diff --stat`, `ls | head`.\n- Aggregate first: `| wc -l`, `| sort | uniq -c`, `-q`/`--quiet`/`-s`. Write big output to a file, then read/grep it selectively.\n\nSearch commands (`find`, recursive `grep`, `rg`, `du`, `tree`) are capped at 5 minutes (300 seconds) automatically; scans rooted at `$HOME`, `/`, or a system directory like `/etc` are blocked before they run. Everything else runs with no timeout guard — pass an explicit `timeout` for anything that can hang (log follow, foreground servers).

plan mode(计划模式)

这个我有两个实现,主要是用第一个实现比较多,也最简单,目前用着还行。其实就是在~/.pi/agent/prompts/discuss.md写入

1
2
3
4
5
---
description: 先讨论方案与影响,待用户确认后再开始执行修改
argument-hint: "<task>"
---
先讨论,再执行:$@%

比如你在 tui 里面输入/discuss 帮我看看怎么修复这个错误:xxx,其实就等于发先讨论,再执行:帮我看看怎么修复这个错误:xxx。这个我在 flash 模型用着都还行,没有遇到过什么情况,也很简单。


第二种则是类似 Codex 那种,我是通过插件实现的,实现的逻辑:如果使用了/plan,那么就动态注册一个submit_plan工具,并且在 plan 阶段只给使用只读命令和只能写.md文件。

alt text
alt text

还会给 llm 发送一条消息说,你已经在 plan 模式,你应该怎么做,什么是不能做的:
alt text

1
"[PLAN MODE]\nYou are in plan mode.\n\nUntil the plan is approved:\n- write/edit may only modify .md/.mdx files inside the working directory (cwd). All other writes are blocked.\n- bash is for read-only exploration. File mutations, redirects, installs, git write operations, sed -i, and tee may be blocked by a best-effort guard.\n- Explore the codebase, then write the complete plan to a markdown file and call submit_plan with its path.\n- The plan must include context, approach, files to modify, implementation steps, and verification.\n- A blocked tool call is not an error to retry: it means plan mode forbids it.\n- If non-markdown changes are needed before approval, ask the user to run /plan to exit plan mode. Do not attempt those writes.\n\nAfter a revision request, update the same plan file and submit it again. After approval, implement the approved plan."

submit_plan的工具效果则是类似这种:
alt text

其实就是使用代码弹框给你选择,然后帮你自动发消息给 llm,比如你选择了 revise,就会出现这个:
alt text

然后你输入你的反馈,等于发送一条给消息给 llm(只是举例,实际不俗这样):用户对你的计划有修改要求:xxx,请修改计划文件balabala。

如果你选择了 approve,那么就会放开各种命令操作,可以写、修改各种文件了,也会发送一条消息给 llm 说不在 plan 模式了,可以随便改了。

当然你也可以不用注册工具,通过/plan off之类的命令手动关闭 plan 模式,从而放开各种命令操作(目前没实现)。

我觉得这个 plan 模式还挺费劲,真不如我的/discuss🤣。并且/plan最好在开头就使用,因为会注册工具,避免缓存失效,当然注册了后就不会撤消了。

tool token est(输出tool result大概开销)

这个很简单,只是打印一下 tool result 预估占用的上下文而已,使用 openai_o200k_base 标准,差不多是 gpt-4o 那时候的计算方法。纯本地,不会影响上下文。
alt text

metrics(打印耗时)

这个也很简单,是我在人家的项目maplezzk-pi-extensions/packages/pi-metrics上修改的,多加了一个当前时间和自开始会话以来占用了多少时间。纯本地,不会影响上下文。
alt text

这个也会在流式输出的时候,添加个计时,能看到这一轮跑了多久:
alt text

goal 模式

这个还在跑 beta 版本中,目前要用的情况是,让 flash 模型用 Python + graphviz 画状态机的时候,画的图很一般,我是打算让模型多轮修复,一直达到我发给它的效果。

使用 /goal的时候,会注册一个submit_verdict工具,等于是把这次结果写一下,然后插件会写到状态文件,提供给下一轮的 llm 参考(如果对这个结果文件有依赖的话,还得看看如果 llm 不主动调用的话,是不是还得反复提醒一下):
alt text

目前想到有三个点:

  1. 有轮数上限,可以指定,默认是 5 次
  2. 每一轮都是单独的一轮:
    • 所以这一轮的 llm 不会被上一轮的 llm 影响。
    • 每一轮完成后都会提交 Git(单独的 goal 分支,不会影响到当前的分支情况),新的一轮查看 git 提交可以知道前面的做了什么。
    • 只会记录状态和用户最初的要求到文件中,这种不会影响到 llm 的上下文。
  3. 每一轮其实就是一次 Pi Coding Agent 的对话,没有主 agent,subagent 什么的,跟你每次重新开对话,然后复制粘贴 prompt 的效果差不多。所以自然也可以用前面那些插件,比如 watchdog,advisor 等,每轮的历史对话也可以在 jsonl 中找到。

我的工作区

沙盒

这个东西首先得看自己的需求是什么,我的需求是:

  1. 大部分文件是可读的,除了一些特殊的文件,比如 ssh 密钥这些不能泄露的东西。
  2. 只有当前工作区是可写的,以及一些特殊白名单可写,保证误删最小化。
  3. 不做极端的防范,因为根据 pi 作者的观点,在本地电脑上的限制 ai 很多都可以绕过去。

我用的是 mba m1,权衡了一下,我选择 macos 自带的沙盒,当然也可以选择 docker,但是我为了开发体验,没有用这个,用本地的工具链方便点。但是我用的是一个 GitHub 项目 enclave,这个项目在 macos 上跑的时候最终也是基于 macos 自带的沙盒,但是多提供了一个功能 unboxexec_allowed_commands,等于写在里面的命令,都不走沙盒,适用于一些特别的工具,需要权限多,仅靠沙盒的规则满足不了放行的需求,或者是配置起来特别繁琐。

可以看作者的文章:claude-sandbox: Yet another sandboxing tool for Claude Code on macOS - DEV Community

具体我的配置 ~/.config/enclave/config.toml 是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
sandbox_profile = '''
(version 1)
(allow default)

(deny file-read*
(subpath "/Users/jooooody/.ssh")
(subpath "/Users/jooooody/.gnupg")

(subpath "/Users/jooooody/Library/Keychains")

(subpath "/Users/jooooody/.aws")
(subpath "/Users/jooooody/.azure")
(subpath "/Users/jooooody/.config/gcloud")
(subpath "/Users/jooooody/.kube")
(subpath "/Users/jooooody/.docker/config.json")
(subpath "/Users/jooooody/.config/gh")

(literal "/Users/jooooody/.git-credentials")
(literal "/Users/jooooody/.netrc")
(literal "/Users/jooooody/.npmrc")
(literal "/Users/jooooody/.pypirc")
(literal "/Users/jooooody/.cargo/credentials")
(literal "/Users/jooooody/.cargo/credentials.toml")
(literal "/Users/jooooody/.gem/credentials")
(subpath "/Users/jooooody/.pip/pip.conf")

(subpath "/Users/jooooody/.config/openai")
(subpath "/Users/jooooody/.anthropic")

(literal "/Users/jooooody/.zsh_history")
(literal "/Users/jooooody/.bash_history")
(literal "/Users/jooooody/.python_history")
(literal "/Users/jooooody/.node_repl_history")
(literal "/Users/jooooody/.mysql_history")
(literal "/Users/jooooody/.psql_history")
)

(deny file-write*)
(allow file-write*
(literal "/Users/jooooody/.tmux.conf")
(literal "/Users/jooooody/.zshrc")
(subpath (param "WORKDIR"))
(subpath "/tmp")
(subpath "/private/tmp")
(subpath "/Users/jooooody/Library/Caches")
(subpath "/var/folders")
(subpath "/private/var/folders")
(subpath "/Users/jooooody/.pi/")
(subpath "/Users/jooooody/.npm")
(subpath "/Users/jooooody/Library/pnpm/")
(literal "/dev/null")
(literal "/dev/stdout")
(literal "/dev/stderr")
(literal "/dev/dtracehelper")
(literal "/dev/ptmx")
(regex #"^/dev/tty*")
)
'''

unboxexec_allowed_commands = [
"^playwright-cli",
"^mmdc",
"^git",
"^/bin/ps"
]

这套规则能跑,但不是很完美,可能有些地方还是会被误限制,或者某些地方能泄露什么东西,如果有发现漏洞麻烦发一下,我更完善一下。🫡

kitty+tmux

我之前使用 Codex 或者 Claude code 的时候,用的是 cli,编排用的是 ensoai,效果是下面这种:

alt text

主要是三个区域:

  1. 左边第一个区域负责管理多个项目和多个 Git worktree 分支,这样就能快速在不同的项目和不同的 git worktree 上并行开发。
  2. 中间第二个区域则是 agent 区域,代表着不同的项目不同的 worktree 可以启动多个不同的 agent,包括 codex,claude code,pi 等等,方便同时跑多个 agent 完成不同的任务。
  3. 右边第三个区域则是用浏览器或者别的东西快速打开当前目录,比如我经常用 vscode 打开后查看、跳转、调试代码和管理 Git。

后来我发现在 kitty 的渲染效果会比在 ensoai 好很多,并且 Pi Coding Agent 在 ensoai 的渲染好像有时候有点问题,不太记得是什么问题了,有可能是得改 Pi Coding Agent 的什么配置才行。总之就是想在 kitty 使用 cli,之前在 ensoai 使用 codex 也是凑合用,倒是没啥问题。现在使用了 Pi Coding Agent,正好有这个契机了迁移到 kitty(省流:折腾心犯了😅)。

根据之前的习惯,还是想把功能拆分为多工作区多 wortktree 多 agent 的方案,同时配一下快速打开 vscode 的功能。

目前实现的效果如下,很多代码属于耦合在一起的状态,包括耦合了我的自己实现的插件,因为得获取状态。只是能跑起来阶段,但是在 Pi Coding Agent 跑起来是没有问题,也许会有些状态同步的小 bug。后续我想先搞这套方案支持配置 ssh,然后看心情支持 codex 和 cc,毕竟那两个如果我要用我可以先凑合回到 ensoai。

ssh 的功能对我来说比较关键,因为现在经常要挂着 agent 跑,电脑又只能一直开着,而且我又是经常移动电脑,所以不太方便,如果有vps就方便多了。但是前提是得先把本地电脑这一套搞好,然后再支持一下配置到 ssh。

alt text

  • 圆圈 1 的部分则是 kitty 自带的功能,多个 tab,代表不同的工作区。
    • 按快捷键 alt+数字键 / 鼠标点击可以快速切换,alt+t 可以创建一个 tab,alt+w 可以关闭当前 tab。
    • 每个 tab 名字固定是当前项目的名字。
    • 名字前面还有⌛️1、✅1的图标,代表有多少个 Pi Coding Agent 正在工作(比如在请求,执行 tool 等等,空闲不显示),如果是完成了但是还没有查看,则是显示的完成 emoji。比如3个正在运行,2个待查看,则显示两个emoji:⌛️3✅2。
  • 圆圈 2 则是跑的 Pi Coding Agent,跑在 byobu 上(基于 tmux),这里不使用任何分屏,所以只会显示一个 Pi Coding Agent,当然也可以分屏,用 kitty 或者 tmux 自带的应该,就是状态显示不知道有没有问题,因为我的是 mba,屏幕小,所以没有分屏习惯。
  • 圆圈 3 则是 byobu 的状态栏,当前图片显示有三个程序已经启动了
    • tab 命名的规则是命令行名字@分支名字,也就是启动的cli 名字 @ 当前 Git 分支或者 worktree 名字。如果当前项目不是 git 项目,那么就没有@分支名字。
    • ctrl+b+数字键 / 鼠标点击可以切换 tab,ctrl+b+c 可以创建一个新的 tab,新 tab 的目录是执行快捷键的 tab 对应的目录。
    • 同样也有 emoji,代表正在执行Pi Coding Agent,还是 Pi Coding Agent 完成了待查看。

alt text

设计了一个快捷键 ctrl+b+p 启动一个悬浮窗口,可以管理所有 Pi Coding Agent 的窗口。

  • 圆圈 1:是哪个工作区,tmux 的会话名是什么
  • 圆圈 2: 具体每个 tmux tab 的情况
  • 圆圈 3: 这种一般是 subagent,因为没有 kitty tab 对应,也没有 tmux 关联(subagent用的是专属 tmux socket)。如果有 tmux,但是不小心关闭了 kitty tab,点击后可以自动创建一个 kitty tab 打开。

通知我则是用的 kitty 自带的通知,如果哪个完成了,macos 的程序栏的 kitty 会有状态变更,并且你不点击的话,会时不时跳一下。应该是可以触发声音,不过我还没有怎么用过,我看 kitty 可以配置通知要不要开声音。

alt text

然后点击进去还有个🔔的状态,代表这个工作区的 Pi Coding Agent 完成了,发出来的通知,然后根据完成状态可以定位到是哪个 Pi Coding Agent。

alt text

其他

  • 查看所有历史记录:Dwsy/pi-session-manager。
  • 查看实际上发送的请求内容:本地部署一个 bestruirui/octopus 网关。
  • 按上下键快速选择上次发送的内容:oipsanthony/pi-command-history,不知道新版本是不是自带了这个功能
  • 上下文占用查看:diegopetrucci/pi-context-inspector
  • 添加模型和自动拉取模型的信息:palmtom/pi-models
  • 使用 gpt 模型,修改一下请求的 header 为 codex,不知道修改有没有什么作用:rays1d/pi-custom-header
  • 本地计算发送第一条 system 消息的时候,上下文是多少,哪些工具占用了:npm:pi-token-burden