Panes and agents
Authored method reference for pane.* and agent.* Local API methods.
Panes and agents
The agent.*, pane.*, and layout.* Local API methods cover pane targeting, input and read semantics, layout identity consequences, and success discriminators. Raw parameter and result shapes live in the generated requests, responses, and enums (PaneDirection, SplitDirection, ReadSource, ReadFormat, PaneAgentState, PaneZoomMode, PaneMoveReason, PaneSwapReason, PaneResizeReason, PaneZoomReason, PaneFocusDirectionReason). Error semantics documents the error envelope; transport and the request envelope are described in the Local API overview.
pane.wait_for_output, agent.wait, and agent.prompt with a wait object hold their connection open; see their entries.
Method index
Agent methods
| Method | Params | Result |
|---|---|---|
agent.list | none | AgentList |
agent.get | AgentTarget | AgentInfo |
agent.start | AgentStartParams | AgentStarted |
agent.focus | AgentTarget | AgentInfo |
agent.read | AgentReadParams | PaneRead |
agent.send_keys | AgentSendKeysParams | Ok |
agent.prompt | AgentPromptParams | AgentPrompted |
agent.wait | AgentWaitParams | AgentInfo |
agent.rename | AgentRenameParams | AgentInfo |
agent.view.set | AgentViewSetParams | AgentView |
agent.view.clear | AgentViewClearParams | AgentView |
agent.explain | AgentTarget | AgentExplain |
Pane methods
| Method | Params | Result |
|---|---|---|
pane.list | PaneListParams | PaneList |
pane.get | PaneTarget | PaneInfo |
pane.current | PaneCurrentParams | PaneCurrent |
pane.split | PaneSplitParams | PaneInfo |
pane.close | PaneTarget | Ok |
pane.focus | PaneTarget | PaneInfo |
pane.send_text | PaneSendTextParams | Ok |
pane.send_input | PaneSendInputParams | Ok |
pane.send_keys | PaneSendKeysParams | Ok |
pane.read | PaneReadParams | PaneRead |
pane.wait_for_output | PaneWaitForOutputParams | OutputMatched |
pane.swap | PaneSwapParams | PaneSwap |
pane.zoom | PaneZoomParams | PaneZoom |
pane.layout | PaneLayoutParams | PaneLayout |
pane.process_info | PaneProcessInfoParams | PaneProcessInfo |
pane.move | PaneMoveParams | PaneMove |
pane.neighbor | PaneNeighborParams | PaneNeighbor |
pane.edges | PaneEdgesParams | PaneEdges |
pane.focus_direction | PaneFocusDirectionParams | PaneFocusDirection |
pane.resize | PaneResizeParams | PaneResize |
pane.rename | PaneRenameParams | PaneInfo |
pane.report_agent | PaneReportAgentParams | Ok |
pane.report_agent_session | PaneReportAgentSessionParams | Ok |
pane.clear_agent_authority | PaneClearAgentAuthorityParams | Ok |
pane.release_agent | PaneReleaseAgentParams | Ok |
pane.report_metadata | PaneReportMetadataParams | Ok |
pane.graphics.set | PaneGraphicsSetParams | Ok |
pane.graphics.clear | PaneGraphicsClearParams | Ok |
pane.graphics.info | PaneTarget | PaneGraphicsInfo |
pane.graphics.stream | PaneGraphicsStreamParams | streaming |
pane.graphics.stream.set | PaneGraphicsSetParams | Ok |
pane.graphics.stream.open | PaneGraphicsStreamParams | Ok |
pane.graphics.stream.close | PaneGraphicsStreamParams | Ok |
pane.graphics.stream.direct | PaneGraphicsDirectParams | PaneGraphicsFrameAck |
Layout methods
| Method | Params | Result |
|---|---|---|
layout.export | LayoutExportParams | LayoutExport |
layout.apply | LayoutApplyParams | LayoutApply |
Identifier semantics
Pane identifiers
pane_id is a stable public identifier of the form "<workspace_id>:p<encoded_number>" (for example, w1943cd2b11a1:p2). The pane number uses the same one-based 32-symbol encoding as tab ids. Closed pane numbers are not reused within a workspace. Methods that accept an optional pane_id follow this resolution order:
- If
pane_idis present, parse it. A parse miss or a pane that no longer exists returnspane_not_found. - If
pane_idis omitted, resolve the active workspace's focused pane.- For
pane.current, the optional field is namedcaller_pane_id. When omitted, the server resolves the focused pane of the currently active workspace. When the target pane cannot be resolved, the result ispane_not_foundwith the generic messagepane not found(it does not echo a caller id).
- For
- Methods that target a tab (
split,moveto an existing tab) accept atarget_pane_idinside the destination object, with its own not-found code.
Cross-workspace pane.move mints a new public pane_id for the moved pane and registers the previous public id as an alias: subsequent requests that use the previous id still resolve to the moved pane. The moved pane keeps its terminal id and process. See pane.move.
Agent targets
agent.get, agent.focus, agent.read, agent.send_keys, agent.prompt, agent.wait, agent.rename, and agent.explain accept a free-form target string resolved in this order:
- Match a terminal id exactly. One match resolves; multiple matches are ambiguous.
- Parse
targetas a publicpane_idand resolve the terminal attached to that pane. - Match against the terminal's stored agent name or effective agent label.
- Otherwise return
agent_not_found.
Two or more terminals resolving to the same target produces agent_target_ambiguous with one candidate summary per terminal. Request an exact terminal id or an unambiguous pane id to disambiguate.
Only terminals marked as agent terminals are returned by agent.list and accepted by the agent methods. A pane backed by a plain shell is not an agent target even when its pane id resolves.
Exact success discriminators
Every mutating pane method returns a result object with a changed boolean. Treat changed as the authoritative signal of whether state was modified:
true: the request modified workspace state. The server emits alayout_updatedevent (and possiblypane_moved,pane_created,pane_closed,tab_created,tab_closed,workspace_created,workspace_closedforpane.move).false: the request was accepted but had no effect, paired withreasonwhen the method exposes one. The response is stillresult, noterror.
A response with error means the request was rejected before any state change. The discriminators below call out the reason enum value for each no-op path.
Common error categories
| Category | Produced by | Meaning |
|---|---|---|
pane_not_found | Every pane method that takes pane_id or target_pane_id. | The pane id did not parse or no longer exists. The message is pane <id> not found or pane not found for the omitted-pane fallback. |
agent_not_found | Agent methods that take target. | target did not resolve to any agent terminal. |
agent_target_ambiguous | Same as agent_not_found. | target matched multiple terminals. The message lists terminal_id, pane_id, workspace_id, tab_id, cwd, and status for each candidate. |
agent_not_ready | agent.send_keys, agent.prompt. | The target has no recognized active agent, or its pane runtime is not hosting that agent. |
agent_not_running | agent.wait, agent.prompt with wait. | The target process, pane, or agent identity disappeared or changed while waiting. |
agent_name_taken | agent.start, agent.rename. | Another agent terminal already owns the requested name. The message lists competing terminals. |
invalid_agent | pane.report_agent, pane.report_agent_session, pane.release_agent. | The reported agent label was empty after trimming. |
invalid_agent_view | agent.view.set, agent.view.clear. | The source, recursive filter, field/value pair, or sort definition is invalid. |
invalid_pane_swap | pane.swap. | The caller supplied both a direction and explicit source/target ids, or neither. |
invalid_metadata_* | pane.report_metadata. | Source, ttl, tokens, or set/clear combinations were invalid. See pane.report_metadata. |
pane_layout_unavailable | Layout-reading methods and pane.move. | The layout snapshot could not be produced for the resolved tab. |
confirmation_required | pane.close. | The configured close policy requires confirmation. |
pane_send_failed / agent_send_keys_failed | Pane input methods / agent.send_keys. | The runtime writer reported an error. |
agent_prompt_failed | agent.prompt. | The agent-aware submission could not be written. |
invalid_key | pane.send_keys, pane.send_input, agent.send_keys. | A requested key could not be parsed. The message is unsupported key <key>. |
invalid_regex | pane.wait_for_output. | A regex match pattern failed to compile. |
timeout | pane.wait_for_output, agent.wait, agent.prompt with wait. | The requested output or state transition did not occur before the deadline. |
invalid_env | agent.start, pane.split. | An env key was empty, contained =, or contained a NUL byte; or a value contained a NUL byte. |
feature_disabled | pane.graphics.* methods. | Kitty graphics is not enabled (experimental.kitty_graphics). |
invalid_layer_id | pane.graphics.set, pane.graphics.clear, pane.graphics.stream.*. | layer_id is empty, longer than 64 characters, or contains characters other than alphanumeric, ., _, :, -. |
invalid_image | pane.graphics.set, pane.graphics.stream.direct. | Zero dimensions, mismatched data length, invalid base64, empty data, or unsupported format for direct frames. |
image_too_large | pane.graphics.set, pane.graphics.stream.set. | Inline image data exceeds the byte limit. |
layer_limit | pane.graphics.set, pane.graphics.stream.open. | The per-pane or total layer cap is reached, or host image ids are exhausted. |
graphics_budget_exceeded | pane.graphics.set, pane.graphics.stream.direct. | The inline memory budget for pane graphics is reached. |
stream_conflict | pane.graphics.set, pane.graphics.clear, pane.graphics.stream.*. | The target layer already has an active stream owned by another owner, or a non-stream method touched a streamed layer. |
stream_closed | pane.graphics.stream.set, pane.graphics.stream.direct. | The stream owner does not match, the stream is not active, or the layer has no stream. |
invalid_stream | pane.graphics.stream, pane.graphics.stream.open. | pane.graphics.stream was sent as a normal request, or stream.open was called with an empty owner. |
invalid_frame_file | pane.graphics.stream.direct. | The referenced file frame could not be leased or read. |
cell_size_unavailable | pane.graphics.info. | The host cell size is unknown, so pixel geometry cannot be reported. |
invalid_request | All. | The request envelope or parameter shape failed to deserialize. id may be empty. |
Method-specific codes are not a closed enum. Consumers must retain unknown codes.
agent methods
agent.list
List every agent terminal across all workspaces and tabs.
| Method | Params | Result |
|---|---|---|
agent.list | none | AgentList { agents: AgentInfo[] } |
agents contains one AgentInfo per pane whose terminal is marked as an agent terminal, in workspace then tab then traversal order. Empty when no agents exist.
{ "id": "ex:agent-list", "method": "agent.list", "params": {} }{
"id": "ex:agent-list",
"result": {
"type": "agent_list",
"agents": [
{
"terminal_id": "term_1943cd2b11a1",
"name": "builder",
"agent": "claude",
"agent_status": "working",
"workspace_id": "w1943cd2b11a1",
"tab_id": "w1943cd2b11a1:t1",
"pane_id": "w1943cd2b11a1:p2",
"focused": false,
"revision": 12
}
]
}
}agent.get
Resolve a target and return its AgentInfo. See Agent targets for resolution and ambiguity behavior.
| Method | Params | Result |
|---|---|---|
agent.get | AgentTarget (target: string) | AgentInfo { agent } |
Errors: agent_not_found, agent_target_ambiguous.
agent.focus
Resolve a target, switch the active workspace and tab to the containing pane, focus the pane, set the server mode to Terminal, mark the active tab as seen, and return the resulting AgentInfo.
| Method | Params | Result |
|---|---|---|
agent.focus | AgentTarget | AgentInfo { agent } |
agent.focus always performs the focus switch even if the pane was already focused. The returned focused reflects post-call state.
Errors: agent_not_found, agent_target_ambiguous.
agent.start
Start a named agent terminal. The new terminal is registered with the supplied name as both its agent name and manual label; the name must be unique across agent terminals.
| Method | Params | Required | Default |
|---|---|---|---|
agent.start | AgentStartParams | — | — |
| Property | Type | Required | Default | Behavior |
|---|---|---|---|---|
name | string | yes | — | Trimmed; empty becomes invalid_agent_name. Must not duplicate an existing agent name or agent_name_taken is returned. |
argv | string[] | yes | — | Command and arguments. Empty produces invalid_agent_argv. |
cwd | string | null | no | inherited placement or local cwd | Legacy local working directory. Cannot be combined with location. |
location | ResourceLocation | null | no | inherited placement | Explicit execution host and host-native path. |
workspace_id | string | null | no | active workspace | Selects the target workspace. When tab_id is also set, both must reference the same workspace or agent_placement_conflict is returned. |
tab_id | string | null | no | active tab of the resolved workspace | Split from this tab's focused pane. |
split | "right" | "down" | null | no | "right" | Direction of the split from the focused pane. |
focus | boolean | no | false | Focus the new pane after creation and switch the active workspace/tab. |
env | object | no | {} | Extra environment variables. See env behavior. |
Placement rules:
- No workspaces exist: create a new workspace with the agent as the root pane.
- No
tab_id, noworkspace_id, and workspaces exist: split the focused pane of the active workspace, using the active tab. workspace_idset, notab_id: split the focused pane of the active tab of that workspace.tab_idset: split the focused pane of that tab. Ifworkspace_idis also set, it must match the tab's workspace.
When neither cwd nor location is supplied, a split agent inherits the source pane's complete resource location. A root agent without a placement target uses the local process cwd.
Result: AgentStarted { agent: AgentInfo, argv: string[] }. argv echoes the supplied command after validation.
Errors: invalid_agent_name, invalid_agent_argv, agent_name_taken, agent_placement_not_found, agent_placement_conflict, invalid_agent_location, agent_start_failed, invalid_env. Supplying both cwd and location returns agent_placement_conflict.
{
"id": "ex:start",
"method": "agent.start",
"params": {
"name": "builder",
"argv": ["claude", "--no-tips"],
"cwd": "/repo",
"split": "down",
"focus": true,
"env": { "CLAUDE_BUDGET": "200" }
}
}{
"id": "ex:start",
"result": {
"type": "agent_started",
"agent": {
"terminal_id": "term_1943cd2b22b2",
"name": "builder",
"agent_status": "unknown",
"screen_detection_skipped": false,
"workspace_id": "w1943cd2b11a1",
"tab_id": "w1943cd2b11a1:t1",
"pane_id": "w1943cd2b11a1:p3",
"focused": true,
"revision": 0
},
"argv": ["claude", "--no-tips"]
}
}agent.read
Read output from the terminal behind an agent target. Identical to pane.read for source, format, and line handling, with these differences:
targetis an agent target, not apane_id. See Agent targets.- The result
pane_idfalls back to the originaltargetstring when a public pane id cannot be resolved. revisionis always0; do not treat it as a content revision.truncatedistruewhen older requested snapshot rows were omitted to fit thelineslimit, andfalsewhen the returned text contains every requested row. See pane.read for the source-specific truncation rules.
| Property | Type | Required | Default |
|---|---|---|---|
target | string | yes | — |
source | ReadSource | yes | — |
lines | integer | null | no | 80, clamped to 1000 |
format | ReadFormat | no | "text" |
strip_ansi | boolean | no | true |
See pane.read for the source matrix and line semantics. Errors: agent_not_found, agent_target_ambiguous.
agent.send_keys
Send named key events to a ready agent runtime. The target must resolve to a recognized agent and the pane runtime must still host that agent; use pane.send_keys when the target is an ordinary terminal rather than an agent-aware runtime.
| Property | Type | Required |
|---|---|---|
target | string | yes |
keys | string[] | yes |
Keys use the same parser and aliases as pane.send_keys. All keys are parsed before the encoded bytes are written. Result: Ok {}.
Errors: agent_not_found, agent_target_ambiguous, agent_not_ready, invalid_key, agent_send_keys_failed.
agent.prompt
Submit non-empty prompt text through the target agent runtime's paste-aware text encoder, then send Enter. This is agent-aware submission, not a raw terminal write: the target must resolve to a recognized agent and the runtime must still host it.
| Property | Type | Required | Default |
|---|---|---|---|
target | string | yes | — |
text | string | yes | — |
wait | object | null | no | null |
wait.until | AgentStatus[] | no | idle, done, or blocked |
wait.timeout_ms | integer | null | no | no deadline |
Without wait, the result is AgentPrompted { agent } immediately after the submission is queued. With wait, the connection stays open until the same pane first shows that the prompt took effect and then reaches one of the requested statuses. If no state change is observed during the bounded prompt-effect phase, the request returns agent_prompt_stalled; if the target disappears or changes identity, it returns agent_not_running. A client disconnect or server shutdown cancels the wait.
Errors: empty_agent_prompt, agent_not_found, agent_target_ambiguous, agent_not_ready, agent_prompt_failed, agent_prompt_stalled, agent_not_running, timeout.
agent.wait
Hold the connection open until an agent reaches a requested status. An empty until list means idle, done, or blocked. If the current status already matches, the method returns immediately; otherwise it follows status-change events for the same terminal and pane.
| Property | Type | Required | Default |
|---|---|---|---|
target | string | yes | — |
until | AgentStatus[] | no | idle, done, or blocked |
timeout_ms | integer | null | no | no deadline |
Result: AgentInfo { agent } for the matching state. Closing or replacing the pane, ending the process, or losing its agent identity returns agent_not_running. A client disconnect or server shutdown cancels the wait without leaving a server-side waiter.
Errors: agent_not_found, agent_target_ambiguous, agent_not_running, timeout.
agent.view.set
Replace the requesting client's temporary agent-list view. The override controls filtered and sorted agent rows in the sidebar, mobile switcher, mouse targets, and agent keyboard navigation without changing shared session state or another attached client.
| Property | Type | Required | Default |
|---|---|---|---|
source | string | yes | — |
label | string | null | no | null |
filter | AgentViewFilter | null | no | all agents |
sort | AgentViewSort[] | no | natural workspace/tab/pane order |
Filters compose with all, any, and not, or compare built-in fields and plugin metadata tokens with eq, in, and exists. Sorts accept built-in order, attention, status, agent, seen, and state-sequence fields or plugin tokens. See the generated requests and enums for the exhaustive recursive shapes.
A source beginning with plugin: must name an installed, enabled plugin. Setting a view resets agent-panel and mobile-switcher scroll for that client. Result: AgentView { active: true, source, label }.
Errors: invalid_agent_view, plugin_not_found, plugin_disabled.
agent.view.clear
Clear the requesting client's temporary agent-list view. With no source, any active override is removed. With source, the override is removed only when its normalized source matches; a nonmatching source is an accepted no-op. The client-local agent-panel and mobile-switcher scroll positions reset only when the active view changes.
| Property | Type | Required | Default |
|---|---|---|---|
source | string | null | no | clear any source |
Result: AgentView { active, source, label } describing the remaining client-local override.
Errors: invalid_agent_view.
agent.rename
Set or clear the agent name (and manual label) on the terminal behind an agent target. The terminal identity, pane, and process are unchanged.
| Property | Type | Required | Default |
|---|---|---|---|
target | string | yes | — |
name | string | null | no | clears the agent name |
The supplied name is trimmed. An empty string or null clears both the agent name and the manual label. A non-empty name that conflicts with another agent terminal returns agent_name_taken. To clear the name while preserving the terminal, pass null explicitly.
Result: AgentInfo { agent }.
Errors: agent_not_found, agent_target_ambiguous, agent_name_taken.
agent.explain
Return a debug object describing how agent detection resolved for the target's terminal. The result is a free-form serde_json::Value shaped by the detection module, not the schema enum shape used by other methods.
| Method | Params | Result |
|---|---|---|
agent.explain | AgentTarget | AgentExplain { explain: object } |
Two outcomes:
- The terminal has full lifecycle hook authority active: the server returns a stable object that reports
agent,state,screen_detection_skipped: true,screen_detection_skip_reason: "full_lifecycle_hook_authority", and nulls for manifest, remote version, rule, and visibility fields. Detection input is not consulted because the hook owns state reporting. - Otherwise the server consults the pane's detection text, OSC title, and OSC progress and runs the manifest detection pipeline. The returned object includes
agent,state,matched_rule,visible_idle,visible_blocker,visible_working,screen_detection_skipped,evaluated_rules, and related fields. Callers should treat the object as opaque debug data and not depend on individual keys across versions.
If the terminal has neither an effective known agent nor a detected agent, agent.explain returns agent_explain_unavailable and does not consult detection input.
Errors: agent_not_found, agent_target_ambiguous, agent_explain_unavailable.
pane methods
Input and read variants
Three pane methods send input, with different encodings:
| Method | Encodes | Order |
|---|---|---|
pane.send_text | Raw bytes from text. | text only. |
pane.send_keys | Each entry in keys as a key combo via the terminal's key encoder. | keys in array order. |
pane.send_input | text first (encoded as in pane.send_text), then keys. | text then keys. |
pane.send_text
| Property | Type | Required |
|---|---|---|
pane_id | string | yes |
text | string | yes |
text is written as raw UTF-8 bytes. No bracketed paste wrapping is applied. To paste content through a terminal that has bracketed paste enabled, use pane.send_input so the runtime wraps the text field as a paste sequence.
Result: Ok {}. Write failure returns pane_send_failed with the runtime error message.
Errors: pane_not_found, pane_send_failed.
pane.send_keys
| Property | Type | Required |
|---|---|---|
pane_id | string | yes |
keys | string[] | yes |
Each entry is a key combo string parsed by the config key parser, with two aliases: C-c and c-c normalize to ctrl+c, and a bare + parses to the plus key. Each parsed KeyEvent is encoded for the target terminal and written in array order. The first unparseable key produces invalid_key and no later keys are sent.
Result: Ok {}.
Errors: pane_not_found, invalid_key, pane_send_failed.
pane.send_input
| Property | Type | Required | Default |
|---|---|---|---|
pane_id | string | yes | — |
text | string | no | "" |
keys | string[] | no | [] |
Both fields are optional but at least one should be supplied for a useful request. When the terminal's input state has bracketed paste active, the text field is wrapped as \x1b[200~<text>\x1b[201~; otherwise it is sent as raw bytes. Keys are then sent in array order with the same encoding as pane.send_keys. Each failed write returns pane_send_failed immediately and stops the remaining keys.
Result: Ok {}.
Errors: pane_not_found, invalid_key, pane_send_failed.
pane.read
Read pane content from one of four sources. pane_id is required.
| Property | Type | Required | Default |
|---|---|---|---|
pane_id | string | yes | — |
source | ReadSource | yes | — |
lines | integer | null | no | 80, clamped to a maximum of 1000 |
format | ReadFormat | no | "text" |
strip_ansi | boolean | no | true |
Read sources:
source | Content |
|---|---|
"visible" | The current terminal viewport. Line wrapping is applied. Format "ansi" returns the raw ANSI bytes of the viewport; "text" returns the visible text with ANSI stripped. |
"recent" | Up to lines recent wrapped lines from the scrollback. Format "ansi" returns recent ANSI; "text" returns recent text. |
"recent_unwrapped" | Up to lines recent logical lines with wrapping undone. Format "ansi" returns the unwrapped ANSI stream; "text" returns unwrapped text. |
"detection" | The detection text the agent state machine consumes. Always returned as text regardless of format; the ansi format has no effect on this source. |
Line handling: lines.unwrap_or(80).min(1000) for both agent.read and pane.read. The default is 80 lines even though the field is otherwise optional. Values above 1000 are clamped to 1000; values of 0 or below are sent to the pane runtime and may return an empty string.
strip_ansi is declared on the request but the current pane.read and agent.read handlers do not strip ANSI separately from the format selection. The text format returns pre-stripped text per source; the ansi format preserves escape sequences regardless of strip_ansi. Treat strip_ansi: true plus format: "ansi" as producing ANSI output; rely on format to choose stripped versus raw content.
Result: PaneRead { read: PaneReadResult } with pane_id echoed, workspace_id, tab_id, source, format, text, revision, and truncated. revision is always 0; do not depend on it for change detection. truncated is true when older requested snapshot rows were omitted to fit the lines limit, and false when the returned text contains every requested row. For "visible" and "detection" sources, truncation is determined by the requested lines count against the total available lines. For "recent" and "recent_unwrapped" sources, truncation is true when the terminal's total row count exceeds the requested line count. An alternate-screen wait read reports truncated as true when the retained history did not reach the top of the scrollback or exceeded the requested row count.
Errors: pane_not_found.
{
"id": "ex:read",
"method": "pane.read",
"params": { "pane_id": "w1943cd2b11a1:p2", "source": "recent", "lines": 2, "format": "text" }
}{
"id": "ex:read",
"result": {
"type": "pane_read",
"read": {
"pane_id": "w1943cd2b11a1:p2",
"workspace_id": "w1943cd2b11a1",
"tab_id": "w1943cd2b11a1:t1",
"source": "recent",
"format": "text",
"text": "$ cargo test\nTests: 0 failed in 3.4s\n",
"revision": 0,
"truncated": true
}
}
}pane.wait_for_output
Block on a pane until its output matches a substring or regex. This is a streaming method: the server responds once, either with a match or with an error, after polling the pane output.
| Property | Type | Required | Default |
|---|---|---|---|
pane_id | string | yes | — |
source | ReadSource | yes | — |
match | OutputMatch | yes | — |
lines | integer | null | no | 80 (passed through to the underlying read) |
timeout_ms | integer | null | no | no timeout (waits indefinitely until match or disconnect) |
strip_ansi | boolean | no | true |
match is a tagged object:
match.type | Other field | Behavior |
|---|---|---|
"substring" | value: string | The first line containing value anywhere is returned as matched_line. |
"regex" | value: string | The first line matching the regex is returned. An unparseable pattern returns invalid_regex immediately with the regex error message. |
Match behavior:
- The wait loop issues internal
pane.readrequests withformat: "text"against a derived source."recent"is rewritten to"recent_unwrapped"before reading;"visible","recent_unwrapped", and"detection"pass through unchanged. This means a"recent"source unwraps lines before matching, so a match pattern that expects wrapped continuation on a single line will not match. - Matching is performed per line with the
lines()iterator. A multi-line match does not span lines. - On match, the server returns
OutputMatched { pane_id, revision, matched_line, read }.revisionis copied from the read result (currently always0).matched_lineis the matched line string, ornullif no line matched at the time of return (the success path always includes a matched line; thenullvariant exists for subscription emission). - On timeout, the server returns
errorwithcode: "timeout"and messagetimed out waiting for output match. - If an underlying read returns an error, the wait handler rewrites the error to echo the outer request
idand returns it. - If the client disconnects during the wait (
should_stop_connectionis true), the handler returns no response to the client and stops polling.
timeout_ms is treated as a deadline measured from the start of the wait. null means wait indefinitely; the connection stays open until a match is found or the socket breaks.
Errors: invalid_regex, timeout, plus any error from the underlying pane.read (typically pane_not_found), with id rewritten to the outer request id.
{
"id": "ex:wait",
"method": "pane.wait_for_output",
"params": {
"pane_id": "w1943cd2b11a1:p2",
"source": "recent",
"match": { "type": "substring", "value": "Tests: 0 failed" },
"timeout_ms": 30000
}
}{
"id": "ex:wait",
"result": {
"type": "output_matched",
"pane_id": "w1943cd2b11a1:p2",
"revision": 0,
"matched_line": "Tests: 0 failed in 3.4s",
"read": {
"pane_id": "w1943cd2b11a1:p2",
"workspace_id": "w1943cd2b11a1",
"tab_id": "w1943cd2b11a1:t1",
"source": "recent_unwrapped",
"format": "text",
"text": "$ cargo test\nTests: 0 failed in 3.4s\n",
"revision": 0,
"truncated": false
}
}
}pane.list
List panes, optionally filtered to a workspace.
| Property | Type | Required | Default |
|---|---|---|---|
workspace_id | string | null | no | all workspaces |
With workspace_id omitted, panes from all workspaces are returned in workspace then tab then traversal order. With workspace_id set to an unknown workspace, the result is workspace_not_found.
Result: PaneList { panes: PaneInfo[] }. Each entry is a full PaneInfo including agent_status, cwd, foreground_cwd, label, scroll, and revision.
Errors: workspace_not_found.
pane.get
Return the PaneInfo for a single pane.
| Property | Type | Required |
|---|---|---|
pane_id | string | yes |
Result: PaneInfo { pane }.
Errors: pane_not_found.
pane.current
Return the PaneInfo for the current pane. The optional caller_pane_id overrides resolution: when present, the server parses and returns that pane, not the active one. When the resolved pane does not exist, the error is pane_not_found with the generic message pane not found.
| Property | Type | Required | Default |
|---|---|---|---|
caller_pane_id | string | null | no | focused pane of the active workspace |
Result: PaneCurrent { pane }.
Errors: pane_not_found.
pane.focus
Focus a specific pane, switch the active workspace and tab to the containing one, mark the active tab as seen, and set the server mode to Terminal.
| Property | Type | Required |
|---|---|---|
pane_id | string | yes |
Result: PaneInfo { pane }.
Errors: pane_not_found.
pane.rename
Set or clear the manual label on a pane. The terminal identity and agent name are unchanged. label is trimmed; empty or null clears the manual label. The pane keeps its agent, title, and display_agent fields.
| Property | Type | Required | Default |
|---|---|---|---|
pane_id | string | yes | — |
label | string | null | no | clears the label |
Result: PaneInfo { pane }.
Errors: pane_not_found.
pane.split
Split a pane into two, creating a new shell pane beside the existing one. The new pane runs the default shell (not an arbitrary command); use agent.start or layout apply for custom commands.
| Property | Type | Required | Default | Behavior |
|---|---|---|---|---|
target_pane_id | string | null | no | focused pane of resolved workspace | The pane to split. |
workspace_id | string | null | no | active workspace | Used only when target_pane_id is omitted, to select the workspace whose focused pane is split. |
direction | "right" | "down" | yes | — | right splits horizontally (new pane on the right); down splits vertically (new pane below). |
ratio | number | null | no | implementation default near 0.5 | First-pane ratio of the new split, clamped to [0.1, 0.9]. |
cwd | string | null | no | source pane path | Legacy local working directory. Cannot be combined with location. |
location | ResourceLocation | null | no | source pane location | Explicit execution host and host-native path. |
env | object | no | {} | Extra environment variables. See env behavior. |
focus | boolean | no | false | Focus the new pane and switch the active workspace and tab. |
Target resolution order: explicit target_pane_id, then workspace_id's focused pane, then resolve_optional_pane(None) (the focused pane of the active workspace). Failure returns pane_not_found with target_pane_id or active pane as the target string in the message.
The new pane is registered with a public pane id. The server removes any alias shadowed by the new pane id, emits a pane_created event, and emits layout_updated for the containing tab. Terminal identity: the new pane gets a new terminal process; the original pane is unchanged.
Result: PaneInfo { pane } for the new pane.
Errors: pane_not_found, invalid_params, invalid_env, pane_split_failed. Supplying both cwd and location returns invalid_params.
{
"id": "ex:split",
"method": "pane.split",
"params": { "direction": "down", "ratio": 0.4, "cwd": "/repo", "focus": true }
}{
"id": "ex:split",
"result": {
"type": "pane_info",
"pane": {
"pane_id": "w1943cd2b11a1:p4",
"terminal_id": "term_1943cd2b33c3",
"workspace_id": "w1943cd2b11a1",
"tab_id": "w1943cd2b11a1:t1",
"focused": true,
"agent_status": "idle",
"revision": 0
}
}
}pane.close
Close a pane. When the pane is the last pane in its tab, the tab closes. The configured close policy can return confirmation_required.
| Property | Type | Required |
|---|---|---|
pane_id | string | yes |
On success the server emits pane_closed for the pane, and when applicable workspace_closed plus layout_updated for the remaining tab. The result is Ok {} with no payload. Use pane.list or events.subscribe to observe the closed state.
Errors: pane_not_found, confirmation_required.
pane.swap
Swap two panes within a tabulated layout. Two modes, mutually exclusive:
- Directional: supply
directionand optionallypane_id(the source). The target is the nearest pane in that direction. - Explicit: supply
source_pane_idandtarget_pane_id.
Supplying both a direction and explicit ids, or supplying neither, returns invalid_pane_swap with message provide either direction with optional pane_id, or source_pane_id and target_pane_id.
| Property | Type | Required | Default |
|---|---|---|---|
pane_id | string | null | no | active pane (directional mode source) |
direction | "left" | "right" | "up" | "down" | null | no | — |
source_pane_id | string | null | no | — |
target_pane_id | string | null | no | — |
Directional mode: resolve the source from pane_id (or the focused pane of the active workspace when omitted), find the neighbor in direction, and swap layout positions. No neighbor produces changed: false and reason: "no_neighbor".
Explicit mode: both ids must parse to live panes in the same tab. Same-source-target produces reason: "same_pane". Panes in different tabs produce reason: "cross_tab"; this is not an error in the wire protocol but the swap does not occur (changed: false). Missing source or target produces reason: "not_found".
Result: PaneSwap { swap: PaneSwapResult } with changed, optional reason, source_pane_id, optional target_pane_id, focused_pane_id, and the post-swap layout. When changed is true, the server emits layout_updated.
Errors: invalid_pane_swap, pane_not_found (directional source).
pane.move
Move a pane to a destination: an existing tab (split into it), a new tab in an existing workspace, or a new workspace.
| Property | Type | Required | Default | Behavior |
|---|---|---|---|---|
pane_id | string | yes | — | The pane to move. |
destination | PaneMoveDestination | yes | — | Tagged union; see below. |
focus | boolean | no | false | Focus the moved pane in its destination and switch the active workspace/tab. |
destination variants:
destination.type | Fields | Behavior |
|---|---|---|
"tab" | tab_id, target_pane_id?, split: SplitDirection, ratio?: number | Move into an existing tab. target_pane_id defaults to the tab's focused pane. ratio defaults to 0.5 and is clamped to [0.1, 0.9] at insert time. split is the direction used to insert the moved pane beside target_pane_id. |
"new_tab" | workspace_id?, label? | Create a new tab in the existing (or explicitly selected) workspace and move the pane into it as the root. |
"new_workspace" | label?, tab_label? | Create a new workspace and tab and move the pane into it. The new workspace's cwd is taken from the moved pane's terminal cwd. |
Identity consequences:
- The moved pane keeps its underlying pane id and terminal id across workspace boundaries. The public
pane_idchanges when the move crosses workspaces. The previous public id is registered as an alias of the new id, so requests that use the previous id still resolve to the moved pane. - When the source workspace becomes empty as a result of the move, it is closed.
closed_workspace_idis set to the previous workspace id,closed_tab_idis set when the source tab closes, and the server emitsworkspace_closed,tab_closedas appropriate. - A move to a tab in the same workspace and tab as the source is a no-op and returns
changed: falsewithreason: "same_tab". - A move to or from a zoomed tab is a no-op and returns
changed: falsewithreason: "zoomed_tab". The zoom state protects the destination, so a caller that wants the move to take effect must unzoom first. - Cross-workspace moves emit
pane_moved, plusworkspace_created,tab_created,tab_closed,workspace_closedas applicable, pluslayout_updatedfor both the source (when it still exists) and destination tabs.
pane_id in destination.tab and the top-level pane_id must both be valid. target_pane_id that does not live in the destination tab returns target_pane_not_found with target pane <raw> is not in tab <tab_id>.
Result: PaneMove { move_result: PaneMoveResult } with changed, optional reason, previous_pane_id, previous_workspace_id, previous_tab_id, pane: PaneInfo, optional source_layout, target_layout, optional created_workspace, optional created_tab, optional closed_workspace_id, optional closed_tab_id, and focused_pane_id.
Errors: pane_not_found, tab_not_found, workspace_not_found, target_pane_not_found, pane_layout_unavailable, pane_move_failed.
{
"id": "ex:move",
"method": "pane.move",
"params": {
"pane_id": "w1943cd2b22b2:p1",
"destination": {
"type": "tab",
"tab_id": "w1943cd2b11a1:t1",
"target_pane_id": "w1943cd2b11a1:p1",
"split": "right",
"ratio": 0.3
},
"focus": true
}
}{
"id": "ex:move",
"result": {
"type": "pane_move",
"move_result": {
"changed": true,
"reason": null,
"previous_pane_id": "w1943cd2b22b2:p1",
"previous_workspace_id": "w1943cd2b22b2",
"previous_tab_id": "w1943cd2b22b2:t1",
"pane": {
"pane_id": "w1943cd2b11a1:p2",
"terminal_id": "term_1943cd2b44d4",
"workspace_id": "w1943cd2b11a1",
"tab_id": "w1943cd2b11a1:t1",
"focused": true,
"agent_status": "idle",
"revision": 0
},
"source_layout": null,
"target_layout": {
"workspace_id": "w1943cd2b11a1",
"tab_id": "w1943cd2b11a1:t1",
"zoomed": false,
"focused_pane_id": "w1943cd2b11a1:p2",
"panes": []
},
"created_workspace": null,
"created_tab": null,
"closed_workspace_id": "w1943cd2b22b2",
"closed_tab_id": "w1943cd2b22b2:t1",
"focused_pane_id": "w1943cd2b11a1:p2"
}
}
}pane.zoom
Zoom a tab to show only the target pane, or manipulate the zoom state.
| Property | Type | Required | Default |
|---|---|---|---|
pane_id | string | null | no | focused pane of the active workspace |
mode | "toggle" | "on" | "off" | no | "toggle" |
mode behavior:
"toggle": invert the tab's zoom state. No-op withreason: "single_pane"when the tab only contains one pane."on": zoom in. No-op withreason: "single_pane"for single-pane tabs,reason: "already_zoomed"when already zoomed."off": zoom out. No-op withreason: "already_unzoomed"when not zoomed.
Focusing: the target pane is focused even when the zoom state does not change. focus_changed captures this independently; changed is true if either the zoom or the focus changed. zoom_changed reports only the zoom transition.
Result: PaneZoom { zoom: PaneZoomResult } with changed, zoom_changed, focus_changed, optional reason, pane_id, focused_pane_id, zoomed (the resulting zoom state), and the current layout.
Errors: pane_not_found, pane_layout_unavailable.
pane.layout
Return the layout snapshot for the tab that contains the resolved pane.
| Property | Type | Required | Default |
|---|---|---|---|
pane_id | string | null | no | focused pane of the active workspace |
The snapshot's zoomed reflects the containing tab's zoom flag. The area falls back to 80x24 when the recorded terminal area is zero (headless or pre-layout). When the layout cannot be produced, returns pane_layout_unavailable.
Result: PaneLayout { layout: PaneLayoutSnapshot } with workspace_id, tab_id, zoomed, area, focused_pane_id, panes, and splits.
Errors: pane_not_found, pane_layout_unavailable.
pane.process_info
Return process information for the runtime behind the resolved pane.
| Property | Type | Required | Default |
|---|---|---|---|
pane_id | string | null | no | focused pane of the active workspace |
The result contains:
pane_id: the resolved public pane id.shell_pid: the runtime's child pid when non-zero, elsenull.foreground_process_group_id: the foreground process group id when a foreground job was detected, elsenull.tty: alwaysnullin the current implementation; reserved for future use.foreground_processes: the processes in the detected foreground job. Each entry haspid,name, optionalargv0, optionalargv, optionalcmdline, and optionalcwd(resolved from the process; absence is not an error). Empty when no foreground job is detected.
shell_pid is null before the shell has spawned and after it exits. foreground_processes is the live foreground job at request time, so a busy shell can return zero entries between commands.
Result: PaneProcessInfo { process_info: PaneProcessInfo }.
Errors: pane_not_found.
pane.neighbor
Return the nearest pane in a direction from a source pane and the containing tab's layout snapshot.
| Property | Type | Required | Default |
|---|---|---|---|
pane_id | string | null | no | focused pane of the active workspace |
direction | "left" | "right" | "up" | "down" | yes | — |
The neighbor is computed geometrically from the layout rect of the source pane within the tab; it does not consider panes in other tabs. When no pane lies in that direction, neighbor_pane_id is null and the response is still a success.
Result: PaneNeighbor { neighbor: PaneNeighborResult } with pane_id (the source), direction, optional neighbor_pane_id, and the current layout.
Errors: pane_not_found, pane_layout_unavailable.
pane.resize
Resize the split adjacent to the resolved pane in a direction by a fraction of the available space.
| Property | Type | Required | Default | Behavior |
|---|---|---|---|---|
pane_id | string | null | no | focused pane of the active workspace | — |
direction | "left" | "right" | "up" | "down" | yes | — | left/right adjust horizontal splits; up/down adjust vertical splits. |
amount | number | null | no | 0.05 | Fraction of the terminal area to shift the split by. Non-finite values fall back to 0.05. abs is applied and the result is clamped to <= 0.5. Direction semantics below. |
No-op conditions:
- The containing tab has only one pane:
changed: false,reason: "unchanged". - The split ratio does not change after clamping (e.g., already at
0.1/0.9bounds):changed: false,reason: "unchanged".
Direction interpretation: the direction selects which split to resize, not which way the boundary moves. For a given direction, the server finds the adjacent split in that direction of the focused pane and applies amount toward growing or shrinking the source pane region. The exact grow/shrink sign is internal to the layout engine; callers should treat direction as selecting the target split and amount as the magnitude, then read the resulting layout.splits[].ratio to confirm the effect.
Result: PaneResize { resize: PaneResizeResult } with changed, optional reason, pane_id, focused_pane_id, and the resulting layout. When changed, the server emits layout_updated and schedules a session save.
Errors: pane_not_found, pane_layout_unavailable.
pane.edges
Report whether the resolved pane touches each edge of the terminal area. As the read-side counterpart of pane.neighbor, it tells you a pane is on the border without naming a neighbor.
| Property | Type | Required | Default |
|---|---|---|---|
pane_id | string | null | no | focused pane of the active workspace |
The result reports left, right, up, down booleans for whether the pane's layout rect abuts the corresponding edge of the tab's terminal area. Edge comparisons use <= against the tab area origin and >= for the far edge, so a full-width pane returns left: true and right: true together. The response includes the current layout snapshot, the same shape returned by pane.layout.
Result: PaneEdges { edges: PaneEdgesResult } with pane_id, left, right, up, down, and layout.
Errors: pane_not_found, pane_layout_unavailable.
pane.focus_direction
Focus the nearest pane in a direction from a source pane. Unlike pane.focus, this navigates geometrically within the current tab; unlike pane.neighbor, it also changes focus.
| Property | Type | Required | Default |
|---|---|---|---|
pane_id | string | null | no | focused pane of the active workspace |
direction | "left" | "right" | "up" | "down" | yes | — |
When a neighbor exists in direction, the server focuses that pane, switches the active tab to the containing tab, sets the server mode to Terminal, and returns changed: true. When no pane lies in that direction, the server leaves focus unchanged and returns changed: false with reason: "no_neighbor". The containing tab's layout is returned in both cases. When the focus changes, the server emits layout_updated.
Result: PaneFocusDirection { focus: PaneFocusDirectionResult } with changed, optional reason, source_pane_id, optional focused_pane_id, and the current layout.
Errors: pane_not_found, pane_layout_unavailable.
Pane graphics
Six methods place, clear, inspect, and stream images on pane surfaces using the Kitty graphics protocol. All require experimental.kitty_graphics to be enabled; a disabled feature returns feature_disabled. Layers are addressed by layer_id, which defaults to "primary" and must contain 1 to 64 characters from A-Z, a-z, 0-9, ., _, :, -. Each pane holds at most 16 layers; the server holds at most 64 layers total. Inline image data across all layers is capped at 64 MiB.
pane.graphics.set
Place an inline image on a pane layer. The image data is sent in the request body.
| Property | Type | Required | Default | Behavior |
|---|---|---|---|---|
pane_id | string | yes | — | — |
layer_id | string | null | no | "primary" | Validated per the layer-id rules above. |
z_index | integer | no | 0 | Stack order within the pane. |
format | PaneGraphicsFormat | yes | — | One of "png", "rgb", "rgba", "bgra". bgra is canonicalized to rgba on the server. |
image_width | integer | yes | — | Pixel width. Must be greater than zero. |
image_height | integer | yes | — | Pixel height. Must be greater than zero. |
data_base64 | string | no | "" | Base64-encoded image data. Decoded when data is absent. Invalid base64 returns invalid_image. |
placement | PaneGraphicsPlacementParams | no | zeros | Optional viewport placement: viewport_col, viewport_row origin and grid_cols, grid_rows cell area. |
data is an internal binary channel used by the stream variants; direct API callers supply data_base64. Exactly one of the two must carry the image. For "rgb", "rgba", and "bgra" formats, the decoded byte length must equal width * height * bytes_per_pixel or invalid_image is returned. "png" has no length contract. Inline data is capped at 512 KiB (PANE_GRAPHICS_SET_MAX_BYTES); larger payloads return image_too_large.
If the target layer has an active stream owned by another caller, the server returns stream_conflict. A stale stream (owner gone) is reclaimed before the check.
Result: Ok {}.
Errors: feature_disabled, pane_not_found, invalid_layer_id, invalid_image, image_too_large, layer_limit, graphics_budget_exceeded, stream_conflict.
pane.graphics.clear
Remove one layer from a pane. With layer_id omitted, clears the "primary" layer.
| Property | Type | Required | Default |
|---|---|---|---|
pane_id | string | yes | — |
layer_id | string | null | no | "primary" |
If the target layer has an active stream, the server returns stream_conflict. A stale stream is reclaimed first. Removing a nonexistent layer is a no-op and still returns Ok {}.
Result: Ok {}.
Errors: feature_disabled, pane_not_found, invalid_layer_id, stream_conflict.
pane.graphics.info
Report the graphics capabilities and visibility state for a pane.
| Property | Type | Required |
|---|---|---|
pane_id | string | yes |
pane_visible is true only when the pane is on the active workspace, in the active tab, and either not hidden by zoom or is the zoomed pane itself. The result also reports the host cell size in pixels. When the host cell size is unknown, the server returns cell_size_unavailable.
When direct file-frame delivery is available, the result includes file_frame_directory (the temp directory the server leases frames from), file_frame_formats (["rgba", "bgra"]), file_frame_max_bytes (16 MiB), file_frame_damage: true, and file_frame_transport: "direct-kitty". When direct delivery is unavailable, file_frame_directory is null, file_frame_formats is empty, and file_frame_max_bytes is null.
max_layers_per_pane is always 16. pixel_mouse reports whether pixel-precise mouse input is available.
Result: PaneGraphicsInfo with cell_width_px, cell_height_px, pane_visible, file_frame_directory, file_frame_formats, file_frame_max_bytes, file_frame_damage, max_layers_per_pane, pixel_mouse, file_frame_transport.
Errors: feature_disabled, pane_not_found, cell_size_unavailable.
pane.graphics.stream
Open a long-lived graphics stream that delivers incremental frames to the server. This method is not dispatched as a normal request: the API server opens the stream internally and rejects any client-sent pane.graphics.stream request with invalid_stream (pane.graphics.stream must be opened by the API server). Clients interact with the stream through stream.open, stream.set, stream.direct, and stream.close.
| Property | Type | Required | Default |
|---|---|---|---|
pane_id | string | yes | — |
layer_id | string | null | no | "primary" |
z_index | integer | no | 0 |
The owner field is set by the server to identify the stream initiator. Stream lifecycle:
stream.openclaims a layer for a namedowner. If the layer already has an active stream, the server returnsstream_conflict. A stale stream (owner gone, not active) is removed first. The server reserves a host image id and marks the slot as stream-owned with an active liveness flag. Result:Ok {}.stream.setupdates the streamed layer with inline image data, using the samePaneGraphicsSetParamsshape aspane.graphics.set. The caller'sownermust match the active stream owner or the server returnsstream_closed(owner mismatch producesstream_conflict). The inline data limit for streamed frames is 16 MiB (PANE_GRAPHICS_STREAM_MAX_BYTES). Result:Ok {}.stream.directsubmits a file-backed frame: the image data is leased from a file on disk identified bypath, avoiding an inline copy. Result:PaneGraphicsFrameAck { sequence, revision }echoing the caller-suppliedsequenceandrevision.stream.closereleases the layer. Only the owningownermay close the stream; a mismatched owner is a silent no-op that still returnsOk {}. The server removes the slot and marks the graphics state changed.
When a stream is active, pane.graphics.set and pane.graphics.clear on the same layer return stream_conflict. The liveness flag is cleared when the stream owner disconnects, at which point the slot becomes stale and the next set, clear, or stream.open reclaims it.
Errors for stream.open: feature_disabled, pane_not_found, invalid_layer_id, invalid_stream (empty owner), stream_conflict, layer_limit.
Errors for stream.set: feature_disabled, pane_not_found, invalid_layer_id, invalid_image, image_too_large, layer_limit, graphics_budget_exceeded, stream_closed, stream_conflict.
Errors for stream.close: pane_not_found, invalid_layer_id.
pane.graphics.stream.direct
Submit a file-backed frame to an active stream. The image data is read from a file on disk rather than sent inline, allowing large frames to bypass the inline memory budget.
| Property | Type | Required | Default | Behavior |
|---|---|---|---|---|
pane_id | string | yes | — | — |
layer_id | string | null | no | "primary" | Validated per the layer-id rules. |
z_index | integer | no | 0 | Stack order. |
owner | string | yes | — | Must match the active stream owner. |
format | PaneGraphicsFormat | yes | — | Only "rgba" and "bgra" are accepted for direct frames; other formats return invalid_image. bgra is canonicalized to rgba. |
image_width | integer | yes | — | Pixel width. Must be greater than zero. |
image_height | integer | yes | — | Pixel height. Must be greater than zero. |
path | string | yes | — | Filesystem path to the frame file. The server leases the file and validates its length against the expected byte count. |
sequence | integer | yes | — | Caller-supplied frame sequence, echoed in the ack. |
revision | integer | yes | — | Caller-supplied revision, echoed in the ack. |
placement | PaneGraphicsPlacementParams | no | zeros | Optional viewport placement. |
The expected byte count is width * height * 4 for RGBA/BGRA. A mismatched file length returns invalid_frame_file. When the primary layer and direct delivery are available, the frame is held as a direct lease; otherwise the file is copied into inline memory, subject to the inline budget (graphics_budget_exceeded on overflow).
Result: PaneGraphicsFrameAck { sequence, revision }.
Errors: feature_disabled, pane_not_found, invalid_layer_id, stream_closed, invalid_image, invalid_frame_file, graphics_budget_exceeded.
Portable layouts
layout.export and layout.apply move a tab layout between sessions as a portable tree. They are distinct from pane.layout: pane.layout reports rendered rectangles and split ids for the current terminal size, while layout.export returns commands, working directories, labels, and split structure that can be applied later.
layout.export
Export one tab as a LayoutDescription.
| Property | Type | Required | Default |
|---|---|---|---|
tab_id | string | null | no | active tab |
pane_id | string | null | no | — |
Supply either tab_id or pane_id, not both. A pane selects its containing tab. Omitting both selects the active tab. Supplying both, an unknown id, or having no active tab returns layout_not_found.
Result: LayoutExport { layout: LayoutDescription }. The tree's pane_id values identify the source panes but are descriptive only when reapplied. Each pane may include its label, current cwd, and original launch command; env is empty because runtime environment is not exported.
{ "id": "layout:export", "method": "layout.export", "params": { "tab_id": "w1943cd2b11a1:t1" } }layout.apply
Create a tab from a portable LayoutNode tree, or atomically replace an existing tab with the new one.
| Property | Type | Required | Default |
|---|---|---|---|
root | LayoutNode | yes | — |
workspace_id | string | null | no | active workspace |
tab_id | string | null | no | — |
tab_label | string | null | no | replaced tab's label, otherwise none |
focus | boolean | no | false |
Use either workspace_id to add a tab or tab_id to replace a tab, never both. When replacing the active tab, the new tab is focused regardless of focus. The server builds the replacement first and closes the old tab only after the new tree succeeds.
LayoutNode is recursive:
{ "type": "pane", "label"?: string, "cwd"?: string, "command"?: string[], "env"?: object }launches one pane. Omittedcommandlaunches the configured shell.command: []is invalid.{ "type": "split", "direction": "right" | "down", "ratio": number, "first": LayoutNode, "second": LayoutNode }splits the available area. Ratios must be finite.
The tree may contain at most 24 panes and have at most 16 levels. Every pane environment uses the same validation as agent.start. A build failure rolls back the partially created tab and its runtimes. Success emits tab_created, one pane_created per pane, and layout_updated; replacement also emits tab_closed.
Result: LayoutApply { layout: LayoutDescription }.
Errors: invalid_target, workspace_not_found, tab_not_found, invalid_layout, invalid_env, layout_apply_failed.
{
"id": "layout:apply",
"method": "layout.apply",
"params": {
"workspace_id": "w1943cd2b11a1",
"tab_label": "review",
"focus": true,
"root": {
"type": "split",
"direction": "right",
"ratio": 0.65,
"first": { "type": "pane", "label": "editor", "cwd": "/repo" },
"second": {
"type": "pane",
"label": "tests",
"cwd": "/repo",
"command": ["sh", "-lc", "pnpm test"]
}
}
}
}Agent hook reporting
Three methods let integration hooks push agent state into a pane's terminal metadata without going through detection. They are normally called by installed agent integration scripts, not by user automation. The server routes their payloads to internal hook events; the response is Ok {} for accepted reports. The server treats these reports as authoritative for the pane's presentation fields (display_agent, title, custom_status, state_labels, tokens) and, when full lifecycle hook authority is active, for the pane's agent_status.
pane.report_agent
Report an agent state transition for a pane.
| Property | Type | Required | Default |
| -------------------- | ----------------------------------------------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------ |
| pane_id | string | yes | — |
| source | string | yes | — | Free-form, conventionally gardn:<agent> or <integration>:<agent>. Used by the resume subsystem to correlate reports. |
| agent | string | yes | — | Agent label; trimmed and normalized when known, kept verbatim otherwise. Empty produces invalid_agent. |
| state | "idle" \| "working" \| "blocked" \| "unknown" | yes | — | Maps directly to the internal AgentState. |
| message | string \| null | no | null | Optional human-oriented message attached to the report. |
| custom_status | string \| null | no | null | Trimmed and filtered to at most 32 non-control characters. |
| seq | integer \| null | no | null | Monotonic sequence used for ordering concurrent reports. |
| agent_session_id | string \| null | no | null | Together with agent_session_path identifies the session for resume; one of the two is the session reference. |
| agent_session_path | string \| null | no | null | — |
| launch_env | object | no | {} | Environment captured at agent launch, forwarded to the resume subsystem. |
Result: Ok {}.
Errors: pane_not_found, invalid_agent.
{
"id": "ex:report-agent",
"method": "pane.report_agent",
"params": {
"pane_id": "w1943cd2b11a1:p2",
"source": "gardn:claude",
"agent": "claude",
"state": "blocked",
"message": "awaiting human review of migration plan",
"custom_status": "needs decision",
"agent_session_id": "sess-abc",
"seq": 7
}
}{ "id": "ex:report-agent", "result": { "type": "ok" } }pane.report_agent_session
Report that an agent session started or resumed for a pane. Same field shape as pane.report_agent minus state, message, and custom_status, plus session_start_source.
| Property | Type | Required | Default |
| ---------------------- | ----------------- | -------- | ------- | ------------------------------------------------------------------------------------------ |
| pane_id | string | yes | — |
| source | string | yes | — |
| agent | string | yes | — |
| seq | integer \| null | no | null |
| agent_session_id | string \| null | no | null |
| agent_session_path | string \| null | no | null |
| launch_env | object | no | {} |
| session_start_source | string \| null | no | null | Normalized by the resume subsystem; conventional values include "launch" and "resume". |
Result: Ok {}.
Errors: pane_not_found, invalid_agent.
pane.release_agent
Tell the server that an agent has released its authority over a pane. The server routes the release to the hook authority subsystem and clears the corresponding agent session.
| Property | Type | Required | Default |
|---|---|---|---|
pane_id | string | yes | — |
source | string | yes | — |
agent | string | yes | — |
agent_session_id | string | null | no | null |
agent_session_path | string | null | no | null |
seq | integer | null | no | null |
Result: Ok {}.
Errors: pane_not_found, invalid_agent.
pane.clear_agent_authority
Clear agent authority for a pane without naming the agent. This is the lighter-weight release: the server drops hook authority metadata regardless of which hook set it.
| Property | Type | Required | Default |
|---|---|---|---|
pane_id | string | yes | — |
source | string | null | no | null |
seq | integer | null | no | null |
source is optional and only forwarded for correlation; it does not affect which authority is cleared.
Result: Ok {}.
Errors: pane_not_found.
pane.report_metadata
Set or clear presentation metadata (title, display agent, custom status, state labels, tokens) on a pane's terminal. This method is the underlying mechanism plugin integrations use to surface richer status than agent-state alone.
| Property | Type | Required | Default | Behavior |
|---|---|---|---|---|
pane_id | string | yes | — | — |
source | string | yes | — | Normalized: trimmed, control chars removed, at most 80 chars, must be non-empty or invalid_metadata_source. |
agent | string | null | no | null | Optional agent label; normalized the same way as a reported agent label. Empty produces invalid_agent. |
applies_to_source | string | null | no | null | When set, the metadata applies to reports from that source. Validated with the same rules as source. |
title | string | null | no | null | Trimmed, control chars removed, at most 80 chars. |
display_agent | string | null | no | null | Same normalization as title. |
custom_status | string | null | no | null | Trimmed, non-control, at most 32 chars. |
state_labels | object | no | {} | Map of state name to label. State names are lowercased and must be one of idle, working, blocked, done, unknown. Unknown state produces invalid_state_label. |
tokens | object | no | {} | Map of token key to optional value. Keys are at most 32 chars, alphanumeric plus _ and -. A request may update at most 16 tokens; a resource may hold at most 32 tokens total. Values, when present, are trimmed to 80 non-control chars. |
clear_title | boolean | no | false | Clear the title. Cannot be combined with title on the same request. |
clear_display_agent | boolean | no | false | Clear display_agent; not combinable with display_agent. |
clear_custom_status | boolean | no | false | Clear custom_status; not combinable with custom_status. |
clear_state_labels | boolean | no | false | Clear all state labels; not combinable with a non-empty state_labels. |
seq | integer | null | no | null | Ordering sequence. |
ttl_ms | integer | null | no | null | Time-to-live for the metadata. Clamped to [1, 86_400_000] ms. Out of range produces invalid_metadata_ttl. |
Validation rules:
- Setting and clearing the same field in one request returns
invalid_metadata_requestwithcannot set and clear the same metadata field. - A request that sets or clears nothing returns
invalid_metadata_requestwithmissing metadata field to set or clear. - Token keys or values failing validation return
invalid_metadata_tokens. - Exceeding the resource cap of 32 total tokens returns
invalid_metadata_tokenswithpane metadata may contain at most 32 tokens.
Result: Ok {}.
Errors: pane_not_found, invalid_agent, invalid_metadata_source, invalid_metadata_ttl, invalid_metadata_tokens, invalid_state_label, invalid_metadata_request.
{
"id": "ex:report",
"method": "pane.report_metadata",
"params": {
"pane_id": "w1943cd2b11a1:p2",
"source": "gardn:claude",
"title": "migration review",
"custom_status": "awaiting input",
"state_labels": { "blocked": "needs decision" },
"tokens": { "branch": "migrate-2", "pr": "1247" },
"seq": 42
}
}{ "id": "ex:report", "result": { "type": "ok" } }Env behavior
agent.start and pane.split accept an env object with these validation rules:
- Keys must not be empty.
- Keys must not contain
=. - Keys must not contain NUL bytes.
- Values must not contain NUL bytes.
Entries are sorted by key before being passed to the spawned process. A violation returns invalid_env with a descriptive message.
The env object is merged on top of the process environment. It does not replace the inherited environment.
Last updated on