# Writing plugins Plugins keep provider and framework dependencies outside the core package. Public protocols cover `Source`, `Ranker`, `Policy`, `Renderer`, and `PythonTokenizer`. ## Ranker ```{testcode} import cognoxium as cx class KeywordRanker: fingerprint = "keyword-v1" def score_batch(self, query, items): query = query.casefold() return [float(query in item.payload.text_value().casefold()) for item in items] frame = cx.CognitionFrame.from_records([ {"id": "a", "payload": "release ready"}, {"id": "b", "payload": "work in progress"}, ]) ranked = frame.rank("release", ranker=KeywordRanker()) print(ranked.explain()) ``` ```{testoutput} Scan[context records] -> Rank[keyword-v1] ``` A ranker must return one numeric score per item. Invalid lengths or non-numeric results raise `PluginError`. ## Policy ```python class RejectUntrustedExternally: id = "example.reject-untrusted.v1" def evaluate(self, item, boundary): if boundary.kind == "external" and item.trust is cx.Trust.UNTRUSTED: return "untrusted_source" return None pack = frame.pack( budget=100, boundary=cx.Boundary.external("provider"), policies=[RejectUntrustedExternally()], ) ``` A policy is called once per item and returns a stable reason code or `None`. Avoid network calls in this per-item method; authenticate and enrich records before packing. ## Source `Source.load()` returns an iterable of record mappings. See the complete `StaticSource` example in {doc}`building`. The Source ID identifies the adapter; each returned item still needs a meaningful `sources` field. ## Renderer ```python class IdRenderer: id = "ids-v1" def render(self, pack): return [item.id for item in pack.items] ids = pack.render(IdRenderer()) ``` A renderer receives an immutable `ContextPack`. It must reject unsupported payloads instead of silently stringifying them. ## Python tokenizer fallback ```python class WordTokenizer: fingerprint = "words-v1" thread_safe = True def count_batch(self, values): return [len(value.split()) for value in values] profile = cx.profiles.huggingface( WordTokenizer(), profile_id="example:words-v1", ) ``` The first use emits `PerformanceWarning` because Python tokenization is a slower fallback. `count_batch()` must return one non-negative integer per value. ## Reproducibility contract Plugins must be deterministic for a stable fingerprint. Change the fingerprint whenever code, vocabulary, model, configuration, envelope accounting, or remote service version can change results. Cognoxium cannot validate that a fingerprint truthfully identifies external state.