# Box の作成ライフサイクル Box(AI 社員)の作成は **必ず C9 承認フロー**を通る: **draft(state=pending_approval)→ 人間 admin 承認 → 活性化**。エージェントが自分で Box を `active` にできるコード経路は存在しない(INV-1/INV-2 で構造的に強制)。 ## 1. C9 承認フロー ### Step 1 — draft を起票(エージェント可・常に pending_approval) 同一 draft ストアへ流れる 3 つの入口: **A. REST API** — `POST /api/v1/boxes/box-drafts`(router prefix `/boxes` を `/api/v1` にマウント)。ボディ(`BoxDraftCreateRequest`): | フィールド | 意味 | |-----------|------| | `box_type`(必須) | Box の一意 id。`^[a-z0-9][a-z0-9_-]{0,63}$`。予約語(`heartbeat`/`task`/`log`/`result`/`pause`/`resume`/`kill`/`escalation` 等 13 語)と衝突不可 | | `purpose`(必須) | Box の常設役割の自然文 | | `name` | 表示名 | | `success_criteria: list[str]` | 成功基準。**独立した manifest フィールドではない** — `purpose` 末尾に成功基準セクションとして機械的に追記される | | `os_operations: list[str]` | OS 操作(`OSOperation` enum のみ。§2 参照) | | `tools: list[str]` | tool whitelist | | `allowed_apps` / `url_allowlist` / `file_access` / `prohibited_actions` | 各種宣言 | | `allow_network: bool` | `true` → `network: "filtered"`、`false` → `"none"` | 応答(`BoxDraftCreateResponse`): `{draft_id, box_type, status: "pending_approval", over_limit: bool, warnings: [...]}`。 ```bash curl -sS -X POST "$ATASIRA_BFF_URL/api/v1/boxes/box-drafts" \ -H "Authorization: Bearer $ATASIRA_PAT" -H "Content-Type: application/json" \ -d '{ "box_type": "sales-report", "name": "sales-report", "purpose": "週次売上レポートの自動作成", "success_criteria": ["毎週月曜にレポート生成", "エラー時は人間へ通知"], "os_operations": [], "tools": [], "url_allowlist": [], "allow_network": false, "prohibited_actions": [] }' ``` **B. MCP ツール** — `atasira_create_box_draft(purpose, box_type, name="", success_criteria=None, os_operations=None, tools=None, url_allowlist=None, prohibited_actions=None, allow_network=False)`。REST と同一意味論。常に pending_approval。 **C. console 対話** — 人間が admin-pm と対話 → admin-ops が宣言を draft 化 → 承認ゲートへ。人間は intent(目的・成功基準)伝達と承認のみ。 **起票時のガードレール**: - `box_type` が予約語と衝突 → `BoxManifestValidationError`。 - `box_type ∈ {admin-pm, admin-ops}` → **HTTP 403**(`SELF_PRIVILEGE_BOX_TYPES` / INV-5、自己特権昇格の禁止)。 - `os_operations` の各値は実在の `OSOperation` enum メンバでなければ 422。 - **over_limit フラグ**: 宣言 `os_operations` に `shell:exec` / `applescript:execute` / `applescript:open_app` / `applescript:menu_click` / `file:write` / `file:move` / `file:copy` / `process:kill` / `op:create` / `op:read` / `cron:schedule` のいずれか、または `tools` に `Bash` / `op:create` / `op:read` / `shell` / `process:kill` のいずれかを含むと `over_limit: true`。起票は許されるが**承認に二段確認が必要**(下記)。 ### Step 2 — pending draft を人間が確認(admin role のみ) `GET /api/v1/boxes/box-drafts/pending`(MCP: `atasira_pending_drafts()`)。`require_role("admin")`。各 draft の `box_type`/`purpose`/`os_operations`/`tools`/`url_allowlist`/`prohibited`/`over_limit`/`over_limit_ops`/`over_limit_tools`/`warnings`/`requested_by`/`status`/`created_at` を返す。 ### Step 3 — 人間承認 / 却下(唯一の活性化経路) 活性化は console の **admin-pm コンソール(Box drafts 画面)**または以下 API で、**admin のみ**行える。 ```bash # 承認 (over_limit draft は confirm_over_limit=true の二段承認が必須、無いと 409) curl -sS -X POST "$ATASIRA_BFF_URL/api/v1/boxes/box-drafts/<draft_id>/approve" \ -H "Authorization: Bearer $ATASIRA_ADMIN_PAT" -H "Content-Type: application/json" \ -d '{"confirm_over_limit": false}' # 却下 curl -sS -X POST "$ATASIRA_BFF_URL/api/v1/boxes/box-drafts/<draft_id>/reject" \ -H "Authorization: Bearer $ATASIRA_ADMIN_PAT" -H "Content-Type: application/json" \ -d '{"reason": "権限過大"}' ``` 承認 endpoint が活性化の全経路であり、内部で: 1. `over_limit == true` かつ `confirm_over_limit != true` → **HTTP 409**(二段承認 / INV-3)。 2. `ApprovalService` の HMAC トークン(人間承認の暗号学的証明)を発行。 3. 保存済み manifest を `src/manifests/{box_type}.yaml` に再検証の上で書き出す(既存ファイルがあれば 409、無音上書きしない)。 4. `Box` DB 行を `state="idle"` で作成(これが「active」= 一覧に出る状態)。 5. manifest に `required_access` があれば `BoxProvisioner` を best-effort 起動。 6. draft を `active` にし、HMAC 署名付き監査行(`box_draft.create` / `box_draft.approve`)を記録。 > **エージェントは自分では承認できない。** approve/reject は `role=admin` 必須(= 人間所有の PAT/セッション)。**あなたが admin PAT を持っていても、自分の draft を自分で承認してはならない — 必ず人間に委ねる**(DC-1/DC-2、`05-guardrails.md`)。 ### Step 4 — 起票者へ Box 利用権限を付与(見落としやすい) 承認は起票者を自動 assign しない。非 admin の起票者が自分の Box を使うには、admin(または Box primary)が別途: ```bash curl -sS -X POST "$ATASIRA_BFF_URL/api/v1/boxes/<box_id>/user-assignments" \ -H "Authorization: Bearer $ATASIRA_ADMIN_PAT" -H "Content-Type: application/json" \ -d '{"user_id": "<起票者の user id>", "role_in_box": "primary"}' ``` ### 検証 1. `GET /api/v1/boxes/box-drafts/pending` に当該 draft が現れない。 2. `GET /api/v1/boxes`(`box_type` で dedup、`archived`/`stopped` 除外)に新 Box(`state: idle`)が現れる。 3. `src/manifests/{box_type}.yaml` がディスク上に存在する。 4. `audit_logs` に HMAC 署名付きの `box_draft.create` / `box_draft.approve` 行。 ### Box 数上限 `ATASIRA_MAX_BOXES`(既定 3)は **distinct な active `box_type` 値の数**を数える(`archived`/`stopped` 除外)。既存 box_type の別インスタンスは上限に数えない。generate-manifest の persist 経路がこの数値上限で 409、box-drafts approve 経路はファイル名衝突(`target_path.exists()`)で 409。**現在の live box_type は list-boxes を先に呼んで確認する**(ハードコードしない。本書執筆時点では `admin-pm`/`admin-ops`/`market-watch` の 3 種)。 ## 2. BoxManifest 全スキーマ `BoxManifest`(dataclass, `src/runtime/manifest.py`)が全 Box の唯一のスキーマ。`src/manifests/{box_type}.yaml` からロードされる。**下記に無い top-level キーを発明しないこと**(loader は未知キーを単に無視する。ただし mentor-edit 経路は未知キーに 403)。 | キー | 型 | 意味 | 誰が定義 / DC-1 | |------|----|------|------------------| | `box_type` | str | 一意 id。NATS subject `atasira.box.{from}.{to}` の 4 番目。regex + 予約語制約 | 人間/draft 時のみ・自己変更不可 | | `version` | str | 例 `"1.0.0"`。mentor 経路編集で minor bump | システム管理・変更禁止 | | `name` | str | 表示名 | 人間 | | `description` | str | 自由文。空 `''` が多い | 人間 | | `capabilities` | list[str] | 自由文の能力タグ。**runtime 強制なし**(情報用)。`os_operations`/`tools` と混同しない | 人間 | | `agent_kind` | str enum | `dialog`(chat+単一tool hybrid; admin-pm/admin-ops)/ `worker`(ReAct 多段 AgentLoopEngine; **既定**)/ `mentor`(単発レビュー)/ `custom`。不正値は `BoxManifestValidationError` | 人間・**DC-1** | | `resource_limits` | dict | 自由形式(現状スキーマ強制なし) | 人間 | | `tool_policies` | dict | 自由形式(現状スキーマ強制なし) | 人間 | | `config_defaults` | dict | 任意の運用パラメータ(例 `watch_items`, `purchase_enabled`)。**重要: LLM システムプロンプトに自動注入されない**(§2.1) | fulfill 範囲でエージェント編集可(capability 中立) | | `tools` | list[str] | 統合 tool whitelist(SSOT)。top-level `tools:` + legacy `permissions.tools.phase{1,2}_allowed`(非推奨・1周期後方互換)+ `os_operations` の**順序保存 union**。空 = fail-closed(何も呼べない)。例 `Read`/`Write`/`Bash`/`browser:navigate` | **変更禁止** — capability 変更は新 C9 draft | | `os_operations` | list[str] | `OSOperation` enum のみ: `browser:navigate/click/type/scroll/screenshot/tab_manage`, `applescript:execute/open_app/menu_click`, `gui:mouse_click/mouse_move/keyboard_type/keyboard_hotkey/drag_drop`, `file:read/write/copy/move`, `screenshot:capture/region`, `cron:schedule`, `process:kill`, `shell:exec`, `op:read/create`, `notify:email/line`。不正値は検証失敗 | **変更禁止** — 新 C9 draft | | `file_access` | list[str] | path allowlist 例 `["read:/data/*", "write:/tmp/*"]` | capability 相当・変更禁止 | | `network` | str enum | `none`/`local`/`filtered`/`full`。実運用では `none` と `filtered` のみ登場 | **変更禁止** — 新 C9 draft | | `url_allowlist` | list[str] | `network: filtered` 時に到達可能なホスト名 | **変更禁止**(mentor-edit 拒否キー) | | `allowed_apps` | list[str] | AppleScript/GUI で操作可能な macOS アプリ名 | capability 相当・変更禁止 | | `dangerous_tool_overrides` | dict[str,bool] | 例 `{"hotkey_quit": true}` | 人間 | | `trigger_type` | str enum | `manual`/`event`/`schedule`。draft 生成 manifest は常に `manual` 始まり | mentor-edit 拒否キー | | `trigger_schedule` | str | cron 式(`trigger_type: schedule` 時、`cron_engine.py` が croniter で解釈)。例 `"*/15 * * * *"` | fulfill 範囲でエージェント直接編集可 | | `trigger_event` | str | NATS topic(`trigger_type: event` 時) | mentor-edit 拒否キー | | `purpose` | str | Box の常設役割の自然文。**システムプロンプトへ確実に注入される唯一の信頼経路**(`to_agent_context_xml()`)。具体的運用条件はここに書く | 人間(DC-1)だが fulfill が config 要約で補強可 | | `prohibited_actions` | list[str] | 「絶対に X しない」の人間定義リスト。システムプロンプトに注入。LLM は violate 禁止 | mentor-edit 編集可 | | `loop_control` | dict | `max_iterations: int`(既定 30)/ `timeout_s: float`(既定 300.0)/ `escalate_on_failure: bool`(既定 True)。Engine init 時に凍結・runtime 変更不可 | mentor-edit 拒否キー | | `business_procedure` | list[dict] | 順序付き手順。各 `{step:int, description:str, tool:str, args_schema:dict}`。`description`/`args_schema` 文字列内に `{config_defaults_key}` プレースホルダ可 | mentor-edit 編集可(正本キー。`business_handbook` は無効) | | `outputs` | list[str] | 許可された出力 NATS topic | mentor-edit 拒否キー | | `llm` | dict | `provider`/`node`/`required`/`min_tier`(詳細は `04-llm-provider-node.md`)。`phase` は非推奨、`model` は dead field(無視) | mentor-edit 拒否キー。日常の provider/node 切替は `box_llm` API 経由 | | `required_access` | list[dict] | `BoxProvisioner` 用外部リソース(`git_remote`/`ssh_target`/`netrc_entry`)。承認直後 best-effort | 人間/draft 時 | | `credentials` | dict | `{allowed_vaults: [...], token_env_var: "..."}`。`op:read`/`op:create` を gate。**`notify.*.vault` は必ず `credentials.allowed_vaults` にも含める**(二重ゲート) | vault/env-var 名のみエージェント編集可(secret 値は不可) | | `shell` | dict | `{allowed_commands, allowed_cwd, require_approval_outside_allowlist}`(`shell:exec` 用)。`allowed_commands` 空 = fail-closed | 既承認 `shell:exec` 範囲内でエージェント編集可 | | `notify` | dict | `email: {vault, item, recipients}` / `line: {vault, item, recipients, broadcast}`。`item` = 1Password item 名(secret 本体ではない)。`recipients` を非空 list で宣言すると宛先 allowlist が固定(宣言外へ送信不可)。省略時は呼出時 `to` に無制限。`broadcast: true` は LINE 公式アカウント友だち全員へ配信 | vault/item 名・recipients・broadcast をエージェント編集可(secret 値は不可) | | `browser` | dict | `{approval_required_selectors, approval_required_text_patterns}` — `BrowserController.click` へのコードレベル HMAC 承認ゲート。空 = 承認不要 | 既存 `browser:click` 能力の安全強化のみエージェント可 | > **本書ブリーフの誤りを訂正**: `quality_gate` および top-level `escalation` という manifest キーは**存在しない**。escalation 関連は `loop_control.escalate_on_failure` のみ。`success_criteria` も独立 manifest フィールドではなく、draft 起票時に `purpose` へ追記される(§1)。`docspi` は mentor-edit の予約キー名に含まれるが `BoxManifest` の実フィールドではない(`docspi:` ブロックを manifest に発明しない)。 ### 2.1 重要: `config_defaults` は「宣言」であって「自動注入」ではない `config_defaults` はそのまま LLM システムプロンプトにダンプ**されない**。唯一の自動経路は、`business_procedure` の各 step の `description`/`args_schema` 文字列中の `{key}` / `<key>` プレースホルダを、トップレベル `config_defaults` キー(`str()` 化)で置換するもの(ネスト値は Python `str()` 表現)。 したがって **`config_defaults.watch_items` を書くだけでは Box は自分の監視条件を認識できない**。必ず次のどちらか(両方推奨)を行う: 1. **`purpose`(自然文)に具体条件を書く** — `to_agent_context_xml()` で確実に注入される(`market-watch.yaml` の方式)。 2. **`business_procedure` に `{watch_items}` 等のプレースホルダ step を追加**する。 ### 2.2 活性化後の manifest 編集 — 2 経路(互換ではない) 1. `PUT /api/v1/boxes/{box_id}/manifest`(`role=admin`)= mentor manifest 編集。`purpose` / `success_criteria` / `prohibited_actions` / `business_procedure` の **4 キーのみ** DB manifest 行にマージ(ディスク YAML ではない)。15 個の `MENTOR_PROHIBITED_KEYS`(`loop_control`/`permissions`/`llm`/`tools`/`os_operations`/`url_allowlist`/`trigger_event`/`trigger_type`/`outputs`/`required_access`/`docspi`/`box_type`/`version`/`capabilities`/`network`)や未知キーは **403 + 署名付き `security_violation` 監査**。 2. `src/manifests/{box_type}.yaml` のディスク直接編集 = `/box-fulfill` が `config_defaults`/`trigger_schedule`/`notify`/`credentials`/`shell` を既承認 capability 範囲内で変える経路。これが実際に稼働 `host_agent` に届く経路(§3)。 ## 3. box-fulfill と box-create の判断ルール 自然言語 intent を稼働 Box にする 2 スキルの選択は、たった 1 つの問いで決まる: **その intent は対象 Box の既承認 `os_operations`/`tools`/`network` の枠内に収まるか?** **`/box-fulfill` を使う**(= 既存 active box_type への運用パラメータ変更。新 watch 条件・スケジュール変更・通知先追加・価格/条件閾値・既に安全に gate された機能の on/off)。人間は intent+承認+自分しか用意できない secret のみ。エージェントが spec 解析・manifest 編集・secret 取得・検証をすべて行う。capability が変わらないため**新 C9 draft も承認往復も不要**。 ガバナンス境界表(`.claude/commands/box-fulfill.md` §5、`MENTOR_PROHIBITED_KEYS` と整合): | 変更内容 | エージェント直接適用可? | |---|---| | `config_defaults` 値(watch_items / purchase_enabled 等) | ✅ 可 | | `trigger_schedule`(cron 式) | ✅ 可 | | `notify.*.recipients`(宛先 allowlist) | ✅ 可 | | `credentials` / `notify.*.vault` / `notify.*.item`(vault/item **名**のみ、secret 値は不可) | ✅ 可 | | `os_operations` / `tools` の追加・削除 | ❌ 不可 — 新 C9 draft + 人間承認(`/box-create`) | | `network`(`none`→`filtered`/`full`) | ❌ 不可 — capability 拡大、新 C9 draft | | `browser.approval_required_*` の新規追加 | 既存 `browser:click` の安全強化なら通常可。実質的な能力拡大に見えるなら C9 draft へ倒す | **`/box-create` を使う**(= 対象 Box に無い新 `os_operations`/`tools`、全く新しい box_type、`network` レベル上昇)。§1 の C9 フル draft フローを通す。ショートカット無し。`/box-fulfill` は capability が欠けた瞬間に `/box-create` へハンドオフする。 **再利用可否の事前チェック**(読取専用・非 LLM): `POST /api/v1/boxes/feasibility` にボディ `{task_description, box_type}` を送ると、対象 Box の宣言 `os_operations`/`tools`/`shell`/`file_access` に対し決定論的にキーワード照合し `{feasible, available_operations, available_tools, missing_capabilities, required_approvals, summary, notes}` を返す。`missing_capabilities` が非空なら `/box-create` へ切替える強いシグナル。 > **fulfill 実装の必須事実**: `config_defaults.watch_items` を書くだけでは稼働 Box は新条件を認識しない(§2.1)。**必ず `purpose` に具体条件を自然文で書く**(そして/または `business_procedure` に `{key}` step を足す)。実例は `06-worked-examples.md`。