What you'll build: a persistent research memory bank on DataK3 that answers real questions over ArXiv papers — filtering by metadata, ranking by meaning, and citing the exact source PDF.

What you'll learn:

  • How a VECTOR column (or one recipe install) gives a bucket semantic search — no separate vector DB.
  • The narrow → rank → resolve retrieval loop that powers a research agent.
  • How to run every step two ways — the dodil CLI, or by prompting an agent through the DODIL MCP.

This is DataK3's reference workload. There's a working implementation — davidmiheev/research-agent ("Atlas") — a small Python agent on Dodil Ignite that keeps all of its long-term memory in DataK3 with semantic vector search. This tutorial generalizes the pattern behind it.

NOTE

Connect the DODIL MCP once — see the two-minute setup. Add the dodil MCP server to Claude Code, Cursor, or VS Code and you can drive every step by chatting. Each step shows both: the CLI tab, and an Ask your agent tab with the exact MCP tools the agent calls and the reply you'd get back.

Problem

The end user here is a research engineer (or the agent working on their behalf) who needs to answer precise questions over a growing pile of ArXiv PDFs — "what did 2026 cs.CL papers say about expanding transformer context windows?" — and cite the exact source. The naive stack bolts an object store to a separate vector DB to a separate warehouse, then hand-writes CDC glue to keep the PDF, its embeddings, and its metadata in sync; embeddings silently drift the moment a paper is replaced, and "filter by category then search semantics" becomes a cross-system join in app code. The payoff of doing it on one DataK3 bucket: the embeddings live in a VECTOR column right next to the metadata (a merge-keyed table) and the raw PDF (an object), and one question flows narrow → rank → resolve without any inter-system plumbing — so a research agent gets durable, citable long-term memory instead of a brittle three-system pipeline.

Prerequisites

  • A DODIL organization, and either the dodil CLI authenticated (dodil auth login) or the DODIL MCP connected to your agent.
  • A sample paper to ingest — e.g. 2603.07890.pdf.

Nothing else to turn on: a new bucket has its SQL/vector tables engine ready by default (there is no engine to enable), and it's created with an internal object source already wired for ingestion.

Scenario

A user asks for recent breakthroughs in "transformer context windows." A capable agent should:

  1. Use SQL to filter papers categorized cs.CL and published in 2026.
  2. Use Vector search to locate the paragraphs that actually discuss context-window expansion.
  3. Pull the source PDF from S3 to cite exact findings.

That single question touches structured filtering, fuzzy semantic retrieval, and heavy-payload fetch — the three things DataK3 unifies over one copy of the rows.

How the three pillars map

PillarHoldsExample keys / columns
S3 (object store)Raw PDFs and LaTeX source; the durable "truth" you cite from.papers/2026/2603.07890.pdf
VectorChunked + embedded paper text.paper_chunks.embedding VECTOR(2048)
SQL (table)Publication metadata for strict filtering and rollups.papers(arxiv_id, title, primary_category, published, s3_key)

The agent's loop becomes: narrow with SQL → rank with Vector → resolve with S3.

Step 1 — Create the bucket

You

Create a DataK3 bucket called blog-research for an ArXiv memory bank.

DODIL MCP tools called
data_bucket_create
Agent

Done — created bucket blog-research (status ACTIVE). Its SQL/vector tables engine is ready and an internal object source is already wired for ingestion.

A vector isn't a separate store any more — it's a VECTOR(<dim>) column on a table, right beside your other columns. We'll keep chunked paper text in a paper_chunks table whose embedding column holds 2048-dim jina-embeddings-v4 vectors. That's the path this tutorial searches, and the one you control end-to-end.

You

In blog-research, create a paper_chunks table keyed on chunk_id with body text and a VECTOR(2048) embedding column.

DODIL MCP tools called
data_table_create
Agent

Created paper_chunks (PRIMARY KEY chunk_id) with columns chunk_id, arxiv_id, body, embedding VECTOR(2048). Ready for jina-embeddings-v4 vectors.

Want DataK3 to auto-embed every PDF you upload? Install the document-rag recipe

One recipe wires the auto-embed pipeline — an embed collection plus the ingest rule that feeds it — in one command, so anything you drop under the folder is chunked and embedded for you (async). It's the hands-off alternative to embedding chunks yourself.

You

On blog-research, install the document-rag recipe so PDFs under papers/ get embedded for semantic search.

DODIL MCP tools called
data_recipe_install
Agent

Installed document-rag on blog-research (2 steps): added the papers_docs collection (text_embedding_index) and the papers_docs_rule ingest rule for **/*.pdf,**/*.docx,**/*.md,**/*.txt. Any doc you upload now embeds automatically.

Auto-embed runs asynchronously as objects land, which is great in production but slow to demo — so the main path above embeds a handful of chunks directly, which is instant and fully under your control.

Step 3 — Create the metadata table

Create a papers table keyed on arxiv_id, so re-inserting the same paper upserts instead of duplicating. A PRIMARY KEY (merge-key) is required — writes are keyed, so re-runs and shard retries upsert idempotently.

You

Create a papers table in blog-research keyed on arxiv_id with columns for title, primary_category, published, and s3_key.

DODIL MCP tools called
data_table_create
Agent

Created the papers table with PRIMARY KEY (arxiv_id). Re-inserting the same paper now upserts the row instead of duplicating it.

TIP

Never write JSON null or an empty string "" into a key column on an upsert — both read back as null and silently drop the row on the next read. Use a real value (here every paper has a real arxiv_id), or a non-empty sentinel like "n/a".

Step 4 — Ingest a paper

Upload the PDF to the object store, embed a chunk of its text with jina-embeddings-v4 into paper_chunks, and record the metadata row so SQL can filter it. Three writes, one bucket — no CDC glue between them.

You

Upload ./2603.07890.pdf to blog-research at papers/2026/2603.07890.pdf, embed its abstract into paper_chunks with jina-embeddings-v4, and add its metadata row to papers.

DODIL MCP tools called
data_object_createignite_models_embeddata_table_upsert
Agent

Uploaded 2603.07890.pdf (status created). Embedded the abstract (2048-dim, jina-embeddings-v4) and upserted chunk c2 into paper_chunks (wal_written: true). Upserted the papers row for 2603.07890 (cs.CL, 2026-03-22). All three are visible to reads immediately.

TIP

Prefer the hands-off recipe path from Step 2? Then you only do (a) and (c) — upload the PDF and write the metadata row; the ingest rule embeds the object for you. Kick discovery now instead of waiting for the next scan with dodil data ingest trigger-discovery -b "$BUCKET" --source "$SRC" (the internalSourceId from bucket create, or dodil data source list -b "$BUCKET").

Step 5 — Answer the question (narrow → rank → resolve)

SQL narrows the candidates, Vector ranks by meaning, and S3 resolves the exact citation. data vsearch embeds the query text with the same model (jina-embeddings-v4) and returns a cosine distance score — lower is closer.

You

In blog-research, find 2026 cs.CL papers about expanding transformer context windows, rank the passages by meaning, and give me a citable link to the top paper's PDF.

DODIL MCP tools called
data_sqldata_vsearchdata_object_url
Agent

SQL narrowed to 2 cs.CL 2026 papers: 2601.01234 and 2603.07890. Ranking their chunks by meaning, the closest is chunk c2 → “Streaming Attention for Million-Token Contexts” (arXiv 2603.07890, cs.CL, 2026-03-22) at cosine distance 0.427 (c1/2601.01234 was 0.477). Citable PDF (valid 1h): https://object.uk-lon-1.dodil.io/blog-research/papers/2026/2603.07890.pdf?X-K3-Token=988d34…&X-K3-Expires=2026-09-01T09:59:40+03:00

NOTE

The MCP turns a CLI gap into a strength. data vsearch doesn't take metadata filters as flags, so on the CLI the "SQL-then-vector" handoff is done by hand (query papers first, keep the returned arxiv_ids). You can fuse both in one SQL statement — data sql "… WHERE primary_category='cs.CL' ORDER BY embedding <=> '[…]' LIMIT 1" with the query embedding as a literal vector — but the easiest path is to ask an agent: it just does the handoff — table query, constrain the vector hits, resolve the PDF — from a single prompt.

Query it — one bucket, drop-in clients

The same rows answer by content (SQL) and by meaning (vector) with no ETL and no second copy — and any Postgres/pgvector client points straight at the bucket. data connect prints the endpoints (DB name = bucket, credential = your login token), so psql or a notebook runs the exact same SELECT … ORDER BY embedding <=> '[…]' KNN.

dodil data connect "$BUCKET" -o psql   # postgresql://…@pg.uk-lon-1.dodil.io:5432/blog-research

DataK3 vs. the three-system stack

ConcernThree-system stack (S3 + vector DB + warehouse)DataK3
Keeping PDF, embeddings, and metadata in syncCustom CDC/pipeline glue; embeddings drift when a paper is replacedObject, VECTOR column, and metadata row live in one bucket
"Filter by category, then search semantics"Cross-system join in app codeSQL narrows, Vector ranks, same rows
Citing the exact sourceSeparate object-store credentials + URL signingobject url presigned link in the same API

Troubleshooting

  • A recipe auto-embed job failed. If you took the recipe path, poll dodil data ingest jobs -b "$BUCKET" -o json and re-trigger with dodil data ingest trigger --retry-failed. Treat a failed job as needing an explicit retry.
  • A KNN over a big table is slow the first time. The default request timeout is 30s; give a cold, large paper_chunks room with --timeout (e.g. --timeout 120s). Once warm, queries return in well under a second.
  • Metadata-filtered vector search isn't a one-liner on the CLI. Run the SQL query first and keep the returned arxiv_ids, fuse it into one data sql … ORDER BY embedding <=> '[…]' statement, or just prompt an agent (Step 5).
  • Embeddings look stale after a model change. A VECTOR column is pinned to whatever model produced its vectors: switching models means re-embedding the column (recompute + upsert) so query and stored vectors share a space. Don't mix jina-embeddings-v4 vectors with another model's.

Test

Verified end-to-end against a live blog-research bucket on 2026-09-01. Reads are strong (read-your-writes) by default, so the metadata row, the embedded chunk, and the uploaded PDF are visible immediately — no compaction needed.

export BUCKET=blog-research
 
# The bucket exists
dodil data bucket get "$BUCKET"
# ran: status BUCKET_STATUS_ACTIVE, internalSourceId present
 
# The merge-keyed metadata rows upserted (not duplicated)
dodil data sql -b "$BUCKET" \
  "SELECT arxiv_id, title, s3_key FROM papers WHERE primary_category='cs.CL' AND published >= '2026-01-01' ORDER BY published"
# ran → 2 rows: 2601.01234 (Ring Attention), 2603.07890 (Streaming Attention)
 
# The source PDF landed in the object store and a citable URL resolves
dodil data object url papers/2026/2603.07890.pdf -b "$BUCKET" --expires 3600
# ran → https://object.uk-lon-1.dodil.io/blog-research/papers/2026/2603.07890.pdf?X-K3-Token=…  (valid 1h)
 
# Vector search ranks the embedded passages by meaning (cosine distance, lower = closer)
dodil data vsearch -b "$BUCKET" -t paper_chunks --column embedding \
  --text "expanding transformer context window length" --model jina-embeddings-v4 --metric cosine --top-k 4
# ran → c3 0.331, c2 0.427, c1 0.477, c4 0.505 — within the cs.CL-2026 set {c1,c2}, c2 (2603.07890) wins

Live-run notes: the bucket, both tables, four jina-embeddings-v4 embeddings (2048-dim), the metadata upserts, the uploaded PDF, the SQL narrow, the data vsearch rank, and the presigned object url all ran for real and their outputs are captured above. The document-rag recipe path (Step 2 details) is shown as code — data recipe show document-rag confirmed it provisions the collection + ingest rule (no vector store), but its async auto-embed is slower to demo than embedding chunks directly, which is why the searched path is the manual VECTOR(2048) column.

One-shot

Give an agent (with the DODIL MCP connected) this prompt to reproduce the whole tutorial end-to-end:

Stand up an ArXiv research memory bank on DataK3 and answer a question over it, all in one bucket.
 
1. Create a DataK3 bucket named blog-research (description "ArXiv research memory bank").
2. Create a paper_chunks table, merge-keyed on chunk_id, with columns chunk_id, arxiv_id,
   body (string), and embedding VECTOR(2048).
3. Create a papers table, merge-keyed on arxiv_id, with string columns arxiv_id, title,
   primary_category, published, and s3_key.
4. Upload ./2603.07890.pdf to key papers/2026/2603.07890.pdf. Embed its abstract with
   jina-embeddings-v4 and upsert it into paper_chunks (chunk_id c2, arxiv_id 2603.07890).
   Upsert the papers row: 2603.07890, "Streaming Attention for Million-Token Contexts",
   cs.CL, 2026-03-22, papers/2026/2603.07890.pdf.
5. Answer: "find 2026 cs.CL papers about expanding transformer context windows, rank the
   passages by meaning, and give me a citable link to the top paper's PDF." Do the handoff:
   query papers (primary_category='cs.CL' AND published >= '2026-01-01'), run a data vsearch
   on paper_chunks with jina-embeddings-v4, then resolve a 1h pre-signed object url for the
   top PDF.
 
Report the top arxiv_id, its cosine distance, and the citable URL.

Ship it — wrap the loop in a public app

This recipe is a query-and-retrieval loop, not a deployed service. To put it behind a URL your users — or their agents — can call, wrap the loop in an Ignite app and ship it the way every DODIL app ships: DODIL git → CI → a scanned image in the registry → a public endpoint, with versioning and one-command rollback. That full lifecycle is its own tutorial: Ship a DODIL App.

Conclusion

You built a research memory bank where a single question flows narrow → rank → resolve across one DataK3 bucket — the embeddings sit in a VECTOR column right next to the metadata and the raw PDF, no separate vector DB required. Every step runs the same whether you type the CLI or ask your agent.

Next steps: