# Skills, tools, and extensions

> SDK recipes for loading skills, registering tools, extensions, context files, prompt templates, and subagent extension wiring.

- Repository: PrimeIntellect-ai/prime-agent
- GitHub: https://github.com/PrimeIntellect-ai/prime-agent
- Human docs: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1
- Complete Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/llms-full.txt

## Source Files

- `packages/coding-agent/examples/sdk/04-skills.ts`
- `packages/coding-agent/examples/sdk/05-tools.ts`
- `packages/coding-agent/examples/sdk/06-extensions.ts`
- `packages/coding-agent/examples/sdk/07-context-files.ts`
- `packages/coding-agent/examples/sdk/08-prompt-templates.ts`
- `packages/coding-agent/examples/extensions/subagent/agents.ts`

---

---
title: "Skills, tools, and extensions"
description: "SDK recipes for loading skills, registering tools, extensions, context files, prompt templates, and subagent extension wiring."
---

SDK composition for agent sessions centers on `createAgentSession` from `@earendil-works/pi-coding-agent`, with `DefaultResourceLoader` as the override surface for skills, extensions, AGENTS.md context files, and prompt templates. Tool allowlists pass as the `tools` option on `createAgentSession`; custom tools and commands register through the extension API (`pi.registerTool`, `pi.registerCommand`). Subagent discovery in the subagent extension sample loads markdown agent specs from user and project agent directories.

## Composition surface

| Surface | Primary API | Session attach path |
| --- | --- | --- |
| Skills | `DefaultResourceLoader` + `skillsOverride` | `createAgentSession({ resourceLoader })` |
| Tools (built-in / selected) | `createAgentSession({ tools })` | Same call; names match available tools |
| Custom tools / commands | Extension default export + `pi.registerTool` / `pi.registerCommand` | Via `resourceLoader` discovery or `extensionFactories` |
| Context files (`AGENTS.md`) | `agentsFilesOverride` + `getAgentsFiles()` | `createAgentSession({ resourceLoader })` |
| Prompt templates (`/name`) | `promptsOverride` + `getPrompts()` | `createAgentSession({ resourceLoader })` |
| Subagent specs | `discoverAgents(cwd, scope)` | Extension / multi-agent wiring (sample) |

Common imports across the SDK examples:

```ts
import {
  createAgentSession,
  createSyntheticSourceInfo,
  DefaultResourceLoader,
  getAgentDir,
  SessionManager,
  type Skill,
  type PromptTemplate,
} from "@earendil-works/pi-coding-agent";
```

`SessionManager.inMemory()` (optionally with a custom cwd) is the in-process session backend used in these recipes. Call `await loader.reload()` after constructing a `DefaultResourceLoader` with overrides before reading discovered resources or creating the session.

```text
createAgentSession
├── tools: string[]                    # e.g. ["ipython"]
├── cwd?: string                       # applied when building built-in tools
├── resourceLoader?: DefaultResourceLoader
│     ├── skillsOverride
│     ├── agentsFilesOverride
│     ├── promptsOverride
│     ├── additionalExtensionPaths
│     └── extensionFactories
└── sessionManager: SessionManager.inMemory([cwd])
```

## Skills

Skills supply specialized instructions loaded into the system prompt. Discover, filter, merge, or replace them through `DefaultResourceLoader`.

### Discovery

After `reload()`, inspect:

```ts
const { skills: allSkills, diagnostics } = loader.getSkills();
```

The skills example discovers skills from paths including:

- `cwd/.pi/skills`
- `~/.pi/agent/skills`
- additional locations covered by the loader (“etc.” in the sample comment)

Non-empty `diagnostics` are logged as warnings in the sample.

### `Skill` shape (inline / synthetic)

| Field | Type / value in sample | Role |
| --- | --- | --- |
| `name` | string | Skill identifier |
| `description` | string | Human-readable summary |
| `filePath` | string (e.g. `/virtual/SKILL.md`) | Path identity |
| `baseDir` | string | Skill base directory |
| `sourceInfo` | from `createSyntheticSourceInfo(path, { source: "sdk" })` | Provenance for synthetic skills |
| `disableModelInvocation` | boolean | Model-invocation gate |
| `kind` | `"markdown"` | Skill kind in the sample |

### Override pattern

`skillsOverride` receives the current skill set and returns `{ skills, diagnostics }`:

```ts
const customSkill: Skill = {
  name: "my-skill",
  description: "Custom project instructions",
  filePath: "/virtual/SKILL.md",
  baseDir: "/virtual",
  sourceInfo: createSyntheticSourceInfo("/virtual/SKILL.md", { source: "sdk" }),
  disableModelInvocation: false,
  kind: "markdown",
};

const loader = new DefaultResourceLoader({
  cwd: process.cwd(),
  agentDir: getAgentDir(),
  skillsOverride: (current) => {
    const filteredSkills = current.skills.filter(
      (s) => s.name.includes("browser") || s.name.includes("search"),
    );
    return {
      skills: [...filteredSkills, customSkill],
      diagnostics: current.diagnostics,
    };
  },
});
await loader.reload();

await createAgentSession({
  resourceLoader: loader,
  sessionManager: SessionManager.inMemory(),
});
```

<Note>
The sample filters discovered skills by name substrings (`browser`, `search`) then appends an inline skill. Preserve `current.diagnostics` unless you intentionally rewrite diagnostics.
</Note>

## Tools

Pass tool **names** to choose which built-in, extension, or custom tools are enabled. Names match against all available tools. Custom tools are not listed as static arrays here; they register via extensions with `pi.registerTool()` (see [Extensions](#extensions)).

### Session options

<ParamField body="tools" type="string[]" required>
Tool name allowlist. SDK samples use `["ipython"]` for the default and explicit IPython surfaces.
</ParamField>

<ParamField body="cwd" type="string">
When set, `createAgentSession()` applies that cwd when it builds the actual built-in tools. Pair with `SessionManager.inMemory(customCwd)` when the sample uses a custom project path.
</ParamField>

<ParamField body="sessionManager" type="SessionManager" required>
In the tools samples: `SessionManager.inMemory()` or `SessionManager.inMemory(customCwd)`.
</ParamField>

### Recipes

```ts
// Default / explicit IPython tool surface
await createAgentSession({
  tools: ["ipython"],
  sessionManager: SessionManager.inMemory(),
});

// Custom cwd
const customCwd = "/path/to/project";
await createAgentSession({
  cwd: customCwd,
  tools: ["ipython"],
  sessionManager: SessionManager.inMemory(customCwd),
});
```

<Info>
For custom tools, use the extensions system (`06-extensions.ts` pattern): register with `pi.registerTool()`, then enable by name through the same `tools` selection model once the tool is available to the session.
</Info>

## Extensions

Extensions intercept agent events and can register custom tools and commands. They are the unified path for extensions, custom tools, commands, and related hooks.

### Discovery defaults

By default, extension files are discovered from:

| Location | Notes |
| --- | --- |
| `~/.pi/agent/extensions/` | User agent dir |
| `<cwd>/.pi/extensions/` | Project-local |
| `settings.json` `"extensions"` array | Additional paths |

An extension is a TypeScript file that exports a default function:

```ts
export default function (pi: ExtensionAPI) { ... }
```

### SDK loader options

```ts
const resourceLoader = new DefaultResourceLoader({
  cwd: process.cwd(),
  agentDir: getAgentDir(),
  additionalExtensionPaths: ["./my-logging-extension.ts", "./my-safety-extension.ts"],
  extensionFactories: [
    (pi) => {
      pi.on("agent_start", () => {
        console.log("[Inline Extension] Agent starting");
      });
    },
  ],
});
await resourceLoader.reload();

const { session } = await createAgentSession({
  resourceLoader,
  sessionManager: SessionManager.inMemory(),
});
```

| Option | Role |
| --- | --- |
| `additionalExtensionPaths` | Extra extension file paths |
| `extensionFactories` | Inline `(pi) => void` factories without a separate file |

### ExtensionAPI surfaces (sample)

#### Events

| Event | Handler signature in sample | Notes |
| --- | --- | --- |
| `agent_start` | `async () => { ... }` | Agent lifecycle start |
| `tool_call` | `async (event) => { ... }` | Logs `event.toolName`; may return `{ block: true, reason: "..." }` to block execution, or `undefined` to allow |
| `agent_end` | `async (event) => { ... }` | Receives `event.messages` (sample logs `event.messages.length`) |

#### `pi.registerTool`

```ts
pi.registerTool({
  name: "my_tool",
  label: "My Tool",
  description: "Does something useful",
  parameters: Type.Object({
    input: Type.String(),
  }),
  execute: async (_toolCallId, params, _signal, _onUpdate, _ctx) => ({
    content: [{ type: "text", text: `Processed: ${params.input}` }],
    details: {},
  }),
});
```

| Field | Role |
| --- | --- |
| `name` | Tool name used for selection / matching |
| `label` | Display label |
| `description` | Tool description |
| `parameters` | Schema object (`Type.Object` in the sample) |
| `execute` | Async runner; returns `{ content: [{ type: "text", text }], details }` |

#### `pi.registerCommand`

```ts
pi.registerCommand("mycommand", {
  description: "Do something",
  handler: async (args, ctx) => {
    ctx.ui.notify(`Command executed with: ${args}`);
  },
});
```

### Session stream hooks (consumer side)

Outside the extension file, the session returned by `createAgentSession` supports subscribe + prompt:

```ts
session.subscribe((event) => {
  if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
    process.stdout.write(event.assistantMessageEvent.delta);
  }
});

await session.prompt("List files in the current directory.");
```

## Context files (`AGENTS.md`)

Context files inject project-specific instructions into the system prompt. Discovery walks up from `cwd` for `AGENTS.md` files. Override or extend with `agentsFilesOverride`.

### Override and discovery

```ts
const loader = new DefaultResourceLoader({
  cwd: process.cwd(),
  agentDir: getAgentDir(),
  agentsFilesOverride: (current) => ({
    agentsFiles: [
      ...current.agentsFiles,
      {
        path: "/virtual/AGENTS.md",
        content: `# Project Guidelines

## Code Style
- Use TypeScript strict mode
- No any types
- Prefer const over let`,
      },
    ],
  }),
});
await loader.reload();

const discovered = loader.getAgentsFiles().agentsFiles;
// each entry: { path, content }

await createAgentSession({
  resourceLoader: loader,
  sessionManager: SessionManager.inMemory(),
});
```

| API | Returns / accepts |
| --- | --- |
| `agentsFilesOverride(current)` | `{ agentsFiles: Array<{ path, content }> }` |
| `getAgentsFiles()` | `{ agentsFiles }` with `path` and `content` (length used for logging) |

To disable context files entirely, return an empty list from `agentsFilesOverride` (commented intent in the sample).

## Prompt templates

File-based templates inject content when invoked as `/templatename`. Discover from:

- `cwd/.pi/prompts/`
- `~/.pi/agent/prompts/`

### `PromptTemplate` shape

| Field | Sample value | Role |
| --- | --- | --- |
| `name` | `"deploy"` | Slash command name → `/deploy` |
| `description` | string | Listing text |
| `filePath` | e.g. `/virtual/prompts/deploy.md` | Path identity |
| `sourceInfo` | `createSyntheticSourceInfo(..., { source: "sdk" })` | Synthetic provenance |
| `content` | markdown string | Injected body |

### Override pattern

```ts
const deployTemplate: PromptTemplate = {
  name: "deploy",
  description: "Deploy the application",
  filePath: "/virtual/prompts/deploy.md",
  sourceInfo: createSyntheticSourceInfo("/virtual/prompts/deploy.md", { source: "sdk" }),
  content: `# Deploy Instructions

1. Build: npm run build
2. Test: npm test
3. Deploy: npm run deploy`,
};

const loader = new DefaultResourceLoader({
  cwd: process.cwd(),
  agentDir: getAgentDir(),
  promptsOverride: (current) => ({
    prompts: [...current.prompts, deployTemplate],
    diagnostics: current.diagnostics,
  }),
});
await loader.reload();

const discovered = loader.getPrompts().prompts;
// log: `/${template.name}: ${template.description}`

await createAgentSession({
  resourceLoader: loader,
  sessionManager: SessionManager.inMemory(),
});
```

## Subagent extension wiring

The subagent extension sample (`packages/coding-agent/examples/extensions/subagent/agents.ts`) discovers markdown agent configs for multi-agent use. It depends on `getAgentDir` and `parseFrontmatter` from `@earendil-works/pi-coding-agent`.

### Types

```ts
export type AgentScope = "user" | "project" | "both";

export interface AgentConfig {
  name: string;
  description: string;
  tools?: string[];
  model?: string;
  systemPrompt: string;
  source: "user" | "project";
  filePath: string;
}

export interface AgentDiscoveryResult {
  agents: AgentConfig[];
  projectAgentsDir: string | null;
}
```

### Directory layout

| Source | Path resolution |
| --- | --- |
| User agents | `path.join(getAgentDir(), "agents")` |
| Project agents | Walk upward from `cwd` until a directory `.prime/agent/agents` exists; nearest wins |

Only `.md` files that are regular files or symbolic links are loaded. Missing dirs and unreadable files are skipped silently.

### Frontmatter contract

`parseFrontmatter` is applied to each file. Required frontmatter fields:

- `name`
- `description`

Optional:

- `tools` — comma-separated string, trimmed, empty tokens dropped → `tools?: string[]`
- `model` — string

The markdown body becomes `systemPrompt`. Files missing `name` or `description` are skipped.

### `discoverAgents(cwd, scope)`

```ts
export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult
```

| `scope` | Behavior |
| --- | --- |
| `"user"` | Only user agents |
| `"project"` | Only project agents (if project dir found) |
| `"both"` | User first, then project; **same name is overwritten by project** (Map set order) |

Returns `{ agents, projectAgentsDir }` where `projectAgentsDir` is the discovered project path or `null`.

### Listing helper

```ts
formatAgentList(agents, maxItems): { text: string; remaining: number }
```

- Empty list → `{ text: "none", remaining: 0 }`
- Otherwise formats up to `maxItems` as `name (source): description` joined by `"; "`, with `remaining` count for overflow.

### Extension package layout (sample)

```text
packages/coding-agent/examples/extensions/subagent/
└── agents.ts    # discovery + AgentConfig + formatAgentList
```

Wire discovered agents into your extension’s tool or spawn surface using the returned `AgentConfig` fields (`tools`, `model`, `systemPrompt`, `source`, `filePath`). The sample file itself only implements discovery and listing.

## ResourceLoader checklist

Use this order for every override-based recipe:

<Steps>
  <Step title="Construct loader">
    `new DefaultResourceLoader({ cwd, agentDir: getAgentDir(), ...overrides })`
  </Step>
  <Step title="Reload">
    `await loader.reload()`
  </Step>
  <Step title="Inspect (optional)">
    `getSkills()`, `getAgentsFiles()`, or `getPrompts()` and handle `diagnostics` where returned
  </Step>
  <Step title="Create session">
    `await createAgentSession({ resourceLoader: loader, sessionManager: SessionManager.inMemory() })` — add `tools` / `cwd` as needed
  </Step>
</Steps>

### Override callback return shapes

| Override | Return object |
| --- | --- |
| `skillsOverride` | `{ skills, diagnostics }` |
| `agentsFilesOverride` | `{ agentsFiles }` |
| `promptsOverride` | `{ prompts, diagnostics }` |

Synthetic resources should use `createSyntheticSourceInfo(path, { source: "sdk" })` for `sourceInfo` when defining inline skills or prompt templates.

## Example index

| Example path | Topic |
| --- | --- |
| `packages/coding-agent/examples/sdk/04-skills.ts` | Skills filter/merge + synthetic skill |
| `packages/coding-agent/examples/sdk/05-tools.ts` | `tools: ["ipython"]`, custom cwd |
| `packages/coding-agent/examples/sdk/06-extensions.ts` | Extension paths, factories, events, registerTool/Command |
| `packages/coding-agent/examples/sdk/07-context-files.ts` | AGENTS.md discovery + override |
| `packages/coding-agent/examples/sdk/08-prompt-templates.ts` | `/name` prompt templates |
| `packages/coding-agent/examples/extensions/subagent/agents.ts` | Subagent markdown discovery and scope |

## Constraints and failure modes

| Area | Constraint from samples |
| --- | --- |
| Tool selection | Names must match available tools; custom tools come from extensions, not a separate `createAgentSession` custom-tool field |
| Extension `tool_call` | Return `{ block: true, reason }` to block; `undefined` allows |
| Context disable | Empty `agentsFiles` via `agentsFilesOverride` |
| Subagent frontmatter | No `name` or `description` → file ignored |
| Subagent tools frontmatter | Comma-separated; empty after trim → `tools` omitted |
| Scope `"both"` | Project agent **wins** on name collision |
| Project agents path | `.prime/agent/agents` (not `.pi/...`) while skills/prompts/extensions samples use `.pi` under cwd and agent dir |
| Diagnostics | Skills and prompts overrides preserve `current.diagnostics` in samples |

## Related pages

<CardGroup>
  <Card title="Minimal SDK agent" href="/sdk-minimal">
    Bootstrap `createAgentSession`, custom prompt and model wiring, first-run shape.
  </Card>
  <Card title="Sessions and full control" href="/sdk-sessions-control">
    Session management, settings injection, runtime hooks, full-control composition.
  </Card>
  <Card title="Skills model" href="/skills-model">
    Skills as packages, SKILL.md constraints, collision precedence, project vs personal scope.
  </Card>
  <Card title="Create and install skills" href="/create-skills">
    Author skill packages, required frontmatter, load-path verification.
  </Card>
  <Card title="Built-in skills reference" href="/builtin-skills">
    Catalog of shipped skills and invocation roles.
  </Card>
  <Card title="Extensions and custom tools" href="/extensions">
    Register extensions, custom tools, plan-mode and subagent samples, allowlists.
  </Card>
  <Card title="Subagents and messaging" href="/subagents-messaging">
    Child agents, agent-message surface, multi-agent orchestration constraints.
  </Card>
</CardGroup>
