Runtime
This page covers building custom runtime providers. If you only need to choose or configure a runtime backend for a deployment, use Providers > Runtime.
Runtime providers are executable provider packages that manage hosted executable-plugin sessions. They run as normal provider processes from Gestalt’s point of view, but instead of exposing plugin operations directly, they expose a control plane for starting and managing hosted plugins.
What a runtime provider does
A runtime provider answers a small set of control-plane questions for
gestaltd. Can this backend host executable plugins at all. How does Gestalt
create and observe a runtime session there. How does a hosted plugin reach
Gestalt-owned services. How is hostname-based egress preserved. How does the
host dial the hosted plugin back once it starts.
It does not replace the host-local authentication, authorization,
IndexedDB, cache, S3, secrets, or workflow providers. Those remain on the
gestaltd host. A runtime provider only changes where the plugin process runs
and how the host reaches it.
Manifest
A runtime provider manifest declares kind: runtime:
kind: runtime
source: github.com/your-org/runtime/example
version: 0.0.1
displayName: Example Runtime
description: Hosted runtime backend for executable plugins.
entrypoint:
artifactPath: ./bin/example-runtime
spec:
configSchemaPath: ./schemas/config.schema.yamlLike authentication, cache, IndexedDB, S3, secrets, and workflow providers,
runtime providers use the common provider package model: manifest, executable
entrypoint, optional config schema, and the normal provider-release.yaml
packaging flow.
Deployment config
Operators configure runtime providers under top-level runtime.providers, not
under plugins or providers.*:
runtime:
providers:
example:
source: ./runtime/example/manifest.yaml
default: true
config:
region: us-east-1
pool: defaultPlugins then opt into that runtime with plugins.<name>.execution.mode: hosted
and plugins.<name>.execution.runtime.
Provider interface
The runtime surface is intentionally small but lifecycle-oriented:
| Method | Purpose |
|---|---|
GetSupport | Advertise grouped runtime support for this backend. |
StartSession | Create a hosted runtime session for a plugin. |
GetSession | Report session status so Gestalt can wait for readiness. |
StopSession | Tear a session down on shutdown or bootstrap failure. |
BindHostService | Bind a host-local socket into the runtime session. |
StartPlugin | Start the hosted plugin and return a host-reachable dial target. |
In addition to the runtime-specific RPCs, the provider still implements the
normal provider lifecycle surface, including Configure.
Go example
package exampleruntime
import (
"context"
gestalt "github.com/valon-technologies/gestalt/sdk/go"
proto "github.com/valon-technologies/gestalt/sdk/go/gen/v1"
"google.golang.org/protobuf/types/known/emptypb"
)
type Provider struct {
proto.UnimplementedPluginRuntimeProviderServer
}
func (p *Provider) Configure(context.Context, string, map[string]any) error {
return nil
}
func (p *Provider) GetSupport(context.Context, *emptypb.Empty) (*proto.PluginRuntimeSupport, error) {
return &proto.PluginRuntimeSupport{
CanHostPlugins: true,
HostServiceAccess: proto.PluginRuntimeHostServiceAccess_PLUGIN_RUNTIME_HOST_SERVICE_ACCESS_DIRECT,
EgressMode: proto.PluginRuntimeEgressMode_PLUGIN_RUNTIME_EGRESS_MODE_HOSTNAME,
}, nil
}
func (p *Provider) StartSession(context.Context, *proto.StartPluginRuntimeSessionRequest) (*proto.PluginRuntimeSession, error) {
return &proto.PluginRuntimeSession{
Id: "session-1",
State: "ready",
}, nil
}
func (p *Provider) GetSession(context.Context, *proto.GetPluginRuntimeSessionRequest) (*proto.PluginRuntimeSession, error) {
return &proto.PluginRuntimeSession{
Id: "session-1",
State: "ready",
}, nil
}
func (p *Provider) StopSession(context.Context, *proto.StopPluginRuntimeSessionRequest) (*emptypb.Empty, error) {
return &emptypb.Empty{}, nil
}
func (p *Provider) BindHostService(context.Context, req *proto.BindPluginRuntimeHostServiceRequest) (*proto.PluginRuntimeHostServiceBinding, error) {
return &proto.PluginRuntimeHostServiceBinding{
Id: "binding-1",
SessionId: req.GetSessionId(),
EnvVar: req.GetEnvVar(),
Relay: req.GetRelay(),
}, nil
}
func (p *Provider) StartPlugin(context.Context, req *proto.StartHostedPluginRequest) (*proto.HostedPlugin, error) {
return &proto.HostedPlugin{
Id: "plugin-1",
SessionId: req.GetSessionId(),
PluginName: req.GetPluginName(),
DialTarget: "unix:///tmp/example-runtime/plugin.sock",
}, nil
}
func main() {
if err := gestalt.ServePluginRuntimeProvider(context.Background(), &Provider{}); err != nil {
panic(err)
}
}Support guidelines
Advertise the smallest truthful grouped support surface you can.
can_host_plugins should only be true when the backend can actually launch a
host-reachable executable plugin session. If that is false, Gestalt will reject
hosted execution for that runtime immediately instead of failing later during
bootstrap.
host_service_access should be direct only when the backend can expose
guest-local host-service sockets inside the runtime session. Most hosted
runtimes should report none and let Gestalt decide whether the current
deployment can provide relay-backed host services through the public relay
path. That relay path is host-provided behavior, not runtime-owned behavior.
egress_mode should be hostname only when the runtime can preserve
Gestalt’s hostname-based allowedHosts model. cidr is useful metadata, but
it is not equivalent to hostname-based policy. If your backend can only express
network controls at firewall or CIDR granularity, advertise cidr.
Hosted runtime providers should run provider images rather than expect Gestalt
to upload a provider bundle into the session. The StartSession request carries
the selected image, template, and metadata. StartPlugin then carries the
manifest-derived command and args to execute inside the image. Execute those
values exactly as provided by the host after applying any environment variables,
host-service bindings, and egress policy in the request.
New providers should populate support. That grouped support shape is the
runtime contract operators see in inspection.
Sessions and host services
The critical design point is that StartPlugin is not the whole runtime
API. Gestalt needs an explicit session lifecycle because it may need to create
the session, wait until it is ready, bind host-local services into it, and only
then start the plugin.
That is why BindHostService exists separately from StartPlugin. A binding
may be direct, using a guest-local socket path, or relay-backed, in which case
the provider receives a public dial target and the hosted plugin talks back to
gestaltd through the host-service relay.
Dial targets
StartPlugin returns a host-reachable dial_target, not a guest-local socket
path. Gestalt currently supports unix://..., tcp://host:port, and
tls://host:port.
If your backend starts the plugin behind a guest-local listener, the runtime provider is responsible for bridging that listener back into one of the host-reachable forms above.
Releasing
Runtime providers use the same release flow as other executable providers:
gestaltd provider release --platform linux/amd64 --output ./distThen point runtime.providers.<name>.source at the resulting
provider-release.yaml, or use a local manifest path during development.
What to read next
For the operator-facing model, see Providers > Runtime. For packaging, see Releasing. For config and manifest fields, see Config File and Provider Manifests.