Reasonix Extension Runtime Set Management and Snapshot Mechanisms Explained
Reasonix treats every UI side-car extension as a runtime set guarded by generation fences, while using timestamp-based snapshot validation to ensure backend runtime state never overwrites fresh user prompts.
Reasonix, the AI coding assistant interface in the esengine/DeepSeek-Reasonix repository, handles UI extensions (plugins) through a sophisticated state management layer. Understanding the runtime set management and snapshot mechanisms for extensions in Reasonix is essential for developers building side-car plugins or debugging state synchronization issues between the TypeScript frontend and Go backend.
Runtime Set Management Architecture
Reasonix models every extension surface as a runtime set living inside the per-tab controller state. The system uses deterministic identity keys and optimistic generation counters to maintain consistency across wire events.
Surface Keys and Identity
Every extension surface is identified by a deterministic surface key constructed as <pluginId>:<surfaceId>. The extensionSurfaceKey utility in desktop/frontend/src/lib/useController.ts generates these identifiers:
export function extensionSurfaceKey(surface: Pick<WireExtensionSurface, "pluginId" | "surfaceId">): string {
return `${surface.pluginId}:${surface.surfaceId}`;
}
This string key serves as the primary index for all extension-related collections in the controller state.
The Three Extension Collections
The controller maintains three distinct collections to track extension state, as implemented in useController.ts:
extensionStatuses– Stores the latest status metadata (label,detail,severity,progress) for every active surface.extensionForm– Holds the currently active form surface, enforcing a singleton constraint where only one form can be pending per tab.extensionNotifications– Queues transient toast-style notifications until the UI drains them.
These collections are updated atomically through the reducer to prevent partial state corruption.
Generation Fencing
All three collections are protected by a generation fence that prevents out-of-order updates from stale runtime epochs. Every surface event carries an optional generation number, and the controller only accepts events with a generation newer than or equal to the last stored value.
The acceptance logic lives in acceptsExtensionGeneration at lines 24-26 of useController.ts:
export function acceptsExtensionGeneration(stored: number | undefined, incoming: number | undefined): boolean {
return incoming === undefined || stored === undefined || incoming >= stored;
}
This function returns true for undefined values (backward compatibility) or when the incoming generation advances the state.
Event Processing Pipeline
When wire events arrive via the extension_surface channel, the controller routes them through applyExtensionSurfaceEvent (lines 70-92 of useController.ts). The pipeline uses withAcceptedExtensionGeneration to gate updates:
- Status events trigger
applyExtensionStatus, writing intoextensionStatuses. - Card events trigger
applyExtensionCard, adding or replacing anExtensionIteminstate.items. - Form events trigger
applyExtensionForm, storing the form instate.extensionForm. - Notification events trigger
applyExtensionNotification, pushing ontostate.extensionNotifications.
Only events passing the generation fence modify state; stale events are silently discarded.
Runtime Snapshot Mechanisms
Reasonix takes runtime snapshots from the Go backend via the runtime channel to synchronize execution state. These snapshots require careful validation to prevent them from overwriting newer UI state.
RuntimeMetaSnapshot Structure
Snapshots are represented by the RuntimeMetaSnapshot type defined in useController.ts:
type RuntimeMetaSnapshot = {
running: boolean;
pendingPrompt?: boolean;
backgroundJobs?: number;
cancelRequested?: boolean;
cancellable?: boolean;
};
This structure captures whether the backend is executing code, waiting for user approval, or processing background jobs.
Staleness Detection Logic
To determine if a snapshot predates user interaction, Reasonix compares the snapshot timestamp (snapshotAt) against the prompt arrival time (promptArrivedAt). The runtimeSnapshotPredatesPrompt function encapsulates this check:
export function runtimeSnapshotPredatesPrompt(
state: { approval?: unknown; ask?: unknown; promptArrivedAt?: number } | undefined,
snapshotAt: number | undefined,
): boolean {
if (!state || (!state.approval && !state.ask)) return false;
if (snapshotAt === undefined || state.promptArrivedAt === undefined) return false;
return snapshotAt <= state.promptArrivedAt;
}
When this returns true, the controller discards the snapshot and schedules a refetch after a debounce interval defined by constants like STALE_TURN_RECONCILE_MS and STALE_PROMPT_RECONCILE_MS (lines 42-50 of useController.ts).
Snapshot-to-Prompt Validation
The validation mechanism protects against race conditions where a stale snapshot arrives after a user has already submitted a new prompt. The controller checks runtimeSnapshotPredatesRetry for similar logic against retry timestamps. If either check fails, the UI retains its current state rather than reverting to the snapshot's view.
Foreground State Translation
The foregroundRunningFromRuntimeMeta function (lines 90-93 of useController.ts) translates backend snapshots into UI-ready state:
export function foregroundRunningFromRuntimeMeta(meta: RuntimeMetaSnapshot): boolean {
if (typeof meta.cancellable === "boolean") return meta.cancellable;
if ((meta.backgroundJobs ?? 0) > 0 && !meta.pendingPrompt) return false;
return Boolean(meta.running);
}
This handles edge cases such as background jobs without pending prompts, ensuring the loading indicator reflects actual foreground execution status.
Practical Implementation Examples
Publishing Extension Status from a Side-Car
Extension plugins publish updates via the wire protocol, bumping the generation to ensure ordering:
await fetch("/api/extension_surface", {
method: "POST",
body: JSON.stringify({
kind: "status",
pluginId: "my-plugin",
surfaceId: "status-panel",
generation: 2,
status: { label: "Ready", severity: "info" }
})
});
The frontend receives this event, computes the surface key as "my-plugin:status-panel", validates the generation via acceptsExtensionGeneration, and updates state.extensionStatuses accordingly.
Detecting Stale Snapshots
Components can guard against snapshot corruption using the predication helper:
if (runtimeSnapshotPredatesPrompt(currentState, snapshotAt)) {
scheduleRefetch();
return; // Ignore stale snapshot
}
This pattern prevents the UI from clearing a pending approval dialog when an older runtime snapshot arrives over the wire.
Summary
- Surface keys use the format
<pluginId>:<surfaceId>to uniquely identify extension runtime sets in the controller state. - Generation fences in
acceptsExtensionGenerationensure only newer or equal generation events updateextensionStatuses,extensionForm, orextensionNotifications. - Runtime snapshots arrive as
RuntimeMetaSnapshotobjects from the Go backend and must pass staleness checks againstpromptArrivedAttimestamps. - Staleness detection via
runtimeSnapshotPredatesPromptprevents outdated backend state from overwriting fresh user interactions. - All logic is centralized in
desktop/frontend/src/lib/useController.ts, with supporting types indesktop/frontend/src/lib/types.ts.
Frequently Asked Questions
How does Reasonix prevent race conditions between extension updates and runtime snapshots?
Reasonix uses orthogonal validation strategies: extension updates are gated by integer generations (monotonic counters per surface), while runtime snapshots are validated against timestamps comparing snapshotAt with promptArrivedAt. The withAcceptedExtensionGeneration guard (lines 99-103 of useController.ts) applies to both event types, ensuring only fresh state survives regardless of network ordering.
What happens when an extension sends an event with an older generation number?
The acceptsExtensionGeneration function returns false when the incoming generation is less than the stored value, causing the reducer to discard the event silently. This prevents out-of-order wire events from reverting the UI to previous states, which is critical when users rapidly interact with extension forms or status panels.
Can multiple extension forms be active simultaneously in Reasonix?
No. The controller enforces a singleton constraint on forms through the extensionForm collection. When applyExtensionForm processes a new form event, it replaces any existing form in the state. This design prevents modal conflicts where multiple plugins might request user input simultaneously.
Where does the backend runtime snapshot originate, and how often is it sent?
Snapshots originate from the Go backend via the runtime channel (connected in desktop/frontend/src/lib/bridge.ts) and represent the current execution meta-state. The backend emits these during state transitions (start/stop of execution, prompt requests), and the frontend debounces rapid updates using constants like STALE_TURN_RECONCILE_MS to avoid UI flicker.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →