Output Formats & Integrations
indx can emit four output formats. This guide covers all four, how to select one, and why the .indx archive lets you move between frameworks freely.
The final stage of every build, 06 Embed+Pack, hands the assembled KnowledgeSpace to an OutputWriter, which decides what artifact lands on disk. You can choose the default portable .indx archive, plain JSONL, or objects ready to drop straight into a LangChain or LlamaIndex application.
The OutputWriter slot
Section titled “The OutputWriter slot”OutputWriter is one of the six swappable component slots. Like every slot it is a typed Protocol, so any object that satisfies it can serialize a space. The built-in writers are resolved by name from the registry.
from pathlib import Pathfrom typing import Protocol, runtime_checkable
@runtime_checkableclass Writer(Protocol): # exported as `OutputWriter` """Serializes a KnowledgeSpace to a destination. Default: indx. Also: jsonl, langchain, llamaindex.""" name: str def write(self, space: KnowledgeSpace, dest: Path, *, name: str = "handbook") -> None: ...The canonical class is Writer, also re-exported under the alias OutputWriter (from indx.output import OutputWriter). Two members make up the entire contract:
name— a string, the writer’s registry identifier, such as"indx".write(space, dest, *, name="handbook")— materializes the space into thedestdirectory, using the keyword-onlynameas the archive base file name.
See the full slot list in the protocols reference.
--format name | Writer class | Emits | Availability |
|---|---|---|---|
indx (default) | IndxWriter | Portable Zip archive (handbook.indx) + expanded layout | Core |
jsonl | JsonlWriter | Newline-delimited documents/chunks | Core (zero-dep) |
langchain | LangChainWriter | LangChain Document objects | Extra: indx[langchain] |
llamaindex | LlamaIndexWriter | LlamaIndex Node objects | Extra: indx[llamaindex] |
Selecting a writer
Section titled “Selecting a writer”The output writer is resolved with the same precedence as every slot: explicit code argument or use() → CLI flag → indx.toml → documented default. See the configuration guide for the full precedence rules.
On the CLI
Section titled “On the CLI”# default: seal a portable .indx archiveindx ./docs --out ./ai-ready
# export newline-delimited JSONL insteadindx ./docs --out ./ai-ready --format jsonl
# emit LangChain Documents (requires the extra)indx ./docs --out ./ai-ready --format langchainIn indx.toml
Section titled “In indx.toml”[output]format = ".indx" # ".indx" and "indx" both select the default archive writerIn the SDK
Section titled “In the SDK”Pass output= as a name string or as a custom instance — see custom components.
from indx import DirectoryPipeline
# by namepipeline = DirectoryPipeline(output="jsonl")
# or swap it later; use() accepts names or instancespipeline = DirectoryPipeline().use(output="llamaindex")
space = pipeline.run("./docs", "./ai-ready").indx — the recommended artifact
Section titled “.indx — the recommended artifact”The default IndxWriter seals the space into a single .indx file — a ZIP container (deflate) with a defined internal layout. That layout holds a manifest.json carrying checksums, the index.json knowledge graph, per-chunk files under chunks/, and the vector matrix under embeddings/.
Running a build also writes the expanded form alongside the archive, so downstream tools can read either shape:
ai-ready/├── handbook.indx # the portable archive├── index.json # the knowledge graph├── chunks/ # agent-readable chunks + per-chunk context└── embeddings/ # vectors + manifestThe defining property of .indx is that it is self-contained — you can re-load it without re-processing. The manifest pins the embedder name and dimensionality, for example the default openai:text-embedding-3-small, dim 1536. A consumer therefore knows exactly which model produced the vectors and can detect a mismatch before querying.
You can hand the file to anyone and reopen it instantly:
from indx import KnowledgeSpace
space = KnowledgeSpace.load("./ai-ready/handbook.indx")hits = space.search("gdpr compliance", k=5)Use --name to control the archive base name (handbook → handbook.indx). For the full byte-level layout, manifest schema, and versioning rules, see the .indx archive reference and the index.json reference.
jsonl — zero-dependency export
Section titled “jsonl — zero-dependency export”The JsonlWriter emits newline-delimited records for documents and chunks. It ships in core and pulls no dependencies, and any tool can stream the output line by line.
This makes it a good fit for custom loaders, data warehouses, or quick scripts that do not need the sealed archive or memory-mapped vectors.
Framework writers — LangChain & LlamaIndex
Section titled “Framework writers — LangChain & LlamaIndex”The framework writers skip the archive entirely. They hand you objects your existing application already understands:
langchain(LangChainWriter) emits LangChainDocumentobjects, with page content plus metadata, ready to push into a LangChain retriever or vector store.llamaindex(LlamaIndexWriter) emits LlamaIndexNodeobjects, preserving chunk text, source provenance, and relationships for a LlamaIndex index.
Both are optional extras. Install the matching extra before selecting one:
pip install "indx[langchain]"# orpip install "indx[llamaindex]"If the extra is missing when the slot is selected, indx raises a single MissingDependencyError naming the exact pip install command. See errors & exit codes and the extras reference for the full matrix.
.indx vs framework writers
Section titled “.indx vs framework writers”.indx (default) | langchain / llamaindex | |
|---|---|---|
| Artifact | Single portable, checksummed file | Native LC Documents / LI Nodes |
| Re-loadable without re-processing | Yes — KnowledgeSpace.load(...) | No — re-derive if you change stacks |
| Carries vectors + manifest | Yes (self-describing) | Hands off to the framework’s own index |
| Best for | Archiving, sharing, future-proofing | Plugging straight into an existing LC/LI app |
Reach for a framework writer when you already have a LangChain or LlamaIndex application and want indx’s structured chunks fed in with no glue code. Reach for .indx when you want a durable, neutral artifact that you can reopen, audit, and re-target later.
A vendor-free migration foundation
Section titled “A vendor-free migration foundation”indx composes the AI stack rather than locking you into one. Every heavy capability sits behind a typed protocol with a named default: parser, LLM, VLM, embedder, store, and output.
This makes the .indx archive a neutral intermediate layer. It captures the expensive work once — walking, parsing, chunking, relating, enriching, and embedding — and the result is framework-agnostic.
That decoupling is what makes downstream migration cheap. The directory has already become a portable knowledge space, so switching the framework or stack on top of it is a configuration choice, not a re-derivation:
- Build once into
.indx, then export to LangChain today and LlamaIndex tomorrow by changing only[output].format— no re-walk, no re-parse, no re-embed. - Keep the canonical
.indxas the source of truth and treat framework writers as disposable projections of it. - Re-embedding is only required if you change the embedder itself (the manifest pins the embedder’s
nameanddim); see reproducibility.
Custom writers
Section titled “Custom writers”Need a format indx does not ship? Implement the Writer protocol (exported as OutputWriter) by setting a name string and a write(self, space, dest, *, name="handbook") method, then pass an instance to DirectoryPipeline(output=...) or use(output=...).
To distribute it for others to select by name, register it under the indx.outputs entry-point group. See custom components and authoring a plugin.
Next steps
Section titled “Next steps”- Inspect & query — read a sealed
.indxarchive back. - .indx archive reference — the full container spec.
- Extras reference — every optional dependency, including the framework writers.
- Protocols reference — the
OutputWritercontract in context.