base

package
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Jun 17, 2026 License: MIT Imports: 25 Imported by: 6

Documentation

Overview

Package base is the runner foundation: it defines the RunnerEnvironment and Proc abstractions and provides three backends — Native, Docker, and Nix — plus the CompanionRunner wrapper that picks the best backend at runtime.

All higher-level runners (runners/golang, runners/python) build on these primitives. Process supervision (process group setup, tree-kill, orphan reaping via pgid files) lives here so every backend behaves identically under SIGTERM and ctx-cancel.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CheckForRuntimes

func CheckForRuntimes(ctx context.Context, requirements []*agentv0.Runtime) error

func CheckNixInstalled added in v0.1.155

func CheckNixInstalled() bool

CheckNixInstalled checks if Nix is available in PATH

func CheckPythonPath

func CheckPythonPath() (string, error)

func FindFreePort added in v0.1.155

func FindFreePort() (int, error)

FindFreePort asks the OS to assign a free TCP port and returns it.

func IsFreePort

func IsFreePort(port int) bool

func IsNixSupported added in v0.1.155

func IsNixSupported() bool

IsNixSupported returns true if the current OS supports Nix

func IsProcessAlive added in v0.2.1

func IsProcessAlive(pid int) bool

IsProcessAlive tests a single PID via Signal(0). Same pattern as daemon.IsRunning but scoped here to keep base self-contained. Exported so the docker runner package (container sweep) can reuse the liveness check.

func NixInstallCommand added in v0.1.155

func NixInstallCommand() string

NixInstallCommand returns the command to install Nix based on OS

func ReapStaleProcessGroups added in v0.1.155

func ReapStaleProcessGroups(ctx context.Context) error

ReapStaleProcessGroups scans ~/.codefly/runs/*.pgid and tree-kills any process group that is still alive — these are orphans left behind by a prior ungraceful CLI exit (SIGKILL of the parent, terminal force-closed, etc). The pgid file is removed either way. Best-effort: a single failed reap does not short-circuit the sweep.

Call this once at the top of `codefly run service`, before any new children are spawned. Idempotent and safe when no files exist.

Convergence: when a parent agent and its NativeProc grandchild both have files, directory order can cause the grandchild to be visited while its parent is still live (so we skip it), then the parent gets reaped a few iterations later. We loop up to maxSweepPasses so a single call resolves the whole orphan tree without relying on a subsequent `codefly run`.

func RemovePgidFile added in v0.1.155

func RemovePgidFile(pgid int) error

RemovePgidFile drops the tracking file for a pgid after graceful stop.

func WaitForPortUnbound

func WaitForPortUnbound(ctx context.Context, port int) error

WaitForPortUnbound waits for the portMappings to be unbound

func WritePgidFile added in v0.1.155

func WritePgidFile(pgid int, cwd string, argv []string) error

WritePgidFile is the exported entry point used by spawners outside this package (agent manager, MCP mutation tools) that use Setpgid:true and need their groups reaped on ungraceful parent death. NativeProc calls the unexported wrapper.

Types

type CPU

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

type Event

type Event struct {
	// Err is the state of error of the service
	Err error

	// Status is the state of the service
	ProcessState

	// CPU
	*observabilityv0.CPU

	// Memory
	*observabilityv0.Memory
}

Event represents data of a **running** service Generic so most fields will be nil

type Memory

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

type NativeEnvironment added in v0.1.89

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

func NewNativeEnvironment added in v0.1.89

func NewNativeEnvironment(ctx context.Context, dir string) (*NativeEnvironment, error)

NewNativeEnvironment creates a new native runner. It runs processes directly on the host using whatever is in PATH.

func (*NativeEnvironment) Init added in v0.1.89

func (native *NativeEnvironment) Init(ctx context.Context) error

func (*NativeEnvironment) NewProcess added in v0.1.89

func (native *NativeEnvironment) NewProcess(bin string, args ...string) (Proc, error)

func (*NativeEnvironment) Shutdown added in v0.1.89

func (native *NativeEnvironment) Shutdown(context.Context) error

func (*NativeEnvironment) Stop added in v0.1.89

func (native *NativeEnvironment) Stop(context.Context) error

func (*NativeEnvironment) WithBinary added in v0.1.91

func (native *NativeEnvironment) WithBinary(bin string) error

func (*NativeEnvironment) WithEnvironmentVariables added in v0.1.89

func (native *NativeEnvironment) WithEnvironmentVariables(ctx context.Context, envs ...*resources.EnvironmentVariable)

type NativeProc added in v0.1.89

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

func (*NativeProc) Forward added in v0.1.89

func (proc *NativeProc) Forward(_ context.Context, r io.Reader)

Forward streams r → proc.output one line at a time with newlines intact. The previous implementation used strings.TrimSpace which dropped the trailing newline between lines, so JSON-lines events merged and the log prefix was re-applied per Write call against zero-byte separators. Plain io.Copy fixed the newline loss but collapsed all output into a single prefix block — worse for interactive tailing. forwardLines strikes the middle: scanner gives per-line Write boundaries (so log prefixes apply correctly), newline is re-appended so downstream parsers see the real separator, and the scanner buffer is sized for 1MiB lines so large structured-log events don't silently truncate.

func (*NativeProc) IsRunning added in v0.1.109

func (proc *NativeProc) IsRunning(ctx context.Context) (bool, error)

IsRunning checks if the exec is still running

func (*NativeProc) Run added in v0.1.89

func (proc *NativeProc) Run(ctx context.Context) error

func (*NativeProc) Start added in v0.1.89

func (proc *NativeProc) Start(ctx context.Context) error

func (*NativeProc) StdinPipe added in v0.1.155

func (proc *NativeProc) StdinPipe() (io.WriteCloser, error)

func (*NativeProc) StdoutPipe added in v0.1.155

func (proc *NativeProc) StdoutPipe() (io.ReadCloser, error)

func (*NativeProc) Stop added in v0.1.89

func (proc *NativeProc) Stop(ctx context.Context) error

func (*NativeProc) Wait added in v0.1.155

func (proc *NativeProc) Wait(ctx context.Context) error

Wait blocks until the process exits or ctx is cancelled. Returns the process's exit error (nil on clean exit). Safe to call multiple times.

func (*NativeProc) WaitOn added in v0.1.107

func (proc *NativeProc) WaitOn(bin string)

func (*NativeProc) WithDir added in v0.1.103

func (proc *NativeProc) WithDir(dir string)

func (*NativeProc) WithEnvironmentVariables added in v0.1.89

func (proc *NativeProc) WithEnvironmentVariables(ctx context.Context, envs ...*resources.EnvironmentVariable)

func (*NativeProc) WithEnvironmentVariablesAppend added in v0.1.144

func (proc *NativeProc) WithEnvironmentVariablesAppend(ctx context.Context, added *resources.EnvironmentVariable, sep string)

func (*NativeProc) WithOutput added in v0.1.89

func (proc *NativeProc) WithOutput(output io.Writer)

func (*NativeProc) WithRunningCmd added in v0.1.89

func (proc *NativeProc) WithRunningCmd(_ string)

type NixEnvironment added in v0.1.155

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

NixEnvironment runs processes inside a Nix development shell.

Two modes:

  • Wrapped (default before Init): each NewProcess wraps the command in `nix develop <dir> --command <bin> <args...>`. Re-evaluates the flake every call — expensive.
  • Materialized (after Init): `nix print-dev-env --json <dir>` is run once to capture the devShell's exported variables. Later NewProcess calls exec `bin` directly with that env, skipping `nix develop` entirely on the hot path. This is what makes Test calls fast once the agent has been through Init.

Binaries come from the flake.nix, so WithBinary is a no-op.

func NewNixEnvironment added in v0.1.155

func NewNixEnvironment(ctx context.Context, dir string) (*NixEnvironment, error)

NewNixEnvironment creates a new Nix runner. It verifies that nix is installed and that a flake.nix exists in dir.

func (*NixEnvironment) Init added in v0.1.155

func (nix *NixEnvironment) Init(ctx context.Context) error

func (*NixEnvironment) NewProcess added in v0.1.155

func (nix *NixEnvironment) NewProcess(bin string, args ...string) (Proc, error)

NewProcess creates a process that runs under the Nix devShell.

If the devShell env has been materialized (see Init), the binary is executed directly with that env injected — no `nix develop` wrapper, no flake re-evaluation. Otherwise falls back to wrapping the command in `nix develop <dir> --command <bin> <args...>`.

func (*NixEnvironment) Shutdown added in v0.1.155

func (nix *NixEnvironment) Shutdown(context.Context) error

func (*NixEnvironment) Stop added in v0.1.155

func (nix *NixEnvironment) Stop(context.Context) error

func (*NixEnvironment) WithBinary added in v0.1.155

func (nix *NixEnvironment) WithBinary(_ string) error

WithBinary is a no-op for Nix -- all binaries come from the flake.

func (*NixEnvironment) WithCacheDir added in v0.1.155

func (nix *NixEnvironment) WithCacheDir(dir string)

WithCacheDir enables persistent materialization caching. Typically set to the agent's cacheLocation so the env survives agent restarts. Safe to call before or after Init — but before is the only useful order (the cache is consulted during Init).

func (*NixEnvironment) WithEnvironmentVariables added in v0.1.155

func (nix *NixEnvironment) WithEnvironmentVariables(ctx context.Context, envs ...*resources.EnvironmentVariable)

type NixProc added in v0.1.155

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

NixProc is a process that runs inside a Nix development shell.

func (*NixProc) IsRunning added in v0.1.155

func (proc *NixProc) IsRunning(ctx context.Context) (bool, error)

func (*NixProc) Run added in v0.1.155

func (proc *NixProc) Run(ctx context.Context) error

func (*NixProc) Start added in v0.1.155

func (proc *NixProc) Start(ctx context.Context) error

func (*NixProc) StdinPipe added in v0.1.155

func (proc *NixProc) StdinPipe() (io.WriteCloser, error)

func (*NixProc) StdoutPipe added in v0.1.155

func (proc *NixProc) StdoutPipe() (io.ReadCloser, error)

func (*NixProc) Stop added in v0.1.155

func (proc *NixProc) Stop(ctx context.Context) error

func (*NixProc) Wait added in v0.1.155

func (proc *NixProc) Wait(ctx context.Context) error

Wait blocks until the nix process exits or ctx is cancelled. Polls IsRunning at 1s intervals — Nix wraps the binary in `nix develop`, so we don't have direct access to the leaf process's cmd.Wait, and the existing forwarder goroutines already hold the cmd.Wait result.

func (*NixProc) WaitOn added in v0.1.155

func (proc *NixProc) WaitOn(bin string)

func (*NixProc) WithDir added in v0.1.155

func (proc *NixProc) WithDir(dir string)

func (*NixProc) WithEnvironmentVariables added in v0.1.155

func (proc *NixProc) WithEnvironmentVariables(ctx context.Context, envs ...*resources.EnvironmentVariable)

func (*NixProc) WithEnvironmentVariablesAppend added in v0.1.155

func (proc *NixProc) WithEnvironmentVariablesAppend(_ context.Context, added *resources.EnvironmentVariable, sep string)

func (*NixProc) WithOutput added in v0.1.155

func (proc *NixProc) WithOutput(output io.Writer)

func (*NixProc) WithRunningCmd added in v0.1.155

func (proc *NixProc) WithRunningCmd(_ string)

type Proc

type Proc interface {
	Start(ctx context.Context) error
	Run(ctx context.Context) error
	Stop(ctx context.Context) error

	// Wait blocks until the process exits or ctx is cancelled. Returns the
	// process's exit error (nil for clean exit, *exec.ExitError for non-zero).
	// Must be safe to call after Start. Multiple callers may share the result
	// via an internal channel — implementations should make Wait idempotent.
	// Used by supervisors to detect when a fire-and-forget Start'd process
	// dies so they can propagate the failure (instead of leaking the parent).
	Wait(ctx context.Context) error

	IsRunning(ctx context.Context) (bool, error)

	// WaitOn For Run, optional, we can wait on another process
	WaitOn(s string)

	// WithDir overrides the location where the Proc runs
	WithDir(local string)

	// WithOutput output to send the logs
	WithOutput(w io.Writer)

	// WithEnvironmentVariables adds environment variables
	WithEnvironmentVariables(ctx context.Context, envs ...*resources.EnvironmentVariable)

	// WithEnvironmentVariablesAppend appends environment variables
	WithEnvironmentVariablesAppend(ctx context.Context, env *resources.EnvironmentVariable, sep string)

	// StdinPipe returns a writer connected to the process's stdin.
	// Must be called before Start/Run.
	StdinPipe() (io.WriteCloser, error)

	// StdoutPipe returns a reader connected to the process's stdout (raw bytes).
	// Must be called before Start/Run. When used, WithOutput is bypassed for
	// stdout; stderr still goes to the writer set via WithOutput.
	StdoutPipe() (io.ReadCloser, error)
}

Proc is a generic process interface Implementations: - LocalEnvironment process: obtained from a local environment - Docker process: obtained by running in a Docker environment

type ProcessState

type ProcessState int
const (
	Unknown  ProcessState = iota
	NotFound ProcessState = iota
	Running
	InterruptibleSleep
	UninterruptibleSleep
	Stopped
	Zombie
	Dead
	TracingStop
	Idle
	Parked
	Waking
)

func (ProcessState) String

func (ps ProcessState) String() string

type RunnerEnvironment

type RunnerEnvironment interface {
	// Init setup the environment
	Init(ctx context.Context) error

	// NewProcess creates a new process for the environment
	NewProcess(bin string, args ...string) (Proc, error)

	// Stop the environment: can potentially be restarted
	Stop(ctx context.Context) error

	// Shutdown the environment: stop and remove all resources
	Shutdown(ctx context.Context) error

	// WithBinary ensures a binary is visible in the environment
	WithBinary(bin string) error

	// WithEnvironmentVariables sets the environment variables
	WithEnvironmentVariables(ctx context.Context, envs ...*resources.EnvironmentVariable)
}

A RunnerEnvironment controls running processes. Implementations: - local - docker - kubernetes (future)

func ResolveStandaloneEnvironment added in v0.1.155

func ResolveStandaloneEnvironment(ctx context.Context, dir string, runtimeCtx *basev0.RuntimeContext) RunnerEnvironment

ResolveStandaloneEnvironment returns a RunnerEnvironment that honors the plugin's declared RuntimeContext when possible, for callers that need a runner but are not plumbed through Runtime.CreateRunnerEnvironment.

Used by Code / Tooling when their shared Service.ActiveEnv is nil — typically when a Code RPC is served before Runtime.Init has run. The fallback chain:

  1. `ctx.Kind == Nix` AND nix is installed AND flake.nix is in dir → NixEnvironment.
  2. `ctx.Kind == Container` → **native fallback with caveat**. Code at the generic layer does not know the plugin's RuntimeImage (that's declared per-plugin in main.go), so we can't spin a Docker env here. Formatters and AST tools operate on host files and produce deterministic output — running them natively is acceptable. Use ActiveEnv (set by Init) when you need full mode consistency.
  3. Anything else → NativeEnvironment.

Never returns nil. Errors creating Nix fall through to native.

type Tracked

type Tracked interface {
	GetState(ctx context.Context) (ProcessState, error)
	GetCPU(ctx context.Context) (*CPU, error)
	GetMemory(ctx context.Context) (*Memory, error)
}

type TrackedProcess

type TrackedProcess struct {
	PID    int
	Killed bool
}

func (*TrackedProcess) GetCPU

func (p *TrackedProcess) GetCPU(ctx context.Context) (*CPU, error)

func (*TrackedProcess) GetMemory

func (p *TrackedProcess) GetMemory(ctx context.Context) (*Memory, error)

func (*TrackedProcess) GetState

func (p *TrackedProcess) GetState(ctx context.Context) (ProcessState, error)

Jump to

Keyboard shortcuts

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