The way I debug changed before my tools did. A coding agent now reads the stack trace, the diff and the failing test — and then I hand it a Grafana link, which is a URL it cannot see, pointing at a search box it cannot use.
tracy starts from the other end: MCP is the primary read path, and the UI is the secondary one. That is not a feature bolted on. It changes what has to be stored.
Two questions, neither of which is "search"
During an incident nobody actually wants a text search. They want one of two things.
What happened to this request. You have a traceId, you want the whole chain: a tree of spans with durations, plus every log line of that request, across every service, in one call. Correlation travels as a standard W3C traceparent, so tracy interoperates with anything else that propagates it.
What happened to this order. Support never brings you a traceId. They bring an order_id. So a field can be marked as an entity key, and then you get the whole history of that entity — across services and across separate traces, including the parts whose log bodies were sampled away.
That second one is the reason the storage looks the way it does. Searching text cannot answer it, because the traces are unrelated to each other; only the entity ties them together.
Templates and data, separated at write time
A structured log line keeps the message template apart from the values. user {id} denied and the id are two different things, stored as two different things.
Do it at write time and you can count exact event frequencies later, group by template, and never confuse what the developer wrote with what a caller supplied. Do it at read time — by parsing the rendered string — and you are reverse-engineering your own format, wrongly, forever.
FTS5 with the trigram tokenizer covers actual text search, plus time-window queries across services, all inside the same binary.
One binary, one file
Kotlin/Native for linuxX64, linuxArm64 and macosArm64, with a JVM target for development. Ktor on CIO. SQLite through sqlx4k, with FTS5 and contentless_delete.
No JVM in production, no Elasticsearch, no log shipper. A KMP plugin sits inside your service and that is the whole client side.
Seven tools, and a budget
The MCP surface is seven read-only tools, and the hard part was not the tools.
An agent has a context window, and a log store can fill it with one query. So there is a hard context budget, a two-phase content release — metadata first, bodies only when asked for — and a static screen over untrusted text, because log content is attacker- influenced data being handed to a model.
Where it actually is
Running on a staging cluster with four services reporting into it, 293 tests, and an agent reading it over MCP.
Two things that number does not cover, stated because a status line listing only wins is not a status line. Those four services are quiet — a day of them is on the order of a hundred kilobytes — so the volume figures are still arithmetic rather than measurements, and the sampling behaviour has not been exercised at any interesting scale. And clock_skew_ms currently measures the age of a record on arrival rather than the difference between clocks, which a retry inflates.
Source: github.com/youndie/tracy