Skip to content

Core or extension

This page tells where a change goes: into Pi Pocket’s core, or into an extension. Read it before you write code, and before you open a pull request. extensions.md tells how to write an extension.

  • Core is the code that Pi Pocket keeps for all people and all extensions. It is the server (src/server/, but not src/server/extensions/), the app (web/), and the interfaces that extensions use: host, web, and pocket.
  • An extension is a module that Pi Pocket loads through its extension loader. The owner can turn each one off in Menu → Extensions, except the system prompt (prompt.ts). There are three types:
    • A built-in extension is a module in src/server/extensions/. It comes with Pi Pocket.
    • A gallery extension is a drop-in that its author keeps in their own repository. The gallery lists it (gallery-review.md).
    • A drop-in is a module in the owner’s ~/.pi-pocket/extensions/ folder.
  • An extension point is a part of the public API that lets an extension add to Pi Pocket or change it. api-stability.md lists them.

Ask these questions in this order. The first “yes” gives the answer.

  • C1. Does it decide who may do what? Then it is core. Sign-in, roles, invites, the sessions that a person can see, steering and turns, approvals, and the access checks of routes and commands are core.
    Reason: a check must be in one place. A module can forget a check, and the owner can turn a module off.
  • C2. Does it change stored data that other parts read, or the rules for how data lasts? Then it is core. The session list, the documents in src/server/docs.ts, replay after a restart, and the keys that make an action happen once are core.
    Reason: stored data lives longer than code. A format that changes with one module can break every session that has the data.
  • C3. Does it spend money or choose a model? Then it is core. Spend limits, budgets, the rule that only people choose models, and the list of models for subagents are core.
    Reason: a limit that one module can go around is not a limit.
  • C4. Does it protect against an attack? Then it is core. The fences around text from outside, the content security policy, path checks, and limits on growth are core.
    Reason: protection that is optional is off for some people.
  • C5. Must it work the same for every extension or every person? Then the mechanism is core, as an extension point.
    Reason: two copies of one mechanism become different over time, and each copy has its own bugs.
  • C6. If all the answers are “no”, it is an extension. Optional workflows, integrations with outside services, presentation preferences, and the process of one team are extensions.
    Reason: core stays small, and people who do not want a feature do not carry it.
  • C7. Split it. Put the mechanism in core, and the workflow in an extension.
    Reason: other extensions can then use the mechanism, and people can turn the workflow off.
  • C8. Do not add a setting to core for one workflow. Write an extension.
    Reason: all people see each setting in core, and the maintainers must keep it.
  • C9. Do not copy a core check into an extension. Use it through host or web, or ask for an extension point.
    Reason: a copy does not change when core fixes the check.
  • C10. Do not patch core from a module. A module must not change core’s objects at run time (replace a method, change a prototype). It must not import core’s files at run time.
    Reason: core’s internal files can change in any release (api-stability.md).
  • C11. Make a drop-in when one person or one machine needs it.
    Reason: it survives updates, and nobody else has to keep it.
  • C12. Make a gallery extension when other people can use it. Its author keeps it in their own repository.
    Reason: people can share it with no change to Pi Pocket.
  • C13. Make a built-in extension only when Pi Pocket must ship it. It is part of the default experience, or it is the interface to a part of core (as the gallery is).
    Reason: the maintainers keep each built-in extension for all people.
  • C14. A new built-in extension uses only host and web at run time. Type imports from core are permitted. Agent Teams obeys this rule. The built-in extensions from before 0.14 do not have to change.
    Reason: the module then shows that the public API is sufficient, and it can move to the gallery later.
  • Core: linked sessions (linked-sessions.ts, host.linkedSessions). Core starts a linked session, carries messages, and lists the sessions. It acts with the rights of the person Pi works for. It refuses work in plan mode. It counts the cost of the team against the budget of the top session. It stops a team at its limits of sessions, and of messages with no message from a person. It uses keys, so a replay acts once. It gives sessions addresses, so no session ID goes into a transcript.
  • Extension: Agent Teams (extensions/agent-teams.ts and agent-teams.js). The team tool, its words to Pi, and how its calls show. It is off by default. At run time it uses only host and web, as a drop-in does.
  • Why: pull request #84 first put Agent Teams in core. In 0.14 the mechanism stayed in core, because it decides permissions, stored data, and spend (C1 to C3). The workflow became a built-in extension (C6, C7). Another team workflow can use the same linked sessions.
  • Core: web.command (pull request #36). Core decides who may run a command. It makes a durable task for each run, so a retry runs the handler once. It shows the run in Menu → Running now, and stops it there. It keeps limits, and it writes cards that Pi does not read.
  • Extension: each command. For example, /clock-zone in examples/clock.ts.
  • Why: the mechanism decides permissions and stored data (C1, C2). Each command is a workflow (C6).
  • Core: the trust rules (src/server/gallery/, host.gallery). Core reads the index strictly. It downloads files only over https, from the pinned commit. It checks each SHA-256. It installs everything off, and it never updates by itself. It warns about revoked versions.
  • Built-in extension: the gallery (gallery.ts, pocket-gallery). Its routes and its screens. When the owner turns it off, it reads nothing.
  • Why: the checks protect the owner (C4). The screens are optional (C6), but Pi Pocket must ship them (C13).
  • Gallery extension: subscription-usage (TannerMidd/pocket-subscription-usage). A page that shows how much of the owner’s Claude, ChatGPT, and OpenCode Go plans is used.
  • Core: nothing new. It uses only web.route and web.client.
  • Why: it is an integration with outside services, and only some owners have them (C6, C12). It reads the owner’s sign-ins, so the gallery review checks that its description says so (gallery-review.md).
  • Core: t() for the app’s fixed text, the fallback to English, and the loader for locale packs.
  • Extension: each language, as a locale pack that has only data.
  • Why: the keys and the fallback must be the same on all screens (C5). A language is the preference of some people (C6).
  • Core: the theme surface, which every theme can change: tokens (CSS variables), the data-part names of the parts of the screen, and their documented states. Also the loading and the checks of theme folders.
  • Not core: each theme, as a folder in ~/.pi-pocket/themes/. Duplicate the Pocket theme, change the copy, and choose it in settings.
  • Why: the tokens and the parts must be the same for every theme (C5). The look is a preference (C6). The default theme stays the one that Pi Pocket ships.
  • The theme guide and the theme surface reference tell what a theme can change.

When the public API cannot do what you need, ask for an extension point. Do not keep a patch to core in a fork if you can stop.

  1. Open an issue in TannerMidd/pi-pocket. Use the title “Extension point: ”.
  2. Show the patch that your fork has today: a link to the commit, or the diff. Mark the lines that are the extension point, and the lines that are your workflow.
  3. Tell what your extension does, in one paragraph.
  4. Tell which parts of the public API you tried, and why they are not sufficient.
  5. Propose the smallest interface that removes the patch: its names, its shapes, and who may call it.
  6. Answer C1 to C5 for it. Tell if it touches permissions, stored data, spend, or security.
  7. Name other extensions that can use it, if you know of them.

Reason: a real patch shows the real need, and the smallest change that serves it.

A maintainer answers with one of these:

  • Use what is there. The maintainer shows how.
  • A new extension point. It comes as experimental first (api-stability.md).
  • A change to core. The behavior is for all people, so it goes into core (C1 to C5).
  • No. The maintainer gives the reason when possible.
  • C15. Each new extension point has a user when it ships: a built-in extension, a gallery extension, or a tested example in docs/examples/. host.linkedSessions and pocket.toolCall shipped with Agent Teams, and web.command with the clock example.
    Reason: an interface with no user is a guess, and a wrong guess becomes a public API that Pi Pocket must keep.
  • C16. In the pull request, say where the change goes, and name the rule that decides it (C1 to C14).
    Reason: the reviewer then uses the same test that you used.
  • C17. Send a change to core and the extension that uses it as separate commits.
    Reason: the maintainer can then take the core part alone, as with linked sessions and Agent Teams.

These pages describe Pi Pocket 0.14.0. Your copy has the pages of its own version in its docs/ folder.