Releases: Netis/heron
Releases · Netis/heron
Release list
Heron v0.7.1
[0.7.1] — 2026-06-24
Fixed
- Console blank-page regression from the v0.7.0 OTel rename. The traces API
serializesspan_ids(renamed fromcall_ids), but theAgentTurnDetail
type and theagent-breakdowncomponent still readcall_ids, so opening an
agent trace detail threwTypeError: undefined is not an object (evaluating '…call_ids.length')and — with no React error boundary — blanked the entire
console. Aligned the type + component tospan_idsand guarded the access so
a missing field degrades to "0 calls" instead of a white screen.
Changed
- UI vocabulary aligned with the OTel model. Renamed user-visible labels:
"Agent Turns" → "Agent Traces" and "HTTP Exchanges" → "HTTP Logs" (nav, detail
titles, the overview gauge, the "Trace ID" metadata row, empty/error states,
the SFT export tooltip, and the distribution chart). A trace is one agent
interaction; a step within it remains a turn/span. Backend metric names, route
paths, and code identifiers are unchanged.
What's Changed
- v0.7.1: fix console blank page (span_ids) + UI rename (Agent Traces / HTTP Logs) by @vaderyang in #177
Full Changelog: v0.7.0...v0.7.1
Heron v0.7.0
[0.7.0] — 2026-06-24
Added
- eBPF capture discoverability — an experimental, opt-in advisory. When no
eBPF source is active, the server now logs a one-line notice (Linux only) that
on-host TLS capture exists and how to enable it (ebpfcargo feature + a
type = "ebpf"source — there is no--ebpfruntime flag). The Settings
page's eBPF capture toggle is now tagged experimental and its description
spells out the opt-in requirements.
Changed
- OpenTelemetry-aligned rename of the storage entities and HTTP API. The
agent_turnstable is nowtraces,llm_callsis nowspans, and
traces.call_idsis nowspan_ids; a new forward-lookingspans.kind
column (always'llm'today) leaves room for wire-visible tool spans. The
Rust domain/query types (AgentTurn→Trace,Turn*/Call*DTOs →
Trace*/Span*) andStorageBackendmethods (write_calls→write_spans,
query_turns→query_traces, …) follow suit. New canonical routes
/api/traces*and/api/spans*; the pre-rename/api/agent-turns*and
/api/llm-calls*keep working as deprecated aliases (RFC 8594Deprecation
header). Retention config keyscalls/turnsare accepted as serde aliases
forspans/traces. Existing DuckDB/ClickHouse databases auto-migrate in
place on init() with no data loss (idempotent detect-then-rename).
Docs
- README rebuilt for the launch — GIF-first, conversion-driven layout.
Headline tagline "The Wireshark for AI Agents", a hero demo GIF up top, a
30-second pcap-replay quick start, and a three-up "What Makes Heron Different"
table (agent-turn reconstruction · service topology · SFT trajectory export),
while preserving the existing technical accuracy (passive positioning across
wire and on-host TLS boundary, opt-in eBPF,docs/configure.mdlinks,
just-based contributor flow). API examples updated to the canonical
/api/tracesroutes. - Corrected launch collateral added under
launch/. Paste-ready Product
Hunt / Hacker News / Twitter copy aligned to the shipped product: install via
thecurl … install.shone-liner (no npm package), canonical/api/traces
endpoint, and v0.7.0 framing.
What's Changed
- ci: repoint agent-loop wrappers to Netis/olympus (agent-ops renamed) by @vaderyang in #165
- Upgrade Olympus mechanism to v0.4.0 (full post-rename migration: .olympus.json + olympus_ref) by @vaderyang in #167
- fix(turn): keep eBPF-sourced turns whose opening call was missed by @vaderyang in #168
- fix(ebpf): detach uprobe links for dead inodes to stop bpf_link fd leak by @vaderyang in #172
- chore(olympus): align .olympus.json — current model + Greek-deity agent names by @vaderyang in #173
- refactor: OpenTelemetry-aligned rename (agent_turns→traces, llm_calls→spans) by @vaderyang in #174
- Launch v0.7.0 → main: README rebuild + collateral + eBPF advisory by @vaderyang in #176
Full Changelog: v0.6.0...v0.7.0
Heron v0.6.0
[0.6.0] — 2026-06-16
Changed
- eBPF on-host SSL-uprobe capture is now a first-class, soak-gated
capability (experimental since 0.5.1). It lifts the plaintext of
TLS-encrypted LLM calls directly at the in-processSSL_read/SSL_write
boundary — covering dynamically-linked OpenSSL/BoringSSL (Pythonopenai/
anthropicSDKs, curl, Node, most CLIs) and statically-linked, symbol-stripped
BoringSSL single-executable runtimes (Claude Code's / opencode's Bun binaries,
located by byte-signature offset) — and stamps every call with its owning
process (pid · command · executable). A new stagingebpf-soakgate replays
real TLS traffic through the freshly deployed binary and asserts the uprobe
attaches, traffic is captured, and a process-attributedLlmCallis parsed and
persisted end-to-end; both prod promotion and release now require a passing
ebpf-soakedstatus alongsidestaging-soaked, so on-host capture can no
longer silently regress into prod or a cut release.
Fixed
- eBPF SSL uprobes attach under the non-root staging service. The staging
unit granted onlyCAP_BPF+CAP_PERFMON, but the kernel gates uprobe
perf_event_open(perf_uprobe_init) onCAP_SYS_ADMINspecifically — which
those caps don't cover andperf_event_paranoiddoesn't relax. TheSSL_write
uprobe attach therefore failed withperf_event_open failed, and because a
failed capture source is non-fatal (the co-located packet tap keeps the
pipeline healthy) the symptom was a silentebpf_uprobes_attached = 0rather
than a crash — which is why theebpf-soakgate had never passed. The
committed staging unit now carriesCAP_SYS_ADMIN(matching what the prod
deploy already injects), so the gate goes green and on-host capture works under
a non-root service.
Docs
- README repositioned from "network wire" to passive capture. The headline
"Agent observability from the network wire" became inaccurate once on-host eBPF
landed — those bytes never touch the wire (on a client they're the
pre-encryption plaintext). It now leads with the durable differentiator
(passive; no SDK, sidecar, or proxy; never in the request path) and names both
capture surfaces: off the network wire, or lifted from the host's TLS boundary
by eBPF.
What's Changed
- ci(ebpf): staging eBPF verification gate before prod by @vaderyang in #148
- fix(ebpf): follow inode rotation & reach already-running sessions (v0.5.4) by @vaderyang in #157
- fix(wire/anthropic): don't record control-plane telemetry as model=unknown LLM calls (v0.5.5) by @vaderyang in #158
- fix(agents/claude-cli): drop security-monitor sidecar from agent-turns (v0.5.6) by @vaderyang in #159
- docs(readme): elevate eBPF on-host capture to a first-class capability by @vaderyang in #160
- fix(ebpf): grant CAP_SYS_ADMIN to staging unit so SSL uprobes attach by @vaderyang in #161
- docs(readme): reposition from "network wire" to passive (wire or eBPF) by @vaderyang in #162
- release: v0.6.0 — eBPF on-host capture promoted to first-class (soak-gated) by @vaderyang in #163
Full Changelog: v0.5.3...v0.6.0
Heron v0.5.3
[0.5.3] — 2026-06-15
Fixed
- eBPF capture of large and multi-connection TLS traffic. A big
SSL_*
buffer is split into several ring-buffer events; the synthesizer used to append
each at a running sequence counter, so a silently-dropped or reordered chunk
shifted every later byte and spliced the next request's bytes into the previous
body (invalid JSON → no captured call). The BPF program now stamps each event
with its absolute per-(connection, direction)stream offset and the
synthesizer places every chunk at that offset, so a dropped chunk leaves a gap
at its true position instead. Connections also finalize properly now — an
SSL_freeuprobe (dynamic libssl) and anSSL_read-returns-0 EOF (static
targets) emit a close, and a per-connection generation folded into the
synthetic tuple gives a reusedSSL*pointer a fresh, non-overlapping flow
key. - Claude Code conversations form agent-turns. The Anthropic
mid-conversation-systemfeature appends a trailingrole=systemnotice after
the user's prompt, so the messages array often ends withsystemon a fresh
turn.is_user_turn_start/extract_user_inputnow skip trailing system
messages and evaluate the last non-system one, so fresh turns are recognized
(and their prompt preview populated) instead of being discarded as
"no user start". - In-progress turn timestamps rendered as the year ~58423. Active-turn
registry rows emittedstart_time/end_timein microseconds, but the field is
milliseconds (the DB path returnsepoch_ms, the console renders with
new Date(ms)). A µs value read as ms is 1000× too far in the future. Both
active-registry conversions now divide to milliseconds, matching finalized
turns. Added a timestamp-unit test suite across the eBPF clock and the API
boundary.
What's Changed
- feat(prod): build the eBPF-enabled binary in deploy-prod by @vaderyang in #150
- fix(prod): raise container fd limit for the eBPF build by @vaderyang in #151
- feat(console): eBPF capture status card on pipeline-health by @vaderyang in #152
- fix(ebpf): correct large/multi-connection capture + form Claude Code agent-turns by @vaderyang in #155
- release: v0.5.3 — eBPF capture correctness + timestamp fix & test suite by @vaderyang in #156
Full Changelog: v0.5.2...v0.5.3
Heron v0.5.2
[0.5.2] — 2026-06-11
Added
- eBPF capture is now a managed Settings source. The console's pipeline
Settings lists, adds, edits, and removes the on-host eBPF SSL-uprobe source
like any other ingress, with an availability guard: a binary built without the
ebpffeature reportsebpf_available = falseon/api/runtime-config, and
the API rejects an eBPF source on such a build so a stray config can't wedge
the next boot. (The capture engine itself shipped in 0.5.1; this makes it
operable from the UI.) - eBPF metrics in pipeline-health. A new
ebpfmetric group surfaces the
capture path's health — uprobes attached, events received / dropped, bytes
captured, frames synthesized, active connections, and process-cache size — in
the debug pipeline-health view and on/api/internal-metrics.
Changed
- Console theme aligned to the Kami parchment design system. Replaced the
too-dark palette with the canonical parchment surfaces (paper canvas, lifted
ivory, ink-blue accent), matching the heron-ai.pages.dev landing site.
Docs
- Refreshed the README screenshots in the corrected Kami theme.
What's Changed
- feat(ebpf): Settings enable/disable toggle + pipeline-health metrics by @vaderyang in #147
- fix(console): align Kami theme to the parchment design system by @vaderyang in #145
- docs: refresh README screenshots in the corrected Kami theme by @vaderyang in #146
- release: v0.5.2 — eBPF Settings source + metrics, Kami theme by @vaderyang in #149
Full Changelog: v0.5.1...v0.5.2
Heron v0.5.1
[0.5.1] — 2026-06-11
Added — eBPF on-host TLS capture (experimental, Linux)
- New
ebpfcapture source: a fourth ingress alongside the packet taps
(pcap/pcap-file/cloud-probe). It attaches uprobes to the target's
SSL_read/SSL_writeand reads plaintext at the in-process TLS boundary —
so Heron can observe TLS-encrypted LLM calls on the host that makes them,
with no proxy, TLS terminator, or MITM, and nothing on the request path.
Plaintext chunks are dressed as synthetic Ethernet/IP/TCP frames
(FlowSynthesizer) and fed through the existing dispatcher → reassembler →
HTTP/SSE parser → wire-API decoder → turn tracker unchanged. - Process attribution. Every eBPF-captured call carries its owning process
(pid·comm· resolved executable), threaded end-to-end through
RawPacket→ParsedPacket→TcpFlow→LlmCallinto the
process_pid/process_comm/process_exestorage columns (DuckDB Phase-7
migration + ClickHouse mirror) and surfaced in the console's LLM-calls list and
call detail. Packet-tap sources leave it null. - Target coverage. Dynamically-linked OpenSSL/BoringSSL by exported symbol
(Python SDKs, curl, …), and statically-linked, symbol-stripped BoringSSL —
e.g. Claude Code's Bun runtime — located by byte-signature → ELF file offset →
offset uprobe. A built-inflavor = "bun"ships read-anchored prologue
signatures, so stock Bun / Claude Code works with zero manual derivation. - Linux-only and off by default: built behind the non-default
ebpfcargo
feature onh-capture(absent from prebuilt release binaries). Needs
CAP_BPF+CAP_PERFMON(kernel ≥ 5.8) or root, plus kernel BTF;heron doctorreports acapture.ebpfcheck. HTTP/1.x only, like every source. See
docs/design/02-capture.mdanddocs/design/03-ebpf-static-targets.md.
Added
- SFT trajectory export from reconstructed agent turns and sessions:
OpenAI-stylemessagesJSONL with tool calls, tool results, and assistant
reasoning preserved and tool-call arguments rehydrated to objects. Export a
single turn/session from its detail view, or batch-export the current Agent
Turns filter as one-line-per-turn JSONL (Anthropic + OpenAI-chat wire formats;
unsupported formats reported and skipped). - Three-theme console, switchable from the sidebar and persisted per browser:
Kami (warm washi-paper, the new default), Dark, and Light —
charts, topology graph, and timeline gantt all re-theme.
Fixed
- ClickHouse SQL literal escaping (dialect-aware): a backslash in a
dimension-filter value could break out of the quoted literal in the ClickHouse
backend. Escaping is now dialect-aware across both backends.
Security / CI
- Self-hosted CI runners are gated to same-repo PRs, closing fork-PR code
execution; a release may only be cut from a commit that passedstaging-soak,
and prod deploys are gated on the load soak.
What's Changed
- test(chaos): fault injection under sustained concurrent write load (PR3) by @vaderyang in #121
- feat(test): pcap regression corpus framework (scrub + manifest + golden, git-LFS) by @vaderyang in #120
- feat(staging): nightly longevity soak — the 102 GB endurance catcher (PR4) by @vaderyang in #122
- ci(perf-gates): enforce load soak before prod + require staging-soaked before release by @vaderyang in #123
- docs: quality/release pipeline + v0.5.0 refresh (ClickHouse, perf gates) by @vaderyang in #124
- feat(test): cliproxy mixed-format (#96) — precise OpenAI-shape + Anthropic-usage cells by @vaderyang in #125
- feat(triage): warm maintainer-voice replies on every verdict; reproduce before auto-queuing by @vaderyang in #126
- dogfood: delegate triage to the agent-ops reusable workflow by @vaderyang in #127
- dogfood: delegate issue-implement (wiwi) to agent-ops@v0.1.0 by @vaderyang in #129
- dogfood: delegate pr-review + pr-revise to agent-ops@v0.2.0 by @vaderyang in #131
- dogfood: delegate leakage guard + bump triage/implement to agent-ops@v0.2.0 by @vaderyang in #132
- dogfood: remove in-tree agent-bot fallbacks (all surfaces delegated + proven) by @vaderyang in #133
- feat(console): three-theme redesign — Dark Observatory, Light Atelier, Kami by @vaderyang in #134
- feat(export): SFT trajectory export from agent turns/sessions by @vaderyang in #135
- fix(staging-soak): measure steady-state RSS growth, not warm-up by @vaderyang in #136
- chore(pr-review): bump agent-ops pin to v0.3.1 (diff-injection review fix) by @vaderyang in #137
- fix(deploy-prod): rebuild console bundle before cargo build (stale embedded UI) by @vaderyang in #138
- fix(deploy-prod): resolve bun via ~/.bun/bin fallback (non-login deploy shell) by @vaderyang in #139
- feat(console): default to Kami theme + refresh README screenshots in Kami by @vaderyang in #140
- fix(ci): gate self-hosted CI to same-repo PRs by @vaderyang in #143
- fix(storage): dialect-aware SQL literal escaping for the ClickHouse backend by @vaderyang in #142
- feat(capture): native eBPF SSL-uprobe capture (process attribution + static-binary targets) by @vaderyang in #141
- release: v0.5.1 — eBPF capture docs + VERSION/CHANGELOG by @vaderyang in #144
Full Changelog: v0.5.0...v0.5.1
Heron v0.5.0
[0.5.0] — 2026-06-04
Added — ClickHouse storage backend
- New
h-storage-clickhousecrate: a drop-inStorageBackendimplementation
backed by ClickHouse (HTTP interface, async serde RowBinary via the
clickhousecrate), selected withstorage.backend = "clickhouse". Mirrors
the DuckDB backend's full read + write surface. Fact tables useMergeTree;
agent_turnsusesReplacingMergeTree(_version)(FINAL reads; version-bumped
full-row re-insert forupdate_turn_metadata). Timestamps are
DateTime64(6, 'UTC')mapped toi64micros. Retention runs via lightweight
DELETEon the shared[storage.retention]schedule. - Backend-neutral logic (dimension-filter SQL builders, header/token-estimate
converters, the serving-software classifier) extracted from
h-storage-duckdbintoh-storage(dialect/convert/classify) and
shared by both backends — single source of truth. storage_benchbinary +scripts/bench-storage.sh(just bench-storage):
ClickHouse-vs-DuckDB write-throughput + read-latency comparison through the
identical workload. Findings + methodology in
docs/design/bench-clickhouse-vs-duckdb.md.- ClickHouse
query_servicesoptimised 46× (5129 ms → 112 ms at 1M rows): the
body-sampleROW_NUMBERwindow (which read every row's ~2 KB body columns)
became anid IN (… LIMIT N BY …)two-phase fetch;arrayDistinct(groupArray)
→groupUniqArray(N);quantileExact→quantileTDigest.
Fixed
- Agent-sessions
agent_kindmulti-select returned nothing. Selecting more
than one agent kind sent a CSV (claude-cli,codex-cli) that the sessions
query exact-matched as a single literal, so the list went empty; selecting one
kind worked. Fixed in both the DuckDB and ClickHouse backends. Root-caused
beyond the symptom:agent_kindis now CSV-parsed once at the API boundary
into aVec(like every other multi-select filter), so no storage backend
ever sees a raw CSV to mis-handle. - Agent classifier no longer flags unrecognized tool names as "suspicious".
Tools from non-Claude-Code agents (e.g.web_search,read_file,
memory_get) were tagged suspicious purely for being absent from a hardcoded
registry. Any named tool is now classified as a function-call surface; the
registry treadmill is gone.
Internal
- Perf/reliability gate for the release pipeline: a
rate_pps-throttled
pcap-file load-soak (tara --load) and criterion hot-path micro-benchmarks. - Deploy pipeline hardening: staging-soak stamps its
staging-soakedstatus via
the REST API (the runner has nogh), anddeploy-prodsupersedes older
waiting approvals instead of letting them pile up and wedge the queue.
What's Changed
- chore(rebrand): scrub residual TokenScope brand strings → Heron by @vaderyang in #72
- fix(storage): Phase-5 migration adds agent columns nullable (DuckDB compat) by @vaderyang in #76
- ci: move runner label tokenscope → heron by @vaderyang in #73
- pr-review: post_review.py downgrades APPROVE → COMMENT on heading-format drift by @vaderyang in #77
- check-leakage: also flag machine-specific username paths by @vaderyang in #78
- heron panics on shutdown: JoinHandle polled after completion (supervisor double-poll, exit 101) by @vaderyang in #80
- fix(llm): register AgentClassifier{Unknown,Mixed}Count on llm worker — stops silent capture loss (#81) by @vaderyang in #82
- docs: launch assets — README badges, technical blog, launch posts by @vaderyang in #87
- docs: warn that a bare cargo build ships a blank console (--features console) by @vaderyang in #83
- feat(staging): continuous auto-deploy to heron-stage VM (Quality Infra L6) by @vaderyang in #84
- ci(staging): add workflow_dispatch manual deploy to deploy-staging by @vaderyang in #89
- ci: bump test-job timeout 25→60m for the on-main artifact build by @vaderyang in #90
- chore(deps): bump DuckDB 1.5.1 → 1.5.3 (fixes checkpoint/ART bug behind the prod outage) by @vaderyang in #92
- feat(agent-bot): mara — prod observer that auto-files heron incidents by @vaderyang in #86
- fix(classifier): expand known-tool registry to current CLI inventory (#85) by @vaderyang in #93
- feat(h-llm): cap stored request/response bodies for 1M-token contexts (#68) by @vaderyang in #95
- feat(staging): tara soak runner — replay-invariant gate after deploy (L7) by @vaderyang in #94
- feat(prod): gated auto-deploy to production (deploy-prod — quality-chain tail) by @vaderyang in #97
- docs: add issue-filing guide + triage-gate explainer by @vaderyang in #98
- fix(classifier): unrecognized tool names are function calls, not "suspicious" (#85) by @vaderyang in #101
- feat(capture): pcap-file loop/duration replay mode (load-soak primitive) by @vaderyang in #99
- docs: scrub internal deployment specifics from pr-review-agent doc by @vaderyang in #100
- feat(mara): confirm-debounce — a deploy/restart blip no longer files a phantom incident by @vaderyang in #104
- chore(ci): run CI on dedicated
ci-poolrunner, off the agent pool by @vaderyang in #112 - feat(perf): tara load + memory soak — the pre-deploy perf/reliability gate (PR1) by @vaderyang in #106
- CI hygiene (§5): scrub internal CI backend model identifier from workflow + script comments (follow-up to #100) by @vaderyang in #109
- feat(storage): ClickHouse backend (drop-in) + storage benchmark by @vaderyang in #103
- CLIproxy 混合格式问题:OpenAI 请求 → Anthropic 响应,usage token 解析不到 by @vaderyang in #107
- feat(agent-bot): wiwi auto-revises on vivi CHANGES_REQUESTED (bounded loop) by @vaderyang in #113
- feat(perf): criterion hot-path micro-benchmarks + CI bitrot gate (PR2) by @vaderyang in #110
- chore(agent-bot): move auto-merge allowlists out of committed source by @vaderyang in #102
- ci(staging): stamp
staging-soakedcommit status — prod go/no-go signal by @vaderyang in #114 - agent-sessions: agent-kind 过滤把整段 CSV 当单值精确匹配,选中后列表清空 by @vaderyang in #111
- fix(staging-soak): stamp staging-soaked status with curl, not gh — unblocks ALL prod deploys by @vaderyang in #115
- refactor(filters): parse agent_kind CSV at the API layer (Vec), not in storage — root-cause fix by @vaderyang in #116
- fix(deploy-prod): supersede older waiting runs (cancel-in-progress: true) — kills the pile-up wedge by @vaderyang in #117
- chore(release): v0.5.0 by @vaderyang in #119
Full Changelog: v0.4.0...v0.5.0
Heron v0.4.0
[0.4.0] — 2026-05-29
Changed — Rebrand to Heron
- Project renamed TokenScope → Heron. The binary is now
heron
(wastokenscope); the 10 internal library crates moved from the
ts-*prefix toh-*; the GitHub repo is nowNetis/heron(the old
URL redirects). Console title, logo (a new heron mark), and all
install/docs URLs updated.
Added — Quality infrastructure
- Deterministic fault-injection harness for the DuckDB backend
(feature-gated) plus recovery tests that drive the FATAL → reopen →
every-surface-works path without relying on real load pressure. - Schema-migration tests over synthesized legacy DB shapes, locking the
auto-migration paths against silent regression. - CI lint gates: referenced-secret provisioning, secret-value sanity,
validated-constructor scoping, and an infra-leakage gate that fails on
any non-allow-listed private IP or private-key block in tracked files.
Security / privacy
- Removed the demo deploy tooling, which hard-coded a server address, a
jump-host username, and a plaintext password. Demo setup is now an
AI-agent prompt in the docs instead. - Scrubbed internal infrastructure identity (private IPs, hostnames)
from source comments, docs, scripts, and test fixtures; tests now use
RFC5737 documentation ranges.
Added — Agent-era observer (H002)
- Agent traffic classification: every LlmCall carries
is_agent_request,
tool_surface,agent_topology,tool_call_count,tool_names. Every
AgentTurn rolls uptool_surfaces,tool_call_total,agent_topology,
suspicious_skills. Newtool_surfacedimension onllm_metrics. - Console: agent-aware columns and filters on Agent Turns; Agent breakdown
section on turn detail; tool-surface facet on Performance. - Config:
[agent_classifier]block indefault.tomlfor tool taxonomy. - Internal metrics:
agent_classifier.unknown_count,
classifier_mixed_count.
Capture
- Default live-capture configuration now covers common LLM-serving ports,
reducing the need for explicit CLI capture filters in quickstart flows.
LLM wire-API support
- OpenAI Chat streaming now captures
delta.reasoning_contentand
delta.reasoning, with console rendering before normal content. - OpenCode agent profile detection added for clients that expose a stable
x-session-affinityanchor.
Agent turn tracking
- Generic fallback turn grouping now requires a tool/function-call anchor, so
text-only SDK calls stay on the LLM Calls page instead of producing
synthetic one-call Agent Turns.
Metrics
- TTFT handling now distinguishes streaming and non-streaming calls, with
stream-only TTFT charts and backfilled rollups from stored call data. - Dashboard active-resource history added for TCP connections and agent
turns. - Long-range chart axes use date-aware labels for multi-day windows.
Console
- Settings page added for capture sources, including interface discovery,
source editing, grouped source-type controls, and restart flow. - LLM Calls gained stream/non-stream filtering.
- List pages persist the selected item in the URL.
- Agent-kind filter options are derived from observed data in the active
window instead of a fixed list.
API
GET /api/capture/interfaceslists available capture interfaces.PUT /api/capture/sourcesupdates capture-source configuration and
restarts the process when needed.
Documentation
- README reframed around agent observability with refreshed screenshots.
- README quickstart now uses the default live-capture command and no longer
includes an explicit capture-filter example. - Removed the LLM call detail screenshot and its README reference.
- Removed project-origin/company copy from public docs.
Development
- Headless PR review workflow added for CI.
- Repository instructions now require PR text to scrub private environment
details before publication.
What's Changed
- feat(settings): in-app capture configuration + self-restart by @vaderyang in #12
- feat(dashboard): TTFT stream/non-stream split + rollup backfill + filters by @vaderyang in #13
- feat(dashboard): Active TCP Connections + Active Agent Turns charts by @vaderyang in #14
- fix(ts-llm/chat-sse): capture delta.reasoning_content + delta.reasoning by @vaderyang in #15
- feat(filters): dynamic agent_kind dropdown from actual data in window by @vaderyang in #16
- feat(ts-llm/agents): opencode profile keyed off x-session-affinity by @vaderyang in #17
- feat(console/list-pages): persist selected item id in URL by @vaderyang in #18
- feat(ci): headless PR review agent (phase 1) by @vaderyang in #28
- docs(README): reframe as agent observability + Playwright-driven screenshots by @vaderyang in #30
- Clean up README capture docs by @vaderyang in #32
- fix(pr-review): :4000 LiteLLM + auth secret + NO_PROXY + auto-merge trusted PRs by @vaderyang in #31
- fix(pr-review): drop dup no_proxy env key by @vaderyang in #33
- fix(pr-review): override inherited http_proxy so curl reaches LiteLLM by @vaderyang in #34
- fix(pr-review): keep http_proxy; only set no_proxy by @vaderyang in #35
- fix(pr-review): overlay reviewer scripts from default branch by @vaderyang in #36
- fix(pr-review): never APPROVE on agent failure / unstructured body by @vaderyang in #37
- fix(pr-review): feed prompt via stdin to claude --print by @vaderyang in #38
- feat(console): selected_at anchor recovers item window on stale shared link by @vaderyang in #19
- fix(pr-review): brand as "vivi"; never post failures to PR by @vaderyang in #39
- feat(console): TPS instead of TPOT, agent-turns column reorder, sidebar logo by @vaderyang in #20
- ci: opt all JS actions into Node.js 24 runtime by @vaderyang in #40
- feat(console/charts): x-axis tick adapts to time-range duration by @vaderyang in #21
- fix(console/agent-sessions): keep prior data during refetch (no flash) by @vaderyang in #24
- feat(api): lite mode for /calls — unblock mega-turn detail page by @vaderyang in #23
- feat: fold llmproxy duplicate turns by passive pair detection by @vaderyang in #22
- feat(services): Path view + Overview agent charts (deploy roll-up) by @vaderyang in #27
- Fix dynamic agent kind filters by @vaderyang in #29
- Fix generic agent turn grouping by @vaderyang in #41
- Revise unreleased changelog by @vaderyang in #42
- Update repository agent guidance by @vaderyang in #43
- Add local agent workflow skills by @vaderyang in #44
- Add agent-bot: issue triage + wiwi dev agent + auto-merge by @vaderyang in #45
- post_review: use ADMIN_GH_TOKEN for the admin-merge fallback by @vaderyang in #46
- Reject out-of-range time params; clamp SQL timestamp formatter by @vaderyang in #47
- pair_sweeper: CHECKPOINT after each batch + reopen on FATAL by @vaderyang in #48
- agent-bot: use AGENT_GH_TOKEN for the agent:try label fan-out by @vaderyang in #51
- ci: P0 lint gates for secret-ref + validated-constructor classes by @vaderyang in #53
- DuckDB FATAL recovery: rebuild every connection (closes #50) by @vaderyang in #52
- wiwi: drop PAT from actions/checkout; embed in push URL instead by @vaderyang in #54
- wiwi: bump timeout to 120m + stream claude output live by @vaderyang in #55
- wiwi: clear extraheader before push so PAT (workflow scope) wins by @vaderyang in #56
- ci: lint secret VALUES, not just references (catches the '-' bug) by @vaderyang in #57
- wiwi: enforce commit + add auto-commit fallback by @vaderyang in #58
- Rebrand → Heron (Phase 1: rename app binary tokenscope → heron) by @vaderyang in #59
- agent-bot: tolerate transient LiteLLM outages — pre-flight wait + mid-run retry by @vaderyang in #63
- ci: deterministic recovery + schema-migration tests (P1 quality infra) by @vaderyang in #64
- ci(pr-review): bump vivi agent + job timeout to 7200s/120min by @vaderyang in #67
- Rebrand → Heron Phase 2: rename ts-* library crates → h-* by @vaderyang in #66
- chore(rebrand+privacy): repo→heron, scrub infra leakage, leakage gate, heron logo by @vaderyang in #69
- fix(privacy): scrub internal IP left in test fixture (lost in #69 race) by @vaderyang in #70
- chore(release): 0.4.0 — Heron rebrand + quality infra + privacy by @vaderyang in #71
**Full Chang...
TokenScope v0.2.0
[0.2.0] — 2026-05-09
Capture
pcap_dumpnow writes to<dir>/<sanitized_source_id>/<minute>.pcap[.snappy]
(per-source subdirectory + wall-clock minute rotation, sparse). Old flat
<dir>/<source>.pcapfiles from prior runs are not migrated and remain
alongside the new layout. Breaking for operators relying on the old
flat path.pcap_dumpsnappy framed compression added (compression = "snappy");
writes.pcap.snappyfiles. Decompress withsnzip -dbefore opening
in Wireshark.pcap_dumpper-pipeline retention sweeper enforcing age and total-size
caps; old minute-files are pruned in the background.- New internal metric
dump_late_minute_pkts(capture group): incremented
when an out-of-order packet's timestamp falls in an earlier minute than
the file currently being written. Late packets ratchet forward into the
current file (timestamps preserved inside the pcap record). - Cloud-probe dumper now flushes on every heartbeat, matching pcap-live's
~1s crash-loss horizonon hard termination. - pcap-live ring buffer raised to 16 MiB to reduce kernel-side drops under
bursty traffic. - New
ts-pcap-extractcrate: read-side filtered extraction over rotated
pcap_dumpdirectories (powers the new/api/pcap/extractendpoint).
LLM wire-API support
- Gemini AI Studio (
gemini-aistudio) decoded end-to-end — request +
streaming response parsing and agent-turn assembly viaGenericProfile.
Persisted identifier follows the<vendor>-<surface>form to leave room
for future Vertex / Gemini variants. - Tiktoken fallback estimator fills
prompt_tokens/completion_tokens
for rows where the upstream response omitted usage.
Agent turn tracking
HermesProfile(Open WebUI / Hermes-style chat clients) detected via
body fingerprint, alongside the existing Claude CLI / Codex CLI /
generic / OpenClaw profiles.- Strict
session_idanchor extraction with profile-match gating —
session_idis no longer populated for wire bodies that don't satisfy
the matched profile's shape. - System-prompt + time-bucket
session_idfallback for helper / one-shot
calls that lack a stable client anchor. - In-progress agent-turn visibility: in-memory registry exposes turns
before they finalize, so the API and console can show the current turn
and its calls in real time.
Metrics
ttft_msis no longer populated for non-streaming responses (was
previously emitted with misleading values derived from full-response
arrival time).
Storage
- Default
flush_interval_mslowered from 1000ms → 200ms — fresher data
on the console with negligible write-amplification cost. - Retention defaults aligned: turns ≤ calls (turns can never reference a
pruned call). ts-storagesplit into an abstraction crate (ts-storage) plus a
DuckDB implementation crate (ts-storage-duckdb); future PostgreSQL /
ClickHouse implementations follow the same per-backend-crate pattern.
No runtime behavior change.
API
GET /api/pcap/extract— filtered packet extraction frompcap_dump,
returns a downloadable.pcap.- In-progress turn detail endpoint returns the live calls list as the
turn is still accumulating.
Console
- Packet-extract dialog wired into traffic / call / turn detail pages
(downloads filtered.pcapvia the new API). - Raw / Tree toggle for HTTP-exchange detail body viewer.
- Auto-refresh defaults to 5s and is now "quiet": previous data is kept
while the next fetch is in flight, and time-series chart animation is
disabled — no flicker on tick. - In-progress agent turn surfaced in the turn list and detail view with
its live call list.
Configuration
[pipeline.pcap_dump]:filename_templateremoved,compressionadded
("none" | "snappy"). Stalefilename_templatekeys in existing TOML
are silently ignored by serde. Breaking if you scripted output paths
off the template.[pipeline.queues]keys renamed to align with the health-page metric
names (paired(received, dropped)counters now share a single
<destination>_*prefix). Breaking for hand-written TOML —
update any custom queue-depth tuning to the new key names.
Operations
- TUI
dbviewremoved; the web console covers all of its surfaces. - Self-hosted-runner GitHub Actions workflow added for CI.
libduckdb-sysdebug info stripped in dev / test profiles, cutting
target/size and link time on incremental rebuilds.
What's Changed
- fix(observability): keepalive capture + helper-shape session grouping by @vaderyang in #1
- feat(ts-llm): tiktoken fallback estimator for missing-usage rows by @vaderyang in #2
- perf(console): quiet auto-refresh — no full-panel flicker on dashboard/performance/traffic by @vaderyang in #3
- fix(ts-llm): only populate ttft_ms for streaming responses (TTFT no longer == E2E for non-stream) by @vaderyang in #4
- perf(storage): default flush_interval_ms 1000 → 200 (cuts capture→visible latency from ~1.1s to ~250ms) by @vaderyang in #5
- feat: in-progress agent-turn visibility via in-memory registry (zero DB write amplification) by @vaderyang in #6
- fix(ts-llm): strict session_id anchor extraction + profile-match gating by @timmy21 in #7
- ci: self-hosted-runner workflow (cargo test + clippy + bun test) by @vaderyang in #8
- refactor: align pipeline.queues keys with pipeline-health metric names by @timmy21 in #9
- refactor(storage): decouple ts-storage trait from DuckDB impl + entity split by @timmy21 in #10
- refactor(ts-api): move duckdb-using route tests to integration suite by @timmy21 in #11
New Contributors
- @vaderyang made their first contribution in #1
- @timmy21 made their first contribution in #7
Full Changelog: v0.1.0...v0.2.0
TokenScope v0.1.0
[0.1.0] — 2026-04-30
Initial release of TokenScope — an LLM API performance monitoring system that
analyzes plaintext HTTP traffic on the LLM provider's server side to measure
and diagnose inference performance.
Capture
- libpcap-based local NIC capture with optional per-pipeline pcap file dump
- Remote packet ingestion via ZMQ from cloud-probe
- Default snaplen 262144 to fit GSO super-frames
- Data-driven heartbeat emission per stream with paired received/dropped counters
- Graceful shutdown on SIGTERM/SIGHUP with pcap dump flush and
pcap_breakloop-based cancel
Protocol parsing
- L2–L4 + HTTP/1.1 + SSE parsing (zero-copy via
httparse) - Per-direction TCP reassembler with out-of-order segment reorder buffer
- Forced resync on snaplen-truncated TCP segments and HTTP boundaries
- Silent-drop observability for the reassembler
LLM wire-API support
- OpenAI Chat Completions, OpenAI Responses, Anthropic Messages, Gemini, vLLM, Ollama
- Two-pass detection via
RouteVerdict+matches_shape - Generic agent profiles (
generic-anthropic,generic-openai-chat,generic-openai-responses) for arbitrary clients hitting standard endpoints - OpenClaw agent profile
- Strict per-endpoint OpenAI parsers (Chat vs Responses split)
- Shared SSE event-JSON parsing across extractors
- Anthropic SSE accumulator keyed by content block index
Agent turn tracking
- Agent profile registry with state-machine-based turn boundary detection
- Buffer-and-finalize tracker with per-stream event-time watermark + wall-clock grace
- Explicit/implicit ingest path split
- Client/server IP tracking and filters on agent turns
- Tool-use ↔ tool-result join across calls within a turn
Metrics
- TTFT, E2E Latency, TPOT, Call Rate, Token Throughput, Active Calls, Call Error Rate, Cache Hit Ratio
- Sliding-window aggregation with
*_sum+*_countpairs (mean derivable client-side) - Dimension filters (
wire_api,agent_kind,model,source_id, etc.) applied to summary / models / timeseries queries - Internal-metrics observability: active-flows / active-turn gauges, queue-depth gauges, packet-drop counters, leak-canary gauges
Storage
- Pluggable backend trait with three implementations:
- DuckDB (default, embedded, single-file)
- PostgreSQL
- ClickHouse
- Three entities:
agent_turns,llm_calls,llm_metrics(+llm_finish_metrics,http_exchanges) - Per-table retention enabled by default with sane TTLs
- No-JOIN read-path rule — cross-entity reads split into PK lookups
- Raw HTTP exchange persistence via joiner stage
API
- Axum REST API + WebSocket
- Agent sessions endpoints
- Per-page dimension filter spec
- Source ID surfaced end-to-end (renamed from
stream_id)
Console (React + TypeScript + shadcn/ui + Tailwind)
- Pages: traffic, LLM calls, agent turns, agent sessions (list + transcript), pipeline health
- LLM call detail with structured I/O renderer per
wire_api×agent_kind - Agent turn detail organized as behavior narrative with tool_id index fusion
- Raw HTTP drawer with Tree/Raw body viewer
- Filters:
agent_kind,client_ip, URI-contains, errors-only - Sidebar state + refresh URL param persistence
Operations
install.shwith XDG-aware config discovery cascade,--help, sudo+user-dir guard, OS-specific next steps- GitHub Actions release workflow (macOS x86_64 + arm64 on
macos-14runners) - Demo flow (SSH/setup + cross-compile + deploy) via
just demo - VERSION-file SSOT pattern with
just bumpsyncingCargo.toml+package.json - LICENSE + public README + glossary + mission doc
Full Changelog: https://github.com/Netis/TokenScope/commits/v0.1.0