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 ¶
const ( PhasePending = "Pending" PhaseInstalling = "Installing" PhaseRunning = "Running" PhaseDegraded = "Degraded" PhaseBlocked = "Blocked" PhasePaused = "Paused" )
Phases mirror AddonPhase in the edges API.
const ( ConditionAllowed = "Allowed" ConditionConfigured = "Configured" ConditionRunning = "Running" )
Condition types the AGENT owns. Published is the provider's and is never written from here.
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.
const ( HarnessCodex = "codex" HarnessClaude = "claude" )
Harness names, mirroring the AddonHarness enum.
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.
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" )
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.
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.
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 ¶
var AddonGVR = schema.GroupVersionResource{Group: edgesGroup, Version: edgesVersion, Resource: "addons"}
AddonGVR addresses Addon objects in the tenant workspace.
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 Factory ¶
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 ¶
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 ¶
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 ¶
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 ¶
Reconcile converges one Addon. It is exported so the agent (and tests) can drive a single object without the informer machinery.
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. |