Skip to content

@flybot/timeline (team-activity): swimlane timeline of Bot turns, tool runs and exchanges #241

Closed

Description

@hsliuustc0106

Part of #3 (umbrella). Proposal reviewed with an interactive UI mockup (swimlanes / communication map / utilization) in chat on 2026-10-18.

Goal

Give the Bot team the observability DSH gives a single session, one level up: a peer plugin that visualizes where time goes across Bots and their communication — per-Bot turn spans, tool runs, and Bot→Bot exchanges with send→reply latency — the way DSH's sessionStats / Trajectory show prefill/decode/tool time for one session.

  • Phase 1 — agent/communication level (local team): turn spans, tool spans, exchange arrows with latency, group-round brackets, live running turns.
  • Phase 2 — model-time drill-down: click a turn to get the sessionStats-style card (LLM ms, TTFT, decode, tok/s, token usage) folded from that Bot's embedded timed streams.

Motivation

  • Multi-Bot latency is dominated by waits between turns (delegation queues, long tool runs). Nothing surfaces this today: the live work view only keeps {since, tool} per Bot, and history vanishes when the turn ends.
  • DSH's timing viz is a pure derivation over the durable session event log (every event carries time + seq; assistant/message embeds the exact timed stream). That philosophy transfers to the team level with no DSH patch: ctx.on('session/event') is a public documented plugin hook (DSH extension cookbook), and assistantStreamFirstTokenTime / expandAssistantStream are public helpers. Hard rule 1 stays intact, including for phase 2.

Data available today (no host changes)

  • state.exchanges[] (core/src/host/delivery.js): every Bot→Bot send with time + replyTo, capped at 600 → send→reply latency is computable today.
  • Relayed messages in the recipient's session log carry full source metadata (kind:'bot', form:'relay', exchangeId, senderName, hop, roomId, replyTo) — the communication graph is reconstructable end to end.
  • Each Bot's session log has turn/start|turn/end, tool/call|tool/result, assistant/message with event times; group rounds stamp every reply with time.

Missing: turn durations as first-class records (derivable from event times), delivered/read stamps, timestamps on room-log lines (seq only), a historical archive, browser push (poll only — a 2 s poll is fine at team granularity).

Design

Plugin @flybot/timeline in plugins/timeline/ (name: 'flybot-timeline', inject: ['flybot']), packaged exactly like social/tree (dual exports, dsh manifest, cordis.patch.yml, new subsystem doc row).

  • Recorder (host): fold live events into an append-only, capped trace (single writer, fsync+rename — the dev/interop journal discipline). Record turn spans, tool spans (callId-paired), and message send/receive stamped with exchangeId; a pass-through bot.send.use middleware (returns undefined, never blocks) stamps sends.
  • **Views (client, window.__ModuleLoader__.load + existing __flybot__ slots — no new slot, so no core edit):
    1. Swimlanes: one lane per Bot (+ You, group rooms); turn bars segmented prefill/decode/tools; dashed request / solid reply arrows; latency badges from replyTo; live turns with a running clock; wall-time and sequence (idle-compressed) scales.
    2. Communication map: per-window exchange graph, req→reply latency per edge ("who waits on whom").
    3. Utilization: per-Bot allocation bars (model / tools / idle) for the window.
  • Phase 2 drill-down: the sessionStats-style card per turn.
  • Endpoint: flybot.api.handle('timeline/window', …, { read: true }), polled every 2 s (face-room pattern).
You       ●ask ────────────────────────────────────────●answer 89.5s
Atlas     [turn 3]──req→   [turn 8]──req→   [round][turn 12]
Scout        [ttft│decode│web_search│decode] ←reply 19.2s
Forge                  [ttft│decode│browser····│decode] ←reply 22.1s   ▌live
Team Room                        [round ─●───●───●]

Design fork to settle (blocking)

How the plugin gets team events:

  • (a) the peer plugin subscribes to DSH ctx.on('session/event') directly — zero core changes for phase 1;
  • (b) core extends the flybot service with a turn/exchange event feed (flybot.on(...) currently only has roster events, core/types/flybot.d.ts) and the plugin subscribes through the documented adapter.

(b) is the cleaner contract (plugins talk through ctx.flybot, per the plugin-runtime invariants) but costs a core PR in plugin-runtime. Recommendation: start with (a), move to (b) when a second consumer appears.

Constraints

  • Never patch DSH — public APIs only (hard rule 1).
  • Untrusted strings (message text, display names) rendered by the panel go through the sanitization helpers, with negative tests (LESSONS: fence escape, Seen 2×).
  • Claim honesty: do not render delivered/read states the recorder cannot see; label idle precisely as "no running turn".
  • Trace capped, append-only, no secrets; tests use the mock model (core/dev/mock-llm.mjs) and the bench.
  • Subsystem doc + plugins/ README in the same PR; arch-docs gate satisfied by the new doc row.

Out of scope (phase 1)

Acceptance

  • plugins/timeline/ passes npm run verify (plugin suite + lint-plugins).
  • Timeline renders from a mock-model bench run: turn spans, tool spans and exchange arrows with latencies matching the session logs.
  • Phase 2 card matches sessionStats semantics for a sampled turn (LLM/TTFT/decode/tools within tolerance).
  • Sanitization negative tests for displayed strings.
  • Subsystem doc + README + changelog.d/ fragment.

Open items

  • Settle the (a)/(b) event-source fork.
  • Trace retention cap and rotation policy.
  • Group rooms: full lanes or round brackets only.

Activity

  1. hsliuustc0106 commented on Oct 18, 2026

    @hsliuustc0106
    ContributorAuthor

    @Birfy — requesting your discussion on this proposal before any implementation starts.

    Background: it came out of asking what DSH's prefill/decode/tool timing would look like one level up, at the Bot-team level. The data exploration is done (summarized above: exchanges[] already carries time + replyTo, relay metadata is in the recipient session logs, and DSH's timed streams make even the phase-2 model-time drill-down derivable without a patch). The three views are based on an interactive mockup reviewed in chat (swimlane timeline with turn/tool spans and latency-badged exchange arrows; communication map; per-Bot utilization bars).

    The decisions where I'd most like your input:

    1. The event-source fork — (a) the peer plugin subscribes to DSH session/event directly, vs (b) core extends the flybot service with a turn/exchange event feed. I lean (a) for phase 1 since it avoids core changes, but you own the plugin-runtime contract: if plugins should not touch raw DSH events, (b) is worth the core PR upfront instead.
    2. Scope cut — phase 1 = communication level, local team only, three views from one recorder. Would you trim to just the swimlane view, or add anything (e.g. full room lanes instead of round brackets)?
    3. Recorder policy — append-only capped trace with the interop-journal write discipline: any preference on the cap/retention before this gets designed in?

    Anything else you'd change about the constraints or the acceptance list is welcome too.

  2. Birfy commented on Oct 18, 2026

    @Birfy
    Collaborator

    Decision from Birfy (2026-10-18):

    1. Event source: (b) — extend the flybot service with a turn/exchange event feed; the timeline plugin subscribes through ctx.flybot, not raw DSH session/event. Do the core PR upfront rather than starting on (a).
    2. Scope: full phase 1 as proposed — three views (swimlanes, communication map, utilization), local team, group rooms as round brackets first (not swimlane-only).
    3. Recorder: append-only capped trace; use a conservative default (about 24h or a few thousand spans, rotate when full). Adjust if needed once the shape is clear.

    One-step preference: land the clean contract and the three views together, not a temporary (a) shortcut.

  3. Birfy commented on Oct 18, 2026

    @Birfy
    Collaborator

    Progress (option (b), per the decision above):

    • PR 1 merged: feat(core): flybot activity feed for turns, tools and deliveries #245 (7ce0586) — the flybot service now has a team activity feed: ctx.flybot.on('turn.start' | 'turn.end' | 'tool.start' | 'tool.end' | 'exchange.delivered'). Own chat, group member Session and the group's own Session (its turn is one round) are told apart by place. Metadata only (ids, times, outcomes); message text, tool arguments and output never leave core/src/host/flybot-feed.js. Types in flybot.d.ts, plugin-runtime doc invariant 12.
    • Next: PR 2 plugins/timeline recorder (capped append-only trace, single writer, fsync+rename) + timeline/window endpoint + packaging; PR 3 the three views (swimlanes, communication map, utilization). Phase 2 stays a follow-up unless it turns out cheap.
  4. Birfy commented on Oct 18, 2026

    @Birfy
    Collaborator

    @flybot/timeline phase 1 is on main, in the three PRs planned in the decision comment:

    1. feat(core): flybot activity feed for turns, tools and deliveries #245 (7ce0586): core activity feed. flybot.on('turn.start' | 'turn.end' | 'tool.start' | 'tool.end' | 'exchange.delivered'), metadata only, in core/types/flybot.d.ts and plugin-runtime. Plugins never read raw session/event.
    2. feat(timeline): @flybot/timeline recorder, capped trace and timeline/window endpoint #246 (c5a7ad3): plugins/timeline recorder, capped trace and the timeline/window endpoint. Trace is about 3000 spans or 24 h, one writer (writer.lock), tmp + fsync + rename like dev/interop/journal.mjs. The endpoint is registered { read: true }. Only ids, times, outcomes, tool names, message lengths and cleaned names are stored. Negative tests cover a secret in text, arguments or output, hostile names, fence markers and bad error codes. The host test drives the real core with a mini agent loop on core/dev/mock-llm.mjs. It also tightened two things in the core feed: an error code must be an upper-case word, and the untrusted fence marker is stripped when spaced out.
    3. feat(timeline): panel with swimlanes, who-waits-on-whom map and utilization #247 (dbd1fea): the panel. A Timeline row under the Bot list (and a sidebar + entry) opens swimlanes (turns with tool calls, request/reply arrows with the wait, group rounds as round brackets, "skip quiet time"), "who waits on whom" (map + table) and utilization (model / tools / no running turn). Polls every 2 s only while open.

    Not done: phase 2. The drill-down card (and a measured model latency) is not cheap: the feed only has turn and tool spans, so "model" time is turn time outside tools. Left as a follow-up: a card per turn with its timed stream data, if DSH's own records can supply it without patching DSH.

    Not verified: no real-DSH bench on my side (no deepseek-harness checkout), so the event shapes come from turns.js/delivery.js and the host test's stand-in loop; and the panel was rendered with real React in headless Chrome from a scripted fixture, not in a live shell. The dialog is position: fixed from the sidebar section, so a transformed ancestor in the shell would clip it.

    Design questions for the owner

    • timeline/window has no actor: any signed-in member who can read can see timing metadata of every local Bot (no text). Fine for a single-owner home; should it be owner-scoped once team mode lands?
    • The feed is new in core, but the plugins' ds-bot peer range is still >=0.1.5-flybot.1. The plugin refuses to start with a clear error on a core without the feed; do you want the peer range bumped when a core version is cut?
    • In turns.js, tool/result carries callId at data.callId or data.message.toolCallId; the feed reads both. If DSH only ever sends one, the other branch can go.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions