# Phase 4 — Write task.toml, spawn via kei-spawn, emit Agent-tool invocation

> Goal: serialize `ROLE / TASK_FULL / WHITELIST / DENYLIST` into a
> `task.toml`, call `kei-spawn spawn --format=json`, parse the JSON, and
> emit a ready-to-paste Agent-tool invocation. One confirm click before
> the spawn call (it writes a ledger row).
> **Verify criterion:** `AGENT_ID`, `DNA`, `SUBAGENT_TYPE`, `ISOLATION`
> all set; Agent-tool invocation printed.

---

## 4.a — Compute `TASK_TOML` path

Generate a short UUID (v4, first 8 hex chars is sufficient — kei-spawn
validates uniqueness on write). Path:

```
tasks/<uuid8>.toml
```

Relative to the current repo root. If `tasks/` does not exist, do NOT
create it from this skill — `kei-spawn spawn` will fail loudly and the
constructive path is to run `mkdir tasks && git add tasks/.gitkeep` in
the orchestrator session.

---

## 4.b — Render task.toml

Use this exact schema (verify against `_primitives/_rust/kei-spawn/src/task.rs`
before any drift; if the CLI schema changes, this phase file updates too):

```toml
# Generated by /spawn-agent skill — do not hand-edit.
# kei-spawn consumes this file; the spawn ceremony writes a ledger row
# (RULE 0.12) and returns agent_id + dna in JSON.

role = "<ROLE>"

task = """
<TASK_FULL verbatim, triple-quoted>
"""

[scope]
whitelist = [
    "<glob 1>",
    "<glob 2>",
    # ...
]
denylist = [
    # empty if Auto; populated if Explicit; absent if Override (see 4.c)
]
```

TOML rules:

- Use triple-quoted `"""..."""` for `task` so newlines in TASK_FULL survive.
- Escape any triple-quote inside TASK_FULL as `\"\"\"` (unlikely but
  validate before write).
- Every whitelist entry is a TOML string (double-quoted, 4-space indented,
  trailing comma).
- If `DENYLIST_OVERRIDE = true` from Phase 3.d, OMIT the `denylist` key
  entirely — `kei-spawn` distinguishes "empty list" (union with defaults)
  from "absent" (no defaults applied) via the `--no-default-deny` flag
  passed in 4.d.

Write the file via the `Write` tool.

---

## 4.c — Confirm-emit click

Send ONE `AskUserQuestion` before calling `kei-spawn`:

```json
{
  "questions": [
    {
      "question": "Spawn agent with this configuration?",
      "header": "Confirm",
      "multiSelect": false,
      "options": [
        {
          "label": "Yes — spawn now",
          "description": "Writes ledger row, returns agent_id + DNA + subagent_type + isolation. The Agent-tool invocation will be emitted for you to paste."
        },
        {
          "label": "No — show me the task.toml first",
          "description": "Print the generated tasks/<uuid>.toml contents verbatim, then re-ask."
        },
        {
          "label": "Abort",
          "description": "Delete the task.toml and exit the skill."
        }
      ]
    }
  ]
}
```

Resolve:

- **Yes** → proceed to 4.d.
- **No** → Read the file back and print it in a fenced code block; loop
  the same AskUserQuestion.
- **Abort** → delete `tasks/<uuid>.toml` and emit a short summary: no
  spawn, no ledger row, no Agent invocation.

---

## 4.d — Run kei-spawn

Call exactly one command (no chaining, so errors surface cleanly):

```bash
kei-spawn spawn tasks/<uuid>.toml --format=json
```

If `DENYLIST_OVERRIDE = true`, append `--no-default-deny`.

If `kei-spawn` is not on PATH, fall back to `"$KEI_RUNTIME_BIN_DIR/kei-spawn"`.
If both fail, surface the SKILL.md runtime-resolution paths (A/B/C) and
STOP — do NOT fabricate a response.

Parse the stdout JSON. Expected shape (exact field names per
`_primitives/_rust/kei-spawn/src/spawn.rs`):

```json
{
  "agent_id": "6a48ca7b-...",
  "dna": "sha256:b37e2d1e9a...",
  "subagent_type": "code-implementer",
  "isolation": "worktree",
  "worktree_path": ".claude/worktrees/agent-6a48ca7b",
  "branch": "agent/<slug>-<ts>"
}
```

Store each field as named (`AGENT_ID`, `DNA`, `SUBAGENT_TYPE`, `ISOLATION`,
`WORKTREE_PATH`, `BRANCH`). If `isolation == "shared"`, `worktree_path`
and `branch` may be absent — handle that gracefully.

On non-zero exit from `kei-spawn`: print stderr verbatim, surface the
error, offer three constructive paths:

- (A) Fix the task.toml and re-spawn (show the diff first).
- (B) Fall back to a smaller scope (loop back to Phase 3).
- (C) Abort — delete task.toml, no ledger row written.

---

## 4.e — Emit the Agent-tool invocation

Print a single fenced code block with the ready-to-paste JSON. Use the
Agent tool's exact parameter names:

```json
{
  "subagent_type": "<SUBAGENT_TYPE>",
  "description": "<first 60 chars of TASK>",
  "prompt": "<TASK_FULL verbatim, with the orchestrator-branch-first preamble>",
  "isolation": "<ISOLATION>"
}
```

Plus a short annotation under the block:

```
Agent ID: <AGENT_ID>
DNA:      <DNA>
Branch:   <BRANCH or n/a for shared>
Worktree: <WORKTREE_PATH or n/a>

Paste the JSON above into an Agent tool call. On return, run:

  kei-spawn verify <AGENT_ID> <WORKTREE_PATH>

to check the 6-file artefact bundle (RULE 0.12) and merge-readiness.
```

---

## 4.f — Final report

Emit the SKILL.md-defined `=== /SPAWN-AGENT REPORT ===` block. Populate
every field from the variables above. Do NOT invent any field value — if
something is absent (e.g. worktree for shared isolation), print `n/a`.

---

## 4.g — Verify criterion

- `AGENT_ID` non-empty UUID
- `DNA` non-empty sha256 prefix
- `SUBAGENT_TYPE ∈ {researcher, code-implementer, ...}` (whatever kei-spawn
  returns — the skill does not enforce a specific set)
- `ISOLATION ∈ {shared, worktree}`
- The Agent-tool invocation JSON is printed in a fenced block
- `TASK_FULL` in the `prompt` field contains the literal phrase
  `MUST NOT invoke git` (RULE 0.13 ban-phrase — added in Phase 2.c)

If the ban-phrase check fails (should be impossible after Phase 2.c), STOP
and surface a bug report — do NOT emit the invocation.

---

## 4.h — Failure paths (NO DOWNGRADE)

Covered inline in 4.d. Additionally, if the user tries to paste the
emitted JSON but the Agent tool rejects it:

- (A) Likely `subagent_type` mismatch — run `kei-spawn roles` to list
  valid subagent_type values in the current runtime and reconcile.
- (B) Likely `isolation` unsupported in the caller's Claude Code version
  — fall back to `shared` and loop back to Phase 1 to re-pick a
  read-only / explorer role.
- (C) Escalate to `/new-agent` if the user actually needs a new agent
  manifest rather than a new instance.
