Files
ISEP/docs/governance/dispatch-and-reply-format.md
T
Leo 0999effcfc 權限一律用無副作用試寫判定,不採信 API permissions 欄位(inkstone/ISEP#120)
派工共通規定(dispatch-and-reply-format.md §2,dispatch-format-guard 每次派工自動注入)
新增第 10 條:判斷「能不能寫進某個 repo」用 `git push --dry-run` 這種無副作用試寫,
不採信 API 的 permissions 欄位——它會回 push:false 卻推得進去
(2026-09-01 實錯 inkstone/Arcrun#190 comment 5805:拿 permissions.push=false
去叫 leo 加協作者,實際推得進去)。並把失敗分成掛住(傳輸)/被拒(權限)/
找不到(不存在)三種,混成「不行」會往錯方向查(同票 comment 5821)。

- §2.1 附完整實測與理由(不進注入,只是背景)
- 掃過現存閘與腳本:零命中——沒有一支用 API permissions 欄位判寫入權限;
  唯一的 permissions 字樣是 Claude Code 自己的 permissions.allow 白名單,另一件事
- dispatch-format-guard.test.sh ⑦b 驗那條規定真的被注入(52→53)
- 用 env -u CLAUDE_CODE_CHILD_SESSION 跑;subagent 環境殘留會假紅(TESTING 已註記)

改到會被注入的內容 ⇒ 待總管定版

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TGxitYq49FzYC7EFkbhzF5
2026-09-18 17:57:21 +00:00

13 KiB
Raw Blame History

派工單與交件回覆的格式(共通規定)

leo 2026-08-27inkstone/ISEP#30 comment 4322 / 4325 / 4327): 「你用一個 output parser 把你給 subagent 的指令規範,分作幾點,每一點規定格式 照這種散文寫法根本無法迭代」/「警察也不能抓」/ 「交件方式不需要寫,定義在原則裡,每張票都要做這件事⋯⋯每次都一樣提取出來變成共通規定」/ 「(那些 session 事實)這些為什麼不寫到票裡?」/「subagent 回覆時要表明身份

本檔就是那份「共通規定」。 它不是給人讀完記住的—— hooks/dispatch-format-guard.sh 會把 §2 自動注入每一次派工, 所以收工方沒讀派工單也會拿到


1. 派工單 票號。就這樣

【工單】inkstone/ISEP#30 → comment 4322

要帶兩張票就兩行 【工單】沒有第二個欄位。

為什麼

派工單裡想寫的東西只有兩種,兩種都不該留在派工單

種類 舉例 該住哪
每次都一樣 交件方式、不要 push main、org 是 inkstone、先讀該 repo 的 CLAUDE.md 本檔 §2(機器自動注入)
這次才知道 main 現在是哪顆、今天撞過什麼、另一條線正在動同一個 repo 寫進那張票

判準一句話:「這句話換一張票還成立嗎?」 還成立 ⇒ 共通規定。只有這次成立 ⇒ 寫進這張票。兩種都不進派工單。

🔴 「票上還沒有」不是把它寫進 prompt 的理由——它是「去把它寫上票」的指令。 寫進 prompt 的後果:那個 agent 被停掉或換人接手,那段事實就隨 prompt 消失。 2026-08-27 實害:總管停掉重派 3 次,前兩次的任務與 session 事實全部蒸發

機械閘

hooks/dispatch-format-guard.shPreToolUse TaskAgent)—— 【工單】 以外還有實質內容就擋,並指出那些內容該搬去哪一格。 判準是**「這一行是不是【工單】欄位」**(在不在),不是「它寫了什麼」。


1.5 派給誰:從名單裡挑一個有名字的工人inkstone/ISEP#86

派工單只有票號,那「這是誰的活」寫在哪?——不寫在派工單裡,寫在 subagent_type

Task(subagent_type="isep-hand", prompt="【工單】inkstone/ISEP#86")
                   ▲ 名單上的名字        ▲ 派工單仍然只有這一行
名單在哪 agents/,一個檔案一位工人(規約:docs/governance/worker-roster.md
怎麼看 python3 scripts/roster listshow <名字>which <owner/repo>
機械閘 hooks/roster-guard.sh——沒指名或名單上沒有這個名字就擋,並印出整份名單

為什麼「你是誰」不能收進 §2 的共通規定:共通規定是「每次都一樣」的東西, 而「你是誰、你該讀哪些、你的紅線」每個工人都不一樣。所以它有自己的家(名單), 由 roster-guard.sh 在派工當下注入——同樣不必有人記得寫。


1.6 回覆也是派工:同一套規矩,同一支閘inkstone/ISEP#88

派工單被壓成只剩票號之後,回覆一個正在跑的 subagent 這條路沒有被管到—— 那支閘掛在 PreToolUse(Task|Agent),而回覆走的是別的工具,根本不經過那個攔截點。

實況(2026-08-27):第一次派工乾乾淨淨只有票號,中途回覆時又把一長串修改要求丟過去。 那些話票上一個字都沒有,那條線被停掉或換人接手就消失。

不另造一支平行的閘:同一支 dispatch-format-guard.sh、同一個判斷函式, 只是把所有通往 subagent 的路一次列全(表在 hooks/lib/dispatch_parse.pytool_channel()):

裝話的欄位
TaskAgent prompt
SendMessage message
雲端 create_session prompt
雲端 send_message textmessage
雲端 create_triggerupdate_trigger prompt
雲端 fire_trigger text
雲端 send_later message
Bash claude -p <prompt> 指令裡那段 prompt

🔴 新增一條路要加在那張表裡,不要再開一支閘。 「認動作的方式漏了一條路」是這一族的通病——ticket-api-bypass-guard.sh 的檔頭 記過同一課(第一版只認大寫裸字 POSTrequests.post() 與隱式 POST 全部漏掉)。

刻意不管的方向subagent 往上回報(SendMessage to: "main")=交件不是派工。 擋它等於擋掉交件本身。


2. 共通規定(每一次派工由機器自動注入給收工方)

你收到的派工單只有一個票號。任務全文在票上。

  1. 第一個動作是去讀那張票(含每一則 comment)。派工單不會再給你別的東西—— 這是刻意的:票活得比任何一個 agent 久。
  2. 票上的脈絡不夠 ⇒ 回票上問,不要憑猜測動手,也不要回頭問派工的人要細節。
  3. 交件=貼回那張票scripts/ticket say <owner/repo#N> -F <檔>), 不是只在對話裡回報。回覆第一行必須是身份欄,見下。
  4. 回覆第一行一律是 【身份】<你的名字><owner/repo><你的分支> 名字用機器注入給你的那個(isep-handscout…);沒有名字時才退回 總管subagentleo 三選一。票上要看得出是哪個工人做的, 而三條線並行時「subagent」這個字回答不了「是誰」。
  5. 不准 push 到 mainmaster,也不准部署 prod。做在自己的分支上,交回分支名。
  6. Gitea 的 org 是 inkstone(不是 Leo);gh 打不到 Gitea 標籤是 s/* 不是 status/*
  7. 先讀你要動的那個 repo 的 CLAUDE.mdsystem-dev/wiki/,照它的慣例走, 不要照你自己習慣的做法。
  8. 交出去之前,你要知道它能不能用——貼實測輸出,不是「我測過了」。 有一格沒驗 ⇒ 那是 report 不是 deliver,講清楚哪一格。
  9. 改了會被載入的東西(pluginworkerbundle)就要升版, 否則產物按版本號分資料夾,你的改動到不了任何人手上。
  10. 要判斷「我能不能寫進某個 repo」時,用一次無副作用的試寫探測,不要採信 API 的 permissions 欄位——它會回 push:false 卻推得進去(實測見 §2.1)。用了那個欄位下結論, 你會去麻煩 leo 加協作者,而其實根本不必。探測法: git push --dry-run <remote> HEAD:refs/heads/<probe 分支>(不真的建分支、無副作用), 或該平台等價的無副作用寫入探測。失敗要分辨三種病,不要混成一句「不行」往錯方向查
  • 掛住=傳輸/逾時(如 RPC failed; GnuTLS recv error (-110)、大檔卡到逾時)——不是權限問題
  • 被拒=權限(試寫明確回 403permission deniedpre-receive 拒絕)
  • 找不到repo 或分支不存在(404repository not found

🔴 這一段是「每次都一樣」的唯一真相源。 想在派工單裡加一句叮嚀之前,先問:它換一張票還成立嗎? 成立就加在這裡(改一次,全機生效),不要加在那一次的 prompt 裡。


2.1 為什麼「能不能寫」只信試寫,不信 permissions 欄位(inkstone/ISEP#120

§2 第 10 條的實測與理由。 這一段不進注入(它是背景,不是每次要念的規定)—— 上面第 10 條才是要被讀到的那一行。

2026-09-01 實錯(inkstone/Arcrun#190 → comment 5805):總管拿 Gitea API 的 permissions.push 欄位判「能不能推」,得到 false,於是叫 leo 去加協作者—— 而實際上推得進去。同一把 GITEA_TOKEN_CLAUDE_CODE,兩種問法給相反的答案:

# API 怎麼說(會說謊的欄位)
GET /api/v1/repos/Leo/arcrun-rag-bundles-staging
  → permissions {admin:false, push:false, pull:true}

# 真的試著寫(無副作用,推到 probe 分支,dry-run 不會真的建)
git push --dry-run https://…@git.uncle6.me/Leo/arcrun-rag-bundles-staging.git \
        HEAD:refs/heads/ship-probe-total
  → To https://git.uncle6.me/Leo/arcrun-rag-bundles-staging.git
     * [new branch]      HEAD -> ship-probe-total
  → exit=0

leo 當場(同票):「沒有這個東西,現在已經有權限,而且地端用此帳號已經出貨很多次了」。 ⇒ 那個欄位反映的是某種角色設定,不是「這一次這個動作會不會成功」。 只有試寫問得到後者。

失敗的形狀要分辨(同票 comment 5821

同一天把「push 掛住到 4 分鐘逾時」(大檔傳輸)講成「被拒絕」(權限),往錯方向查了兩輪:

git clone <完整歷史>                        → RPC failed; GnuTLS recv error (-110)   ← 傳輸(掛住)
git clone --filter=blob:none --depth 1 …    → 成功,.git 409 MB                      ← 同一個 repo

掛住/被拒絕/找不到是三種不同的病,混成「不行」就查不下去。 分辨法(可帶進派工)寫在 §2 第 10 條那三個 bullet。

📌 掃過現存的閘與腳本(inkstone/ISEP#120 驗收第 2 條): 沒有任何一支在用 API 的 permissions 欄位判寫入權限——零命中。 唯一出現「permissions」字樣的地方是 Claude Code 自己的 permissions.allow 設定白名單(docs/permissions-allow.jsonscripts/settings-allow-sync), 那是「這台機器准不准跑這個工具」,跟「這個 repo 能不能推」是兩件事,不在本條射程內。 scripts/pr-verdict mergescripts/release-shiphooks/github-contact-guard.sh 都是直接動作或看 push 目標,沒有一支先去問那個欄位。


3. 交件回覆 第一行表明身份

【身份】subagentinkstone/ISEPfeat/ticket-carries-the-task
  • 角色是允許清單總管subagentleo再加上 agents/ 名單上的每一個工人名字 isep-handarcrun-handscout…,inkstone/ISEP#86)。清單以外的名字一律擋。
  • 第二格是你動的 repo,第三格是分支(沒有就寫 -

🔴 有名字就用名字。 三條線並行時三則「【身份】subagent/…」長得一模一樣, 等於沒有身份——這正是 ISEP#86 要解的那件事。

為什麼

2026-08-27 實害:多條線並行時票上的留言看不出身份, 總管寫的診斷被當成 subagent 的結論,而其中一則是錯的

🔴 這條管所有人,不是只管 subagent。 總管寫在票上的東西同樣要標 【身份】總管/…——leo 要分得出哪一則是誰寫的。

機械閘(兩道,因為這個動作有兩條路)

正門 scripts/ticket say / decide 腳本內建檢查,貼上去之前就擋
側門 直接打 Gitea API 貼 comment hooks/reply-identity-guard.shPreToolUse Bash

只封正門的閘等於沒封——ticket-api-bypass-guard.sh 的檔頭已經記過這一課: 「規範有、閘也有,但閘長在『工具』上,而那個動作有兩條路,只封了一條。」


4. 這份規範自己怎麼被驗

bash hooks/tests/dispatch-format-guard.test.sh       派工單閘+**回覆那條路**:該擋的與不該擋的
bash hooks/tests/reply-identity.test.sh              身份欄:正門與側門兩道
bash hooks/tests/roster-guard.test.sh                工人名單:指名、沒有這個人、注入
bash hooks/tests/diagnosis-evidence-guard.test.sh    未經調查不寫診斷

測資裡放的是真的發生過的那幾份違規派工單,不是想像出來的例子。


5. 寫上票之前:未經調查不寫診斷inkstone/ISEP#87

順序只有一個方向:先派人查 → 拿到查的結果 → 才可以寫診斷。

「診斷寫完了,工人就不知道自己要做什麼——它會照著我的結論去驗證,而不是去查。 而我不是那個 repo 的專家,它才是。」

機械閘 hooks/diagnosis-evidence-guard.sh 看三個結構訊號(一個關鍵字都沒有):

  1. 這個 session 有沒有派人查過那張票investigate-first-stamp.sh 的戳記)
  2. 這段話裡有沒有走得過去的出處(檔案:行號/commit/comment 號/票號/網址/實測輸出)
  3. 這段話有沒有份量(一句「收到」不可能是診斷)

①有 ⇒ 放行。①無+②有 ⇒ 放行(轉述有出處不能被誤擋)。三者皆缺 ⇒ 擋一次。

🔴 「根因」「因為」「應該是」這類詞一律不准當判準——leo 2026-08-17 已證偽文字層封路: 當日 8 次誤攔、0 次正確攔截,而且紅線寫得越細,命中關鍵字的機率越高 ⇒ 那些閘在懲罰謹慎