Plugins

Plugin Lifecycle

Understand Agent plugin registration, unregistration, and when lifecycle.start / lifecycle.stop run

Plugin Lifecycle

Plugins belong to an Agent.

The lifecycle can be simplified to two operations:

  • register: the plugin successfully enters agent.plugins
  • unregister: the plugin is successfully removed from agent.plugins

Direct host calls through agent.plugins immediately observe the configured registry. Existing Sessions switch their plugin action and metadata execution view at the next Session step checkpoint. An execution lease keeps the old plugin lifecycle active until the current step and its tool calls finish. If the current Session turn has no later step, the queued view change is applied when the next Session turn starts. A Session fixes its system the first time it is generated, so registry changes do not alter it; newly created Sessions collect plugin system text from the latest registry.

You no longer need a separate start, stop, restart, or project-level enabled switch.

Registration Is Enablement

Plugins passed to the Agent constructor are registered automatically:

import { Agent, Workspace } from "@downcity/agent";
import { PlaywrightBrowserProvider, WebPlugin } from "@downcity/plugins/web";

const agent = new Agent({
  id: "agent",
  workspace: new Workspace({ path: process.cwd() }),
  plugins: [new WebPlugin({
    browser: new PlaywrightBrowserProvider({ cdp_url: "http://127.0.0.1:9222" }),
  })],
});

await agent.ready();

After the Agent is instantiated, you can register more plugins:

await agent.plugins.register(new SkillPlugin());

Registering the same plugin name replaces the old instance. The runtime unregisters the old plugin before registering the new one.

Unregistration Is Disablement

await agent.plugins.unregister("skill");

After unregistering:

  • agent.plugins.get("skill") returns null
  • agent.plugins.run_action({ plugin: "skill", ... }) returns unknown plugin
  • that plugin's hooks, resolves, and actions no longer participate in future runs; system text already fixed by existing Sessions is not removed retroactively

lifecycle.start / lifecycle.stop

Plugins can still declare lifecycle hooks:

  • lifecycle.start
  • lifecycle.stop
  • lifecycle.command

But they are no longer a public state machine.

The rules are:

  • register(plugin) automatically calls plugin.lifecycle?.start(context)
  • unregister(name) removes the plugin from the configured registry immediately; plugin.lifecycle?.stop(context) runs after active Session step leases release
  • agent.dispose() unregisters all plugins

Lifecycle is useful for:

  • workers
  • poll loops
  • long-lived connections
  • in-memory runtime state
  • recoverable runtime engines

Lifecycle command vs action

Use lifecycle.command when the plugin needs a runtime-control command such as:

  • reload
  • reschedule
  • restart

Use actions when the plugin is exposing normal capability work.

task is a good example of a plugin that has both.

Inspecting Registration State

agent.plugins.snapshots();
agent.plugins.status("skill");
agent.plugins.has("skill");

States are intentionally small:

  • ready
  • error

An unregistered plugin does not need a state because it is no longer in the registry.

ActionSchedule

ActionSchedule is not a plugin.

It is an Agent-level action scheduling loop. Due jobs call actions on currently registered plugins.