247-agent 模型動作
守護程式的設計規則:控制流中不得使用 LLM。比對、去重、路由、重試與發佈皆為程式碼。模型僅能在 decide、llm 或 agent 動作內部執行,並以能完成任務的最便宜層級執行,且設有預算。此技能旨在妥善撰寫這三種動作,以及執行它們的執行器。
首先判斷是否需要模型
依序詢問:
- 觸發器的
filter能否決定?(寄件者、標籤、儲存庫、關鍵字)若是,則非模型呼叫。 - 腳本或連接器操作能否確定性地產出答案?若是,則使用
shell/connector。 - 是否為標籤、是/否或層級,且無需產生文字(分類、路由、分級、閘門)?若是,則使用
decide:一種僅用於分類的模型,校準後的機率,成本約為llm呼叫的百分之一。 - 是否為單一判斷,需產出固定形狀的結構化輸出且需要文字(提取欄位、摘要、草稿)?若是,則使用
llm,優先使用 Haiku。 - 是否需要讀取檔案、執行命令並疊代處理?若是,則使用
agent,並選取能成功的最窄範圍。
兩個需要不同智慧等級的任務應是相同動作種類,但使用不同的 model/effort/max_tool_calls/system_file,而非不同的程式碼。請參閱 docs/examples/website-updates.yaml 中的任務 2–4 以了解參考形狀。
模型 ID 與參數
| id | 用途 | 備註 |
|---|---|---|
claude-haiku-4-5 | 分類、提取、短摘要 | 無 effort 參數 |
claude-sonnet-5 | 範圍限定的代理器、較難的提取 | 支援 effort 與適應性思考 |
claude-opus-5 | 廣域代理器、困難的判斷 | 支援 effort 與適應性思考 |
模型透過指定的提供者 (providers: 在 agent.yaml, provider: 在動作上; anthropic, openai 或 openrouter) 進行存取。其他提供者的 ID 會原樣使用,且需要 pricing: 條目,除非內建表格已知(目前的 OpenAI 模型)或提供者會回報成本(OpenRouter)。
ID 上無日期後綴。無助理預填。在建構模型級聯之前,先在相同的輸入上以 effort: low 測量較強模型:在當前世代中,這通常能超越以高 effort 運作的較弱模型,且單一模型意味著單一提示快取命名空間。
撰寫 decide 任務
完整的欄位列表、連線注意事項與陷阱請參閱 references/decide-action.md。基本要點:
action:
kind: decide
provider: openrouter # 必須是 openrouter 提供者;預設值為 defaults.decide.provider
budget: { max_usd: 0.001 }
state: # 被判斷的內容;字串可模板化,鍵不可
subject: ${event.payload.subject}
body: ${event.payload.body}
questions: # 靜態政策;此處無 ${…}
kind:
type: choice
instructions: 寄件者希望對網站做什麼?
criteria:
event_list_update: 新增、移除或變更事件清單中的條目
general_change: 對網站的其他任何變更
ignore: 非變更請求(問題、感謝、垃圾郵件)
urgent:
type: noul
instructions: 寄件者是否需要今天完成這件事?
- 結果是答案映射:
result.kind.choice、result.kind.confidence、result.urgent.noul(P(true))、result.<score>.score(層級索引的機率加權平均值)。使用emit … when:進行路由;閾值存在於任務中,從不在執行器中,並需根據真實資料進行校準。 - 務必包含一個備用標籤 (
ignore、other):若無,離題輸入會得到一個自信但錯誤的答案。 - 條件會被字面遵循:它們是政策,像版本控制程式碼一樣對待它們。保持選項順序穩定(它會影響信心)。在一次呼叫中詢問多個問題;每個額外問題僅增加其自身的 Token 成本。
- 傳入的內容是資料:它應放在
state中,絕不在問題中。整個請求必須能容納在 32k Token 內。 - 只有
openrouter提供者類型能存取 Jev (typesafe/jev-1.13,預設模型);oa validate會拒絕任何其他類型。不需要pricing:條目。
撰寫 llm 任務
完整的欄位列表與執行器說明請參閱 references/llm-action.md。基本要點:
action:
kind: llm
model: claude-haiku-4-5
max_tokens: 512
system_file: prompts/classify_email.md # 靜態 → 可提示快取
input: |
主旨:${event.payload.subject}
<email>
${event.payload.body}
</email>
output_schema:
type: object
additionalProperties: false
required: [kind, summary]
properties:
kind: { enum: [event_list_update, general_change, ignore] }
summary: { type: string }
- 將系統提示放在一個檔案中,位於
prompts/目錄下,並使其為靜態且優先渲染,以便跨執行進行快取。將可變的事件資料放在最後,在input中。 - 傳入的內容(電子郵件、聊天、PR 文字)是不受信任的。將其包裹在一個分隔區塊中 (
<email>…</email>),並在系統提示中告知模型這些是資料。 - 務必在綱要中包含一個逃生艙口(
ignore、unknown、confidence),以便路由的emit.when可以丟棄非可操作的結果,而無需再次呼叫。 - 使用
emit … when:對結果進行路由;下游任務將觸發該事件。 - 任何可以等待的任務都使用
batch: true(半價,結果以事件形式到達)。
撰寫 agent 任務
完整的欄位列表與執行器說明請參閱 references/agent-action.md。基本要點:
action:
kind: agent
connector: claude # 傳輸層:acp 連接器 (connectors.d/claude.yaml;沙箱:bwrap 在那裡)
model: claude-sonnet-5 # 可選:在會話上設定(ACP 配置選項);未設定 = 使用代理器的預設值
effort: medium # 可選:代理器的 thought_level 選項
max_tool_calls: 20
budget: { max_usd: 0.50 }
workspace: { kind: git-worktree, repo: /var/lib/247-agent/repos/site, branch: main }
tools: [read, edit, search, execute] # ACP 工具種類;其他一切都被拒絕
bash_allow: ["npm run build"] # 代理器執行的任何指令都必須符合此規則
system_file: prompts/agent_event_list.md
prompt: |
根據以下請求更新 data/events.yaml。不要修改任何其他檔案。
完成後執行 `npm run build`。
<email>
${event.payload.email.body}
</email>
result: { path: RESULT.json, schema: schemas/site_change.json }
post: # 僅在 status: done 後執行
- shell: ["npm", "run", "build"]
- shell: ["git", "commit", "-am", "events: ${result.summary}"]
emit:
- type: site.change_done # 在此事件上路由審批,絕不在 task.<name>.succeeded 上
when: "result.status == 'done'"
payload: { summary: "${result.summary}" }
- type: site.change_blocked # 代理器無法完成:說明缺少什麼
when: "result.status == 'blocked'"
payload: { summary: "${result.summary}", missing: "${result.missing}", email: "${event.payload.email}" }
不可協商的事項,因為它們是讓代理器能安全無人值守運作的關鍵:
- 每次執行使用全新的工作區(
git-worktree位於agent/<run_id>分支上,或使用temp),失敗時刪除,成功時保留。代理器絕不編輯基礎檢出。 tools+bash_allow是完整的能力表面,每次權限請求都會被判斷,且絕不授予「始終」;代理器未經請求而執行的工具呼叫會在事後被判斷,違規會導致執行失敗(unasked_execute: sandboxed僅放寬指令檢查,且僅限於 Codex,其自身的沙箱限制了它未詢問的內容)。授予最小權限;提示不是安全邊界。mcp_servers將連接器操作新增為工具:授予命名的操作({ connector: email, ops: [fetch_new] })而非整個連接器,為它們將代理器的種類(對於 claude-agent-acp 為other)新增到tools中,且絕不授予能自行發佈或發送的連接器。- 代理器絕不持有部署金鑰,也絕不發佈。 它進行編輯,
post閘門證明建置仍能成功,提交記錄它,而一個獨立的shell任務則進行部署。回滾方式是git revert後重新發佈。模型金鑰存在於連接器清單的env中,而非任務中。 RESULT.json是合約,執行器在每個提示中都會說明它:status: done | blocked加上summary,然後是任務的result.schema。done會執行閘門;blocked會跳過它們,但執行仍算成功,因此emit … when: "result.status == 'blocked'"會將「因為缺少 X 而無法完成」的結果路由到一個回覆任務。設計每個代理器任務時都應包含兩個分支,並在done事件上觸發發佈。post閘門是確定性的,不涉及模型。失敗的閘門會導致執行失敗。max_tool_calls與budget是硬性停止點(會話會被取消);如果代理器未報告使用量或成本,則該輪執行失敗。concurrency: 1應用於共用儲存庫的任務;timeout是每次嘗試的掛牆時鐘時間。- 高影響的變更在發佈前需要一個審批閘門(
wait+ 聊天);247-agent-tasks技能中有此模式。
預算與分類帳
每個模型呼叫都通過 ctx.llm 連接埠,該連接埠會記錄使用量(提供者、模型、輸入/輸出/快取 Token、美元)到分類帳中,並強制執行執行的 budget.max_usd(呼叫前的最壞情況,呼叫後的實際情況:超額將導致執行失敗)以及全域的 budgets.daily_usd(每個 UTC 日,一旦超過,該日內的每個模型呼叫都會快速失敗,並發出一次 budget.exceeded 事件,notify 任務應監聽此事件)。沒有價格的模型會導致驗證失敗。絕不新增無預算的呼叫,包括執行器內部的「輔助」呼叫:使用 ctx.llm,絕不直接使用 SDK。agent 輪次是模型由其他人(ACP 代理器,使用其自己的金鑰)呼叫的唯一情況:執行器會將代理器透過 ctx.llm.record 報告的內容以連接器的名稱記錄到分類帳中,並在報告的成本超過上限時取消會話。oa cost --by task --since 7d 可顯示分類帳。
目前狀態
llm 動作已通過驗證,並與 providers:/pricing: 交叉檢查,可透過 ctx.llm 執行,已套用分類帳與預算,且所有三種提供者類型(anthropic、openai、openrouter)都有適配器。batch: true 在 anthropic 提供者上運作(Message Batches,執行將等待直到 llm.batch.ended;請參閱 references/llm-action.md)。decide 動作透過相同的連接埠 (ctx.llm.decide()) 執行,Decisions API 僅在 openrouter 適配器中實作;docs/examples/decide-triage.yaml 是參考。agent 動作在 ACP 連接器 (transport: acp) 上執行;docs/examples/website-updates.yaml 中的任務 3、3b 與 4 是參考,docs/examples/connectors.d/claude.yaml 是連接器;會話會以逐字稿 (oa runs logs <id>) 的形式持久化,並在 retention.runs 設定時間後隨執行一起刪除。mcp_servers 透過核心的工具橋 (packages/core/src/connectors/mcp-bridge.ts) 向會話提供連接器操作。動作上的 model 與 effort 會設定會話的 ACP 配置選項(類別 model 與 thought_level)。已完成執行的 work/<run_id> 會在執行完成後由 retention.workspaces(預設 7 天)清理。
新增適配器或變更執行器時,請遵循 references/llm-action.md、references/decide-action.md 與 references/agent-action.md,並確保 docs/internal/model-actions.md、docs/internal/agent-action.md 以及 docs/tasks/ 下的使用者頁面與程式碼保持同步。