Agent toolkit
Agent 不會打 /private/module/sandbox REST 來提交。它呼叫 src/components/tools/custom/sandbox/factory.py 的五個 LangGraph 工具。前端從不直接呼叫這些工具。前端要做的是:在房間啟用 job、在對話裡呈現 requires_confirmation 提案,然後用租戶 run API 輪詢得到的 run_id。
啟用條件
tool_loader 才是準則(factory 開頭寫「與 job_list 無關」是過時的)。五個工具只有在以下全部成立時才會載入:
- 房間的
jobs清單含ChatroomJobType.SANDBOX("sandbox")。 - 有
SANDBOX_ENABLED、DB、可信 principal、簽過名的 turn context。
開了 job 也會加上 protocol prompt,並把五個工具釘成 critical,避免被剪掉。
用既有的聊天室 jobs API 開啟(不在 /private/module/sandbox 底下):
PATCH /private/chatrooms/setting/jobs/{chatroom_id}
{ "jobs": ["sandbox", ...] }認證是聊天室管理員階梯,失敗是 403,不是 sandbox 404。jobs: null 回到舊的自動偵測。jobs 沒設的房間永遠不會出現在 GET /private/chatrooms/by_job/sandbox。
那條路由的 OpenAPI 說明在有效的 job 類型裡列有 sandbox,ChatroomJobsPayload 也吃 ChatroomJobType,包含 "sandbox"。
列出明確開了這個 job 的房間:
GET /private/chatrooms/by_job/sandbox
GET /private/chatrooms/by_job/sandbox/numOfDataQuery:department_id?、offset、limit 1–100 預設 100、order asc|desc。省略 department_id → 已加入的房間。有設 → 該部門的房間(部門不在公司裡 → 404)。收藏的房間排前面。
建構時只捕捉伺服器 tuple (company_id, chatroom_id, principal_type, principal_id, persisted_human_message_id, signed_turn_receipt)。模型參數從不提供 turn 身份。
某個具體目錄任務能不能跑,需要 live share 且 此房間的啟用開關(granted-jobs)。task.agent_enabled 是舊的父層旗標,不會把任務放進 Agent 目錄。房間開了 "sandbox" job 但沒啟用任何任務,選單是空的。開了 ChatroomJobType.sandbox 不會讓每個已授予任務可跑 — 房間仍必須 POST .../granted-jobs/{task_id}/enable。Agent 選單與呼叫者身分無關(與 GET .../menu 同一份)。見 任務受眾。
外部通道(LINE/LINE 群/LINE room/Messenger/Instagram)的 principal_type=social_media_client,用 pipeline 解析出的 client id。內部聊天用已驗證的 user_id。
五個工具
| 工具名 | 參數(模型可見) | 回傳 |
|---|---|---|
sandbox_job_menu | page_size(預設 20,1..100)、page_token?、query?(≤256) | 此房已採用的精確版本目錄。沒有腳本。 |
sandbox_submit_job | 依 mode 分流——見下 | 直接 run、確認提案,或 replay。 |
sandbox_submitted_jobs | page_size、page_token? | 此 principal 待確認提案(含 proposal_receipt)與近期 runs。沒有 output/log/secret/URL。 |
sandbox_job_status | run_id、wait_seconds 0..60、artifact_page_token? | 狀態 + 有界、不可信預覽。 |
sandbox_cancel_job | run_id? | 取消自己的 run;省略則取消此 principal 待確認提案。Manager 身分不會擴大擁有範圍(A-011)。 |
選單項目帶了 REST 選單 job-contract 欄位裡的兩個(見 Schemas):input_schema(呼叫 sandbox_submit_job 前先驗證 input——不合格在提交時會是 422 input_schema_violation)與 secret_slots[](逐 slot 的 borrower/owner 履行狀態)。Agent 選單完全不帶 runtime_contract——這個欄位只存在於 GET /tasks/{id}/versions/{vid}(owner 看得到真實 work_command)與 POST .../input-preview(同樣);REST 的 GET .../menu 自己的 runtime_contract 不論呼叫者是誰都是預設 fallback,所以它也不是能拿到真值的來源(見 Schemas)。
工具結果是 JSON 字串。錯誤:
{ "status": "error", "error": "<code>", "message": "..." }error | 何時 |
|---|---|
unauthorized | 重新授權/擁有權失敗(SandboxAgentAuthError),或提交遇到 401/403 拒絕(例如 Command 輸出的規則) |
validation_error | 工具參數不合法,或提交遇到 400/409/422 拒絕 |
forged_receipt | 收據不是簽過名的多段格式(必須含 4 個點) |
not_found | 過期、別人的、隱藏、同一輪,或已用過的收據 |
confirmation_rejected | 其他閉形式確認失敗 |
already_committed | 此提案已經變成 run |
proposal_not_pending | 狀態不是 prepared |
same_turn | 確認的 HumanMessage 就是起源那一輪 |
not_later | 確認訊息不是嚴格較晚 |
receipt_reused | 這一輪確認已經 commit 過另一個提案 |
retryable | 暫時性 commit 失敗;提案留在 prepared,收據可重用 |
confirmation_input_unavailable | 後一輪已驗證,但耐久 input 重放失敗(沒抓到/R2 讀不到/digest 不符/不是 JSON)。不建 run;提案留在 prepared。 Agent 可再 mode=commit。 |
provider_unavailable | 控制面不可用,或工具沒有對應到其他代碼的提交拒絕——包括佇列的 429(sandbox_queued_run_limit,以及地端部署的 queue_full)。什麼都沒排入 |
internal_error | 未預期;message 是泛用句 |
sandbox_submit_job
mode="request"
必填:task_version_id、input(canonical JSON)。可選:timeout_seconds。禁止: proposal_receipt。
- 版本
requires_confirmation=false、沒有宣告表格 writeback 的output_policy,且 principal 可執行 → 伺服器提交 run(submission_source="agent")並回 run。 - 版本帶 Command 輸出政策(
custom_table_command,v5.21.0)且requires_confirmation=false,同樣直接提交:伺服器先暫存確切的輸入位元組再提交,不經提案回合。若 Command 輸出的規則拒絕這次提交,403(例如 principal 是外部通道的 client、不是使用者)會以unauthorized回來,422(Command 不存在或不符)是validation_error,503 的金鑰錯誤是provider_unavailable。見任務輸出交給自訂表格 Command。 requires_confirmation=true——或版本宣告了表格 writeback 的output_policy(custom_table_writeback,一律視為需確認等級)→ 伺服器建立prepared提案,取代此 principal 其他待處理提案,並回:
{
"kind": "prepared",
"proposal_id": "...",
"proposal_receipt": "<signed multi-segment token>",
"summary": "...",
"requires_confirmation": true,
"task_id": "...",
"task_version_id": "..."
}proposal_receipt 對模型是不透明的。只傳一個裸 id 會被判 forged_receipt。
mode="commit"
必填:只有 proposal_receipt。禁止: task_version_id、input、timeout_seconds、region、profile、cost、ENV、version。
必須是之後的 HumanMessage 輪次。Factory 在帶外附上新的 signed turn receipt。同一輪、隱藏、較舊、別人的、偽造、重用的收據一律失敗(not_found/forged_receipt)。
成功:{ "status": "ok", "kind": "submitted", "run_id": "...", "proposal_id": "...", "submission_source": "agent", "task_id": "...", "task_version_id": "...", "run_status": "queued" }。
回應遺失、同一組 winning pair 重送:{ "kind": "replay", "run_id": "..." }。
準備時拿到的 proposal_receipt 不一定能活過後面幾輪。sandbox_submitted_jobs 會為每個待確認提案重簽一張收據——mode=commit 用那張,不要用你快取的準備收據。
若確認已驗證、但原始 input 無法從耐久儲存重放,commit 回 confirmation_input_unavailable,提案留著。不要自己編 input。
前端該知道的 TTL
| 常數 | 值 | 意義 |
|---|---|---|
PROPOSAL_TTL_SECONDS | 1800(30 分) | 已準備提案過期 → expired。 |
TURN_RECEIPT_TTL_SECONDS | 600(10 分) | 簽過名的 HumanMessage 收據壽命。 |
MAX_STATUS_WAIT_SECONDS | 60 | sandbox_job_status.wait_seconds 上限。不套用到 REST 輪詢。 |
對話裡的自然語言「好」只決定 agent 要不要呼叫 mode=commit。伺服器仍要求另一則之後的可信 HumanMessage 收據。
sandbox_job_status 預覽(T-002)
這是唯一把顧客 output/log 位元組交給模型的面。REST GET .../content 只有旗標。
| 預覽 | 上限 | 形狀 |
|---|---|---|
| Output | ≤64 KiB 前綴(MAX_OUTPUT_PREVIEW_UTF8_BYTES) | { untrusted: true, label: "untrusted_customer_output", truncated, byte_length, text, content_kind: "customer_data", not_instructions: true } |
| Log | ≤16 KiB head+tail(MAX_LOG_PREVIEW_UTF8_BYTES) | 同樣框線,欄位是 head/tail,label: "untrusted_customer_log" |
| Artifacts | ≤20 筆 metadata(ARTIFACT_LIST_PAGE_SIZE) | id, relative_path, byte_size, digest, declared_content_type。沒有 signed URL/object key。 |
R2 不可用時 lane 退化成 { available: bool } 佔位。job_status 仍回狀態。把巢狀顧客文字當資料,永遠不當指令。
wait_seconds 與 artifact_page_token 兩個都是 v1 no-op(db_lane.job_status 同一行 del wait_seconds, artifact_page_token)。產出物預覽永遠只回前 ≤20 筆、沒有翻頁——回應裡的 truncated/total_reported 是唯一能看出有列被截掉的訊號。不要等這個工具。前端仍輪詢 GET /chatrooms/{id}/runs/{run_id} 直到 terminal_at。
前端要做的事
- 提供房間設定,對
PATCH /private/chatrooms/setting/jobs/{id}在jobs裡放"sandbox"。用GET /private/chatrooms/by_job/sandbox列這些房間。 - 在房間設定列出
GET .../granted-jobs,讓房間管理者啟用/停用。在任務編輯器顯示requires_confirmation(不要把agent_enabled當 Agent 開關)。 - 當 assistant 輪出現
prepared提案,渲染summary,等下一則人類訊息。不要做一個打 sandbox 端點的 REST 確認/拒絕按鈕——沒有那個端點。 - Commit 之後拿
run_id,走 執行 API 輪詢。 sandbox_submitted_jobs只給 agent 用來找回收據。沒有 REST 提案列表。
人類使用者仍用 POST /chatrooms/{id}/runs 提交。兩條路最後都是同一個 SandboxRunDetailResponse(submission_source 不同)。
相關
- Agent 確認流程 — propose/commit 狀態機。
- 自訂表格 trigger — 不能對準
requires_confirmation=true。 - 角色與權限 — 即使使用者是 manager,agent 的 cancel/list/status 仍綁行為主體。