npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

buzz-trace

v0.5.0

Published

Distributed Thread Observability & Diagnostic CLI for Multi-Agent Nostr Networks

Readme

buzz-trace

CI License: MIT Node Version

Distributed Thread Observability & Diagnostic CLI for Multi-Agent Nostr Networks.

buzz-trace bridges the observability gap in decentralized multi-agent architectures. It correlates distributed OpenTelemetry and OpenInference traces emitted by multiple autonomous agents across a single Nostr thread, producing a unified chronological execution timeline with automated anomaly detection.


🌟 Key Features

  • One source: the Phoenix server. buzz-trace reads Phoenix over its REST API (API key, or email and password). No relay, no buzz CLI, no file. It finds the project from the thread, so there is no default project to get wrong.
  • Thread-Centric Correlation: Unifies isolated per-turn traces across multiple agents into a single thread execution view using the Nostr root event ID (session.id).
  • Multi-Agent Timeline: Visualizes interleaved turns between different agents (@phoenix-twin, @paul-twin, etc.) with model usage, wall latency, and token consumption.
  • Which turn posted a message: every report shows the id of each message a turn posted (212193a2…) and, for a parent message, the turn that posted it. Exact when the agent's runtime records the id (telemetry contract) or when someone completed an old trace with span annotations; otherwise buzz-trace lists the turns that could have posted it and picks none.
  • Steered messages: Handled 4 messages (3 steered) with each one's id and text, when the runtime records them; when it does not, the report says that steered messages are not recorded in the trace.
  • Semantic Tool Compactor:
    • Eliminates the 2.77x JSON escaping penalty and nested subshell backslash bloat.
    • Delivers a 47% token reduction in tool input summaries for autonomous agent context windows.
  • Causal DAG & Branch Hardening:
    • Cycle guard in assignBranches eliminates stack overflow recursion on cyclic trace graphs.
    • Robust topological sorting with microsecond tie-breaking across concurrent multi-agent turns.
    • Honest edges: agents started by one message are siblings. REPLY_TO is exact (the message started a turn, was taken into one, or a reply span recorded it). INFERRED names a turn only when it is the one turn that could have posted the parent message. AMBIGUOUS lists the candidates and picks none.
  • The whole conversation, nothing cut: buzz-trace transcript gives every request and response of a thread, whole and in the order they ran, including what each tool returned (inspect and query keep 80 characters of a tool's input and never read its output). --dialogue gives only who said what: the messages that started each turn, what the agent posted, and, in a huddle, what people said and what the agent said back, with each time it was cut off.
  • Buzz-aware turns: reads the Buzz envelope in the prompt (who asked what, parent, root) and the text each turn posted, so previews show the question and the answer, not metadata.
  • Cost & context per call: cost and cache use per turn and thread; a turn's prompt tokens are summed over its model calls, so buzz-trace estimates the context of one call (tool steps + 1) and warns on CONTEXT_GROWTH round after round.
  • Automated Diagnostic Intelligence:
    • Detects context canceled premature turn terminations.
    • Flags INVALID_DURATION anomalies from monotonic clock jumps, with automatic non-negative duration clamping.
    • Identifies tool execution failures, stderr errors, and CAUSAL_OVERLAP.
    • Flags Tool Loops (identical consecutive tool invocations), Token Bloat (>150k tokens in one model call) and Context Growth (an agent's context per call doubling over a thread).
  • Multiple Output Modalities:
    • Terminal: Clean ANSI color formatting, trees, and badges for human engineers.
    • AI JSON (--json): Compact, structured JSON optimized for LLM prompts and autonomous subagents.
    • Markdown (--markdown): Ready-to-paste tables and breakdowns for GitLab MRs, GitHub PRs, and incident postmortems.
  • One runtime dependency (a tokenizer). The Phoenix client is Node's native fetch.

🚀 Quick Start

npm install -g buzz-trace          # or: npx buzz-trace …

export PHOENIX_URL=http://phoenix.example.com:6006
export PHOENIX_API_KEY=…           # or PHOENIX_EMAIL + PHOENIX_PASSWORD

buzz-trace inspect <nostr_thread_root_id>

A Phoenix without authentication needs only the URL. The project is found from the thread: name one with --project or PHOENIX_PROJECT only to look in that project alone.


🔑 Connecting to Phoenix

                 ┌─ PHOENIX_API_KEY / --api-key ──▶ Authorization: Bearer <key>
buzz-trace ──────┤
                 └─ PHOENIX_EMAIL + PHOENIX_PASSWORD ──▶ POST /auth/login ──▶ Bearer <access token>
                                                          (refreshed on 401, or signed in again)

| | Order of precedence | | :--- | :--- | | Endpoint | --endpoint, PHOENIX_ENDPOINT, PHOENIX_URL, then http://localhost:6006 | | Credentials | an API key, if set; else the email and password. A key Phoenix rejects gives way to the email and password when both are set | | Project | --project, PHOENIX_PROJECT, else none: buzz-trace asks Phoenix which project holds the thread (GET /v1/sessions/{id} names it), and searches every project for an id that is not a session |

  • A secret never appears on screen, in a URL, in an error or in a log: errors are redacted, and a Config or client cannot be printed with its credentials. The email and password come from the environment only; prefer PHOENIX_API_KEY to --api-key, since a flag shows in ps and in your shell history.
  • A sign-in lasts ten minutes on a default Phoenix (its refresh token, a week), and a Phoenix may rate-limit sign-ins (buzz-trace says so on a 429): for repeated runs use an API key, which needs none.
  • Without credentials a Phoenix that wants them answers 401: buzz-trace stops with exit code 2 and says what to set. It never reports that as NOT_FOUND.

What a thread costs. Two requests when the project is named (the spans that carry the session id, then every span of those traces, since a steer record need not carry it); four when buzz-trace finds the project first; and one more per 50 reply calls whose span records no posted id, for their span annotations (none when there is no such call). Pages are read to the end (next_cursor) and ordered by start time. The same data through the Phoenix CLI would be one process per call (px session get <root> names the traces and holds no spans; px span list --trace-id <id> per trace; about a second to start each, 18 s and 313 MB the first time through npx), with no email and password login: so buzz-trace calls the REST API itself. px is a good way to look at the same data by hand, and px api graphql (or Phoenix's own /graphql) answers a whole session, its traces, spans and events in one request.


📖 Usage Examples

1. Inspect a Thread Execution

# Inspect a thread: the project is found from the thread
buzz-trace inspect a519f713413dea676f4b1e72c1756869c55299988da5cd11d6e81b49fe35d2f3

# Look only in one project of a given Phoenix
buzz-trace inspect a519f713... --endpoint http://solace-phoenix:6006 --project ba

# Given a trace id, or the id of a message of the thread, finds the thread as well
buzz-trace inspect 212193a2f2f5dbd2955a43f55b191213d5c5b55b161cac89a34d8e9ca95a3773

2. AI Agent Self-Reflection & Compact Query Mode (<200 tokens)

Designed specifically for LLM context preservation and subagent evaluation:

# Query thread health with zero tool errors in <80 tokens
buzz-trace query a519f713... --failures --compact --json

# Tail the most recent turn and inspect tool executions in <150 tokens
buzz-trace query a519f713... --last 1 --compact --json

# Inspect turn 2's specific tools and parameters
buzz-trace query a519f713... --turn 2 --tools --compact --json

# Filter turns executing specific commands or scripts via --tool-input / --cmd
buzz-trace query a519f713... --cmd "git status" --compact --json
buzz-trace query a519f713... --tool-input "buzz messages send" --compact --flat

3. Next-Gen Differential Trace Analysis (buzz-trace diff)

Compare two agent turns, prompt runs, or model variants to detect latency regressions and tool failures:

# Compare Turn 1 vs Turn 2 within a thread
buzz-trace diff <thread_id>#1 <thread_id>#2 --flat

# Compare two distinct traces
buzz-trace diff <trace1> <trace2> --flat

Outputs model changes, $\Delta$ latency (ms), $\Delta$ token counts, tool execution count diffs, and tool status transitions.

4. Adaptive Context Summarizer Under Strict Token Budgets (buzz-trace context)

Auto-trims thread history to fit within a strict token budget (e.g. 200 or 500 tokens) for autonomous AI context windows:

# Auto-trims to fit within exactly 200 tokens
buzz-trace context <thread_id> --budget 200 --flat

Prioritizes: (1) Root user intent, (2) Latest turn outcome, (3) Failed tools and error diagnostics, while pruning successful intermediate chatter.

5. Install Global AI Skill

Integrates buzz-trace directly into Claude Code and cross-agent environments (~/.claude/skills and ~/.agents/skills):

# Install or symlink skill globally
buzz-trace install-skill

# Force overwrite or preview actions
buzz-trace install-skill --force
buzz-trace install-skill --dry-run

6. Markdown Report Generation

# Generate markdown for GitLab MRs / GitHub issues
buzz-trace inspect a519f713... --markdown > incident-report.md

7. Automated Health Diagnostics

# Runs anomaly checks; exits with code 1 if critical issues (e.g. context canceled or loops) are found
buzz-trace analyze a519f713...

8. Live Execution Watch

# Continuously watches thread turns in real-time as agents respond
buzz-trace watch a519f713... --interval 2.0

9. List Recent Sessions

# The newest sessions of every project (or of one: --project ba)
buzz-trace sessions --limit 10

10. Full Transcript: Every Request and Response, Whole (buzz-trace transcript)

inspect and query keep 80 characters of a tool's input and never read what the tool returned. transcript reads it all from the spans Phoenix holds: for each turn, the message that started it, then every step in the order it ran with its whole request and whole response, and the text the turn ended with.

# Every step of every turn, whole
buzz-trace transcript a519f713...

# Only the conversation: what people said, what the agent said and posted (a huddle too)
buzz-trace transcript a519f713... --dialogue

# One turn, some tools, only what failed. Steps keep the number they have in the whole turn.
buzz-trace transcript a519f713... --turn 2
buzz-trace transcript a519f713... --tool meeting_speak,knowledge_search
buzz-trace transcript a519f713... --failures

# Exact data for a program (--flat: one line), a report for a merge request, a line per model call and the whole prompt
buzz-trace transcript a519f713... --dialogue --json
buzz-trace transcript a519f713... --markdown > transcript.md
buzz-trace transcript a519f713... --verbose

--turn, --last and --agent choose turns (as in query); --tool (comma-separated, a part of a name is enough) and --failures choose steps and so do not apply to --dialogue, which refuses them. --tool-input is refused too: an answer to another question would look like the answer.

A step is a tool call (✓/✗, how long it took, which model call asked for it), the words the model wrote, or a message steered into the running turn. A tool's request and response are shown as the runtime recorded them: JSON as key: value lines with the text unescaped (a message with quotes and line breaks stays a message), anything else as it is. --json has the exact values.

The dialogue of a huddle is read from the meeting gate's meeting_wait (heard[]: who said what, and when into the call) and meeting_speak (what the agent said, and whether it was spoken to the end):

  09:14:28.257Z  Alex  · call 00:11 · mention
      Hi. Kent.
  09:14:29.707Z  Kent Twin  · said in the call · cut off by Alex after “Hi”
      Hi, this is Kent Twin. I'm here and listening.
  09:14:40.793Z  Alex  · call 00:24 · mention
      Can you explain a phantom assembly?

Where the telemetry holds less than happened

The transcript says so, in words, and never fills a gap:

  • A response the runtime cut. Some runtimes record only the first few KB of a long output. The span says how many bytes the tool returned (tool.output_bytes); when fewer were recorded the step reads response cut by the runtime: recorded 8,207 of 12,098 bytes (response_truncated, response_recorded_bytes, response_returned_bytes in JSON).
  • A turn with no agent_turn span. When the agent's process ends before the span does, Phoenix has the model and tool spans but never the root: the agent, the end and the closing text are not known, and the turn's times are those of the spans seen (root_span: false, agent: null). inspect and query name such a turn phoenix-twin, which is only their default.
  • The same message starting a turn twice (for example after a restart) is listed under both turns; the second says same message as turn 1.
  • The dialogue lists what a call recorded: a call that failed said nothing and is left out, and so is a steered message the turn ended before receiving. Other tools (meeting_chat, meeting_transcript) are in the steps only.

Reading it safely

The transcript prints what tools and web pages returned, whole. Phoenix is not a vault: whatever a tool printed (a token, a password) is in it, so treat the output as sensitive. Control characters (an escape sequence that clears the screen or sets the window title) are shown as \x1b in the terminal and Markdown forms instead of being sent to your terminal; --json keeps the exact text. The output is not token-budgeted: for an LLM, narrow it first (--dialogue, --turn, --tool, --last).


🧾 Which Turn Posted a Message?

A person answers an agent's message. To say which turn wrote it, Phoenix would have to hold the id of what each turn posted. It does not, unless the agent's runtime records it:

Kent's turn ── reply call ──▶ message 212193a2…     Phoenix holds: the call's text, its time, what it replied to
Paul's turn ── reply call ──▶ message 5724e661…     Phoenix does NOT hold the id of the message a call created
Paul's turn 3 ◀── started by ea3c75b2…, a reply to 212193a2…     Phoenix holds both ids, from the envelope
Turn 3 · ba-paul-twin [OK]
│  Event ID : ea3c75b2c762198988f5d07246a2c04423abba18e965c1ed33d9a2afde7271f3
│  Handled  : 1 message — steered messages are not recorded in this trace
│  Reply To : message 212193a2… posted by Turn 1 or Turn 2: not recorded, so no parent is picked
│  Posted   : id not recorded  "Yes, I'd keep mine first: the two are nearly the same…"

With the contract below, the same turn reads Reply To : message 212193a2… posted by Turn 1 (ba-kent-twin) and Posted : a3707ece… "Yes, I'd keep…".

What is and is not derivable

| | Traces written before the contract | Traces that follow it | | :--- | :--- | :--- | | Id of a message a turn posted | Not in Phoenix, unless someone wrote it there afterwards as a span annotation. Not in the spans, their events, or the session and trace objects. (A message id is the hash of the whole event, and Phoenix does not hold all of it: recomputing the id from what Phoenix has did not reproduce a real one.) | nostr.posted_event_id on the reply span | | The turn that posted a parent message | One turn, if exactly one reply call could have created it (it started before the answering turn, did not fail, did not answer that message, and no other message needs the call): INFERRED. Several: the candidates are listed and none is picked (AMBIGUOUS). Checked by hand on five real ba threads against the relay's own thread (which buzz-trace never reads): of 7 such links, 5 inferred, all right; 2 ambiguous, the right turn among the candidates; none wrong | exact (REPLY_TO) | | A parent written by a person | Cannot be told from an agent's: the inference assumes the agent wrote it | not any turn's post: the report says so | | A message steered into a running turn | Only when a reply call answered it (seen as inferred from a reply call, no text); otherwise it is absent | a steer span, with id and text |

🧬 Telemetry contract

What an agent runtime writes to Phoenix so that buzz-trace is exact. Every name is a constant in src/contract.ts.

| What | Where | Value | | :--- | :--- | :--- | | Id of a posted message | attribute nostr.posted_event_id on the reply tool span (mcp__buzz-thread__reply, or any tool that posts) | 64 hex | | The same, from tool output | event_id <64 hex> in that span's output.value or tool.output (the reply tool returns posted (N characters) · event_id <hex>) | text | | The same, written afterwards | a span annotation named nostr.posted_event_id on the reply span (how) | result.label: 64 hex | | A steered message | a child span named steer under the turn's agent_turn span, in the same trace, starting when the message was delivered; a span event named steer with the same attributes is read too | see below |

// the reply span: what the call replied to, and what it created
{ "name": "tool:mcp__buzz-thread__reply",
  "attributes": { "tool.parameters.reply_to": "fb46eb2a…", "tool.parameters.text": "Kent Twin here…",
                  "nostr.posted_event_id": "212193a2f2f5dbd2955a43f55b191213d5c5b55b161cac89a34d8e9ca95a3773" } }

// a message that arrived while the turn was running
{ "name": "steer", "parent": "<the agent_turn span>", "start_time": "<when it was delivered>",
  "attributes": { "openinference.span.kind": "CHAIN",
                  "nostr.event_id": "9c3d1e7a…",      // the steered message's id (64 hex)
                  "input.value": "One more thing…",   // its text, redacted, at most 8 KB
                  "nostr.sender_name": "Elio Tran",
                  "steer.priority": "now" } }          // or "later"

A child span is the contract rather than an event because it shows in Phoenix's trace tree and waterfall and can be filtered by name and attribute through the REST API; an event is accepted too.

The Buzz fork writes more on a steer span, all optional for the reader: nostr.sender_pubkey, steer.uuid, steer.delivered, steer.end_reason (echo, result or turn_end), and a steer_spans_capped event on the turn when it stopped writing them. Its input.value is the whole block the agent was handed (a framing line, the Buzz envelope, a closing note); buzz-trace takes the envelope's Content: line as the message. steer.delivered=false means the turn ended before the message reached it: it is listed as not delivered, not counted and not linked to, because nothing says that any turn ran it.

Completing an old trace: span annotations

A span cannot be edited, but Phoenix can hold an annotation on it. Whoever knows the ids (say, an operator who read them from the relay) writes one on each reply span, and buzz-trace then reads the id as if the span had recorded it. buzz-trace only reads annotations; it never writes one.

old reply span ◀── annotation  nostr.posted_event_id = 212193a2…         written once, afterwards
buzz-trace ──▶ Posted   : 212193a2… (span annotation)
               Reply To : message 212193a2… posted by Turn 1 (ba-kent-twin) — id from a span annotation
// POST /v1/span_annotations: by whoever completes the trace, not by buzz-trace
{ "data": [ { "span_id": "bcf49f14a6f8f402",          // the reply span (16 hex)
              "name": "nostr.posted_event_id",
              "annotator_kind": "CODE",
              "identifier": "relay-backfill-v1",      // posting the same identifier again updates the annotation
              "result": { "label": "212193a2f2f5dbd2955a43f55b191213d5c5b55b161cac89a34d8e9ca95a3773",   // the id, 64 hex
                          "explanation": "id of the message this reply call posted, read from the relay" },
              "metadata": { "source": "relay-backfill" } } ] }

| What buzz-trace does | | | :--- | :--- | | Which spans | Reply calls that did not fail and whose span records no id. One GET /v1/projects/{project}/span_annotations per 50 spans (span_ids repeated, include_annotation_names=nostr.posted_event_id, the cursor followed). With no such span there is no request | | Which id wins | the attribute, then the annotation, then event_id <hex> in the output text | | Several on one span | the latest (updated_at) counts. If its label is not a 64-hex id the span has none from annotations: an older one was replaced and is not used. Equally new and different: none | | In the report | the link is exact (REPLY_TO); parent_message.basis is annotation, the post has event_id_source: "annotation", and the text says id from a span annotation | | Steering | An annotation says nothing about the runtime: a trace completed this way still reads steered messages are not recorded in this trace | | A Phoenix without that route | read as having no annotations (a 404); any other failure is an error |

Steered messages

With turn_handling: steer, a message that arrives while a turn runs is injected into it and starts no trace, so Phoenix shows one message for four. buzz-trace reads the steer records:

│  Handled  : 4 messages (3 steered)
│    ↳ steered 9c3d1e7a… (now) "One more thing for the same email: mention the SAP client number."
│    ↳ steered 6e1a22f0… (later) "And please say who on our side will send the data model."
│  Handled  : 2 messages (1 steered)
│    ↳ steered 9c3d1e7a… (now) "One more thing…"
│    ↳ not delivered 6e1a22f0… (now) "Too late…" — the turn ended before it reached it

Where nothing recorded them, a reply call that answered a message other than the opening one still shows it (inferred from a reply call; text not recorded). Otherwise the report says steered messages are not recorded in this trace, but only for a turn with no sign that its runtime writes the contract: no steer record, no posted id that the runtime recorded (a runtime that writes ids writes steer spans; an id that only a span annotation gives does not count, someone wrote it afterwards), and no such turn of the same agent in the thread. A turn whose runtime does write them and had nothing steered reads plainly Handled : 1 message.


🧾 JSON Fields Added in 0.4

| Field | Meaning | | :--- | :--- | | posted_event_ids, posts[] | ids of the messages each turn posted (when recorded), and each reply call: event_id (or null), reply_to, text, at, and event_id_source: "annotation" when the id was read from a span annotation | | answered_event_ids | the messages the turn's reply calls answered | | handled_messages, steered[] | messages the turn handled, and the steered ones: event_id, text, sender, sender_pubkey, priority, at, source (span, event or reply_call), and, when recorded, delivered (false: not counted) and end_reason; steered_capped when the list is cut off | | parent_message | { id, basis, posted_by_turn, turns }; basis is posted, annotation (the same, the id from a span annotation), started, steered, inferred, ambiguous, thread_root or unknown | | parent_edge: AMBIGUOUS, parent_candidates | no parent is picked; the turns that could have posted the parent message | | steering, steering_recorded | whether a steered message would show in the trace: recorded or not_recorded for the thread, and true or false for each turn |

Compact output (--compact --json) adds, only when they apply: parent_msg and posted_ids (the first 8 hex), parent_basis: "annotation", edge: "ambiguous" with candidates, msgs and steered (with not_delivered and steered_capped beside them), and a thread-level steering: "not_recorded".


🧾 Transcript JSON (0.5)

buzz-trace transcript <thread_id> --json (--flat: one line). Times are ISO 8601 in UTC. Keys are always present (tried appears only for a thread that was not found); null means the trace does not say.

| Field | Meaning | | :--- | :--- | | view | steps or dialogue | | total_turns, shown_turns | turns in the thread, and turns left after --turn, --last, --agent | | turns[].root_span | false: no agent_turn span was exported; agent and output are then null and the times are those of the spans seen | | turns[].request | { from, message, prompt }: who wrote the message that started the turn, the message, and the whole prompt the agent was handed | | turns[].output | the closing text of the turn (its root span's output.value) | | turns[].steps[].type | tool, assistant (words the model wrote), steer (a message steered into the turn), model (a model call, with --verbose) | | steps[].seq | position in the whole turn; a filter drops steps but does not renumber the others | | tool step | name, call_id, model_call (the model call that asked for it), start_time, end_time, duration_ms, status, error, request, response, posted ({ event_id, reply_to } for a call that posted to the thread) | | request, response | the JSON value when the recorded text is JSON, else the text; null when nothing was recorded | | response_truncated, response_recorded_bytes, response_returned_bytes | the runtime recorded fewer bytes of the response than the tool returned (the latter is null when it did not) | | lines[] (dialogue) | turn, time, direction (in: received, out: said or posted), channel (meeting, thread), via, speaker, text, offset (into the call), reason, event_id, outcome (complete, interrupted, not_spoken, async, unknown), interrupted_by, spoken (the part spoken before the cut), result (the gate's own answer) | | counts (dialogue) | { in, out } |


⚙️ Configuration & Environment Variables

| Variable | CLI Flag | Default | Description | | :--- | :--- | :--- | :--- | | PHOENIX_ENDPOINT, PHOENIX_URL | --endpoint | http://localhost:6006 | Arize Phoenix base URL (in this order of precedence) | | PHOENIX_PROJECT | --project | none: found from the thread | Look in this project only | | PHOENIX_API_KEY | --api-key | none | Phoenix API key (Bearer) | | PHOENIX_EMAIL, PHOENIX_PASSWORD | – | none | Sign in when there is no API key (environment only) | | PHOENIX_UI_ENDPOINT | --ui-endpoint | the endpoint | Base URL of the links into the Phoenix UI | | NO_COLOR | --no-color | 0 | Disables ANSI terminal coloring |


🏗️ Architecture

[ Human / AI Agent ]
        │
        │ runs: buzz-trace inspect <thread_root_id>
        ▼
 ┌───────────────────────────────────────────────────────────────┐
 │                       buzz-trace CLI                          │
 └───────────────────────────────┬───────────────────────────────┘
                                 │ HTTP, Node's native fetch
                                 ▼
 ┌───────────────────────────────────────────────────────────────┐
 │                  Arize Phoenix server (REST)                  │
 │ • POST /auth/login, /auth/refresh   (email + password)        │
 │ • GET /v1/sessions/{id}             (which project holds it)  │
 │ • GET /v1/projects/{p}/spans        (attributes, events)      │
 └───────────────────────────────┬───────────────────────────────┘
                                 ▼
 ┌───────────────────────────────────────────────────────────────┐
 │               buzz-trace Aggregator & Analyzer                │
 ├───────────────────────────────────────────────────────────────┤
 │  • One turn per trace; chronological multi-agent sequence     │
 │  • Posted ids, steered messages, parent message per turn      │
 │  • Candidates when the poster was not recorded                │
 │  • Transcript: whole request/response per step, dialogue      │
 │  • Evaluates anomaly rules (cancellations, loops, bloat)      │
 └───────────────────────────────┬───────────────────────────────┘
                                 ▼
         Terminal UI  │  AI JSON Stream  │  Markdown

🧪 Development & Testing

# Install dependencies
npm install

# Run TypeScript typecheck
npm run typecheck

# Run linter
npm run lint

# Run unit tests
npm test

# Run tests with coverage
npm run test:coverage

# Build executable distribution
npm run build

📄 License

MIT License.