Extension protocols
The extension ports of indx: what each protocol is, why it exists, and the rules they share.
The protocols in
indx_interfaces.protocols
are the stable ports between indx packages. They describe the behavior a
collaborator must provide without making the interfaces package depend on a
router, executor, model provider, or application.
Python protocols use structural typing. An implementation satisfies one of these contracts by exposing methods with the required signatures; it does not need to inherit from the protocol. Most are runtime-checkable for coarse integration checks, while static type checking verifies their method signatures.
Executor and Embedder are the exception on the map below: they are stages of
the one deployable system rather than ports a collaborator fills, so each has one
implementation and no declared protocol. They are listed because the map is what
says how a request travels, not because something outside the workspace supplies
one.
How the protocols fit together
Section titled “How the protocols fit together”installed CapabilityProviders │ ▼ CapabilityRegistry ──► CapabilitySnapshot │ │ ├──► SourceLoaders ──────┤ ├──► SourceObservers ────┤ │ │source + constraints ────────────────►│ │ ▼ │ Planner │ │ │ ▼ │ RoutePlan │ │ │ ▼ ├──► Chunkers ──────► Executor ──► blocks + trace + usage ├──► LanguageDetectors ──┤ ├──► Classifiers ────────┤ └──► EntityExtractors ───┘
query + embedding-space ID ─────────► Embedder ──► query vectorsThe snapshot gives planning a reproducible inventory. The loaders say which URIs a source can arrive from and the observers say which media types it can be applied to at all – neither carries an ID, and what they resolve reaches the snapshot only as advertisement, outside its content hash, because they change what can be planned while the snapshot records what can be run. The plan records the snapshot and policy versions used to select routes. Execution follows those routes and their declared fallbacks, asks the installed chunkers what a chunk is once everything is read, and asks the installed language detectors what the text turned out to be written in – a boundary and a language are both execution output rather than routing decisions, so neither is advertised at all – and asks the annotation ports a request enabled by ID what the text turned out to say. Those ports are a grid of two axes: the unit handed in – document, page or chunk – against the return shape – labels, which is what a classifier answers, or spans, which is what an entity extractor answers. Five of the six cells ship; a whole-document extractor is deliberately absent, because pages already tile a document with no gaps. Annotation IDs are advertised, outside the hash, because a request has to name one, and they share one namespace across all five. Query embedding produces vectors in the same declared spaces as document embeddings.
Protocol map
Section titled “Protocol map”| Protocol | Method | Input | Output | Primary responsibility |
|---|---|---|---|---|
Planner |
plan |
PlanRequest |
RoutePlan |
Select an explainable route without running expensive processing |
Executor |
encode |
EncodeRequest |
EncodeResult |
Execute selected capabilities and report results and actuals |
Embedder |
embed |
EmbedRequest |
EmbedResult |
Encode a query in a declared embedding space |
CapabilityRegistry |
snapshot, create, observers, loaders, chunkers, language_detectors, document_classifiers, page_classifiers, chunk_classifiers, page_entity_extractors, chunk_entity_extractors, document_enrichers, page_enrichers, chunk_enrichers |
Optional snapshot ID; capability ID for creation | CapabilitySnapshot, a capability instance, or the installed observers, loaders, chunkers, language detectors, classifiers, entity extractors and enrichers |
Expose a deterministic, versioned capability inventory and reach the implementations in it |
CapabilityProvider |
descriptors, create |
Capability ID for creation | Descriptors or capability instance | Advertise and lazily construct plugin capabilities |
PageReader |
read |
Source bytes, media type, page numbers | PageOutput per page |
Read the named pages, or say explicitly that it could not |
SignatureDetector |
detect |
Source bytes, media type | SignatureMatch per recognized scope |
Recognize a document type cheaply, so planning can nominate a specialist |
SourceLoader |
fetch |
A URI whose scheme it declares | The content in chunks | Fetch the bytes behind a URI this installation was taught to resolve |
SourceObserver |
observe, sniff |
Source bytes, media type | PageEvidence per page, or nothing for a type it does not claim; a media type recognized from the bytes, or nothing |
Produce the cheap structural evidence routing decides on, and recognize the format it is evidence about |
Chunker |
chunk |
Source bytes, media type, every PageOutput |
PageChunks per claimed page, or nothing for a document it has no boundary for |
Decide what a chunk is, from structure the flat text lost |
LanguageDetector |
detect |
One text a reader produced | A LanguageScore per language, highest first, or nothing for a text too short to judge |
Name the languages a page is written in, without claiming more than the characters support |
DocumentClassifier |
classify |
The full text a document was read as | Per facet, LabelScore entries highest first; a facet omitted is no opinion |
Say what a document is, when a request enabled this classifier by ID |
PageClassifier |
classify |
The text of one page | Per facet, LabelScore entries highest first |
Say what one page is, at the unit a mixed document needs |
ChunkClassifier |
classify |
The text of one chunk | Per facet, LabelScore entries highest first |
Say what one chunk is; requires chunk granularity |
PageEntityExtractor |
extract |
The text of one page | Per label, EntitySpan entries indexing that page block’s text |
Find the names in a page and say where they are |
ChunkEntityExtractor |
extract |
The text of one chunk | Per label, EntitySpan entries indexing that chunk block’s text |
Find the names in a chunk; requires chunk granularity |
DocumentEnricher |
enrich |
The full text of one document | An Enrichment: a summary, tags, or both |
Say what a document says and what it is about |
PageEnricher |
enrich |
The text of one page | An Enrichment |
Say what one page says and is about |
ChunkEnricher |
enrich |
The text of one chunk | An Enrichment |
Say what one chunk says and is about; requires chunk granularity |
EmbeddingSpaceProvider |
embedding_spaces |
— | EmbeddingSpace descriptors |
Declare the versioned spaces this provider’s embedders form |
SourceLoaderProvider |
source_loaders |
— | SourceLoader implementations |
Declare the loaders this provider ships, and so which URI schemes become resolvable |
SourceObserverProvider |
source_observers |
— | SourceObserver implementations |
Declare the observers this provider ships, and so which media types become plannable |
ChunkerProvider |
chunkers |
— | Chunker implementations |
Declare the chunkers this provider ships, and so how encoded output is cut |
LanguageDetectorProvider |
language_detectors |
— | LanguageDetector implementations |
Declare the detectors this provider ships, and so whether blocks carry a language at all |
DocumentClassifierProvider |
document_classifiers |
— | DocumentClassifier implementations |
Declare the classifiers this provider ships, and so which IDs a request may enable |
PageClassifierProvider |
page_classifiers |
— | PageClassifier implementations |
Declare the page classifiers this provider ships |
ChunkClassifierProvider |
chunk_classifiers |
— | ChunkClassifier implementations |
Declare the chunk classifiers this provider ships |
PageEntityExtractorProvider |
page_entity_extractors |
— | PageEntityExtractor implementations |
Declare the page extractors this provider ships |
ChunkEntityExtractorProvider |
chunk_entity_extractors |
— | ChunkEntityExtractor implementations |
Declare the chunk extractors this provider ships |
DocumentEnricherProvider |
document_enrichers |
— | DocumentEnricher implementations |
Declare the document enrichers this provider ships |
PageEnricherProvider |
page_enrichers |
— | PageEnricher implementations |
Declare the page enrichers this provider ships |
ChunkEnricherProvider |
chunk_enrichers |
— | ChunkEnricher implementations |
Declare the chunk enrichers this provider ships |
VectorEncoder |
encode |
Texts or image bytes | One vector per input | Produce vectors in a declared space, for documents and queries alike |
Shared design rules
Section titled “Shared design rules”- Public inputs and outputs are strict Pydantic contracts owned by
indx-interfaces. - Planning is inspectable and does not invoke expensive processing paths. Two
things it does reach are installed implementations, and both are held to
preflight’s budget — cheap, local, deterministic, no OCR, no model, no
rendering, no network.
SourceObserverruns for every plan, because a source nothing observes cannot be routed at all;signature_detectionis opt-in per request, and a detector that cannot answer within the budget answers with nothing. - A source’s bytes are fetched by an installed loader, recognized by an
installed observer, and bounded, digested and typed by
indx-source, never by either: the ceiling and the identity a plan binds to must not vary with how the content arrived or with who recognized it. - Plans are bound to a source digest, media type, policy version, and capability snapshot.
- Execution does not silently change a supplied plan and uses only its selected route or declared fallbacks.
- Provider metadata stays lightweight; expensive implementations are initialized only when execution needs them.
- Implementations live outside
indx-interfaces, preserving the workspace’s dependency direction. The one exception is fenced: a helper that is standard library only, holds no state, does no I/O and reads noINDX_*variable is a rule the packages below already share rather than an implementation, and lives beneath them (ADR-0034).