Downcity
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
  • built-in plugin exports
  • the local Agent plugins: [...] assembly entry point

Minimal skeleton

import type { Plugin } from "@downcity/agent";

export const notesPlugin: Plugin = {
  name: "notes",
  title: "Notes Helper",
  description: "Adds note-related actions and prompt guidance.",
  actions: {
    status: {
      execute: async ({ context }) => {
        return {
          success: true,
          data: {
            rootPath: context.rootPath,
          },
        };
      },
    },
  },
};

Attach the custom plugin to a local Agent

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

const notesPlugin: Plugin = {
  name: "notes",
  title: "Notes Helper",
  description: "Adds note-related actions and runtime guidance.",
  actions: {
    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: [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