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 ondecisionsfor 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: thegitbinary); StationGitService composes it. - GitRunner
-
The git-command SEAM — the single point where StationGitService shells out to
git. A reference type (carries theRunnerrole name; predictable-flutter). - GitRunResult
-
The result of one
gitinvocation — stdout (combined with stderr, gc'sCombinedOutput), 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'sgit.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'ssession.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
Controllerrole 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
Openerrole 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=quarantinedwithquarantine_cycle/quarantined_until(gc'sQuarantinePatch); 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
DispatchInteractorattaches 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_requestedis set so the dispatcher re-spawns (gc'sRequestFreshRestart,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
originset and the default branch probed fromorigin/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
statetransitions 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-199Capabilities()), 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 aclaudeper ready bead in its worktree. - RuntimeEvent
-
An observation from a RuntimeProvider's session lifecycle, emitted on the
eventsstream (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 (theProviderrole 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) orclosed(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
gitbinary + the PR-open boundary — and is constructed with its dependencies). Futures for acts (register/provision/land/reap); point-in-timelistis 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'sBdRunner: 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 (theSpawnerrole name). - SystemGitRunner
-
The real seam: execs
gitwith the blacklist stripped and combines stdout+stderr. The ONLY place this file touchesdart:ioprocess spawning. - SystemProcessGroupController
-
The real seam: resolves pgid via
ps, probes liveness with a harmless signal, and signals groups viaProcess.killPid(-pgid, …). - SystemSubprocessSpawner
-
Spawns real agent subprocesses via
dart:iowith 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
boolthat returnstrueon 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
TransitionCommandvocabulary,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
gitexec so a subprocess git command uses the intended cwd, not a parent repo'sGIT_DIR/GIT_WORK_TREE. Verbatim from gc'sgitEnvBlacklist(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'sAllowedCommands), 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
gitexec: every entry ofparentEnvEXCEPT the gitEnvBlacklist keys (gc'sgit.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
stationPidthe 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
pathis strictly contained withindir— the scope gate that guarantees the reaper can only ever delete inside the_grid's own worktrees root. gc'sisStrictlyUnderDir(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 --porcelainoutput. Each block is separated by a blank line:worktree <path>,HEAD <sha>,branch refs/heads/<name>. gc'sparseWorktreeList(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_PASSWORDis 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 withingrace, then escalates to SIGKILL — gc'sprocessgroup.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
commandto a session infromis legal and returns the new state, or null when the transition is disallowed (the non-throwing analog of gc'sTransition). -
validateAncestorWorktreesNotStale(
String path) → StaleAncestorRejection? -
Walks
path's ancestor chain and returns a rejection reason when any ancestor has a regular-file.gitworktree pointer whosegitdir:target is unusable — the VERBATIM port of gc'sValidateAncestorWorktreesNotStale(gascity/internal/workdir/workdir.go:303-359). Returnsnullwhen safe.
Typedefs
- SetSidCall = int Function()
-
Injectable POSIX
setsid()call used by establishStationProcessGroup. - StaleAncestorRejection = String?
-
Result of validateAncestorWorktreesNotStale —
nullwhen 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.