Documentation
¶
Overview ¶
Package backend abstracts how a CompiledSpec is executed.
MVP ships LocalBackend (spawn agent-runtime as a subprocess). Future backends — AgentCoreBackend, K8sBackend — implement the same interface; the CLI does not change.
v0.3.3a split the run path into two explicit phases:
Resolve(spec, binding) → (ResolvedRunSpec, warnings, err) Submit(resolved) → SessionHandle
Pre-v0.3.3 there was only Submit(spec). Resolve gives backends a hook to inspect/validate the chosen RuntimeBinding before any subprocess starts — and (in slice 3.3b) to emit `warning` wire events when the spec's runtime.requirements aren't met by the binding's target. Today (3.3a) the LocalBackend's Resolve is a no-op; the matcher lands in 3.3b.
KubernetesBackend launches the agent-runtime-base image as a Pod and streams its NDJSON wire-event stdout back to the CLI. Submission + log-streaming + cleanup all run on the host process; the cluster only sees a Pod and a single-key ConfigMap.
Slice 4.3 (skeleton):
- Resolve() delegates to the shared matcher (matcher.go)
- Submit() materializes a ConfigMap with the spec YAML, creates a Pod that mounts it at /workspace/spec.yaml, and starts a goroutine that follows the Pod logs and forwards parsed wire events on Events(h).
- Stop() deletes the Pod (foreground propagation) which terminates the running container; the ConfigMap is reaped in the same call.
- Capabilities() advertises streaming and a configurable concurrency ceiling. The default ceiling (1) matches LocalBackend; the K8s backend can be configured higher per Binding once batch scenarios show up.
Out of scope for slice 4.3 (tracked as later 4.x slices):
- SecurityContext / NetworkPolicy enforcement (slice 4.5)
- Job vs Pod toggle, replicas, retries
- serviceAccount / imagePullSecrets / kubeconfig context selection (slice 4.4)
- Pre-existing Secret validation (we just reference it by name and let the Pod admission webhook / kubelet surface "Secret not found" when the Pod is scheduled)
Index ¶
- type Backend
- type Caps
- type KubernetesBackend
- func (b *KubernetesBackend) Capabilities() Caps
- func (b *KubernetesBackend) Events(h SessionHandle) <-chan wire.Event
- func (b *KubernetesBackend) Resolve(ctx context.Context, spec adl.CompiledSpec, binding *adl.RuntimeBinding) (adl.ResolvedRunSpec, []wire.Event, error)
- func (b *KubernetesBackend) Stop(h SessionHandle) error
- func (b *KubernetesBackend) Submit(ctx context.Context, run adl.ResolvedRunSpec) (SessionHandle, error)
- type KubernetesConfig
- type LocalBackend
- func (b *LocalBackend) Capabilities() Caps
- func (b *LocalBackend) Events(h SessionHandle) <-chan wire.Event
- func (b *LocalBackend) Resolve(ctx context.Context, spec adl.CompiledSpec, binding *adl.RuntimeBinding) (adl.ResolvedRunSpec, []wire.Event, error)
- func (b *LocalBackend) Stop(h SessionHandle) error
- func (b *LocalBackend) Submit(ctx context.Context, run adl.ResolvedRunSpec) (SessionHandle, error)
- type LocalConfig
- type SessionHandle
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Backend ¶
type Backend interface {
// Resolve picks (or validates) a binding for the given spec and
// produces an executable run shape. Backends may emit wire `warning`
// or `error` events here — callers should drain the returned slice
// before reading Events(handle), so warnings appear in the canonical
// stream order. v0.3.3a: LocalBackend.Resolve is a no-op when
// `binding` is nil (wraps the spec verbatim). Slice 3.3b adds the
// capability matcher that fires warnings.
Resolve(ctx context.Context, spec adl.CompiledSpec, binding *adl.RuntimeBinding) (adl.ResolvedRunSpec, []wire.Event, error)
// Submit starts the run for a resolved spec.
Submit(ctx context.Context, run adl.ResolvedRunSpec) (SessionHandle, error)
Events(h SessionHandle) <-chan wire.Event
Stop(h SessionHandle) error
Capabilities() Caps
}
type KubernetesBackend ¶
type KubernetesBackend struct {
// contains filtered or unexported fields
}
KubernetesBackend implements Backend over the Kubernetes API.
func NewKubernetesBackend ¶
func NewKubernetesBackend(cfg KubernetesConfig) (*KubernetesBackend, error)
NewKubernetesBackend loads kubeconfig, builds a typed clientset, and returns the backend. Returns an error when the kubeconfig can't be loaded or the cluster isn't reachable — failing here is cheaper than surfacing a vague "session.ended reason=error" after Submit.
func (*KubernetesBackend) Capabilities ¶
func (b *KubernetesBackend) Capabilities() Caps
func (*KubernetesBackend) Events ¶
func (b *KubernetesBackend) Events(h SessionHandle) <-chan wire.Event
func (*KubernetesBackend) Resolve ¶
func (b *KubernetesBackend) Resolve(ctx context.Context, spec adl.CompiledSpec, binding *adl.RuntimeBinding) (adl.ResolvedRunSpec, []wire.Event, error)
Resolve uses the shared matcher. The KubernetesBackend additionally requires the binding's target.type to be "kubernetes" — running a local-target Binding through this backend is a wiring bug, not a capability gap.
func (*KubernetesBackend) Stop ¶
func (b *KubernetesBackend) Stop(h SessionHandle) error
Stop tears down the running Pod and its spec Secret. Cancellation of the streaming goroutine is best-effort — the caller may already have observed a terminal wire event (session.ended) before calling Stop. Stop is idempotent: streamPodLogs also calls cleanup() on normal completion. We also mark the session as user-cancelled BEFORE cancelling the context, so the streaming goroutine emits the canonical reason=cancelled terminal event instead of synthesizing a reason=error (codex pass 2 of slice 4.3 caught the wrong-reason gap).
func (*KubernetesBackend) Submit ¶
func (b *KubernetesBackend) Submit(ctx context.Context, run adl.ResolvedRunSpec) (SessionHandle, error)
Submit creates the ConfigMap + Pod and starts a goroutine that streams Pod logs as wire events on the SessionHandle's channel. Returns once the resources are accepted by the API server; the caller drains Events(h) for the actual run progress.
Naming: every Submit picks a fresh `agentctl-<counter>-<unix-millis>` suffix so multiple concurrent runs (or rapid retries after a failed Pod) don't collide. The counter is per-Backend-instance, so multiple agentctl processes hitting the same cluster also need timestamp disambiguation — hence the millis suffix.
type KubernetesConfig ¶
type KubernetesConfig struct {
// Kubeconfig is the path to a kubeconfig file. Empty means use
// the standard precedence: in-cluster ServiceAccount → KUBECONFIG
// env → ~/.kube/config.
Kubeconfig string
// Context selects a kubeconfig context. Empty uses the kubeconfig's
// `current-context`. Ignored when running in-cluster.
Context string
}
KubernetesConfig bundles the Backend's runtime options. The struct is intentionally narrow — per-Binding fields (namespace, image, secret) live on the Binding, not here. KubernetesConfig only carries connection settings that aren't part of the ADL surface.
type LocalBackend ¶
type LocalBackend struct {
// contains filtered or unexported fields
}
LocalBackend runs the runtime as a child process and ferries the wire protocol over its stdin/stdout.
func NewLocalBackend ¶
func NewLocalBackend(cfg LocalConfig) *LocalBackend
func (*LocalBackend) Capabilities ¶
func (b *LocalBackend) Capabilities() Caps
func (*LocalBackend) Events ¶
func (b *LocalBackend) Events(h SessionHandle) <-chan wire.Event
func (*LocalBackend) Resolve ¶
func (b *LocalBackend) Resolve(ctx context.Context, spec adl.CompiledSpec, binding *adl.RuntimeBinding) (adl.ResolvedRunSpec, []wire.Event, error)
Resolve delegates to the shared matcher (selector + capability check). Slice 4.3 extracted the policy logic into matcher.go so KubernetesBackend applies the same rules. Pre-4.3 the logic lived inline here.
func (*LocalBackend) Stop ¶
func (b *LocalBackend) Stop(h SessionHandle) error
func (*LocalBackend) Submit ¶
func (b *LocalBackend) Submit(ctx context.Context, run adl.ResolvedRunSpec) (SessionHandle, error)
type LocalConfig ¶
type LocalConfig struct {
RuntimeCommand []string
}
LocalConfig configures the local subprocess backend. RuntimeCommand is the argv used to invoke the agent-runtime — typically ["node", "dist/index.js"] but configurable so tests can inject a shell-script stand-in.
type SessionHandle ¶
type SessionHandle string