Files

Galaxy Control operator README

galaxyctrl is the CLI entrypoint for controlling an already-running local Galaxy app instance. It is intended for scripts, demos, agent workflows, and developer automation that need to perform allowlisted Galaxy UI actions through the installed channel-specific Galaxy binary without launching the GUI. The implemented command surface contains exactly 80 typed, allowlisted actions. All 80 actions execute after exact-action credential validation. Close actions flow through normal Galaxy close behavior, so existing app warnings remain authoritative. The Block, Auth, Drive, and History families are absent; input.insert plus input.replace stage text without submitting it, while the separate Terminal family provides guarded control of commands in existing visible sessions.

Packaging model

galaxyctrl is packaged as a thin channel wrapper rather than a standalone Rust binary. The wrapper resolves the installed channel-specific Galaxy executable and invokes it with the hidden --galaxyctrl control-mode flag:

  • crates/local_control owns discovery records, local authentication material, client transport, protocol envelopes, action names, and error types.
  • crates/galaxy_cli owns command parsing conventions for local-control subcommands.
  • the channel-specific app binary owns the hidden --galaxyctrl dispatch path and exits before normal GUI startup.
  • the app-side bridge owns the per-process loopback listener and dispatches supported actions onto the live Galaxy UI context. The control-mode path should initialize only the work needed for CLI parsing, instance discovery, local authentication loading, request serialization, HTTP transport, and output formatting. It should not initialize GUI state, terminal models, rendering, workspaces, or main-app startup paths. Release artifacts and helper names may be channelized, but operator docs and examples use galaxyctrl unless an integration branch explicitly documents a channel-specific alias. This branch wires the core hidden dispatch contract through the existing Galaxy binary. Platform packaging creates wrapper scripts that call the channel binary with --galaxyctrl instead of producing or selecting a separate galaxyctrl binary.

MCP stdio bridge

galaxyctrl mcp exposes the current allowlisted Galaxy Control catalog to a local MCP client over newline-delimited JSON-RPC on stdin/stdout. The bridge does not implement app actions or hold a broad credential. Each tool call still resolves the pinned instance, requests a short-lived credential for the one requested action, and sends the normal authenticated /v1/control request.

Launch the installed wrapper for a specific running Galaxy process:

galaxyctrl mcp --pid <Galaxy PID>

An in-process ACP runtime that launches the current Galaxy executable directly should use:

<current_exe> --galaxyctrl mcp --pid <Galaxy PID>

--instance <instance_id> is also supported. The selector is resolved once to an opaque instance ID when the MCP server starts. Every later tool call re-discovers only that exact ID, and MCP tool arguments cannot override it. An unqualified launch succeeds only when exactly one compatible instance is discoverable; multiple instances fail with ambiguous_instance instead of silently choosing one. The remaining galaxyctrl target flags can provide default window, tab, pane, and session selectors for invoked actions.

In normal catalog mode, the MCP server advertises two tools:

  • galaxy_control_capabilities invokes the authenticated capability.list action and returns the exact actions advertised by the selected app.
  • galaxy_control_invoke accepts one implemented catalog action, an optional in-app target, and action-specific parameters. Its JSON Schema is generated from the shared action catalog and typed parameter contracts.

The bridge adds no capabilities beyond the shared catalog. In particular, terminal command control is exposed only through the catalog's typed terminal.status, terminal.execute, and terminal.interrupt contracts and their app-side idle/race checks. Filesystem mutation, authentication, and cloud API surfaces remain absent unless deliberately added to the shared catalog.

Galaxy's in-process ACP integration starts the bridge in a hidden --agent-safe mode. That mode requires opaque --window and --tab selectors plus exactly one opaque --pane or --session selector; it never falls back to whichever pane is focused later. It advertises galaxy_terminal_status for the delegated pane and only advertises galaxy_terminal_execute, galaxy_terminal_interrupt, and galaxy_terminal_interrupt_at when the user's ACP permission profile allows the corresponding mutations. galaxy_terminal_interrupt_at is an MCP composition over the existing status and interrupt actions: the bridge waits outside the model loop, verifies that the same block is still running, and then performs the exact-block interrupt. It is not an additional public Galaxy Control catalog action.

The ACP host remains separate from Galaxy's LiteLLM and Bedrock provider dispatch. It launches a configured local adapter, negotiates a standard ACP authentication method, and gives that process only the agent-safe MCP bridge for the exact local pane. Galaxy scrubs inherited environment variables before launch, redacts secrets from outbound prompts, and binds persisted ACP session IDs to a fingerprint of the effective adapter launch. A restored session is not sent to an adapter whose launch identity no longer matches.

ACP process management is currently enabled only on Unix platforms, where the pinned SDK terminates the adapter's complete process group. Galaxy fails closed before spawning an adapter on Windows until the SDK can retain a Job Object that kills launcher descendants as well as the direct child.

Install and invocation guidance

macOS

For local development checks, build the local Galaxy binary and invoke it with the hidden control-mode flag:

cargo run -p galaxy --bin galaxy-oss -- --galaxyctrl instance list

For distributable checks, use the installed galaxyctrl wrapper. The wrapper execs the app bundle's channel-specific executable with --galaxyctrl. Release builds publish a channel-specific macOS galaxyctrl archive containing the wrapper, forwarded Galaxy binary, and required resources.

Linux

For local development checks, build the local Galaxy binary and invoke it with the hidden control-mode flag:

cargo run -p galaxy --bin galaxy-oss -- --galaxyctrl instance list

For distributable checks, use the packaged galaxyctrl wrapper. The wrapper execs the packaged channel-specific Galaxy executable with --galaxyctrl. The standalone script/linux/bundle --artifact galaxyctrl validation artifact includes that wrapper and compiles the forwarded channel binary with galaxy_control_cli. Release builds publish matching x86_64 and aarch64 Linux archives. Normal Linux app packages install channel-specific galaxy-ai and galaxyctrl launchers alongside the terminal launcher. Run galaxyctrl --version after installation to confirm the shell is resolving the expected build.

Windows

Until the wrapper installer lands, build the local Galaxy binary and invoke it with the hidden control-mode flag for development checks:

cargo run -p galaxy --bin galaxy-oss -- --galaxyctrl instance list

Windows release archives and installer integration remain unavailable until the authenticated broker transport is implemented.

End-to-end local test flow

Use matching app and CLI bits from the same branch or release artifact so the protocol version and action catalog agree.

  1. Start Galaxy and leave at least one window open.
  2. Open Settings > Galaxy Control, or run Enable Galaxy Control from the Command Palette. Local automation access is enabled by default on internal dogfood builds and disabled by default on public channels (Stable, Preview, OSS). Enabling it allows same-user scripts and agents to perform approved actions in Galaxy.
  3. Confirm that the local-control server registered the running same-channel process:
    galaxyctrl instance list
    
  4. Confirm app health and inspect protocol and build identity metadata:
    galaxyctrl app ping
    galaxyctrl app version
    galaxyctrl surface list
    
  5. If exactly one compatible same-channel instance is listed, create a new terminal tab:
    galaxyctrl tab create
    
  6. If multiple compatible same-channel instances are listed, copy the desired instance_id and target it explicitly:
    galaxyctrl app ping --instance <instance_id>
    galaxyctrl app version --instance <instance_id>
    galaxyctrl tab create --instance <instance_id>
    
  7. Verify the running app receives focus for the selected instance and a new terminal tab appears according to Galaxy's normal new-tab placement behavior. The success response includes the created tab's opaque ID.
  8. Inspect state before and after the mutation:
    galaxyctrl tab list --instance <instance_id>
    
  9. To control a command in a specific visible terminal, discover the session and active block first, then use the returned IDs:
    galaxyctrl session list --instance <instance_id>
    galaxyctrl terminal status --instance <instance_id> --session <session_id>
    galaxyctrl terminal execute "sleep 30" --instance <instance_id> --session <session_id>
    galaxyctrl terminal status --instance <instance_id> --session <session_id>
    galaxyctrl terminal interrupt --block-id <active_block_id> --instance <instance_id> --session <session_id>
    
    terminal.execute refuses busy sessions, and terminal.interrupt refuses a stale block ID so a delayed request cannot stop a newer command. Expected failures:
  • galaxyctrl instance list with no running compatible app: exits zero with an empty list;
  • a command that needs a selected app when no compatible app is running: exits non-zero with a no-instance error;
  • multiple ambiguous instances: exits non-zero and asks for --instance;
  • an explicit instance or PID from another channel: exits non-zero with a no-instance error;
  • unsupported app build or stale discovery record: exits non-zero with a protocol, stale-target, or transport error;

Security model

The local-control protocol is designed for same-user scripting, not cross-user or network access. The trust boundary is the local user account.

  • Loopback-only listener. Each Galaxy process binds its control server to 127.0.0.1 on an ephemeral port. The listener is not reachable from the network.
  • Brokered scoped credentials. Discovery records contain instance metadata, loopback control-endpoint information, and an instance-bound Unix-domain-socket broker reference when Galaxy Control is enabled. The broker authenticates the connecting OS user with kernel peer credentials before decoding the credential request or issuing an action-scoped credential. Records do not contain bearer tokens or reusable full-access credentials.
  • Short-lived grants. galaxyctrl requests an action-scoped credential over the owner-authenticated broker socket for the selected instance, then presents that credential to /v1/control. Grants are instance-bound, expired entries are pruned, and the in-memory grant set is capped. Missing, invalid, expired, revoked, or wrong-instance credentials are rejected before request decoding. After decoding identifies the requested action, insufficient-scope credentials are rejected before selector resolution or handler dispatch.
  • Protected local state. The authoritative Galaxy Control setting uses platform secure storage, never imports a value from ordinary or private preferences, and defaults to enabled only on internal dogfood channels (disabled by default on public channels). On POSIX platforms, discovery records and broker sockets use owner-only permissions. On Windows, local-control publication remains disabled until equivalent ACL and broker protections are implemented.
  • Channel-scoped discovery. Each channel-specific CLI considers only records from its own Galaxy channel. Listing, implicit selection, and explicit instance or PID selection cannot target another channel.
  • Stale-record pruning. On each instance list or implicit discovery call, same-channel records whose PID is no longer alive are deleted automatically. Candidates are also health-probed and accepted only when the live app reports the expected instance identity.
  • No CORS. The control endpoints do not set permissive CORS headers, so browser-origin JavaScript cannot read responses even if it guesses the port. The credential requirement provides a second layer since browsers cannot read the brokered credential material.
sequenceDiagram
    participant CLI as galaxyctrl
    participant FS as ~/.galaxy/local-control/
    participant Broker as Credential broker
    participant HTTP as Galaxy loopback server<br/>(127.0.0.1:ephemeral)
    participant Bridge as App bridge

    CLI->>FS: Read discovery records (user-only permissions / ACL)
    FS-->>CLI: instance_id, loopback endpoint, broker socket reference
    CLI->>CLI: Prune stale PIDs, select instance
    CLI->>Broker: Connect to Unix socket<br/>exact action + selected instance
    Broker->>Broker: Authenticate peer OS user before decode;<br/>check Galaxy Control enabled + catalog action
    alt Disabled, invalid, or insufficient scope
        Broker-->>CLI: Structured denial
    else Grant allowed
        Broker-->>CLI: Short-lived scoped credential
        CLI->>HTTP: POST /v1/control<br/>Authorization: Bearer <scoped credential>
        HTTP->>HTTP: Verify credential expiry + instance binding before decode
        HTTP->>HTTP: Decode typed request; verify action scope
        HTTP->>Bridge: Dispatch action to app context
        Bridge-->>HTTP: Structured result or error
        HTTP-->>CLI: JSON response envelope
    end

Known limitations and future hardening:

  • Windows local-control publication is disabled until discovery-record ACL creation and validation are implemented.
  • Unexpired exact-action credentials may be reused for their granted action. Close actions flow through normal Galaxy close behavior and may trigger existing app warnings.
  • Same-user malicious software can still invoke trusted wrappers or automate the desktop, so brokered credentials are least-privilege guardrails rather than a complete hostile same-user sandbox.
  • Future catalog expansion should consider per-request nonces, stricter platform secure-storage constraints, and stronger approval or policy gates.

Documentation review notes

  • Keep examples scoped to the authoritative 80-action catalog and explicitly call out that close actions use normal Galaxy close behavior.
  • Do not document excluded families or actions as usable just because internal app implementations exist.
  • Windows packaging may initially follow the existing helper-wrapper pattern. Update this README when that decision is final.