Skip to content

state

state

Baton state model — the conductor's execution memory.

These are the data structures the baton uses to track what's happening during a performance: which sheets are in what state, which instruments are healthy, how many attempts have been made, what the cost budget looks like. This is the conductor's working memory — separate from the persistent CheckpointState, which is the sheet's record of outcomes.

The baton state persists to SQLite (in ~/.marianne/marianne-state.db) for restart recovery. All models support to_dict()/from_dict() serialization for this purpose.

Key types:

  • BatonSheetStatus — extended status enum (pending → ready → dispatched → running → completed/failed/skipped, plus waiting and fermata)
  • SheetExecutionState — per-sheet tracking (attempts, costs, status)
  • InstrumentState — per-instrument tracking (rate limits, circuit breaker)
  • BatonJobState — per-job tracking (sheets, cost, pause state)
  • AttemptContext — what the conductor tells the musician for each attempt
  • CircuitBreakerState — three-state circuit breaker (closed/open/half-open)

See: docs/plans/2026-03-26-baton-design.md — Persistent State section

Classes

AttemptMode

Bases: str, Enum

The mode a sheet attempt runs in.

  • NORMAL — standard execution (first try or retry)
  • COMPLETION — partial validation passed, trying to complete
  • HEALING — self-healing after retry exhaustion

CircuitBreakerState

Bases: str, Enum

Three-state circuit breaker for per-instrument health tracking.

  • CLOSED — healthy, accepting requests
  • OPEN — unhealthy, rejecting requests, waiting for recovery timer
  • HALF_OPEN — probing, allowing one request to test recovery

AttemptContext dataclass

AttemptContext(attempt_number, mode, completion_prompt_suffix=None, healing_context=None, previous_results=None, learned_patterns=None, total_sheets=1, total_movements=1, previous_outputs=dict(), previous_files=dict())

Context provided by the conductor to the musician for a single attempt.

Each dispatch carries this context so the musician knows: - Which attempt this is (1 = first try, 2+ = retry) - What mode to operate in (normal/completion/healing) - Any extra context for non-normal modes - Learned patterns from the learning store (instrument-scoped) - Previous attempt results (for failure history injection)

Attributes
attempt_number instance-attribute
attempt_number

1-based attempt number. First try = 1, first retry = 2, etc.

mode instance-attribute
mode

The execution mode for this attempt.

completion_prompt_suffix class-attribute instance-attribute
completion_prompt_suffix = None

For completion mode: appended to the prompt to fix partial failures.

healing_context class-attribute instance-attribute
healing_context = None

For healing mode: diagnostic context from self-healing analysis.

previous_results class-attribute instance-attribute
previous_results = None

Previous attempt results for failure history injection into prompts.

learned_patterns class-attribute instance-attribute
learned_patterns = None

Patterns from the learning store, scoped to this instrument.

total_sheets class-attribute instance-attribute
total_sheets = 1

Total concrete sheet count in the job (for preamble and template vars).

total_movements class-attribute instance-attribute
total_movements = 1

Total movement count in the job (for template vars).

previous_outputs class-attribute instance-attribute
previous_outputs = field(default_factory=dict)

Stdout outputs from completed sheets. Keys are sheet numbers (1-indexed). Populated by the adapter from completed sheet attempt results.

previous_files class-attribute instance-attribute
previous_files = field(default_factory=dict)

File contents captured via capture_files patterns. Keys are file paths. Populated by the adapter from workspace files matching CrossSheetConfig patterns.

InstrumentState dataclass

InstrumentState(name, max_concurrent, running_count=0, rate_limited=False, rate_limit_expires_at=None, circuit_breaker=CLOSED, consecutive_failures=0, circuit_breaker_threshold=5, circuit_breaker_recovery_at=None)

Per-instrument state tracking for rate limits, circuit breakers, and concurrency.

The baton tracks each instrument's health independently. Rate limits on claude-code don't affect gemini-cli. Circuit breaker thresholds are per-instrument. Concurrency limits come from the InstrumentProfile.

Attributes
name instance-attribute
name

Instrument name (matches InstrumentProfile.name).

max_concurrent instance-attribute
max_concurrent

Maximum concurrent sheets on this instrument (from InstrumentProfile).

running_count class-attribute instance-attribute
running_count = 0

Number of currently running sheets on this instrument.

rate_limited class-attribute instance-attribute
rate_limited = False

Whether this instrument is currently rate-limited.

rate_limit_expires_at class-attribute instance-attribute
rate_limit_expires_at = None

Monotonic time when the rate limit is expected to clear.

circuit_breaker class-attribute instance-attribute
circuit_breaker = CLOSED

Current circuit breaker state.

consecutive_failures class-attribute instance-attribute
consecutive_failures = 0

Consecutive failures across all jobs for this instrument.

circuit_breaker_threshold class-attribute instance-attribute
circuit_breaker_threshold = 5

Number of consecutive failures to trip the circuit breaker.

circuit_breaker_recovery_at class-attribute instance-attribute
circuit_breaker_recovery_at = None

Monotonic time for circuit breaker recovery check.

is_available property
is_available

Whether this instrument can accept new sheets.

Available when not rate-limited and circuit breaker is not open. Half-open allows one probe request through.

at_capacity property
at_capacity

Whether all concurrent slots are in use.

Functions
record_success
record_success()

Record a successful execution on this instrument.

Resets consecutive failures. If circuit breaker is half-open, closes it (the probe succeeded).

Source code in src/marianne/daemon/baton/state.py
def record_success(self) -> None:
    """Record a successful execution on this instrument.

    Resets consecutive failures. If circuit breaker is half-open,
    closes it (the probe succeeded).
    """
    self.consecutive_failures = 0
    if self.circuit_breaker == CircuitBreakerState.HALF_OPEN:
        self.circuit_breaker = CircuitBreakerState.CLOSED
record_failure
record_failure()

Record a failed execution on this instrument.

Increments consecutive failures. If threshold reached, opens the circuit breaker. If already half-open, reopens it.

Source code in src/marianne/daemon/baton/state.py
def record_failure(self) -> None:
    """Record a failed execution on this instrument.

    Increments consecutive failures. If threshold reached, opens
    the circuit breaker. If already half-open, reopens it.
    """
    self.consecutive_failures += 1

    if self.circuit_breaker == CircuitBreakerState.HALF_OPEN:
        # Probe failed — back to open
        self.circuit_breaker = CircuitBreakerState.OPEN
    elif self.consecutive_failures >= self.circuit_breaker_threshold:
        self.circuit_breaker = CircuitBreakerState.OPEN
to_dict
to_dict()

Serialize to a dict for SQLite persistence.

Source code in src/marianne/daemon/baton/state.py
def to_dict(self) -> dict[str, Any]:
    """Serialize to a dict for SQLite persistence."""
    return {
        "name": self.name,
        "max_concurrent": self.max_concurrent,
        "rate_limited": self.rate_limited,
        "rate_limit_expires_at": self.rate_limit_expires_at,
        "circuit_breaker": self.circuit_breaker.value,
        "consecutive_failures": self.consecutive_failures,
        "circuit_breaker_threshold": self.circuit_breaker_threshold,
        "circuit_breaker_recovery_at": self.circuit_breaker_recovery_at,
    }
from_dict classmethod
from_dict(data)

Restore from a serialized dict.

Source code in src/marianne/daemon/baton/state.py
@classmethod
def from_dict(cls, data: dict[str, Any]) -> InstrumentState:
    """Restore from a serialized dict."""
    state = cls(
        name=data["name"],
        max_concurrent=data.get("max_concurrent", 4),
    )
    state.rate_limited = data.get("rate_limited", False)
    state.rate_limit_expires_at = data.get("rate_limit_expires_at")
    state.circuit_breaker = CircuitBreakerState(
        data.get("circuit_breaker", "closed")
    )
    state.consecutive_failures = data.get("consecutive_failures", 0)
    state.circuit_breaker_threshold = data.get("circuit_breaker_threshold", 5)
    state.circuit_breaker_recovery_at = data.get("circuit_breaker_recovery_at")
    return state

BatonJobState dataclass

BatonJobState(job_id, total_sheets, paused=False, pacing_active=False, sheets=dict())

The baton's per-job tracking during a performance.

Contains all sheet states for a job, plus job-level flags (paused, pacing, cost tracking).

Attributes
job_id instance-attribute
job_id

The unique job identifier.

total_sheets instance-attribute
total_sheets

Total number of sheets in this job.

paused class-attribute instance-attribute
paused = False

Whether dispatching is paused for this job.

pacing_active class-attribute instance-attribute
pacing_active = False

Whether inter-sheet pacing delay is currently active.

sheets class-attribute instance-attribute
sheets = field(default_factory=dict)

Map of sheet_num → SheetExecutionState.

total_cost_usd property
total_cost_usd

Total cost across all sheets in this job.

completed_count property
completed_count

Number of sheets in COMPLETED status.

terminal_count property
terminal_count

Number of sheets in a terminal status (completed, failed, skipped).

is_complete property
is_complete

Whether all registered sheets have reached a terminal status.

running_sheets property
running_sheets

Sheets currently in RUNNING status.

has_any_failed property
has_any_failed

Whether any sheet has reached FAILED status.

Functions
register_sheet
register_sheet(sheet)

Register a sheet's execution state with this job.

Source code in src/marianne/daemon/baton/state.py
def register_sheet(self, sheet: SheetExecutionState) -> None:
    """Register a sheet's execution state with this job."""
    self.sheets[sheet.sheet_num] = sheet
get_sheet
get_sheet(sheet_num)

Get a sheet's execution state, or None if not registered.

Source code in src/marianne/daemon/baton/state.py
def get_sheet(self, sheet_num: int) -> SheetExecutionState | None:
    """Get a sheet's execution state, or None if not registered."""
    return self.sheets.get(sheet_num)