stackbone.rag
Parse, chunk, embed, store and retrieve documents on top of the agent's own Postgres. The RAG schema lives in the same database as
stackbone.databaseand shares the same connection pool, so touching both surfaces still opens one pool. You reach it through the ambientstackboneclient, the same handle on every surface, from an agent tool or a durable workflow step.
import { stackbone } from '@stackbone/sdk';
const result = await stackbone.rag.retrieve({
text: 'how do I reset my password?',
topK: 5,
namespace: 'help-center', // the collection to search
});
if (result.error) throw new Error(`${result.error.code}: ${result.error.message}`);
console.log(result.data);stackbone.rag runs the same across local development, self-host and
cloud, because it sits on the agent's own Postgres: its only dependency is
the STACKBONE_POSTGRES_URL the runtime injects. There is no separate
hosted RAG service to provision.
Mental model
stackbone.rag stores its data in these canonical tables:
rag_collections, rag_documents, rag_chunks and rag_jobs, in the
managed stackbone_platform schema that Stackbone provisions in your
agent's Postgres for you. Stackbone also keeps rebuild progress in this schema.
- The tables live in the agent's own Postgres
(
STACKBONE_POSTGRES_URL), in thestackbone_platformschema, next to your own tables. There is no separate "RAG store". stackbone.ragreuses the pool thatstackbone.databasebuilds, so an agent that touches both surfaces opens exactly one connection pool.- Stackbone provisions the tables on every install. You use
stackbone.raginstead of installing its tables yourself.
The collection owns its embedding provider, model and dimension. Ingestion and retrieval read that saved configuration before they embed text. Collections with 1536 and 768 dimensions can share the same database without mixing their results.
Configure a collection
Update your box before you create configured collections in Studio. Choose an embedding provider and model in the collection dialog. If the provider catalogue does not identify embedding models, enter the model id manually. Studio asks the box to probe the selected model. A nonempty vector of finite numbers determines the saved dimension. A failed probe leaves no configured collection on an updated box. A keyless provider is valid.
The collection details show the saved provider, model, dimension and search mode. Dimensions from 1 to 2000 use HNSW when the dimension index is available. Larger vectors, up to 16000 dimensions, use exact search. The reported search mode tells you which path is available.
You can also create a collection with your first stackbone.rag.ingest or
ingestAsync call. Its explicit model, or the SDK's configured fallback,
initializes the collection. A bare model id initializes provider default. A
provider pin such as local::nomic-embed-text initializes provider local when
the SDK sends embeddings through the box proxy. The first chunk's embedding
probes the dimension and is reused when the SDK stores that chunk.
After creation, omit model to use the saved configuration. A matching bare
model id keeps the collection's saved provider. A provider pin must match both
provider and model. A conflicting override returns rag_embedding_conflict
before the SDK embeds or writes anything. Changing the workspace fallback does
not change an existing collection.
An explicit createClient({ modelProviderBaseUrl, modelProviderKey }) keeps its
direct provider target. That target cannot route a collection assigned to a named
box provider. Use the box proxy for that collection, or use a collection assigned
to default whose model your direct endpoint serves.
Historical collections keep provider default, model
openai/text-embedding-3-small and dimension 1536. The legacy create request with
only a name and metadata still works.
Change a collection's model
Update the box and CLI before you use this feature. In Studio, open RAG, choose the collection and click Change model. Select its target provider and embedding model, or enter a model ID. Read the warning, then start the rebuild. The box probes the target and checks the source of every stored version before it accepts the change. A failed check leaves the collection unchanged.
Every stored version takes part, including unpublished and older versions. The box uses each version's saved original. When the original is missing, it uses stored chunk text instead. If neither source exists, the error names the affected versions. A storage outage or an unreadable original remains an error; it does not silently switch to chunk text.
While rebuilding, Studio shows the current and target models and the number of completed versions. Queries, uploads, publication and content deletion pause. You can still open history and download originals. A failure keeps the collection paused and retains the completed count. Fix the provider or runtime problem, then click Retry rebuild to resume the same operation. A worker restart or a lost launch receipt does not require new document versions.
When all versions finish, the collection uses the target provider, model and detected dimension. Document keys, version numbers, original links, metadata and publication flags stay intact. Try and Publish also work on older rebuilt versions. Publishing one selects that version without embedding it again.
Update the code that runs your RAG calls
The calls remain stackbone.rag.ingest, ingestAsync and retrieve.
Ingestion names a collection; retrieval names a namespace.
| Where your code runs | What must change |
|---|---|
| A packaged deep agent with the SDK external | A new runtime image can supply the corrected SDK while preserving your bundle and declared SDK dependency. Confirm which SDK bytes the image contains. |
| A compiled workflow containing the old RAG implementation | Update its installed SDK, compile the workflow again, and ship the new artifacts. Replacing the runtime package alone does not replace inlined code. |
| Local development | Update the SDK installed in your project and compile again. Check workflows/node_modules too if your workflows install their own copy. |
| Studio or the CLI | Update the box first, then use a compatible Studio and CLI. An older CLI may reject the new exact search mode. |
A creator workflow step can also keep its SDK import external. If the emitted
step does so, it loads the installed SDK. Check your artifacts before assuming
they contain the old implementation. To compile new TypeScript calls that omit
model, update your local SDK declarations too; an image update only changes
runtime bytes. An old managed ingest workflow can keep its
own fixed model, which may now conflict with the collection. Update the CLI and
regenerate that managed workflow as part of the update. The updated CLI also
provides the managed collection-rebuild workflow. Compile and ship it with the
updated box before changing existing collections.
These collection settings require coordinated schema and SDK changes. A package version alone cannot identify an unreleased build. Use a released SDK and runtime that explicitly support collection-owned embeddings once available. Upgrade instructions do not authorize skipping the SDK major release required for a breaking schema or index change.
An older box may accept a create request while ignoring its embedding fields. Current Studio checks the returned provider and model, but that check cannot undo a legacy row the older box already created. Update the box before saving. Old inlined SDK implementations and direct SQL do not acquire collection-state checks automatically.
Where RAG calls live
stackbone.rag is the ambient handle: you import it once and call it from
wherever your code runs. Two places matter for an agent:
- From an agent tool: retrieve inside a tool call and return the hits, so the model can use them to answer. A deep agent grounds its replies in your documents this way. See LangChain's tool docs.
- From a durable workflow step: ingest is
I/O that can be slow and can fail, so put it inside a
'use step'. The Workflow SDK runs each step once, persists its result and auto-retries on failure, which is what a parse → chunk → embed → store pipeline needs.
The pure helpers parse and chunk touch nothing external, so you can
also call them from a one-off script or a test.
Ingestion
Pure helpers: parse and chunk
stackbone.rag.parse(...) and stackbone.rag.chunk(...) are pure helpers.
They never touch the database and never call the embedding provider, so you
can use them anywhere (in a tool, a step, a script or a test) to iterate
on chunking before committing to ingestion:
import { stackbone } from '@stackbone/sdk';
// `parse` accepts the document bytes (or a plain string / Blob), not a URL:
// fetch the source yourself first.
const bytes = await fetch('https://example.com/post').then((r) => r.arrayBuffer());
const text = await stackbone.rag.parse(bytes);
const chunks = stackbone.rag.chunk(text, { size: 512, overlap: 64 });parse accepts a string, a Blob, a Uint8Array or an ArrayBuffer.
Markdown and plain text come back verbatim; parse flattens a PDF to one
string. The reader is text-only (no OCR): a scanned, image-only PDF has no
text layer, so parse rejects it with a clear error rather than ingesting
an empty document.
Synchronous ingest: stackbone.rag.ingest
For one-off ingestion (a setup script, a small upload handler) call
ingest directly. The promise resolves once the SDK has embedded and
persisted every chunk:
import { stackbone } from '@stackbone/sdk';
const bytes = await fetch('https://example.com/post').then((r) => r.arrayBuffer());
const text = await stackbone.rag.parse(bytes);
const result = await stackbone.rag.ingest({
id: 'post-123',
collection: 'help-center',
chunks: stackbone.rag.chunk(text),
model: 'openai/text-embedding-3-small',
metadata: { source: 'help-center', url: 'https://example.com/post' },
});
if (result.error) {
// SdkError: see "Errors" below.
throw new Error(`${result.error.code}: ${result.error.message}`);
}
console.log(`Ingested ${result.data.chunks} chunks for ${result.data.id}.`);The SDK embeds string chunks with the saved collection configuration. Pass chunks as
{ content, embedding } objects instead when you embedded them yourself: that
shape takes no model. Re-ingesting with the same id replaces all chunks for
that document atomically, useful for periodic re-crawls.
You can pass onProgress to observe per-chunk progress without paying for
the SQL job writer:
await stackbone.rag.ingest({
id: 'post-123',
collection: 'help-center',
chunks,
onProgress: (event) => console.log(event.type, event),
});Ingest inside a durable workflow step
A real ingest is slow and can fail mid-flight. Put it in a workflow
'use step'
so it runs once, persists its result and auto-retries on transient
failures:
import { z } from 'zod';
import { stackbone } from '@stackbone/sdk';
export const inputSchema = z.object({ url: z.string().url(), id: z.string() });
export const outputSchema = z.object({ chunks: z.number() });
export async function indexDocsWorkflow(input: z.infer<typeof inputSchema>) {
'use workflow';
return ingestStep(input.url, input.id);
}
async function ingestStep(url: string, id: string) {
'use step'; // runs once, persisted, retried on failure. Keep it idempotent
const bytes = await fetch(url).then((r) => r.arrayBuffer());
const text = await stackbone.rag.parse(bytes);
const result = await stackbone.rag.ingest({
id,
collection: 'help-center',
chunks: stackbone.rag.chunk(text),
metadata: { source: 'help-center', url },
});
if (result.error) throw new Error(result.error.code);
return { chunks: result.data.chunks };
}Durable ingest in one call: ingestDocuments
The step above wires the parse → chunk → embed → store pipeline by hand. To
ingest a document durably from a workflow without customising the pipeline,
call ingestDocuments from @stackbone/sdk/workflow.
It runs the built-in ingest workflow as a durable sub-workflow and resolves once
the document is stored, returning { documentId, chunks }:
import { ingestDocuments } from '@stackbone/sdk/workflow';
export async function indexDocsWorkflow(input: { markdown: string }) {
'use workflow';
// Hand it the text: the helper stages it to storage and ingests it durably.
const { documentId, chunks } = await ingestDocuments({
collection: 'docs',
content: input.markdown,
contentType: 'text/markdown',
});
return { documentId, chunks };
}It takes one of two input shapes:
{ content }: inline text or bytes. The helper stages it to the agent's storage for you (in a durable, replay-safe step keyed by the content hash) and then ingests it.{ storageKey }: a file already staged under the reservedrag/prefix (for example one an operator uploaded from Studio). The helper ingests it directly, so a large file never travels in the workflow input.
Both must be called from a workflow body. If your workspace ships its own
version of the ingest workflow, ingestDocuments uses that one instead. For a
custom pipeline, call stackbone.rag.ingest inside your own step, as above.
Asynchronous ingest: stackbone.rag.ingestAsync
For a long-running ingest you want to observe outside a workflow (a webhook
handler that must return in seconds, a backfill that takes minutes) call
ingestAsync. It allocates a rag_jobs row synchronously, returns the job
id immediately, and exposes both a streaming channel of progress events and
the final Result:
import { stackbone } from '@stackbone/sdk';
const handle = await stackbone.rag.ingestAsync({
id: 'post-123',
collection: 'help-center',
chunks,
});
if (handle.error) throw new Error(handle.error.code);
// You now have a stable job id. Hand it back to the caller and return early.
const { jobId } = handle.data;
// Drain progress events into your own logs / stream when you want to follow it.
for await (const event of handle.data.events) {
switch (event.type) {
case 'started':
console.log('rag.ingest.started', event);
break;
case 'progress':
console.log('rag.ingest.progress', event);
break;
case 'completed':
console.log('rag.ingest.completed', event);
break;
case 'failed':
console.error('rag.ingest.failed', event);
break;
}
}The handle is the observability surface for a job your own code starts:
events streams the progress, and handle.data.result settles with the
final Result. The Jobs list in Studio and stackbone rag jobs report the
durable ingest runs an upload starts. They do not read the rag_jobs rows
ingestAsync writes, so follow your own job through the handle.
The worker re-reads its job row between chunk batches. Mark that row
cancelled and the ingest stops with rag_ingest_cancelled.
Retrieval
stackbone.rag.retrieve is the only read API. Pass a text query to let
the SDK embed it for you, and namespace to name the collection to search.
A query is scoped to one collection. Without namespace it searches the
collection called default, so a document ingested into help-center is only
found when the query names it. The most common place to call it is inside an
agent tool, returning the hits so the model can ground its answer:
import { tool } from '@langchain/core/tools';
import { stackbone, z } from '@stackbone/sdk';
const searchDocs = tool(
async ({ query }: { query: string }) => {
const result = await stackbone.rag.retrieve({
text: query,
topK: 5,
namespace: 'help-center',
});
if (result.error) throw new Error(result.error.code);
// Return the hits to the model so it can answer from them.
return result.data.map((hit) => ({
score: hit.score,
content: hit.content,
}));
},
{
name: 'search_docs',
description: 'Search the help-center knowledge base before answering a question.',
schema: z.object({ query: z.string() }),
},
);topK defaults to 5. Each hit is { id, chunkIdx, content?, metadata?, score, rerankScore? }, ordered by descending cosine similarity (score is in
[0, 1], 1 = identical). rerankScore and a different order only appear
when you rerank the hits.
Filtering on metadata
Pass filter to scope the search to documents whose metadata matches a
JSON sub-object (matched server-side via jsonb @> $1):
const result = await stackbone.rag.retrieve({
text: 'pricing tiers',
namespace: 'help-center',
filter: { source: 'help-center', locale: 'en' },
topK: 10,
});V1 supports exact-match equality on simple paths. Range queries and
jsonpath operators are out of scope for now.
Precomputed embeddings
If you already have an embedding (e.g. from your own embedder or a cached vector), pass it directly:
await stackbone.rag.retrieve({
embedding: cachedQueryVector, // number[]
topK: 5,
});The pipeline skips embedding and checks the vector dimension against the saved collection. Supply vectors from that collection's model. Equal dimensions do not prove that two vectors use the same model.
Rerank the hits
Pass rerank to reorder the hits with a rerank model before they reach
your agent. A rerank model reads the question and each fragment together, so
it puts the fragment that answers the question first, where cosine order can
put a fragment that only looks similar. It helps most over a large collection
of similar documents. Rerank is off by default, because each reranked call
adds one model request.
const result = await stackbone.rag.retrieve({
text: query,
namespace: 'help-center',
topK: 5,
rerank: true,
});
if (result.error) throw new Error(result.error.code);
for (const hit of result.data) {
console.log(hit.rerankScore, hit.score, hit.content);
}rerank: true uses voyageai/rerank-3-lite. The call goes through
stackbone.ai.rerank, with the
same model provider and key as every other stackbone.ai call, so you
configure nothing new. Pass an object instead of true to change the defaults:
| Option | Default | What it does |
|---|---|---|
model |
voyageai/rerank-3-lite |
The rerank model, as a creator/model-name id. |
candidates |
topK * 4 |
How many vector hits the reranker reads before the cut to topK. Capped at 50, and never fewer than topK. |
minScore |
none | Drops every hit the reranker scored below it. See Return nothing below a threshold. |
The search first fetches candidates hits in cosine order. Your filter
and the published-version rule apply to them as usual. The reranker scores
each one, and retrieve returns the best topK in rerank order.
scorekeeps its meaning: the cosine similarity.rerankScoreis the rerank model's score for that hit.includeContent: falsestill works. The reranker reads each fragment's text, and the hits come back withoutcontent.
Return nothing below a threshold
minScore drops the hits the reranker scored below it. A question nothing in
your documents answers then returns zero hits, so your agent can say "I don't
have that" instead of answering from weak fragments. Treat an empty list as
that answer:
// Each rerank model scores on its own scale. Pick the threshold from the
// rerankScore your own questions get with `rerank: true`.
const MIN_RERANK_SCORE = 0.5;
async function searchHelpCenter(query: string) {
const result = await stackbone.rag.retrieve({
text: query,
namespace: 'help-center',
rerank: { minScore: MIN_RERANK_SCORE },
});
if (result.error) throw new Error(result.error.code);
// Zero hits: nothing in the help center answers the question.
if (result.data.length === 0) return 'The help center does not cover this.';
return result.data.map((hit) => hit.content);
}There is no default threshold. Each rerank model scores on its own scale, so
a threshold that works for one model does not carry over to another. Set it
again when you change model.
When the rerank call fails
A failed rerank call never fails retrieve. When the provider is down, the
model is retired or the account has no credit, retrieve still returns the
first topK hits:
- in vector order, as without
rerank, - without
rerankScore, so your code can tell, - without the
minScorefilter, because that threshold is on the rerank model's scale. An outage does not make your agent answer "I don't have that" to a question your documents cover.
The SDK logs one console.warn with the model and the provider's message, so
the failure shows in your agent's logs.
Mistakes that fail the call
These are programming mistakes, so retrieve returns rag_invalid_request
before it embeds the question or touches the database:
rerankwith a precomputedembedding. The reranker needs the questiontext.- A
minScorethat is not a finite number. - A
candidatesthat is not a positive integer.
Deletion
Two atomic deletion APIs:
// Delete one or many documents by id (re-ingest replaces; this removes).
await stackbone.rag.delete(['post-123', 'post-124']);
// Delete every chunk whose metadata matches a sub-object.
await stackbone.rag.deleteWhere({ source: 'help-center' });Both run inside a single SQL statement and cascade through to chunks via
the foreign key. Pass { namespace } to scope either call.
RAG calls and your own transactions
RAG and your own tables share one connection pool, but a stackbone.rag
call does not join a stackbone.database.transaction(...) callback. It
checks out its own connection from the pool and commits on its own, even
when you call it inside the callback. Do not rely on a rollback of your
transaction to undo an ingest.
To keep your own rows and the RAG store consistent, run the ingest first. Write your own row only after it succeeds:
import { stackbone } from '@stackbone/sdk';
import { eq } from '@stackbone/sdk/db';
import { documents } from '../src/schema';
async function reindexStep(id: string, parsedText: string) {
'use step';
const result = await stackbone.rag.ingest({
id,
collection: 'help-center',
chunks: stackbone.rag.chunk(parsedText),
});
if (result.error) throw new Error(result.error.code);
// Only mark your own row once the ingest is stored.
await stackbone.database
.update(documents)
.set({ ragIndexedAt: new Date() })
.where(eq(documents.id, id));
}A failed step retries, and re-ingesting the same id replaces its chunks
atomically, so the retry converges instead of duplicating data.
Inspecting RAG data
Three surfaces read the same rows:
stackbone rag: list collections and documents, run a similarity query, and follow ingest jobs from your shell.- Studio's RAG explorer: read-write. A person can create a collection, upload a file and ingest it from the screen. See Managed ingestion from Studio below.
- The agent's own HTTP surface:
GET /api/rag/collections,POST /api/rag/collections/:name/query,GET /api/rag/jobsandPOST /api/rag/jobs/:jobId/cancel. Studio and the CLI both go through it.
Managed ingestion from Studio
stackbone.rag.ingest is the code path: your agent calls it at
runtime. There is also a human path: anyone who installed your agent
(and, locally, you as the creator running stackbone dev) can feed a
collection straight from the Studio screen, with no ingest code to write.
Both paths land in the same rag_* tables, so the agent retrieves
manually-uploaded chunks with stackbone.rag.retrieve() exactly as if your
code had ingested them: there is no second-class "uploaded by hand" flag.
What a person can do from the screen:
- Create an empty collection with a name, an optional description and a probed embedding provider and model, before uploading anything.
- Upload a file (
.txt,.mdor.pdf) into a collection. The upload returns immediately with a job id, and the parse, chunk, embed and store work runs in the background, so the browser never freezes on a large PDF. - Watch the job move through
queued → running → succeeded | failedin the Jobs tab. Retry a failed job without re-uploading: the retry starts from the original the upload stored. Cancelling marks the jobcancelled, and it is best effort: an ingest already mid-flight runs to the end. - Upload a new version of a document, try it, and publish it when it is ready. See Version documents from Studio.
- Delete a document with every version of it (their chunks cascade), or delete a whole collection (documents and chunks cascade).
- Download the original file. The platform keeps the uploaded source, linked to its document row.
Two contracts matter for your agent:
- Manual uploads and SDK retrieval use the collection's saved provider, model and dimension. The built-in ingest workflow follows that configuration. If you override the ingest workflow, update its SDK and compile it again so its RAG calls follow the same contract.
- The schema stays with the platform. Stackbone provisions the
rag_*tables on every install, so the screen works from the first boot. Studio never creates or migrates the schema itself; it only reads and writes through it.
The platform keeps files uploaded this way in object storage under a
reserved, read-only rag/ prefix. See
stackbone.storage.
Version documents from Studio
A document you upload from Studio can hold versions. You add one with New version on the document's row, and you decide when your agent starts to read it. Each version keeps its own chunks and its own original file.
| In Studio | What happens |
|---|---|
| First upload of a file | It becomes version 1, published at once. |
| New version on a document | Studio ingests the file you pick, whatever its name, as the next version. It stays unpublished, and the row shows v2 ready to publish once Studio has ingested it. |
| Upload a file with the name of a published document | Studio refuses it (case does not matter, as on macOS) and offers Upload as new version. |
| A new version identical to an earlier one | Studio refuses it with Same as v1 and offers to publish v1. |
| History on a document | Lists every version with its status, and Download, Try, Publish and Delete where they apply. |
| Try on a ready, unpublished version | Studio searches the whole collection as if that version were published. |
| Publish | Your agent reads that version from its next search on. Publish an earlier version to roll back. Publishing re-embeds nothing, because each version has its chunks. |
| Delete on an unpublished version | Removes that version, its chunks and its original file, once its ingest is over. The rest of the document stays. |
| Delete the document | Removes every version, with their chunks and original files. |
What your code sees
stackbone.rag.retrievesearches the published version of each document only. Your agent never gets a hit from an unpublished version.stackbone.rag.ingestnever creates a version. Re-ingest with the sameidand it still replaces that document's chunks in place. The document keeps its version number and its published state.stackbone.rag.deleteremoves the one version whose id you pass, and leaves the other versions of that document. To remove a document with every version, delete it from Studio.
Update the SDK before you add versions
Versions need your workspace on an SDK release that supports them. An older SDK does not know about versions:
- Its
retrievealso returns chunks from unpublished versions. - Its ingest of a new version publishes that version as a separate document, next to the one it was meant to replace.
Update @stackbone/sdk before anyone uploads a new version from Studio.
Update the SDK gives the
command, the second copy under workflows/, and how to restart or redeploy
your agent on the new release.
Custom pgvector columns
If you need a vector column on your own table (e.g. an embedding field on
documents you maintain by hand),
@stackbone/sdk/db re-exports vector:
import { pgTable, text, vector } from '@stackbone/sdk/db';
export const documents = pgTable('documents', {
id: text('id').primaryKey(),
content: text('content').notNull(),
embedding: vector('embedding', { dimensions: 1536 }).notNull(),
});The first migration that introduces a vector(...) column emits a
CREATE EXTENSION IF NOT EXISTS vector; the RAG installer
also ensures the extension exists, so the order in which you add custom and
platform vector columns does not matter.
Errors
Every stackbone.rag.* call returns Result<T>: check .error and
branch on error.code:
| Code | When | Hint |
|---|---|---|
rag_embedding_conflict |
The explicit provider/model or a concurrent configuration revision conflicts with the collection. | Use the saved configuration and retry after checking the collection. |
rag_collection_unavailable |
The collection state does not permit the operation. | Inspect the collection state before retrying. |
rag_schema_missing |
The platform RAG schema is not present in this database. This should not happen on a managed install: the schema is provisioned for you. | If you hit it, the install's setup did not complete; redeploy the install or contact support. |
rag_embedding_model_unsupported |
OpenRouter resolved the model but it does not expose the /v1/embeddings endpoint. |
Pick an embedding-capable model (e.g. openai/text-embedding-3-small). |
rag_embedding_failed |
The embedding provider rejected the request (auth, rate limit, transient). | The message carries the provider's error; retry policy is up to the caller. |
rag_invalid_request |
Required field missing or malformed (e.g. empty id, no chunks, topK not a positive integer, rerank with a precomputed embedding). |
The message names the offending field. |
rag_ingest_cancelled |
The job's row was marked cancelled while the worker was running. |
Reachable for a job your own code started with ingestAsync. |
rag_rerank_failed |
The rerank call of a retrieve({ rerank }) failed. retrieve never returns it: you see it only in the warning the SDK logs. |
Nothing to branch on. A hit without rerankScore tells you the fallback ran. See When the rerank call fails. |
rag_error |
Anything the pipeline could not classify (a Postgres error, a dropped connection). | error.cause carries the original error. |
The contract gate adds contract_version_unsupported,
capability_unavailable, contract_unreachable and contract_malformed.
Every gated stackbone.rag method requires the rag.basic capability:
see overview.
Pure helpers (parse, chunk) never hit the database or the embedding
provider, so the gate exempts them.
Internal subpaths
@stackbone/sdk exposes one low-level subpath behind the RAG pipeline. It
exists for the SDK's own use: creators usually do not import it
directly. This page lists it so you can find the surface when you need
something low-level.
| Subpath | Purpose |
|---|---|
@stackbone/sdk/rag/schema |
Drizzle table definitions for rag_collections, rag_documents, rag_chunks and rag_jobs (in the stackbone_platform schema), plus job-status types. Keep your own joins against these tables read-only. |
This subpath is internal to the platform contract: it may change shape between SDK majors.
Where to go next
stackbone.database: the same pool RAG runs on, for your own tables and migrations.- Agents & sessions: where a retrieval tool lives, and how an agent grounds answers in your documents.
- Workflows: where a long ingest belongs, as a step that persists and auto-retries.
@stackbone/sdkoverview: the rest of the surfaces: storage, AI, approval, secrets, config.