Plugins

Custom Plugin

Build a custom plugin with config, actions, hooks, system text, availability checks, lifecycle, and optional runtime HTTP routes

Custom Plugin

If the thing you want to add is mainly:

  • a set of actions
  • a set of hooks
  • one system text layer
  • one lifecycle-owned runtime boundary
  • one set of runtime HTTP routes

then a custom plugin is usually the right extension point.

Current public entry points

@downcity/agent exposes:

  • plugin types and contracts
  • the BasePlugin base class
  • the local Agent plugins: [...] assembly entry point

Built-in plugin classes are exported from @downcity/plugins.

Minimal skeleton

import { BasePlugin, type PluginActions } from "@downcity/agent";

export class NotesPlugin extends BasePlugin {
  readonly name = "notes";
  readonly title = "Notes Helper";
  readonly description = "Adds note-related actions and prompt guidance.";

  readonly actions: PluginActions = {
    status: {
      execute: async ({ context }) => {
        return {
          success: true,
          data: {
            rootPath: context.rootPath,
          },
        };
      },
    },
  };
}

Attach the custom plugin to a local Agent

import { Agent, BasePlugin, type PluginActions } from "@downcity/agent";

class NotesPlugin extends BasePlugin {
  readonly name = "notes";
  readonly title = "Notes Helper";
  readonly description = "Adds note-related actions and runtime guidance.";

  readonly actions: PluginActions = {
    status: {
      allowWhenDisabled: true,
      execute: async ({ context }) => ({
        success: true,
        data: {
          rootPath: context.rootPath,
        },
      }),
    },
  };
}

const agent = new Agent({
  id: "repo-helper",
  path: "/path/to/project",
  tools: {},
  plugins: [new NotesPlugin()],
});

The most important design questions

1. What exactly are you extending

Be explicit about whether you need:

  • actions
  • hooks
  • resolve points
  • system text
  • lifecycle
  • HTTP

Do not default to implementing every field.

2. Should some actions run while disabled

Actions like:

  • status
  • install
  • configure

often need allowWhenDisabled: true.

3. Should runtime state stay inside lifecycle

If the capability must:

  • start and stop with the plugin
  • keep runtime state in memory
  • recover after restart

then model that inside plugin lifecycle instead of hiding it in unrelated hooks.

One practical rule

Your plugin logic should rely on the minimal stable plugin context when possible instead of assuming access to every runtime singleton.

If you want scenario-driven examples