Target architecture
Why the workspace boundaries sit where they do.
Product boundary
Section titled “Product boundary”The product has two logical stages in one deployable system:
plan()observes an input cheaply and returns a serializable, explainable route bound to a versioned capability snapshot.encode()executes either that supplied plan or a newly created plan and returns a block tree, embeddings, usage, and an execution trace.
Planning never invokes the expensive processing paths. Execution never silently changes a caller-supplied plan: it may use only the fallbacks already declared in that plan.
Workspace
Section titled “Workspace”indx-interfaces leaf contracts, extension protocols, one shared helperindx-source bounded loading, digest, media-type detection├── indx-router preflight, signatures, registry view, planning├── indx-executor capability invocation, fallback, blocks, embeddings└── indx public facade over router and executor ├── indx-app-server FastAPI transport └── indx-app-cli in-process CLI and server launcherThe arrows are dependency direction toward the packages above them:
indx-interfaceshas no workspace dependency. It is contracts and protocols with one fenced exception: a helper that is standard library only, holds no state, does no I/O and reads noINDX_*variable may live here, because it is a rule the packages below already share rather than an implementation (ADR-0034).indx-sourcedepends only onindx-interfaces. It exists as a package rather than a router module because both stages need the same bytes: the router observes them, and the executor validates a supplied plan’ssource_digestagainst them before handing the content to a capability. Loading it twice in two places is how the two stages would come to disagree about what the caller sent.indx-routerandindx-executordepend onindx-interfacesandindx-source, and never on one another.indxcomposes both and re-exports only the supported public surface.- Application packages depend on
indx;indxnever imports applications.
The protocol guide explains the cross-package ports that connect these layers and the responsibilities on each side of those boundaries.
Runtime flow
Section titled “Runtime flow”source + constraints │ ▼cheap preflight ──► capability snapshot ──► RoutePlan │ supplied or generated │ ▼ executor + fallbacks │ ▼ blocks + embedding spaces + traceRoutes have a document default, page overrides, and optional region overrides. Every selected capability exists in the recorded snapshot. A plan is bound to the source content digest, policy version, and snapshot ID so it can be replayed or rejected explicitly when incompatible.
Capability extension
Section titled “Capability extension”The extension contract is a typed CapabilityProvider. Installed distributions
will advertise providers in the indx.capabilities Python entry-point group.
Discovery uses importlib.metadata.entry_points(); the registry rejects
duplicate IDs and produces a deterministically ordered, content-addressed
snapshot. Heavy implementations must remain lazy until execution.
Discovery loads provider distributions at run time. That is not a workspace dependency: no first-party package imports a provider, and the boundary test forbids naming one, so “router and executor never depend on one another” still holds when a provider module is imported during discovery.
Hook frameworks are deferred. The first requirement is keyed discovery and a reproducible snapshot, not ordered 1:N hook invocation.
See the detailed guides for
CapabilityRegistry and
CapabilityProvider.
First implementation slice
Section titled “First implementation slice”- Native text extraction on CPU.
- Generic OCR on CPU for scanned pages.
- Generic VLM on GPU where OCR cannot meet quality.
- One optional invoice signature and invoice parser.
- One optional process-chart signature and parser, page-scoped, over PowerPoint shapes.
- Manual review or explicit failure as the terminal fallback.
Region optimization, broad modality coverage, RAG export, persistence, and separate router/executor deployments follow only after the first slice has measured baselines.
Benchmark ownership
Section titled “Benchmark ownership”Benchmark data and labels live under the active repository’s benchmarks/
contract. There is no dependency on indx-everything-bench. Every admitted
document has more than one page and carries scope-level requirement and
acceptable-route labels under pinned constraints and capability snapshots.