Extensions
An extension is optional TypeScript code loaded by Pi. It can register a purpose-built tool, connect to Pi lifecycle events, or provide deterministic behavior that should not depend on model judgment.
The recipe manifest declares its extensions, and every agent session in the
package loads them — including delegated children. Registering a tool does not
expose it: an agent still has to list the tool in its tools allowlist, so
two roles can share one extension and see different halves of it.
Pi loads the TypeScript directly — nothing to compile, no bundler. Recipe-owned
tools receive the selected path and agent as PI_RECIPE_DIR and
PI_AGENT_NAME, so an extension can find files in its own package without a
hardcoded path.
Use an extension when the recipe needs a stable operation such as parsing a domain file, enforcing a structured transformation, or wrapping an approved internal library. Use a skill when the work is primarily a procedure the model should reason through, and use MCP for tools provided by an external service.
Extensions run as code in the Pi host, so keep them small, reviewable, and
limited to the capability the recipe actually needs. Declare runtime
dependencies in the recipe’s package.json and commit a lockfile for recipes
distributed from Git.
See the standard’s extension documentation for supported loading and dependency behavior.
Ask the user for input
An extension can pause for a question or approval with the portable interaction
helpers from @introspection-ai/recipes/interactions. They work in local Pi,
remote UIs, and headless hosts; the host decides how to render or resume the
request. Return the helper result directly and keep interaction tools
sequential so a pause cannot race another tool call.
See the interaction contract
for the askUserQuestion() and askUserApproval() examples. On the
Introspection platform, these pauses surface as task interrupts: the task
moves to awaiting_user and the caller resumes it — see
Tasks and runs.