const TOOLS: &str = r#"[
{"name":"query_records",
"description":"Search logs or spans. Returns matching records newest-first with all attributes merged in, plus the blocks and rows the scan touched. Terms are AND-ed. Use `attr` for OpenTelemetry attributes (service.name, http.route, k8s.pod.name) and `field` for columns of the record itself (severity_text, severity_number, body, name, duration_nano, status_code, trace_id, span_id). Attributes are searched at all three levels - record, resource and scope - so you do not need to know where the SDK put them. Time bounds default to the last hour; widening them costs blocks scanned. `rows_matched` above `limit` means you are seeing a page: narrow the filter, or page with `after`.",
"inputSchema":{"type":"object","properties":{
"signal":{"type":"string","enum":["logs","traces"],"description":"default logs"},
"from":{"type":"string","description":"'-15m', 'now', or absolute nanoseconds. Default -1h."},
"to":{"type":"string","description":"same forms. Default now."},
"where":{"type":"array","description":"AND-ed terms, each {attr|field: <name>, <op>: <value>} where op is one of eq ne lt lte gt gte contains","items":{"type":"object"}},
"limit":{"type":"integer","description":"default 100, max 10000"},
"after":{"type":"string","description":"the `next` value from a previous response, verbatim, to continue where it stopped. Absent `next` means that was the last page. There is no offset: a store still being written to shifts under one."}}}},
{"name":"get_trace",
"description":"Every span of one trace, by trace id, over all of retention. Prefer this to query_records with a trace_id filter: blocks carry a trace-id index, and this is the call that uses it. Returns spans newest-first; parent_span_id links them into the tree.",
"inputSchema":{"type":"object","required":["trace_id"],"properties":{
"trace_id":{"type":"string","description":"32 hex characters, as returned in any span or log record"},
"limit":{"type":"integer","description":"default 1000, max 10000"}}}},
{"name":"query_metric",
"description":"One metric as time series, grouped by attribute set. Each series carries its identifying attributes, its `temporality` as the OTLP enum (0 unspecified, 1 delta, 2 cumulative), whether it is `monotonic`, and its points. Points are the values as stored: no step, no aggregation and no rate, so a cumulative sum is the running counter and a per-second rate is yours to derive by subtracting consecutive points and dividing by the gap between their timestamps. Omit `name` at your peril - it scans every metric in the window.",
"inputSchema":{"type":"object","properties":{
"name":{"type":"string","description":"exact metric name, from list_metrics"},
"from":{"type":"string"},"to":{"type":"string"},
"where":{"type":"array","description":"same term grammar as query_records","items":{"type":"object"}},
"max_series":{"type":"integer","description":"default 200"},
"max_points":{"type":"integer","description":"default 5000"}}}},
{"name":"list_metrics",
"description":"Metric names present in a time window, with unit and kind. Call this before query_metric rather than guessing a name.",
"inputSchema":{"type":"object","properties":{
"from":{"type":"string"},"to":{"type":"string"}}}},
{"name":"correlate",
"description":"The frame around what a search matched: the time window, the traces those records belong to, and the services that took part. This is the tool for 'what else was happening', and it replaces the usual three round trips of query, read a trace id, query again. `expand` is an ordered walk, and the order matters: 'traces' widens the window to the real start and end of the traces found, which you almost always want first because a log line is written after the request it describes; 'peers' then adds every service that appears in those traces; 'around:<duration>' pads the window by hand. Every field of the answer is an input to another call - feed a trace to get_trace, a service name to query_records as {attr: service.name, eq: <name>}, and from/to to anything. `truncated` true means the frame hit its cap and is a sample, so narrow the search before drawing conclusions.",
"inputSchema":{"type":"object","properties":{
"signal":{"type":"string","enum":["logs","traces"],"description":"what to anchor on, default logs"},
"from":{"type":"string"},"to":{"type":"string"},
"where":{"type":"array","description":"same term grammar as query_records","items":{"type":"object"}},
"expand":{"type":"array","description":"ordered steps, each one of: traces, peers, around:<duration> such as around:30s","items":{"type":"string"}}}}},
{"name":"service_map",
"description":"Who calls whom in a time window, computed from parent_span_id at read time. Nodes are services with their span and error counts; edges carry calls, errors, average and max duration in nanoseconds. The edge from \"entry\" is traffic arriving from outside the traced system. `unresolved` counts spans whose parent was not in the sample - if it is a large fraction of the spans, raise max_spans or narrow the window before trusting a thin edge. Use this to find the failing dependency before querying its records.",
"inputSchema":{"type":"object","properties":{
"from":{"type":"string"},"to":{"type":"string"},
"max_spans":{"type":"integer","description":"span budget, default 1000000. Newest blocks are read first, so a small budget is a recent sample, not a truncated one."}}}},
{"name":"list_services",
"description":"Every service that emitted anything in a time window, with the stable entity key Mira identifies it by. Call this before filtering on service.name rather than guessing at the spelling. Two entries with the same name and different keys are two distinct instances or deployments.",
"inputSchema":{"type":"object","properties":{
"from":{"type":"string"},"to":{"type":"string"}}}},
{"name":"list_alerts",
"description":"Every alerting rule this node evaluates and what it is currently doing: state is one of ok, pending (breaching but has not held for `for_nano` yet) and firing. `value` is the last evaluation, `threshold` and `op` are what it is compared against, and `matched`/`total` are the record counts behind it - a ratio rule counts `matched` of `total`, a count rule counts `matched`. `link` opens the same records in Mira's UI. An empty list means this node has no rules file, not that everything is healthy. `error` non-null means the rule could not be evaluated, which is not the same as not firing. Rules are static KYAML in a file, so this tool reads and never writes.",
"inputSchema":{"type":"object","properties":{}}}
]"#;Expand description
Tool definitions, verbatim.
Written by hand rather than generated, because this text is the prompt. A model chooses a tool and fills its arguments from these descriptions alone, so what belongs here is the shape of the data and the mistake to avoid — not a restatement of the parameter names.