# Cage Orchestrator Iteration Loop

**Date:** 2026-03-28
**Status:** Approved
**Scope:** cage-orchestrator skill + trusty-cage CLI

## Problem

The cage-orchestrator workflow is currently one-shot: outer Claude launches inner Claude, monitors progress, exports results, and cleans up. There is no mechanism for the user to review exported work, provide feedback, and send inner Claude back to revise — all within the same cage session with preserved context.

## Design

### High-Level Flow

```
1. Outer Claude spins up cage, launches inner Claude
2. Inner Claude works, sends messages (progress_update, task_complete)
3. Outer Claude monitors (tc logs, tc outbox --poll), keeps user informed
4. On task_complete: outer Claude exports, overlays on host git repo
5. User reviews with outer Claude (git diff, testing, feedback)
6. If revisions needed:
   a. Outer Claude composes task_revision message
   b. Sends via tc inbox
   c. Inner Claude (still running, polling inbox) picks it up
   d. Inner Claude resumes work → back to step 2
7. If done: cleanup (destroy cage or keep it)
```

Inner Claude stays alive between iterations, preserving full conversational context. The cage (container + volume) persists throughout.

### New Message Types

Two additions to the messaging protocol:

#### `task_revision` (outer → inner)

Sent by outer Claude when the user has feedback after reviewing exported work.

```json
{
  "id": "msg-20260328T143000-a1b2",
  "type": "task_revision",
  "timestamp": "2026-03-28T14:30:00.000Z",
  "payload": {
    "instructions": "The auth module works but the error messages are too vague. Make them specific — include the HTTP status code and response body."
  },
  "version": 1
}
```

#### `going_idle` (inner → outer)

Sent by inner Claude when its polling loop times out without receiving a revision.

```json
{
  "id": "msg-20260328T150000-c3d4",
  "type": "going_idle",
  "timestamp": "2026-03-28T15:00:00.000Z",
  "payload": {
    "reason": "No task_revision received within polling timeout",
    "waited_seconds": 3600
  },
  "version": 1
}
```

### Inner Agent Prompt Changes

A new "After Task Completion" section is appended to the inner agent prompt template:

> When you have completed the task:
> 1. Send a `task_complete` message via `cage-send`
> 2. Run the following polling script to wait for revised instructions:
> ```bash
> INTERVAL=10; MAX_WAIT=3600; ELAPSED=0
> CURSOR_FILE=~/.cage/cursor/inbox.cursor
> CURSOR=$(cat "$CURSOR_FILE" 2>/dev/null || echo "")
> while [ $ELAPSED -lt $MAX_WAIT ]; do
>   for f in $(ls -1 ~/.cage/inbox/ 2>/dev/null | sort); do
>     if [ -z "$CURSOR" ] || [ "$f" \> "$CURSOR" ]; then
>       cat ~/.cage/inbox/"$f"
>       echo "$f" > "$CURSOR_FILE"
>       exit 0
>     fi
>   done
>   sleep $INTERVAL
>   ELAPSED=$((ELAPSED + INTERVAL))
>   INTERVAL=$((INTERVAL * 2 > 300 ? 300 : INTERVAL * 2))
> done
> echo "POLL_TIMEOUT"
> ```
> 3. If the script returns JSON, parse it — it will be a `task_revision` message. Read the `payload.instructions` field and continue working on the project accordingly. When done, repeat from step 1.
> 4. If the script returns `POLL_TIMEOUT`, send a `going_idle` message via `cage-send` and stop.

**Polling behavior:**
- Exponential backoff: 10s → 20s → 40s → 80s → 160s → cap at 300s (5 min)
- Maximum wait: 3600s (1 hour)
- Uses inbox cursor (`~/.cage/cursor/inbox.cursor`) to track read position, consistent with existing outbox cursor mechanism
- Polling runs as a single bash tool call — no context/token burn during the wait
- When a revision arrives, inner Claude gets the JSON as bash output and resumes reasoning with full prior context

### Outer Orchestration Workflow Changes

The current steps 10-11 (review changes, cleanup) are restructured:

**Step 10: Review Changes with User**
Same as today — show `git diff`, discuss results.

**Step 11: Revision Decision (new)**
Ask user: "Would you like to revise and send inner Claude back to work, or are we done?"

If **revise**:
1. Gather revised instructions from user
2. Send via `tc inbox <name> task_revision '{"instructions": "..."}'`
3. Resume monitoring via `tc outbox <name> --poll`
4. On `task_complete`: go back to step 9 (export) → step 10 (review) → step 11 (decision)

If **done**:
Proceed to step 12 (cleanup).

**Step 12: Cleanup (renumbered)**
Same as current step 11 — ask user whether to destroy cage or keep it.

**Monitoring updates (step 7):**
Replace raw `docker exec ls` polling with `tc outbox <name> --poll` and `tc logs <name>`.

### Edge Cases

**Inner Claude goes idle (polling timeout):**
- Outer Claude receives `going_idle` during `tc outbox --poll`
- Informs user: "Inner Claude timed out waiting. The cage is still alive — I can re-launch if you want to continue."
- Re-launch via `tc launch` with prompt: "Check your inbox for instructions and continue working on the project"
- Graceful degradation: file state preserved, conversational context lost

**Inner Claude crashes:**
- `tc outbox --poll` times out with no `task_complete`
- Before sending `task_revision`, outer Claude verifies inner Claude is alive (container process check or `tc logs`)
- If dead: inform user, offer to re-launch

**Multiple rapid revisions:**
- Not supported in v1 — outer Claude waits for `task_complete` before offering another revision cycle
- Simple and predictable

## Changes Required

### trusty-cage repo

| File | Change |
|---|---|
| `src/trusty_cage/assets/cage-send` | Add `going_idle` to valid message types |
| `src/trusty_cage/cli.py` | Update `inbox_send` help text to list `task_revision` as a valid type (no runtime type validation exists — help text is the only documentation of accepted types) |
| `src/trusty_cage/cli.py` | Add `going_idle` as a poll-terminating condition in `outbox_read`'s `--poll` loop (exit code 2 to distinguish from `task_complete` exit code 0) |
| `src/trusty_cage/cli.py` | Update `--poll` help text to mention `going_idle` as a terminal condition |

### agent_skills repo

| File | Change |
|---|---|
| `.agent/skills/cage-orchestrator/SKILL.md` | Inner prompt: add polling/revision loop after task_complete |
| `.agent/skills/cage-orchestrator/SKILL.md` | Outer workflow: restructure steps 10-11 into revision cycle |
| `.agent/skills/cage-orchestrator/SKILL.md` | Outer workflow: replace raw docker exec polling with `tc outbox --poll` and `tc logs` |
| `.agent/skills/cage-orchestrator/SKILL.md` | Messaging protocol reference: add `task_revision` and `going_idle` types |

No new files, no new CLI commands, no new scripts.

### Exit code semantics for `tc outbox --poll`

| Exit Code | Meaning |
|---|---|
| 0 | `task_complete` received — inner Claude finished successfully |
| 1 | Error (container not running, timeout, etc.) |
| 2 | `going_idle` received — inner Claude's polling timed out, session ended |
