runner

package
v0.2.13 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: Apache-2.0 Imports: 31 Imported by: 0

Documentation

Overview

Package runner provides a durable, authenticated local execution service.

Index

Constants

View Source
const (
	EventAccepted   = "accepted"
	EventStarted    = "started"
	EventProgress   = "progress"
	EventCheckpoint = "checkpoint"
	EventNeedsInput = "needs_input"
	EventArtifact   = "artifact"
	EventCompleted  = "completed"
	EventFailed     = "failed"
	EventCancelled  = "cancelled"
)

EventType values are the bounded, replayable events exposed by the runner.

View Source
const (
	ErrorInvalidRequest        = "invalid_request"
	ErrorUnauthorized          = "unauthorized"
	ErrorForbidden             = "forbidden"
	ErrorUnsupportedVersion    = "unsupported_version"
	ErrorUnsupportedCapability = "unsupported_capability"
	ErrorBusy                  = "busy"
	ErrorStaleAttempt          = "stale_attempt"
	ErrorIdempotencyConflict   = "idempotency_conflict"
	ErrorCheckpointUnavailable = "checkpoint_unavailable"
	ErrorCursorExpired         = "cursor_expired"
	ErrorUnavailable           = "unavailable"
)

ErrorCode values are stable protocol errors. Callers should branch on Code, not on Message.

View Source
const (
	PermissionAllow = "allow"
	PermissionDeny  = "deny"
)

PermissionDecision values are the verdicts a resume may carry. They are spelled out rather than being a bool so that "no decision" is a distinct, refusable state: a resume that forgot to say must not silently allow.

View Source
const ProtocolVersion = "runner/v1"

ProtocolVersion is the wire protocol implemented by the local runner.

Variables

This section is empty.

Functions

This section is empty.

Types

type Artifact

type Artifact struct {
	ID        string    `json:"id"`
	Name      string    `json:"name"`
	Digest    string    `json:"digest"`
	Length    int64     `json:"length"`
	MediaType string    `json:"mediaType"`
	CreatedAt time.Time `json:"createdAt"`
}

Artifact is an immutable, named result reference.

type ArtifactResponse

type ArtifactResponse struct {
	Artifact
}

ArtifactResponse contains immutable artifact metadata and bytes are served by the artifact endpoint. It is kept separate so callers never receive a filesystem path.

type ArtifactSpec

type ArtifactSpec struct {
	Name      string `json:"name"`
	Path      string `json:"path,omitempty"`
	MediaType string `json:"mediaType,omitempty"`
	Digest    string `json:"digest,omitempty"`
}

ArtifactSpec is an approved logical artifact. Path is relative to the task worktree and is only used when the adapter reports an artifact event.

type CancelRequest

type CancelRequest struct {
	ProtocolVersion string `json:"protocolVersion,omitempty"`
	RequestID       string `json:"requestID"`
	TaskID          string `json:"taskID"`
	AttemptID       string `json:"attemptID"`
	AttemptEpoch    uint64 `json:"attemptEpoch"`
}

CancelRequest requests cancellation of an active attempt.

type Capabilities

type Capabilities struct {
	ProtocolVersion string              `json:"protocolVersion"`
	RunnerID        string              `json:"runnerID"`
	Version         string              `json:"version"`
	OS              string              `json:"os"`
	Architecture    string              `json:"architecture"`
	Harnesses       []HarnessCapability `json:"harnesses,omitempty"`
	Toolchains      []string            `json:"toolchains,omitempty"`
	Environment     []string            `json:"environment,omitempty"`
	Verification    []string            `json:"verificationCapabilities,omitempty"`
	Capacity        Capacity            `json:"capacity"`
	Ready           bool                `json:"ready"`
	Reasons         []string            `json:"reasons,omitempty"`
}

Capabilities is returned by GET /runner/v1/capabilities.

type Capacity

type Capacity struct {
	Maximum int `json:"maximum"`
	Used    int `json:"used"`
}

Capacity is the execution capacity advertised by a runner.

type Clarification

type Clarification struct {
	ID   string `json:"id"`
	Text string `json:"text"`
}

Clarification is a bounded product question captured from a genuine request-user-input harness interaction. Its ID is stable for one session, turn, and interaction item and must be echoed by a matching resume.

type Config

type Config struct {
	ProtocolVersion string                      `json:"protocolVersion,omitempty"`
	RunnerID        string                      `json:"runnerID"`
	Version         string                      `json:"version,omitempty"`
	Listen          string                      `json:"listen,omitempty"`
	StateDir        string                      `json:"stateDir"`
	TokenFile       string                      `json:"tokenFile"`
	Token           string                      `json:"-"`
	Toolchains      []string                    `json:"toolchains,omitempty"`
	Environment     []string                    `json:"environment,omitempty"`
	Verification    []string                    `json:"verificationCapabilities,omitempty"`
	MaximumCapacity int                         `json:"maximumCapacity,omitempty"`
	MaxEvents       int                         `json:"maxEvents,omitempty"`
	MaxEventBytes   int                         `json:"maxEventBytes,omitempty"`
	MaxBodyBytes    int                         `json:"maxBodyBytes,omitempty"`
	MaxArtifactSize int64                       `json:"maxArtifactSize,omitempty"`
	Repositories    map[string]RepositoryConfig `json:"repositories,omitempty"`
	Resources       map[string]ResourceConfig   `json:"resources,omitempty"`
}

Config contains local runner identity, enrollment, persistence, and capability settings. It is intentionally independent of the Codex adapter.

func LoadConfig

func LoadConfig(path string) (Config, error)

LoadConfig reads a JSON runner configuration. A missing path is equivalent to an empty configuration and is useful for callers that supply Config directly; the CLI requires a path when --config is set.

type Error

type Error struct {
	Code             string   `json:"code"`
	Retryable        bool     `json:"retryable"`
	Message          string   `json:"message"`
	Receipt          *Receipt `json:"receipt,omitempty"`
	SnapshotRequired bool     `json:"snapshotRequired,omitempty"`
}

Error describes a structured protocol failure.

func (*Error) Error

func (e *Error) Error() string

type Event

type Event struct {
	Cursor       uint64          `json:"cursor"`
	Timestamp    time.Time       `json:"timestamp"`
	AttemptEpoch uint64          `json:"attemptEpoch"`
	Type         string          `json:"type"`
	Message      string          `json:"message,omitempty"`
	Data         json.RawMessage `json:"data,omitempty"`
}

Event is an ordered, durable attempt event. Data is bounded by the runner before the event is persisted.

type ExecutionLimits

type ExecutionLimits struct {
	MaxDurationSeconds int `json:"maxDurationSeconds,omitempty"`
	MaxOutputBytes     int `json:"maxOutputBytes,omitempty"`
	MaxTurns           int `json:"maxTurns,omitempty"`
}

ExecutionLimits bounds adapter execution. The runner enforces duration and emitted-output limits; MaxTurns is limited to one because one adapter launch represents one turn in this protocol revision.

type HarnessCapability

type HarnessCapability struct {
	Name    string   `json:"name"`
	Version string   `json:"version"`
	Ready   bool     `json:"ready"`
	Reasons []string `json:"reasons,omitempty"`
}

HarnessCapability describes an installed harness and its readiness.

type HarnessCredential added in v0.2.8

type HarnessCredential struct {
	// Kind is one of the harness.Credential kinds: claude-oauth,
	// claude-apikey, codex-auth.
	Kind string `json:"kind"`
	// Value is the credential itself. It is never logged, never written to
	// disk by the runner, and never placed on a command line.
	Value string `json:"value"`
}

HarnessCredential is the caller's harness identity for one attempt.

Like RepositorySource it is DISPATCH DATA, not approved attempt input: Start and Resume strip it before the request is fingerprinted or persisted, so it never reaches the runner's durable journal and a retry carrying a freshly minted value is still the same request. The runner holds it in memory for the life of the attempt and hands it to the adapter for each launch.

A runner has no identity of its own. Every start must carry one, which is what keeps a machine that hosts a harness from being a shared model account.

type PermissionRequest added in v0.2.8

type PermissionRequest struct {
	ID   string `json:"id"`
	Tool string `json:"tool"`
	// Input is a bounded JSON rendering of the tool's input, and it is what the
	// verdict authorizes: that call, those arguments.
	Input string `json:"input,omitempty"`
}

PermissionRequest is a harness permission prompt waiting on a human.

It is NOT a Clarification, and a caller must not treat it as one. A clarification is a product question answered with text, which becomes the next turn's instructions. A permission request is a named tool call, with the input the harness is holding, waiting on a verdict that is delivered back into the call that is still open — so the same turn continues.

Its ID is stable for one session and one tool call and must be echoed by the matching resume, exactly as a Clarification's is.

type Phase

type Phase string

Phase describes the durable lifecycle of an attempt.

const (
	PhaseAccepted   Phase = "accepted"
	PhaseStarting   Phase = "starting"
	PhaseRunning    Phase = "running"
	PhaseNeedsInput Phase = "needs_input"
	PhaseCancelling Phase = "cancelling"
	PhaseCancelled  Phase = "cancelled"
	PhaseCompleted  Phase = "completed"
	PhaseFailed     Phase = "failed"
)

func (Phase) IsTerminal

func (p Phase) IsTerminal() bool

IsTerminal reports whether p no longer has a live execution.

type Receipt

type Receipt struct {
	ProtocolVersion string         `json:"protocolVersion"`
	TaskID          string         `json:"taskID"`
	AttemptID       string         `json:"attemptID"`
	AttemptEpoch    uint64         `json:"attemptEpoch"`
	Phase           Phase          `json:"phase"`
	SessionID       string         `json:"sessionID,omitempty"`
	Workdir         string         `json:"workdir,omitempty"`
	Cursor          uint64         `json:"cursor"`
	Blocker         string         `json:"blocker,omitempty"`
	Clarification   *Clarification `json:"clarification,omitempty"`
	// Permission is set when the attempt is parked on a permission prompt
	// rather than on a question. At most one of Clarification and Permission is
	// ever set: they are answered differently and a caller has to know which.
	Permission *PermissionRequest `json:"permission,omitempty"`
	Resources  []string           `json:"resources,omitempty"`
	LastError  *Error             `json:"lastError,omitempty"`
	Artifacts  []Artifact         `json:"artifacts,omitempty"`
	AcceptedAt time.Time          `json:"acceptedAt"`
	UpdatedAt  time.Time          `json:"updatedAt"`
}

Receipt is the durable view of an attempt returned by start and inspect.

type RepositoryConfig

type RepositoryConfig struct {
	Source         string `json:"source"`
	BaseCommit     string `json:"baseCommit,omitempty"`
	FetchRemoteURL string `json:"fetchRemoteURL,omitempty"`
}

RepositoryConfig pins what the runner may do with one repository. Every field is optional, and the whole map usually is: a runner clones what the coordinator names with an attempt into a copy it owns, so nothing has to be staged on the host. An enrollment is for the two cases where the host has something to say — Source names an existing checkout to work from instead, FetchRemoteURL a remote to use when no attempt supplies one, and BaseCommit pins the single commit this runner will accept.

type RepositorySource added in v0.2.7

type RepositorySource struct {
	// RemoteURL is the Git URL to clone from. HTTPS and SSH are accepted;
	// the URL itself must never carry embedded credentials.
	RemoteURL string `json:"remoteURL"`
	// Username accompanies Token for HTTPS remotes. Empty means the usual
	// token-bearing placeholder.
	Username string `json:"username,omitempty"`
	// Token is a short-lived read-only credential. It is used for one fetch
	// and is never written to disk, logged, or passed on the command line.
	Token string `json:"token,omitempty"`
}

RepositorySource tells the runner where to get a repository it does not keep a local checkout of. The coordinator sends it with each start; the runner clones into a cache it owns under its state directory.

It is dispatch data, not approved attempt input: Start strips it before the request is fingerprinted or persisted, so the credential never reaches the runner's durable state and a retry carrying a freshly minted one is still the same request.

type ResourceConfig

type ResourceConfig struct {
	Kind     string            `json:"kind,omitempty"`
	Capacity int               `json:"capacity,omitempty"`
	Labels   map[string]string `json:"labels,omitempty"`
}

ResourceConfig names a preconfigured shared resource. Capacity is a count of simultaneous reservations; it does not cause the runner to provision the resource.

type ResourceRequest

type ResourceRequest struct {
	Name string `json:"name"`
}

ResourceRequest identifies a preconfigured shared resource. Resource values are names only: the runner never provisions ports, containers, or clusters.

type ResumeRequest

type ResumeRequest struct {
	ProtocolVersion   string             `json:"protocolVersion,omitempty"`
	RequestID         string             `json:"requestID"`
	TaskID            string             `json:"taskID"`
	AttemptID         string             `json:"attemptID"`
	AttemptEpoch      uint64             `json:"attemptEpoch"`
	SessionID         string             `json:"sessionID,omitempty"`
	ClarificationID   string             `json:"clarificationID,omitempty"`
	ApprovedInput     json.RawMessage    `json:"approvedInput,omitempty"`
	Resolution        string             `json:"resolution,omitempty"`
	Instructions      string             `json:"instructions,omitempty"`
	HarnessCredential *HarnessCredential `json:"harnessCredential,omitempty"`
	// PermissionID answers an outstanding Receipt.Permission, and must equal
	// its ID. It is the fence: a verdict that names a different request is
	// refused rather than applied to whatever the harness is waiting on now.
	PermissionID string `json:"permissionID,omitempty"`
	// PermissionDecision is the verdict, PermissionAllow or PermissionDeny. It
	// is required whenever PermissionID is set, and forbidden otherwise.
	//
	// A permission resume does NOT relaunch the harness: the tool call is still
	// open on a child that never stopped, so the verdict is handed to it and
	// the SAME turn continues. Resolution, when present, is the human's own
	// words and is passed to the harness as the reason for a denial.
	PermissionDecision string `json:"permissionDecision,omitempty"`
}

ResumeRequest continues a needs-input attempt in its existing session.

It carries the harness credential again rather than relying on one the runner kept: a resume may arrive after a runner restart, and nothing about the caller's identity survives on the host.

type Runner

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

Runner is the generic local execution service. It owns protocol state and workspace/resource boundaries; harness.Adapter owns the child process.

func New

func New(cfg Config, adapter harness.Adapter) (*Runner, error)

New creates a runner, loads durable state, probes the harness without model calls, and acquires the state-directory singleton lock.

func NewRunner

func NewRunner(cfg Config, adapter harness.Adapter) (*Runner, error)

NewRunner is an explicit alias for callers that prefer the type name.

func NewServer

func NewServer(cfg Config, adapter harness.Adapter) (*Runner, error)

NewServer is retained as a transport-oriented constructor alias.

func (*Runner) Cancel

func (r *Runner) Cancel(_ context.Context, request CancelRequest) (Receipt, error)

Cancel requests adapter cancellation. Active attempts remain cancelling until adapter exit. Unseen attempts receive a durable cancellation fence before acknowledgement, so a delayed Start cannot launch them.

func (*Runner) Capabilities

func (r *Runner) Capabilities() Capabilities

Capabilities returns a copy of the current discovery document.

func (*Runner) Close

func (r *Runner) Close() error

Close gracefully drains active adapters, waits for them to exit, closes the listener, and releases the singleton state lock. An adapter interrupted by this shutdown remains resumable; an explicit Cancel request is still terminal.

func (*Runner) Handler

func (r *Runner) Handler() http.Handler

Handler returns the authenticated loopback runner protocol handler. The handler itself does not bind a socket; ListenAndServe enforces loopback.

func (*Runner) Inspect

func (r *Runner) Inspect(_ context.Context, attemptID string) (Receipt, error)

Inspect returns the durable current receipt.

func (*Runner) ListenAndServe

func (r *Runner) ListenAndServe(ctx context.Context) error

ListenAndServe starts the runner on the configured loopback address and stops when ctx is cancelled.

func (*Runner) Resume

func (r *Runner) Resume(ctx context.Context, request ResumeRequest) (Receipt, error)

Resume continues a needs-input attempt with its existing session and worktree. It never accepts a different instruction envelope or session ID.

func (*Runner) Start

func (r *Runner) Start(ctx context.Context, request StartRequest) (Receipt, error)

Start accepts an approved attempt and returns once its receipt and accepted event have been durably persisted. Execution continues asynchronously.

type StartRequest

type StartRequest struct {
	ProtocolVersion string `json:"protocolVersion,omitempty"`
	RequestID       string `json:"requestID"`
	TaskID          string `json:"taskID"`
	AttemptID       string `json:"attemptID"`
	AttemptEpoch    uint64 `json:"attemptEpoch"`
	// RepositoryID and BaseCommit are required unless WorkspaceID is set.
	RepositoryID string `json:"repositoryID,omitempty"`
	BaseCommit   string `json:"baseCommit,omitempty"`
	// WorkspaceID names a directory the runner owns and keeps across
	// attempts, under its managed worktree root. Mutually exclusive with
	// RepositoryID: a workspace has no approved commit to verify.
	WorkspaceID string `json:"workspaceID,omitempty"`
	// SessionID continues an existing harness session in a NEW attempt, which
	// is how consecutive turns of one conversation stay one session. Resuming
	// a parked attempt uses ResumeRequest instead.
	SessionID string `json:"sessionID,omitempty"`
	// HarnessCredential is the identity this attempt runs as. Required, and
	// stripped before anything durable is derived from the request.
	HarnessCredential      *HarnessCredential `json:"harnessCredential,omitempty"`
	Instructions           string             `json:"instructions"`
	Model                  string             `json:"model,omitempty"`
	ApprovedInput          json.RawMessage    `json:"approvedInput,omitempty"`
	RequiredCapabilities   []string           `json:"requiredCapabilities,omitempty"`
	RequiredToolchains     []string           `json:"requiredToolchains,omitempty"`
	RequiredEnvironment    []string           `json:"requiredEnvironment,omitempty"`
	RequiredHarness        string             `json:"requiredHarness,omitempty"`
	RequiredHarnessVersion string             `json:"requiredHarnessVersion,omitempty"`
	// AskPermission opts this attempt into the permission round-trip: a tool
	// call the harness's permission mode does not pre-approve becomes a
	// needs_input on the attempt, carrying Receipt.Permission, instead of being
	// denied where nobody can see it. The resume answers it with a verdict.
	//
	// It is opt-in because answering one is work a coordinator has to do. A
	// coordinator that does not send it — or an older one that has never heard
	// of the field — gets exactly the behaviour it had before: anything that
	// would have prompted is denied and the turn carries on.
	AskPermission   bool `json:"askPermission,omitempty"`
	ExportGitResult bool `json:"exportGitResult,omitempty"`
	// CommitMessage is the subject the exported git result's snapshot commit
	// is stamped with: one line of coordinator-authored public text, such as
	// the ticket's title or "Address review on #7". It is the coordinator's
	// to choose and the coordinator's to verify downstream — nothing the model
	// wrote reaches git metadata. Empty keeps the canonical message.
	CommitMessage string                   `json:"commitMessage,omitempty"`
	Limits        ExecutionLimits          `json:"limits,omitempty"`
	Verification  VerificationRequirements `json:"verification,omitempty"`
	Resources     []ResourceRequest        `json:"resources,omitempty"`
	Artifacts     []ArtifactSpec           `json:"artifacts,omitempty"`
	Repository    *RepositorySource        `json:"repository,omitempty"`
}

StartRequest is the approved execution envelope accepted by Start.

There are two shapes. A REPOSITORY attempt names RepositoryID and BaseCommit and runs in a fresh task clone at that commit; this is what a coding coordinator sends. A WORKSPACE attempt names WorkspaceID instead and runs in a directory the runner keeps across attempts; this is what a conversational agent sends, where there is no commit to pin and the working files are the point. The two are mutually exclusive.

type VerificationRequirements

type VerificationRequirements struct {
	Names    []string `json:"names,omitempty"`
	Commands []string `json:"commands,omitempty"`
}

VerificationRequirements records the checks approved for an attempt.

Directories

Path Synopsis
Package client reaches one enrolled railgrid runner through the edges provider's published Service proxy, speaking the typed runner/v1 protocol in pkg/runner.
Package client reaches one enrolled railgrid runner through the edges provider's published Service proxy, speaking the typed runner/v1 protocol in pkg/runner.
Package dispatch is the runner/v1 attempt lifecycle, written once.
Package dispatch is the runner/v1 attempt lifecycle, written once.
Package harness defines the public execution seam for runner adapters.
Package harness defines the public execution seam for runner adapters.
claude
Package claude adapts headless Claude Code to the runner harness.
Package claude adapts headless Claude Code to the runner harness.
codex
Package codex adapts the Codex app-server protocol to the runner harness.
Package codex adapts the Codex app-server protocol to the runner harness.
internal/proc
Package proc holds the process-group primitives every harness adapter needs.
Package proc holds the process-group primitives every harness adapter needs.
Package runnercli holds the launch logic shared by the standalone railgrid-runner binary (cmd/railgrid-runner) and the `railgrid runner run` subcommand of the main CLI (pkg/cli/cmd/runner.go).
Package runnercli holds the launch logic shared by the standalone railgrid-runner binary (cmd/railgrid-runner) and the `railgrid runner run` subcommand of the main CLI (pkg/cli/cmd/runner.go).

Jump to

Keyboard shortcuts

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