DocsPlugins

Plugins

A Podcode plugin is a single JavaScript module that the server imports at boot. It hands back a set of hook functions, and from then on it sits inside the agent loop — adding tools, adjusting model parameters, and watching every tool call go by.

There is no build step, no manifest, and no SDK to install. A plugin is one file with one default export.

~/.config/podcode/plugins/hello.mjs
export default {
  id: "hello",
  server({ bus, cwd, version }, { config }) {
    return {
      "chat.params": (params) => ({ ...params, temperature: 0.2 }),
    }
  },
}

Register the path in your global config, restart the server, and every model call in every session now runs at temperature 0.2.

What a plugin can do

Four hooks are defined and wired today. Implement any subset — a plugin that only wants one of them returns only that key.

HookRunsPurpose
toolOnce, at loadRegister custom tools into the agent's tool set
chat.paramsBefore every model callAdjust temperature, topP, topK, output cap, provider options
tool.execute.beforeBefore every tool runsInspect or rewrite the arguments
tool.execute.afterAfter every tool runsInspect or rewrite the result

Hook names are exact strings. A typo is not an error — the key is simply never called. See the hook reference for payload shapes and ordering.

Where a plugin runs

Plugins are imported into the server process. There is no sandbox and no permission prompt at load time: the module executes with the full privileges of the server the moment it is imported — filesystem, network, environment, everything.

That is why registration lives in the operator-owned global config (~/.config/podcode/config.json) and never in a project's .podcode/config.json. A cloned repository cannot register a plugin, cannot supply plugin config, and cannot flip the auto-discovery switch. Those keys are stripped from the project layer before it merges.

Two more boundaries are worth knowing before you write anything:

  • Server-wide, not per-workspace. Plugins load once at boot using the server's own working directory. Every workspace and every session shares the same loaded set.
  • Fail-soft, always. A plugin that fails to import is skipped and logged. A plugin whose server() throws is skipped. A hook that throws or stalls has its contribution dropped and the turn continues. Nothing a plugin does can take the server down — which also means nothing a plugin does can reliably stop a turn.

Where to go next