Flow State
These pages describe the current implementation. They may lag behind code changes.
The runtime’s source of truth for an active flow is a single JSON object — FlowRun — persisted at:
{stateRoot}/{projectNamespace}/{flowId}/flow.jsonThe state root is {workspaceRoot}/.a-society/state.
SessionStore.saveFlowRun writes it; SessionStore.loadFlowRun reads and validates it. All mutations go through SessionStore.updateFlowRun, which serializes concurrent writes using a per-flow async lock so no two coroutines corrupt each other’s state.
loadFlowRun also hydrates defaults for older nullable fields that still belong to the current state version, normalizes consent state, defaults missing feedback context to standard, and re-derives recordName / recordSummary from record metadata. recordName and recordSummary are stripped before writing flow.json.
Identity fields
Section titled “Identity fields”| Field | Description |
|---|---|
flowId | Unique timestamp-based ID (e.g. 20260527T143000123Z-a1b2c3). Set at flow creation, never changes. |
projectNamespace | The project subfolder name (e.g. my-project). Together with flowId forms the FlowRef used everywhere. |
workspaceRoot | Absolute path to the workspace root. Verified on load — a mismatch throws rather than silently proceeding. |
recordFolderPath | Absolute path to the flow's record folder under .a-society/state/{projectNamespace}/{flowId}/record/. Contains workflow.yaml, record.yaml, and flow artifacts. |
stateVersion | Schema version string. Current value is 11 via CURRENT_FLOW_STATE_VERSION. Load fails if this doesn't match — flows cannot be reopened after a breaking schema change. |
Execution state
Section titled “Execution state”status
Section titled “status”'running' | 'awaiting_improvement_choice' | 'awaiting_feedback_consent' | 'completed'Controls what the scheduler does next:
running— the scheduler picks up runnable nodesawaiting_improvement_choice— forward pass closed; waiting for the human to choose graph-based, parallel, or noneawaiting_feedback_consent— meta-analysis complete; waiting for human decision on feedbackcompleted— flow is done; no further orchestration
runningNodes
Section titled “runningNodes”string[] — node IDs claimed by an active role turn. A node enters this list when the scheduler claims it (claimRunnableWorkForParallelRun) and leaves when the turn completes or the node is suspended.
Only one node per role instance can be in runningNodes at a time — the scheduler enforces this to prevent the same role’s session from being used concurrently.
awaitingHumanNodes
Section titled “awaitingHumanNodes”Record<string, { role: string; reason: AwaitingHumanReason }> — nodes paused waiting for human input. The reason field is one of:
| Reason | Cause |
|---|---|
prompt-human | Agent emitted a prompt-human signal |
autonomous-abort | Turn aborted or errored without producing a handoff |
consent | A consent prompt is in-flight (transitional — rarely persisted) |
consent-denied | A tool call was denied and the turn was stopped |
role-configuration | Runtime is waiting for model, Skill, or MCP server selection for this role instance |
handoff-approval | A human-colab node emitted a forward handoff and the runtime is waiting for operator approval |
pendingHumanInputs
Section titled “pendingHumanInputs”Record<string, { text: string; receivedAt: string }> — human replies queued by the UI before the scheduler has consumed them. Keyed by node ID. When the scheduler picks up a node from this map it delivers the text as a user message and removes the entry.
This map is only for plain text replies. Nodes awaiting consent, role configuration, or handoff approval use their own protocol paths.
pendingHandoffApprovals
Section titled “pendingHandoffApprovals”Record<string, HandoffTarget[]> — forward handoffs staged by human-colab nodes and held until the operator approves or declines them. Keyed by the emitting node ID.
If approved, the runtime commits the already-emitted handoff without re-running the role. If declined, the staged handoff is discarded and the node is parked as prompt-human so the operator can give corrective guidance.
visitedNodeIds
Section titled “visitedNodeIds”string[] — node IDs whose first-entry workflow contract has already been delivered. The runtime injects node work, node-specific required_readings, and the human-collaboration directive only on first entry. If the node is re-entered (e.g. after a backward handoff), those first-entry contract fields are not re-injected.
Handoff tracking
Section titled “Handoff tracking”completedHandoffs
Section titled “completedHandoffs”string[] — edge keys ("${from}=>${to}") for forward handoffs that have been realized. An entry here means the source node successfully handed off to the target. A backward handoff removes the entry so the edge can be traversed again with corrected work.
receivingHandoff
Section titled “receivingHandoff”Record<string, string[]> — maps edge keys to artifact paths queued along that edge. Both forward and backward handoffs populate this. The orchestrator reads this when building the node-entry message so the receiving node gets the artifact in context.
historyHandoff
Section titled “historyHandoff”Record<string, string[]> — the full cumulative history of artifacts ever sent along each edge, deduplicated. Used solely to reject artifact reuse — an agent cannot pass the same artifact path twice across the flow’s lifetime.
awaitingHandoff
Section titled “awaitingHandoff”string[] — node IDs currently suspended after emitting a backward handoff. They are waiting for the predecessor to send a revised forward handoff back. A node in this list is not runnable and not in runningNodes.
Consent state
Section titled “Consent state”consentState
Section titled “consentState”{ mode: 'no-access' | 'partial-access' | 'full-access'; bash: { allowedCommands: Record<string, { command: string; grantedAt: string }>; }; mcp: { allowedTools: Record<string, { toolName: string; grantedAt: string }>; };}Persisted with the flow so consent grants survive server restarts. allowedCommands is keyed by exact command string. allowedTools is keyed by namespaced MCP tool name (for example, mcp__server__tool). On load, normalizeConsentState sanitizes the object — unrecognized modes fall back to no-access, and malformed grants are dropped.
Improvement phase
Section titled “Improvement phase”improvementPhase
Section titled “improvementPhase”Present only after the forward pass closes. See type ImprovementPhaseState:
| Field | Description |
|---|---|
status | awaiting_choice → running → awaiting_feedback_consent → completed (or skipped) |
mode | graph-based, parallel, or none — set when the human makes the improvement choice |
completedRoles | Role instance IDs whose meta-analysis session has finished |
runningRoles | Role instance IDs with a session in progress |
awaitingHumanRoles | Roles blocked on human input during improvement |
pendingHumanInputs | Queued human replies for improvement roles |
findingsProduced | Maps role instance ID → findings file path (repo-relative) |
improvementWorkflowPath | Repo-relative path to the runtime-generated improvement.yaml |
feedbackArtifactPath | Runtime-assigned path for the upstream feedback artifact |
feedbackConsent | pending, granted, or denied |
singleRole | true when the workflow has only one unique base role — affects UI presentation |
feedbackContext
Section titled “feedbackContext”FeedbackContext tells the optional feedback session what kind of flow produced the findings, which changes the focus instructions injected into that session:
{ kind: 'standard' | 'initialization' | 'update'; initializationMode?: 'takeover' | 'greenfield'; updateFromVersion?: string; updateToVersion?: string;}Standard flows focus on reusable framework gaps and workflow friction. Initialization flows focus on scaffolding, inferred project facts, and missing human input. Update flows focus on whether the changelog and framework delta were clear enough to apply to an existing a-docs/ layer.