Skip to content

Plugin author's guide

You're writing a new extractor, exporter, benchmark suite, or conformance fixture. This guide gets you to "where do I start" in five minutes; the authority for each plugin family is the protocol declared in that package's registry.py.

You want to Start here Protocol lives in
Write a new extractor (Reddit-like source, domain API, file format) Extractors particles/extraction/
Write a new exporter (Notion, JSON Lines, …) Exporters particles/exporters/
Add a benchmark suite for an extractor you already have Benchmark suites particles/benchmark/
Add a conformance fixture for an extractor Conformance particles/conformance/

Two of those four live in the other distribution. Extraction and conformance are Client-layer, so they ship in linkedparticles-core and their source is in the particles-core-py repository; exporters and benchmark are Engine-layer and live here. Both distributions import as the same particles package, so this changes where you read the code, not how you import it.

The two-source rule

This guide is the welcome mat: it explains the shape of each plugin family and links to a worked example. The code is the contract — protocol signatures, normative naming, lifecycle rules. When this guide and the code disagree, the code wins.

Why split: the guide is for a stranger landing cold; the code is for a contributor already inside it. Different audiences, different reading paths. We deliberately don't duplicate the protocol definitions — your <X>Plugin protocol lives once, in particles/<package>/registry.py, and that file is what to read when this page runs out.

Where the plumbing lives

Every plugin family follows the same shape:

particles/<package>/
├── registry.py          # XxxPlugin Protocol + get_xxxs() registry
└── <name>.py            # Your new plugin module (FORMAT = "name")

Two-file rule: a new plugin is one new module + one line added to the registry's _make_* function. No CLI changes, no app.py changes, no FastAPI changes.

What about a brand-new plugin family?

If you want to add an entirely new family (e.g. "exporters for custom storage backends"), that's an architectural change rather than a plugin — raise it as a proposal before building, per the contribution process in the repository's CONTRIBUTING.md.

Front-end clients (not SDK plugins)

Front-end clients of the engine — distinct from the in-package plugin families above — live in the top-level clients/ directory and are built separately from the Python SDK. They consume the frozen FastAPI contract, not the XxxPlugin registries.

  • Obsidian lint-callout plugin (clients/obsidian-plugin/) — a TypeScript Obsidian community plugin that renders POST /lint findings as in-vault callouts and lets you act on each one (link / confirm / retract) over the engine. It complements — never replaces — the read-only obsidian exporter.
  • Web UI (clients/web-ui/) — the browser surface the engine serves at /app; see Operator guide → running in a container.

The other two doors

Plugins are written against a running system, so the other two guides are where the behaviour you are extending is described:

  • User guide — what deposit, extract, query and export look like from the outside. Exporting in particular shows the flags your exporter will be invoked with.
  • Operator guide — the knobs an operator turns against your plugin: tuning for extractor trust and calibration, configuration for how your config sub-model is loaded.