cell

package
v0.0.0 Latest Latest
Warning

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

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

Documentation

Overview

Package cell holds the cell-semantics layer that every engine (docker, tart, winkit) shares: the environment a guest receives and the shell quoting used to hand it over. Engines decide how to transport these values (docker -e flags, `env K=V` over tart exec, WSL over ssh); they must not decide what the values are.

Index

Constants

View Source
const (
	DefaultGitName  = "DevCell"
	DefaultGitEmail = "devcell@devcell.io"
)

Defaults for any git identity field no precedence tier supplies.

View Source
const BasePromptFile = "system-prompt.md"

BasePromptFile is the generated file carrying the base prompt — the one that REPLACES Claude Code's built-in prompt.

View Source
const DefaultLocale = "en_US.UTF-8"

DefaultLocale is used for LANG and LC_ALL when neither cell.locale nor a usable host $LANG is set.

View Source
const OverlayPromptFile = "additional-systemprompt.md"

OverlayPromptFile is the generated file carrying container context plus the resolved operator prompt — everything that layers *on top of* whichever base prompt is in effect.

Variables

This section is empty.

Functions

func AssembleOverlayPrompt

func AssembleOverlayPrompt(c config.Config, cellCfg cfg.CellConfig, opts ResolveOpts) (string, error)

AssembleOverlayPrompt builds the overlay: the auto-generated container context followed by the resolved append prompt. This is what reaches claude via --append-system-prompt-file.

Container context lives on the overlay rather than the base deliberately — it is regenerated per run from live container facts, so a user-supplied base prompt must not be able to displace it.

When the append layer resolves empty, the container context alone is returned; it is never empty, so the overlay file is always written.

func ClaudePromptFlags

func ClaudePromptFlags(c config.Config, cellCfg cfg.CellConfig, opts ResolveOpts) ([]string, error)

ClaudePromptFlags materializes both prompt layers and returns the argv that points claude at them.

The base flag is emitted only when a base prompt is configured. Claude Code's stock prompt is ~10.6 KB of tool guidance and safety instructions, and --system-prompt-file discards all of it — so an unconfigured cell must keep it.

Only the file forms are emitted: claude rejects the inline and file forms of the same layer together, and inline capped the prompt at MAX_ARG_STRLEN while exposing its text to `ps aux` and `docker inspect`.

func CodexPromptFlags

func CodexPromptFlags(c config.Config, cellCfg cfg.CellConfig, opts ResolveOpts) ([]string, error)

CodexPromptFlags builds the overlay prompt (container context + TOML append prompt) and returns it as inline argv for Codex's -c developer_instructions.

Unlike ClaudePromptFlags, which writes files and passes --system-prompt-file / --append-system-prompt-file, Codex has no file-based flag — the content travels as a TOML config value on the command line.

func ContainerContext

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 FillOpenRouterKey

func FillOpenRouterKey(env map[string]string) error

FillOpenRouterKey fills OPENROUTER_API_KEY from the environment. Called after 1Password resolution so the key is available. Env builders that need the key set OPENROUTER_API_KEY to "" as a placeholder; the engine fills it.

func GuestEnv

func GuestEnv(in EnvInput) []string

GuestEnv returns the KEY=VALUE pairs every runtime hands to its guest, in this fixed order (later pairs win for duplicate keys, matching both `docker run -e` and `env`):

CELL_NAME, APP_NAME, DEVCELL_WORKSPACE, DEVCELL_CELL_NAME, DEVCELL_CONTAINER=1, IS_SANDBOX=1, DEVCELL_PROJECT_DIR, WORKSPACE
GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL (ResolveGitIdentity)
TZ:          [cell].timezone > host $TZ > omitted
LANG/LC_ALL: [cell].locale > host $LANG (unless C or POSIX) > DefaultLocale
[env]        sorted by key, so a user key overrides any built-in above
[mise]       sorted by key, as MISE_<UPPER_KEY>

TERM is not included: it describes the host terminal the session is attached to, and each runtime forwards it with its own transport.

func HostGitConfig

func HostGitConfig(key string) string

HostGitConfig returns `git config --get <key>` as seen from the current directory on the host, or "" when git is missing or the key is unset.

func ResolveAppendPrompt

func ResolveAppendPrompt(opts ResolveOpts) (string, error)

ResolveAppendPrompt walks the same tier chain over the *append* sources. It never reads the base sources: after the split, [llm].system_prompt replaces Claude Code's built-in prompt while the append layer stacks on top of whichever base ends up in effect.

Resolution order (first match wins):

  1. opts.AppendFlagFile (--append-system-prompt-file)
  2. opts.AppendFlagInline (--append-system-prompt)
  3. opts.AppendEnvFile (DEVCELL_APPEND_SYSTEM_PROMPT_FILE)
  4. opts.AppendEnvInline (DEVCELL_APPEND_SYSTEM_PROMPT)
  5. CellCfg.LLM.AppendSystemPromptFile ([llm].append_system_prompt_file)
  6. CellCfg.LLM.AppendSystemPrompt ([llm].append_system_prompt)
  7. ""

func ResolveSystemPrompt

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, which is the signal that no base is configured and Claude Code's built-in prompt must stay in effect.

Resolution order (first match wins):

  1. opts.FlagFile (--system-prompt-file)
  2. opts.FlagInline (--system-prompt)
  3. opts.EnvFile (DEVCELL_SYSTEM_PROMPT_FILE)
  4. opts.EnvInline (DEVCELL_SYSTEM_PROMPT)
  5. CellCfg.LLM.SystemPromptFile ([llm].system_prompt_file)
  6. CellCfg.LLM.SystemPrompt ([llm].system_prompt)
  7. ""

func SessionID

func SessionID(appName string) string

SessionID returns a deterministic UUIDv5 derived from appName. Same appName (project+pane) always produces the same UUID, so agents resume the same conversation when relaunched in the same tmux pane.

func ShellJoin

func ShellJoin(words []string) string

ShellJoin quotes each word with ShellQuote and joins them with spaces, producing a string a POSIX shell (e.g. `bash -c`) splits back into words.

func ShellQuote

func ShellQuote(s string) string

ShellQuote returns s as exactly one POSIX shell word. Words made only of letters, digits and _@%+=:,./- are returned unchanged for readability. Everything else is wrapped in single quotes; an embedded single quote closes the quoting, is emitted backslash-escaped, and reopens it. The empty string becomes an empty pair of single quotes.

func StackTag

func StackTag(stack string, modules []string) string

StackTag returns the canonical tag for a stack plus optional modules, used to name per-stack VM templates and images. Module order does not matter. Examples: "ultimate", "dev-linear-plex-a1b2c3d4".

func WriteBasePrompt

func WriteBasePrompt(c config.Config, opts ResolveOpts) (string, error)

WriteBasePrompt materializes the base prompt, returning the container path for --system-prompt-file — or "" when no base is configured.

The empty return is the switch that keeps this opt-in: with no base set, the caller emits no flag and Claude Code's stock prompt stays in effect. The content is written verbatim; container context belongs on the overlay.

func WriteOverlayPrompt

func WriteOverlayPrompt(c config.Config, cellCfg cfg.CellConfig, opts ResolveOpts) (string, error)

WriteOverlayPrompt assembles the overlay and materializes it, returning the container path for --append-system-prompt-file.

Container context is always present, so this file is always written even when no append prompt is configured.

func WritePromptFile

func WritePromptFile(c config.Config, name, content string) (string, error)

WritePromptFile materializes prompt content to disk and returns the path the *container* will read it at.

Prompts used to travel as a single argv element. That capped them at MAX_ARG_STRLEN (128 KiB on Linux) and published their full text to `ps aux` and `docker inspect`. Writing a file and passing its path removes both limits — claude reads it via --system-prompt-file / --append-system-prompt-file.

Files are namespaced per cell. .devcell/ is per project, but a container is per (cell, project) pair and ContainerContext embeds the cell name, so a shared path would let one cell boot with another cell's container context.

The returned path is the container-side path. Prompt files live under the workspace home (not the project dir) because the project bind mount inherits the host directory's permissions. On Linux with a UID mismatch (host UID != container UID) and a private home dir (0700), the container session user cannot traverse the project mount at all.

The workspace home is mounted at /home/<HostUser>, and the entrypoint chowns it to the session user, so files there are always accessible.

Types

type EnvInput

type EnvInput struct {
	WorkspaceName string              // DEVCELL_WORKSPACE (and deprecated DEVCELL_CELL_NAME); omitted when empty
	CellName      string              // CELL_NAME (and deprecated APP_NAME); omitted when empty
	ProjectDir    string              // DEVCELL_PROJECT_DIR (and deprecated WORKSPACE); omitted when empty
	Git           cfg.GitSection      // [git]
	Timezone      string              // [cell].timezone
	Locale        string              // [cell].locale
	Env           map[string]string   // [env], already expanded against the host
	Mise          map[string]string   // [mise]
	Getenv        func(string) string // host env lookup; nil reads nothing
	GitConfig     func(string) string // host `git config` lookup (HostGitConfig); nil skips that tier
}

EnvInput is everything GuestEnv needs. Runtimes fill it from the loaded cfg.CellConfig plus what they know about the guest.

type GitIdentity

type GitIdentity struct {
	AuthorName     string
	AuthorEmail    string
	CommitterName  string
	CommitterEmail string
	Source         GitSource
}

GitIdentity is the author and committer identity handed to the guest. Every field is non-empty once resolved.

func ResolveGitIdentity

func ResolveGitIdentity(getenv func(string) string, git cfg.GitSection, gitConfig func(string) string) GitIdentity

ResolveGitIdentity picks the guest git identity. Tiers are taken whole: the first tier that sets anything wins, and any field it leaves empty falls back to DefaultGitName / DefaultGitEmail rather than to a lower tier.

  1. Host env: any of GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL is set.
  2. [git] in devcell.toml: any field set. Committer fields default to the author fields (cfg.GitSection.ResolvedCommitter*).
  3. Host `git config user.name` / `user.email`, via gitConfig. Used for both author and committer. A nil gitConfig skips this tier.
  4. DefaultGitName / DefaultGitEmail.

A nil getenv reads no host env.

type GitSource

type GitSource string

GitSource names the precedence tier a GitIdentity came from.

const (
	GitSourceHostEnv   GitSource = "host env"
	GitSourceConfig    GitSource = "[git]"
	GitSourceGitConfig GitSource = "git config"
	GitSourceDefault   GitSource = "default"
)

type ResolveOpts

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
	// AppendFlagFile / AppendFlagInline are the --append-system-prompt-file /
	// --append-system-prompt CLI flags. Currently exposed only on `cell serve`.
	AppendFlagFile, AppendFlagInline string
	// AppendEnvFile / AppendEnvInline are DEVCELL_APPEND_SYSTEM_PROMPT_FILE /
	// DEVCELL_APPEND_SYSTEM_PROMPT. Read by every surface.
	AppendEnvFile, AppendEnvInline string
	// CellCfg supplies [llm].system_prompt / system_prompt_file (base) and
	// [llm].append_system_prompt / append_system_prompt_file (overlay) 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.

Jump to

Keyboard shortcuts

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