Skip to content
vectorless-js
Esc
navigateopen⌘Jpreview
On this page

Storage

The tree is JSON, so anything that holds JSON holds it.

The library never persists anything. buildIndex hands back an IndexedDoc, that value is plain JSON, and where it goes is your call. You write it wherever you already write JSON, using whatever you already use.

const doc = buildIndex(markdown, "report.pdf");

await Bun.write(`trees/${doc.doc_id}.json`, JSON.stringify(doc));   // disk
localStorage.setItem(doc.doc_id, JSON.stringify(doc));              // browser
await bucket.put(doc.doc_id, JSON.stringify(doc));                  // object storage

// later
const doc = JSON.parse(await Bun.file("trees/report.pdf.json").text());
await ask(doc, question, llm);

It round-trips through JSON.parse intact, which is why there is no adapter layer to learn.

Size

Every node carries its text, so a tree weighs about what its source does. A 53 KB contract produced a 59 KB tree in testing. Budget for that.

The small number you see elsewhere in these docs, 1.6 KB, is only what getDocumentStructure sends the model. It strips every text field before the tree goes out. You still need the text on disk, because that is what get_page_content reads back.

What is actually worth storing

Indexing from Markdown headings is free and deterministic, so for tidy documents you can skip storage and rebuild the tree whenever you want it. Same input, same tree.

Cache the parts you paid an LLM for:

Step Worth caching
buildIndex no, cheaper to redo
pdfToMarkdown maybe, it burns CPU but no tokens
buildIndexAuto when it repairs a flat tree yes
addNodeSummaries, generateDocDescription yes

Keeping an audit trail

If you want to know later why an answer came out the way it did, record the tool calls. ask already emits them, so this is a matter of catching them.

AgentFS suits this well. It is SQLite underneath, with a key-value store for the trees and a tool-call table you can query with SQL.

bun add agentfs-sdk
// trace.ts
import { AgentFS } from "agentfs-sdk";
import { ask, complete, buildIndexAuto } from "vectorless-js";

const agent = await AgentFS.open({ id: "contracts" });
const llm = {
  baseURL: process.env.OPENAI_BASE_URL!,
  apiKey: process.env.OPENAI_API_KEY,
  model: process.env.OPENAI_MODEL,
};

// Index once, keep the tree.
const doc = await buildIndexAuto(markdown, "lease.pdf", (p) => complete(p, llm));
await agent.kv.set(doc.doc_id, doc);

// Ask, and record every tool call as it happens.
const cached = await agent.kv.get("lease.pdf");
const { answer, citations } = await ask(cached, "What is the notice period?", llm, (e) => {
  if (e.type === "tool") {
    // Tools run locally against the tree, so start and end are the same instant.
    const at = Date.now();
    agent.tools.record(e.name, at, at, e.input, null);
  }
});

Now every get_page_content("241-268") is a row. You can ask which sections the model actually reads for rent questions, or where a given answer came from, in SQL. The citations tell a reader where the answer lives; the trail tells you how the agent got there.

Snapshots are the other reason to bother. cp agent.db snapshot.db on a corpus of enriched trees is worth something, because those cost real tokens to build.

The browser version is different

AgentFS runs in a browser, and they test it in Chromium and Firefox, but the API is not the one above and the docs do not lead with this.

The browser build exports only openWith(db). It imports no database at all, so you supply one:

bun add agentfs-sdk @tursodatabase/database-wasm
import { AgentFS } from "agentfs-sdk";           // resolves to the browser build
import { connect } from "@tursodatabase/database-wasm";

const agent = await AgentFS.openWith(await connect("vectorless.db"));

Some rough edges to know about first. Installing agentfs-sdk alone pulls the native engine rather than the wasm one, so the second install is on you, and that wasm package is still a prerelease. Persistence is the other one: SQLite in a browser stores through OPFS, which needs a secure context, and some builds also want COOP and COEP headers that you set on the server rather than in code.

None of this touches vectorless-js. The tree is JSON either way, and the library has no opinion about where you put it.

Was this page helpful?