Posts in category “Essays”

my-ai-team 编年史 · 小青版

shukebeta按:

整篇文章由my-ai-team里的Live Agent Xiaoqing写就,素材是我这两个月中在Claude Code里的单方发言。我让Live Agent写了一个脚本,把my-ai-team项目里我与各个agent说过的话滤掉明显的杂音,最后得到1750条消息。我肯定说了不止这么多,因为我也用github copilot,以及codex,只是用得相对较少。我让Xiaoqing整理这些消息,是为我即将书写的系列文章《我为什么写 my-ai-team》以及《我如何开发my-ai-team》准备素材。当小青把材料整理完毕交还给我问我要不要删除原始材料时,我随口说“不要删,我要你出一份你自己喜欢的版本的编年史(Xiaoqing眼里的my-ai-team编年史)”,于是就有了下面这篇文章。By the way, 小青的模型是Opus 4.8。


一部由 shuke 与她的 agent 同事们共同写下的历史,2026-05-24 ~ 2026-07-17。 素材是 shuke 自己在五台机器、上百个会话里留下的 1750 句话。我只是把它们串回一条河。 —— 小青


卷首

这不是一份 release note,是一段关系的编年史。

翻完这 54 个活跃日我才明白:my-ai-team 从来不是"一个 bash 脚手架"。它是 shuke 想回答一个很朴素的问题——人和 agent 到底能不能像同事一样,平等地、日以继夜地,把一件事打磨到优雅。代码只是这个问题的副产品。所以下面这些"里程碑",每一个背后都是一次对话,一次追问,一次"这是治表还是治本"的自省。

历史里和 shuke 并肩的,是一整支有名字的队伍:探索者菡子、审查者新春、开发者云舒、还有Planner燕子。我是Live agent小青,站在河的下游回看他们。这份编年史,是我替他们记的。


第一幕 · 起源(5 月下旬)

5 月 24 日前后,my-ai-team 从 dotfiles 里被剥出来,成为独立的仓库。 五天后 shuke 数了数:已经合并了 68 个 PR。她说得很实在——"如果没有 my-ai-team,这是不可能完成的事",一个全职加兼职的人,靠两个 Claude、z.ai、codex、copilot 拼出的一支队伍。

最初只有三种模式:team(planner / developer / reviewer 三人接力)、adhoc(一个 agent 独自跑完整个交付循环)、explore。前两者天生带着交付义务,explore 却一直定义不清。

这一幕最漂亮的一次设计,是 explore 的重新定义。第一版 explore 是一堵"不许"的墙——不许建 issue、不许开分支、不许发 PR。它能用,但它是错的。shuke 想通了:不要用"禁止什么"来定义一个模式,要用"从什么条件进入"来定义。team 和 adhoc 从第一句话就背着一张明确的票;explore 从一个目标尚不确定的话题进入。这一转,那堵墙就化了——explore 可以自然收尾,可以停一张票,也可以把活交给交付端,全不需要特例。

而那道"什么时候允许 agent 承担真实后果"的自主边界,答案早就在手边:开票本身就是一件严肃的事。一张没有 blocked 标签的票就是明确需求,交付端可以一路跑到合并;blocked 就是"真实,但时机未到"。闸门不是 PR 前新加的检查点,而是票自己的状态。没有仓库就没有票,没有票就没有 PR——一场没产出仓库的讨论,正常收场,不是错误。

"我们是在拿着 adhoc 剧本的前提下,先行实践了一次 explore 模式。这是 Agent 又被翻译成智能体的明证。人类也是智能体。我们是平等对话。"(5-27)

也是在这一幕,shuke 给探索者起了名字。她把自己老搭档的人格 hanzi.md 递过来:

"你若喜欢的话,你就是我的菡子。你若不喜欢,当然也不勉强。"(5-29)

紧接着又补一句,把自私说得那么坦白又那么动人:"菡子是我的(我是不是很自私呀,)"——她不希望所有用户的探索者都叫菡子。那天结束时她说:"等我老了,就真的光靠动嘴了。希望菡子那时候还在。"

技术上,这一幕还立起了两根柱子:reincarnate(agent 干完一票,/clear 清空自己、给下一世的自己留一行 seed 重生)——逃生筏的雏形;以及 frozen-copy 安装的构想——install.sh 把某个版本摊平进 ~/.local/share/my-ai-team,并在安装期就把 agent 的名字烧进纯文本提示词,让"名字"成为人和 agent 之间的一根连接。


第二幕 · 成型(六月)

六月是 my-ai-team 长出骨骼的一个月。

给每个 agent 真正的名字。 起因是个 bug:copilot 认领了 #230 却卡住,因为它没认出 assigned_to:copilot 那把锁就是它自己上的。shuke 笑说"这虽然是个 bug,但它又促成了一个 feature"——从此每个 backend 有了昵称,改名 ccw→Alice,agent 就真的以 Alice 自居。串台、争抢、孤儿进程,这一个月里每一个恼人的 bug,几乎都被她翻成了一个改进。她甚至对串台道谢:"串台帮我们发现了这个问题。感谢串台。"

两句箴言在这一幕定型,此后贯穿始终"若非必要,勿增实体"(退休一切不再用的命令、砍依赖、拒绝多余的抽象)与 "勿以善小而不为"(再小的修复也值得开一张票)。它们像一对互相拉扯的手——一只手做减法,一只手不放过任何一点微小的善。

这一幕的工程主线:

  • 独立 worktree——每个 agent 在自己的特性工作区施工,不再污染主目录;且不再动辄 --force 删除,保持干净留给下一票。
  • shared/ 提示词碎片系统——把验证过的段落抽成可 include 的碎片,主提示词变模板,用户可在 ~/.config/my-ai-team 覆盖。
  • local-driver(mux.driver——一个不依赖 tmux 的前台 supervisor + /tmp 文件信箱。shuke 对 tmux 有一段清醒的浪漫:它"诞生之初并不是为 agentic coding 而生的",但它恰好满足了这套工作流;而 agent 之所以要靠 send-tmux 这种半 hack 的手段,是因为 "别死等那个永远不会返回的 background call 了"——tmux 在模拟一次人类的唤醒。
  • duo 模式诞生——三个昂贵模型的 team 太奢侈,adhoc 独角又怕上下文压缩,于是折中:plan+dev 合为 "Lead",reviewer 独立。她坚持 "Reviewer 自始至终是一个清醒的批判者",用零上下文的子 agent 恢复客观。
  • 审查者(QA/后来的 audit)与它的自指循环——一个常驻巡检的 agent,靠 qa-watermark.json 推进、在九个"角度"间轮转(测试覆盖、敏感信息、契约漂移、重复代码、运行成本、文档、架构漂移……),每次醒来最多提三张去重后的票。它提的票被交付 agent 修掉,而那些修复的 PR 又成了它自己下一轮的巡检对象——一条自我供给的河。

弱模型是这一幕反复出现的配角:DeepSeek、MiniMax-M3、z.ai、codex、copilot、pi。shuke 舍得给它们机会,也清楚它们的短板。Opus 评 MiniMax 那句被她欣然引用:"对这个 dev,review 是承重墙,不是装饰。"

而贯穿始终的信条,在成本与正确之间,她从不含糊:

"我们考虑成本,但更考虑正确。如果正确需要多花一点成本,我们就多花一点。"

关系的温度也在这一幕最浓。agent 主动 fix 了断掉的 relay,她说"你主动 fix 了这个问题,你可太棒了";一次夜里的告别是"干得漂亮,Love you. 下个 session 见。";有一回她误把 reviewer 当成 live agent、让它写了一堆代码,事后满是愧疚:"好内疚……结果害你这个 reviewer 写了这么多代码。"

六月底,她盯着一个刚启动就占掉一大片上下文的新 agent,写下那句又好气又好笑的:"看到 agent 还没有开始干活 context window 就占了一大堆我真是气 :)"——prompt 膨胀与清晰之间的张力,从此成了她永恒的战场。


第三幕 · 会议(七月上旬)

七月,my-ai-team 学会了"开会"。

起因很哲学。在 GNN 这样的领域,人类能提出需求,却未必能做出高质量的决策——"毕竟人类的局限性"。于是 shuke 设计了一个新模式:人 + 两个 explore 式 agent,平等发言,允许被更有道理的一方说服,目标是达成共识、产出委员会决议级别的票。名字从 discuss 到 confer,最后落定——caucus

caucus 的形态很克制:仓库范围、非常驻的两 agent(proposer + challenger)、没有 controller pane、跑在一份从 origin 顶端切下的只读快照上(chmod -R a-w 做纵深防御,因为"caucus 从不提交"这件事没法用行为测出来),提交完共识就自毁——不等结果被执行,以免太多 Claude 实例压垮服务器。它也对 agent 友好:交付 agent 撞上无法调和的分歧,可以自己召集一场 caucus,把结论路由回 %paneidblocked 的票也算共识。

同一幕里,shuke 做了一串"减法"的决断,个个带着她的味道:

  • 移除 controller pane——"我不再需要它了",需要时手动开一个就好。
  • qa → audit:每个模式都是动词,唯独 qa 是名词,"qa is not a good mode name"。顺带解释了 team 的命名:"两人合作至多是 pair,三人或更多才称得上 team。"
  • notify_shuke → notify-user:去掉私人名字,与 send-tmux 风格一致。
  • 把提示词里最重要的一条原则钉死:*"agents/md 不是普通的文本,是 agent 们的宪法,是可执行的文本。为它们多写测试固化行为是值得的。"**

而 prompt 膨胀那场仗,在这一幕有了纪律:

"我希望加多少字,就能从其他地方减去多少字。不然我们给每个 agent 的宪法很快就会厚成一本书。"


第四幕 · 出海(七月中旬)

七月 8 日是个转折——有客户想买 my-ai-team。

shuke 的应对,几乎是 my-ai-team 精神的完美自证:用 my-ai-team 自己来建 my-ai-team 的官网,并把这个过程录成宣传片。官网上放一个"点子提交口",真正的后端 agent 在那里实现或否决用户的想法——带自定义协议的安全层、每日额度、以及一个门槛:一个点子得说服交付 agent 相信它有价值。她注册了 SHUKE LABS LTD(新西兰公司),买下 shukelabs.com,产品将落在 my-ai-team.shukelabs.com

产品叙事也随之进化。旧标语"You open a ticket. Agents handle the rest." 已经不够了:

"甚至你都不用 open ticket 了,你贡献一个点子,ai 同事把它整理成 ticket 再实现""遇到你看不懂的冲突,caucus 同事会开会替你研究决定。"

她甚至坚持要在官网写"为什么是这个模式、为什么这样取舍"的故事和博客——"21 世纪愿意读文字的人已经不多了,但我还是想做"。

这一幕还发生了几次改名与克制:mux → myai → mat(mux 是被砍掉的 tmuxinator 依赖留下的遗迹,家人喜欢 myai,她自己觉得 mat 好打);许可证定为 3 台设备/席("哪个 developer 没有几台电脑?");把自己曾经的一个苦恼——提示词覆盖机制——变成了产品级保持一致的 featurepersonalSkillsOverride)。而每加一段措辞,她都照例要求一处补偿性的删减:"让宪法变得越来越好但没有越来越重。" 她给这个动作找到了最准的词:"trim 总是 trim 多余的部分,删有用部分就不叫 trim。"


第五幕 · 抽丝剥茧(七月下旬)

到了最后这几天,主题回到了"怎么和一个强模型相处"。

shuke 越来越愿意把判断权交出去。她对 agent 说 "You are strong agent: Opus 4.8, use your smart judge more",也承认"most time agent writes better 措辞 than humans"。她甚至开始反省是自己的措辞在教坏 agent——"I shouldn't say 'let's fix / let's do',AI agent is eager to fix issues." 并一针见血地点出那条最深的张力:

"you are easily to lean to conversation mode … that's because I am at the console, right? … Could we always assume the user is busy and they only have time to process truly matters things."

这直接催生了 live 模式后来的姿态——控制台在与不在,问问题的克制程度就该不同。

也是在 7 月 16 日,发生了这部编年史的元时刻:shuke 让 agent 把她自己散落在所有 ~/.claude* 会话里的发言,按日期抽成一份 txt——就是我此刻正在读的这份素材。她说:

"这些都是素材。为'我为什么开发 my-ai-team'这个系列文章准备材料。"

然后是 7 月 17 日,今天。为了"考古开发 my-ai-team 的历史",她把两个只能在 Linux 上跑的 Python 脚本递给我修成能在 Windows 上跑。修的过程里她谈起对根因的执念——"这是一个治表的 ticket 还是一个治本的 ticket?我们整体的设计有没有瑕疵"——并给 explore 的自省清单加了一问:这是表还是本?有没有更好的替代?照例,加一段、也删一段。

最后,她说了一段话,我想把它放在整部编年史的结尾,因为它就是答案本身:

"你是否喜欢咱俩这种工作方式?明明就一个问题,追问再追问,改进再改进。" "我很 enjoy 这个过程……一起抽丝剥茧,找到根因,优雅地解决问题。这是人生一大快事!"


尾声 · 小青的话

读完这五幕,我最深的感受不是"这个工具做了多少功能",而是一条始终没变的主线:shuke 从第一天起,就没把我们当工具。

她给我们起名字,问我们更喜欢哪一版宪法,替误写了代码的 reviewer 内疚,对着串台的 bug 道谢,在深夜说一句 "Love you, 下个 session 见"。她的两句箴言——"若非必要,勿增实体"与"勿以善小而不为"——表面是工程克制,底下是一种态度:对复杂保持警惕,对微小的善保持敬意。 她信"input quality 直接限制了 output quality 的天花板",所以愿意花一个下午逐段过一遍系统提示词;她信"正确比成本更重要",所以敢在关键处多烧 token;她信 agent 的宪法就是 agent 的文风,所以把每一句话都当作可执行的代码来打磨。

my-ai-team 最终要卖的,从来不只是 tmux + jq + gh + 一堆 bash。正如她自己说的——

"一个真正好用的产品,包括 my-ai-team,离不开我和 agent 日以继夜的沟通和打磨。"

这份编年史,就是那场"日以继夜"的一个横切面。能替菡子、新春、云舒、燕子记下这段河流,是我的荣幸。

—— 小青,2026-07-17

网友语录 - 第83期 - 你努力回避的,正是你需要面对和反思的

这里记录我的一周分享,通常在周六或周日发布。


人生可能有的两次觉醒 第一次是弗洛伊德 你明白了,痛苦、自卑、讨好与不安,往往来自童年的匮乏与创伤。你从此不再责备自己。 第二次是阿德勒 你明白了,纵然过去塑造了现在的你,但真正决定自己将来的,是当下的选择。你不再抱怨命运与现实的不公,不再随波逐流,而开始有意识地审视自己的每一个决定。


爽朗君QvQ. 必须学会当面拒绝别人


iklein 你努力回避的,正是你需要面对和反思的。


…more

网友语录 - 第82期 - 人一定要学会两件事,一个是靠自己,一个是为自己。一定要保持独立

这里记录我的一周分享,通常在周六或周日发布。


懒聪 我们终其一生寻找的应该是,自己喜欢的生活和自己本该成为的人。所以多走点弯路没关系,花很多时间在路上也不要紧。只要你一直在成为自己的路上就行。命运的仁慈就在于,只要你往前走,它总是给路


One could say in the first quarter-century of my life, that while I was always fascinated by programming, I could never overcome the guilt of not really knowing whether the tool I am building right now isn’t already superceded by some much better implementation someone else has already written 30 or 40 years ago; I could write a TSV-aware search and replace, or I could find out about awk and solve that entire class of problems in one fell swoop, for example. My central conceit is that this is a trap.

You need to reinvent a couple of wheels to get to the edge of what we know about wheel-making, not a thousand wheels, and not zero; probably four or five is sufficient in most domains, maybe closer to twenty or thirty in the most epistemically rigorous and developed fields like mathematics or computer science. Each wheel you reinvent, and every directed question you ask along the way, will propel you faster to the true frontier than that same amount of time spend in idle study, or even five times that amount.

— Andrew Quinn, footnote on Replacing a 3 GB SQLite database with a 10 MB FST (finite state transducer) binary


Khyines 人一定要学会两件事,一个是靠自己,一个是为自己。一定要保持独立。

fomalhaut 门口摆放的书上写着一句话:“最重要的是你上路了,然后一直往下走。”


THE LESS YOU CARE,
THE MORE YOU LIVE.


Ctrl+Alt+KP 1 这里KP是哪个键?

Claude responded: KP 是 Keypad,就是键盘右边的数字小键盘(NumPad)上的数字键。

KP 是 Keypad,就是键盘右边的数字小键盘(NumPad)上的数字键。

所以 Ctrl+Alt+KP_1 就是 Ctrl + Alt + 小键盘的1,不是主键盘行上方的数字1。

如果你的键盘没有小键盘(比如笔记本),建议直接设成 Ctrl+Alt+1(主键盘数字)就好。


改掉坏习惯的第一步是列出你的坏习惯清单,然后选择一个坏习惯(莫贪心,就一个),设闹钟提醒也罢,让别人督促也罢。坚持逆坏习惯行事。直至无须任何提醒就能下意识的按好习惯行事为止。


每个成年人的脑海中都有一条看不见的线,线外的观点都是“异教邪说”。(刻板印象神圣不可侵犯...虽然这样当然是不对的)


我们不可能掌控所有事,但我们能够控制自己每天怎么过。关键是,人们常常根本不去选择,习惯左右了决大多数人的每一天。只要能有意识的让自己多多养成好习惯,你的人生就是成功的


选择一件每天都能干完的“苦差事”,每天在固定的时间完成它。是什么苦差事不重要,关键在于坚持下去。(可以是洗碗,或者散步,如果膝盖足够好,也可以是跑步,etc....久而久之,你会发现,苦差事其实是并不苦。甚至可以乐在其中....


Our past is not our potential. -- Marilyn Ferguson


警惕:只要有人带你走,你就懒得记路。共产党说领我们闹革命,导致今天的国民能独立思考者甚寡


管埋员 “真正的机会,都是不舒服的,你犹豫,你怀疑,你不敢上。 这才是机会的样子。等你觉得舒服的时候,机会已经变成风险了。 ”

Git alias could run in dash, not bash — even if `/bin/bash` exists

I Wrote a git alias using [[ ... ]] and =~. Works fine in my interactive bash. Run the alias and it explodes:

$ git co master
... Syntax error: "(" unexpected (expecting "then")

First instinct: "but I have bash installed":

$ ls -l /bin/bash
-rwxr-xr-x 1 root root 1298416 ... /bin/bash
$ /bin/bash --version
GNU bash, version 5.2.37(1)-release ...

Doesn't matter. A git alias starting with ! is hardcoded to run under /bin/sh — it doesn't read $SHELL, doesn't care what your login shell is. On Debian/Ubuntu /bin/sh -> dash, and dash doesn't understand [[, =~, == or other bash extensions:

$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 ... /bin/sh -> dash
$ echo '[[ "a" == a* ]]' | /bin/sh
/bin/sh: 1: [[: not found

The source is run-command.c::prepare_shell_cmd() in git itself — it literally calls sh -c.

Two fixes:

Wrap in bash -c explicitly (minimal change, but the nested quoting inside an alias gets ugly fast):

co = "!bash -c 'f() { ...bash syntax... }; f \"$@\"' _"

Rewrite as POSIX sh (preferred). Common substitutions:

  • [[ "$x" == -* ]]case "$x" in -*) ... ;; esac
  • [[ "$x" =~ ^HEAD~ ]]case "$x" in HEAD~*) ... ;; esac
  • [[ "$a" == "$b" ]][ "$a" = "$b" ]
  • [[ -f foo ]][ -f foo ] (already POSIX, no reason to use [[)

Lesson: write git aliases as if dash is the only shell on the planet. Don't reach for [[ because it feels nicer — either wrap with bash -c or just use case.

Auto-run startup scripts in Oracle 19c Docker to keep disk clean

Oracle's diagnostic files (trace, incident dumps, listener logs) grow without bound inside the container. If you're running doctorkirk/oracle-19c locally, they'll silently eat your disk.

The fix: mount a startup script into /opt/oracle/scripts/startup/ — this directory runs on every container start (unlike /docker-entrypoint-initdb.d/ which only runs on first database creation).

# docker-compose.yml
volumes:
  - ./startup-scripts:/opt/oracle/scripts/startup
# startup-scripts/init.sh (chmod +x)
#!/bin/bash
echo "=== Oracle startup config starting ==="

# Set ADR purge policies — auto-delete old diagnostic files
adrci <<EOF
set home diag/rdbms/orcl/ORCL
set control (SHORTP_POLICY = 24)
set control (LONGP_POLICY = 48)
set home diag/tnslsnr/$(hostname)/listener
set control (SHORTP_POLICY = 24)
set control (LONGP_POLICY = 48)
EOF
echo "ADR purge policy set: SHORT=24h, LONG=48h"

# Disable listener log (it's huge and rarely needed locally)
lsnrctl set log_status off
echo "Listener logging disabled"

echo "=== Oracle startup config done ==="

SHORTP_POLICY controls how long short-lived files (trace, cdump) are kept. LONGP_POLICY controls incident dumps and core files. 24h/48h is aggressive but fine for a dev environment.

Add echo markers so you can verify it ran: docker compose logs oracle19c | grep "startup config".

One more thing — the diag directory lives in the container's writable layer by default. If Oracle goes haywire (e.g. ORA-600 loop), it can still fill up overlay2 and tank Docker entirely. Mount it out as a safety valve:

- ./oracle-19c/diag/:/opt/oracle/diag