addons

package
v0.2.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 18, 2026 License: Apache-2.0 Imports: 38 Imported by: 0

Documentation

Overview

Package addons materializes edges.railgrid.ai Addon objects on the host next to the agent.

It is the same loop as the workload plane in pkg/agent/reconciler: the hub DECLARES an Addon, the agent MATERIALIZES it locally, and the agent REPORTS what happened on the object's status. There is deliberately no imperative channel from the hub to the host — an Addon is a desired state, and an agent that disagrees with it (because the machine owner never allowed the type) simply says so in status and does nothing.

Two keys are required before anything runs. The tenant creates the Addon, AND the machine owner started the agent with --allow-addon for its type. Neither alone materializes anything. See docs/edge-addons.md.

The Addon API lives in the standalone edges provider module, so — exactly like pkg/agent/reconciler — this package reads the objects as unstructured and decodes only the fields it needs into local mirror structs.

Index

Constants

View Source
const (
	PhasePending    = "Pending"
	PhaseInstalling = "Installing"
	PhaseRunning    = "Running"
	PhaseDegraded   = "Degraded"
	PhaseBlocked    = "Blocked"
	PhasePaused     = "Paused"
)

Phases mirror AddonPhase in the edges API.

View Source
const (
	ConditionAllowed    = "Allowed"
	ConditionConfigured = "Configured"
	ConditionRunning    = "Running"
)

Condition types the AGENT owns. Published is the provider's and is never written from here.

View Source
const (
	ReasonNotAllowedOnEdge = "NotAllowedOnEdge"
	ReasonAllowedOnEdge    = "AllowedOnEdge"
	ReasonCodexAuthMissing = "CodexAuthMissing"
	// ReasonClaudeAuthMissing: no Claude Code credential Secret, or the Secret
	// carries neither credential key. Nothing is started.
	ReasonClaudeAuthMissing = "ClaudeAuthMissing"
	// ReasonClaudeAuthInvalid: the Secret carries BOTH credential keys, or the
	// value is unusable. Which identity the tenant meant is not a guess the
	// agent will make, so nothing is started.
	ReasonClaudeAuthInvalid = "ClaudeAuthInvalid"
	ReasonPaused            = "Paused"
	ReasonConfigured        = "Configured"
	ReasonConfigError       = "ConfigError"
	ReasonProbeOK           = "ProbeSucceeded"
	ReasonProbeFailed       = "ProbeFailed"
	ReasonStarting          = "Starting"
	ReasonNotRunning        = "NotRunning"
)

Condition reasons. Keep in step with the AddonReason* constants in the edges API: the portal and docs key on these strings.

View Source
const (
	HarnessCodex  = "codex"
	HarnessClaude = "claude"
)

Harness names, mirroring the AddonHarness enum.

View Source
const (
	ClaudeOAuthTokenKey = "oauthToken"
	ClaudeAPIKeyKey     = "apiKey"
)

Secret keys a Claude Code credential may be carried in. Exactly one of them, never both: which identity the tenant meant is not a guess.

View Source
const (
	// TokenSecretSuffix is appended to the Addon name to form the Secret that
	// carries the runner's bearer token. The provider's add-on controller waits
	// for this Secret before it publishes a Service, and points the Service's
	// authSecretRef at it.
	TokenSecretSuffix = "-runner-token"
	// TokenSecretKey is the Secret key holding the bearer token. It matches the
	// key an edges Service's authSecretRef is read from.
	TokenSecretKey = "token"
	// CodexAuthKey is the Secret key holding the Codex login session file.
	CodexAuthKey = "auth.json"
	// TokenSecretNamespace is where the token Secret is published. The agent's
	// ClusterRole grants core secrets get/create/update, and `default` is the
	// namespace every tenant workspace has.
	TokenSecretNamespace = "default"
	// LabelAddon ties a published object back to its Addon.
	LabelAddon = edgesGroup + "/addon"
	// LabelEdge is the shared edge correlation label.
	LabelEdge = edgesGroup + "/edge"
	// AddonKind is the owner kind stamped on published objects.
	AddonKind = "Addon"
)
View Source
const DefaultResync = 60 * time.Second

DefaultResync is how often every known Addon is reconciled even without a watch event. It is short because the Running condition is a live health probe of the supervised process, not a cached fact.

View Source
const InheritUID = supervisor.InheritUID

InheritUID marks an account as "the agent's own", for a non-root agent that launches the child as itself. Re-exported so callers do not have to import the supervisor package just to say so.

View Source
const (
	// TypeRunner supervises `railgrid runner run` on this host.
	TypeRunner = "runner"
)

Known add-on types. Keep in step with the AddonType enum in the edges provider's API (providers/edges/apis/v1alpha1/types_addon.go).

Variables

View Source
var AddonGVR = schema.GroupVersionResource{Group: edgesGroup, Version: edgesVersion, Resource: "addons"}

AddonGVR addresses Addon objects in the tenant workspace.

View Source
var KnownTypes = []string{TypeRunner}

KnownTypes are the add-on types this agent build can materialize. The --allow-addon flag rejects anything outside this set so a typo fails at startup rather than silently allowing nothing.

Functions

This section is empty.

Types

type Addon

type Addon interface {
	// Type is the AddonType this implementation serves.
	Type() string
	// Reconcile converges the host to spec and reports what it observed. It
	// must be idempotent: the manager calls it on every change and on a
	// periodic resync.
	Reconcile(ctx context.Context, spec Spec) (Status, error)
	// Stop tears down the supervised process. It keeps the add-on's state
	// directory and NEVER touches enrolled repositories: pausing or deleting an
	// Addon must not destroy a developer's checkouts.
	Stop(ctx context.Context) error
}

Addon materializes one Addon object on this host. One instance per Addon object; the manager creates it on first sight and keeps it until the object is deleted or stops targeting this edge.

A new add-on type is: an implementation of this interface, a Factory registered with the manager, a value in the API's AddonType enum, and — if it should be reachable through the hub — a hook in the provider's add-on controller. See docs/edge-addons.md.

type Claude

type Claude struct {
	Binary        string     `json:"binary,omitempty"`
	VersionPin    string     `json:"versionPin,omitempty"`
	Model         string     `json:"model,omitempty"`
	AuthSecretRef *SecretRef `json:"authSecretRef,omitempty"`
	// PermissionMode and AllowedTools are passed through to the runner; the
	// harness validates the mode and the API restricts it to two values.
	PermissionMode string   `json:"permissionMode,omitempty"`
	AllowedTools   []string `json:"allowedTools,omitempty"`
}

Claude mirrors AddonClaude. Unlike Codex there is no portable on-disk login state, so the credential is a VALUE the adapter injects as one environment variable; the agent only ever puts it in a runner-owned 0600 file.

type Codex

type Codex struct {
	Binary        string     `json:"binary,omitempty"`
	VersionPin    string     `json:"versionPin,omitempty"`
	AuthSecretRef *SecretRef `json:"authSecretRef,omitempty"`
}

Codex mirrors AddonCodex. There is no API-key field on purpose: the Codex adapter strips API-key environment variables from the harness, so one could never be used — see pkg/runner/harness/codex.blockedEnvKey.

type Condition

type Condition struct {
	Type    string
	Status  metav1.ConditionStatus
	Reason  string
	Message string
}

Condition is one agent-owned status condition.

type EdgeRef

type EdgeRef struct {
	Kind string `json:"kind,omitempty"`
	Name string `json:"name"`
}

EdgeRef is the connectable an Addon is bound to.

type Factory

type Factory func(name string) (Addon, error)

Factory builds an Addon instance for one object name.

func NewRunnerFactory

func NewRunnerFactory(opts RunnerOptions) (Factory, error)

NewRunnerFactory returns a Factory the manager registers for TypeRunner.

type HarnessStatus

type HarnessStatus struct {
	Name    string
	Version string
	Ready   bool
	Reasons []string
}

HarnessStatus is the harness half of a runner's capabilities response. It is reported onto the Addon so a portal can show "claude-code 2.1.273, ready" without holding a runner bearer token and calling the runner itself.

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager watches the tenant workspace's Addon objects for this edge and keeps one Addon implementation per object.

func NewManager

func NewManager(hub dynamic.Interface, opts Options) (*Manager, error)

NewManager builds a manager. hub is a dynamic client scoped to the edge's tenant workspace — the same credential the edge status reporter uses.

func (*Manager) AllowedTypes

func (m *Manager) AllowedTypes() []string

AllowedTypes returns the locally allowed add-on types, sorted. The edge status reporter publishes them on the edge object so a portal can show what a machine will accept BEFORE anyone creates an Addon for it.

func (*Manager) Reconcile

func (m *Manager) Reconcile(ctx context.Context, name string) error

Reconcile converges one Addon. It is exported so the agent (and tests) can drive a single object without the informer machinery.

func (*Manager) Register

func (m *Manager) Register(addonType string, factory Factory)

Register wires an implementation for one add-on type. Registering a type the machine owner did not allow is harmless: the factory is simply never called.

func (*Manager) Run

func (m *Manager) Run(ctx context.Context) error

Run starts the manager and blocks until ctx is cancelled, at which point every supervised child is stopped.

type Options

type Options struct {
	// EdgeKind and EdgeName identify this edge. An Addon whose spec.edgeRef
	// names anything else is ignored entirely — not even its status is touched.
	EdgeKind string
	EdgeName string
	// Allowed are the add-on types the machine owner opted this host into with
	// --allow-addon. Empty (the default) means this edge materializes nothing.
	Allowed []string
	// Resync is how often every Addon is reconciled without a watch event.
	Resync time.Duration
}

Options configures a Manager.

type Repository

type Repository struct {
	Source         string `json:"source"`
	FetchRemoteURL string `json:"fetchRemoteURL,omitempty"`
}

Repository mirrors AddonRepository.

type RunAsAccount

type RunAsAccount struct {
	// Home is the account's home directory; the add-on's state lives under it.
	Home string
	// UID/GID are the account's ids, or supervisor.InheritUID when the agent is
	// already non-root and the child runs as the agent's own account.
	UID int
	GID int
}

RunAsAccount is the local account the supervised child runs as.

func (RunAsAccount) Inherited

func (a RunAsAccount) Inherited() bool

Inherited reports whether the child runs as the agent's own account.

type RunnerOptions

type RunnerOptions struct {
	// EdgeName is this edge's name; it is half of the runner's identity.
	EdgeName string
	// Executable is the agent's own binary, supervised as `<exe> runner run`.
	Executable string
	// Account is the non-root account the runner runs as.
	Account RunAsAccount
	// Kube is a clientset scoped to the tenant workspace, used to read the
	// Codex auth Secret and to publish the runner's token Secret.
	Kube kubernetes.Interface
	// ProbeDeadline bounds how long a reconcile waits for a freshly started
	// runner to answer its capabilities probe before reporting Installing.
	ProbeDeadline time.Duration
	// ProbeTimeout bounds a single probe request.
	ProbeTimeout time.Duration
	// InitialBackoff is handed to the supervisor; tests shorten it.
	InitialBackoff time.Duration
	// HTTPClient probes the loopback runner; nil builds a loopback-only client.
	HTTPClient *http.Client
}

RunnerOptions configures the runner add-on factory.

type RunnerSpec

type RunnerSpec struct {
	Port                     int32                 `json:"port,omitempty"`
	MaximumCapacity          int32                 `json:"maximumCapacity,omitempty"`
	Toolchains               []string              `json:"toolchains,omitempty"`
	VerificationCapabilities []string              `json:"verificationCapabilities,omitempty"`
	Repositories             map[string]Repository `json:"repositories,omitempty"`
	Harness                  string                `json:"harness,omitempty"`
	Codex                    *Codex                `json:"codex,omitempty"`
	Claude                   *Claude               `json:"claude,omitempty"`
}

RunnerSpec mirrors AddonRunnerSpec.

func (*RunnerSpec) HarnessName

func (s *RunnerSpec) HarnessName() string

HarnessName returns the selected harness, defaulting to Codex exactly as the API's own default does — so an Addon written before the field existed keeps behaving the way it always did.

type SecretRef

type SecretRef struct {
	Name      string `json:"name,omitempty"`
	Namespace string `json:"namespace,omitempty"`
}

SecretRef names a Secret in the tenant workspace.

type Spec

type Spec struct {
	// Name is the Addon's metadata.name. It names the add-on's state directory
	// and the Secret it publishes, so it is validated before use.
	Name string
	// UID and Generation come from metadata; UID goes into the ownerReference
	// of anything the add-on publishes, Generation into status.observedGeneration.
	UID        types.UID
	Generation int64
	// EdgeRef, Type, Paused and Runner are the decoded spec.
	EdgeRef EdgeRef
	Type    string
	Paused  bool
	Runner  *RunnerSpec
}

Spec is one Addon object as the agent sees it: the identity it needs to own the object's children plus the decoded spec.

type Status

type Status struct {
	Phase      string
	Version    string
	Message    string
	Harness    *HarnessStatus
	Conditions []Condition
}

Status is what an Addon implementation reports back after a reconcile. The manager turns it into the object's status subresource, adding observedGeneration and leaving the provider's Published condition alone.

Directories

Path Synopsis
Package supervisor runs one long-lived child process for an edge add-on and keeps it running.
Package supervisor runs one long-lived child process for an edge add-on and keeps it running.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL