/** * Named workflow subagent definitions ("agentType" registry). * * A workflow script can route an agent() call to a reusable, named definition: * * agent("audit this dir", { agentType: "security-auditor" }) * * Definitions live as Markdown files under `.pi/agents/*.md` (project, cwd-relative) * and `~/.pi/agents/*.md` (user). Frontmatter binds the subagent's tools, model, * and a body prompt; project definitions win on a name collision. This mirrors * Claude Code's `.claude/agents` registry: agentType is a real binding of * tools+model+system-prompt, not a prose hint. * * Bound today: `tools` (allowlist), `disallowedTools` (denylist), `model`, * and the markdown body (`prompt`). Parsed-but-ignored for now (documented): * `mcp`, `skills`, `background`, `isolation` — each is wired independently later. */ import { type SystemPromptMode } from "./context-mode.js"; /** * Coding tools that mutate the workspace. * * Invariant (read-only fence): when `opts.readOnly` is passed to `applyToolPolicy`, * every tool whose name appears here is unconditionally stripped — the fence is * the last filter step, so an allowlist from `harness_config` or `agentType` can * never re-grant a write tool on a read-only run. * * Canonical names come from `createCodingTools(cwd)` in the Pi SDK: * `read`, `bash`, `edit`, `write` (plus `grep`, `find`, `ls` from allTools). * Only `bash`, `edit`, `write` are workspace-mutating. */ export declare const WRITE_TOOL_NAMES: ReadonlySet; export interface AgentDefinition { /** Stable identity used as the `agentType` value. */ name: string; /** One-line summary (for discoverability in the tool guideline). */ description?: string; /** Allowlist of coding-tool names the subagent may use. Undefined = all. */ tools?: string[]; /** Denylist of coding-tool names, applied after the allowlist. */ disallowedTools?: string[]; /** Model spec (`provider/modelId` or bare id) for this subagent. */ model?: string; /** Markdown body, prepended to the subagent's task as role guidance. */ prompt: string; /** Named context-inheritance posture (expands to the three primitives below). */ contextMode?: string; /** Load project AGENTS.md / context files into the subagent session. Default true. */ inheritProjectContext?: boolean; /** "append": keep base prompt + role-as-task (default); "replace": role IS the base system prompt. */ systemPromptMode?: SystemPromptMode; /** Load skills into the subagent session. Default true. */ inheritSkills?: boolean; /** Inherit the main-agent append channel (`.pi/APPEND_SYSTEM.md`). Default false (no leak). */ inheritMainRules?: boolean; /** Opaque harness selector, bound from the agentType `.md` (tentative, pending dev-system #230). */ harness_type?: string; /** Opaque harness configuration string (tentative, pending dev-system #230). */ harness_config?: string; /** Where the definition was loaded from (project wins over user). */ source: "project" | "user"; } export type AgentRegistry = Map; /** * Parse one agent-definition markdown file. Returns null only when there is no * usable content (no name derivable and an empty body). */ export declare function parseAgentDefinition(content: string, source: "project" | "user", fileName: string): AgentDefinition | null; /** * Load the agent registry once for a run. Scans the project dir then the user * dir; the FIRST definition for a name wins (project > user, then filename * order), so a name collision is resolved deterministically and silently. * * `opts` overrides the scanned directories (used by tests). */ export declare function loadAgentRegistry(cwd: string, opts?: { projectDir?: string; userDir?: string; }): AgentRegistry; /** Resolve an agentType name to its definition, or undefined if not registered. */ export declare function resolveAgentType(name: string | undefined, registry: AgentRegistry): AgentDefinition | undefined; /** * Apply a definition's tool policy to a tool list: keep only allowlisted names * (when an allowlist is given), then drop any denylisted names. An **explicit** * allowlist (even empty) is a fence — an empty allowlist means deny-all (no tools), * while `undefined` means no restriction. This matters for per-step harness * intersections: a disjoint allowlist must never widen to "all tools". When * `opts.readOnly` is true, strip every write-capable tool as the final step — * this fence is unconditional and applied AFTER allow/deny so a harness_config * allowlist can never re-grant a write tool. * * Generic over any object with a `name` so it is unit-testable without real * ToolDefinitions. */ export declare function applyToolPolicy(tools: T[], allow?: string[], deny?: string[], opts?: { readOnly?: boolean; }): T[]; /** * A stable identity string for a resolved definition, folded into the resume * call-hash so editing an agent `.md` invalidates that call's cached result. * * Already includes `harness_type` and `harness_config` fields (part of the * per-call identity hash). The top-level harness selection hash that keys the * workflow run lives in `workflow.ts` (`resolveHarnessSelection`). */ export declare function agentDefinitionKey(def: AgentDefinition | undefined): string | null; /** List registered agent types for discoverability in the tool guideline. */ export declare function listAgentTypes(registry: AgentRegistry): Array<{ name: string; description?: string; }>;