Skip to main content
Use the official RAG extension to retrieve application knowledge from PostgreSQL or a custom RAGBackend. Its shared document and query types let you change storage without rewriting Agent Tools. The extension chunks and embeds documents, scopes access by namespace, and delegates search to the backend. Your application decides which operations to expose as tools.

Choose RAG or long-term memory

Use RAG for ranked document retrieval. Use context.memory for user facts and preferences, searched by literal text. Neither adds retrieved data to prompts automatically. rag.memory(...) is a process-local RAG test backend, separate from context.memory.

Install the extension

Version 0.1.3 requires Harnest >=1.0.0,<2 and loads asyncpg when the PostgreSQL resource starts.

Supply an embedder

Implement the small provider-neutral Embedder contract in application code:
lib/embeddings.py
The extension requires exactly one finite vector per input. Keyword-only retrieval does not require an embedder.

Configure PostgreSQL

Create one application lifecycle resource and publish it to trusted agent code:
lifecycle/knowledge.py
The backend creates a tenant-scoped table and indexes. Keyword search uses PostgreSQL full-text search; semantic and hybrid search use cosine similarity over float arrays. pgvector is not required. 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. See rag.postgres configuration for connection, embedding, chunking, and namespace options.

Retrieve from an Agent Tool

tools/search_knowledge.py
Keep stable source identity and URI fields in the Tool result so the model can cite retrieved evidence. Use portable metadata filters instead of accepting SQL from model input.

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

Use await 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

Ingestion atomically replaces supplied documents and removes obsolete chunks. Writes emit payload-free audit events. Require explicit permission and approval before exposing ingestion or deletion to a model. Delete complete documents with await knowledge.delete(("manual-2025",), trigger="user"). The active namespace applies to the complete mutation.

Customize chunking

The default FixedSizeChunker(size=2000, overlap=200) splits near whitespace with stable IDs. Supply a synchronous Chunker for format-specific boundaries. It must return non-empty strings within ingestion bounds. Use rag.memory(max_chunks=...) for unit tests and small demos. Its data is lost on exit.

Add another datastore extension

A provider extension depends on rag instead of redefining its public API:
extensions/company-search/extension.yaml
Implement RAGBackend, then wrap it with rag.service. The reference documents each required method and its result type. Backends must filter, rank, order, and limit results in the datastore. RAGService validates returned hits and namespace isolation. Declare harnest-extension-rag as a distribution dependency; Harnest starts it before dependent extensions. See the RAG API reference for service methods, data contracts, provider protocols, and ingestion bounds. Validate workload sizes against those bounds before sending large batches.

API reference

Reference: harnest.agent.