# moss
Elixir SDK for moss — local-first indexing and querying for real-time agent workflows, with built-in embedding flows for `"moss-minilm"` and `"moss-mediumlm"`.
## Features
- **Local Session Index** — Index and query entirely in memory during a live session
- **Built-In Embeddings** — `add_docs/3` and `query/3` embed automatically for built-in models
- **Custom Embeddings** — Set `.embedding` on each `DocumentInfo` and pass `embedding:` to `query/3`
- **Index Loading** — Load indexes into memory and query them with `Moss.Client.query/4`
- **Cloud Sync** — Push a session index to cloud, or manage cloud indexes directly
- **High-Performance Rust Core** — Indexing, search, and embedding execution run through Rust NIFs
## Installation
```elixir
# mix.exs
defp deps do
[{:moss, "~> 1.0"}]
end
```
## Quick Start
```elixir
alias Moss.{Client, DocumentInfo, Session}
# 1. Create a client
{:ok, client} = Client.new("your-project-id", "your-project-key")
# 2. Open a session (auto-loads from cloud if the index exists)
{:ok, session} = Client.session(client, "session-abc")
# 3. Index documents locally
docs = [
%DocumentInfo{id: "1", text: "Customer asked about their invoice from March"},
%DocumentInfo{id: "2", text: "Discussed upgrading to the business plan"},
%DocumentInfo{id: "3", text: "Customer mentioned difficulty logging in"}
]
{:ok, {3, 0}} = Session.add_docs(session, docs)
# 4. Query (local, no cloud round trip)
{:ok, result} = Session.query(session, "billing question", top_k: 2)
for doc <- result.docs do
IO.puts("#{Float.round(doc.score, 3)} #{String.slice(doc.text, 0, 60)}")
end
# 5. Push to cloud
{:ok, push_result} = Session.push_index(session)
IO.puts("Pushed #{push_result.doc_count} docs as '#{push_result.index_name}'")
```
## Custom Embeddings
```elixir
{:ok, client} = Moss.Client.new("your-project-id", "your-project-key")
{:ok, session} = Moss.Client.session(client, "session-abc", model_id: "custom")
docs = [
%Moss.DocumentInfo{id: "1", text: "Invoice from March", embedding: embedding_1},
%Moss.DocumentInfo{id: "2", text: "Plan upgrade", embedding: embedding_2}
]
{:ok, {2, 0}} = Moss.Session.add_docs(session, docs)
{:ok, result} = Moss.Session.query(session, "billing", embedding: query_embedding, top_k: 2)
```
## Load and Query Cloud Indexes Locally
```elixir
{:ok, client} = Moss.Client.new("project-id", "project-key")
# Load a cloud index into memory
{:ok, _} = Moss.Client.load_index(client, "faq-index")
# Query it locally
{:ok, results} = Moss.Client.query(client, "faq-index", "cancel subscription", top_k: 3)
```
## Session + Loaded Cloud Index
```elixir
{:ok, client} = Moss.Client.new("project-id", "project-key")
{:ok, _} = Moss.Client.load_index(client, "faq-index")
{:ok, session} = Moss.Client.session(client, "session-xyz")
# Index session-specific documents
{:ok, _} =
Moss.Session.add_docs(session, [
%Moss.DocumentInfo{id: "turn-1", text: "Customer: I need to cancel my subscription"},
%Moss.DocumentInfo{id: "turn-2", text: "Agent: I can help with that. Can I ask why?"}
])
# Query session and cloud indexes independently
{:ok, session_results} = Moss.Session.query(session, "subscription cancellation", top_k: 3)
{:ok, faq_results} = Moss.Client.query(client, "faq-index", "subscription cancellation", top_k: 3)
# Push session to cloud when done
{:ok, _} = Moss.Session.push_index(session)
```
For loaded `"custom"` indexes, pass `embedding: [...]` to `Moss.Client.query/4`.
## Metadata Filters
All local query functions accept the same `:filter` keyword argument.
```elixir
filter = %{"field" => "type", "condition" => %{"$eq" => "billing"}}
{:ok, result} = Moss.Session.query(session, "invoice", filter: filter)
filter = %{
"$and" => [
%{"field" => "type", "condition" => %{"$eq" => "billing"}},
%{"field" => "priority", "condition" => %{"$gte" => "5"}}
]
}
{:ok, result} = Moss.Client.query(client, "faq-index", "urgent billing", filter: filter)
```
Supported operators: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$near`
Logical combinators: `$and`, `$or`
## Available Models
| `model_id` | Behavior |
|---|---|
| `"moss-minilm"` | Built-in local embeddings, optimized for speed |
| `"moss-mediumlm"` | Built-in local embeddings, higher quality |
| `"custom"` | Caller supplies embeddings via `DocumentInfo.embedding` and `embedding:` opt |
## Hybrid Search
The `:alpha` option blends semantic and keyword search.
```elixir
Moss.Session.query(session, "billing", alpha: 0.0) # keyword-only
Moss.Session.query(session, "billing") # balanced (default 0.8)
Moss.Session.query(session, "billing", alpha: 1.0) # semantic-only
```
## Cloud CRUD
```elixir
{:ok, client} = Moss.Client.new("project-id", "project-key")
{:ok, result} = Moss.Client.create_index(client, "my-index", docs)
{:ok, info} = Moss.Client.get_index(client, "my-index")
{:ok, status} = Moss.Client.get_job_status(client, result.job_id)
```
## Module Reference
| Module | Description |
|---|---|
| `Moss.Client` | Entry point — cloud CRUD, local index ops, and sessions |
| `Moss.Session` | GenServer for local session indexing |
| `Moss.DocumentInfo` | `%{id, text, metadata, embedding}` |
| `Moss.SearchResult` | `%{docs, query, index_name, time_taken_ms}` |
| `Moss.QueryResultDoc` | `%{id, text, metadata, score}` |
| `Moss.IndexInfo` | `%{id, name, version, status, doc_count, created_at, updated_at, model}` |
| `Moss.PushIndexResult` | `%{job_id, index_name, doc_count, status}` |
## License
This package is licensed under the [PolyForm Shield License 1.0.0](./LICENSE).
- Free for testing, evaluation, internal use, and modifications.
- Not permitted for production or competing commercial use.
- For commercial licenses, contact: <contact@moss.dev>
`moss` reports aggregated usage counts to Moss servers for billing. No document content is sent.
## Contact
For support, commercial licensing, or partnership inquiries: [contact@moss.dev](mailto:contact@moss.dev)