grid_runtime library

M3 runtime providers for the_grid — the layer that gives the_grid hands.

Ports gc's runtime.Provider contract (ADR-0004) into Dart, trimmed to what M3 consumes: Futures for acts (start/stop/interrupt), Streams for observations (a sealed RuntimeEvent stream + live session output). A SubprocessProvider (the Friday dogfood default) spawns a claude agent per ready bead in a git worktree, supervises it as a process group, and tracks the session as a the_grid-owned bead through the single bd write chokepoint (bd-only, --actor grid-controller, never SQL). A TmuxProvider over the standalone tmux package is the gc-compatible alternative. Layering follows predictable-flutter (Services → Repositories → Interactors → View); see docs/adr/ADR-0002 + ADR-0004 and docs/M3-BUILD-ORDER.md.

Track 2 built. The RuntimeProvider interface + RuntimeConfig / RuntimeEvent / RuntimeCapabilities value types + the SubprocessProvider impl (new-process-group spawn, explicit env allowlist, whole-tree kill) are implemented here.

Track 3 built. StationGitService gives git-worktree-per-bead isolation: Layer-1 root-checkout registration (probed default branch from origin/HEAD), Layer-2 git worktree add -b grid/<beadId> under <root>/.grid/worktrees/<rig>/<beadId>, the land step (commit → push → open PR via the injectable PrOpener, never auto-merge), and the VERBATIM gc safety: the three-gate fail-closed reaper, the GIT_* env blacklist on every exec, and the stale-ancestor guard.

Track 4 built. Lifecycle-as-beads + the single bd write chokepoint: a Dart port of gc's session state transition table (session_state.dart), the BeadOwnershipPredicate (the bead-shaped ownership gate sharing the rig allow-set with M2's OwnsSubstations), the StationBeadWriter chokepoint (fail-closed ownership re-check before EVERY create/update/close/delete, bd-only, --actor grid-controller), and the RuntimeActuator that consumes Track-2 RuntimeEvents and writes session beads THROUGH the chokepoint — including crash detection → restart / crash-loop quarantine.

Classes

AgentEnvAllowlist
The explicit parent-environment allowlist for a spawned agent child — the Dart port of gc's processenv.ProviderProcessPassthroughEnv (gascity/internal/processenv/provider.go:98-126).
BeadOwnershipPredicate
The bead-shaped ownership gate (ADR-0006 Decision 1; ADR-0000 A32) — the dispatch-side analog of the substation-ownership check grid_engine's convergence path applies to convergence beads.
BeadWorktree
A provisioned per-bead worktree — Layer 2 (ADR-0006 Decision 3). Plain value type. The dir name encodes the bead id so an orphaned worktree can be re-bound to its lifecycle bead on restart without external state (WorktreeLayout.beadIdFromName).
CrashDecision
What RuntimeActuator decided to do with a crashed session — surfaced so the dispatcher (Track 5) can re-spawn a restart or leave a quarantined session parked. Futures for acts, Streams for observations: this is returned by the act (onExited/onDied) AND emitted on decisions for observers.
GhPrOpener
The real PR opener: shells gh pr create. Constructed ONLY by the live dogfood arming — the offline test suite always injects a fake. Kept tiny and over the GitRunner-style shell so it inherits no real-GitHub dependency in the type graph the tests exercise.
GitOps
Low-level git operations scoped to a working directory — the Dart port of gc's Git (gascity/internal/git/git.go), over the injectable GitRunner seam. This is a stateless Service in predictable-flutter terms (owns one source: the git binary); StationGitService composes it.
GitRunner
The git-command SEAM — the single point where StationGitService shells out to git. A reference type (carries the Runner role name; predictable-flutter).
GitRunResult
The result of one git invocation — stdout (combined with stderr, gc's CombinedOutput), the exit code, and a flag for whether the process even launched. Modelled as a plain value type (predictable-flutter: value types are plain).
GitWorktree
One git worktree entry, parsed from git worktree list --porcelain. Plain value type (predictable-flutter). gc's git.Worktree (internal/git/git.go:13-18).
GridIssueTypes
the_grid's registered custom issue-type vocabulary.
GridReadyWorkSource
The live ReadyWorkSource over a beads_dart GridControllerRuntime.
IncarnationEnv
The per-incarnation environment a live agent session receives from the controller — the_grid's GRID_* analog of gc's session.RuntimeEnv (gascity/internal/session/lifecycle.go:30-67), trimmed to the four vars M3 needs (M3-BUILD-ORDER Track 2).
LandResult
The result of the land step (DIVERGES from gc; ADR-0006 Decision 3): commit → push → open PR. Carries either the PullRequestRef or a failure reason so the caller records the outcome on the lifecycle bead.
PrimaryCheckoutFreshness
Immutable result of inspecting one Layer-1 primary checkout.
ProcessGroupController
The OS process-group SEAM — the single point where SubprocessProvider touches process signalling and pgid resolution. A reference type (carries the Controller role name; predictable-flutter).
PrOpener
The PR-opener SEAM — the single point where the land step opens a pull request. INJECTABLE so the whole land path (commit → push → open PR) runs offline against a fake that records the branch it was asked to open (Fakes, not mocks). A reference type (the Opener role name; predictable-flutter).
PrOpenFailure
Why a PR open did not produce a PullRequestRef. Modelled as a sealed-ish value so the land step can record the failure on the lifecycle bead rather than throwing.
PullRequestRef
A reference to an opened pull request — what the land step records on the lifecycle bead (ADR-0006 Decision 3). Plain value type (predictable-flutter).
PullRequestResult
The result of PrOpener.open: either the ref or a failure. A tiny sealed union via a record-style holder (kept simple; no freezed needed for two branches consumed at one call site).
QuarantineSession
The session crashed too many times in the window — quarantined. The bead is parked at state=quarantined with quarantine_cycle/quarantined_until (gc's QuarantinePatch); the dispatcher does NOT re-spawn it.
ReadyWorkSource
The dispatch read seam over beads_dart's reactive surface (M3 Track 5; ADR-0006 Decision 1) — a second consumer of the same observable surface grid_engine's convergence path reads. The DispatchInteractor attaches as a SECOND consumer of the same observable surface M2 uses; it does not go through reduce→gate→actuate.
ReapOutcome
The outcome of a reap attempt — distinguishes a clean removal from a fail-closed REFUSAL, so the dispatcher (and a test) can assert WHY a worktree was kept. Mirrors gc's reaper skip-vs-remove split (cmd/gc/bead_worktree_reaper.go:101-141).
RestartSession
The session should be restarted: a fresh incarnation. The bead is NOT closed; restart_requested is set so the dispatcher re-spawns (gc's RequestFreshRestart, manager.go:867-879).
RootCheckout
The Layer-1 root checkout registration — a the_grid-OWNED real clone of the target repo (lenny), registered ONCE, with origin set and the default branch probed from origin/HEAD (ADR-0006 Decision 3; gc's substation model). This is NOT a worktree; it is the_grid's "rig" in gc's sense.
RuntimeActuator
The bd write chokepoint consumer (M3 Track 4): turns Track-2 RuntimeEvents into state transitions on the_grid-owned session beads, written exclusively through the StationBeadWriter chokepoint (bd-only, --actor grid-controller, fail-closed ownership re-check before every write).
RuntimeCapabilities
What a RuntimeProvider can reliably detect, so callers degrade explicitly instead of assuming — gc's ProviderCapabilities (runtime.go:197-199 Capabilities()), as a record-style freezed value.
RuntimeConfig
Parameters for starting one agent session — the Dart port of gc's runtime.Config (runtime.go:459-578), trimmed to M3 (M3-BUILD-ORDER Track 2): the overlay/pack/MCP/T3/fingerprint/dialog machinery is CUT (reference only). What survives is exactly what SubprocessProvider needs to spawn a claude per ready bead in its worktree.
RuntimeEvent
An observation from a RuntimeProvider's session lifecycle, emitted on the events stream (Streams for observations; APIs convention, CLAUDE.md).
RuntimeProvider
Manages agent sessions — the Dart port of gc's runtime.Provider (gascity/internal/runtime/runtime.go:107-200), trimmed to M3 (M3-BUILD-ORDER Track 2). A reference type (the Provider role name; no extra classifier — predictable-flutter).
SessionParked
The session ended cleanly — no restart, no quarantine. The lifecycle bead is transitioned to asleep (a clean exit) or closed (the caller's terminal choice via RuntimeActuator.closeSession).
SpawnedProcess
A handle over one spawned agent process — the seam's return value, so a fake can synthesize stdout/stderr streams and an exit signal without a real OS process.
StationBeadWriter
The single bd write chokepoint (ADR-0006 Decision 2; ADR-0000 A32) — the ONLY path through which the_grid's session/lifecycle/recovery beads are written, wrapping the M2 BdCliService.
StationGitService
The Service that gives the_grid git-worktree-per-bead isolation (M3 Track 3; ADR-0006 Decision 3). Stateless IO over the injectable GitRunner + PrOpener seams (predictable-flutter: a Service owns one source — here the git binary + the PR-open boundary — and is constructed with its dependencies). Futures for acts (register/provision/land/reap); point-in-time list is a Future too (a read act, not an observation stream).
SubprocessProvider
SubprocessSpawner
The Process SEAM for spawning agents — the single point where SubprocessProvider touches Process.start. Mirrors beads_dart's BdRunner: the real impl (SystemSubprocessSpawner) spawns; tests inject a fake that returns a programmed handle, so the supervision/event/env logic runs offline (Fakes, not mocks). A reference type (the Spawner role name).
SystemGitRunner
The real seam: execs git with the blacklist stripped and combines stdout+stderr. The ONLY place this file touches dart:io process spawning.
SystemProcessGroupController
The real seam: resolves pgid via ps, probes liveness with a harmless signal, and signals groups via Process.killPid(-pgid, …).
SystemSubprocessSpawner
Spawns real agent subprocesses via dart:io with the Track-2 contract.
WorktreeLayout
Pure helpers for the <root>/.grid/worktrees/<substation>/<beadId> layout + grid/<beadId> branch naming — mirrors gc's .gc/worktrees/<substation>/<name> (internal/workdir/workdir.go:76-86). Separated out so the path/branch derivation and the bead-id round-trip are unit-tested with no IO.

Enums

GateOutcome
The outcome of one of the three pre-removal safety gates. Distinguishes a clean "no" from a fail-closed "couldn't tell, assume unsafe" so callers (and tests) can assert WHICH rung tripped and that a probe ERROR is treated as unsafe — not silently as safe. gc collapses this into a bool that returns true on probe error (git.go:146-152,166-172); we keep the distinction explicit because fail-closed-on-probe-error is the load-bearing invariant.
GroupTerminateResult
The result of a terminateGroup escalation, so the caller (and a test) can assert which rung fired without scraping logs.
Lifecycle
The expected lifetime of a runtime command — gc's runtime.Lifecycle (gascity/internal/runtime/runtime.go:451-457), trimmed to the two M3 cases.
LifecycleCommand
What triggered a state change — the verb the runtime invoked, not the resulting state (gc's TransitionCommand vocabulary, state_machine.go).
PrimaryCheckoutState
Classified state of one Layer-1 primary checkout.

Extension Types

LifecycleState
The fine-grained session lifecycle state stamped on metadata.state.

Extensions

GridIssueTypeClassification on IssueType
the_grid-only classifications over beads' open IssueType.

Constants

gitEnvBlacklist → const Set<String>
The git environment variables that MUST be stripped before every git exec so a subprocess git command uses the intended cwd, not a parent repo's GIT_DIR/GIT_WORK_TREE. Verbatim from gc's gitEnvBlacklist (gascity/internal/git/git.go:285-301). the_grid is itself a git repo invoked from melos/hooks, so this leak is real, not theoretical.
gridRuntimeScaffold → const String
The package's identity, exposed so the empty scaffold has a referenced symbol and downstream wiring can assert the package is on the path before Track 2 lands. Replaced by the real runtime surface in Track 2.

Functions

allowedCommands(LifecycleState from) List<LifecycleCommand>
The commands legal from from (gc's AllowedCommands), sorted by name for a stable rendering — useful for diagnostics / "what can happen next?".
cleanGitEnvironment(Map<String, String> parentEnv) Map<String, String>
Builds the clean child environment for a git exec: every entry of parentEnv EXCEPT the gitEnvBlacklist keys (gc's git.go:314-320). Pure over an injected map so the blacklist is unit-tested with a fake env.
establishStationProcessGroup({required int stationPid, ProcessGroupController controller = const SystemProcessGroupController(), SetSidCall setSid = _systemSetSid}) Future<int>
Makes stationPid the leader of a new session and process group.
gateBlocks(GateOutcome outcome) bool
Whether GateOutcome blocks removal — both GateOutcome.present and GateOutcome.probeError block; only GateOutcome.clear permits.
isStrictlyUnderDir(String dir, String path) bool
Whether path is strictly contained within dir — the scope gate that guarantees the reaper can only ever delete inside the_grid's own worktrees root. gc's isStrictlyUnderDir (cmd/gc/bead_worktree_reaper.go:191-198).
newInstanceToken([Random? random]) String
A cryptographically random 16-byte hex token for fencing drain/stop and async delivery against a stale session incarnation — gc's session.NewInstanceToken (lifecycle.go:21-27).
parseWorktreeList(String output) List<GitWorktree>
Parses git worktree list --porcelain output. Each block is separated by a blank line: worktree <path>, HEAD <sha>, branch refs/heads/<name>. gc's parseWorktreeList (git.go:331-360).
systemEnvironment() Map<String, String>
The live process environment as a plain map. Wrapped so callers and tests share one read point (and so a test can prove a real exported GC_DOLT_PASSWORD is filtered by running AgentEnvAllowlist.build over this map).
terminateGroup({required ProcessGroupController controller, required int pgid, required int leaderPid, Duration grace = const Duration(seconds: 2), Duration pollPeriod = const Duration(milliseconds: 25)}) Future<GroupTerminateResult>
Sends SIGTERM to pgid, polls for the group to exit within grace, then escalates to SIGKILL — gc's processgroup.Terminate (processgroup_unix.go:53-68), as a free function over the injected ProcessGroupController seam.
transition(LifecycleState from, LifecycleCommand command) LifecycleState
Validates the transition and returns the new state, throwing IllegalLifecycleTransition when disallowed (gc's Transition).
transitionOrNull(LifecycleState from, LifecycleCommand command) LifecycleState?
Validates that applying command to a session in from is legal and returns the new state, or null when the transition is disallowed (the non-throwing analog of gc's Transition).
validateAncestorWorktreesNotStale(String path) StaleAncestorRejection?
Walks path's ancestor chain and returns a rejection reason when any ancestor has a regular-file .git worktree pointer whose gitdir: target is unusable — the VERBATIM port of gc's ValidateAncestorWorktreesNotStale (gascity/internal/workdir/workdir.go:303-359). Returns null when safe.

Typedefs

SetSidCall = int Function()
Injectable POSIX setsid() call used by establishStationProcessGroup.
StaleAncestorRejection = String?
Result of validateAncestorWorktreesNotStalenull when the spawn is safe, or a human-legible rejection reason when an ancestor has a stale worktree pointer.

Exceptions / Errors

IllegalLifecycleTransition
Raised when transition is asked for a (state, command) pair the table does not allow — the analog of gc's ErrIllegalTransition. Callers either guard with transitionOrNull or treat this as a programmer error.
OwnershipRefused
Raised when the StationBeadWriter chokepoint refuses a write because the target bead's substation is absent or not in the shared allow-set (fail-closed).
SessionAlreadyExists
Thrown by RuntimeProvider.start when a live session already holds the requested name — gc's ErrSessionExists (runtime.go:22-24).
SessionClosedRefused
Raised when StationBeadWriter refuses to mint or refresh a gate for a session bead that the state snapshot already shows as closed.