#[non_exhaustive]pub struct State {Show 18 fields
pub stage: Stage,
pub phase: u32,
pub agent: AgentKind,
pub mode: Mode,
pub gate_pending: bool,
pub consecutive_failures: u32,
pub infra_failures: u32,
pub preflight_retries: u32,
pub started_at: String,
pub project_root: PathBuf,
pub worktree_path: Option<PathBuf>,
pub monitor_pid: Option<u32>,
pub session_id: Option<String>,
pub checkpoint_resumes: u32,
pub stop_until: Option<Stage>,
pub stopped: bool,
pub stop_reason: Option<String>,
pub yes_ship: bool,
}Expand description
Full workflow state persisted to .devflow/state.json.
§Construction
Marked #[non_exhaustive]: downstream crates must build this through
State::new and then assign the fields they care about, rather than by
struct literal. Deserialization is unaffected — the Deserialize derive
and every #[serde(default)] field keep working exactly as before, so
state files written by older binaries still load.
This exists because State accumulates a field roughly every phase that
adds a run-scoped concept (worktree_path, monitor_pid, stop_until,
yes_ship, and — in phase 28 — session_id and checkpoint_resumes).
Without non_exhaustive, each of those additions is a semver-breaking
change for any consumer that used a struct literal, which would force a
major bump for what is really an internal bookkeeping change. Paying that
cost once here makes every future field additive.
Fields (Non-exhaustive)§
This struct is marked as non-exhaustive
Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.stage: StageCurrent workflow stage.
phase: u32Phase number being worked on.
agent: AgentKindWhich coding agent was launched.
mode: ModeHow the pipeline is driven (auto vs. supervise).
gate_pending: boolWhether a gate has been written and is awaiting a human response.
consecutive_failures: u32Consecutive Validate failures — drives the Auto-mode forced gate after
crate::mode::MAX_CONSECUTIVE_FAILURES failures. Persisted across
devflow advance invocations so the counter survives monitor restarts.
infra_failures: u32Consecutive infrastructure-class faults (ResourceKilled,
AgentUnavailable) — distinct from Self::consecutive_failures
(D-08, 17-01). Gates at crate::mode::MAX_INFRA_FAILURES. Any
increment (wired in Plan 04) must use saturating_add so a
long-running stuck loop cannot overflow u32. A serde-absent value
(older persisted state) defaults to 0. Reset to 0 on every successful
stage transition, alongside consecutive_failures (CR-01, 17-06 gap
closure), so the ceiling bounds a stuck loop, not a phase’s lifetime.
preflight_retries: u32How many times a preflight gate has been resolved and retried for
this phase (18f). Bounded by crate::mode::MAX_PREFLIGHT_RETRIES.
Persisted rather than recursion-scoped because the documented wedge
spanned separate devflow invocations after a monitor death — an
in-process recursion-depth counter would reset to zero on every new
process and fail to bound the exact incident it exists to prevent.
Reset to 0 whenever preflight passes and whenever a human explicitly
approves (GateAction::Advance), both inside run_preflight. Unlike
Self::consecutive_failures and Self::infra_failures, this
counter is NOT touched by transition().
started_at: StringWhen the phase started (Unix seconds).
project_root: PathBufPath to the project root.
worktree_path: Option<PathBuf>Working directory for the agent when running in a git worktree.
None means the agent runs in project_root. State and capture files
always live under the main project_root; only the agent’s cwd changes.
monitor_pid: Option<u32>PID of the detached monitor process that owns the agent for the
current stage, recorded by launch_stage at spawn time. None means
no monitor has been spawned for this state yet, OR the state was
written by a binary predating this field — in both cases the
liveness probe reports Unknown, never Stuck.
session_id: Option<String>The Claude session id captured from the most recent captured stdout
envelope for this phase’s current stage (D-04, 28-02), read via
crate::agent_result::session_id_from_capture. None means EITHER
“no session has been captured for this state yet” OR “the state was
written by a binary predating this field” — both cases behave
identically (no relaunch target to address). Recorded so a checkpoint
auto-decide relaunch (plan 28-03) can --resume the exact session
that hit the checkpoint rather than spawning a fresh one, which would
lose the original session’s conversation context and permission mode.
checkpoint_resumes: u32How many times the current stage’s agent has been relaunched via a
checkpoint auto-decide resume (D-04, 28-03). Bounds a stuck
checkpoint loop against mode::MAX_CHECKPOINT_RESUMES (added in plan
28-03) the same way Self::infra_failures bounds an infra-fault
loop against mode::MAX_INFRA_FAILURES. Reset to 0 by every ordinary fresh stage
launch, so the ceiling bounds one stage’s resume budget, not a
phase’s lifetime (the same distinction MAX_INFRA_FAILURES’ doc
comment draws for infra_failures). Any increment must use
saturating_add so a stuck loop cannot overflow u32. A
serde-absent value (state written by a binary predating this field)
defaults to 0.
stop_until: Option<Stage>The stage devflow start --until <stage> requests as the last stage
to run before halting (20c). None means no stop point was
requested (the pipeline runs to Ship), OR the state was written by a
binary predating this field — both cases behave identically (no
interception in transition()).
stopped: boolSet by transition() when stop_until names the stage just
completed — a terminal-but-not-failed halt short of Ship (20c).
false for a normal in-flight or completed-to-Ship phase, and for
any state written by a binary predating this field.
stop_reason: Option<String>Human-readable reason recorded alongside stopped (20c). None
when stopped is false, or when the state predates this field.
yes_ship: boolPre-authorization for the Ship gate (D-04/D-05/D-06, 23-09),
set only from the --yes-ship CLI flag typed on devflow start.
Persisted rather than passed through the call stack: the Ship gate
fires inside a detached monitor’s advance process, minutes to
hours after the launching devflow start process has already
exited, so a CLI-scoped value would be gone by the time it matters —
only a value written to state.json at start time survives to be
read back by that later, separate process. false for any state
written by a binary predating this field.