Plugin author's guide¶
You're writing a new extractor, Subject Authority, exporter, benchmark suite, or conformance fixture. Each page below is that family's contract: what to build, where it plugs in, and the rules no function signature states: identity constants you must never change, naming, registration, lifecycle.
| You want to | Start here | Protocol lives in |
|---|---|---|
| Write a new extractor (Reddit-like source, domain API, file format) | Extractors | particles/extraction/ |
| Resolve subjects against a new external ontology | Subject authorities | particles/ingest/authorities/ |
| 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/ |
Extraction and conformance live in the other distribution. They are
Client-layer, so they ship in linkedparticles-core and their source is in
the particles-core-py repository; Subject Authorities, 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 guide is the contract¶
Each family page states that family's contract in full: the protocol's shape, and the normative rules around it that a signature cannot carry: which module constants are identity and must stay stable across versions, how a plugin is named and registered, what it may and may not do at extraction or export time, and what a change to an existing plugin obliges. Those rules are binding; a plugin that follows the protocol's signatures but breaks one of them is a broken plugin.
The protocol itself lives once, in particles/<package>/registry.py (or
the family's schema.py / contract.py), and the guide links it rather
than copying it. Read the two together: the page for the rules, the source
for the exact signatures. If they ever disagree, that is a bug in one of
them. Please report it.
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 rendersPOST /lintfindings as in-vault callouts and lets you act on each one (link / confirm / retract) over the engine. It complements, and never replaces, the read-onlyobsidianexporter. - 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.