Skip to main content
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:
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:
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:
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:
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. 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 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 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.