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

# Debugging with an agent

> Four tool calls from a symptom to the captured value that explains it, with what each one cost.

A customer says order `o-5003` was never assigned a dasher. An AI agent holds that symptom and
nothing else: no trace id, no stack trace, no idea which function decided. This page is one real
pass through the tools, against the demo service's own run of `assign o-5003`, with the bytes and
tokens each response cost on that run.

The order of the calls is the point. Each one narrows, and the agent pays only for what it opens.

## 1. Find the trace

`vexlo_search_traces` with `team: ["demo"]`, `service: ["delivery-service"]`, and
`hasError: true`. One row comes back:

```json theme={null}
{
  "items": [
    {
      "trace_id": "df515ee28e83d82e31189ef7bb4afc4d",
      "service": "delivery-service",
      "team": "demo",
      "environment": "dev",
      "instance_id": "23dc3edfab2aef024b2cf964b185841e",
      "tags": {
        "order_id": "o-5003"
      },
      "root_function": "delivery.api.assign_dasher",
      "root_status": "ok",
      "completeness": "complete",
      "started_at": "2026-11-05T06:25:59.714Z",
      "duration_ms": 0,
      "step_count": 10,
      "error_step_count": 1,
      "records_lost": 0,
      "records_shed": 0,
      "truncated": false,
      "instance_mismatch": false
    }
  ],
  "limit": 10,
  "next_cursor": null
}
```

`hasError: true` is what finds this trace. `root_status` is `ok`: the request itself returned
normally, because `assign_dasher` caught the rejection and answered with it. A search for
`rootStatus: "error"` would have come back empty, and an agent that stopped there would have
concluded nothing went wrong. `error_step_count: 1` says exactly one step inside the request
errored, and `completeness: "complete"` says every record of the trace arrived, so the tree is
whole before anything is read.

## 2. Read the shape

`vexlo_get_trace` with that `traceId`. Ten steps, each with its attempts, timing, and slot
previews. The one errored step and the envelope around it:

```json theme={null}
{
  "trace_id": "df515ee28e83d82e31189ef7bb4afc4d",
  "service": "delivery-service",
  "team": "demo",
  "environment": "dev",
  "format_version": 2,
  "instance_id": "23dc3edfab2aef024b2cf964b185841e",
  "sdk_version": "0.1.0",
  "tags": {
    "order_id": "o-5003"
  },
  "started_at": "2026-11-05T06:25:59.714Z",
  "first_seen_at": "2026-11-05T06:25:59.719Z",
  "last_seen_at": "2026-11-05T06:25:59.719Z",
  "root_status": "ok",
  "completeness": "complete",
  "step_count": 10,
  "error_step_count": 1,
  "duration_ms": 0,
  "coverage": {
    "records_lost": 0,
    "records_shed": 0,
    "truncated": false,
    "instance_mismatch": false,
    "service_loss_24h": {
      "delivery-service": 0
    }
  },
  "steps": [
... 165 lines omitted
    {
      "path": "AABA",
      "parent_path": "",
      "depth": 1,
      "name": "delivery.assignment.DasherSelection.pick",
      "parent_name": "delivery.api.assign_dasher",
      "attempt_grouping": "single",
      "duration_us": 155,
      "attempts": [
        {
          "attempt": 0,
          "started_us": 276,
          "duration_us": 155,
          "status": "error",
          "error_class": "delivery.errors.NoEligibleDasherError",
          "slots": {
            "input": {
              "state": "stored",
              "bytes_count": 19,
              "truncated": false,
              "encoding": "json",
              "preview": "{\"zone\":\"SF-94110\"}"
            },
            "error": {
              "state": "stored",
              "bytes_count": 195,
              "truncated": false,
              "encoding": "json",
              "preview": "{\"message\":\"dasher selection failed in zone 'SF-94110': no dashe"
            }
          }
        }
      ]
    },
... 132 lines omitted
  ],
  "limit": 500,
  "next_cursor": null
}
```

`DasherSelection.pick` raised `delivery.errors.NoEligibleDasherError` on zone `SF-94110`. Its
two children, `DasherSelection.candidates` and `DasherSelection.in_zone`, both have
`status: "ok"`, and their `output` previews in the omitted lines read `[]`. Nothing crashed
below `pick`: the functions that look for dashers ran and found none. The shape has already
moved the question from "what broke" to "why was that list empty", and the `coverage` block says
no record was lost or shed, so no step is missing from that conclusion.

## 3. Read the error

Previews cut at 64 code points, so the error message is still half-read. `vexlo_get_values` with
`path: ["AABA"]` returns both of that step's slots in full:

```json theme={null}
{
  "values": [
    {
      "path": "AABA",
      "slot": "error",
      "state": "stored",
      "encoding": "json",
      "value": "{\"message\":\"dasher selection failed in zone 'SF-94110': no dasher is working this zone\",\"stack\":[\"_markers.py:2839 in _make_wrapper.<locals>.wrapper\",\"assignment.py:104 in DasherSelection.pick\"]}",
      "bytes_count": 195,
      "truncated": false
    },
    {
      "path": "AABA",
      "slot": "input",
      "state": "stored",
      "encoding": "json",
      "value": "{\"zone\":\"SF-94110\"}",
      "bytes_count": 19,
      "truncated": false
    }
  ],
  "unknown_paths": []
}
```

The message names the zone and the reason. `pick` was called with one argument, `zone`.

## 4. Confirm the empty list

The claim left to check is that no dasher works that zone, as the code saw it.
`vexlo_get_values` with `path: ["AABAAAI"]`, the `in_zone` step `pick` called:

```json theme={null}
{
  "values": [
    {
      "path": "AABAAAI",
      "slot": "input",
      "state": "pending",
      "encoding": "json",
      "value": "{\"zone\":\"SF-94110\"}",
      "bytes_count": 19,
      "truncated": false
    },
    {
      "path": "AABAAAI",
      "slot": "output",
      "state": "pending",
      "encoding": "json",
      "value": "[]",
      "bytes_count": 2,
      "truncated": false
    }
  ],
  "unknown_paths": []
}
```

`in_zone("SF-94110")` returned an empty list. The order was rejected because the zone had no
working dasher, not because `pick` chose wrongly. Both slots read `state: "pending"` and still
carry a `value`, which is why the rule is to branch on whether `value` is present.

## What the pass cost

Measured on this run, over the one-line text each tool returned; the blocks above are `jq .`
renderings of it.

| Call | Bytes | Tokens |
| - | -: | -: |
| `vexlo_search_traces`, one row | 491 | 173 |
| `vexlo_get_trace`, 10 steps with previews | 5,926 | 1,703 |
| `vexlo_get_values`, the `pick` step | 484 | 135 |
| `vexlo_get_values`, the `in_zone` step | 288 | 83 |
| Total | 7,189 | 2,094 |

The shape is 81% of it. An agent that trusted the `[]` previews in step 2 and stopped there
would have paid 1,876 tokens for the same conclusion with weaker evidence. The one-time startup
handshake, 224 tokens, is not in the table: [startup checks and exit
codes](https://docs.vexlo.ai/mcp/startup-and-exit-codes.md) covers it.

## Four rules this pass follows

Call `vexlo_check_coverage` before reading an empty result as "nothing happened". A misspelled
service name and a real, empty answer look identical everywhere else. On this server it costs
171 bytes and 65 tokens, and it names the service and environment that actually exist.

Call `vexlo_list_tags` before filtering by tag. It is the cheapest call on the surface, 117
bytes and 34 tokens here, and it is what tells an agent the key is `order_id` rather than
`order` or `orderId`.

Call `vexlo_check_trace_status` for ids you already hold. One id costs 490 bytes and 173 tokens
here, the same as re-running the search above, because what comes back is the same row. The
saving is against a search that is not already narrowed to one: the
[ladder](https://docs.vexlo.ai/mcp/token-costs.md) measures the default ten-trace page at 1,473 tokens.

Call `vexlo_find_related_traces` to follow one request past this trace. On this trace it
returns no siblings for 180 bytes and 69 tokens, because no other trace carries
`order_id: o-5003`. Service plays no part in the query, which is keyed on team plus tag key and
value: the same call on one of this team's three `o-5007` traces, all of them this one service,
returns the other two for 523 bytes and 215 tokens. It reports a shared tag, never a call.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.