RAGBackend contract used by additional datastore extensions, so agents do not change their document, chunk, query, or hit types when storage changes.
The extension chunks and embeds documents, enforces namespaces and result bounds, and delegates filtering, ranking, ordering, and limiting to the backend. Your agent decides which retrieval operations become Tools and which ingestion operations remain trusted Task code.
Choose RAG or long-term memory
Use this extension for application knowledge: documents are chunked, optionally embedded, filtered, and ranked for grounded retrieval. Use Harnest’s built-incontext.memory for deliberate, user-scoped facts and preferences that Agent Tools save and retrieve across sessions. Core memory uses literal text search, while RAG supports keyword, semantic, and hybrid ranking. Neither system adds data to a model prompt automatically.
The similarly named rag.memory(...) factory is only the process-local test backend for this RAG API; it is unrelated to context.memory. One agent may use both: core memory for explicit user facts and RAG for a searchable document corpus.
Install the extension
0.1.3 requires Harnest >=1.0.0,<2. It depends on asyncpg and imports it only when a PostgreSQL lifecycle resource starts. It does not install Elasticsearch, Neo4j, or a PostgreSQL server extension.
Supply an embedder
Implement the small provider-neutralEmbedder contract in application code:
lib/embeddings.py
Configure PostgreSQL
Create one application lifecycle resource and publish it to trusted agent code:lifecycle/knowledge.py
pgvector.
Pass an existing asyncpg pool with pool=... when the application already owns connection lifecycle. Set setup_schema=False only when operators provision the same table shape before startup.
Retrieve from an Agent Tool
tools/search_knowledge.py
Choose a search mode
search accepts a result limit from 1 to 100, portable metadata filters, and an optional finite min_score. A scalar filter requires equality. A tuple such as {"kind": ("manual", "release-note")} performs membership matching. Different fields combine with AND; tuple values within one field combine with OR. Nested metadata and caller-authored SQL or datastore query syntax are rejected.
Fetch exact chunks
Useawait knowledge.fetch(("manual-2026:0", "manual-2026:3")) when application code already knows chunk IDs. PostgreSQL returns existing chunks in caller order, omits missing IDs, and applies the active namespace without relevance ranking.
Replace documents from trusted code
ingest or delete directly to the model without explicit permission and approval policy.
Delete complete documents with await knowledge.delete(("manual-2025",), trigger="user"). The active namespace applies to the complete mutation.
Customize chunking
The defaultFixedSizeChunker(size=2000, overlap=200) creates overlapping passages at nearby whitespace and stable IDs from document identity and source order. Supply a synchronous Chunker implementation when Markdown, source code, or another structured format needs application-specific boundaries. A custom chunker must return a non-empty sequence of non-empty strings, and shared ingestion bounds still apply.
Use rag.memory(max_chunks=...) for deterministic unit tests and small local demonstrations. It implements the same replacement, filtering, scoring, and result validation contracts, but it is process-local, non-durable, and not intended for production knowledge.
Add another datastore extension
A provider extension depends onrag instead of redefining its public API:
extensions/company-search/extension.yaml
query.limit in its datastore. RAGService validates returned types, namespace isolation, score threshold, ordering, and result count before exposing hits to agent code. Harnest rejects missing or cyclic extension dependencies, starts dependencies first, and stops them last. A published provider also declares harnest-extension-rag as a Python distribution dependency.
Public API and bounds
The shared boundary permits at most 1,000 documents, 50 million source characters, and 10,000 generated chunks per ingestion request. Documents may contain up to 10 million characters, individual chunks up to 1 million, queries up to 16,384, and embeddings up to 65,536 dimensions. Metadata and filters allow 64 fields, with up to 100 scalar values in one membership filter. Empty text, duplicate identities, nested metadata, boolean vector values, and non-finite numbers fail before model or datastore I/O.