Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion agent/orchestrator/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ A session started from CanvasTTY's launcher with the **Orchestrator** role has t
1. `list_providers`: which agents CanvasTTY can launch here. Each entry's `id` is the exact `spawn_agent.provider` value; prefer entries that are `available` with `signIn: "ok"` (`unknown` only means CanvasTTY has not checked yet). When plugins offer launch options (for example an accounts plugin's `list_routes` tool), the answer names that tool.
2. `spawn_agent` once per part of the task: a known `provider` id, an absolute `cwd` and a self-contained prompt. An unknown provider is refused with a pointer back to `list_providers`. **If the person names a model, pass it as `model`** in the format `list_providers` gives for that provider (`model.format`; OpenCode takes `provider/model`, for example `zai-coding-plan/glm-5.3-flash`, and `model.known` lists what `opencode models` reported); without `model` the CLI uses its own default. `effort` sets the reasoning effort where the CLI has one (`efforts`). Without `profile` the subagent gets your own launch profile, or the next lower one its CLI has (never more than yours, never YOLO); `list_providers` shows each provider's `profiles`. `cwd` must be this project's folder or a folder inside it. A refusal says why (a folder outside the project, a profile above yours, a limit the person set): change the request, do not retry it.
3. `wait_for_agent` for each subagent (`timeoutSeconds` up to 600; call it again after `timeout`) instead of polling. `needs_approval` means the person must answer a prompt in that card: never answer it yourself.
4. `get_agent_result` to read what the subagent produced; check the actual files before reporting success.
4. Read the answer returned by `wait_for_agent`; use `get_agent_result` only if the answer is missing or to read remaining pages at `answer.nextOffset`. Do not reread the first page. Read all pages of the current answer before closing the child; check the actual files before reporting success.

Do not explore the filesystem, `PATH` or config folders (for example `~/.local/bin` or `~/.config/<cli>`) looking for agents or their configuration: `list_providers` is what CanvasTTY can launch, and delegation goes through `spawn_agent`.

Expand Down
6 changes: 3 additions & 3 deletions docs/agent-orchestration.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ A session launched from the desktop with the **Orchestrator** role gets the `can
| `list_providers` | The agent providers CanvasTTY can launch as subagents: `id` (the exact `spawn_agent.provider` value), `name`, `installed`/`available` (from the provider CLI registry resolved at startup or on a recheck), `signIn` (`ok`, `signed_out`, `expired` or `unknown`, from the last usage-limits read; it never starts a read and never reads credentials), `subagent` and `orchestrator` support, `model` (whether the CLI takes one, its format, and for OpenCode the models a cached `opencode models` listing reported: run in the background with a 5 s timeout, refreshed after 10 minutes, never awaited) and `efforts`, and plugin launch options (`launchOptions`, plus `launchOptionTools` such as an accounts plugin's `list_routes`). Fast: cached state only, no network calls. |
| `spawn_agent` | Launches a subagent (profile: see below). `provider` must be a `list_providers` id (the schema lists them); an unknown one is refused with `INVALID_REQUEST` naming `list_providers`. Optional `model` goes to the CLI's own `--model` for that run (OpenCode `provider/model`; Codex, Claude, Qwen, Kimi alias, Grok, OMP, Pi, Cursor; Hermes, MiniMax, Devin and Antigravity take none) and optional `effort` to its reasoning effort (Codex `-c model_reasoning_effort`, Claude `--effort`, Grok `--reasoning-effort`). Both are checked per provider, kept on restart and restore, and never written to the CLI's configuration. If the person names a model, the orchestrator passes it as `model`. An OpenCode model its own `opencode models` list does not contain is refused before launch with up to five closest listed ids (OpenCode would otherwise stop with only "Unexpected server error"); when that list cannot be read, the model is passed as given. A subagent that exits anyway reports the last lines of its screen as `exitLines` in `wait_for_agent`, `observe_agent` and `get_agent_result`. |
| `wait_for_agent` | `{ sessionId, timeoutSeconds ≤ 600 }` (default 55). Returns when the subagent is `idle` (after a prompt: only once a turn that started after the latest delivered prompt has ended, so the CLI's startup idle does not count), `needs_approval`, `done`/`failed` (exited), `quiet` (reports no status and its screen stopped changing), `closed`, or on `timeout`, with `status`, `exitCode`, `waitedMs` and the masked terminal tail (masked before it is cut). Only this orchestrator's own subagents; it stops at once when the call is canceled or the orchestrator disconnects. |
| `get_agent_result`, `observe_agent` | `get_agent_result`: `answer`, the final reply of the subagent's last turn as the agent itself reported it (Codex through its Stop hook, OpenCode through CanvasTTY's plugin reading the session's last assistant message at `session.idle`; at most 4,096 characters with the end kept, masked, in memory only and cleared when the next turn starts), plus `status`, the exit state and the masked terminal tail. Other providers have no `answer`: their tail is raw screen output. `observe_agent`: the current status and tail. |
| `get_agent_result`, `observe_agent` | `get_agent_result`: `answer`, the final reply of the subagent's last turn as the agent itself reported it (Codex through its Stop hook, OpenCode through CanvasTTY's plugin reading the session's last assistant message at `session.idle`; up to 65,536 UTF-16 code units retained, masked, in memory only and cleared when the next turn starts; MCP returns 16,384-unit pages via `answer.nextOffset` and `get_agent_result(answerOffset=...)`), plus `status` and the exit state. When `answer` is present, `output` is empty; use `observe_agent` for the terminal tail. Other providers have no `answer`: their tail is raw screen output. `observe_agent`: the current status and tail. |
| `send_to_agent`, `cancel_agent`, `list_agents` | Follow-up prompts, disposing a subagent, listing this session's subagents. |

### Subagent profiles
Expand All @@ -32,7 +32,7 @@ A launch contributor that puts OpenCode on another model keeps `bash` asking, li

The control CLI's `create --profile auto|normal|acceptEdits|plan|yolo` is the equivalent for its workers (from the person's own terminal it defaults to YOLO, which needs the person's acknowledgement for that CLI).

The workflow is `list_providers` → `spawn_agent` (one per part) → `wait_for_agent` → `get_agent_result`. Agents should not explore the filesystem for agent CLIs or their configuration; the MCP server's instructions, the tool descriptions and the refusal messages say so.
The workflow is `list_providers` → `spawn_agent` (one per part) → `wait_for_agent`. The answer page returned by `wait_for_agent` already counts as read; a complete short answer needs no duplicate `get_agent_result`. If `answer.nextOffset` is present, read only remaining pages with `get_agent_result(answerOffset=answer.nextOffset)`. If the wait returned no answer, use `get_agent_result` when the child finishes. Read every page of the current reply before `cancel_agent`. Closing a child with an unread captured result is refused. After successful cleanup, use the results already received and do not wait on that child again. A late wait for a recently closed child returns `reason: "closed"` and `resultReadBeforeClose`, not a subtree error. These bounded, memory-only receipts retain no answer text or terminal output; they are visible only to the orchestrator that closed the child. Unknown and foreign IDs remain refused. A new `send_to_agent` invalidates the previous result receipt; an actively working child can still be canceled directly. A wait timeout only ends that wait: inspect reported errors and stop on failure instead of repeatedly waiting on a failed request. `sourceTruncated: true` means the capture itself exceeded its limit: have the worker save the full deliverable in a project file and read it before closing. Only captured final replies may use a 512 KiB runtime frame; decision and ordinary status frames retain their 64 KiB limit, and MCP frames remain capped at 128 KiB. Agents should not explore the filesystem for agent CLIs or their configuration; the MCP server's instructions, the tool descriptions and the refusal messages say so.

The control CLI below is a separate surface for automation; `providers` is its equivalent of `list_providers`.

Expand Down Expand Up @@ -117,7 +117,7 @@ The default private controller file is next to the connection descriptor. Use a
- An accepted send means text was written to the PTY. It is not a completed task.
- A result becomes fresh only after a corresponding working-to-idle lifecycle transition. Provider turn IDs, when present, reject stale completion signals.
- `resultRevision` counts completed observations. A previous completed answer is kept separate from the active turn. `result --after N` returns `fresh: false` until a newer result exists.
- Only controlled Codex sessions opt into the Stop hook's final-message capture. Text is bounded to 4,096 UTF-16 code units, with a `truncated` flag. Ordinary sessions continue reporting lifecycle without capturing their answers.
- Only controlled Codex sessions opt into the Stop hook's final-message capture. Text is bounded to 65,536 UTF-16 code units, with a `truncated` flag. Ordinary sessions continue reporting lifecycle without capturing their answers.
- `no_result` means a turn became idle without a final-message payload. Inspect files and screen; do not report success from that state alone.
- `interrupt` returns `stopped: false` because Ctrl-C delivery is only a request. Confirm the resulting lifecycle before proceeding.

Expand Down
Loading
Loading