# Terminals and agents

> PTY lifecycle, `TuiAgent` catalog and detection order, pane keys, agent status hooks, terminal env injection (`ORCA_*`), and when to use terminal CLI vs orchestration messaging.

- Repository: stablyai/orca
- GitHub: https://github.com/stablyai/orca
- Human docs: https://grok-wiki.com/public/docs/stablyai-orca-2036d532bf1c
- Complete Markdown: https://grok-wiki.com/public/docs/stablyai-orca-2036d532bf1c/llms-full.txt

## Source Files

- `src/shared/types.ts`
- `src/shared/tui-agent-config.ts`
- `src/shared/agent-detection.ts`
- `src/main/ipc/pty.ts`
- `src/main/agent-hooks/server.ts`
- `src/cli/specs/agent-hooks.ts`
- `skills/orca-cli/SKILL.md`

---

---
title: "Terminals and agents"
description: "PTY lifecycle, `TuiAgent` catalog and detection order, pane keys, agent status hooks, terminal env injection (`ORCA_*`), and when to use terminal CLI vs orchestration messaging."
---

Orca hosts interactive terminals through a main-process PTY layer that routes local `node-pty` sessions and SSH relay leases, injects per-pane `ORCA_*` environment variables at spawn, and pairs each session with optional loopback agent-status hooks. Launchable coding agents are identified by the `TuiAgent` union and `TUI_AGENT_CONFIG` catalog; PATH detection and automatic fallback order are separate concerns.

## PTY lifecycle

PTY operations flow through a provider registry: `connectionId === null` uses the local provider (optionally backed by the Orca daemon); non-null IDs use per-connection SSH providers. Each spawned PTY records ownership, geometry, last-input time, and bidirectional `ptyId` ↔ `paneKey` maps so teardown can clear hook caches and serializer state.

```mermaid
stateDiagram-v2
  [*] --> Spawning: pty:spawn
  Spawning --> Live: provider.spawn OK
  Live --> Live: write / resize / data batch
  Live --> Teardown: pty:kill or process exit
  Teardown --> [*]: clearProviderPtyState
  note right of Teardown
    Unregisters paneKey maps,
    hook/OpenCode/Pi overlays,
    migration flags, serializers
  end note
```

On exit or kill, `clearProviderPtyState` runs a single cleanup path: OpenCode and Pi overlay state, agent-hook pane caches, advertised-URL bindings, Claude live-PTY gates, renderer-serializer flags, and `paneKeyTeardownListeners` (for example cursor-agent synthesized title loops). SSH connection loss calls the same cleanup via `clearPtyOwnershipForConnection`.

Daemon-backed spawns use a stable `sessionId` (minted from worktree context) so Pi/Codex overlay directories survive daemon reconnects. Renderer scrollback cooperation uses generation tokens on `declarePendingPaneSerializer` / `settlePaneSerializer` so a remounted pane is not overwritten by an older PTY’s teardown.

| IPC / concern | Role |
| --- | --- |
| `pty:spawn` | Create session; validate `paneKey`; call `buildPtyHostEnv` on local/daemon host path |
| `pty:kill` | Terminate; `finishPtyShutdown` drops ownership |
| `pty:write` / resize | Routed via `getProviderForPty` |
| `pty:declarePendingPaneSerializer` | Renderer claims scrollback before spawn |
| Provider `onExit` | Mirrors kill cleanup for local exits |

<Warning>
`buildPtyHostEnv` must not run for SSH remote shells: hook server coordinates, attribution shims, and host filesystem paths are local-only. Remote pane identity env vars are forwarded only when `isRemoteAgentHooksEnabled()` is true.
</Warning>

## `TuiAgent` catalog

`TuiAgent` is the closed union of agents Orca can launch from the new-workspace flow and default-agent settings. Per-agent metadata lives in `TUI_AGENT_CONFIG`:

| Field | Purpose |
| --- | --- |
| `detectCmd` | Binary checked on PATH during preflight |
| `launchCmd` | Command line passed to the PTY (may include flags, e.g. `cursor-agent`, `hermes --tui`) |
| `expectedProcess` | Foreground process name for agent-vs-shell detection |
| `promptInjectionMode` | How initial prompts are delivered (`argv`, `flag-prompt`, `stdin-after-start`, etc.) |
| `draftPromptFlag` / `draftPromptEnvVar` | Native draft seeding (`--prefill`, `ORCA_PI_PREFILL`, `ORCA_OMP_PREFILL`) |
| `preflightTrust` | Pre-write trust artifacts so first-run menus do not swallow pasted drafts |
| `draftPasteReadySignal` | Agent-specific readiness before bracketed paste |

The catalog currently includes Claude, Codex, Copilot, Grok, OpenCode, Pi, OMP, Gemini, Antigravity, Aider, Cursor, Droid, Hermes, and additional BYOC CLIs—each mapped to concrete binaries (for example `kiro` → `kiro-cli`, `aug` → `auggie`).

## Detection and auto-pick order

**PATH detection** runs in preflight (`detectInstalledAgents`): for every `TUI_AGENT_CONFIG` entry, Orca checks whether `detectCmd` resolves with `which` / `where` (or inside a WSL distro when configured). Detection is parallel and returns the list of installed agent ids; it does not impose UI ordering.

**Auto-pick fallback** when the user leaves the default at “auto” (`null`) or picks an agent that is not installed uses `TUI_AGENT_AUTO_PICK_ORDER`:

1. Honor explicit user preference if that agent is detected.
2. Otherwise walk the ordered list and return the first detected agent.
3. `'blank'` yields no agent.

Order (highest priority first): `claude`, `codex`, `grok`, `copilot`, `opencode`, `pi`, `omp`, `gemini`, `antigravity`, then the remaining catalog entries through `openclaw`.

`refreshShellPathAndDetectAgents` re-hydrates PATH from the login shell before re-running detection—useful after installing a new CLI without restarting Orca. Remote repos call `preflight.detectAgents` over SSH with the same command list.

<Tip>
Renderer state caches detection per session via `ensureDetectedAgents` / `refreshDetectedAgents`. Remote SSH hosts keep a separate `remoteDetectedAgentIds` map keyed by `connectionId`.
</Tip>

## Pane keys

A **pane key** is `${tabId}:${stableLeafUuid}` where `stableLeafId` is a durable terminal-layout leaf UUID. It replaces legacy `${tabId}:${numericPaneId}` keys for hook routing and retained dashboard rows.

| Env var | Set when | Used for |
| --- | --- | --- |
| `ORCA_PANE_KEY` | Spawn validates tab + leaf against layout | Hook POST bodies, agent scripts, status routing |
| `ORCA_TAB_ID` | Matching `tabId` on spawn | Hook payloads, relay revival |
| `ORCA_WORKTREE_ID` | Worktree known at spawn | Worktree-scoped status and CLI context |

Spawn logic only keeps these variables when the incoming `ORCA_PANE_KEY` parses and matches `tabId` / `leafId`; otherwise they are stripped so hooks cannot attribute events to the wrong pane. Legacy numeric pane keys are migrated to UUID keys when metadata proves the mapping; unsupported combinations are recorded for diagnostics.

`makePaneKey(tabId, leafId)` and `parsePaneKey` are the canonical helpers; `getPtyIdForPaneKey` resolves live PTYs from the main-process map.

## Agent status: hooks and title signals

Orca uses two complementary signals:

### Explicit status (hooks)

When `agentStatusHooksEnabled` is not `false` (default on), Orca starts a loopback HTTP hook server and injects runtime coordinates into each PTY. Managed installers cover: `claude`, `codex`, `gemini`, `antigravity`, `cursor`, `droid`, `command-code`, `grok`, `copilot`, `hermes`. Pi/OpenCode use overlay config dirs rather than separate HTTP targets in that list.

Hook-reported states: `working`, `blocked`, `waiting`, `done`. Payloads include prompt previews, tool name/input, assistant message snippets, and optional orchestration context (`taskId`, `dispatchId`, parent handles).

| Hook runtime env | Meaning |
| --- | --- |
| `ORCA_AGENT_HOOK_PORT` | Loopback port |
| `ORCA_AGENT_HOOK_TOKEN` | Bearer token for `X-Orca-Agent-Hook-Token` |
| `ORCA_AGENT_HOOK_ENV` | Environment label (`dev` / `prod`) |
| `ORCA_AGENT_HOOK_VERSION` | Protocol version (`1`) |
| `ORCA_AGENT_HOOK_ENDPOINT` | Optional on-disk endpoint file path |

CLI surface:

```bash
orca agent hooks status [--json]
orca agent hooks on [--json]
orca agent hooks off [--json]
```

`off` removes managed hook entries locally; `on` re-enables installation. Status entries persist in `userData/agent-hooks/last-status.json` (7-day hydrate cap) so dashboard rows can survive restarts. Stale explicit status decays after `AGENT_STATUS_STALE_AFTER_MS` (30 minutes).

### Title-based signals (OSC / heuristics)

OSC title parsing from PTY output drives sidebar badges, unread notifications, and `terminal wait --for tui-idle`. States are coarser: `working`, `permission`, `idle`. This path is intentionally narrower than the full `TuiAgent` list (substring false positives are avoided for short names like `amp`).

`createAgentStatusTracker` fires `onBecameIdle` on working→idle transitions— the trigger for unread-style attention. Gemini and Pi titles are normalized to stable labels to avoid renderer churn.

<Note>
Dashboard-grade status comes from hooks; title inference still drives several UX paths (TUI idle wait, Claude prompt-cache timer scoping, smart attention).
</Note>

## Terminal environment injection (`ORCA_*`)

`buildPtyHostEnv` is the single source of truth for host-local injections before the provider wraps `TERM`, `LANG`, and shell startup.

| Variable group | When set | Purpose |
| --- | --- | --- |
| Agent hooks | Hooks enabled | Loopback server + strip inherited stale hook env |
| `ORCA_OPENCODE_*` / `OPENCODE_CONFIG_DIR` | Hooks enabled | Per-PTY OpenCode config overlay |
| `ORCA_PI_*` / `ORCA_OMP_*` / `PI_CODING_AGENT_DIR` | Hooks enabled | Pi/OMP agent-dir overlays and status extension |
| `ORCA_CODEX_HOME` / `CODEX_HOME` | Codex account selected | Scoped Codex auth home |
| `ORCA_USER_DATA_PATH` + dev `PATH` prepend | Dev unpackaged | CLI calls hit the dev runtime |
| `ORCA_ENABLE_GIT_ATTRIBUTION` + shims | Setting on | Git/gh attribution in Orca PTYs only |
| `ORCA_TERMINAL_HANDLE` | Runtime present | Orchestration self-addressing |
| Draft prefill | Draft launch | `ORCA_PI_PREFILL`, `ORCA_OMP_PREFILL` |

Attribution and hook keys are deleted when disabled so nested Orca terminals do not inherit another session’s overlays.

## Terminal CLI vs orchestration messaging

Both surfaces talk to the running Orca runtime, but they target different recipients.

| Use | Surface | Why |
| --- | --- | --- |
| Shell commands, builds, tests | `orca terminal send` | Writes to the PTY byte stream; safe for non-agent processes |
| Read output, wait for exit or TUI idle | `orca terminal read` / `wait` | Observes PTY transcript and OSC-derived idle |
| Create/split/list terminals | `orca terminal create` / `split` / `list` | Structural terminal management |
| Message another **agent** terminal | `orca orchestration send` / `check` / `reply` | Structured agent-to-agent mailboxes; uses `ORCA_TERMINAL_HANDLE` |
| Dispatch tasks with preamble | `orca orchestration dispatch --inject` | Requires recognized agent CLI in target terminal |
| Multi-agent coordinator loop | `orca orchestration run` | Experimental; needs runtime + feature flag |

<Warning>
Do not use `orca terminal send` to hand off work to Claude Code, Codex, Gemini, or other agent TUIs when the goal is agent coordination—use orchestration messaging. Terminal send injects raw keystrokes and races with interactive UIs. Orchestration `--inject` also requires an agent process in the target pane; for a bare shell, send the prompt with `terminal send` instead.
</Warning>

Orca CLI guidance: **writes** go to non-agent terminals; **reads and waits** are valid on any terminal, including agents. Orchestration owns nudges, replies, task DAGs, and worker completion messages.

### Typical automation patterns

<Steps>
<Step title="Observe a shell or build">
```bash
orca terminal list --worktree active --json
orca terminal read --terminal <handle> --json
orca terminal wait --terminal <handle> --for exit --timeout-ms 60000 --json
```
</Step>
<Step title="Wait for an agent turn to finish">
```bash
orca terminal wait --terminal <handle> --for tui-idle --timeout-ms 120000 --json
```
Uses OSC title transitions (`working` → `idle`). Unsupported CLIs may time out—always set `--timeout-ms`.
</Step>
<Step title="Coordinate agents">
```bash
orca orchestration send --to <handle> --subject "Review API changes" --body "Focus on auth middleware" --json
orca orchestration check --terminal <handle> --unread --wait --timeout-ms 30000 --json
```
Requires experimental orchestration enabled in Settings.
</Step>
</Steps>

Handles are runtime-scoped; after reload, re-list terminals if you see `terminal_handle_stale`.

## Verification

| Check | Command / signal |
| --- | --- |
| Runtime up | `orca status --json` |
| Installed agents | Settings → Agents refresh, or RPC `preflight.detectAgents` |
| Pane key in PTY | `printf '%s\n' "$ORCA_PANE_KEY"` inside the pane |
| Hooks enabled | `orca agent hooks status --json` |
| Hook reachability | `ORCA_AGENT_HOOK_PORT` set; POST `/hook/<target>` with token header |
| TUI idle | `orca terminal wait --for tui-idle` after agent boot |

## Related pages

<CardGroup>
<Card title="CLI scripting workflow" href="/cli-scripting-workflow">
End-to-end automation with terminal read/send/wait and worktree comments.
</Card>
<Card title="Agent orchestration" href="/agent-orchestration">
Messaging, task DAGs, dispatch, and coordinator `run`.
</Card>
<Card title="CLI core reference" href="/cli-core-reference">
Full `terminal` and `agent hooks` command signatures.
</Card>
<Card title="SSH remote worktrees" href="/ssh-remote-worktrees">
Remote PTY leases and hook forwarding constraints.
</Card>
<Card title="Settings reference" href="/settings-reference">
`agentStatusHooksEnabled`, terminal defaults, and experimental flags.
</Card>
</CardGroup>
