Writing Plugins For Clawback

How to add a new provider, ingress adapter, action executor, or worker pack to Clawback without having to understand the whole codebase first.

Audience: Contributors and developers extending Clawback. Assumes you can edit TypeScript in the repo, but not that you already know the architecture in detail.


The Big Idea

Clawback has a stable core model and plugin-capable edges.

The stable core is the product itself:

  • workspace
  • worker
  • input route
  • connection
  • work item
  • inbox item
  • review
  • activity

The plugin-capable edges are how Clawback grows:

  • connection providers
  • ingress adapters
  • action executors
  • worker packs

If you are adding something new, you are usually extending one of those four plugin classes.


What "Plugin" Means Today

Today, a plugin is not an external package dropped into a runtime folder.

It means:

  • typed manifest contracts in packages/plugin-sdk
  • first-party manifests in packages/plugin-manifests
  • control-plane lookup and behavior in services/control-plane
  • console rendering through registry-backed contracts

So the current plugin model is:

  • typed
  • first-party
  • monorepo-based
  • safe for parallel development

This is deliberate. It keeps the system understandable and avoids dynamic runtime loading too early.

For simple fallback providers, a manifest may still be enough.

For a real provider that operators are expected to configure and use, there is now an additional contract:

  • docs/architecture/plugin-operator-lifecycle-and-doctor.md

That lifecycle covers:

  • setup help
  • validation
  • probing
  • operator-facing status
  • recovery hints

The Four Main Plugin Classes

1. Connection provider

Use this when Clawback needs to connect to an external system.

Examples:

  • Gmail read-only
  • SMTP relay
  • Drive
  • Calendar

2. Ingress adapter

Use this when an external event needs to enter Clawback in a normalized form.

Examples:

  • Postmark inbound
  • Gmail watch hook

3. Action executor

Use this when Clawback needs to carry out an approved action.

Examples:

  • SMTP reviewed send
  • future Gmail send
  • future ticket creation

4. Worker pack

Use this when you want to add a new installable worker template.

Examples:

  • Client Follow-Up
  • Proposal

The Two-Layer Rule For Worker Packs

This is the single most important thing to understand.

Worker packs are split into two layers:

Manifest layer

Lives in:

  • packages/plugin-manifests

Purpose:

  • metadata
  • setup text
  • supported route kinds
  • supported action kinds
  • ordering / visibility / stability

Runtime layer

Lives in:

  • services/control-plane/src/worker-packs

Purpose:

  • system prompt
  • install behavior
  • execution defaults
  • runtime-facing pack definition

They are linked by ID and verified by tests.

Example:

  • manifest worker-pack.proposal
  • runtime pack proposal_v1

The manifest says what the pack is. The runtime pack says how it behaves.

Do not merge those layers unless an ADR changes the design.


Where Things Live

LayerPathWhat belongs there
Manifest typespackages/plugin-sdkshared TypeScript types for plugin manifests
First-party manifestspackages/plugin-manifestsactual provider/executor/worker-pack manifests
Shared API contractspackages/contractsregistry and worker-pack API response schemas
Control-plane registryservices/control-plane/src/pluginslookup helpers and tests
Runtime worker packsservices/control-plane/src/worker-packsinstall behavior, prompts, defaults
Console renderingapps/console/app/workspace/*setup, connections, install UI

The Simplest Safe Workflow

When adding a new plugin, work in this order:

  1. choose the correct plugin class
  2. add the manifest
  3. add or update shared contracts if the UI/API shape changes
  4. add runtime behavior if the plugin needs execution logic
  5. add tests
  6. verify the console or control-plane actually consumes it

That order prevents decorative manifests that never affect the product.


Example: Add A Connection Provider

Let’s say you want to add a new read-only knowledge provider.

Step 1: Add the manifest

Create a manifest under:

  • packages/plugin-manifests/src/connection-providers/

Existing example:

What the manifest should define:

  • stable plugin id
  • provider name
  • access modes
  • capabilities
  • compatible input route kinds
  • setup mode
  • secret keys
  • setup steps

Step 2: Export it

Add it to:

If you forget this, the manifest exists in code but not in the registry.

Step 3: Check the registry surfaces

The provider should now be visible through:

  • registry lookups in the control plane
  • GET /api/workspace/registry
  • the Connections UI as a manifest-driven provider

If the provider is not ready yet, mark it with the appropriate stability and let it show as Coming soon.

Step 4: Add custom setup UI only if needed

If the provider needs special setup behavior, add a custom console panel.

If it does not, the generic manifest-driven provider shell is enough.


Example: Add An Ingress Adapter

Use an ingress adapter when you need to normalize an external event into Clawback’s route/event model.

Existing examples:

Typical work:

  1. add the ingress manifest
  2. expose or reuse the right webhook/ingress handler in the control plane
  3. normalize incoming payloads into the existing source-event / route flow
  4. add tests for auth, parsing, and idempotency

Keep the adapter focused on normalization and trust boundaries. Do not make it invent a new work-item lifecycle.


Example: Add An Action Executor

Use an action executor when Clawback needs to perform a governed external action.

Existing example:

Typical work:

  1. add the executor manifest
  2. add control-plane/runtime behavior for the actual external action
  3. keep approval truth and execution truth separate
  4. update the reviewed-send path only if the new executor is meant to be live

Do not shortcut governance. If an executor changes how reviewed actions become executing, completed, or failed, review that carefully.


Example: Add A Worker Pack

This is the most common contributor task.

Step 1: Add the manifest

Create a manifest under:

  • packages/plugin-manifests/src/worker-packs/

Existing example:

This should define:

  • workerPackId
  • worker kind
  • default scope
  • supported input route kinds
  • output kinds
  • action kinds
  • required and optional connection providers
  • setup steps

Step 2: Add the runtime pack

Create or update the runtime definition in:

  • services/control-plane/src/worker-packs/

Existing example:

This is where you put:

  • summary
  • system prompt
  • installable route definitions
  • installable action capability defaults

Step 3: Register it with the app

Make sure the runtime pack is included where the control plane builds its available worker pack list.

Current assembly happens in:

Step 4: Let the tests protect you

The alignment tests should fail if your manifest and runtime pack drift.

See:

These tests check:

  • same worker-pack ID
  • same worker kind
  • same default scope
  • same route kinds
  • same action kinds
  • same output kinds

The Main Mistakes To Avoid

1. Putting execution logic in the manifest

Bad:

  • prompts
  • runtime secrets handling
  • install side effects

Those belong in runtime/control-plane code, not in the shared manifest package.

2. Creating decorative manifests

If you add a manifest but the product never consumes it, you have only created documentation, not a working extension.

Always verify:

  • registry route
  • console surface
  • runtime behavior if needed

3. Changing core lifecycle semantics in a plugin sprint

Do not let plugin work quietly redefine:

  • work item lifecycle
  • review lifecycle
  • inbox lifecycle
  • activity semantics

That belongs to the core product model.

4. Adding console-only concepts to shared manifests

Avoid putting React-specific or view-specific identifiers in shared manifests.

A good example:

  • setup step title

A bad example:

  • a React component name

How The Console Uses Plugin Metadata

Today the console uses plugin metadata for:

  • provider listing
  • setup labels and descriptions
  • worker-pack install options
  • ordering/grouping work in progress

The console may still use custom first-party panels for complex providers such as Gmail and SMTP. That is expected.

The right pattern is:

  • generic shell from manifest metadata
  • custom panel only where truly necessary

Minimal Checklist For A New Plugin

Connection provider

  • add manifest
  • export it from packages/plugin-manifests
  • confirm it appears in registry output
  • add custom setup flow only if needed
  • add tests

Ingress adapter

  • add manifest
  • add or wire ingress handler
  • normalize payload into existing route/event model
  • add auth and idempotency tests

Action executor

  • add manifest
  • wire execution behavior
  • preserve review/execution truth separation
  • add success/failure/retry tests

Worker pack

  • add manifest
  • add runtime pack
  • register runtime pack in the app
  • pass manifest/runtime alignment tests
  • verify it appears in install/setup surfaces

Good First Contributions

If you are new to this codebase, the easiest plugin-adjacent tasks are:

  • add a new experimental provider manifest that renders as Coming soon
  • improve setup metadata for an existing manifest
  • add alignment tests for a new worker pack
  • add a small console improvement that consumes existing registry metadata

These are much safer than starting with Gmail auth or reviewed-send execution.


Related Docs

For architecture background: