State Management
Arial uses a file-based state system to track workstream progress across runs. This document covers the state schema, storage, and operations.
Overview
State is stored in .arial/state.json and managed through pure functions in lib/state.ts. The design prioritizes:
- Atomicity - No partial writes
- Simplicity - Plain JSON, easy to inspect
- Recoverability - State survives crashes
Schema
ArialState
interface ArialState {
version: 2
specsDir: string
baseBranch: string
status: RunStatus
createdAt: string // ISO 8601 timestamp
updatedAt: string // ISO 8601 timestamp
workstreams: Workstream[]
}
type RunStatus = 'planning' | 'running' | 'completed' | 'failed'Workstream
interface Workstream {
id: string // Derived from spec filename
specFile: string // Relative path to spec
title: string // Human-readable title
description: string // What it accomplishes
status: WorkstreamStatus
branch: string // Git branch name
worktreePath: string // Path to Git worktree
pid?: number // Process ID if running
startedAt?: string // ISO timestamp
completedAt?: string // ISO timestamp
retryCount: number
lastError?: string
summary?: string // Completion summary
adapter?: string // Override default adapter
requiredCapabilities?: AdapterCapability[]
}
type WorkstreamStatus = 'pending' | 'running' | 'done' | 'failed'Storage Layout
.arial/
├── state.json # Main state file
├── worktrees/ # Git worktrees per workstream
│ ├── auth-system/
│ ├── payment-flow/
│ └── ...
└── logs/ # Per-workstream logs
├── auth-system.log
├── payment-flow.log
└── ...State Operations
Initialization
function initArial(repoRoot: string): Result<void>Creates the .arial/ directory structure:
.arial/
├── worktrees/
└── logs/Creating State
function createState(
specsDir: string,
baseBranch: string
): ArialStateCreates a new run state with no workstreams.
Loading State
function loadState(repoRoot: string): Result<ArialState>Loads and validates state from .arial/state.json.
Saving State
function saveState(
repoRoot: string,
state: ArialState
): Result<void>Atomic write using temp file + rename:
// Pseudocode
const tempPath = `${statePath}.tmp`
writeFileSync(tempPath, JSON.stringify(state))
renameSync(tempPath, statePath) // Atomic on POSIXThis prevents corruption from interrupted writes.
Query Functions
// Check if Arial is initialized
function isInitialized(repoRoot: string): boolean
// Check if there's an active run
function hasActiveRun(repoRoot: string): boolean
// Get workstreams by status
function getWorkstreamsByStatus(
state: ArialState,
status: WorkstreamStatus
): Workstream[]Mutation Functions
// Add a new workstream
function addWorkstream(
state: ArialState,
repoRoot: string,
input: WorkstreamInput
): ArialState
// Update workstream status
function updateWorkstreamStatus(
state: ArialState,
id: string,
status: WorkstreamStatus,
extra?: Partial<Workstream>
): ArialState
// Derive run status from workstreams
function updateRunStatus(state: ArialState): ArialStateAll mutation functions return a new state object (immutable).
Status Transitions
Run Status
planning ──> running ──> completed
└──> failedplanning- Initial state duringarial planrunning- Set whenarial runstartscompleted- All workstreams donefailed- Any workstream failed (without recovery)
Workstream Status
pending ──> running ──> done
└──> failedpending- Created but not startedrunning- Executor is activedone- Completed and mergedfailed- Error occurred
State Derivation
Run status is derived from workstream statuses:
function updateRunStatus(state: ArialState): ArialState {
const statuses = state.workstreams.map(w => w.status)
if (statuses.every(s => s === 'done')) {
return { ...state, status: 'completed' }
}
if (statuses.some(s => s === 'failed')) {
return { ...state, status: 'failed' }
}
if (statuses.some(s => s === 'running')) {
return { ...state, status: 'running' }
}
return state
}Recovery
If Arial crashes during execution:
- State file preserves last known status
arial statusshows current statearial runcan resume pending workstreamsarial cleanupremoves orphaned resources
Version Migration
State includes a version field for schema changes:
interface ArialState {
version: 2 // Current version
// ...
}Future versions can include migration logic:
function loadState(repoRoot: string): Result<ArialState> {
const raw = readStateFile(repoRoot)
if (raw.version === 1) {
return migrateV1toV2(raw)
}
return { ok: true, value: raw }
}Example State
{
"version": 2,
"specsDir": "./specs",
"baseBranch": "main",
"status": "running",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:35:00Z",
"workstreams": [
{
"id": "auth-system",
"specFile": "specs/auth-system.md",
"title": "Implement Authentication",
"description": "Add user authentication with JWT",
"status": "done",
"branch": "arial/auth-system",
"worktreePath": ".arial/worktrees/auth-system",
"startedAt": "2024-01-15T10:30:05Z",
"completedAt": "2024-01-15T10:32:00Z",
"retryCount": 0,
"summary": "Added JWT auth with login/logout endpoints"
},
{
"id": "payment-flow",
"specFile": "specs/payment-flow.md",
"title": "Payment Integration",
"description": "Integrate Stripe payments",
"status": "running",
"branch": "arial/payment-flow",
"worktreePath": ".arial/worktrees/payment-flow",
"startedAt": "2024-01-15T10:30:05Z",
"retryCount": 0
}
]
}