Documentation
¶
Overview ¶
Package runner builds the system prompt that devcell injects into agent CLIs (claude, opencode, codex) and the cell serve HTTP server.
The prompt has two distinct conceptual layers, always concatenated in order — see ContainerContext and ResolveSystemPrompt — and a third per-request layer that lives outside this package (cell serve merges per-request `instructions` / `system` role from the API body into the user prompt directly).
Index ¶
- Constants
- Variables
- func AssembleSystemPrompt(c config.Config, cellCfg cfg.CellConfig, opts ResolveOpts) (string, error)
- func BaseImageTag() string
- func BuildArgv(spec RunSpec, fs FS, lookPath func(string) (string, error)) []string
- func BuildImage(ctx context.Context, configDir string, noCache bool, verbose bool, ...) error
- func BuildVagrantSSHArgv(spec VagrantSpec) []string
- func ChangedBuildFiles(configDir string) ([]string, bool)
- func ContainerContext(c config.Config, cellCfg cfg.CellConfig) string
- func DiffBuildFile(configDir, name string) string
- func DiscoverStacks(ctx context.Context, configDir string, out io.Writer) ([]string, error)
- func DockerfileChanged(configDir string) bool
- func EnsureNetwork(ctx context.Context) error
- func ImageExists(ctx context.Context, tag string) bool
- func ImageVersions(ctx context.Context) (base, user string)
- func LocalImageID(ctx context.Context) (string, error)
- func ParseVagrantGlobalStatus(output string) map[string]string
- func ParseVagrantPortOutput(output, guestPort string) (string, bool)
- func PullImage(ctx context.Context, tag string, verbose bool) error
- func RemoveOrphanedContainer(ctx context.Context, name string) error
- func ResolveSystemPrompt(opts ResolveOpts) (string, error)
- func StackImageTag(stack string) string
- func UpdateFlakeLock(ctx context.Context, configDir string, lockOnly bool, verbose bool, ...) error
- func UserImageTag() string
- func VagrantBinaryExists(ctx context.Context, vagrantDir, binary string) bool
- func VagrantEnsureGUI(ctx context.Context, vagrantDir string, dryRun bool) error
- func VagrantEnsureUp(ctx context.Context, vagrantDir, provider string, dryRun bool) error
- func VagrantIsRunning(vagrantDir string) bool
- func VagrantMachineCreated(vagrantDir string) bool
- func VagrantMachinePort(machineID, guestPort string) (string, bool)
- func VagrantProvision(ctx context.Context, vagrantDir string, dryRun bool) error
- func VagrantReadForwardedPort(vagrantDir, portID string) (string, bool)
- func VagrantRunningCells() map[string]string
- func VagrantStatusRunning(output string) bool
- func VagrantUploadNixhome(ctx context.Context, vagrantDir, nixhomePath string, dryRun bool) error
- type FS
- type FSFunc
- type ImageMetadata
- type ResolveOpts
- type RunSpec
- type VagrantSpec
Constants ¶
const (
// DefaultRegistry is the fallback registry prefix for devcell images.
DefaultRegistry = "public.ecr.aws/w1l3v2k8/devcell"
)
Variables ¶
var Modules []string
Modules is the list of extra nix modules composed on top of the stack. Set from CellConfig at startup.
var PerSessionImage bool
PerSessionImage tags user images per tmux session instead of per stack. Set from CellConfig at startup; defaults to false (stack-based).
var Registry = DefaultRegistry
Registry is the active container registry. Set via cfg.ResolvedRegistry() at startup; defaults to DefaultRegistry.
var Stack = "base"
Stack is the resolved nix stack name (e.g. "ultimate", "go"). Set from CellConfig at startup; defaults to "base".
Functions ¶
func AssembleSystemPrompt ¶ added in v0.6.0
func AssembleSystemPrompt(c config.Config, cellCfg cfg.CellConfig, opts ResolveOpts) (string, error)
AssembleSystemPrompt is the single entry point callers should use to build the string passed to claude's --append-system-prompt (or any future agent's equivalent). It prepends ContainerContext to the resolved prompt with a blank-line separator. When the resolved prompt is empty, returns just ContainerContext.
func BaseImageTag ¶
func BaseImageTag() string
BaseImageTag returns the base image tag used in scaffold FROM, allowing override via DEVCELL_BASE_IMAGE env var (local dev, CI, tests).
func BuildArgv ¶
BuildArgv constructs the full docker run argv for the given spec. It is pure given injectable FS and LookPath.
func BuildImage ¶
func BuildImage(ctx context.Context, configDir string, noCache bool, verbose bool, out io.Writer) error
BuildImage runs docker build to build UserImageTag from configDir. verbose=true streams plain-text output to out; verbose=false suppresses all docker output (quiet mode) and captures stderr to out for error replay. --pull is always passed so Docker checks for a newer base image digest and busts the layer cache when the upstream image has been updated.
func BuildVagrantSSHArgv ¶ added in v0.5.0
func BuildVagrantSSHArgv(spec VagrantSpec) []string
BuildVagrantSSHArgv constructs the remote-command argv for:
vagrant ssh -- -t bash -l -c "cd ~/project && [env KEY=VAL ...] <binary> <defaultFlags...> <userArgs...>"
The remote command is wrapped in `bash -l -c "..."` so that the login shell sources ~/.profile and ~/.nix-profile/etc/profile.d/nix.sh, putting home-manager-installed binaries (claude, codex, etc.) on PATH.
When ProjectDir is set, the command cds into ~/basename(ProjectDir) first, mirroring Docker's --workdir behaviour. The post-up rsync trigger syncs the project there, so the agent sees the correct working directory.
The caller is responsible for running the command with its working directory set to VagrantDir (via cmd.Dir) so vagrant finds the correct Vagrantfile. It is a pure function: no I/O, no exec.
func ChangedBuildFiles ¶ added in v0.4.0
ChangedBuildFiles returns which build context files are newer than the image. Returns the list of changed file names and true if any changed.
func ContainerContext ¶ added in v0.6.0
func ContainerContext(c config.Config, cellCfg cfg.CellConfig) string
ContainerContext returns the auto-generated filesystem/runtime preamble — bind mounts, host path mappings, hard constraints — describing the devcell container the agent is running inside. Pure container facts; no user-controllable content.
This is what makes the agent file-aware: when the user mentions a host path, the agent can translate it to the matching container path. Every surface that ships a system prompt (cell claude, cell serve) prepends this so the agent reasons correctly about its filesystem.
func DiffBuildFile ¶ added in v0.4.0
DiffBuildFile returns a unified diff between the local build context file and the version baked into the image. Returns "" if the file isn't in the image (e.g. Dockerfile) or if they're identical. Uses docker cp to extract.
func DiscoverStacks ¶ added in v0.4.0
DiscoverStacks runs nix flake lock + discovers available stacks from the locked devcell input inside a Docker container. Returns stack names (e.g. "base", "go"). Falls back to nil on error (caller should use hardcoded defaults).
func DockerfileChanged ¶
DockerfileChanged reports whether any build-input file in configDir (Dockerfile, flake.nix) is newer than the user image. Returns true when the user image doesn't exist or inspect fails.
func EnsureNetwork ¶
EnsureNetwork creates the devcell-network docker network if it doesn't exist.
func ImageExists ¶
ImageExists returns true if a Docker image with the given tag exists locally.
func ImageVersions ¶ added in v0.3.0
ImageVersions reads build metadata from the user image. Returns (base, user) strings for backward compatibility with callers.
func LocalImageID ¶
LocalImageID returns the full image ID (sha256:...) of the user image. Used to pin the running container to the exact image just built, rather than the mutable tag which could race with a concurrent build.
func ParseVagrantGlobalStatus ¶ added in v0.5.0
ParseVagrantGlobalStatus extracts running devcell VM entries from `vagrant global-status` output. Returns projectBasename → machineID for running VMs.
Only VMs whose directory ends in ".devcell" are considered devcell cells. UTM reports state as "started"; other providers use "running" — both are accepted.
Output format:
id name provider state directory abc1234 default utm started /Users/dmitry/dev/myproject/.devcell
func ParseVagrantPortOutput ¶ added in v0.5.0
ParseVagrantPortOutput extracts the host port for guestPort from `vagrant port --machine-readable` output. Line format: timestamp,target,forwarded_port,guestPort,hostPort
func PullImage ¶ added in v0.4.0
PullImage attempts to pull a Docker image. Returns nil on success. When verbose is true, docker pull output is streamed to os.Stderr.
func RemoveOrphanedContainer ¶
RemoveOrphanedContainer removes a stopped container with the given name if it exists. Returns nil if the container doesn't exist or was successfully removed. Returns an error if the container is currently running.
func ResolveSystemPrompt ¶ added in v0.6.0
func ResolveSystemPrompt(opts ResolveOpts) (string, error)
ResolveSystemPrompt walks the seven-tier source chain in order — flags, env, TOML — returning the first match. Within a tier, setting both the file and inline form is rejected as ambiguous so the caller never has to guess which one won. Across tiers, higher silently shadows lower: the layering is the whole point of having multiple sources.
Returns ("", nil) when no source is set — callers concatenate this with ContainerContext via AssembleSystemPrompt.
Resolution order (first match wins):
- opts.FlagFile (--system-prompt-file)
- opts.FlagInline (--system-prompt)
- opts.EnvFile (DEVCELL_SYSTEM_PROMPT_FILE)
- opts.EnvInline (DEVCELL_SYSTEM_PROMPT)
- CellCfg.LLM.SystemPromptFile ([llm].system_prompt_file)
- CellCfg.LLM.SystemPrompt ([llm].system_prompt)
- ""
func StackImageTag ¶ added in v0.4.0
StackImageTag returns the registry tag for a pre-built stack image. e.g. "go" → "ghcr.io/dimmkirr/devcell:v1.2.3-go"
func UpdateFlakeLock ¶ added in v0.3.0
func UpdateFlakeLock(ctx context.Context, configDir string, lockOnly bool, verbose bool, out io.Writer) error
UpdateFlakeLock runs nix flake lock (or update) inside a temp base container with configDir bind-mounted. When lockOnly is true, runs "nix flake lock" (resolves inputs, generates lock if missing, doesn't update existing pins). When lockOnly is false, runs "nix flake update" (pulls latest for all inputs).
func UserImageTag ¶
func UserImageTag() string
UserImageTag returns the user image tag. Default (stack-based): devcell-user:<stack> or devcell-user:<stack>-<mod1>-<mod2>-<sha8> Legacy (per_session_image=true): devcell-user:<session> (one image per tmux session) Override with DEVCELL_USER_IMAGE env var (used by tests).
func VagrantBinaryExists ¶ added in v0.5.0
VagrantBinaryExists checks whether a binary is reachable and executable in the VM's login shell. Used for auto-detect: if the binary is missing, the caller should provision before running.
func VagrantEnsureGUI ¶ added in v0.5.0
VagrantEnsureGUI starts GUI services (Xvfb, fluxbox, x11vnc, xrdp) inside the VM if they are not already running. Idempotent — pgrep guards prevent double-start. Called when the cell stack includes the desktop module and GUI is enabled.
func VagrantEnsureUp ¶ added in v0.5.0
VagrantEnsureUp brings the VM up if it is not already running. In dry-run mode prints the would-be command and returns.
func VagrantIsRunning ¶ added in v0.5.0
VagrantIsRunning checks whether the vagrant VM in vagrantDir is currently running. Returns false quickly when vagrantDir has no Vagrantfile (no subprocess needed). When vagrant CLI is unavailable, falls back to VagrantMachineCreated which checks whether the machine has been provisioned at least once (id file present).
func VagrantMachineCreated ¶ added in v0.5.0
VagrantMachineCreated returns true if the vagrant VM in vagrantDir has been created at least once — i.e. .vagrant/machines/default/<provider>/id exists. Used as a fallback when vagrant CLI is not available in the current environment.
func VagrantMachinePort ¶ added in v0.5.0
VagrantMachinePort returns the host port mapped from guestPort for the VM identified by machineID. Uses `vagrant port <id> --machine-readable` — no file-system access needed, works regardless of where the Vagrantfile lives on disk.
func VagrantProvision ¶ added in v0.5.0
VagrantProvision runs `vagrant provision` to (re-)apply the nixhome flake. In dry-run mode prints the would-be command and returns.
func VagrantReadForwardedPort ¶ added in v0.5.0
VagrantReadForwardedPort reads the Vagrantfile in vagrantDir and returns the host port for the forwarded_port entry with the given id ("rdp" or "vnc"). Looks for lines of the form:
config.vm.network "forwarded_port", guest: 3389, host: 36289, id: "rdp"
func VagrantRunningCells ¶ added in v0.5.0
VagrantRunningCells parses `vagrant global-status` and returns a map of projectBasename → machineID for all running devcell VMs. Returns an empty map (not an error) if vagrant is not installed or has no VMs.
func VagrantStatusRunning ¶ added in v0.5.0
VagrantStatusRunning parses `vagrant status --machine-readable` output and returns true if the machine state is "running" (libvirt/virtualbox) or "started" (UTM).
Machine-readable format: timestamp,target,type,data (CSV, 4 fields) We look for a record where type=="state" and data is "running" or "started".
func VagrantUploadNixhome ¶ added in v0.5.0
VagrantUploadNixhome uploads a local nixhome directory into the VM at ~/nixhome using `vagrant upload <src> nixhome`. No-op when nixhomePath is empty. The provisioner checks $HOME/nixhome first (set by this upload), then falls back to GitHub.
Types ¶
type ImageMetadata ¶ added in v0.4.0
type ImageMetadata struct {
BaseImage string `json:"base_image"`
Stack string `json:"stack"`
Modules []string `json:"modules"`
GitCommit string `json:"git_commit"`
BuildDate string `json:"build_date"`
Packages int `json:"packages"`
}
ImageMetadata holds structured build metadata from /etc/devcell/metadata.json.
func ImageMetadataFromContainer ¶ added in v0.4.0
func ImageMetadataFromContainer(ctx context.Context) ImageMetadata
ImageMetadataFromContainer reads /etc/devcell/metadata.json from the user image. Falls back to legacy base-image-version + user-image-version files.
func ParseImageMetadata ¶ added in v0.4.0
func ParseImageMetadata(data []byte) ImageMetadata
ParseImageMetadata parses JSON into ImageMetadata. Returns zero value on error.
type ResolveOpts ¶ added in v0.6.0
type ResolveOpts struct {
// FlagFile / FlagInline are the --system-prompt-file / --system-prompt
// CLI flags. Currently exposed only on `cell serve`.
FlagFile, FlagInline string
// EnvFile / EnvInline are the DEVCELL_SYSTEM_PROMPT_FILE /
// DEVCELL_SYSTEM_PROMPT env vars. Read by every surface.
EnvFile, EnvInline string
// CellCfg supplies [llm].system_prompt and [llm].system_prompt_file
// from the merged devcell.toml.
CellCfg cfg.CellConfig
// CfgBaseDir is the project base dir, used to resolve a relative
// `[llm].system_prompt_file` path. Empty disables relative resolution
// (absolute paths still work).
CfgBaseDir string
}
ResolveOpts bundles every input source the system-prompt resolver looks at. Surfaces wire only the inputs they have — `cell claude` leaves the flag fields empty; `cell serve` populates everything.
type RunSpec ¶
type RunSpec struct {
Config config.Config
CellCfg cfg.CellConfig
Binary string
DefaultFlags []string
UserArgs []string
Debug bool // pass DEVCELL_DEBUG=true into the container
Image string // image ID or tag to run; defaults to UserImageTag
ExtraEnv map[string]string // additional env vars injected by the command handler
InheritEnv []string // env var names to inherit from host (passed as -e KEY with no value)
Getenv func(string) string // env lookup; defaults to os.Getenv when nil
}
RunSpec holds everything needed to build the docker run argv.
type VagrantSpec ¶ added in v0.5.0
type VagrantSpec struct {
Config config.Config
CellCfg cfg.CellConfig
Binary string // agent binary to run inside the VM (e.g. "claude")
DefaultFlags []string // flags always passed to the binary
UserArgs []string // additional args from the user
VagrantDir string // directory containing the Vagrantfile
Provider string // vagrant provider ("utm" or "libvirt")
EnvVars []string // KEY=VALUE pairs to set inside the VM via `env`
ProjectDir string // host project directory — basename is used as workdir in VM
}
VagrantSpec holds everything needed to build a vagrant ssh argv.