Gardn
DocumentationLocal APIReference

Extensions and control

Local API methods for execution hosts, server control, notifications, integrations, plugins, diagnostics, and event subscriptions.

Extensions and control

The extension and control methods cover execution-host connections, server control, notifications, window titles, integrations, plugins, configuration diagnostics, and event subscriptions. Workspace, tab, agent, and pane methods are documented on Resources and Panes and agents. Canonical wire shapes live in the generated reference; Error semantics documents the error envelope, and transport and the request envelope are described in the Local API overview.

Trust boundary. The Local API is a local-user control surface. Any process that can open its socket can ask Gardn to stop or replace the server, write integration and plugin files, spawn plugin commands and panes as the current user, send content to a client, or hold a long-lived event stream. Treat the socket and every caller as trusted only when that is intended.

Methods

The schema defines these methods in the namespaces covered here:

  • ping.
  • connection.list, connection.save, connection.delete, connection.test, connection.connect, connection.disconnect, connection.retire.start, and connection.retire.status.
  • server.stop, server.live_handoff, server.reload_config, server.agent_manifests, and server.reload_agent_manifests.
  • notification.show.
  • client.window_title.set and client.window_title.clear.
  • integration.install and integration.uninstall.
  • plugin.link, plugin.list, plugin.unlink, plugin.enable, plugin.disable, plugin.action.list, plugin.action.invoke, plugin.log.list, plugin.pane.open, plugin.pane.focus, and plugin.pane.close.
  • events.subscribe and events.wait.

Execution-host connections

Connection profiles contain OpenSSH destination metadata. Authentication remains owned by system OpenSSH. Profile ids use ASCII letters, numbers, ., _, and -. The coordinator persists profiles in ~/.config/gardn/ssh-profiles.json.

ConnectionProfileInfo has these fields:

FieldTypeDescription
profile_idstringStable profile id.
namestringDisplay name.
targetstringRaw OpenSSH target.
suggested_directorystring | nullOptional default path on the execution host.
execution_host_idstringPlacement id, such as ssh:workbox:1.
statusConnectionStatusKinddisconnected, connecting, connected, reconnecting, disconnecting, authentication_required, or unavailable.
errorstring | nullConnection detail for reconnecting or unavailable.

connection.list

Returns ConnectionList { profiles }. Each profile includes its current connection status.

{ "id": "connection-list", "method": "connection.list", "params": {} }

connection.save

Creates or replaces a profile. Updating target increments the host-binding generation and changes execution_host_id. Updating only name or suggested_directory preserves the host id.

PropertyTypeRequiredDescription
profile_idstringyesStable profile id.
namestringyesNon-empty display name.
targetstringyesNon-empty OpenSSH target.
suggested_directorystring | nullnoDefault remote path for resource creation.

Returns ConnectionProfile { profile }.

Errors: connection_profile_invalid, connection_profile_save_failed.

{
  "id": "connection-save",
  "method": "connection.save",
  "params": {
    "profile_id": "workbox",
    "name": "Work box",
    "target": "alice@workbox",
    "suggested_directory": "/srv/project"
  }
}

connection.delete

Removes an unreferenced profile by profile_id. Returns ConnectionDeleted { profile_id, removed }; removed is false when the id did not exist. The method fails while any live or persisted session, pending creation, or managed worker binding still references the profile's execution host.

Use the Settings removal workflow for cross-session inventory and cleanup. API automation must retire the execution host in every affected session before it calls connection.delete.

Errors: connection_profile_referenced, connection_profile_delete_failed.

connection.retire.start and connection.retire.status

These methods are per-session retirement primitives. They do not inventory other named sessions or delete the global profile. Use the Settings removal workflow unless the caller coordinates every affected session.

Both methods accept:

PropertyTypeRequiredDescription
profile_idstringyesSaved profile id.
execution_host_idstringyesExact host id currently mapped by the profile. A stale or mismatched id fails closed.
local_onlybooleannoDefault false. When true, forgets local runtime and termination records without claiming that remote cleanup occurred.

connection.retire.start fences new work on the execution host, clears affected Group defaults, moves affected Workspace defaults to the coordinator's Local home directory, closes panes on that host, and persists the changed session before returning. It is idempotent after the host is fenced.

The response is ConnectionRetireStart { profile_id, execution_host_id, accepted, remaining_panes, remaining_terminals, pending_terminations }.

Poll connection.retire.status until ready is true. The response is ConnectionRetireStatus { profile_id, execution_host_id, ready, remaining_panes, remaining_terminals, pending_terminations }. A ready status also requests a cooperative worker shutdown when the worker is reachable.

After every affected session reports ready and owned worker bindings are removed, call connection.delete.

Errors include connection_profile_not_found, connection_retire_host_invalid, connection_retire_host_mismatch, execution_hosts_unavailable, and connection_retire_persist_failed.

{
  "id": "connection-retire-start",
  "method": "connection.retire.start",
  "params": {
    "profile_id": "workbox",
    "execution_host_id": "ssh:workbox:1",
    "local_only": false
  }
}
{
  "id": "connection-retire-status",
  "method": "connection.retire.status",
  "params": {
    "profile_id": "workbox",
    "execution_host_id": "ssh:workbox:1",
    "local_only": false
  }
}

connection.test, connection.connect, and connection.disconnect

Each method accepts { "profile_id": "..." } and queues the lifecycle action. The response is ConnectionActionQueued { profile, action }. action is test, connect, or disconnect. connection.connect automatically installs or selects the exact checksum-addressed worker required by this Gardn build. A busy incompatible worker drains without losing its runtimes; a compatible artifact is reused. The profile status in this immediate response is the status before the queued action is polled; call connection.list to observe the resulting status.

Errors: connection_profile_not_found.

ping

Returns the server version, client protocol number, and capability flags. server.live_handoff clients should inspect capabilities.live_handoff before attempting a handoff.

Params: PingParams, no properties.

Success: ResponseResult::Pong, discriminator "pong", with version: string, protocol: integer, and optional capabilities: ServerCapabilities. ServerCapabilities currently contains live_handoff: boolean.

{"id":"ping-1","method":"ping","params":{}}
{"id":"ping-1","result":{"type":"pong","version":"0.2.19","protocol":13,"capabilities":{"live_handoff":true}}}

Server control

server.stop

Requests server shutdown. The application sets its quit flag and returns before the event loop exits. In headless mode, requests received after shutdown has begun are rejected with server_unavailable.

Params: EmptyParams, no properties.

Success: ResponseResult::Ok, discriminator "ok".

ErrorMeaning
server_unavailableA headless server is already shutting down, or the API cannot dispatch to the application.
{"id":"stop-1","method":"server.stop","params":{}}
{"id":"stop-1","result":{"type":"ok"}}

server.live_handoff

Replaces a running headless Unix server with another Gardn process while preserving the session snapshot and live pane runtimes. It is intended for an in-place update; it is not an ordinary restart.

Params: ServerLiveHandoffParams.

PropertyTypeDefaultDescription
import_exestring | nullnullReplacement executable. If omitted, the current executable path is used.
expected_protocolinteger | nullnullIf present, the replacement must speak this client protocol number.
expected_versionstring | nullnullIf present, the replacement must report this Gardn version.

Success: ResponseResult::Ok, discriminator "ok". The response is written before the old server exits; the connection then closes as part of the handoff.

Preconditions and execution boundary:

  • ping advertises capabilities.live_handoff = true only on Unix. Windows returns handoff_failed with "live handoff is only supported on Unix".
  • The interactive application handler does not perform a handoff and returns unsupported_in_app_mode.
  • At most 64 pane file descriptors can be transferred in one handoff. The request fails before transfer when more are attached.
  • The server creates a private handoff socket, disconnects clients and rejects pending client connections, pauses pane readers, captures the session and runtime state, starts the import process, transfers pane descriptors, waits for restored and ready, then sends committed. The old server exits only after commit. A failure before commit kills the import process and restores the old server's readers and public sockets where possible.
  • expected_protocol and expected_version are placed in the handoff manifest and validated by the replacement. A mismatch is a handoff_failed response from the old server.
  • The API and client socket files are removed when the replacement takes ownership. The closed connection does not accept retries; clients must reconnect.
ErrorMeaning
unsupported_in_app_modeThe request reached an interactive App, not the headless server.
handoff_failedUnix handoff could not bind its socket, exceeded the 64-pane limit, could not spawn the import process, could not pause or duplicate a pane runtime, failed token/manifest validation, failed runtime restoration, or the replacement did not become ready or commit. The message identifies the failed step.
server_unavailableShutdown was already in progress or the request could not be dispatched.
{"id":"handoff-1","method":"server.live_handoff","params":{"expected_version":"0.2.19"}}
{"id":"handoff-1","result":{"type":"ok"}}

server.reload_config

Loads config.toml and applies the live configuration without restarting the server. In headless mode, the server keybinding set is restored before the disk configuration is applied; this keeps server-owned keybindings distinct from a client's local keybindings.

Params: EmptyParams, no properties.

Success: ResponseResult::ConfigReload, discriminator "config_reload".

FieldTypeMeaning
status"applied" | "partial" | "failed"Whether the loaded configuration was fully applied.
diagnosticsstring[]Human-readable load, parse, validation, or retained-value diagnostics.

applied has an empty diagnostics array and clears the current configuration issue. partial means the file loaded but one or more values or sections were rejected; accepted values are applied and rejected values retain their previous settings. failed means the file could not be loaded or parsed into usable state; the previous configuration remains active. Diagnostic strings are display text, not a stable machine-readable sub-protocol. There is no config.* success discriminator.

ErrorMeaning
serialization_errorHeadless mode could not encode the reload result; this fallback error uses an empty id.
server_unavailableShutdown was already in progress or the request could not be dispatched.
{"id":"reload-1","method":"server.reload_config","params":{}}
{"id":"reload-1","result":{"type":"config_reload","status":"partial","diagnostics":["ui.sidebar_min_width (40) is greater than sidebar_max_width (20); keeping previous [ui] settings"]}}

server.agent_manifests

Returns the currently loaded agent-manifest summaries and the persisted remote-update status. It refreshes the App's summary view from its loaded manifest state; it does not reload manifest files. Use server.reload_agent_manifests to reload them.

Params: EmptyParams, no properties.

Success: ResponseResult::AgentManifestStatus, discriminator "agent_manifest_status".

FieldTypeDefaultDescription
last_check_unixinteger | nullomittedLast remote manifest check time.
last_resultstring | nullomittedLast remote update result.
manifestsAgentManifestInfo[][] when none are loadedOne summary per agent.

Each AgentManifestInfo contains agent, source, source_kind, local_override_shadowing_remote, and optional active_version, cached_remote_version, remote_update_result, remote_update_error, remote_last_checked_unix, and warning.

ErrorMeaning
server_unavailableShutdown was already in progress or the request could not be dispatched.
{"id":"manifests-1","method":"server.agent_manifests","params":{}}
{"id":"manifests-1","result":{"type":"agent_manifest_status","manifests":[]}}

server.reload_agent_manifests

Reloads agent manifests from disk, replaces the in-memory summaries, and resets all agent-detection runtimes so subsequent detection uses the new manifests.

Params: EmptyParams, no properties.

Success: ResponseResult::AgentManifestReload, discriminator "agent_manifest_reload", with manifests: AgentManifestInfo[].

ErrorMeaning
server_unavailableShutdown was already in progress or the request could not be dispatched.
{"id":"manifests-2","method":"server.reload_agent_manifests","params":{}}
{"id":"manifests-2","result":{"type":"agent_manifest_reload","manifests":[]}}

Notifications and window titles

These methods are client-facing. Headless mode sends terminal/system notifications and window-title commands to the current foreground client. With no foreground client, the request still succeeds with a negative result. Interactive App mode has no API foreground-client transport for window titles, so its title methods return no_foreground_client.

notification.show

Shows a notification using the configured toast.delivery mode.

Params: NotificationShowParams.

PropertyTypeDefaultDescription
titlestringrequiredControl characters are removed, whitespace is collapsed, the value is trimmed, and the result is capped at 80 characters. An empty result is invalid.
bodystring | nullnullSanitized like title, capped at 240 characters. A value that sanitizes to empty is treated as absent.
positionToastGardnPosition | nullnullOptional built-in toast position: top-left, top-right, bottom-left, or bottom-right. It has no effect for terminal/system delivery.
soundNotificationShowSound"none""none", "done", or "request". Sound is played/forwarded only when local sound playback and the configured sound set allow it.

Success: ResponseResult::NotificationShow, discriminator "notification_show", with shown: boolean and reason.

reasonshownMeaning
showntrueA toast or foreground notification sink accepted the notification.
disabledfalsetoast.delivery is off.
busyfalseBuilt-in Gardn delivery already has a toast.
rate_limitedfalseAnother API notification was shown less than 250 ms ago.
no_foreground_clientfalseTerminal/system delivery has no foreground client, or its notification helper returned false/failed.

Built-in gardn delivery checks busy before the rate limit, stores the toast in App state, and can play the requested sound. terminal and system delivery rate-limit first and then send to the foreground client (headless) or invoke the configured helper (interactive).

ErrorMeaning
invalid_paramstitle is empty after sanitization ("notification title is empty").
server_unavailableShutdown was already in progress or the request could not be dispatched.
{"id":"notify-1","method":"notification.show","params":{"title":"Build finished","body":"api workspace","position":"top-right","sound":"done"}}
{"id":"notify-1","result":{"type":"notification_show","shown":true,"reason":"shown"}}

client.window_title.set

Sets the title of the current foreground client window.

Params: ClientWindowTitleSetParams.

PropertyTypeDefaultDescription
titlestringrequiredControl characters including terminal title-control characters are removed, the value is trimmed, and the result is capped at 200 characters. An empty result is invalid.

Success: ResponseResult::ClientWindowTitle, discriminator "client_window_title", with changed: boolean and reason: "set" or "no_foreground_client".

ErrorMeaning
invalid_paramstitle is empty after sanitization ("window title is empty").
server_unavailableShutdown was already in progress or the request could not be dispatched.
{"id":"title-1","method":"client.window_title.set","params":{"title":"api-refactor"}}
{"id":"title-1","result":{"type":"client_window_title","changed":true,"reason":"set"}}

client.window_title.clear

Clears the title previously set through the API on the current foreground client.

Params: EmptyParams, no properties.

Success: ResponseResult::ClientWindowTitle, discriminator "client_window_title". A delivered clear is {"changed":true,"reason":"cleared"}; without a foreground client it is {"changed":false,"reason":"no_foreground_client"}.

It has no title to validate, so it does not return invalid_params.

{"id":"title-2","method":"client.window_title.clear","params":{}}
{"id":"title-2","result":{"type":"client_window_title","changed":true,"reason":"cleared"}}

Integrations

Integration methods write or remove Gardn-managed hooks, extensions, plugins, and configuration entries in the selected agent's configuration area on the Coordinator Host. They run with the Local API process's user permissions. They do not target saved SSH Execution Hosts; use Settings > Integrations for remote integration management. Message strings describe paths and warnings and are not a stable machine-readable sub-protocol.

The IntegrationTarget values are pi, omp, claude, codex, copilot, devin, kimi, droid, opencode, hermes, qodercli, cursor, and grok.

On non-Windows platforms all targets are supported. On Windows all targets are supported. kimi is the only target with an agent-version probe: an old version fails, while an unavailable or unparsable version adds a warning and continues.

integration.install

Installs the selected target. Codex is special: the API calls the agent-profile installer, which installs into the default Codex home and every enabled Codex profile home that can be determined. A missing additional profile directory becomes a warning; if no Codex directory can be installed, the whole request fails.

Target installation writes these kinds of assets:

TargetInstalled asset reported in details.messages
piPi extension.
ompOne or more Gardn extensions.
claudeClaude hook and settings entry.
codexCodex hook plus Codex hooks/config entries, for the default and enabled profile homes.
copilotCopilot hook and settings entry.
devinDevin hook and settings entry.
kimiKimi hook and config entry.
droidDroid hook and hooks entry.
opencodeOpenCode plugin and TUI session plugin, with the TUI session plugin enabled in the TUI config.
hermesHermes plugin directory and enabled config entry.
qodercliQoder CLI hook and settings entry.
cursorCursor hook and hooks entry.
grokGrok hook and hook config.

Params: IntegrationInstallParams.

PropertyTypeDefaultDescription
targetIntegrationTargetrequiredOne of the values above.

Success: ResponseResult::IntegrationInstall, discriminator "integration_install", with target and details.messages: string[].

ErrorMeaning
integration_install_failedUnsupported platform, missing/old required agent, missing config directory, failed config edit, or another install I/O error.
server_unavailableShutdown was already in progress or the request could not be dispatched.
{"id":"integration-1","method":"integration.install","params":{"target":"codex"}}
{"id":"integration-1","result":{"type":"integration_install","target":"codex","details":{"messages":["installed codex integration hook to /home/me/.codex/gardn-agent-state.sh"]}}}

integration.uninstall

Removes Gardn-managed assets and entries for the selected target. It does not uninstall the agent and does not ask for confirmation. A target with nothing installed reports a message; it is not an error.

Params: IntegrationUninstallParams.

PropertyTypeDefaultDescription
targetIntegrationTargetrequiredOne of the values above.

The API handler calls the single-target uninstall path. In particular, unlike integration.install for Codex, API uninstall removes the default Codex installation only; it does not iterate enabled Codex profile homes.

Success: ResponseResult::IntegrationUninstall, discriminator "integration_uninstall", with target and details.messages: string[].

ErrorMeaning
integration_uninstall_failedFailed to remove a managed file or edit agent configuration.
server_unavailableShutdown was already in progress or the request could not be dispatched.
{"id":"integration-2","method":"integration.uninstall","params":{"target":"claude"}}
{"id":"integration-2","result":{"type":"integration_uninstall","target":"claude","details":{"messages":["no claude hook found at /home/me/.claude/hooks/gardn-agent-state.sh"]}}}

Plugins

Plugin commands are not sandboxed. A plugin action, event hook, build command when run by another surface, and plugin pane command can execute as the current user. Action commands use the plugin root as their working directory. Pane commands use the plugin root unless cwd overrides it. The API only links and exposes manifests; plugin.link does not run a manifest build command.

A manifest is loaded from gardn-plugin.toml. Plugin IDs are trimmed, non-empty, at most 120 characters, and contain only ASCII letters, digits, :, ., _, and -. Local action, pane, and link-handler IDs are trimmed, non-empty, at most 120 characters, and contain only ASCII letters, digits, :, _, and - (no dot). Platform values are linux, macos, and windows.

An installed plugin record contains plugin_id, name, version, min_gardn_version, optional description, manifest_path, plugin_root, enabled, optional plugin platforms, arrays build, actions, events, panes, and link_handlers, source, and warnings. A missing source manifest is retained with a warning beginning manifest unavailable: ; action listing skips it and action/pane execution rejects it.

Loads and registers one plugin manifest. If the ID is already registered, the new record replaces it after the manifest is validated. The registry write is rolled back in memory if persistence fails.

Params: PluginLinkParams.

PropertyTypeDefaultDescription
pathstringrequiredPlugin directory or direct manifest path.
enabledbooleantrueWhether actions, event hooks, and panes may run.
sourcePluginSourceInfo | nullnullOptional provenance. kind is local (default) or github; optional fields are owner, repo, subdir, requested_ref, resolved_commit, managed_path, and installed_unix_ms. GitHub sources require a canonical managed_path under the managed plugin directory matching the plugin ID.

Manifest validation also requires non-empty name, version, and semantic min_gardn_version, non-empty command argv, valid platform names, unique action/pane/link-handler IDs, valid link-handler regexes, and link handlers that reference an existing action. Unknown event names are retained as warnings; only known hook event kinds execute.

Success: ResponseResult::PluginLinked, discriminator "plugin_linked", with plugin: InstalledPluginInfo.

ErrorMeaning
plugin_manifest_not_foundThe path could not be canonicalized.
invalid_plugin_manifest_pathThe canonical manifest has no parent directory.
plugin_manifest_read_failedThe manifest could not be read.
plugin_manifest_parse_failedTOML parsing failed.
invalid_plugin_id, invalid_plugin_name, invalid_plugin_version, invalid_plugin_min_gardn_versionA required identity/version field is invalid.
plugin_requires_newer_gardnThe manifest requires a newer Gardn release.
invalid_plugin_platformA platform name is unknown or platforms is an empty array.
invalid_plugin_commandA command is empty or contains an empty argv string.
invalid_plugin_action_id, invalid_plugin_action_title, invalid_plugin_pane_id, invalid_plugin_pane_title, invalid_plugin_eventA manifest member has an invalid ID, title, or event name.
duplicate_plugin_action_id, duplicate_plugin_pane_id, duplicate_plugin_link_handler_idA manifest contains duplicate IDs.
invalid_plugin_link_handler_pattern, invalid_plugin_link_handler_actionA link-handler pattern or action reference is invalid.
invalid_plugin_sourceSupplied provenance is inconsistent or its managed path is invalid.
plugin_registry_save_failedplugins.json could not be written; the previous record is restored.
server_unavailableShutdown was already in progress or the request could not be dispatched.
{"id":"link-1","method":"plugin.link","params":{"path":"/home/me/plugins/workspace-tools"}}
{"id":"link-1","result":{"type":"plugin_linked","plugin":{"plugin_id":"workspace-tools","name":"Workspace Tools","version":"0.1.0","min_gardn_version":"0.2.0","manifest_path":"/home/me/plugins/workspace-tools/gardn-plugin.toml","plugin_root":"/home/me/plugins/workspace-tools","enabled":true,"source":{"kind":"local"},"build":[],"actions":[],"events":[],"panes":[],"link_handlers":[],"warnings":[]}}}

plugin.list

Lists registered plugins in ascending plugin_id order. A manifest-unavailable record remains listable with its warning. A filter that matches no plugin returns an empty array.

Params: PluginListParams.

PropertyTypeDefaultDescription
plugin_idstring | nullnullOptional normalized plugin ID filter.

Success: ResponseResult::PluginList, discriminator "plugin_list", with plugins: InstalledPluginInfo[].

ErrorMeaning
invalid_plugin_idA supplied filter is not a valid plugin ID.
server_unavailableShutdown was already in progress or the request could not be dispatched.
{"id":"list-1","method":"plugin.list","params":{}}
{"id":"list-1","result":{"type":"plugin_list","plugins":[]}}

Removes a plugin record from the registry. It does not delete the plugin directory. Existing plugin panes keep running as ordinary panes; the plugin attribution records are removed, so those panes can no longer be focused or closed through the plugin pane methods.

Params: PluginUnlinkParams, with plugin_id: string.

Success: ResponseResult::PluginUnlinked, discriminator "plugin_unlinked", with plugin_id and removed: boolean. An unknown but valid ID succeeds with removed: false.

ErrorMeaning
invalid_plugin_idThe ID is invalid.
plugin_registry_save_failedRegistry persistence failed; the plugin record and pane attribution are restored.
server_unavailableShutdown was already in progress or the request could not be dispatched.
{"id":"unlink-1","method":"plugin.unlink","params":{"plugin_id":"workspace-tools"}}
{"id":"unlink-1","result":{"type":"plugin_unlinked","plugin_id":"workspace-tools","removed":true}}

plugin.enable

Sets a registered plugin's enabled flag to true. The flag is persisted. Enabling does not run actions, hooks, or panes immediately.

Params: PluginSetEnabledParams, with plugin_id: string.

Success: ResponseResult::PluginEnabled, discriminator "plugin_enabled", with the updated plugin: InstalledPluginInfo.

ErrorMeaning
invalid_plugin_idThe ID is invalid.
plugin_not_foundNo registered plugin has that ID.
plugin_registry_save_failedPersistence failed; the previous flag is restored.
server_unavailableShutdown was already in progress or the request could not be dispatched.

plugin.disable

Sets a registered plugin's enabled flag to false. The plugin remains registered and listable; action invocation, event hooks, and pane opening are rejected until it is enabled again. Existing panes are not automatically closed.

Params: PluginSetEnabledParams, with plugin_id: string.

Success: ResponseResult::PluginDisabled, discriminator "plugin_disabled", with the updated plugin: InstalledPluginInfo.

Errors are the same as plugin.enable.

{"id":"disable-1","method":"plugin.disable","params":{"plugin_id":"workspace-tools"}}
{"id":"disable-1","result":{"type":"plugin_disabled","plugin":{"plugin_id":"workspace-tools","name":"Workspace Tools","version":"0.1.0","min_gardn_version":"0.2.0","manifest_path":"/home/me/plugins/workspace-tools/gardn-plugin.toml","plugin_root":"/home/me/plugins/workspace-tools","enabled":false,"source":{"kind":"local"},"build":[],"actions":[],"events":[],"panes":[],"link_handlers":[],"warnings":[]}}}

plugin.action.list

Lists actions from available manifests, sorted by qualified ID (plugin_id.action_id). Disabled plugins are included; only unavailable manifests are skipped.

Params: PluginActionListParams.

PropertyTypeDefaultDescription
plugin_idstring | nullnullOptional normalized plugin ID filter.

Success: ResponseResult::PluginActionList, discriminator "plugin_action_list", with actions: PluginActionInfo[]. Each action has plugin_id, action_id, title, optional description, contexts (global, workspace, tab, pane, selection), command, and optional platforms.

ErrorMeaning
invalid_plugin_idA supplied filter is invalid.
server_unavailableShutdown was already in progress or the request could not be dispatched.
{"id":"actions-1","method":"plugin.action.list","params":{"plugin_id":"workspace-tools"}}
{"id":"actions-1","result":{"type":"plugin_action_list","actions":[]}}

plugin.action.invoke

Starts a manifest action asynchronously as the current user, with the plugin root as the process working directory. The response returns while the command is running; completion is reflected later by plugin.log.list.

Params: PluginActionInvokeParams.

PropertyTypeDefaultDescription
action_idstringrequiredWith plugin_id, a local action ID. Without plugin_id, either a local ID or a qualified plugin_id.action_id.
plugin_idstring | nullnullRestricts lookup to one plugin.
contextPluginInvocationContext | nullnullOptional caller context merged over server-derived active workspace/tab/focused-pane context.

With plugin_id, the plugin and local action ID are normalized and looked up directly. Without it, the trimmed action string is matched against local and qualified IDs: no match is plugin_action_not_found, and multiple matches are ambiguous_plugin_action. Caller-supplied non-null context fields take precedence; the request ID supplies correlation_id when none is supplied. Context fields are optional: workspace_id, workspace_label, workspace_cwd, tab_id, tab_label, focused_pane_id, focused_pane_cwd, focused_pane_agent, focused_pane_status, selected_text, invocation_source, correlation_id, clicked_url, and link_handler_id.

Execution requires an available manifest, enabled: true, and an effective platform list that includes the host. An action-level platforms list overrides the plugin-level list. The command receives protected GARDN_* plugin environment variables, including the socket path, plugin root/config/state directories, plugin ID, action ID, context JSON, and current workspace/tab/pane IDs when present. At most 32 plugin commands run concurrently. Stdout and stderr are captured after completion and capped at the last 64 KiB each.

Success: ResponseResult::PluginActionInvoked, discriminator "plugin_action_invoked", with action: PluginActionInfo, merged context, and log: PluginCommandLogInfo. The initial log has status: "running", finished_unix_ms: null, exit_code: null, stdout: null, stderr: null, and error: null.

ErrorMeaning
invalid_plugin_idSupplied plugin_id is invalid.
invalid_plugin_action_idWith plugin_id, action_id is not a valid local ID.
plugin_not_foundThe requested plugin is not registered.
plugin_manifest_unavailableThe selected plugin's manifest is unavailable.
plugin_action_not_foundNo action matches.
ambiguous_plugin_actionA local action ID matches more than one plugin; include plugin_id.
plugin_disabledThe plugin is disabled.
platform_unsupportedThe effective action/plugin platform list excludes the host.
invalid_plugin_commandThe command is empty or contains an empty argv string.
invalid_plugin_contextThe merged context could not be serialized.
plugin_command_limit_reached32 commands are already running. A failed log record is added for the attempt.
server_unavailableShutdown was already in progress or the request could not be dispatched.
{"id":"invoke-1","method":"plugin.action.invoke","params":{"plugin_id":"workspace-tools","action_id":"bootstrap","context":{"invocation_source":"api"}}}
{"id":"invoke-1","result":{"type":"plugin_action_invoked","action":{"plugin_id":"workspace-tools","action_id":"bootstrap","title":"Bootstrap","contexts":["workspace"],"command":["./bootstrap"]},"context":{"invocation_source":"api","correlation_id":"invoke-1"},"log":{"log_id":"plugin-log-1","plugin_id":"workspace-tools","action_id":"bootstrap","command":["./bootstrap"],"status":"running","started_unix_ms":1700000000000}}}

plugin.log.list

Returns recent plugin command records. The in-memory log retains at most 200 records for the session. Results contain the newest selected window in chronological order (oldest first within that window).

Params: PluginLogListParams.

PropertyTypeDefaultDescription
plugin_idstring | nullnullOptional normalized plugin ID filter.
limitinteger | null50Clamped to 1 through 200.

Each PluginCommandLogInfo contains log_id, plugin_id, optional action_id, optional event name event, command, status (running, succeeded, or failed), started_unix_ms, optional finished_unix_ms, exit_code, stdout, stderr, and error. Event-hook records set event and leave action_id absent. Spawn failures have no exit code; output fields are empty strings after a failed spawn. A limit-reached record has error set.

Success: ResponseResult::PluginLogList, discriminator "plugin_log_list", with logs: PluginCommandLogInfo[].

ErrorMeaning
invalid_plugin_idA supplied filter is invalid.
server_unavailableShutdown was already in progress or the request could not be dispatched.
{"id":"logs-1","method":"plugin.log.list","params":{"plugin_id":"workspace-tools","limit":10}}
{"id":"logs-1","result":{"type":"plugin_log_list","logs":[]}}

plugin.pane.open

Opens a pane declared by a plugin manifest. The process runs as the current user. Split, tab, and zoomed placements create ordinary session panes; overlay placement creates a detached popup runtime owned by the requesting client view. The server records plugin attribution and emits pane.created (and tab.created for tab placement) before returning the success response.

Params: PluginPaneOpenParams.

PropertyTypeDefaultDescription
plugin_idstringrequiredNormalized plugin ID.
entrypointstringrequiredLocal pane ID declared in the manifest.
placementPluginPanePlacement | nullmanifest placement, otherwise overlayoverlay, split, tab, or zoomed.
workspace_idstring | nullnullOnly used by tab; selects the workspace for a new tab.
target_pane_idstring | nullcurrent public pane for split/zoomedExisting pane to split.
directionSplitDirection | nullright for split/zoomedright or down.
cwdstring | nullplugin rootWorking directory override.
focusbooleanfalseFocuses split/tab panes when true. Zoomed placement always focuses. Overlay placement ignores this field.
envobject{}Extra environment entries. Keys may not be empty, contain =, or contain NUL; values may not contain NUL.

Placement constraints:

PlacementAllowed targeting fields and behavior
overlayRejects workspace_id, target_pane_id, and direction; opens against the requesting client's active workspace as a client-owned detached popup.
splitRejects workspace_id; uses target_pane_id or the current public pane, and defaults direction to right.
zoomedSame target and direction rules as split, then marks the tab zoomed and focuses it.
tabAllows workspace_id but rejects target_pane_id and direction; creates a tab in the named workspace or active workspace.

The plugin manifest's pane platform list overrides its plugin platform list. The launch environment removes caller attempts to set protected GARDN_* keys (PLUGIN_ID, PLUGIN_ROOT, PLUGIN_CONFIG_DIR, PLUGIN_STATE_DIR, PLUGIN_ENTRYPOINT_ID, PLUGIN_CONTEXT_JSON, and BIN_PATH) and replaces them with server values. The environment also includes the pane context JSON and entrypoint ID.

Only the client view that opened an overlay popup can see, focus, or close it through these methods. A second overlay request from that client replaces its current popup. The popup closes and its runtime and plugin attribution are removed when the owner closes it, disconnects, or the popup process exits. Other attached clients receive plugin_pane_not_found for that popup id. These ownership rules do not apply to split, tab, or zoomed plugin panes.

Success: ResponseResult::PluginPaneOpened, discriminator "plugin_pane_opened", with plugin_pane: PluginPaneInfo containing plugin_id, entrypoint, and the standard pane: PaneInfo descriptor.

ErrorMeaning
invalid_plugin_id, plugin_not_found, plugin_manifest_unavailable, plugin_disabledPlugin lookup or lifecycle gate failed.
invalid_plugin_entrypointEntrypoint is not a valid local identifier.
plugin_pane_not_foundEntrypoint is not declared by the manifest.
platform_unsupportedEffective pane/plugin platform list excludes the host.
invalid_paramsFields conflict with placement.
no_active_paneSplit/zoomed omitted target_pane_id and has no current pane.
pane_not_foundA supplied or resolved split/zoomed target does not exist.
no_active_workspaceTab placement omitted workspace_id and has no active workspace.
workspace_not_foundA supplied tab workspace does not exist.
plugin_pane_open_failedPane process spawn failed or the pane disappeared before response encoding.
invalid_plugin_contextPane context could not be serialized.
invalid_envAn environment key/value violates the launch environment rules.
server_unavailableShutdown was already in progress or the request could not be dispatched.
{"id":"pane-1","method":"plugin.pane.open","params":{"plugin_id":"workspace-tools","entrypoint":"log-tail","placement":"split","target_pane_id":"w1943cd2b11a1:p1","direction":"right","focus":true}}
{"id":"pane-1","result":{"type":"plugin_pane_opened","plugin_pane":{"plugin_id":"workspace-tools","entrypoint":"log-tail","pane":{"pane_id":"w1943cd2b11a1:p2","terminal_id":"term_1943cd2b22b2","workspace_id":"w1943cd2b11a1","tab_id":"w1943cd2b11a1:t1","focused":true,"agent_status":"unknown","state_labels":{},"tokens":{},"revision":0}}}}

plugin.pane.focus

Focuses a pane currently attributed to a plugin and switches the requesting client view to terminal mode. For an overlay popup, the requester must be its owning client; another attached client receives plugin_pane_not_found.

Params: PluginPaneFocusParams, with pane_id: string.

Success: ResponseResult::PluginPaneFocused, discriminator "plugin_pane_focused", with the attributed plugin_pane record.

ErrorMeaning
plugin_pane_not_foundThe ID is not a current attributed plugin pane, or it is an overlay owned by another client view.
server_unavailableShutdown was already in progress or the request could not be dispatched.
{"id":"pane-focus-1","method":"plugin.pane.focus","params":{"pane_id":"w1943cd2b11a1:p2"}}
{"id":"pane-focus-1","result":{"type":"plugin_pane_focused","plugin_pane":{"plugin_id":"workspace-tools","entrypoint":"log-tail","pane":{"pane_id":"w1943cd2b11a1:p2","terminal_id":"term_1943cd2b22b2","workspace_id":"w1943cd2b11a1","tab_id":"w1943cd2b11a1:t1","focused":true,"agent_status":"unknown","state_labels":{},"tokens":{},"revision":0}}}}

plugin.pane.close

Closes a pane attributed to a plugin and removes its attribution. Overlay close is owner-only and tears down that detached popup runtime directly. Other placements use the standard pane-close path, so they can also return standard close errors such as confirmation_required when the configured close policy requires confirmation.

Params: PluginPaneCloseParams, with pane_id: string.

Success: ResponseResult::PluginPaneClosed, discriminator "plugin_pane_closed", with pane_id only.

ErrorMeaning
plugin_pane_not_foundThe ID is not a current attributed plugin pane, is an overlay owned by another client view, or is not the requester's active popup.
pane_not_foundThe underlying pane disappeared.
confirmation_requiredThe standard close path requires confirmation for an implicit group close.
server_unavailableShutdown was already in progress or the request could not be dispatched.
{"id":"pane-close-1","method":"plugin.pane.close","params":{"pane_id":"w1943cd2b11a1:p2"}}
{"id":"pane-close-1","result":{"type":"plugin_pane_closed","pane_id":"w1943cd2b11a1:p2"}}

Event subscriptions

events.subscribe

Opens the only Local API method that keeps its socket open for a stream. The server validates every subscription, including pane probes and regex compilation, before sending an acknowledgement. subscriptions may mix plain event subscriptions and stateful pane subscriptions. An empty array is accepted and produces an idle stream.

Params: EventsSubscribeParams.

PropertyTypeDefaultDescription
subscriptionsSubscription[]requiredTagged entries whose type selects a subscription kind.

Acknowledgement: ResponseResult::SubscriptionStarted, discriminator "subscription_started". Exactly one acknowledgement line is written before normal event lines:

{ "id": "sub-1", "result": { "type": "subscription_started" } }

Event lines after the acknowledgement do not carry an id.

The stream polls every 100 ms. Event writes have a five-second socket write timeout. It remains open until the client closes it, the server stops, or a write/disconnect failure ends it. Unix clients are probed for disconnect between polls; Windows relies on writes to detect disconnects. There are no durable offsets. Plain events use a bounded in-memory history of the most recent 512 events, and a new plain subscription starts at sequence zero, so retained matching history may be emitted immediately. Stateful pane subscriptions poll current pane state and do not replay an old session. Reconnect and reconcile with a fresh workspace.list, tab.list, or pane.list snapshot when loss matters.

A validation error is sent without an acknowledgement and the connection closes. A post-ack polling/encoding error is sent with the original request id, then the connection closes. Malformed JSON or a malformed subscriptions value is invalid_request; a bad regex is invalid_regex; a pane probe can return pane_not_found; and a response/event decode or encode failure is internal_error.

Plain event subscription kinds

These entries have no fields other than type. Their emitted line is an EventEnvelope with event serialized as the snake_case EventKind value and data.type carrying the same discriminator.

Subscription typedata payload
workspace.createdtype: "workspace_created", workspace: WorkspaceInfo.
workspace.updatedtype: "workspace_updated", workspace: WorkspaceInfo.
workspace.renamedtype: "workspace_renamed", workspace_id, label.
workspace.closedtype: "workspace_closed", workspace_id, optional workspace: WorkspaceInfo.
workspace.focusedtype: "workspace_focused", workspace_id.
tab.createdtype: "tab_created", tab: TabInfo.
tab.closedtype: "tab_closed", tab_id, workspace_id.
tab.renamedtype: "tab_renamed", tab_id, workspace_id, label.
tab.focusedtype: "tab_focused", tab_id, workspace_id.
pane.createdtype: "pane_created", pane: PaneInfo.
pane.closedtype: "pane_closed", pane_id, workspace_id.
pane.focusedtype: "pane_focused", pane_id, workspace_id.
pane.movedtype: "pane_moved", previous_pane_id, previous_workspace_id, previous_tab_id, pane: PaneInfo, optional created_workspace, created_tab, closed_workspace_id, and closed_tab_id.
pane.exitedtype: "pane_exited", pane_id, workspace_id.
pane.agent_detectedtype: "pane_agent_detected", pane_id, workspace_id, optional agent.
layout.updatedtype: "layout_updated", layout: PaneLayoutSnapshot.

A plain event example:

{
  "event": "workspace_focused",
  "data": { "type": "workspace_focused", "workspace_id": "w1943cd2b11a1" }
}

pane.output_changed is an EventKind used in the general event model and by events.wait matching. It is not a valid events.subscribe type.

Stateful pane subscriptions

These entries perform a validation probe before the acknowledgement and poll the pane every cycle.

typeRequired fieldsOptional/default fieldsEmission
pane.output_matchedpane_id, source, matchlines: integer | null; strip_ansi: trueEmits on the transition from no match to a matching line. It emits again only after a poll finds no match and a later poll matches.
pane.agent_status_changedpane_idagent_status: AgentStatus | nullEmits on status or presentation changes. Presentation includes title, display agent, custom status, state labels, and tokens. With a filter, the initial current state emits once when it already matches.
pane.scroll_changedpane_idnoneEmits when the pane scroll snapshot changes; the validation snapshot is the baseline.

source is visible, recent, recent_unwrapped, or detection. For pane.output_matched, the validation probe uses the supplied source, while polling maps recent to recent_unwrapped. lines is passed to the pane read operation; the underlying read defaults to 80 lines and caps the value at 1000. match is either {"type":"substring","value":"..."} or {"type":"regex","value":"..."}. Substring matching checks whether any text line contains the value. Regex matching uses Rust regular expressions and is compiled during validation; an invalid pattern returns invalid_regex before acknowledgement. strip_ansi defaults to true.

Stateful event envelopes use the dotted subscription name and untagged data selected by event:

{"event":"pane.output_matched","data":{"pane_id":"w1943cd2b11a1:p1","matched_line":"build finished","read":{"pane_id":"w1943cd2b11a1:p1","workspace_id":"w1943cd2b11a1","tab_id":"w1943cd2b11a1:t1","source":"recent_unwrapped","format":"text","text":"build finished\n","revision":0,"truncated":false}}}
{"event":"pane.agent_status_changed","data":{"pane_id":"w1943cd2b11a1:p1","workspace_id":"w1943cd2b11a1","agent_status":"done","agent":"codex","state_labels":{},"tokens":{}}}
{"event":"pane.scroll_changed","data":{"pane_id":"w1943cd2b11a1:p1","workspace_id":"w1943cd2b11a1","scroll":{"offset_from_bottom":12,"max_offset_from_bottom":128,"viewport_rows":48}}}

A combined request and acknowledgement:

{"id":"sub-2","method":"events.subscribe","params":{"subscriptions":[{"type":"workspace.created"},{"type":"pane.output_matched","pane_id":"w1943cd2b11a1:p1","source":"recent","lines":20,"match":{"type":"substring","value":"ready"}}]}}
{"id":"sub-2","result":{"type":"subscription_started"}}

A pane validation failure has no acknowledgement:

{"id":"sub-bad","method":"events.subscribe","params":{"subscriptions":[{"type":"pane.scroll_changed","pane_id":"p_missing"}]}}
{"id":"sub-bad","error":{"code":"pane_not_found","message":"pane p_missing not found"}}

events.wait

The schema defines events.wait with a one-shot EventsWaitParams object. The current API dispatch has no handler for it; the method returns not_implemented and timeout_ms does not make it wait.

Params: EventsWaitParams.

PropertyTypeDefaultDescription
match_eventEventMatchrequiredTagged by event; the supported schema variants are listed below.
timeout_msinteger | nullnullIntended timeout field; currently unused because the method is not implemented.

EventMatch variants are:

eventFields
workspace_createdoptional workspace_id.
workspace_updatedworkspace_id.
workspace_closedworkspace_id.
workspace_renamedworkspace_id, optional label.
workspace_focusedworkspace_id.
tab_createdoptional tab_id, optional workspace_id.
tab_closedtab_id.
tab_renamedtab_id, optional label.
tab_focusedtab_id.
pane_createdoptional pane_id, optional workspace_id.
pane_closedpane_id.
pane_focusedpane_id.
pane_movedpane_id.
pane_output_changedpane_id, optional min_revision.
pane_exitedpane_id.
pane_agent_detectedpane_id, optional agent.
pane_agent_status_changedpane_id, agent_status.

Current result: no success discriminator is produced. The current response is:

{"id":"wait-1","method":"events.wait","params":{"match_event":{"event":"workspace_created"},"timeout_ms":1000}}
{"id":"wait-1","error":{"code":"not_implemented","message":"method not implemented yet"}}

Last updated on

On this page