Gardn
DocumentationLocal APIReference

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

MethodParamsResult
agent.listnoneAgentList
agent.getAgentTargetAgentInfo
agent.startAgentStartParamsAgentStarted
agent.focusAgentTargetAgentInfo
agent.readAgentReadParamsPaneRead
agent.send_keysAgentSendKeysParamsOk
agent.promptAgentPromptParamsAgentPrompted
agent.waitAgentWaitParamsAgentInfo
agent.renameAgentRenameParamsAgentInfo
agent.view.setAgentViewSetParamsAgentView
agent.view.clearAgentViewClearParamsAgentView
agent.explainAgentTargetAgentExplain

Pane methods

MethodParamsResult
pane.listPaneListParamsPaneList
pane.getPaneTargetPaneInfo
pane.currentPaneCurrentParamsPaneCurrent
pane.splitPaneSplitParamsPaneInfo
pane.closePaneTargetOk
pane.focusPaneTargetPaneInfo
pane.send_textPaneSendTextParamsOk
pane.send_inputPaneSendInputParamsOk
pane.send_keysPaneSendKeysParamsOk
pane.readPaneReadParamsPaneRead
pane.wait_for_outputPaneWaitForOutputParamsOutputMatched
pane.swapPaneSwapParamsPaneSwap
pane.zoomPaneZoomParamsPaneZoom
pane.layoutPaneLayoutParamsPaneLayout
pane.process_infoPaneProcessInfoParamsPaneProcessInfo
pane.movePaneMoveParamsPaneMove
pane.neighborPaneNeighborParamsPaneNeighbor
pane.edgesPaneEdgesParamsPaneEdges
pane.focus_directionPaneFocusDirectionParamsPaneFocusDirection
pane.resizePaneResizeParamsPaneResize
pane.renamePaneRenameParamsPaneInfo
pane.report_agentPaneReportAgentParamsOk
pane.report_agent_sessionPaneReportAgentSessionParamsOk
pane.clear_agent_authorityPaneClearAgentAuthorityParamsOk
pane.release_agentPaneReleaseAgentParamsOk
pane.report_metadataPaneReportMetadataParamsOk
pane.graphics.setPaneGraphicsSetParamsOk
pane.graphics.clearPaneGraphicsClearParamsOk
pane.graphics.infoPaneTargetPaneGraphicsInfo
pane.graphics.streamPaneGraphicsStreamParamsstreaming
pane.graphics.stream.setPaneGraphicsSetParamsOk
pane.graphics.stream.openPaneGraphicsStreamParamsOk
pane.graphics.stream.closePaneGraphicsStreamParamsOk
pane.graphics.stream.directPaneGraphicsDirectParamsPaneGraphicsFrameAck

Layout methods

MethodParamsResult
layout.exportLayoutExportParamsLayoutExport
layout.applyLayoutApplyParamsLayoutApply

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:

  1. If pane_id is present, parse it. A parse miss or a pane that no longer exists returns pane_not_found.
  2. If pane_id is omitted, resolve the active workspace's focused pane.
    • For pane.current, the optional field is named caller_pane_id. When omitted, the server resolves the focused pane of the currently active workspace. When the target pane cannot be resolved, the result is pane_not_found with the generic message pane not found (it does not echo a caller id).
  3. Methods that target a tab (split, move to an existing tab) accept a target_pane_id inside 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:

  1. Match a terminal id exactly. One match resolves; multiple matches are ambiguous.
  2. Parse target as a public pane_id and resolve the terminal attached to that pane.
  3. Match against the terminal's stored agent name or effective agent label.
  4. 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 a layout_updated event (and possibly pane_moved, pane_created, pane_closed, tab_created, tab_closed, workspace_created, workspace_closed for pane.move).
  • false: the request was accepted but had no effect, paired with reason when the method exposes one. The response is still result, not error.

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

CategoryProduced byMeaning
pane_not_foundEvery 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_foundAgent methods that take target.target did not resolve to any agent terminal.
agent_target_ambiguousSame 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_readyagent.send_keys, agent.prompt.The target has no recognized active agent, or its pane runtime is not hosting that agent.
agent_not_runningagent.wait, agent.prompt with wait.The target process, pane, or agent identity disappeared or changed while waiting.
agent_name_takenagent.start, agent.rename.Another agent terminal already owns the requested name. The message lists competing terminals.
invalid_agentpane.report_agent, pane.report_agent_session, pane.release_agent.The reported agent label was empty after trimming.
invalid_agent_viewagent.view.set, agent.view.clear.The source, recursive filter, field/value pair, or sort definition is invalid.
invalid_pane_swappane.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_unavailableLayout-reading methods and pane.move.The layout snapshot could not be produced for the resolved tab.
confirmation_requiredpane.close.The configured close policy requires confirmation.
pane_send_failed / agent_send_keys_failedPane input methods / agent.send_keys.The runtime writer reported an error.
agent_prompt_failedagent.prompt.The agent-aware submission could not be written.
invalid_keypane.send_keys, pane.send_input, agent.send_keys.A requested key could not be parsed. The message is unsupported key <key>.
invalid_regexpane.wait_for_output.A regex match pattern failed to compile.
timeoutpane.wait_for_output, agent.wait, agent.prompt with wait.The requested output or state transition did not occur before the deadline.
invalid_envagent.start, pane.split.An env key was empty, contained =, or contained a NUL byte; or a value contained a NUL byte.
feature_disabledpane.graphics.* methods.Kitty graphics is not enabled (experimental.kitty_graphics).
invalid_layer_idpane.graphics.set, pane.graphics.clear, pane.graphics.stream.*.layer_id is empty, longer than 64 characters, or contains characters other than alphanumeric, ., _, :, -.
invalid_imagepane.graphics.set, pane.graphics.stream.direct.Zero dimensions, mismatched data length, invalid base64, empty data, or unsupported format for direct frames.
image_too_largepane.graphics.set, pane.graphics.stream.set.Inline image data exceeds the byte limit.
layer_limitpane.graphics.set, pane.graphics.stream.open.The per-pane or total layer cap is reached, or host image ids are exhausted.
graphics_budget_exceededpane.graphics.set, pane.graphics.stream.direct.The inline memory budget for pane graphics is reached.
stream_conflictpane.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_closedpane.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_streampane.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_filepane.graphics.stream.direct.The referenced file frame could not be leased or read.
cell_size_unavailablepane.graphics.info.The host cell size is unknown, so pixel geometry cannot be reported.
invalid_requestAll.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.

MethodParamsResult
agent.listnoneAgentList { 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.

MethodParamsResult
agent.getAgentTarget (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.

MethodParamsResult
agent.focusAgentTargetAgentInfo { 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.

MethodParamsRequiredDefault
agent.startAgentStartParams
PropertyTypeRequiredDefaultBehavior
namestringyesTrimmed; empty becomes invalid_agent_name. Must not duplicate an existing agent name or agent_name_taken is returned.
argvstring[]yesCommand and arguments. Empty produces invalid_agent_argv.
cwdstring | nullnoinherited placement or local cwdLegacy local working directory. Cannot be combined with location.
locationResourceLocation | nullnoinherited placementExplicit execution host and host-native path.
workspace_idstring | nullnoactive workspaceSelects the target workspace. When tab_id is also set, both must reference the same workspace or agent_placement_conflict is returned.
tab_idstring | nullnoactive tab of the resolved workspaceSplit from this tab's focused pane.
split"right" | "down" | nullno"right"Direction of the split from the focused pane.
focusbooleannofalseFocus the new pane after creation and switch the active workspace/tab.
envobjectno{}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, no workspace_id, and workspaces exist: split the focused pane of the active workspace, using the active tab.
  • workspace_id set, no tab_id: split the focused pane of the active tab of that workspace.
  • tab_id set: split the focused pane of that tab. If workspace_id is 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:

  • target is an agent target, not a pane_id. See Agent targets.
  • The result pane_id falls back to the original target string when a public pane id cannot be resolved.
  • revision is always 0; do not treat it as a content revision. 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. See pane.read for the source-specific truncation rules.
PropertyTypeRequiredDefault
targetstringyes
sourceReadSourceyes
linesinteger | nullno80, clamped to 1000
formatReadFormatno"text"
strip_ansibooleannotrue

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.

PropertyTypeRequired
targetstringyes
keysstring[]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.

PropertyTypeRequiredDefault
targetstringyes
textstringyes
waitobject | nullnonull
wait.untilAgentStatus[]noidle, done, or blocked
wait.timeout_msinteger | nullnono 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.

PropertyTypeRequiredDefault
targetstringyes
untilAgentStatus[]noidle, done, or blocked
timeout_msinteger | nullnono 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.

PropertyTypeRequiredDefault
sourcestringyes
labelstring | nullnonull
filterAgentViewFilter | nullnoall agents
sortAgentViewSort[]nonatural 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.

PropertyTypeRequiredDefault
sourcestring | nullnoclear 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.

PropertyTypeRequiredDefault
targetstringyes
namestring | nullnoclears 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.

MethodParamsResult
agent.explainAgentTargetAgentExplain { 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:

MethodEncodesOrder
pane.send_textRaw bytes from text.text only.
pane.send_keysEach entry in keys as a key combo via the terminal's key encoder.keys in array order.
pane.send_inputtext first (encoded as in pane.send_text), then keys.text then keys.

pane.send_text

PropertyTypeRequired
pane_idstringyes
textstringyes

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

PropertyTypeRequired
pane_idstringyes
keysstring[]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

PropertyTypeRequiredDefault
pane_idstringyes
textstringno""
keysstring[]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.

PropertyTypeRequiredDefault
pane_idstringyes
sourceReadSourceyes
linesinteger | nullno80, clamped to a maximum of 1000
formatReadFormatno"text"
strip_ansibooleannotrue

Read sources:

sourceContent
"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.

PropertyTypeRequiredDefault
pane_idstringyes
sourceReadSourceyes
matchOutputMatchyes
linesinteger | nullno80 (passed through to the underlying read)
timeout_msinteger | nullnono timeout (waits indefinitely until match or disconnect)
strip_ansibooleannotrue

match is a tagged object:

match.typeOther fieldBehavior
"substring"value: stringThe first line containing value anywhere is returned as matched_line.
"regex"value: stringThe 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.read requests with format: "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 }. revision is copied from the read result (currently always 0). matched_line is the matched line string, or null if no line matched at the time of return (the success path always includes a matched line; the null variant exists for subscription emission).
  • On timeout, the server returns error with code: "timeout" and message timed out waiting for output match.
  • If an underlying read returns an error, the wait handler rewrites the error to echo the outer request id and returns it.
  • If the client disconnects during the wait (should_stop_connection is 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.

PropertyTypeRequiredDefault
workspace_idstring | nullnoall 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.

PropertyTypeRequired
pane_idstringyes

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.

PropertyTypeRequiredDefault
caller_pane_idstring | nullnofocused 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.

PropertyTypeRequired
pane_idstringyes

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.

PropertyTypeRequiredDefault
pane_idstringyes
labelstring | nullnoclears 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.

PropertyTypeRequiredDefaultBehavior
target_pane_idstring | nullnofocused pane of resolved workspaceThe pane to split.
workspace_idstring | nullnoactive workspaceUsed only when target_pane_id is omitted, to select the workspace whose focused pane is split.
direction"right" | "down"yesright splits horizontally (new pane on the right); down splits vertically (new pane below).
rationumber | nullnoimplementation default near 0.5First-pane ratio of the new split, clamped to [0.1, 0.9].
cwdstring | nullnosource pane pathLegacy local working directory. Cannot be combined with location.
locationResourceLocation | nullnosource pane locationExplicit execution host and host-native path.
envobjectno{}Extra environment variables. See env behavior.
focusbooleannofalseFocus 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.

PropertyTypeRequired
pane_idstringyes

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 direction and optionally pane_id (the source). The target is the nearest pane in that direction.
  • Explicit: supply source_pane_id and target_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.

PropertyTypeRequiredDefault
pane_idstring | nullnoactive pane (directional mode source)
direction"left" | "right" | "up" | "down" | nullno
source_pane_idstring | nullno
target_pane_idstring | nullno

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.

PropertyTypeRequiredDefaultBehavior
pane_idstringyesThe pane to move.
destinationPaneMoveDestinationyesTagged union; see below.
focusbooleannofalseFocus the moved pane in its destination and switch the active workspace/tab.

destination variants:

destination.typeFieldsBehavior
"tab"tab_id, target_pane_id?, split: SplitDirection, ratio?: numberMove 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_id changes 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_id is set to the previous workspace id, closed_tab_id is set when the source tab closes, and the server emits workspace_closed, tab_closed as appropriate.
  • A move to a tab in the same workspace and tab as the source is a no-op and returns changed: false with reason: "same_tab".
  • A move to or from a zoomed tab is a no-op and returns changed: false with reason: "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, plus workspace_created, tab_created, tab_closed, workspace_closed as applicable, plus layout_updated for 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.

PropertyTypeRequiredDefault
pane_idstring | nullnofocused pane of the active workspace
mode"toggle" | "on" | "off"no"toggle"

mode behavior:

  • "toggle": invert the tab's zoom state. No-op with reason: "single_pane" when the tab only contains one pane.
  • "on": zoom in. No-op with reason: "single_pane" for single-pane tabs, reason: "already_zoomed" when already zoomed.
  • "off": zoom out. No-op with reason: "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.

PropertyTypeRequiredDefault
pane_idstring | nullnofocused 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.

PropertyTypeRequiredDefault
pane_idstring | nullnofocused 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, else null.
  • foreground_process_group_id: the foreground process group id when a foreground job was detected, else null.
  • tty: always null in the current implementation; reserved for future use.
  • foreground_processes: the processes in the detected foreground job. Each entry has pid, name, optional argv0, optional argv, optional cmdline, and optional cwd (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.

PropertyTypeRequiredDefault
pane_idstring | nullnofocused 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.

PropertyTypeRequiredDefaultBehavior
pane_idstring | nullnofocused pane of the active workspace
direction"left" | "right" | "up" | "down"yesleft/right adjust horizontal splits; up/down adjust vertical splits.
amountnumber | nullno0.05Fraction 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.9 bounds): 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.

PropertyTypeRequiredDefault
pane_idstring | nullnofocused 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.

PropertyTypeRequiredDefault
pane_idstring | nullnofocused 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.

PropertyTypeRequiredDefaultBehavior
pane_idstringyes
layer_idstring | nullno"primary"Validated per the layer-id rules above.
z_indexintegerno0Stack order within the pane.
formatPaneGraphicsFormatyesOne of "png", "rgb", "rgba", "bgra". bgra is canonicalized to rgba on the server.
image_widthintegeryesPixel width. Must be greater than zero.
image_heightintegeryesPixel height. Must be greater than zero.
data_base64stringno""Base64-encoded image data. Decoded when data is absent. Invalid base64 returns invalid_image.
placementPaneGraphicsPlacementParamsnozerosOptional 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.

PropertyTypeRequiredDefault
pane_idstringyes
layer_idstring | nullno"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.

PropertyTypeRequired
pane_idstringyes

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.

PropertyTypeRequiredDefault
pane_idstringyes
layer_idstring | nullno"primary"
z_indexintegerno0

The owner field is set by the server to identify the stream initiator. Stream lifecycle:

  1. stream.open claims a layer for a named owner. If the layer already has an active stream, the server returns stream_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 {}.
  2. stream.set updates the streamed layer with inline image data, using the same PaneGraphicsSetParams shape as pane.graphics.set. The caller's owner must match the active stream owner or the server returns stream_closed (owner mismatch produces stream_conflict). The inline data limit for streamed frames is 16 MiB (PANE_GRAPHICS_STREAM_MAX_BYTES). Result: Ok {}.
  3. stream.direct submits a file-backed frame: the image data is leased from a file on disk identified by path, avoiding an inline copy. Result: PaneGraphicsFrameAck { sequence, revision } echoing the caller-supplied sequence and revision.
  4. stream.close releases the layer. Only the owning owner may close the stream; a mismatched owner is a silent no-op that still returns Ok {}. 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.

PropertyTypeRequiredDefaultBehavior
pane_idstringyes
layer_idstring | nullno"primary"Validated per the layer-id rules.
z_indexintegerno0Stack order.
ownerstringyesMust match the active stream owner.
formatPaneGraphicsFormatyesOnly "rgba" and "bgra" are accepted for direct frames; other formats return invalid_image. bgra is canonicalized to rgba.
image_widthintegeryesPixel width. Must be greater than zero.
image_heightintegeryesPixel height. Must be greater than zero.
pathstringyesFilesystem path to the frame file. The server leases the file and validates its length against the expected byte count.
sequenceintegeryesCaller-supplied frame sequence, echoed in the ack.
revisionintegeryesCaller-supplied revision, echoed in the ack.
placementPaneGraphicsPlacementParamsnozerosOptional 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.

PropertyTypeRequiredDefault
tab_idstring | nullnoactive tab
pane_idstring | nullno

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.

PropertyTypeRequiredDefault
rootLayoutNodeyes
workspace_idstring | nullnoactive workspace
tab_idstring | nullno
tab_labelstring | nullnoreplaced tab's label, otherwise none
focusbooleannofalse

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. Omitted command launches 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.

PropertyTypeRequiredDefault
pane_idstringyes
sourcestringyes
agentstringyes
agent_session_idstring | nullnonull
agent_session_pathstring | nullnonull
seqinteger | nullnonull

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.

PropertyTypeRequiredDefault
pane_idstringyes
sourcestring | nullnonull
seqinteger | nullnonull

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.

PropertyTypeRequiredDefaultBehavior
pane_idstringyes
sourcestringyesNormalized: trimmed, control chars removed, at most 80 chars, must be non-empty or invalid_metadata_source.
agentstring | nullnonullOptional agent label; normalized the same way as a reported agent label. Empty produces invalid_agent.
applies_to_sourcestring | nullnonullWhen set, the metadata applies to reports from that source. Validated with the same rules as source.
titlestring | nullnonullTrimmed, control chars removed, at most 80 chars.
display_agentstring | nullnonullSame normalization as title.
custom_statusstring | nullnonullTrimmed, non-control, at most 32 chars.
state_labelsobjectno{}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.
tokensobjectno{}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_titlebooleannofalseClear the title. Cannot be combined with title on the same request.
clear_display_agentbooleannofalseClear display_agent; not combinable with display_agent.
clear_custom_statusbooleannofalseClear custom_status; not combinable with custom_status.
clear_state_labelsbooleannofalseClear all state labels; not combinable with a non-empty state_labels.
seqinteger | nullnonullOrdering sequence.
ttl_msinteger | nullnonullTime-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_request with cannot set and clear the same metadata field.
  • A request that sets or clears nothing returns invalid_metadata_request with missing 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_tokens with pane 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

On this page