Discovery
sitectl discovers plugins by searching$PATH for binaries named sitectl-<plugin>. No configuration file is needed. If sitectl-drupal is on your path, sitectl drupal works.
When a full plugin inspection is needed (e.g. for sitectl converge, sitectl validate, or sitectl verify), sitectl runs sitectl-<plugin> __sitectl-rpc with a JSON request envelope for the plugin.metadata method. The plugin returns a JSON response envelope with its capabilities: name, version, description, included plugins, and boolean flags for which SDK runner interfaces it has registered (CanDebug, CanConverge, CanSet, CanValidate, CanHealthcheck, CanVerify, CanDeploy, CanCreate).
For performance-sensitive paths like sitectl --help, sitectl uses lightweight discovery (binary name only, no subprocess invocation) and falls back to full inspection only when needed.
Command routing
When you runsitectl drupal drush cr, sitectl:
- Looks up
sitectl-drupalon$PATH - Invokes it with
drush cras arguments, plus any sitectl flags (--context,--log-level) prepended - The plugin binary handles the rest and writes output to stdout/stderr
plugin field validates that the command makes sense for the context. Running sitectl drupal drush against a context with plugin: core returns an error.
Plugin inclusion
Plugins declare which other plugins they include via theIncludes field in their SDK metadata. ISLE declares:
isle plugin implicitly covers all drupal commands. A context with plugin: isle accepts both sitectl isle ... and sitectl drupal ... commands.
Application plugins use the same mechanism only for real application hierarchies. Shared service commands such as sitectl mariadb, sitectl solr, and sitectl traefik are core command namespaces, so application plugins do not include service plugins just to expose common service operations. The standalone app compose project carries app-specific service wiring.
sitectl resolves inclusion transitively: if isle includes drupal and drupal includes core, then isle covers all three.
Create Definitions
Plugins expose create definitions throughplugin.metadata. A create definition describes a first-class Compose template: repository, branch, lifecycle commands, init artifacts, image specs, and the default definition for the plugin.
Core sitectl consumes create definitions in several places:
sitectl create listandsitectl create <plugin>/<definition>for guided stack creationsitectl image setand create-time image flags for local Compose image and build-arg overridessitectl compose upfor local first-run reconcile on plugin-owned contexts
compose up contract and Application plugin template for the thin app-plugin scaffold.
RPC protocol
Core sitectl commands that fan out to plugins do so through one private entrypoint:__sitectl-rpc. The host sends a JSON request envelope and the plugin writes one JSON response envelope to stdout. Human-facing plugin output is captured and returned in the response output field; structured data is returned in result. Plugin logs, progress, and interactive prompts use stderr so stdout remains reserved for the envelope.
The SDK registers __sitectl-rpc automatically in plugin.NewSDK. Plugin authors normally register typed SDK handlers; they do not write transport code.
For non-interactive RPC calls, core sitectl writes the JSON request to the plugin’s stdin. When a plugin handler needs stdin for prompts or an interactive command, core sitectl passes the request as a base64-encoded JSON value in --request and leaves stdin attached to the handler. Because argv is visible in process listings, method params used with --request must not carry secrets.
Both transports reject request envelopes larger than 4 MiB. Interactive calls also reject requests built from params marked with RPCSensitiveParams or the rpc_sensitive:"true" struct tag, because those calls would expose the encoded request through process argv.
Plugin stdout is fully buffered by the RPC layer and appears in response.output after the plugin exits. Long-running progress should be written to stderr; stdout should be reserved for final human-facing output or command results.
protocol_version is currently 1. A missing or zero value is treated as the current version for backward compatibility; any explicit non-current version is rejected.
Use the typed request builders in pkg/plugin (NewJobRunRequest, NewComponentDescribeRequest, NewValidateRunRequest, NewHealthcheckRunRequest, NewVerifyRunRequest, etc.) when invoking RPC from core or another plugin. These builders encode the method-specific params contract once, preserve sensitive-param metadata, and avoid hand-written JSON maps. args is only for true passthrough arguments that are owned by the plugin command itself.
The JSON field names are part of the host/plugin wire contract. Envelope fields use lower snake_case tags exactly as shown below: protocol_version, method, context, log_level, args, params, ok, result, output, error, code, and message. Method params and result payloads follow the same lower snake_case rule.
Request envelope:
ok to false:
The response fields are intentionally split:
Because stdout is the response channel, plugin code must not write ad hoc text directly to stdout during RPC handling. Use Cobra command output or SDK helpers for returned
output, and use stderr for logs, prompts, and streaming progress.
Host-owned flags are encoded in params, not args. Examples include name, path, report, verbose, format, yolo, and the generic codebase_rootfs field. The RPC contract uses codebase_rootfs so Drupal, ISLE, and non-Drupal plugins can share the same concept.
When a runner binds its own rootfs flag name, call plugin.MarkCodebaseRootfsFlag(cmd, flagName) so typed codebase_rootfs params are reconstructed into the correct Cobra flag before the command runs. Document --codebase-rootfs as the canonical flag in operator-facing docs.
Plugin Authoring Notes
- Plugin command handlers must write user-visible output through
cmd.OutOrStdout()and errors throughcmd.ErrOrStderr(). Direct writes toos.Stdoutare reserved for the RPC response envelope. - Plugin commands must declare every host-bridged RPC flag they accept; registration validates this and panics on missing flags.
- Do not put secrets in RPC args or params because interactive requests may be transported through process argv.
Method catalog
debug.run
Registered by sdk.RegisterDebugRunner(runner). Core sitectl debug invokes this after collecting its own diagnostics when the plugin advertises CanDebug: true. The plugin appends its own sections to the report.
Interface:
deploy.run
Registered by sdk.RegisterDeployRunner(runner). Core sitectl deploy calls deploy.run with pre-down before docker compose down and post-up after docker compose up.
Interface:
job.list / job.run
Registered via sdk.RegisterJob(spec) or sdk.RegisterContextJob(spec, runner). Core uses job.list to discover jobs and job.run to invoke the owning plugin.
component.list / component.describe / component.reconcile / component.set
Registered by sdk.RegisterServiceComponents(...) or sdk.RegisterComponentCommand(cmd) for custom component implementations. These power the lower-level sitectl component list, sitectl component describe, sitectl component reconcile, and sitectl component set commands.
converge.run
Registered by sdk.RegisterConvergeRunner(runner). Core sitectl converge invokes this after confirming the context’s plugin supports it (CanConverge: true in metadata). Core-owned flags such as --path, --report, --verbose, --format, and rootfs overrides are passed as typed params; plugin-specific flags remain in passthrough args.
Interface:
isleConvergeRunner) delegates to the existing component reconcile machinery.
set.run
Registered by sdk.RegisterSetRunner(runner). Core sitectl set invokes this after resolving the owning plugin from either the context or the plugin/component namespace prefix in the component argument.
Interface:
validate.run
Registered by sdk.RegisterValidateRunner(runner). Core sitectl validate runs its own core validators first, then invokes validate.run if the context plugin supports validation (CanValidate: true). The plugin returns []validate.Result in the JSON response result. Core merges those results with its own before rendering the final report.
This is different from the other fan-out commands: the merge happens in core, not the plugin. The plugin does not know about or render the core results.
The SDK calls ValidateRunner.Run and marshals the returned results into the RPC result. A validate runner should not render its own validation report or write result JSON to stdout. Diagnostics that should stream during validation should go to cmd.ErrOrStderr().
Interface:
healthcheck.run
Registered by sdk.RegisterHealthcheckRunner(runner). Core sitectl healthcheck checks Compose service containers first, then - if the context plugin supports healthcheck (CanHealthcheck: true) - invokes healthcheck.run. The plugin returns []validate.Result in the JSON response result. Core merges those runtime results with the Compose service results before rendering the final report.
The SDK calls HealthcheckRunner.Run and marshals the returned results into the RPC result. A healthcheck runner should not render its own report or write result JSON to stdout. Diagnostics that should stream during health checks should go to cmd.ErrOrStderr().
Interface:
verify.run
Registered by sdk.RegisterVerifyRunner(runner). Core sitectl verify invokes verify.run when the active context plugin supports verification (CanVerify: true). Use this runner for CI and update validation checks that go beyond basic running-app health, such as important application workflows or feature-specific assertions that should survive a site update. The plugin returns []validate.Result in the JSON response result, and core sorts and renders those results with the normal validation report renderer.
Unlike validate and healthcheck, core verify has no built-in checks today. A context with no plugin-specific verify runner renders a warning result rather than failing. --format is host-owned because core renders the final report; all other unclaimed flags are passed through in args for the plugin’s verify runner.
The SDK calls VerifyRunner.Run and marshals the returned results into the RPC result. A verify runner should not render its own report or write result JSON to stdout. Diagnostics that should stream during verification should go to cmd.ErrOrStderr().
Interface:
create.run / create.component_definitions
Registered implicitly when create definitions are registered. create.run powers sitectl create <plugin>/<definition> flows. create.component_definitions lets including plugins reuse component create options from included plugins.
Create definitions are also used outside create.run: core reads them from metadata for sitectl create list, local image override handling, and command-scoped sitectl compose up reconcile.
Core fan-out summary
Shared renderer
Whether it’s debug output or component status, each plugin’s contribution is rendered usingdebugui.RenderPanel from pkg/plugin/debugui. This keeps output consistently formatted regardless of which plugins contributed. Plugin authors must use the shared renderer rather than rolling their own panel format.
Invoking included plugins from plugin code
A plugin that includes other plugins can invoke registered RPC methods programmatically via the SDK:InvokeIncludedPluginRPC enforces that you can only call plugins explicitly listed in your Includes. It returns an error if you try to call a plugin your metadata does not declare.
For command-like methods that may prompt, pass Stdin, Stderr, and LiveStderr: true; the SDK will use --request for the envelope and keep stdin available to the plugin handler. Stdout remains reserved for the JSON response envelope.
Adding a new plugin
A new sitectl plugin is a standalone Go binary that:- Creates an SDK instance via
plugin.NewSDK(metadata) - Registers its commands and runners with the SDK (
sdk.AddCommand,sdk.RegisterDebugRunner,sdk.RegisterConvergeRunner, etc.) - Calls
sdk.Execute()to enter the Cobra command tree
sitectl-<name> convention, and metadata.Name must match the suffix. The binary must be on $PATH for core sitectl to discover it.
See the development guide for how to set up a local workspace with make work for developing against a local sitectl checkout.
