/** * Agent dispatcher — 5-tier resolution wrapper for the playbook runtime. * * This module ships the canonical `AgentDispatcher` implementation consumed by * `@cleocode/playbooks` runtime. The runtime declares a structural * `AgentDispatcher` interface but injects it purely by dependency; the * implementation lives here in `@cleocode/core` so the playbook package keeps * its zero-dependency-on-store invariant intact. * * Resolution precedence (highest wins): * * 1. `meta` — agents shipped by `@cleocode/agents/meta/` * (meta-agents: agent-architect, skill-architect, ...) * 2. `project` — `/.cleo/cant/agents/.cant` * (registry rows tagged `tier='project'`) * 3. `global` — `~/.local/share/cleo/cant/agents/.cant` * (registry rows tagged `tier='global'`) * 4. `packaged` — `@cleocode/agents/seed-agents/.cant` * (registry rows tagged `tier='packaged'`) * 5. `fallback` — synthesised envelope when the bundled seed file exists but * no registry row has been written yet * * Tiers 2-5 delegate to the existing `resolveAgent()` helper in * `@cleocode/core/store/agent-resolver`. Tier 1 (`meta`) is added here without * mutating that function — meta-agents are filesystem-only and need no registry * row, so a dedicated lookup walks the bundled `packages/agents/meta/` * directory first before the standard resolver runs. * * The dispatcher does NOT invoke an LLM or spawn a subprocess — it produces a * `AgentDispatchResult` envelope with the resolved agent metadata and lets the * playbook runtime decide how to consume it. Call sites that need actual LLM * spawning wire a different dispatcher (e.g. the CLI's orchestrate engine). * * @module playbooks/agent-dispatcher * @task T1239 — meta-agent infrastructure (epic T1232) * @see ADR-055 — agents architecture & meta-agents */ import type { DatabaseSync as _DatabaseSyncType } from 'node:sqlite'; import type { ResolvedAgent } from '@cleocode/contracts'; type DatabaseSync = _DatabaseSyncType; /** * Tier id for the meta-agent lookup added on top of the 4-tier resolver. * * Exported as a constant so callers (tests, CLI audit surfaces) can assert * against the canonical string instead of hard-coding `'meta'`. * * @task T1239 */ export declare const AGENT_TIER_META: "meta"; /** * Dispatch input envelope. Shape mirrors `AgentDispatchInput` exported by * `@cleocode/playbooks/runtime` so a core dispatcher can be passed straight * into `executePlaybook()` without a wrapper. * * @task T1239 */ export interface DispatchContext { /** Playbook run identifier (FK into `playbook_runs.run_id`). */ runId: string; /** Node identifier within the run graph. */ nodeId: string; /** Agent identity resolved from `node.agent` (or `node.skill`). */ agentId: string; /** Task identifier lifted from `context.taskId`, falls back to `runId`. */ taskId: string; /** Snapshot of accumulated bindings at dispatch time. */ context: Record; /** 1-based iteration counter for this specific node. */ iteration: number; } /** * Dispatch result envelope. Shape mirrors `AgentDispatchResult` from the * playbook runtime for structural compatibility. * * @task T1239 */ export interface DispatchResult { status: 'success' | 'failure'; /** Key-value pairs merged into the run context on success. */ output: Record; /** Human-readable failure reason on `status === 'failure'`. */ error?: string; } /** * Canonical core dispatcher interface. Structurally matches the playbook * runtime's `AgentDispatcher` so `new CoreAgentDispatcher(...)` can be passed * directly as `dispatcher` in `executePlaybook({ dispatcher, ... })`. * * @task T1239 */ export interface AgentDispatcher { /** Execute a single `agentic` node; return a success/failure envelope. */ dispatch(context: DispatchContext): Promise; } /** * Attempt to resolve `agentId` in the meta-tier (filesystem-only). * * Meta-agents live in `@cleocode/agents/meta/` and are not required to have a * registry row — they are first-class bundled artefacts. This helper reads the * `.cant` file directly, hashes it, and synthesises a `ResolvedAgent` envelope * tagged `tier='fallback'` with `source='meta'` so consumers can tell the * origin apart from the 4-tier resolver's `fallback` outputs. * * Returns `null` when no meta-tier file exists — the caller should then * delegate to the standard `resolveAgent()` helper. * * @param agentId - Business id of the agent to resolve. * @param overrideDir - Optional override for the meta-agents directory * (used by tests to pin a fixture location). * @returns Resolved envelope or `null` when the meta-tier misses. * @task T1239 */ export declare function resolveMetaAgent(agentId: string, overrideDir?: string): ResolvedAgent | null; /** * Options accepted by {@link CoreAgentDispatcher}. * * @task T1239 */ export interface CoreAgentDispatcherOptions { /** Open handle to global `signaldock.db`. Required. */ db: DatabaseSync; /** Absolute project root used by the `project` tier lookup. */ projectRoot?: string; /** Override for the meta-agents directory (tests only). */ metaDir?: string; /** Override for the bundled seed-agents directory (tests only). */ packagedSeedDir?: string; /** * Override for the universal-base `.cant` file used by the 5th-tier * fallback in `resolveAgent`. Tests pin this to a known-missing path so * they can still exercise the "no tier resolves" failure branch. * * @task T1241 */ universalBasePath?: string; /** * Optional hook invoked after a successful resolution. The playbook runtime * uses this to emit a `dispatcher.resolve` event for audit trails. */ onResolve?: (agent: ResolvedAgent, context: DispatchContext) => void; /** * Optional executor that performs the actual spawn once an agent has been * resolved. When unset, the dispatcher returns a success envelope with the * resolved-agent metadata in `output.agent` — suitable for tests and for * playbook runs that only need to verify dispatchability. */ executor?: (agent: ResolvedAgent, context: DispatchContext) => Promise; } /** * 5-tier agent dispatcher for the playbook runtime. * * Wraps `@cleocode/core/store/agent-resolver.resolveAgent` with a * meta-tier lookup that runs first. Never mutates the wrapped resolver — * cascades via `null` return instead. * * @example * ```ts * const dispatcher = new CoreAgentDispatcher({ db, projectRoot: process.cwd() }); * await executePlaybook({ dispatcher, ... }); * ``` * * @task T1239 */ export declare class CoreAgentDispatcher implements AgentDispatcher { private readonly db; private readonly projectRoot; private readonly metaDir; private readonly packagedSeedDir; private readonly universalBasePath; private readonly onResolve; private readonly executor; constructor(opts: CoreAgentDispatcherOptions); /** * Resolve the agent via the 5-tier cascade and optionally execute it. * * Returns `status: 'failure'` (rather than throwing) when the agent id is * absent from every tier — this keeps the playbook runtime's retry / * escalation semantics intact. * * @param context - Dispatch input from the playbook runtime. * @returns Dispatch result envelope. */ dispatch(context: DispatchContext): Promise; /** * Resolve an agent id against the 5-tier cascade without executing it. * * Useful for callers that want to audit availability before dispatch * (e.g. `cleo agent doctor`). * * @param agentId - Business id to resolve. * @returns The resolved envelope, or `null` when every tier misses. */ resolve(agentId: string): ResolvedAgent | null; /** * Assemble the {@link import('../store/agent-resolver.js').ResolveAgentOptions} * payload used by the dispatcher's lookup calls. Keeping the assembly in * one place ensures `resolve()` and `resolveTier()` stay in lock-step and * the new-in-T1241 `universalBasePath` override is never silently dropped. * * @task T1241 */ private buildResolveOptions; /** * Classify which tier bucket produced `agentId`. * * Unlike {@link resolve}, this helper reports the logical tier * (`'meta' | 'project' | 'global' | 'packaged' | 'fallback' | 'universal'`) * rather than the `AgentTier` enum baked into `ResolvedAgent`. Useful for * dispatch telemetry and for tests that need to assert meta-tier was * consulted. The `'universal'` tier was added in v2026.4.111 (T1241) as * the 5th fallback tier in the base resolver. * * @param agentId - Business id to classify. * @returns Tier label, or `null` when no tier resolves. */ resolveTier(agentId: string): typeof AGENT_TIER_META | 'project' | 'global' | 'packaged' | 'fallback' | 'universal' | null; } /** * Factory helper — create a {@link CoreAgentDispatcher} from the simplest * possible config. Prefer this over constructing directly when you only have * a database handle. * * @param db - Open `signaldock.db` handle. * @param projectRoot - Optional project root. * @returns A ready-to-dispatch instance. * @task T1239 */ export declare function createAgentDispatcher(db: DatabaseSync, projectRoot?: string): CoreAgentDispatcher; export {}; //# sourceMappingURL=agent-dispatcher.d.ts.map