Skip to content

Debug applications with DevTools

Use DevTools to follow a request from submission through layout and Action outcomes. Live activity collects metadata automatically. Start a recording before reproducing a problem to retain supported prompts, arguments, and transcript content.

For local read-only MCP inspection, use DevTools MCP. It uses a separate pairing step and does not enable recording or application control.

  • Configure a working React application with Vessels and an Engine connector. The examples below extend the files from Creating Vessels.
  • Follow the installation guide and use DevTools from the same release or PR preview as your Maelstrom packages.
  1. Install the React adapter in your application directory. This command uses the stable npm release; use your matching preview tag when testing a PR.

    Terminal window
    bun add @maelstrom-co/react-devtools
  2. Apply the extension to your configured instance. Keep your existing Vessel entries. This example reuses the Greeting definition and component from the prerequisite guide.

    src/maelstrom.ts
    import { createMaelstrom, vessel } from '@maelstrom-co/react';
    import { devtools } from '@maelstrom-co/react-devtools';
    import GreetingComponent from './vessels/greeting/greeting.component';
    import { greetingVessel } from './vessels/greeting/greeting.vessel';
    export const greetingEntry = vessel(greetingVessel, GreetingComponent);
    export const m = createMaelstrom({
    vessels: [greetingEntry],
    }).with(devtools());
  3. Mount m.Devtools near the Provider. Use your existing connector. Keeping the tools outside a conditional Provider preserves access to retained evidence when that Provider unmounts.

    src/app.tsx
    import { m } from './maelstrom';
    import { myConnector } from './connector';
    export default function App() {
    return (
    <>
    <m.Provider connector={myConnector}>
    <m.Chart />
    </m.Provider>
    <m.Devtools />
    </>
    );
    }
  4. Open the floating Maelstrom button in your development build. Submit an intent through your existing application and select it in Requests. Inspect its observed stages beside the JSON.

The extension attaches collection before the Provider connects. Closing the panel keeps collection active. Several mounts of m.Devtools share one panel. Each mounted Provider creates a separate app lifetime; an ended lifetime can remain visible as bounded history after a remount.

Choose an Evidence source and App instance, then open Evidence & setup. Read the status rows, then expand one for its explanation and next steps. Rows stay open as evidence updates. The section scrolls separately from the workspace. Open Setup guides for documentation links. Each link opens in a new tab, keeping the application and its recordings open.

For live apps, Attached and Current Engine state describe the collector’s latest projected state. Ended apps and recordings show the last retained Engine state. An imported recording cannot establish a current connection or attachment.

Use the reported limitation to choose the next step:

Signal What it means Next step
Hidden by view filters Events remain retained. Reset event filters or widen the app selection.
Collection filter configured The application can exclude events before retention. Exclusions have no count here. Check DevtoolsOptions.filter before reproducing the problem.
Recent or request events evicted A collector buffer discarded older metadata. Counts cover the entire source, including other apps. Record a reproduction or increase the relevant retention limit.
Recording events dropped Capture bounds or later redaction discarded events. The count does not locate them. Check capture limits and redaction before a new recording.
Events omitted by the viewer Earlier retained events sit outside the viewer window. Read the collector snapshot for live metadata or inspect an exported recording outside this viewer.
Live metadata only Application content is absent from this feed, including while recording. Record before reproducing the problem, then inspect Current recording.

Engine diagnostics available and Voice observations available report observations, not current setup permissions or provider support. Their absence leaves setup unknown. Check optional Engine evidence or voice observation when the corresponding events are missing.

Live guidance includes retained terminal voice observations after they leave the recent activity buffer. Recordings use only their own saved evidence. If an app snapshot falls outside the viewer window, its absence from the view does not establish loss from the recording.

In a recording, absent fields may be unrecorded, redacted, truncated, or unsupported. An unreported loss count also stays unknown. Raw audio is excluded. Opening this guidance does not start recording or change collection setup.

  1. Select Evidence and the app instance you want to inspect.
  2. Open Overview. Choose Request round-trip or Client application under Timing measure to keep the two reported durations separate.
  3. Filter by Outcome or Timing availability. Search by request ID or recorded intent to narrow the retained sample.
  4. Select a chart point or a request in the table to open its supporting events and JSON in Requests. Your Overview filters remain when you return.

Each page contains at most 50 request identities. Only reported durations become chart points; requests without that timing remain in the table. Chart time uses each request’s first retained client observation when the page has one usable client/session clock. Otherwise, its horizontal axis follows each request’s first retained event in observation order. Use the table when points overlap or their client timestamps are unavailable.

The window covers first retained observations across all retained requests. Later events do not extend it. Read this window and the counts before comparing results. The retained count and event-loss counts describe the selected evidence source. Matching, hidden, and missing-timing counts follow your filters. Paging hides additional matches without changing retention. An unreported loss count remains unknown.

Cancelled, discarded, abandoned, failed, and applied-with-issues outcomes remain separate. Missing Action counters leave the outcome unknown unless application evidence confirms an issue. An explicit unknown request result stays unknown. Withheld or unreadable timings retain an explanation instead of becoming zero. A push response has no request round-trip measurement.

Changing Evidence resets Overview filters and pagination. This view reads diagnostic request evidence; telemetry delivery counters do not supply request counts or timings. Opening Overview does not start a recording.

Select a request to read its outcome and observed stages. Response receipt and application completion are separate milestones. The client applies the layout before executing the response’s Actions.

Outcome What it establishes What to inspect next
Response received The response reached the browser. Wait for an application outcome or inspect missing stages.
Applied The client completed the response command batch. Inspect the resulting structure and Action outcomes.
Applied with Action issues The batch completed with failed or skipped Actions. Inspect each Action’s outcome and readiness evidence.
Failed The client reported a request or application failure. Inspect the error; supported details require recording.
Cancelled The request ended through cancellation. Inspect its reason; cancellation is separate from failure.
Discarded or abandoned Work became stale or ended before completion. Inspect the reported reason and app lifetime.

An applied outcome does not establish browser paint, animation completion, or audible speech. Missing or evicted stages remain partial evidence. Durations use the emitting process’s clock; server and browser timestamps are independent.

When retained events reuse one request identity or report conflicting sessions, Ambiguous request evidence exposes individual observations. Aggregate outcome and timing are unavailable for that group.

Use Issue category to find timeout, transport, validation, readiness, application, provider, or unknown problems. Category not reported finds issue events without a recognized category, including older recordings. The filter works in Requests and All events. A request can contain a failed attempt and still finish successfully. Read its final outcome alongside Observed issues. Category explanations use metadata and need no recording or telemetry exporter; full error content still requires explicit recording.

Compare Request round-trip with Client application to locate delays. Round-trip includes the wait for the Engine response. Client application covers the whole response batch. Open Timings and evidence coverage for retained command work and individual Engine request, planning, and stage durations. Nested timings overlap, so do not add them. Older recordings can report command work without whole application time; the panel leaves that time as Not reported.

Use Request phases to compare reported durations beside each event’s phase and outcome. Select a row with the mouse or keyboard to open its evidence and JSON. Repeated Engine attempts remain separate rows in observed order. The bars share a duration scale; their positions do not represent a shared clock or non-overlapping work. Events without reported durations have no bar. Clear Show duration bars for a text table. Use Show more phases for longer histories.

Check the evidence coverage before drawing conclusions. A retained start and outcome do not prove that every intermediate event survived. A missing outcome can mean work is still running or the outcome is outside the evidence window. Dropped or evicted counts cover the evidence source across app instances; they do not identify which requests lost events. Voice telemetry source loss and exporter delivery health are separate from these DevTools history counts.

The request list, readable details, and JSON pane scroll independently. Use the JSON pane’s Tree presentation to explore collapsible sections, or Raw JSON for highlighted text. Open Info for the selected evidence’s description. Open Using DevTools in the toolbar for guidance on the current view. Selection stays fixed while new requests arrive. If the selected trace expires, select another request from the list.

Choose Pin request to keep one request at the top of the list while new activity arrives. A pin stores only the request identity. It does not extend event retention or start a recording. The panel reports when filters hide the pinned request or its evidence is no longer retained.

Choose Unpin request or select another request to clear the pin. Changing Evidence or App instance, or replacing an imported recording, also clears it. Starting a new recording clears the pin while Evidence is set to Current recording. Pins in Live activity and Imported recording stay selected because their evidence is unchanged. Pins last until the panel unmounts. Closing and reopening the panel preserves the pin.

Select a request and expand Planner evidence to inspect retained planning outcomes, attempt markers, candidate validations, and model-call reports. Select an observation to open its JSON. In comparison, each column keeps its own selection. Comparisons omit the section when no planner observations are retained.

Read the reported model, duration, input tokens, and output tokens on each model-call report. A reported zero stays zero. Missing, redacted, truncated, and invalid values retain their own labels. These values come from planner instrumentation; they can include several internal provider calls. The panel does not sum usage or overlapping durations.

Attempt rows show the producer’s reported number, starting at zero. They do not link nearby calls or validations to that attempt. A passed validation does not establish candidate acceptance or client application. Read the request’s final outcome separately.

The section shows at most 50 planner observations per page. Use Next planner observations and Previous planner observations to inspect the rest. Each comparison column keeps its own page. New observations preserve your current page. View limits and filters leave recordings unchanged. The section uses the same evidence in live activity, current recordings, and imported recordings.

If no planner observations are retained, check optional Engine evidence and reproduce the request after observation starts. Opening this section does not enable host forwarding or start recording.

Select a request in Requests and choose Compare request. It becomes A. Select a second request from the list for B. Both requests must belong to the selected Evidence source. Choose All app instances before comparing requests from different app lifetimes.

Compare the outcomes and reported durations. B − A shows the difference when both requests report the same timing measure. Missing durations stay Not reported. Push responses have no request round-trip measurement. Stages and repeated Actions remain separate sequences; rows do not identify matching attempts across requests. Duration bars use one scale across both requests. Open Comparison help for selection guidance and measurement limits.

Select a phase on each side to inspect two events together. Each request has its own details and event JSON inspector. Choosing another phase updates only that side’s JSON. Scroll the details and JSON separately within each column.

Use each inspector’s tree or raw JSON controls to explore the full event, including metadata and any recorded content. Show JSON hides or shows both inspectors. The shared JSON pane is hidden during comparison. Live activity contains metadata only; viewing a request does not start a recording.

Read Other request beside each phase to find one-sided observations. Unmatched in retained evidence means the other request has no compatible observation in its retained history. Incomplete or redacted evidence stays unknown. Similar metadata does not establish a matching attempt or Action invocation. Reported history loss also leaves missing counterparts unknown because the source does not identify which request lost an event.

Use Choose A or Choose B, then select a request from the list to replace that side. Incoming activity preserves both identities. Filters change the list while selected requests remain inspectable. Eviction leaves an explicit message and clears unavailable JSON; comparison does not extend retention.

Choose Exit comparison to return to one request. Changing Evidence or App instance, replacing an imported recording, or unmounting the panel clears the comparison. Starting a new recording or changing its redaction policy also clears comparison while Evidence is set to Current recording. Closing and reopening the panel preserves the selected requests.

Start a recording before reproducing a layout change. Stop the recording, select Current recording under Evidence, and select the request. What changed in the Chart shows the state before layout application and after the response finishes, including a failed application.

Use the change table to find added, removed, shown, hidden, moved, or resized Instances and Variant changes. Hidden Instances remain retained by the client; removed Instances do not. Select an Instance in either miniature or the table to inspect its supporting snapshots, commands, outcomes, and JSON. Select Before or After to open the corresponding snapshot.

The miniatures use reported Chart dimensions. Grid comparisons measure changes in cells; tiling comparisons use relative proportions. The diagrams are schematic and do not establish browser paint. Chart resizes and overlapping commands appear as attribution limits. Application-owned updates can also contribute to the observed changes.

Layout snapshots require explicit recording and pass through redaction before retention and export. Open an exported recording to inspect the same comparison without running commands. Live activity and older recordings can lack these snapshots. Missing, redacted, truncated, or unsupported evidence leaves the comparison unavailable; the panel does not infer an empty Chart.

Recording limits still apply. The viewer compares up to 1,000 retained Instances per snapshot and displays 200 changes per page. Each miniature draws up to 200 visible Instances. Select a change in the table to reveal its Instance when it lies outside that set. Geometry that exceeds the viewer’s limits remains unavailable while supported structural changes remain readable.

  1. Open Requests and choose All events in the activity selector.
  2. Choose a preset: Voice, Engine, Actions, or Failures. A preset fills editable criteria and clears the search and issue category.
  3. Open Event filters to combine a family, exact event kind, reported outcome, request, voice connection, and time range. Use App instance to narrow the source to one client lifetime. Every active criterion must match.
  4. Select a matching event to inspect its JSON. Choose Reset to clear the event filters, search, and issue category. The evidence source and app scope stay selected.

The toolbar reports matching events and events hidden by filters. Show more events reveals additional matches; pagination does not count as filtering or evidence loss. Dropped, evicted, and omitted events remain separate counts.

Outcome filters describe individual reported events. Unknown means the supported metadata does not establish an outcome. Not reported means the event has no supported outcome fact. Failures excludes cancellation, skipped work, and unknown outcomes. Grouped requests keep their complete retained evidence when you return to Requests.

Time bounds include their endpoints and use your local time zone. They filter reported timestamps; producer clocks can differ. Events without valid timestamps do not match an active bound. A reversed range shows an error and no matches.

Filters work with live activity, stopped recordings, and imports. They do not change retention, recording scope, or exported content. Changing the evidence source, replacing an import, or clearing all evidence resets source-specific event filters. Ordinary retention keeps an expired target selected so the view does not silently broaden to unrelated events.

In Requests, choose All events and the Voice preset. Open Event filters and choose a Voice connection filter to isolate a retained app, provider lifetime, generation scope, and generation. The connection choices require those identities; the Voice preset also includes events with incomplete identities. Search matches event kinds, request IDs, and metadata. Captured content does not participate in search.

Open Connections for Engine connection state and separate voice summaries. Select a connection or voice event to highlight its row and inspect its JSON. Voice evidence distinguishes provider lifetimes and connection generations. Connections labels session and provider generations separately. These counters can differ after a cancelled startup; matching numbers do not establish that they describe the same attempt. Older recordings can show a generation without an explicit scope. Voice observation requires the optional realtime integration; DevTools itself does not connect a voice provider or acquire a microphone.

React realtime attaches voice diagnostics automatically. For another framework, use attachRealtimeDiagnostics with the existing session, adapter, owning client channel, and provider lifetime identity. Release the attachment when retiring that lifetime. Supported native provider facts appear under voice.provider.*; arguments and intent remain explicit recording content.

The App instance selector identifies a Maelstrom client, not a voice provider. It filters inspection. Search also changes the displayed evidence, without changing what is recorded.

  1. Open Voice and choose an Evidence source: live activity, a stopped recording, or an imported recording. The optional realtime attachment must have observed the interaction.
  2. Choose a Voice provider lifetime and Voice generation to narrow the lanes. Session and provider generations remain separate even when their numbers match. Older recordings without an explicit scope use a legacy lane. Long lists offer search and Show more. Search by provider name, full lifetime ID, or generation; searching the choices preserves the current event.
  3. Select a lane, then a numbered observation in its diagram or table. Both selections open the same JSON evidence. Recorded transcript text appears below the table; live metadata does not contain the text. Without an explicit selection, the view follows the latest observed lane.
  4. Select Open request when a tool observation reports an Engine request whose supporting events remain retained. This opens that request in Requests. Returning to Voice preserves your filters and selected evidence. An unavailable link means the request ID was reported but its supporting events are not retained. Contradictory retained app context makes request ownership ambiguous and also disables the link. Repeated request lifecycles with the same identity disable the link too.

The diagram shows retained buffer order, without aligning process or media clocks. Tool invocation IDs separate overlapping calls that reuse a provider tool ID. Provider adapters project response identity and lifecycle into common diagnostic fields. These fields group response observations within their provider generation. Older captures with only native event labels keep those observations unlinked. Missing or ambiguous identities leave observations unlinked. These groups do not establish complete conversational turns, speech-to-tool ownership, or audible playback. Expand What can this timeline prove? for these limits.

Redacted, truncated, unreadable, and missing identities have separate labels. An Unknown tool outcome means the tools cannot confirm how the work finished.

Once live identities conflict, grouping stays unavailable for that lane in the current collector, including across activity gaps and panel remounts. The tools keep bounded identity state. If that history fills, live grouping becomes unavailable across the collector. Individual observations remain inspectable. Completed lifecycle identities stay in that history after their events leave the live buffer, so a reused identity cannot inherit earlier evidence.

Successful queued tools report their applied Engine response’s request ID. Legacy immediate bridges and failed or unconfirmed work can lack that link. The view does not infer it from nearby events or matching tool IDs.

Live voice lanes use the recent activity buffer. The separate request history supplies related-request availability, without adding older voice events to that sequence. Collector eviction and capture limits can remove observations; they do not establish loss at the voice source. This feed does not report that source loss. If a selected observation expires, its JSON becomes unavailable until you select another observation.

Filtering leaves collection and recording scope unchanged. Opening this view does not connect voice, acquire a microphone, or start content capture.

Open Inspector to see a schematic Chart, retained Instances, current Variants, and declared Actions. Select a live Instance to highlight it in its owning Chart. Chart outlines is a separate display control.

The schematic uses reported Chart dimensions and layout structure. It does not retain exact DOM pixels. Hidden retained Instances remain in the list. A recording’s Inspector shows its latest retained structure; selecting an old event does not move a history cursor. Recorded and imported selection never highlights a live Instance or changes the application.

  1. Open Recordings and choose the recording scope. Select all app instances or one active app. The scope stays fixed after recording starts. An all-app recording includes later attachments; a selected-app recording stops when that lifetime ends.
  2. Select Start recording, then reproduce the problem. The recording begins with structural baselines. It retains supported subsequent content and cannot recover earlier prompts, transcripts, or Action arguments.
  3. Select Stop, then Inspect recording. Use the same readable views and JSON to inspect the retained evidence. Use What did you expect? What happened? to add a note about the problem. Notes are manual context, not an automated test.
  4. Select Export JSON to download the recording. Use Open recording to inspect a saved file. Imported evidence and notes are read-only.

Use Evidence to switch between live activity, the current recording, and an imported recording. The recording scope is independent of the App instance inspection filter.

Clear recording removes the current recording and its notes while keeping live activity. Closing the panel does not stop a recording. Evidence stays in memory until cleared or reloaded; files are downloaded only when you export. DevTools does not upload recordings or store them in browser storage.

Replace the devtools() call in the installation example with options for your application. This fragment changes the trigger, theme, and retention limits:

devtools({
position: 'bottom-left',
theme: 'dark',
maxActivityEvents: 250,
maxRequestEvents: 2000,
capture: { maxEvents: 2000, maxCharacters: 4000000 },
});

Defaults retain 100 recent metadata events, 1,000 request metadata events, 16 app lifetimes, and 1,000 recording events. Recordings permit 2,000,000 serialized event characters and 16,384 characters per string. Dropped counts make incomplete evidence visible. Request history has its own budget and evicts whole requests; an oversized request keeps its latest stages and reports partial evidence.

Use DevtoolsOptions for the complete configuration. Its redact callback receives detached JSON and should be idempotent. Its filter callback excludes events before retention; this differs from the panel’s search filter. Set enabled: false to disable collection and mounting.

If your application already owns a configured Telemetry instance, pass it to DevTools. Replace devtools() in the installation example with this fragment:

devtools({ telemetry });

Open Telemetry. Check Queued and Exporting for pending work, Exporter acknowledged for acknowledged facts, and Loss ledger for local loss counts. Acknowledgement does not prove backend persistence. These counts cover the supplied telemetry instance across its attached producers. They do not identify missing request events or voice source observations.

The flow labels current queue sizes as Now and cumulative counts as Lifetime. The loss ledger highlights nonzero reasons. Select Show all loss counters to inspect every reason. Open Scope and counting rules for interpretation limits; both disclosures stay open while counters refresh.

Check Delivery trends for queue growth, exporter activity, and delivery rates. The charts retain up to 120 local read attempts. Accepted in window, Acknowledged in window, and Lost in window count changes during observed intervals. Rates use actual elapsed time, including delayed refreshes. The counters above the charts remain lifetime totals or current queue sizes.

Select a time in either chart or a row in Telemetry samples to inspect its exact counters and loss reasons as JSON. Use the arrow keys to move between samples, or Home and End to select the oldest and newest retained samples. Select Follow latest to resume following new reads. If a selected sample expires, the inspector reports that its evidence is no longer retained.

Close the view to pause sampling. Reopening it preserves retained history and marks the gap. Failed reads, counter resets, and a different getSnapshot method also break continuity. These gaps contribute no time or counter changes to window rates. A failed read remains visible after the reader recovers.

Samples belong to the mounted panel and stay outside recordings. They describe delivery across the reader’s producers and contain no request identities.

The supplied getSnapshot() must return synchronously. DevTools calls it when you open the tab and every second while the tab is visible. Changing tabs, closing the panel, or unmounting its final copy stops these reads. A missing reader shows No telemetry reader supplied. A failed or invalid read shows unavailable; the next refresh can recover.

Disabled means no sink was configured. Timed out means the exporter deadline stopped delivery; the application must create a replacement telemetry instance to resume. Disposed means the application ended that instance. Active can still have failed batches, so inspect loss counts too.

The view always shows live totals, including when you have an imported recording open elsewhere in the panel. App instance and evidence filters do not apply. These counters are not added to recordings. DevTools never creates an exporter, attaches producers, flushes, or disposes telemetry. Keep those operations in the application that owns the instance.

The default import is inert in production. To expose browser diagnostics in every build, replace the adapter import in your configured instance:

import { devtools } from '@maelstrom-co/react-devtools/production';

Apply the same extension and mount m.Devtools. The demo uses this entrypoint in development, preview, and production. Content still requires Start recording. If you supply a capture store, create it from @maelstrom-co/devtools/production. Source consumers must replace process.env.NODE_ENV at build time, as Vite does.

Local client diagnostics work with every Engine connector. Planner attempts, validation, and reported usage require a compatible transport and an explicitly enabled host. The WebSocket connector carries diagnostics over its existing connection; DevTools opens no separate debug server.

In the Engine host, use createDiagnosticConnection for each authorized connection. Bind its sessionId to the authenticated session, handle diagnostic envelopes in the existing connection handler, and dispose the helper when the connection closes. Metadata and content forwarding have separate permissions; explicit recording enables supported content.

The host owns authentication and session ownership checks. Server forwarding remains disabled in production, including when browser tools use /production. Absent server stages or provider-reported usage remain unknown.

The core package supplies the same collector and browser panel. Supply an existing Orchestrator, attach before connecting it, and release both lifecycles when the application surface is removed:

src/attach-devtools.ts
import type { Orchestrator } from '@maelstrom-co/client';
import { createDevtools, mountDevtools } from '@maelstrom-co/devtools';
export function attachDevtools(orchestrator: Orchestrator) {
const tools = createDevtools();
const detach = tools.attach(orchestrator);
const unmount = mountDevtools(tools);
return () => {
unmount();
detach();
tools.dispose();
};
}

Install @maelstrom-co/devtools from the matching release or preview for this integration. Mounting requires a browser DOM; server rendering mounts no panel. Use the core’s /production entrypoint for the same explicit production opt-in.

  • Open the development panel and submit a request. Confirm that its receipt, layout, and available Action outcomes remain distinct.
  • Start recording before another request, stop, export, and reopen it. Confirm that supported content appears and import changes no live application state.
  • Remount the Provider and confirm that its new app lifetime stays separate from retained evidence. Keep the tools mounted while checking an ended lifetime.
  • Build for production. Confirm that the default import mounts no tools, or that the explicit /production import exposes the intended recording flow.

If the panel is empty, check that the extension and Chart use the same configured instance and that collection is enabled. For the exact public interfaces, see the React adapter reference and core reference.