> ## Documentation Index
> Fetch the complete documentation index at: https://uom-demo.vercel.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Runtime

# UOM Assistant Frontend: State Synchronization & LangGraph Runtime

This document explains the runtime state machine integration, event stream adapters, checkpoint backtracking logic, and manual checkpoint recovery routines utilized in the **UOM Assistant** frontend dashboard.

***

## 1. The Runtime Wrapper & Local State Machine

The interface communicates with the LangGraph backend via the `useLangGraphRuntime` hook, which is wrapped in a provider component to distribute state across the workspace:

* **File Path**: [`frontend/uom-translator-ui/components/assistant-ui/runtime/assistant-runtime-provider.tsx`](../../frontend/uom-translator-ui/components/assistant-ui/runtime/assistant-runtime-provider.tsx)
* **Component**: `AssistantRuntimeProviderWrapper`

The wrapper maintains the local reactive state machine:

* `graphState`: A `Partial<BackendState>` containing the active translation data, validation errors, and equivalence diff logs.
* `error` and `runError`: Diagnostic models logging compilation failures and stream aborted triggers.
* `activeNode`: Identifies the current executing python node in the backend (e.g., input extraction, schema inspection, compilation validation, equivalence checks, and evaluations).

***

## 2. Configurable Context Injection (`stream`)

When a user submits a query via the Composer input, the custom `stream` callback triggers. This method loads credentials and database configuration settings from `localStorage` under the `"uom_translator_config"` key and injects them as a structured payload into the run execution context. For more information on LanGraph SDK client methods/options (streamMode, streamSubgraphs, context), see the [LangGraph SDK documentation](https://reference.langchain.com/javascript/langchain-langgraph-sdk).

```typescript theme={null}
const savedConfig = typeof window !== "undefined" ? localStorage.getItem("uom_translator_config") : null;
const configurable: UomConfig = savedConfig ? JSON.parse(savedConfig) : {};

const payload = {
    input: messages.length ? { messages } : null,
    // "updates" is required for human-in-the-loop: @assistant-ui/react-langgraph only
    // captures LangGraph's native `interrupt()` payload from `updates` events (it reads
    // `chunk.data.__interrupt__`). Without it the suspension is never surfaced live.
    streamMode: ["messages-tuple", "updates", "values", "custom"],
    streamSubgraphs: true,
    ...(config.abortSignal != null && { signal: config.abortSignal }),
    onDisconnect: "cancel",
    multitaskStrategy: "reject",
    ...(config.command != null && { command: config.command }),
    ...(config.checkpointId != null && {
        checkpoint: { checkpoint_id: config.checkpointId },
    }),
    context: {
        ollama_host: configurable.ollamaHost || undefined,
        openai_api_url: configurable.openaiApiUrl || undefined,
        openai_api_key: configurable.openaiApiKey || undefined,
        model: configurable.model || undefined,
        db_toolbox_uri: configurable.dbToolboxUri || undefined,
        mongodb_mcp_uri: configurable.mongodbMcpUri || undefined,
        ms_sql_connection_string: configurable.mssqlConnectionString || undefined,
        mongodb_uri: configurable.mongodbUri || undefined,
        neo4j_uri: configurable.neo4jUri || undefined,
        neo4j_password: configurable.neo4jPassword || undefined,
        daytona_api_url: configurable.daytonaApiUrl || undefined,
        daytona_api_key: configurable.daytonaApiKey || undefined,
        daytona_target: configurable.daytonaTarget || undefined,
        sandbox_execution_timeout: configurable.daytonaTimeout || undefined,
    },
};

const eventStream = await client.runs.stream(externalId, ASSISTANT_ID, payload);
```

Some payload parameters, come directly from `assistant-ui` runtime, which are passed through the `config` argument of the `stream` method:

* `abortSignal`: An `AbortController` signal that can be triggered to cancel the stream.
* `command`: An optional command object (e.g. `{ resume: ... }`), used by the `InterruptHandler` component to resume a suspended graph. Its `resume` value is forwarded verbatim to the backend `interrupt()` call (see [§6](#6-interrupt--user-decision-flow-interrupthandler)).
* `checkpointId`: An optional string identifier for resuming from a specific checkpoint in the thread history.

### 2.1 LLM Backend Switcher

This injection logic allows users to swap models and providers on-the-fly:

* **Local Ollama Deployment**: Directs requests to a local daemon (e.g., `http://localhost:11434`) running open-weights models like `qwen2.5-coder`.
* **Remote Metacentrum e-INFRA CZ**: Routes calls through Metacentrum's OpenAI-compatible APIs, providing access to larger models like `einfra/kimi-k2.6` or `einfra/deepseek-v4-pro-thinking`.

***

## 3. Sub-graphs, Event Handlers, & Custom Telemetry

The runtime adapter processes LangGraph events to sync backend execution details with the UI.

### 3.1 Sub-graph State Merging

To track execution parameters inside nested validation or equivalence test subgraphs, the runtime processes sub-graph values and merges them into the main state object. See [LangGraph Subgraph Docs](https://docs.langchain.com/oss/python/langgraph/use-subgraphs) how multi-agent orchestration works.

```typescript theme={null}
onSubgraphValues: (namespace: string, values: any) => {
    if (values) {
        setGraphState((prev) => ({ ...prev, ...values }));
    }
}
```

### 3.2 Custom Event Logs (`onCustomEvent`)

The Python orchestrator streams container log details and validation updates as custom events, which are processed by the runtime for debugging:

```typescript theme={null}
onCustomEvent: (type: string, data: any) => {
    console.log(`[UOM] Custom event [${type}]:`, data);
}
```

### 3.3 Noise Reduction Error Filtering

To prevent system warnings or connection resets from flooding the user console, the error handler implements a filtering system that ignores known harmless messages:

```typescript theme={null}
const EXCLUDED_ERRORS = ["signal is aborted without reason"];

const handleError = (msg: string, error?: any) => {
    if (EXCLUDED_ERRORS.includes(error?.message)) {
        console.warn("Excluded error occurred:", error);
        return;
    }
    console.error(`[UOM Error] ${msg}:`, error);
};
```

***

## 5. Thread List Synchronization (`RemoteThreadListAdapter`)

*Note: The adapter definitions in this section are simplified conceptual representations of the actual implementation.*

The frontend maps UI actions (like creating or deleting threads) to the backend database using a thread list adapter:

### 5.1 Adapter Methods

* **`list()`**: Queries the thread catalog using `client.threads.search` (returning up to 50 threads sorted by creation date descending):
  ```typescript theme={null}
  list: async () => {
      return await client.threads.search({ limit: 50 });
  }
  ```
* **`rename(remoteId, newTitle)`**: Updates metadata tags stored on the server:
  ```typescript theme={null}
  rename: async (remoteId, newTitle) => {
      await client.threads.update(remoteId, { metadata: { title: newTitle } });
  }
  ```
* **`delete(remoteId)`**: Removes the thread from server persistence:
  ```typescript theme={null}
  delete: async (remoteId) => {
      await client.threads.delete(remoteId);
  }
  ```
* **`initialize()`**: Provisions a new thread identifier on the server, initialized with a timestamped title:
  ```typescript theme={null}
  initialize: async () => {
      const title = `Migration ${new Date().toISOString()}`;
      return await client.threads.create({ metadata: { title } });
  }
  ```
* **`fetch(threadId)`**: Retrieves the current state and messages for the selected thread:
  ```typescript theme={null}
  fetch: async (threadId) => {
      return await client.threads.get(threadId);
  }
  ```

***

## 6. Interrupt & User Decision Flow (`InterruptHandler`)

* **File Path**: [`frontend/uom-translator-ui/components/assistant-ui/interrupt-handler.tsx`](../../frontend/uom-translator-ui/components/assistant-ui/interrupt-handler.tsx)
* **Component**: `InterruptHandler`

When the orchestrator reaches a manual validation gate, the backend `human_intervention_node` suspends execution using LangGraph's **native `interrupt()` API** (not a tool call). The interrupt payload carries an `instruction` plus the current translation `state`:

```python theme={null}
# services/orchestrator/src/react_agent/graph.py — human_intervention_node
response = interrupt({
    "instruction": "Review the current state, generated translation and validation results...",
    "state": {
        "translated_query_code": state.translated_query_code,
        "translated_schema_code": state.translated_schema_code,
        "explanation_message": state.explanation_message,
        "query_equivalence_deep_diffs": state.query_equivalence_deep_diffs,
    },
})
output = HumanInterventionResponse.model_validate(response)  # { decision, feedback }
```

The `InterruptHandler` reads this payload, renders the explanation and the query-equivalence deep diffs, and presents the Accept / Reject decision controls.

### 6.1 State Transitions during Suspends

```mermaid theme={null}
stateDiagram-v2
    [*] --> Suspended: Graph reaches human_intervention_node (interrupt())
    Suspended --> RenderCard: Read interrupt.value { instruction, state }
    RenderCard --> AcceptSelection: User clicks "Accept & Save"
    RenderCard --> RejectSelection: User clicks "Reject & Correct"

    AcceptSelection --> ResumeGraph: resume = { decision: "accept", feedback: "" }
    RejectSelection --> InputFeedback: Show textarea for correction hints
    InputFeedback --> ResumeGraph: resume = { decision: "reject", feedback }

    ResumeGraph --> [*]: Command(resume) returned from interrupt()
```

The component uses hooks from `@assistant-ui/react-langgraph` to resume the graph:

* `useLangGraphInterruptState()`: Accesses the active suspend payload via `interrupt.value`. The card renders whenever a payload is present.

  > **Do not gate on `interrupt.resumable`.** That field is **deprecated** in `@langchain/langgraph-sdk` (≥ 1.x) and omitted by recent servers, so guarding on it hides the card entirely (both live and on reload).

* `useLangGraphSendCommand()`: Resumes execution by posting the decision object back to the suspended node. The value becomes the return value of the backend `interrupt()` call and is validated against the `HumanInterventionResponse` Pydantic model, so **both fields are required** and it must be an **object** — not a bare string or a JSON-encoded string:
  ```typescript theme={null}
  const resume =
      decision === "accept"
          ? { decision: "accept", feedback: "" }
          : { decision: "reject", feedback };
  // LangGraphCommand.resume is typed as `string` upstream, but the value is forwarded
  // verbatim to the server; cast past the narrow type.
  await sendCommand({ resume: resume as unknown as string });
  ```

### 6.2 Persistence across reloads

Because the backend uses a native `interrupt()`, the suspension is part of the thread's server-side checkpoint. The runtime's `load()` callback returns it so the card reappears when an interrupted conversation is reopened from the thread list:

```typescript theme={null}
load: async (externalId) => {
    const state = await client.threads.getState(externalId, undefined, { subgraphs: true });
    return {
        messages: state.values?.messages || [],
        // Collect pending interrupts across all tasks; the runtime feeds interrupts[0]
        // into useLangGraphInterruptState().
        interrupts: (state.tasks ?? []).flatMap((task) => task.interrupts ?? []),
    };
}
```

> **Why native `interrupt()` rather than the tool-message approval pattern?** The assistant-ui [Approval UI tutorial](https://www.assistant-ui.com/docs/runtimes/langgraph/tutorial/part-3) resumes by appending a `ToolMessage` (via `addResult`) to a *pending tool call*. That mechanism is incompatible with `interrupt()` + `Command(resume)`: `addResult` sends new input rather than a resume command, and it would require the backend graph to be rebuilt around a pending tool call. The native interrupt keeps the human-in-the-loop gate as a first-class checkpoint that persists and restores cleanly.

For more information on interrupt handling and human-in-the-loop patterns, see the [LangGraph Interrupts Documentation](https://docs.langchain.com/oss/python/langgraph/interrupts) and the [assistant-ui LangGraph Interrupts guide](https://www.assistant-ui.com/docs/runtimes/langgraph/interrupts).
