Skip to main content
Vexlo records executions of instrumented entry points, such as request handlers or background tasks. A recording combines the calls that ran, their relationships and the data captured for each call. It describes one observed execution rather than all behavior possible in the code.

From an execution to a graph

Consider a checkout that calculates the price of two line items, applies a discount and adds tax. This recording captures the entry point and five functions involved in that calculation. One function runs twice, producing seven recorded calls: checkout is the root call, the entry point at the top of the graph. price_order is its recorded child. Below that, price_lines calls line_total for each order item, while discount_for and tax_for calculate the adjustments to the subtotal. Calls sharing the same recorded parent are siblings. That relationship alone does not imply that they ran simultaneously or in completion order. An edge connects a call to its nearest recorded parent. It does not prove that the parent called it directly in the source. In another captured execution, an instrumented operation called an uninstrumented helper, which called instrumented read_part. The recording contains operation and read_part, with the latter directly beneath the root. The helper still ran; it was outside the instrumentation boundary. This is an observed execution graph. A static call graph describes relationships inferred from code, including paths that a particular request did not take. Vexlo’s recordings provide the complementary evidence of instrumented paths that executed and the values they saw.

Traces, recorded calls and call identity

A trace is the recording of one instrumented entry-point execution. Its identifier, trace_id, lets a reader find the same recording through the UI, HTTP API or MCP. A step is a recorded function call within that trace. Its path identifies the call, and parent_path identifies the recorded parent. The root has no parent, and its own path is the empty string. The checkout recording contains two line_total steps. They have the same function name but different paths because the application invoked the function for two line items. A function name identifies the function within its service; a step path identifies a call in this execution. Recorded names are module-qualified, such as order_service.line_total. Copy the full name when querying a function. Paths are opaque. Use them unchanged to select values or relate a child to its parent; do not decode them to infer order, source locations or function identity. Structural tree order helps navigate a recording, while timestamps describe when its calls ran. An entry-point marker inside an existing trace adds a step to that recording instead of starting another trace. A step marker outside an active trace does not start a recording.

The data attached to a call

Each completed attempt can have captured inputs, output, error and logs. Inputs describe arguments received by the function. Output describes its return value. Error data describes an exception, while log entries belong to the attempt during which they were captured. Log capture is opt-in. In this recording, price_order received these arguments, decoded from its recorded JSON input:
It returned:
The output explains the final checkout amount: the two line totals produced the subtotal, then discount and tax produced the final total. Reading successful calls is useful for understanding calculations and data transformations, independently of failures. Captured data is a bounded representation of what the function saw. Supported adapters can convert objects into mappings; unsupported types can appear as type descriptions, and capture limits can truncate values. A recorded representation is not a promise that the original object, its identity or its external dependencies can be reconstructed automatically. When using captured inputs for a test, select data whose representation is understood, check availability and truncation, and construct the application types and fixtures the test needs. See Capture controls and Python API for the capture behavior and limits.

Attempts and retries

An attempt is one execution of a recorded call. A retrying call has several attempts, each with its own status, timing and capture metadata. A more extensively instrumented checkout recording also captures payment. That call failed on the primary processor and succeeded on the backup processor: The application performs retries. Vexlo records and groups them. The SDK supports declared retry grouping and inference from qualifying repeated calls; the exact matching rules are in Python API. Declaring a retry does not cause the SDK to rerun a function after an error. The two successful line_total calls remain separate steps. The two executions of authorize_payment appear as attempts of one step. This preserves the distinction between repeated work for different items and repeated attempts to complete a logical operation. A successful recovery does not erase the earlier failure. Per-attempt previews, status and timing retain attempt identity. The trace-values route, /v1/traces/{id}/values, selects paths and slots without an attempt selector or attempt identity in its results. Do not assign an arbitrary returned value to a numbered retry attempt. The recorded-values guide explains this current interface limitation.

Timing and concurrent work

Each attempt records a start offset from the trace’s start and, when completed, its elapsed duration. These describe elapsed intervals measured by a monotonic clock, not exclusive CPU time. A parent can remain active while its children run, and concurrent children can overlap. A separate captured operation started two reads concurrently. The profile read started first, but the preferences read finished first: Adding the two child durations would overstate the operation’s elapsed time because part of their work overlapped. Structural call order is also different from completion order. Use the Timeline view to inspect intervals and the Table view to examine individual calls. A step’s total duration is the sum of its completed attempts. It excludes unfinished attempts and does not measure the entire interval between the first attempt’s start and a later retry’s completion. For an ongoing retry, that total is a lower bound rather than a final duration. An attempt with no reported completion has no completed duration. It can still be running, but a lost completion record leaves the same evidence.

Execution status and recording completeness

The entry point’s status, a call attempt’s status and the recording’s completeness answer different questions. Two captured executions illustrate the distinction: In failed-root, the entry point raised an error after a successful read. In handled-error, the entry point caught a child’s exception and returned a rejection response normally. Both recordings arrived completely. Neither business success nor failure determines completeness. A running recording has no root completion and has received recent data. An abandoned one still has no root completion after the inactivity window. A complete recording has a root completion without reported loss, shedding, truncation or instance mismatch; a partial one has a root completion and at least one of those conditions. These states are derived when data is read, so later arrivals can change the result. Completeness does not establish that every possible function was instrumented or that every branch ran. Inspect completeness and loss information before interpreting a missing call or unfinished attempt.

Connect recordings to code and other requests

Function metadata aggregates observed callers, callees and examples across recordings. Use these relationships to find executions relevant to a function and inspect how it was used. Use application-revision provenance when relating a recording to source or comparing runs around a change. A user-defined revision tag can identify the application version. The sdk_version field identifies the SDK, not the application’s code revision. A captured stack line does not establish that the reader’s current checkout contains the same code at that line. A tag is a key/value pair attached to a trace. Shared identifiers, such as an order or request ID, can find recordings across services. Those recordings remain separate traces. Sharing a tag relates the records; it does not prove one service called another or supply an automatic cross-service call edge.

Inspecting calls and data

A recording connects each call with its timing, attempts and captured data. In the UI, follow the calls first, then select the function whose arguments, return value or error you need. Previews let you inspect a call before fetching its full value. A preview can remain after full value bytes become unavailable. Fetch the selected value to check what is readable now. Inspect recorded values explains capture states, truncation and unavailable data; Retention explains how long the recording and its values remain available. For agent queries, Use execution data explains how to choose reads and manage payloads in the agent’s working context.

How recorded data reaches readers

The SDK records inside the application and writes locally. A relay on the application host forwards those records to the server. The server stores the recording and serves it through the UI and HTTP API. The MCP process reads that API for an AI agent; it does not collect application execution itself. The SDK’s flush operation requests a local drain. It does not confirm disk success or server ingestion. Relay and server health checks describe their own boundaries; observing a trace and its values confirms delivery of that recorded execution to the read interface. Continue with Why execution graphs for the rationale, Quickstart for a first execution, Explore execution graphs for UI navigation, or AI agents for agent access to this data.