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
| Layer | Path | What belongs there |
|---|---|---|
| Manifest types | packages/plugin-sdk | shared TypeScript types for plugin manifests |
| First-party manifests | packages/plugin-manifests | actual provider/executor/worker-pack manifests |
| Shared API contracts | packages/contracts | registry and worker-pack API response schemas |
| Control-plane registry | services/control-plane/src/plugins | lookup helpers and tests |
| Runtime worker packs | services/control-plane/src/worker-packs | install behavior, prompts, defaults |
| Console rendering | apps/console/app/workspace/* | setup, connections, install UI |
The Simplest Safe Workflow
When adding a new plugin, work in this order:
- choose the correct plugin class
- add the manifest
- add or update shared contracts if the UI/API shape changes
- add runtime behavior if the plugin needs execution logic
- add tests
- 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
ConnectionsUI 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:
- add the ingress manifest
- expose or reuse the right webhook/ingress handler in the control plane
- normalize incoming payloads into the existing source-event / route flow
- 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:
- add the executor manifest
- add control-plane/runtime behavior for the actual external action
- keep approval truth and execution truth separate
- 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: