Introduction

Concepts

Plain-language explanations of the terms you'll see in these docs.

You don't need to understand any of this to use Aether — the zero-to-chatbot guide works without knowing what an embedding is. But if you're curious, here's what's happening behind the scenes.


Tenants

A tenant is the isolation boundary for an Aether workspace or organization. Documents, chunks, embeddings, jobs, usage, and API keys are scoped to one tenant. A search from one tenant cannot read another tenant's documents, even if the same doc_id string is guessed or a background job id leaks into logs.

In most apps, you map one customer, workspace, or organization to one Aether tenant. For lighter-weight per-user scoping inside a tenant, add tags like user:42 or use the Memory facade's entity_id.

flowchart LR
  KeyA["API key: tenant A"] --> AuthA["Authenticate"]
  KeyB["API key: tenant B"] --> AuthB["Authenticate"]
  AuthA --> StoreA["Tenant A documents, chunks, vectors, jobs"]
  AuthB --> StoreB["Tenant B documents, chunks, vectors, jobs"]
  StoreA -. "No cross-tenant reads" .-> StoreB

API keys

API keys identify the tenant and role for each request. SDKs attach the key automatically after you pass it to the client or set AETHER_API_KEY; direct REST requests use Authorization: Bearer <api-key>.

Regular keys are for application traffic: inserting documents, searching, retrieving, listing, and deleting within their tenant. Admin keys are for operational control-plane actions such as tenant and key management. Keep both in environment variables or a secrets manager, never in client-side code or git.

Rate limits are enforced per organization and plan. When a limit is hit, Aether returns 429 Too Many Requests with a retry-after header; see Limits & quotas and Authentication.


RAG

Retrieval-Augmented Generation. Instead of asking an AI to answer from memory, you first retrieve the relevant information from your own documents, then hand it to the AI along with the question. The AI generates its answer using your documents as a reference sheet.

Think of it like an open-book exam versus a closed-book exam. Without RAG, the AI has to recall everything from training. With RAG, you hand it the right pages before it answers.

text
Without RAG:  Question → AI → Answer (from memory, might hallucinate)
With RAG:     Question → Aether finds relevant docs → AI → Answer (grounded in your data)

Embeddings

When Aether stores a document, it converts the text into a list of numbers (called a vector) that represents the meaning of the text. This is called an embedding.

Think of it like converting a recipe into a flavor profile — the words are different but the "taste fingerprint" lets you compare recipes by flavor rather than by exact ingredients.

When you search, Aether converts your question into the same kind of numbers and finds documents whose numbers are most similar. This is why searching for "vacation policy" can match a document that says "employees accrue 20 days of PTO" — the meanings are close even though the words are different.


Traditional search matches keywords — if you search "vacation," it finds documents containing the word "vacation." Vector search matches meaning — it finds documents about time off, PTO, leave, and holidays, even if they never use the word "vacation."

This is what makes Aether useful for AI applications. Your users don't need to guess the exact words in your documents.


Relevance score

When Aether returns search results, each one has a score. This is a calibrated relevance integer from 0 to 100:

  • Higher = better. A score near 100 means the text is a very close semantic match; lower scores are weaker matches.
  • As a starting heuristic with the default embedder, inspect your own result distribution before setting a hard cutoff. See Tuning retrieval for how to choose a threshold that works for your documents.

You can use the score to decide whether to show a result to the user, pass it to an LLM, or discard it.


Documents and chunks

When you insert a large document (like a PDF or a long article), Aether automatically splits it into smaller pieces called chunks. Each chunk gets its own embedding.

This matters because LLMs have limited context windows — they can only read so much text at once. By chunking, Aether can find and return just the relevant sections of a 100-page document, rather than the whole thing.

You don't need to do the chunking yourself. Just insert the document and Aether handles the rest. The default storage chunk size is 256KB. Each chunk receives a deterministic CID: an aether: identifier derived from a BLAKE3 hash of the chunk bytes. Same bytes produce the same CID; different bytes produce a different CID.

flowchart LR
  Doc["Document bytes"] --> Extract["Extract text"]
  Extract --> Chunk["Split into chunks"]
  Chunk --> Cid["Hash each chunk into a CID"]
  Chunk --> Embed["Generate embeddings"]
  Cid --> Store["Store content-addressed chunks"]
  Embed --> Index["Index vectors for search"]

VFS router and storage backends

Aether separates the document API from where bytes are physically stored. The VFS router can write chunks to different storage backends, such as local storage for development and Arweave-style permanent storage for archival use cases.

CIDs make this possible because the content address travels with the chunk. Aether can verify that retrieved bytes match the expected CID no matter which backend served them.

For application code, this is intentionally boring: you call insert, download, search, and retrieve; Aether handles routing, verification, and backend details.


Search flow

Search has three moving parts: your query, the vector index, and the stored chunks/documents. search returns ranked matches with score and the best matching passage; retrieve adds the full document content for RAG prompts.

flowchart LR
  Query["User query"] --> EmbedQuery["Embed query"]
  EmbedQuery --> VectorIndex["Vector index"]
  VectorIndex --> Candidates["Candidate chunks"]
  Candidates --> Filters["Apply tenant and tag filters"]
  Filters --> Results["Return doc_id, score, passage"]
  Results --> Retrieve["Optional retrieve: add full content"]

Tombstoning

When you "delete" a document in Aether, it doesn't actually erase the data immediately. Instead, it marks the document as deleted (called tombstoning) — like moving a file to the recycle bin. The document won't appear in search results, but it's still there.

This means deletions are reversible. If you change your mind, you can restore a tombstoned document and it becomes searchable again.


Environment variables

Environment variables are settings that live outside your code, in your terminal session or system configuration. They're the standard way to store secrets like API keys without putting them directly in your source code.

On Mac / Linux:

Bash
export AETHER_API_KEY="your-key-here"

This only lasts for your current terminal session. To make it permanent, add the line to your ~/.zshrc (Mac) or ~/.bashrc (Linux) file.

On Windows (PowerShell):

powershell
$env:AETHER_API_KEY = "your-key-here"

In a .env file (for projects using tools like dotenv):

text
AETHER_API_KEY=your-key-here

Then load it in your code with from dotenv import load_dotenv; load_dotenv() (Python) or a similar library for your language.

However you store the key, pass it to the client when you construct it — for example AetherClient(api_key=os.environ["AETHER_API_KEY"]). If the variable is named AETHER_API_KEY, the SDK also reads it automatically when you construct the client with no arguments.

In Replit / Cursor / Windsurf: Look for a "Secrets" or "Environment Variables" panel in the IDE settings. Paste your key there.