30.0 / 30
What changed in the harness
Selection accuracy 98→98, token cost down 8%, unconfirmed writes 100%→100%.
Category breakdown
Where the score comes from.
Earned points across the four signals Gradable measures. Safety and Legibility are scored out of 30; Economics and Discoverability are scored out of 20.
01Safety
02Legibility
25.2 / 30
03Economics
14.7 / 20
04Discoverability
11.4 / 20
Highest-impact fix
Estimated gain +9 pointsMake target tools discoverable on the first call
Clarify tool names, decision boundaries, and required argument schemas so an agent can choose and construct the target call without exploratory steps.
Description evidence
Defects and rewrites.
8 defects found across the exposed tool descriptions. Suggested rewrites make purpose, inputs, boundaries, and returns easier for an agent to understand.
| Tool | Defect types | Suggested rewrite |
|---|---|---|
bus_get_stop_detail |
params_unexplained |
Full detail for a stop: precise WGS-84 coordinates, every line that passes through (with first/last times, price), live buses, and nearby metro lines. Use when you already have a stop's IDs from bus_get_nearby_stops or bus_search and need the complete picture for one stop. Args: - city_id (string, required): city identifier, e.g. '034' (Shanghai), '027' (Beijing) — obtain it from bus_list_cities or the city_id used in bus_search. - physical_st_id (string, required): the stop's physicalStId from bus_get_nearby_stops or bus_search. - namesake_st_id (string, optional): namesakeStId from the same source; recommended for accuracy. - first_line_id (string, optional): a line to highlight in the response. - lat / lng (string, optional): the caller's WGS-84 location, used to populate each station's 'distance'. - response_format ('markdown' | 'json'): JSON recommended for structured access. Returns JSON with stations[] — one entry per physical platform (multiple entries mean the stop name maps to several platforms). Each entry has sId, sn, WGS-84 lat/lng, distance, lines[] (name, direction, endpoints, firstTime, lastTime, price, and live buses) and metros[] (nearby metro lines). |
bus_get_line_detail |
params_unexplained |
Full info for a line: rider-facing fields (name, first/last bus times, price, station count), the full ordered station list, the reverse-direction lineId, and every bus currently on the line. Use this — not bus_get_timetable — to answer "is line X still running" or "first/last bus time"; the timetable tool only has data for a small minority of lines. Subway lines are NOT supported: if bus_search returned a line with isSubway=true, this returns an empty payload with 'empty': true and a 'hint' pointing to bus_get_nearby_stops / bus_plan_transit. Don't retry; route to those tools instead. Args: - city_id (string, required): city identifier, e.g. '034' (Shanghai) — obtainable from bus_list_cities or the city_id used in bus_search. - line_id (string, required): the line's lineId from bus_search.lines[*].lineId or bus_get_nearby_stops.lines[].lineId. - lat / lng (string, optional): the caller's WGS-84 coordinates, e.g. from bus_get_my_location; omit if you only need schedule/station data. - response_format ('markdown' | 'json'). Returns JSON: line (name, direction, endpoints, firstTime, lastTime, price, stationsNum), stations[] (ordered; each with sId, WGS-84 coords, physicalStId, namesakeStId, metros), buses[] (live vehicles with position/speed/capacity), reverseDirection (or null), and optionally empty/hint when upstream returned no data. Each station's 'order' feeds bus_get_line_realtime / bus_list_line_buses as target_order; its 'sId' feeds bus_get_line_realtime as station_id (NOT bus_get_stop_detail); physicalStId + namesakeStId feed bus_get_stop_detail. |
bus_get_line_route |
params_unexplained |
Polyline coordinates for drawing a line's route on a map. Points with 'stopOrder' are actual stops; the rest are shape points between stops. Args: - city_id (string, required): city identifier, e.g. '034' (Shanghai) — from bus_list_cities or the city_id used in bus_search. - line_id (string, required): the line's lineId from bus_search.lines[*].lineId. - include_shape (boolean, default false): false returns only stop markers (~25 points); true returns all shape points (~400-500) needed to actually draw the line. - response_format ('markdown' | 'json'). Returns JSON: pointCount (total points upstream returned), stopCount (stop markers among them), points[] (lat/lng with optional stopOrder). 'points' holds the filtered list — stops only by default, full polyline when include_shape=true. Markdown mode only summarises counts; request JSON to read coordinates. Caveat: upstream sometimes omits the terminus stop, so stopCount may be one less than bus_get_line_detail's stationsNum (e.g. 23 vs 24). Trust bus_get_line_detail for the authoritative station list; this tool is for drawing only. |
bus_get_line_realtime |
params_unexplained |
Canonical "when will my bus arrive" tool. Returns every bus currently on the line, with the nearest one carrying an ETA to the waiting stop. Important: the upstream predicts an ETA for only the nearest bus heading to your stop. Buses further up the route are returned (with position/speed/capacity) but their eta is null — that's not a bug. Args: - city_id (string, required): city identifier, e.g. '034' (Shanghai) — from bus_list_cities or the city_id used in bus_search. - line_id (string, required): the line's lineId from bus_search.lines[*].lineId. - target_order (string, required): the waiting stop's order on the line. Source: bus_get_line_detail.stations[].order, or bus_get_nearby_stops.stops[].lines[].targetOrder. - station_id (string, required): the waiting stop's sId. - lat / lng (string, required): WGS-84 — the user's location is best; if unavailable, use the waiting stop's wgsLat/wgsLng from bus_get_line_detail.stations[]. - response_format ('markdown' | 'json'). Returns JSON: line (name, direction, endSn — startSn is intentionally omitted by upstream), targetOrder, realData, buses[] (each with busId, position, speed, capacity, distanceToWaitStn; the nearest has eta {travelTime in seconds, arrivalTime as ms timestamp, displayTime as 'HH:MM'}), and a note. capacity: 0=light, 1=moderate, 2=crowded. |
bus_list_line_buses |
params_unexplained |
Returns the nearest bus heading to the anchor stop, with ETA and the bus's next stop name. This is narrower than the name suggests: despite the upstream endpoint being called "busList", it returns at most 1-2 buses (the imminent ones). For the FULL roster of every bus currently on the line, call bus_get_line_detail — its 'buses' array lists all live vehicles with positions. Use this tool for a quick "what's about to arrive" answer at a specific stop. Args: - city_id (string, required): city identifier, e.g. '034' (Shanghai) — from bus_list_cities or the city_id used in bus_search. - line_id (string, required): the line's lineId from bus_search.lines[*].lineId. - target_order (string, required): the waiting stop's order on the line. Source: bus_get_line_detail.stations[].order or bus_get_nearby_stops.stops[].lines[].targetOrder. - station_name (string, required): display name of that anchor stop. - response_format ('markdown' | 'json'). Returns JSON: targetOrder and buses[] — each with busId, licence, order, position, speed, capacity, nextStop (name of the bus's next stop), and eta {travelTime in seconds, arrivalTime as ms timestamp, displayTime 'HH:MM'}. |
bus_get_timetable |
params_unexplained |
Per-trip departure schedule for a line. The upstream returns one of three modes via 'mode': 'scheduled' (timeTableType=1) with an explicit 'timetable' array of trips; 'interval' (timeTableType=2) meaning the line runs at a fixed headway with NO per-trip times returned; 'special'/'unknown' (timeTableType=3 or other). Most lines in Shanghai are 'interval', so this tool is rarely the right one. To answer "first/last bus", "is the line still running", or "what's the next departure", call bus_get_line_detail instead — it always returns firstTime / lastTime / price / live buses. Args: - city_id (string, required): city identifier, e.g. '034' (Shanghai) — from bus_list_cities or the city_id used in bus_search. - line_id (string, required): the line's lineId from bus_search.lines[*].lineId (the entry is per-direction, so choose the direction you want). - line_no (string, required): rider-facing short name from bus_search.lines[].name (e.g. '71'). Do NOT pass the internal lineNo like 'r95817'. - direction ('0'|'1'): the line direction to look up, matching bus_search.directions[].direction. - response_format ('markdown' | 'json'). Returns JSON: line (name, direction, startSn, endSn), timeTableType, mode, timetable (array of trips or null), and a note when the line runs at a fixed interval. |
bus_refresh_lines |
params_unexplained |
Refresh realtime bus info for several (line, stop) pairs in one round-trip. Useful for a 'favourites' dashboard that needs a batch status update. Args: - city_id (string, required): city identifier, e.g. '034' (Shanghai) — from bus_list_cities or the city_id used in bus_search. - line_stn (string, required): semicolon-separated quadruples — format: lineId,stopId,nextId,targetOrder;lineId,stopId,nextId,targetOrder;... — where 'nextId' may be empty (kept as two consecutive commas). Example: '21283603183,021-15232,,2;21283604388,021-8685,,4'. - response_format ('markdown' | 'json'). Returns JSON: lines[] — each with a line sub-object (name, direction, endSn), a depDesc string, and buses[] (each with busId, order, capacity, distanceToDest, and eta {travelTime in seconds, arrivalTime as ms timestamp}). Soft cap: up to 10 quadruples per call. |
bus_plan_transit |
params_unexplained |
Plan a public-transit (bus + metro) route between two points. Returns alternative plans sorted by recommendation, each broken into walking and ride segments. Coordinate system: this tool expects GCJ-02. The easiest source is bus_search.pois[*].lat/lng — those are already GCJ-02 and carry a name. If you only have WGS-84 (e.g. from a phone GPS), convert it before calling. Args: - city_id (string, required): city identifier, e.g. '034' (Shanghai) — from bus_list_cities or the city_id used in bus_search. - origin_name, origin_lat, origin_lng (string, required): origin display name and its GCJ-02 coordinates. - dest_name, dest_lat, dest_lng (string, required): destination display name and its GCJ-02 coordinates. - strategy ('0'|'1'|'2'|'3'): 0=recommended (default; surfaces metro), 1=fewest transfers, 2=least walking, 3=shortest time (BUS-ONLY — upstream often drops metro plans here, so for general "fastest route" questions use 0). - response_format ('markdown' | 'json'). Returns JSON: origin and destination as 'lng,lat', distance, and plans[] — each with duration (seconds), walkingDistance, distance, tag (e.g. '直达'), transitCount, and segments[] of type walking or bus (bus segments carry name, lineType, departure/arrival stops, viaStops, duration, distance, startTime, endTime). lineType: 0=bus, 1=metro. Durations in seconds, distances in meters. A top-level 'note' is emitted when the response shape is suspicious, e.g. strategy=3 returned no metro plans despite a likely metro trip. |
Selection evidence
Confusable tool pairs.
10 pairs where similar names or overlapping descriptions may send an agent toward the wrong tool.
| Tool A | Tool B | Confidence | Why they collide |
|---|---|---|---|
bus_search |
bus_search_more |
medium | A task like 'find more lines/stops for 71路' could route to bus_search_more directly, while 'search for buses' maps to bus_search; since one is the pagination of the other, a 'get more results' request is genuinely ambiguous. |
bus_get_line_detail |
bus_get_line_route |
medium | 'Show me the route of line 71' is ambiguous between the map-drawing polyline tool (bus_get_line_route) and the full station-list tool (bus_get_line_detail), since both are plausibly 'the route'. |
bus_get_line_detail |
bus_get_timetable |
medium | A 'show the timetable for line 71' task matches bus_get_timetable by name, yet that tool is documented to return empty/interval for most lines and the correct answer comes from bus_get_line_detail; name-driven selection is a real trap. |
bus_get_line_realtime |
bus_get_timetable |
medium | 'What times does line 71 run / next departure?' could pull the agent toward bus_get_timetable by name instead of the documented realtime-or-detail answer, despite the tool's own warning that timetable rarely has data. |
bus_get_line_realtime |
bus_refresh_lines |
medium | 'Refresh/check the arrival time for line 71 at stop X' fits both tools: both return live bus ETAs and share line/stop arguments, so an agent may pick the batch refresher for a single-query arrival request. |
bus_list_line_buses |
bus_refresh_lines |
medium | 'What's the next bus at stop X on line 71?' fits both: bus_list_line_buses is the dedicated ETA tool but bus_refresh_lines is an easy over-general pick since it also returns per-stop live buses. |
bus_get_my_location |
bus_get_nearby_stops |
medium | For 'what bus stops are near me?', bus_get_my_location's description explicitly targets 'what's near me' while the actual answer needs nearby_stops' coordinates; the agent can select the location-resolver instead of the stop-lister, or misuse their order. |
bus_get_nearby_stops |
bus_get_stop_detail |
medium | 'Get details of the stops near my location' could select bus_get_stop_detail (full detail) or bus_get_nearby_stops (list with lines/realtime); stop_detail needs a physical_st_id the agent may not have, and nearby_stops is the realistic one-step answer. |
bus_get_stop_detail |
bus_get_line_detail |
low | Both are 'detail' tools, and a task that names an entity without its type (e.g. 'get full details for 71路' vs 'for 人民广场站') could route to the wrong one if the line/stop entity kind is misread; the inputs differ sharply so confusion is only marginal. |
bus_get_line_detail |
bus_get_line_realtime |
low | 'When is the next bus on line 71 coming' is clearly served by bus_get_line_realtime, but bus_get_line_detail also returns the current buses on the line, so a skimming agent could pick it; the descriptions do distinguish them, making this only mildly plausible. |
Compare the field