---
name: apt:terminal
description: "Spawn a sibling Aperant terminal with its own worktree + CLI instance for parallel work"
apt-skill-version: {{APT_VERSION}}
stage: ops
intent: configure
when_to_use: "The user is inside an Aperant-spawned PTY and wants to open a sibling terminal with its own worktree for parallel subtask execution."
user_invocable: true
internal: false
spawns_agent: false
agent_name: null
task_context: none
default_execution_mode: step
execution_modes:
  - auto
  - step
allowed-tools: "Bash, Read, Write"
argument-hint: "apt:terminal [task-id] [--cwd <path>]"
gates: []
---
<objective>
Open a sibling Aperant terminal from inside an existing Aperant-spawned PTY. Each spawned terminal gets its own worktree (cwd) and starts a fresh Claude Code (or other CLI) instance — useful when the executor has identified parallel-safe subtasks and wants to fan out work across multiple agent windows.

This is an OPT-IN capability for executors. The default `apt:execute` flow stays sequential. Use `apt:terminal` only when:
1. You have ≥2 subtasks with NO file overlap and NO ordering dependencies, AND
2. You are running inside an Aperant terminal (host-detect `aperant_terminal:true`).

The hard cap is 4 levels of nested spawn (APERANT_MAX_SPAWN_DEPTH) to prevent fork-bomb feedback loops.
</objective>

<your_environment>
- **Working directory:** Whatever the current task's worktree is (or project root if no worktree).
- **apt-tools:** Always invoke as `node packages/framework/bin/apt-tools.mjs` from the project root.
- **Detection:** `apt-tools host-detect` returns `aperant_terminal: true|false` plus `aperant_socket: <path|null>`.
- **Transport:** Unix-domain socket at `process.env.APERANT_IPC_SOCKET`. Web-mode callers use the HTTP proxy at `http://127.0.0.1:14714/terminal/spawn` instead — same payload shape.
- **Constitution:** Read `AGENTS.md` if it exists.
</your_environment>

<state_files>
## State Files

**Reads:**
- `process.env.APERANT_IPC_SOCKET` — absolute path to AperantIpcServer's Unix socket (set by NodePtyProvider when the parent terminal was spawned by Aperant)
- `process.env.APERANT_SPAWN_DEPTH` — current depth (0 for user-launched, 1+ for nested). Read by the CLI client and forwarded as `_callerDepth` so the server can enforce the cap.
- `.aperant/state.json` (read indirectly by the server's worktree allowlist) — used to validate that `--worktree` points to a path under a known Aperant project.

**Writes:**
- Each spawned terminal becomes a new PTY session inside Electron-main; the spawned `sessionId` is also visible in `apt-tools terminal list`.
- The agent SHOULD record spawned task IDs in its own task notes (`.aperant/tasks/{task-id}/build-progress.json` or equivalent) for traceability.

**No filesystem state of its own.** The skill is pure compute glue.
</state_files>

<process>

## 1. Preflight — confirm we are inside Aperant

```bash
node packages/framework/bin/apt-tools.mjs host-detect
```

Inspect the JSON output:
- `host.aperant_terminal` MUST be `true`. If `false`, **stop**: spawning is unavailable in the current shell.
- `host.aperant_socket` MUST be a non-null path. If `null`, **stop** with the same reason.

If preflight fails, surface this to the user verbatim and fall back to sequential execution. Do NOT try to start an IPC server yourself — only Electron main owns it.

## 2. Decide whether to spawn

The executor MAY call `apt:terminal spawn` when ALL of these hold:

- Two or more remaining subtasks share NO files (compare `subtask.files` arrays — strict set disjoint).
- None of those subtasks declare a dependency on another subtask in the same fan-out group.
- The subtasks each touch ≤1 package boundary (so ./pnpm install or build-step dependencies do not collide).

If even one condition fails, do NOT spawn — keep the sequential `apt:execute` flow.

## 3. Spawn

The exact command shape (`apt-tools terminal spawn` is the canonical name; the
.mjs suffix is just the local invocation when running from a checkout):

```bash
node packages/framework/bin/apt-tools.mjs terminal spawn \
  --worktree /abs/path/to/worktree \
  --task <task-id> \
  [--instructions "kick-off prompt for Claude Code"] \
  [--cli claude-code] \
  [--yolo]
```

The response is JSON to stdout. On success:

```json
{"id":"…","ok":true,"result":{"sessionId":"…","wsUrl":"ws://127.0.0.1:14714/terminal/…","depth":N}}
```

`depth` is the new terminal's level (caller_depth + 1). Record `sessionId` in your task notes.

## 4. Recognise the graceful-degrade error envelopes

These are **expected** error shapes — do not retry blindly. Each one tells you exactly which obligation discharged:

| Error code | Meaning | Action |
|---|---|---|
| `not_in_aperant_terminal` | `APERANT_IPC_SOCKET` was not in env. You are not inside an Aperant PTY. | Fall back to sequential execution. |
| `electron_compute_unavailable` | The socket file does not exist (Electron main is not running). | Surface to the user and fall back. |
| `depth_exceeded` | The new spawn would exceed `APERANT_MAX_SPAWN_DEPTH=4`. | Stop fanning out. The current depth is `(payload._callerDepth)`. |
| `outside_known_project` | `--worktree` was not under a directory containing `.aperant/state.json`. | Re-check the worktree path. |
| `validation_failed` | Missing `--worktree` or `--task`, or non-absolute path. | Fix the flags. |
| `timeout` | Server did not respond within 10 s. | Retry once; if it persists, surface to the user. |

## 5. List / status / health (read-only)

```bash
# Every session known to AperantIpcServer (via its TerminalManager)
node packages/framework/bin/apt-tools.mjs terminal list

# One session's metadata
node packages/framework/bin/apt-tools.mjs terminal status --session <sessionId>

# Smoke-test the socket — handy when debugging "why isn't spawn working?"
node packages/framework/bin/apt-tools.mjs terminal health
```

## 6. After spawn — track and let the spawned agent finish

The spawned terminal runs its own Claude Code (or other CLI) instance. It is a peer, not a child of the current process. Once spawned:

- The current agent SHOULD continue with whatever subtask remains in its own queue.
- The spawned agent will pick up its task via the `--instructions` it was given (or a follow-up `apt:plan`/`apt:execute` round started by the user).
- If the spawned terminal's task is also in the same plan, mark its subtask in `implementation_plan.json` as `status: "in-progress"` and add `assigned_to: "<sessionId>"` for traceability.

**Do NOT** add auto-spawn logic to `apt:execute` from this skill — auto-spawn is a follow-up subtask in a different milestone. `apt:terminal` is a documented capability that the executor MAY invoke when appropriate, never a default.
</process>

<security>
## Security Notes

- The IPC socket is `chmod 0600` under a `chmod 0700` parent dir, with the OS username embedded in the path. Single-user desktop threat model.
- The socket is bound to a Unix domain — never a TCP port.
- The HTTP proxy at `:14714/terminal/spawn` is bound to `127.0.0.1` only (verified in `terminal-websocket-server.ts`).
- Worktree paths are validated against a "must be under a directory containing `.aperant/state.json`" allowlist. Arbitrary `cwd` injection is rejected.
- The `--instructions` payload is shell-quoted before being written to PTY stdin. There is no `eval` or template-string interpolation into a shell command.
- Spawn-depth cap (4) prevents fork-bomb feedback loops.
</security>
