Skip to content

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.

installed CapabilityProviders
CapabilityRegistry ──► CapabilitySnapshot
│ │
├──► SourceLoaders ──────┤
├──► SourceObservers ────┤
│ │
source + constraints ────────────────►│
│ ▼
│ Planner
│ │
│ ▼
│ RoutePlan
│ │
│ ▼
├──► Chunkers ──────► Executor ──► blocks + trace + usage
├──► LanguageDetectors ──┤
├──► Classifiers ────────┤
└──► EntityExtractors ───┘
query + embedding-space ID ─────────► Embedder ──► query vectors

The 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 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
  • 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. SourceObserver runs for every plan, because a source nothing observes cannot be routed at all; signature_detection is 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 no INDX_* variable is a rule the packages below already share rather than an implementation, and lives beneath them (ADR-0034).