import type { query } from '@anthropic-ai/claude-agent-sdk'; import type { AgentRunner } from './agent-runner.js'; import { OrgBus } from './bus.js'; import { type OrgCheckpoint } from './checkpoint.js'; import { type RoleFence } from './fence.js'; import { Mailbox } from './mailbox.js'; import { PolicyEngine } from './policy.js'; import { TaskDag } from './task-dag.js'; import { type BusEvent, type DecisionGate, type OrgDef, type OrgRole, type ProviderConfig } from './types.js'; /** Resolve which AgentRunner hosts an org's role sessions. * Precedence: role `runtime` field > org def `runtime` field > * MONOMIND_RUNTIME env > auto-resolve from provider kind > undefined (the * default path, where session.ts falls back to ClaudeAgentRunner). Returning * undefined for the default path keeps Claude/Antigravity orgs byte-for-byte * unchanged. Callers pass `role.runtime ?? def.runtime` as `orgRuntime` * (see resolveRoleRunner). */ export type RuntimeKind = 'claude' | 'kimicode' | 'opencode' | 'vercel' | 'codex' | 'antigravity' | 'grok' | 'qwen' | 'crush' | 'copilot' | 'pi' /** Opt-in alternate to 'pi': keeps the pi subprocess alive for the whole * mailbox session (--mode rpc) instead of spawning fresh per turn — see * pi-rpc-runner.ts's header for the protocol source (live-verified * against pi v0.73.1, issue #179). Prefer plain 'pi' unless you * specifically want session-lifetime context continuity. */ | 'pi-rpc' /** Opt-in alternate to 'qwen': keeps the qwen subprocess alive for the * whole mailbox session (--input-format/--output-format stream-json) * instead of spawning fresh per turn — see qwen-rpc-runner.ts's header * for the protocol source (live-verified against qwen-code v0.21.13, * issue #182). One gap not independently re-verified: whether `result` * fires exactly once per turn even when qwen runs several of its own * native tools in sequence first (inferred by symmetry with the non-RPC * QwenAgentRunner, not separately live-tested for this runner). Prefer * plain 'qwen' unless you specifically want session-lifetime context * continuity. */ | 'qwen-rpc'; export type ProviderKind = 'subscription' | 'api-key' | 'base-url' | 'bedrock' | 'vertex' | 'gemini' | 'openai' | 'vercel-api-key' | 'codex' | 'antigravity'; export declare function resolveRunner(orgRuntime?: RuntimeKind, providerKind?: ProviderKind, provider?: ProviderConfig): AgentRunner | undefined; /** Per-session variant: a role's own `runtime` field wins over the org-level * one (and the env var) — including `role.runtime === 'claude'`, which forces * the default Claude path even when the org/env select another runtime. * Roles without a `runtime` inherit the org-level resolution unchanged. * If no explicit runtime is set, auto-resolve from the provider kind. */ export declare function resolveRoleRunner(roleRuntime?: RuntimeKind, orgRuntime?: RuntimeKind, roleProviderKind?: ProviderKind, orgProviderKind?: ProviderKind, roleProvider?: ProviderConfig): AgentRunner | undefined; /** Per-role token budget: a role's own `budget_tokens` wins; otherwise the * even split of run_config.budget_tokens across all roles. */ export declare function roleTokenBudget(role: OrgRole, def: OrgDef): number; /** Idle watchdog's per-tick recovery check: given the previous nudge timestamp, * the cumulative nudge count, and the timestamp of the most recent real tool * call, returns the nudge count that should carry forward now that fresh * activity means the org is no longer idle. * * `nudgedAt !== 0` means we're recovering from an outstanding nudge. Resetting * the counter here means it only ever tracks UNRESOLVED idle spells in a row, * not a lifetime total — a long-running org that goes idle and recovers any * number of times (e.g. periodic checkpoints on a slow background task) is * never punished for having had several separate, healthy idle spells over * its lifetime. Before this existed, `nudges` only ever incremented, so a run * that answered every single nudge with real work still hit the "org idle * again after 3 nudges" cap and got force-stopped on its 4th idle spell — * observed live killing an in-progress 24h soak test under 90 minutes in. * * But "recovering" must mean genuine forward progress, not just any bus * event — a bare, content-free reply to the nudge (a boss that answers with * "✓ Complete" and calls no tools at all) still updates lastActivity, so the * org isn't flagged as silent, but it accomplishes nothing: nobody outside * the boss's own turn ever sees it, since role coordination only happens via * tool calls (org_send, org_task, ...). Requiring `lastToolActivity >= * nudgedAt` — a real tool call happened AFTER this nudge was sent — closes * that gap: observed live, a boss stuck responding to four consecutive * 10-minute nudges with one-line acknowledgments and zero tool calls looped * indefinitely making no progress, because every trivial reply reset the cap * that was supposed to catch exactly this. */ export declare function resolvedIdleNudgeCount(nudgedAt: number, nudges: number, lastToolActivity: number): number; /** Bounded ring buffer for agent terminal scrollback. */ export declare class ScrollbackBuffer { private maxLines; private lines; constructor(maxLines?: number); push(line: string): void; snapshot(): string[]; clear(): void; } export interface AgentRuntime { mailbox: Mailbox; policy: PolicyEngine; done: Promise; /** 'running' until the session promise settles; 'crashed' if it rejected (see error). */ status: 'running' | 'ended' | 'crashed'; error?: string; /** Token/cost tracking for this role — persisted to runtime.json */ metrics: { tokens: number; costUsd: number; }; /** Track last message ID for threading responses */ lastMessageId?: string; /** SDK session ID — set by the session layer on first response (P2-13). Enables checkpoint resume. */ sessionId?: string; /** Per-role worktree path (workspace: 'worktree-per-role'). */ worktreePath?: string; /** Terminal scrollback — capped ring buffer of agent output lines. */ scrollback: ScrollbackBuffer; } export interface RunningOrg { def: OrgDef; run: string; bus: OrgBus; agents: Map; busEvents: () => BusEvent[]; /** Roles not yet spawned — spawned lazily on first message. */ pendingRoles?: Map; /** Spawn a pending role on demand. */ spawnRole?: (role: OrgRole) => void; /** Git worktree path if workspace: 'worktree' — cleaned up on stop. */ worktreePath?: string; /** Task DAG for structured work ordering. */ taskDag?: TaskDag; /** Directory role sessions run in — oversized mail digests are written here. */ workdir?: string; /** MonoFence guardrail instances keyed by role ID. */ fences?: Map; } export interface DaemonOpts { queryFn?: typeof query; /** Explicit agent runner (takes precedence over everything). When unset, * session.ts builds a ClaudeAgentRunner from queryFn/the default — so the * Claude path is unchanged unless MONOMIND_RUNTIME=opencode is set. */ runner?: AgentRunner; forward?: boolean; controlJson?: string; /** Enables cross-process inter-org routing: on a local delivery miss, ask the * machine-local broker whether another `monomind org` process (e.g. a * different project directory) hosts the target org, and deliver over HTTP * if so. Off by default — tests and single-process runs don't need it. */ crossProcess?: boolean; /** Base URL at which OTHER processes can reach this daemon's inbox (see * server.ts POST /api/xdeliver). Set this to make orgs hosted here * discoverable; omit for outbound-only cross-process delivery. */ inboxUrl?: string; /** Override the broker's file registry directory (tests only). */ brokerDir?: string; /** Override how long stopOrg() waits for agent sessions before proceeding anyway (tests only; default 15000ms). */ stopWaitMs?: number; /** Override the per-role crash-retry backoff schedule (tests only; default [1000,5000,15000]ms). * After this many retries a crash is terminal and triggers worker→boss notification / * boss auto-restart. */ crashBackoffsMs?: number[]; /** Override the whole-org restart backoff after the boss terminally crashes (tests only; * default [10000,30000]ms). */ bossRestartBackoffMs?: number[]; /** Auth credential for the org server (passed to broker so cross-process senders can authenticate). */ inboxCredential?: string; /** Filter tool audit events by tool name or decision (allow|deny) before forwarding */ auditFilter?: { tool?: string; decision?: 'allow' | 'deny'; }; } export declare class OrgDaemon { /** @internal */ root: string; /** @internal */ opts: DaemonOpts; /** @internal */ orgs: Map; /** @internal */ waking: Set; /** @internal */ globalSubscribers: Set<(e: BusEvent) => void>; /** @internal */ private leases; /** @internal */ private forwarders; /** @internal */ private watchdogs; /** @internal */ stopping: Map>; /** @internal */ approvals: Map; /** @internal */ approvalLocks: Map>; /** @internal */ gatesLocks: Map>; /** @internal */ questionsLocks: Map>; /** @internal */ spawning: Map>; static readonly MAX_BOSS_RESTARTS = 2; static readonly BOSS_RESTART_BACKOFF_MS: number[]; /** @internal */ bossRestartCounts: Map; /** @internal */ restarting: Set; private static readonly CONTEXT_LIMIT_RE; /** @internal */ recallUsage: Map>; /** @internal */ orgLearnedRuns: Set; /** @internal */ abandoned: Map>; constructor( /** @internal */ root: string, /** @internal */ opts?: DaemonOpts); /** Publish this daemon's inbox so orgs started AFTER this call register with the broker. */ setInboxUrl(url: string, credential?: string): void; /** subscribe to events from ALL running orgs (dashboard server uses this) */ subscribe(fn: (e: BusEvent) => void): () => void; listOrgs(): RunningOrg[]; getOrg(name: string): RunningOrg | undefined; /** Hot-reload an org definition from disk without stopping running sessions. * Applies: goal, run_config, schedule. New roles are added as pending (lazy-spawnable). * Removed roles are NOT killed — they finish their current work and won't be re-spawned. * Returns a summary of what changed. */ reloadOrgDef(name: string): { changed: string[]; newRoles: string[]; removedRoles: string[]; }; /** Names of the orgs this daemon currently has running. Snapshot — safe to * iterate while stopOrg() mutates the underlying map. */ listRunning(): string[]; /** Hook for the SSE server — registers a listener for all bus events across all orgs. */ onBusEvent?: (fn: (e: BusEvent) => void) => void; /** Snapshot of all running orgs for dashboard initial load. */ getStatusSnapshot?: () => Record; /** Resolve run_config.workspace to 'repo' | 'isolated' | an absolute path. * A relative path is resolved against the project root rather than the * daemon's cwd, which is not the same directory when `org serve` is started * from a subdirectory. */ private workspaceSetting; startOrg(name: string, taskOverride?: string, options?: { resume?: boolean; }): Promise; /** @internal */ hasOrgDef(name: string): boolean; /** @param opts.drainMs how long to let in-flight agent sessions finish before * reaping. Defaults to the short abort bound; the planned-completion path * passes a far longer window (see COMPLETE_DRAIN_MS). * @param opts.closedBy #206: tags WHY the run ended, persisted into * runtime.json so `org run` can tell a clean, goal-driven end * (closedBy: 'org-complete') from every other kind of stop (idle * watchdog, boss-restart-exhausted, manual `org stop`) and exit non-zero * for the latter. Only the org_complete auto-stop path passes this. */ stopOrg(name: string, opts?: { drainMs?: number; closedBy?: string; }): Promise; private finishStop; stopAll(): Promise; /** @internal * @param closedBy #206: why the run ended — 'org-complete' for a clean, * goal-driven end (the only value any caller currently passes); absent for * every other stop (idle watchdog, boss-restart-exhausted, manual `org * stop`). Mirrors persistCrashStateAll()'s existing closedBy: 'crash-handler' * for the process-crash path, which org.ts already reads. */ persistState(name: string, status: string, run: string, org?: RunningOrg, checkpointOverride?: OrgCheckpoint | null, closedBy?: string): void; /** Mark every currently-running org as crashed in runtime.json. * Called from process-level crash handlers — must be synchronous and best-effort. * @param error the uncaught error/rejection reason, if known — without * this, `runOutcomeResult` (org.ts)'s "crashed: " message always * read "crashed: unknown error" regardless of what actually happened. */ persistCrashStateAll(error?: string): void; private heartbeatPath; /** Write a heartbeat file so `org status` can distinguish "daemon alive" from * "daemon gone" even when runtime.json still says running. */ writeHeartbeat(): void; clearHeartbeat(): void; private checkApproval; setApproval(org: string, role: string, action: string, approved: boolean): Promise<{ ok: true; } | { ok: false; error: string; }>; askHuman(org: string, role: string, question: string): Promise; answerQuestion(org: string, role: string, questionId: string, answer: string): Promise<{ ok: true; } | { ok: false; error: string; }>; private readGates; createGate(org: string, role: string, name: string, description: string): Promise; resolveGate(org: string, gateId: string, approved: boolean, resolution?: string, resolvedBy?: string): Promise<{ ok: true; } | { ok: false; error: string; }>; listGates(org: string, status?: 'pending' | 'approved' | 'rejected'): DecisionGate[]; private dagCreateTask; private dagCompleteTask; private dagSplitTask; private dagMergeTask; private dagCancelTask; private dagBlockTask; private dagPlanGraph; recordDecision(org: string, role: string, decision: { type: 'tool' | 'handoff' | 'approval' | 'routing'; context: string; reasoning: string; alternatives?: Array<{ choice: string; score: number; reason: string; }>; outcome: string; }): void; deliver(fromOrg: string, fromRole: string, to: string, subject: string, body: string): Promise; receiveRemote(toOrg: string, toRole: string, fromQualified: string, subject: string, body: string): { ok: true; receipt: string; } | { ok: false; error: string; }; private mailBody; /** @internal */ autoWake(name: string): void; private scheduleBossRestart; /** @internal */ scheduleDeferredSpawn(name: string, running: RunningOrg, role: OrgRole, spawnRole: (role: OrgRole) => void): void; private orgMemoryNamespace; private orgMemoryDbPath; private orgMemoryUsable; private rememberOrgMemory; private recallOrgMemory; searchProjectKnowledge(query: string): Promise<{ text: string; hits: number; }>; private learnOrgKnowledge; private storeRunMemory; replayFrom(name: string, run: string): Promise; resumeOrg(name: string): Promise; } //# sourceMappingURL=daemon.d.ts.map