lifecycle

package
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Nov 4, 2026 License: Apache-2.0 Imports: 25 Imported by: 0

Documentation

Overview

Package lifecycle is the engine's life as a process (design §13): one instance per state directory behind an exclusive lock, started on demand by any client command, and shut down again once it has been idle long enough. The engine's own rules — leases, fencing, sweeps — are the engine's; this package owns only its coming and going.

Index

Constants

View Source
const (
	TokenNameClient = "cli"
	TokenNameAdmin  = "admin"
	TokenNameRunner = "runner-1"
)

The names of the tokens EnsureTokenFile writes: one for the local client commands, one for the debug submit's admin calls, one for this machine's runner.

Variables

This section is empty.

Functions

func CheckConfigWritePath

func CheckConfigWritePath(path string) error

CheckConfigWritePath refuses symlinked config files. Parent directories may be symlinks, as when the user's config directory lives in a dotfiles repo. Init calls this before applying any part of its plan; the writer rechecks it.

func CheckSocketPath

func CheckSocketPath(path string) error

CheckSocketPath refuses a socket path the kernel could not bind, naming the length and the cap so the fix — a shorter state directory — is on the line.

func DefaultConfigPath

func DefaultConfigPath() string

DefaultConfigPath is where the config file lives when nothing names another place: $XDG_CONFIG_HOME/brigada/config, else ~/.config/brigada/config.

func DefaultStateDir

func DefaultStateDir() string

DefaultStateDir is where the engine's state lives when nothing names another place (§2): $XDG_STATE_HOME/brigada, else ~/.local/state/brigada.

func EnginePid

func EnginePid(path string) int

EnginePid reads the pid of the engine holding the lock at path, or 0 when none does. Callers that need certainty take the lock instead; this is for messages and tests.

func EngineRunning

func EngineRunning(ctx context.Context, socket string) bool

EngineRunning probes whether an engine answers on the socket: the start-on-demand question without the start.

func EnsureEngine

func EnsureEngine(ctx context.Context, o StartOptions) error

EnsureEngine makes sure an engine answers on the socket (§13): a probe first — an engine already there is never disturbed — then, when nothing answers, `brigada engine` started in the background, detached in its own session and logging into the state directory, and a bounded wait for its health.

func EnsureTokenFile

func EnsureTokenFile(path string) error

EnsureTokenFile creates the token file with fresh secrets when it does not exist yet (§6.4): a state directory born by its first engine start gets working tokens at once, mode 0600, written through a temp file so a concurrent starter never reads half of it.

func ParseTools

func ParseTools(value string) ([]string, error)

ParseTools parses a tool scope in its flag and config spelling: Claude Code tool patterns separated by "+", or "none" for the file tools alone. The result is non-nil for none, so a caller can tell an explicit none from no scope at all. A pattern containing a comma cannot ride an --agent value, which splits on commas.

func ResolveHarnessEnv

func ResolveHarnessEnv(refs map[string]map[string]string, harnesses []string, run SecretCommand) (map[string][]string, error)

ResolveHarnessEnv resolves the env.<harness>.<VAR> references of the config for the named harnesses into "VAR=value" entries per harness. A failure names the key and the reference, never a value. A nil run uses the real `security` program.

func Run

func Run(ctx context.Context, cfg Config) error

Run is the engine role (§13): it takes the single-instance lock, opens the store, records itself as the engine, and serves the API on the socket until ctx ends, serving fails, or it has been idle long enough. On the way out it flushes notifications (§10's hook), removes the socket and releases the lock, in that order.

func SetConfigValues

func SetConfigValues(path string, values map[string]string) (bool, error)

SetConfigValues updates only the supplied keys, preserving other lines, comments and blanks. Missing keys share one init comment. Writes are atomic; new files are private, while existing files retain their permissions.

func TokenForRole

func TokenForRole(path, role string) (name, secret string, err error)

TokenForRole reads the first token of role from the token file, by the name it authenticates as. A missing file or a file without the role is an error naming the file, so the fix — start an engine once, or add the token by hand — is on the line.

func ToolsRefusal

func ToolsRefusal(harness string) string

ToolsRefusal is why a harness other than claude takes no tools scope, naming the sandbox model it runs under instead (read from each adapter): brigada scopes the claude CLI's tools only.

Types

type Config

type Config struct {
	StateDir  string
	StorePath string
	Socket    string
	LockPath  string
	TokenFile string

	// Idle is how long with no live jobs and no connected requests the
	// engine tolerates before it exits (§13); zero keeps 30 minutes.
	Idle time.Duration
	// Caps are the concurrency caps and cost weights the config file
	// named (§8.1); the zero value caps nothing.
	Caps caps.Limits
	// Notify selects the local notifier (§10): notify.ModeAuto picks
	// the OS tool on PATH, notify.ModeNone silences notifications; the
	// empty value keeps auto.
	Notify string
	// NotifyWebhook is the optional webhook URL (§10): one JSON POST
	// per delivered notification, beside the OS banner. Empty = none.
	// The URL may carry a token, so it is never logged and never
	// reaches the status or the history.
	NotifyWebhook string
	// SnapshotDir is where the engine snapshots its store daily (§6.4);
	// empty takes no snapshots.
	SnapshotDir string
	// Rounds are the config file's default round caps; unnamed keys keep
	// the engine defaults.
	Rounds RoundsConfig
	// Review is the config file's default tier, effort and family rule of
	// a review; unnamed keys keep the engine defaults.
	Review engine.ReviewConfig
	// TaskAutoRetry controls transient work retries; nil keeps the default, on.
	TaskAutoRetry *bool
	// ForemanWakeBudget is the default daily foreman wake budget of every
	// project (§4.3); 0 keeps the built-in default of 20. A project's
	// foreman_wakes_per_day setting overrides it.
	ForemanWakeBudget int
	// DefaultReviewer is the harness a standalone review goes to when its
	// author's family cannot be told; empty = none.
	DefaultReviewer string
	// IdleTick is how often busyness is checked; zero derives a tick
	// from Idle, clamped to [200ms, 30s].
	IdleTick time.Duration
	// Now is the idle rule's clock; tests inject one and drive it. Nil
	// is real time.
	Now func() time.Time
	// FlushNotifications is the §10 hook, called once before an idle
	// exit with unsent notifications to deliver; nil is "nothing to
	// flush yet".
	FlushNotifications func(ctx context.Context) error
	// FlushGrace bounds every shutdown wait on the notification flush
	// and on a delivery still in flight (§13): past it the engine
	// leaves without the wait, with a log line. Zero keeps 10s.
	FlushGrace time.Duration
	// Notifier overrides the notifier the notify mode resolves (§10):
	// the tests inject one; production leaves it nil.
	Notifier notify.Notifier
	// Log is the engine's logger; nil uses slog's default.
	Log *slog.Logger
}

Config is the engine role's own settings (§13): where its state lives and when it may idle out. Paths left zero derive from StateDir.

type FileConfig

type FileConfig struct {
	StateDir    string
	IdleMinutes float64 // 0 = the default
	Notify      string  // notify = auto|none (§10); empty = auto
	// NotifyWebhook is the optional webhook URL (notify.webhook, §10):
	// one JSON POST per delivered notification. Empty = none. The URL
	// may carry a token, so it lives in this file alone — never in a
	// project's own files — and never reaches a log, status or error.
	NotifyWebhook string
	Caps          caps.Limits // concurrency caps, cost weights and tier overrides (§8.1)
	// Rounds are the default per-task round caps (rounds.fix,
	// rounds.review, rounds.restart); a key the file never names stays nil
	// and keeps the engine default.
	Rounds RoundsConfig
	// Review is the default tier, effort and family rule of a review
	// (review.tier, review.effort, review.family, §7, §8); a key the file
	// never names stays empty and keeps the engine default.
	Review engine.ReviewConfig
	// SnapshotDir is where the engine snapshots its store daily (§6.4):
	// an absolute directory outside the state directory's store. Empty =
	// no snapshots.
	SnapshotDir string
	// TaskAutoRetry controls one automatic restart after a transient work
	// failure. Nil keeps the engine default, on.
	TaskAutoRetry *bool
	// ForemanWakesPerDay is the default daily foreman wake budget of
	// every project (§4.3); 0 keeps the built-in default. A project's
	// foreman_wakes_per_day setting overrides it.
	ForemanWakesPerDay int
	// DefaultReviewer is the harness a standalone review (§7) goes to when
	// its author's family cannot be told. Empty = none.
	DefaultReviewer string
	// Tools is the default claude worker tool scope per "harness/model"
	// (§5.2, tools.<harness>/<model>): the parsed patterns, an empty
	// non-nil list for an explicit none. A pair the file never names has
	// no entry, which is the Go default and not none.
	Tools map[string][]string
	// Env holds the references of env.<harness>.<VAR> (§5.1), harness then
	// variable: keychain:<item> or file:<path>. Never a value.
	Env map[string]map[string]string
}

FileConfig is the user's config file (§2): the settings that do not change per invocation. One `key = value` per line; `#` comments and blank lines allowed; unknown keys are refused, so a typo cannot silently tune nothing.

func LoadConfigFile

func LoadConfigFile(path string) (FileConfig, error)

LoadConfigFile reads the config file. A missing file is the empty config, not an error: most installs have none.

func LoadRequiredConfigFile

func LoadRequiredConfigFile(path string) (FileConfig, error)

LoadRequiredConfigFile reads a config file explicitly named by the caller. Missing explicit files are errors: falling back could select unintended state.

type Lock

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

Lock is the single-instance lock (§13): an exclusive flock on a file beside the store. The kernel releases it when the process dies, however it dies, so a killed engine never wedges its state directory; the pid written into the file is for the message a second engine prints, not for the decision itself.

func AcquireLock

func AcquireLock(path string) (*Lock, error)

AcquireLock takes the exclusive lock at path. A lock another engine holds is refused with its pid named (§13: "a second engine refuses with a clear message naming the live one's pid").

func (*Lock) Release

func (l *Lock) Release() error

Release drops the lock and closes its file. The lock file itself stays: removing it would let a second engine create a fresh file and lock that, two engines in one state directory for a moment.

type Paths

type Paths struct {
	StateDir string
	Store    string
	Socket   string
	Lock     string
	Tokens   string
	Log      string
}

Paths are the engine's files inside one state directory: the store, the API's socket, the single-instance lock beside the store (§13), the token file (§6.4) and the log a detached engine writes.

func PathsOf

func PathsOf(stateDir string) Paths

PathsOf derives every path from stateDir.

type RecoverConfig

type RecoverConfig struct {
	StateDir  string
	StorePath string // zero derives it from StateDir
	LockPath  string // zero derives it from StateDir
	// SnapshotDir is where the engine's snapshots are; empty = none, and
	// the store is rebuilt from plan files alone.
	SnapshotDir string
	// Repos name repositories, beyond the projects the snapshot holds,
	// whose plan files are applied: a project born after the last
	// snapshot, or every project when there is no snapshot.
	Repos []string
	// Now is the clock; nil is real time.
	Now func() time.Time
}

RecoverConfig is what `brigada recover` works from (§6.4).

type RecoverReport

type RecoverReport struct {
	Snapshot   string // the snapshot restored; "" when there was none
	MovedAside string // where the old store file went; "" when there was none
	Plans      []engine.PlanRecovery
	Warnings   []string
	Epoch      int           // the fence epoch after the bump
	Hold       time.Duration // how long dispatch is held after the recovery
}

RecoverReport says what recover did, for the command to print.

func Recover

func Recover(ctx context.Context, cfg RecoverConfig) (RecoverReport, error)

Recover rebuilds the store (§6.4). Holding the engine's lock, so no engine runs or starts meanwhile, it

  1. moves a store file that is still there aside, never deleting it;
  2. restores the newest snapshot into a fresh store file at the same path through the backup API, or starts an empty store when there is none;
  3. applies the plan files of the repos' plans branches;
  4. bumps the fence epoch and stamps the recovery time, so late runners are fenced out and dispatch waits one lease-expiry window.

The tokens live beside the store, untouched.

type RoundsConfig

type RoundsConfig struct {
	Fix, Review, Restart *int
}

RoundsConfig holds the round caps the config file names; nil = unnamed.

func (RoundsConfig) Caps

func (r RoundsConfig) Caps() engine.RoundCaps

Caps resolves the file's round caps over the engine defaults.

type RunnerStartOptions

type RunnerStartOptions struct {
	StateDir   string
	ConfigPath string
	Binary     string
	Args       []string
	Wait       time.Duration
	// Restart, when "drain" or "now", makes a running runner make way
	// for the start: "drain" hands it a request it honours between
	// jobs — it takes no new lease and exits once its running jobs
	// finish — while "now" has it exit at once, cutting them. Empty
	// keeps a running runner running and only reports it.
	Restart string
}

RunnerStartOptions uses the same detach/log convention as EnsureEngine. Args are already validated runner flags, including the chosen agents.

type RunnerStartResult

type RunnerStartResult struct {
	Existing bool
	Record   runnerstate.Record
}

func EnsureRunner

EnsureRunner serializes starters until the child owns runner.lock. The runner itself holds that lock throughout its life, including manual starts. Stale logs and pid records never count as a running process.

type SecretCommand

type SecretCommand func(name string, args ...string) ([]byte, error)

SecretCommand runs a program and returns its standard output. It is the seam a test replaces; nil means the real program.

type StartOptions

type StartOptions struct {
	Socket   string
	StateDir string
	// ConfigPath, when set, is passed to the engine so it reads the same
	// config file the client did.
	ConfigPath string
	// Binary is the brigada to start; empty means this executable.
	Binary string
	// Wait overrides the 5 s health wait; zero keeps it.
	Wait time.Duration
	// Log is where the starter's messages go; nil uses slog's default.
	Log *slog.Logger
}

StartOptions is what starting the engine on demand needs (§13): where it answers, where its state lives, and — for the tests — which binary to start.

Jump to

Keyboard shortcuts

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