The tree was a debranded snapshot of upstream taken around 2026-02-20, and upstream has moved about five months since. This replaces it with upstream master, mechanically re-derived: bin/debrand does the rename, and the pipeline and squashed migrations are restored from the shipped tree. Not a merge. The previous import replaced the tree wholesale in a single commit, so there is no shared content history to merge against. Reproducing the rename on the new tree is what actually converges. What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints, compose files, LLM.md, and insights/migrations. The squashed initial migration is load-bearing — the live database records it by name, so upstream's ~918 individual migrations cannot come back. Nine products are dropped as unreferenced. Twenty-one more that we do not run are kept anyway because the core app still imports them; unpicking those is a refactor and does not belong in a rebase. This base does not build yet, and the reasons are not in the rename: upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against our 4.2, and the dependency manifests still name upstream packages that we republish under our own names. Those are decisions, not renames.
4.1 KiB
Get adaptive-interval bucket counts for a filtered log stream. Returns a flat list of {date_from, date_to, count} buckets covering the requested window. Modeled on Elasticsearch's auto_date_histogram — caller specifies a target bucket count, the engine picks the interval.
Use this to find where the volume is concentrated before pulling rows. Cheaper than query-logs, more agent-friendly than logs-sparkline-query (each bucket carries explicit date_from/date_to you can feed straight back as the next call's dateRange to drill in).
When to use this vs other tools
logs-count— total volume in a window. Scalar.logs-count-ranges(this tool) — when in the window the volume sits. Time-bucketed.query-logs— pull individual log rows. Most expensive; only call after counts confirm the window is right-sized.
Recursion pattern (the main reason this tool exists)
Use the response to narrow into a sub-range without reasoning about interval width:
- Call
logs-count-rangeswith the user's window (e.g. last 24h). - Pick the bucket(s) of interest (densest, an obvious spike, an unexpectedly empty stretch).
- Call
logs-count-rangesagain with that bucket'sdate_fromanddate_toas the nextdateRange. - Repeat up to ~3–4 levels — stop when buckets are shorter than your precision goal (e.g. 1 minute).
- Once narrowed, call
query-logsfor the actual rows.
This is the same pattern Elasticsearch users follow with auto_date_histogram. Keep recursion shallow — every call is cheap individually but they multiply quickly.
Parameters
All parameters must be nested inside a query object.
query.dateRange
Window to bucket. Defaults to the last hour (-1h). Same format as query-logs.
query.targetBuckets
Approximate bucket count. Defaults to 10, max 100. The engine picks the interval adaptively from a fixed list (1/5/10s, 1/2/5/10/15/30/60/120/240/360/720/1440m) to land near this target — actual count may differ slightly. Empty buckets are dropped, so the response can have fewer rows than targetBuckets.
Pick a value based on what you're doing:
- 10 (default) — overview, finding spikes, "is this concentrated or spread out?"
- 20–30 — characterising a known busy window
- 50+ — high-resolution drill-down, only when you know the window is small
query.severityLevels, query.serviceNames, query.searchTerm, query.filterGroup
Same shape as query-logs. Applied before bucketing.
Response
{
"ranges": [
{ "date_from": "2026-04-26T00:00:00Z", "date_to": "2026-04-26T02:24:00Z", "count": 1024 },
{ "date_from": "2026-04-26T02:24:00Z", "date_to": "2026-04-26T04:48:00Z", "count": 47 }
],
"interval": "2h"
}
ranges— buckets ordered bydate_fromascending. Empty buckets are omitted — infer gaps by comparing each bucket'sdate_toto the next bucket'sdate_from.interval— short-form duration of the chosen bucket width (1s/5m/1h/1d). Informational only — for follow-up queries, use the per-bucketdate_from/date_to.
Examples
Find when errors spiked over the last day
{
"query": {
"dateRange": { "date_from": "-1d" },
"targetBuckets": 24,
"serviceNames": ["api-gateway"],
"severityLevels": ["error", "fatal"]
}
}
Drill into the densest hour from a previous call
After picking the densest bucket from the response above (say {date_from: "2026-04-26T15:00:00Z", date_to: "2026-04-26T16:00:00Z", count: 894}):
{
"query": {
"dateRange": {
"date_from": "2026-04-26T15:00:00Z",
"date_to": "2026-04-26T16:00:00Z"
},
"targetBuckets": 12,
"serviceNames": ["api-gateway"],
"severityLevels": ["error", "fatal"]
}
}
Reminders
- Cap recursion at ~3–4 levels. If your bucket width drops below your precision goal (e.g. 1 minute), stop and call
query-logs. - Empty windows return
{"ranges": [], "interval": "..."}— that's not an error, it's "I asked, nothing matched." - Always include
serviceNamesor a resource attribute filter, just likequery-logs. Don't bucket the entire team's log stream.