agent¶
Source: src/agent.ts
An agent is content: a system prompt, a model, a set of tools. It is declared as Markdown + frontmatter, following the pi convention.
This file knows nothing about sessions or workflows: it turns files into
data. Bringing an agent to life is spawn()’s job.
Agent¶
type
export type Agent = {
/** Unique name, and how every caller refers to it. Mandatory in the file. */
name: string;
/**
* What this agent is for, in one line. Mandatory in the file.
*
* Not decoration: `route` and `orchestrate` hand this text to a model to
* decide who does the work, so a vague description produces vague routing
* that no parser can repair.
*/
description: string;
/** Markdown body, used verbatim as the system prompt. */
systemPrompt: string;
/** Allowed tools. Absent means the read-only default is applied at spawn. */
tools?: string[];
/**
* Skills this agent may load, by name - an allowlist, exactly like `tools`.
*
* Absent means none: a subagent is offered no skill it did not ask for.
* `resolveSkills` in `src/skills.ts` says where a name is looked up.
*/
skills?: string[];
/** Model pattern, e.g. `"anthropic/claude-sonnet-5"`. Absent means pi's default. */
model?: string;
/** Default lifetime, in a workflow as in `spawn`. An explicit call always wins. */
lifetime?: Lifetime;
/**
* How many subagents this agent runs at once when it delegates.
*
* Only meaningful for an agent whose `tools:` names `subagent`. It is the
* agent's own business rather than the caller's: how wide a split is worth
* making depends on how the agent was told to think about its task, which is
* what its definition says.
*/
concurrency?: number;
/**
* Default for "give this agent its own herdr split". An explicit call wins.
*
* Declaring it here is often what you want: a scout is worth watching every
* time, whoever calls it.
*/
openInHerdr?: boolean;
/** Where the definition was found - a repository's agents are loaded only on request. */
source: AgentSource;
/** File path, or a free label for an agent built in memory. */
filePath: string;
};
An agent: the “who”. Inert data, no state, no session.
AgentScope¶
type
export type AgentScope = "user" | "project" | "both";
Where to look for definitions. Defaults to "user" - see {@link loadAgents}.
AgentSource¶
type
export type AgentSource = "user" | "project" | "builtin";
Where an agent definition came from.
"builtin" is what this package ships. It is the lowest priority of the
three: a "user" definition of the same name replaces it, and a "project"
one replaces both.
findAgent¶
function
export function findAgent(agents: Agent[], name: string): Agent { /* … */ }
Looks up an agent by name, or throws.
An unknown agent name is a programming error, not a runtime failure: we do
not want a failed Result several steps later because of a typo in a
workflow.
Lifetime¶
type
export type Lifetime = "task" | "workflow" | "session";
Lifetime of a subagent - the central choice of this library.
"task": born and dies with each task. Minimal context, reproducible."workflow": lives for the duration of the workflow. Remembers iterations."session": lives as long as the pi session. Long memory, watch it.
LIFETIMES¶
const
export const LIFETIMES = ["task", "workflow", "session"] as const satisfies readonly Lifetime[];
Every {@link Lifetime}, for whoever validates one - the tool’s schema names them from here.
loadAgents¶
function
export function loadAgents(options: { cwd?: string; scope?: AgentScope; builtin?: boolean } = {}): Agent[] { /* … */ }
Discovers the available agents.
The scope defaults to "user", and that is not a detail: project agents
(.pi/agents/) are repository-controlled content, hence third-party
instructions. They are only loaded on explicit request.
Precedence runs from the least specific to the most: the shipped definitions
first when builtin is set, then the user’s, then the repository’s. Whoever
is closer to the work wins the name. builtin is off by default: a script
that asks for “the user’s agents” must not be handed ours as well. The
extension asks for them, because there it is the difference between working
out of the box and not working at all.
Discovery happens on every call: editing a .md is enough to reload it.
loadAgentsFromDir¶
function
export function loadAgentsFromDir(dir: string, source: AgentSource): Agent[] { /* … */ }
Reads every .md in a directory.
A file that does not parse is dropped in silence - that is pi’s own
behaviour for an agent, and we keep it. A missing or unreadable directory
yields [].
parseAgent¶
function
export function parseAgent(content: string, filePath: string, source: AgentSource): Agent | undefined { /* … */ }
Parses an agent definition.
Returns undefined when the frontmatter is not valid YAML, or when name or
description is missing: this is pi’s behaviour, a file that is not an agent
is ignored silently. Kept separate from {@link loadAgents} so it stays
testable without touching the disk.