# How to Debug A2UI: A Complete Guide to Troubleshooting Agent-to-UI Applications

> Debug A2UI applications effectively by enabling debug logging, using the A2UI Inspector, and stepping through message processing. Trace issues from JSON payloads to rendered components with this comprehensive guide.

- Repository: [Google/A2UI](https://github.com/google/A2UI)
- Tags: how-to-guide
- Published: 2026-03-13

---

**Enable `debug` logging, launch the A2UI Inspector at `localhost:5173`, and step through message processing in the Gallery App to trace issues from JSON payloads to rendered components.**

Debugging A2UI requires tracing data flow across three distinct layers: message processing, state management, and framework-specific rendering. This guide covers the first-party debugging tools provided by the `google/A2UI` repository—including the Inspector web UI, Gallery App, and Eval harness—to help you isolate bugs in the JSON payload, data model bindings, or component lifecycle.

## Enable Verbose Logging to Capture State Transitions

Most core libraries emit logs through a Winston-style logger. The default level is `info`, but you must set it to `debug` to see internal state changes.

For the **Inspector**, edit [`tools/inspector/package.json`](https://github.com/google/A2UI/blob/main/tools/inspector/package.json) or run:

```bash
npm run dev -- --log-level=debug

```

For **Eval tests**, pass the flag as documented in [`specification/v0_9/eval/README.md`](https://github.com/google/A2UI/blob/main/specification/v0_9/eval/README.md):

```bash
--log-level=debug

```

At the `debug` level, the logger prints chronological traces of:

- `MessageProcessor` → model mutations
- `DataModel` → path subscriptions and cascade notifications
- Component lifecycle events (create, update, delete)

These logs appear in the console and in `output.log`, giving you a complete trace of the system.

To enable logging programmatically in a custom renderer (as implemented in [`renderers/lit/src/index.ts`](https://github.com/google/A2UI/blob/main/renderers/lit/src/index.ts)):

```typescript
import { createLogger } from 'a2ui-core/logging';

// Set the global logger level before any renderer code runs
createLogger({ level: 'debug' });

```

## Visualize Live State with the A2UI Inspector

The A2UI Inspector is the quickest way to view live state while a client runs. Start it from `tools/inspector`:

```bash
npm i && npm run dev

```

Then open `http://localhost:5173/`. The interface shows three panels:

- **Message Stream** – Raw JSON payloads received from the agent
- **Component Tree** – Hierarchical view of the current surface with component `id`, `type`, and resolved properties
- **Data Model** – JSON view of the full `DataModel`, updating in real time as paths change

When a component appears blank or behaves unexpectedly, click it in the tree to reveal its **resolved props** after binder processing. Hover over a data path in the Data Model panel to see which components subscribe to it—the inspector highlights them automatically.

The inspector receives messages over a WebSocket at `ws://localhost:5173/api`. For advanced testing, you can manually send payloads:

```javascript
const ws = new WebSocket('ws://localhost:5173/api');
ws.onopen = () => {
  ws.send(JSON.stringify({
    type: 'updateComponents',
    components: [{ id: 'lbl1', type: 'Text', properties: { text: 'Hello' } }]
  }));
};

```

The source and setup instructions live in [`tools/inspector/README.md`](https://github.com/google/A2UI/blob/main/tools/inspector/README.md).

## Step Through Rendering with the Gallery App

The Gallery App serves as the reference implementation for all renderers, implementing a three-column layout documented in [`specification/v0_9/docs/renderer_guide.md`](https://github.com/google/A2UI/blob/main/specification/v0_9/docs/renderer_guide.md):

| Column | Purpose |
|--------|---------|
| **Samples** | Select predefined A2UI JSON samples |
| **Surface + Messages** | Render the UI and display messages with an **Advance** button |
| **Inspection** | Live Data Model view and Action logs |

Running the Gallery lets you **pause after each message** to isolate the exact point where a component is added or updated. Inspect the **Action log** to verify that dispatched actions contain the expected `surfaceId`, `sourceComponentId`, and payload. The Gallery forwards logger configuration, so you can toggle `debug` logging while the UI stays responsive.

If a bug reproduces in the Gallery, the problem likely exists in the core layer rather than a framework-specific adapter.

## Diagnose Data-Model Path Binding Issues

A common source of "empty" UI is an incorrect JSON Pointer in a `DynamicString` or `DynamicNumber`. The `DataModel` implements auto-vivification and cascade notifications (see the architecture guide in [`specification/v0_9/docs/renderer_guide.md`](https://github.com/google/A2UI/blob/main/specification/v0_9/docs/renderer_guide.md)). 

To verify path subscriptions manually:

```typescript
// Example: manually subscribe to a path while the app runs
const sub = surfaceModel.dataModel.subscribe<string>('/user/name', v => {
  console.log('User name changed to', v);
});

// Later, when you no longer need the subscription
sub.unsubscribe();

```

If the callback never fires, the path either does not exist or is never set. Use the Data Model panel in the Inspector to verify that the key `/user/name` appears after the relevant message.

For custom components, use the binder layer's `subscribeDynamicValue` method (as described in the DataContext section of [`specification/v0_9/docs/renderer_guide.md`](https://github.com/google/A2UI/blob/main/specification/v0_9/docs/renderer_guide.md)):

```typescript
export const MyDynamicLabel = (ctx: ComponentContext<any>) => {
  const sub = ctx.dataContext.subscribeDynamicValue<string>(
    ctx.componentModel.properties['label'],
    value => {
      console.log('Label resolved to', value);
    }
  );

  // Clean up when the component unmounts
  return () => sub.unsubscribe();
};

```

## Verify Component Lifecycle Hooks to Prevent Leaks

Each component implementation must respect three lifecycle rules: **lazy subscription**, **path stability**, and **cleanup** (see "Component Subscription Lifecycle Rules" in [`specification/v0_9/docs/renderer_guide.md`](https://github.com/google/A2UI/blob/main/specification/v0_9/docs/renderer_guide.md)). When debugging memory-leak symptoms:

1. Open browser DevTools **Performance** tab
2. Record a short interaction (e.g., open a dialog, then close it)
3. Look for lingering **EventSource** or **Subscription** objects

If you spot lingering objects, ensure your component's `dispose()` method calls `binding.dispose()` and unsubscribes from all `DataModel` paths.

## Run Automated Checks with the Eval Test Harness

The Eval framework provides a command-line test harness with configurable log levels. It runs prompts against a chosen LLM and validates generated UI against the spec while recording detailed events in `output.log`.

To isolate a failing scenario:

```bash
pnpm run eval -- --model=gemini-2.5-flash-lite --prompt=loginForm --log-level=debug

```

After the run, open `results/<model>/output.log` and search for `ERROR` or `WARN`. The log contains timestamps for each `MessageProcessor.processMessages` call, making it easy to pinpoint the exact message that caused an invalid component or missing data path.

Configuration details are documented in [`specification/v0_9/eval/README.md`](https://github.com/google/A2UI/blob/main/specification/v0_9/eval/README.md).

## Summary

Debugging A2UI systematically requires the right tool for each layer:

- **Enable `debug` logging** in [`tools/inspector/package.json`](https://github.com/google/A2UI/blob/main/tools/inspector/package.json) or Eval runs to capture all model mutations in `output.log`
- **Run the A2UI Inspector** from `tools/inspector` to visualize the component tree, resolved props, and real-time data paths at `localhost:5173`
- **Step through the Gallery App** to isolate offending messages using the **Advance** button and Action logs
- **Verify DataModel paths** using `subscribe()` or `subscribeDynamicValue()` to ensure dynamic bindings resolve correctly
- **Check component lifecycle** in the browser Performance tab to confirm `dispose()` methods clean up subscriptions
- **Use the Eval harness** with `--log-level=debug` for automated, reproducible regression checks against the specification

## Frequently Asked Questions

### How do I enable debug logging in A2UI?

Set the Winston-style logger level to `debug` before starting your tool. For the Inspector, run `npm run dev -- --log-level=debug` or edit [`tools/inspector/package.json`](https://github.com/google/A2UI/blob/main/tools/inspector/package.json). For Eval tests, pass `--log-level=debug` as documented in [`specification/v0_9/eval/README.md`](https://github.com/google/A2UI/blob/main/specification/v0_9/eval/README.md). This captures `MessageProcessor` mutations, `DataModel` subscriptions, and component lifecycle events in the console and `output.log`.

### What is the A2UI Inspector and how does it help?

The A2UI Inspector is a lightweight web UI in `tools/inspector` that visualizes incoming messages, the component tree, and live data models. Running on `localhost:5173`, it shows raw JSON payloads, resolved component properties, and real-time DataModel updates. You can hover over data paths to see which components subscribe to them, making it ideal for diagnosing blank or misbehaving UI elements.

### How can I check if my data bindings are working correctly?

Verify that the JSON Pointer in your `DynamicString` or `DynamicNumber` exists in the DataModel. Use the Inspector's Data Model panel to confirm the path appears after message processing. Programmatically, call `surfaceModel.dataModel.subscribe('/path', callback)` or `ctx.dataContext.subscribeDynamicValue()`—if the callback never fires, the path is unset or incorrect.

### Where can I find logs from the Eval test harness?

The Eval harness writes detailed logs to `results/<model>/output.log` after each run. Execute the harness with `--log-level=debug` to capture timestamps for every `MessageProcessor.processMessages` call. Search this file for `ERROR` or `WARN` to identify the specific message causing spec violations or missing data paths.