/** * FleetSupervisor — brain-gated shadow watcher over the generic fleet. * * The Director gives a leader agent orchestration TOOLS; this module gives * the fleet a SUPERVISOR that works even while the leader is deep in its * own task: it continuously watches queue shape + worker activity, detects * imbalance (a pending task starving behind a busy worker while a sibling * idles, a deep backlog, a stuck or repeatedly-failing worker), and — after * clearing every proposal through the tiered BrainArbiter (policy → LLM → * human, honoring the /brain risk ceiling) — acts: retargets pending * tasks ("I'm reducing your workload"), spawns a helper, steers an agent * by mail, notifies the leader, or (opt-in) terminates. * * Design lineage: BrainMonitor (watch bus → decide → intervene, cooldowns, * single engagement in flight) generalized from "steer the leader" to the * full fleet action set; SddSupervisor's brain-verdict contract reused for * the decision step. The SDD subsystem itself is untouched. * * Non-interference guarantees (also pinned by tests): * - never calls extend/deny on budget negotiations — the Director's * budgetFilter remains the sole negotiator; we only OBSERVE events * - never preempts a RUNNING task — only still-pending tasks move * - never flips autonomy mode * - dormant once the director declares work complete * * @module fleet-supervisor */ import type { EventBus } from '../kernel/events.js'; import type { TaskSpec } from '../types/multi-agent.js'; import type { FleetSupervisorConfig } from '../types/config.js'; import type { BrainArbiter } from './brain.js'; import type { FleetBus } from './fleet-bus.js'; export type { FleetSupervisorConfig } from '../types/config.js'; /** One subagent row of the snapshot the supervisor reasons over. */ export interface SupervisedSubagent { id: string; name: string; status: 'running' | 'idle' | 'stopped' | 'error'; currentTask?: string | undefined; } /** Read port — implemented over Director/coordinator state by the host. */ export interface FleetSupervisorSource { subagents(): SupervisedSubagent[]; listPendingTasks(): readonly TaskSpec[]; isWorkComplete(): boolean; } /** Action port — every mutation the supervisor may perform, brain-gated. */ export interface FleetSupervisorActions { /** Move a still-pending task to another (or any idle) worker. */ retargetPendingTask(taskId: string, subagentId: string | undefined): boolean | Promise; /** Spawn an additional helper worker. Return {error} to degrade gracefully. */ spawnHelper(input: { reason: string; /** Oldest queued task used to route the helper's role/tool profile. */ task?: TaskSpec | undefined; }): Promise<{ subagentId: string; } | { error: string; }>; /** High-priority steer mail to one agent. */ steerAgent(subagentId: string, subject: string, body: string): Promise; /** Status/steer mail to the session leader. */ notifyLeader(subject: string, body: string): Promise; /** Abort a subagent (only used when config.allowTerminate). */ terminate(subagentId: string): Promise; } export interface FleetSupervisorOptions { events: EventBus; fleet: FleetBus; brain: BrainArbiter; source: FleetSupervisorSource; actions: FleetSupervisorActions; /** Active host session id, read lazily (resume/session-swap safe). */ sessionId?: (() => string | undefined) | undefined; config?: FleetSupervisorConfig | undefined; /** Injectable clock for tests. Default Date.now. */ now?: (() => number) | undefined; } export interface SupervisorLogEntry { at: number; kind: string; subagentId?: string | undefined; taskId?: string | undefined; proposedAction: string; outcome: 'approved' | 'denied' | 'escalated' | 'skipped' | 'error'; detail: string; } interface ResolvedSupervisorConfig { enabled: boolean; intervalMs: number; cooldownMs: number; maxInterventionsPerSubagent: number; pinnedWaitMs: number; overloadPinnedThreshold: number; backlogFactor: number; stuckMs: number; failureStreak: number; allowSpawn: boolean; allowTerminate: boolean; } export declare class FleetSupervisor { private readonly opts; private readonly cfg; private readonly now; private timer; private readonly offHandles; private engaging; private stopped; /** taskId → first tick timestamp we saw it pending. */ private readonly pendingSince; /** subagentId → last observable fleet event timestamp. */ private readonly lastActivityAt; /** subagentId → consecutive non-success completions. */ private readonly failStreaks; /** Consecutive ticks the backlog condition held. */ private backlogTicks; /** `${kind}|${subject}` → last engagement ts. */ private readonly lastEngagedAt; private readonly interventionsBySubagent; /** Tasks already retargeted once — never bounced twice. */ private readonly retargetedTasks; private readonly log; constructor(opts: FleetSupervisorOptions); /** Idempotent; also re-arms after a stop() so `/supervisor on` works. */ start(): void; stop(): void; history(): readonly SupervisorLogEntry[]; /** True while the evaluation loop is armed. */ isRunning(): boolean; /** Resolved (defaulted) configuration — for status surfaces. */ configSnapshot(): Readonly; private nudgeTimer; private scheduleNudge; private isCollab; /** One evaluation pass. Public for tests; production runs it on the tick. */ evaluate(): Promise; private cooldownOk; private interventionBudgetOk; private recordIntervention; private emitSignal; private record; private emitAction; /** * Ask the brain to approve a proposal. Same contract as BrainMonitor: * `source:'system'`, the rule's proposal `recommended:true`, fallback * 'ask_human' (routes to the LLM tier under the risk ceiling before any * human). Returns the chosen option id, or null for deny/escalate. */ private decide; private engageRebalance; private engageBacklog; private engageStuck; private engageFailureStreak; private engageIdleWithWork; } //# sourceMappingURL=fleet-supervisor.d.ts.map