AgentSpec

The serialisable substrate every agent reduces to.

ts
interface AgentSpec<ISchema = TSchema, OSchema = TSchema> {
  id: string;
  systemPrompt: string | (ctx: RunContext) => string | Promise<string>;
  inputSchema: ISchema;
  outputSchema: OSchema;
  model: ModelSpec;
  tools: readonly string[];
  skills?: readonly string[];
  mcpServers?: readonly string[];
  subagents?: SubagentConfig;
  outputRetries?: number;     // default 2
  plugins?: readonly HarnessPlugin[];
  quota?: RunQuota;           // { maxTurns?, maxDurationMs?, maxCostUsd? }
}

JSON-serialisable except for systemPrompt (when fn) and plugins (functions). Dynamic agents (LLM-composed at runtime) build the same shape — drover doesn’t distinguish.

ModelSpec

ts
type ModelSpec =
  | string                          // "cheap" | "google/gemini-2.5-flash-lite" | "sonnet:high"
  | { name: string; reasoning?: ReasoningLevel; temperature?: number; maxTokens?: number };

Lookup order: alias map (@drover/model’s DEFAULT_ALIASES plus per- call overrides) → provider-prefixed slug provider:modelId → pi-ai builtin model id (searched across every registered provider).

Slug form: "<name>:<reasoning>" parses cleanly. Reasoning levels: minimal | low | medium | high | xhigh. Models with reasoning: true in pi-ai’s registry default to "medium" if you don’t pick.

SubagentConfig

ts
interface SubagentConfig {
  allowed: readonly string[];   // required
  depth?: number;                // default 2
  fanOut?: number;               // default 3
}

See Subagents.

Identity builder

defineAgent(spec) → spec returns the input unchanged. The builder exists for type inference: AgentInput<S> and AgentOutput<S> derive their static types from the spec’s TypeBox schemas.

ts
const spec = defineAgent({ /* ... */ });
type In = AgentInput<typeof spec>;
type Out = AgentOutput<typeof spec>;

Spec hash

hashSpec(spec) covers every field that affects execution: id / tools / skills / mcpServers / model / systemPrompt / inputSchema / outputSchema / subagents / outputRetries / quota / plugins (by id).

Plugin function bodies aren’t included — bump the plugin’s id when its behaviour changes. Function-valued systemPrompt uses Function.toString() so source edits produce different hashes (but closure-over-variables drift is invisible — prefer string prompts on resumable agents).

Type to search…

↑↓ navigate open esc close