/** * dispatch-history.ts — Module-level ring buffer that records every * orchestration dispatch decision so the `/agents → Health check` report can * surface a "dispatch-decision histogram" (auto → single/swarm/crew counts in * the last N spawns). * * Why a separate module, not a field on `AgentRecord`: * - The decision is made BEFORE we know the agent's final id (e.g. an auto * pick that fans into 3 crew members all share one dispatch decision). * Tying the decision to the first member's id would corrupt later counts. * - The histogram is a category-aggregate concern. Ring-buffer logic + the * aggregate helper live next to each other and stay trivially testable * (no manager state, no agent id, just structural operations). * - The auditor can reset the buffer between tests via `clearDispatchHistory()` * without needing to spin down an `AgentManager`. * * Thread / process model: this module is intentionally single-process and * shared-state. The `Agent` tool is the only writer (the one place that * computes the dispatch decision in user-driven flows), and `buildHealthReport` * is a reader that snapshots via `computeDispatchHistogram()`. The * `recentErrors` section on the same report already follows this pattern, so * we're staying consistent with the existing health-report conventions. */ import type { OrchestrationMode } from "./agent-registry.js"; /** * Final dispatch kind the orchestrator fans out into. For an `auto`-mode call, * this is what the heuristic picked; for explicit `single`/`swarm`/`crew` * calls, this matches the configured mode verbatim. */ export type DispatchKind = "single" | "swarm" | "crew"; /** * Where the decision came from. `explicit` means the user pinned the mode via * settings (`single` / `swarm` / `crew` directly); `auto-heuristic` means the * user-pinned mode was `auto` and the prompt-analysis heuristic picked the * concrete kind. */ export type DispatchSource = "explicit" | "auto-heuristic"; export interface DispatchDecision { /** `Date.now()` at the time the decision was recorded. */ timestamp: number; /** What got fanned out. */ kind: DispatchKind; /** User-pinned `OrchestrationMode` at decision time (`auto` → heuristic fired). */ configuredMode: OrchestrationMode; /** * `explicit` if the user pinned the same mode in settings, * `auto-heuristic` if the user pinned `auto` and this entry records the * concrete kind the heuristic picked for this prompt. */ source: DispatchSource; /** Prompt length in characters — useful context for histogram follow-ups. */ promptLength: number; /** * Short description string from the agent call, for triage when the user * sees "the heuristic picked single 4 times in a row" and wants to know * what those 4 prompts were. */ description: string; } /** (Re)configure the ring buffer. Mirrors `configureAuditLogger` shape. */ export declare function configureDispatchHistory(config?: { maxEntries?: number; }): void; /** * Record one dispatch decision. Called by the Agent tool's execute path * immediately after `resolveOrchestrationMode(...)` resolves the decision. * * Records are appended at the tail; older entries slide off the head when the * cap (`maxEntries`, default 200) is exceeded — a true FIFO ring, so the most * recent N are always preserved for the histogram. * * Side effect: emits `subagent:dispatch_decision` via `emitTelemetry(...)` * so downstream consumers (sentry / splunk / Go cinematic sidecar) see the * same decision flow as the in-memory ring buffer. The emit happens AFTER * the ring-buffer write so a telemetry handler that throws cannot corrupt * the histogram (the `emitTelemetry` helper itself logs + swallows handler * errors, see `src/telemetry.ts`). */ export declare function recordDispatchDecision(decision: Omit): void; /** * Newest → oldest. Returned array is a defensive copy so callers cannot mutate * the buffer. The name carries the ordering because the audit-logger's * sibling (`getAuditLog`) returns oldest-first — the difference is intentional, * and a contributor landing on this module should read both JSDocs before * swapping them. */ export declare function getDispatchHistory(): readonly DispatchDecision[]; /** Ordered oldest → newest. Defensive copy. */ export declare function getDispatchHistoryOldestFirst(): readonly DispatchDecision[]; export interface DispatchHistogram { /** Total spawns recorded in the buffer. */ total: number; /** Histogram across the eventual kinds (regardless of source). */ byKind: { single: number; swarm: number; crew: number; }; /** Histogram across how the kind was decided (user-pinned vs heuristic). */ bySource: { explicit: number; autoHeuristic: number; }; /** * Same as `byKind`, but only counting the auto-heuristic-decided entries. * Lets the user answer "of the prompts the heuristic saw, how many did it * route to single / swarm / crew?" — which is the exact signal the health * report is meant to surface. Returns zero entries for the case where the * user never set `auto`. */ autoPicks: { single: number; swarm: number; crew: number; }; /** Buffer cap so the TUI renderer can phrase "(none in last N spawns)". */ bufferCapacity: number; /** * Timestamp of the most recent entry, or `null` when the buffer is empty. * Lets the renderer note "last dispatch: 2s ago" so a stale session with no * recent spawns signals "no traffic yet" rather than "broken". */ lastDecisionAt: number | null; } /** * Compute the histogram from the current buffer. * * Pure / side-effect free — safe to call from any reader (no need for a * snapshotting lock since the only writer is `recordDispatchDecision` and we * accept that two adjacent reads might see one new entry each other missed). */ export declare function computeDispatchHistogram(): DispatchHistogram; /** Clear all buffered decisions. Test-only. */ export declare function clearDispatchHistory(): void; /** Reset to defaults. Test-only. */ export declare function resetDispatchHistory(): void;