Skip to content

Extending pcode

pcode is meant to be bent to your workflow. The quickest way is to ask it: pcode knows how its own extension system works, so "add a /standup command that lists yesterday's commits" or "block any shell command that force-pushes to main" is a normal request. It writes the file and tells you to /reload.

Extensions: one Python file

An extension is a Python file with a setup(pcode) function. Drop it in ~/.config/pcode/extensions/ for every workspace, or in a repository's .pcode/extensions/ to share it with the team. /reload picks up changes without restarting or losing the conversation, and a broken extension is reported with its error while the session keeps working.

An extension can add:

  • Tools the model can call: wrap your ticket tracker, deploy script or internal API.
  • Guardrails on tool calls: inspect every call before it runs, rewrite its arguments, or refuse it with a message the model reads and adapts to.
  • Slash commands for you to run.
  • Instructions added to every conversation.
  • Sub-agents with their own tools and instructions, which the agent can delegate to like the built-in worker.

A complete example that keeps the agent away from secret files:

"""Refuse to edit secret files and offer a /secrets command to list them."""

from fnmatch import fnmatch

from pydantic_ai import ModelRetry

PROTECTED = (".env", ".env.*", "*.pem", "*.tfvars")
EDIT_TOOLS = {"write_file", "edit_file"}


def setup(pcode):
    def protected(path: str) -> bool:
        name = path.rsplit("/", 1)[-1]
        return any(fnmatch(name, pattern) for pattern in PROTECTED)

    @pcode.hooks.on.before_tool_execute
    async def guard(ctx, *, call, tool_def, args):
        if tool_def.name in EDIT_TOOLS and protected(str(args.get("path", ""))):
            pcode.ui.notify(f"Refused to edit {args['path']}", "warning")
            raise ModelRetry(f"{args['path']} holds secrets; ask the user to edit it.")
        return args

    pcode.instructions("Never write to .env, key, or tfvars files; ask the user instead.")

    pcode.register_command(
        "/secrets",
        "Show which file patterns are write-protected",
        lambda _: pcode.ui.notify("Protected: " + ", ".join(PROTECTED)),
    )

Extensions are plain Pydantic AI underneath, so anything a Pydantic AI capability can do, an extension can do.

Several of pcode's own features ship as extensions: web search, the browser, session recall. /extensions lists every extension with what it added, /extensions off NAME turns one off (bundled ones included), and a file of the same name in your extensions directory replaces a bundled one.

Extensions run in-process with your permissions. A repository's .pcode/extensions/ only loads once you've trusted that repository, since it's code someone else wrote; see trusting a repository's own code.

The extension guide covers the full API. It's the same document pcode reads when you ask it to write one.

Skills: reusable prompts

For a workflow that's instructions rather than code, write a skill: a SKILL.md file under .agents/skills/NAME/ (or .claude/skills/NAME/) in a repository, or ~/.agents/skills/NAME/ for yourself. Each skill becomes a slash command (/skill:NAME), and the agent is told which skill files the workspace has, so it can also read one on its own. See skills as slash commands.

Repository instructions

pcode reads AGENTS.md and CLAUDE.md from the workspace, and from parent directories up to your home folder, so build commands, conventions and known traps reach every session without repeating them. See repository instructions.

Settings

Everything else is a setting: themes and syntax styles, vi editing, a leader key for shortcuts, what goes into scrollback, send mode, and more. Set them globally with pcode config set, or per repository in a committed .pcode/preferences.json. See configuration.