Skip to Content

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.yaml

Like 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: default

Plugins 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:

MethodPurpose
GetSupportAdvertise grouped runtime support for this backend.
StartSessionCreate a hosted runtime session for a plugin.
GetSessionReport session status so Gestalt can wait for readiness.
StopSessionTear a session down on shutdown or bootstrap failure.
BindHostServiceBind a host-local socket into the runtime session.
StartPluginStart 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 ./dist

Then point runtime.providers.<name>.source at the resulting provider-release.yaml, or use a local manifest path during development.

For the operator-facing model, see Providers > Runtime. For packaging, see Releasing. For config and manifest fields, see Config File and Provider Manifests.