Downcity
PluginsExamples

Plugin and Service Composition

Use a combined scenario to show the common split where plugins own extension points and services own long-lived state

Plugin and Service Composition

Scenario

You are building a "message review" capability:

  • a plugin inserts guard and effect logic into an existing chain
  • a service owns review cache state and statistics over time

The cleanest design often becomes:

  • the plugin owns extension points
  • the service owns long-lived runtime state

A common split

ReviewCacheService

  • owns recent review results
  • provides long-lived state actions such as status and clear

ReviewPolicyPlugin

  • checks a guard point before the message enters the main chain
  • records an effect when the event was observed

Why not make it all a plugin

Because a plugin should not automatically become your long-lived state owner for:

  • worker state
  • session pools
  • instance-lifetime data

Why not make it all a service

Because a service is not the most expressive place for:

  • pipeline
  • guard
  • effect
  • resolve

A stable design takeaway

If one capability needs both:

  • runtime-chain extension
  • long-lived state

then plugin plus service collaboration is often the right split.

A minimal implementation skeleton

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

class ReviewCacheService extends BaseService {
  readonly name = "review_cache";
}

const reviewPolicyPlugin: Plugin = {
  name: "review_policy",
  title: "Review Policy",
  description: "Adds guard and effect logic to the review flow.",
  hooks: {
    guard: {
      "review.require_checked": [
        async ({ value }) => {
          const body = value as { reviewed?: unknown };
          if (body.reviewed !== true) {
            throw new Error("review is required before continuing");
          }
        },
      ],
    },
  },
};

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