# `AgentDb`
[🔗](https://github.com/manhvu/agent_db/blob/v0.1.0/lib/agent_db/agent_db.ex#L1)

Embedded offline context store for AI agents (OpenViking-inspired).

A URI-addressed tree with layered content (L0 abstract, L1 overview, L2 full
content), keyword and vector search over it, append-only sessions that can be
committed into the tree, and typed durable memories with their provenance.

Every function here is a stable entry point. The work behind them belongs to
the application workflows, which reach the store and the models through
replaceable ports rather than naming either directly -- so what a caller
depends on stays the same when what serves it changes.

## Starting

```elixir
{:ok, _} = Application.ensure_all_started(:agent_db)
:ok = AgentDb.write("viking://resources/readme.md", "# Title")
{:ok, content} = AgentDb.read("viking://resources/readme.md")
```

## Layered content

A document is stored with its full content and, optionally, a caller's own
abstract and overview. A layer that was not supplied is generated in the
background, and a read resolves to the first thing available: the stored
layer, then the generated one, then a deterministic fallback that needs no
model at all.

# `content`

```elixir
@type content() :: String.t()
```

# `uri`

```elixir
@type uri() :: String.t()
```

# `abstract`

```elixir
@spec abstract(uri()) :: {:ok, content()} | {:error, term()}
```

Reads a document's L0 abstract, falling back to frontmatter identity or the first non-empty body line.

# `append_message`

```elixir
@spec append_message(String.t(), atom(), String.t()) :: :ok | {:error, term()}
```

Appends a message to a session (role: `:user` | `:assistant` | `:system`).

# `backfill_vector_index`

```elixir
@spec backfill_vector_index() :: {:ok, non_neg_integer()} | {:error, term()}
```

Enqueues `:embed` jobs only for URIs missing in the active dim table.

Switching providers creates the new table if missing and backfills the diff;
URIs already covered are never re-enqueued.

# `bulk_set_enabled`

```elixir
@spec bulk_set_enabled([uri()], boolean()) ::
  {:ok,
   %{results: [map()], updated: non_neg_integer(), failed: non_neg_integer()}}
```

Applies `set_enabled` to every URI in `uris`, attempting each one.
Answers per-URI outcomes plus summary counts.

# `bulk_set_group`

```elixir
@spec bulk_set_group([uri()], String.t()) ::
  {:ok,
   %{results: [map()], updated: non_neg_integer(), failed: non_neg_integer()}}
```

Applies `set_group` to every URI in `uris`, attempting each one.

# `cache_stats`

```elixir
@spec cache_stats() :: map()
```

The size of the store's disposable read caches: entries and bytes per table.

# `commit_session`

```elixir
@spec commit_session(String.t(), String.t(), keyword()) ::
  {:ok, String.t() | :unchanged} | {:error, term()}
```

Commits a session into the tree at `destination_uri` as one document.

Idempotent per (session, destination): re-committing an unchanged session
returns `{:ok, :unchanged}`. If the destination has since been removed, the
commit rebuilds it rather than reporting it unchanged.

# `create_session`

```elixir
@spec create_session() :: {:ok, String.t()} | {:error, term()}
```

Creates a session and returns its id.

# `data_transfer_limits`

```elixir
@spec data_transfer_limits() :: %{
  max_entries: pos_integer(),
  max_bytes: pos_integer()
}
```

The bounds one data transfer accepts, for a caller that has to bound its own input.

# `default_confidence`

```elixir
@spec default_confidence() :: float()
```

The confidence recorded when a caller supplies none.

# `export_data`

```elixir
@spec export_data(Path.t(), keyword()) :: {:ok, map()} | {:error, term()}
```

Exports store data to a tar archive at `path`.

Snapshots documents (full content plus caller-supplied abstract/overview),
memory provenance, and sessions into a single `.tar` or `.tar.gz` file that
another store can import with `import_data/1`. With `scope:` the export is
limited to one subtree (sessions are included only for a full export).

Returns `{:ok, %{path:, scope:, documents:, memories:, sessions:, messages:}}`.

# `export_data_error_message`

```elixir
@spec export_data_error_message(term()) :: String.t()
```

Why a data export or import was refused, as a sentence an operator can act on.

# `find`

```elixir
@spec find(String.t(), keyword()) :: {:ok, [map()]} | {:error, term()}
```

Discovers files and directories whose URI path contains `query`.

A literal, case-insensitive substring match over the URI path (excluding
the `viking://` scheme). See AgentDb.Application.Documents.find/2 for
scoping, limits, ordering, and errors.

# `forget`

```elixir
@spec forget(uri()) :: :ok | {:error, term()}
```

Removes a memory and its provenance. See AgentDb.Application.Memories.forget/1.

# `get_session`

```elixir
@spec get_session(String.t()) :: {:ok, [map()]} | {:error, term()}
```

Returns every message of a session, in order.

# `grep`

```elixir
@spec grep(String.t(), keyword()) :: {:ok, [map()]} | {:error, term()}
```

Searches full document content (L2) for a literal substring.

A literal, case-insensitive match that never looks at abstracts or
overviews. See AgentDb.Application.Search.grep/2 for scoping, limits,
ordering, and errors.

# `health_check`

```elixir
@spec health_check() :: %{
  status: String.t(),
  checks: %{db: boolean(), models: boolean()}
}
```

Whether the store and its models are usable, checked individually.

# `import_data`

```elixir
@spec import_data(Path.t() | {:archive, binary()}, keyword()) ::
  {:ok, map()} | {:error, term()}
```

Imports a transfer archive created by `export_data/1` into the running store.

Accepts a filesystem path or `{:archive, binary}`. Validates the whole
archive before writing anything; a refused archive leaves the store exactly
as it was. Import merges by URI and never deletes content outside the
archive. Re-importing an unchanged archive converges without duplication.

# `import_skills`

```elixir
@spec import_skills(String.t(), AgentDb.Skills.Source.source()) ::
  {:ok,
   %{
     skills: [
       %{
         name: String.t(),
         status: :imported | :replaced | :failed,
         files: non_neg_integer(),
         reason: term() | nil
       }
     ]
   }}
  | {:error, term()}
```

Imports Agent Skills into `viking://user/{user_id}/skills`.

A source is a folder, a tar archive (gzip-compressed or not), or the files of a
browser directory selection:

    AgentDb.import_skills("alice", {:path, "./my-skills"})
    AgentDb.import_skills("alice", {:path, "./my-skills.tar.gz"})
    AgentDb.import_skills("alice", {:uploads, [%{path: "alpha/SKILL.md", content: body}]})

Each skill is a directory with a `SKILL.md` at its root, and a source may hold
one skill or a collection of them; a skill's name is its directory's name. See
`AgentDb.Skills.Source` for what is accepted and what is refused.

A skill stored under that name already is replaced whole -- files the new
source does not have go with it -- and a replacement that fails leaves the
stored skill as it was, so a valid skill in the same source still lands.

Answers the outcome of every skill, or one error for the source as a whole.
Nothing is written until the whole source has been accepted, so an error leaves
the store as it was. `AgentDb.skill_import_error_message/1` turns the error
into a sentence.

# `index_coverage`

```elixir
@spec index_coverage() :: map()
```

How much of the store's content each index covers.

Vector index availability and row count against the document count, plus the
documents under each index's own roots. An index that cannot be queried
reports unavailable rather than reporting zero. The vector leg additionally
reports `active_dim` and `needs_backfill` when the provider serves them.

# `layers`

```elixir
@spec layers(uri()) ::
  %{
    abstract: %{
      text: content(),
      source: :stored | :fallback | :unavailable,
      chars: non_neg_integer()
    },
    overview: %{
      text: content(),
      source: :stored | :fallback | :unavailable,
      chars: non_neg_integer()
    },
    content: %{
      text: content(),
      source: :stored | :unavailable,
      chars: non_neg_integer()
    }
  }
  | {:error, term()}
```

Reads a document's three LLM-facing layers together, each with its source.

Answers `%{abstract: layer, overview: layer, content: layer}`, where each layer
carries `text`, `source` (`:stored`, `:fallback`, or `:unavailable`), and
`chars`. A `:fallback` layer was derived from L2 at read time because nothing
was stored; `:unavailable` means that layer's read failed. A missing URI is an
error, as with any other read.

One call instead of four, so a caller deciding whether to read a whole
document pays one round trip rather than one per layer.

# `layers_batch`

```elixir
@spec layers_batch([uri()]) :: %{required(uri()) =&gt; map()}
```

Reads the L0/L1/L2 layers for several URIs in one store operation.

# `list`

```elixir
@spec list(uri()) :: {:ok, [String.t()]} | {:error, term()}
```

Lists the names of a URI's direct children.

# `list_all_documents`

```elixir
@spec list_all_documents(uri(), keyword()) :: {:ok, map()} | {:error, term()}
```

Recursive document URIs under `scope` in deterministic order, paged.

Options: `:page` (default 1), `:per_page` (default 50, max 200),
`:substring`, `:include_disabled` (default false), `:group`.

# `list_skills`

```elixir
@spec list_skills(keyword()) :: {:ok, map()} | {:error, term()}
```

Installed skill roots in URI order, paged. Each entry carries `name`,
`owner`, `uri`, `files`, `enabled`, and `group_tag`.

Options: `:page`, `:per_page`, `:substring`, `:owner`, `:include_disabled`,
`:group`.

# `memory_conflicts`

```elixir
@spec memory_conflicts(keyword()) :: {:ok, [map()]} | {:error, term()}
```

Possible conflicts between active memories. See AgentDb.Application.Memories.conflicts/1.

# `memory_count`

```elixir
@spec memory_count() :: {:ok, map()} | {:error, term()}
```

Alias for `memory_statistics/0`.

# `memory_statistics`

```elixir
@spec memory_statistics() :: {:ok, map()} | {:error, term()}
```

Memory statistics: counts per type and status, pending, never surfaced. See AgentDb.Application.Memories.statistics/0.

# `memory_stats`

```elixir
@spec memory_stats() :: {:ok, map()} | {:error, term()}
```

Alias for `memory_statistics/0`.

# `memory_types`

```elixir
@spec memory_types() :: [String.t()]
```

The memory types, for callers that need to enumerate the taxonomy.

# `model_status`

```elixir
@spec model_status() :: map()
```

How the store's models are doing, including a load in progress.

# `operation_stats`

```elixir
@spec operation_stats() :: map()
```

Operation, job, and model counts by outcome since this process started.

# `overview`

```elixir
@spec overview(uri()) :: {:ok, content()} | {:error, term()}
```

Reads a document's L1 overview, falling back to the first 280 characters of the body after frontmatter.

# `pending_memory_candidates`

```elixir
@spec pending_memory_candidates(keyword()) :: {:ok, [map()]} | {:error, term()}
```

Candidates waiting for promotion review. See AgentDb.Application.Memories.pending_candidates/1.

# `promote_memory`

```elixir
@spec promote_memory(uri()) :: {:ok, uri()} | {:error, term()}
```

Promotes the waiting candidate at `uri` to the active memory.

# `prune_vector_index`

```elixir
@spec prune_vector_index(pos_integer()) :: :ok | {:error, term()}
```

Drops a non-active vector dim table (`vec_nodes_<dim>`).

Refuses the active dim; boot never wipes. An operator action for reclaiming
disk after a provider switch, not part of any write path.

# `queue_detail`

```elixir
@spec queue_detail(pos_integer()) :: map()
```

How far behind the queue is, and which jobs are failing.

Beyond `queue_stats/0`'s counts: the age of the longest-waiting pending job,
and the failures that spent their retries, each with the classified reason.

# `queue_stats`

```elixir
@spec queue_stats() :: map()
```

How much background work is outstanding, by status.

# `read`

```elixir
@spec read(uri()) :: {:ok, content()} | {:error, term()}
```

Reads a document's full content (L2).

# `recall`

```elixir
@spec recall(uri() | keyword()) :: {:ok, [map()]} | {:error, term()}
```

Reads memories back. See AgentDb.Application.Memories.recall/1.

# `recent_errors`

```elixir
@spec recent_errors(pos_integer()) :: [map()]
```

Recent operational failures, newest first, with the operation they happened in
and their classified reason. Bounded and in memory only.

# `reject_memory_candidate`

```elixir
@spec reject_memory_candidate(uri()) :: :ok | {:error, term()}
```

Rejects the waiting candidate at `uri`, leaving no trace of it.

# `remember`

```elixir
@spec remember(uri(), content(), keyword()) :: {:ok, uri()} | {:error, term()}
```

Records a durable fact as a memory. See AgentDb.Application.Memories.remember/3.

# `rm`

```elixir
@spec rm(uri()) :: :ok | {:error, term()}
```

Removes the subtree at `uri`, from every store keyed by URI.

# `runtime_snapshot`

```elixir
@spec runtime_snapshot() :: {:ok, map()} | {:error, term()}
```

A bounded, read-only view of the BEAM runtime: uptime, applications,
supervisors, process counts, ETS tables, memory, and scheduler figures.

Read-only by construction: nothing is started, stopped, or messaged to take
one. Never writes, so a snapshot is safe to take at any time.

# `search`

```elixir
@spec search(String.t(), keyword()) :: {:ok, [map()]} | {:error, term()}
```

Searches documents by `:keyword`, `:vector`, or `:hybrid` (default
`:keyword`).

Options:
  - `:mode` - `:keyword`, `:vector`, or `:hybrid`
  - `:scope` - a URI prefix to limit the search to one subtree
  - `:top_k` - maximum results (default 10)
  - `:hybrid_weights` - `{keyword_weight, vector_weight}` or `[keyword: kw, vector: vw]` (default `{0.5, 0.5}`)
  - `:fields` - which per-result fields to return: any of `:uri`, `:content`,
    `:abstract`, `:overview`, `:score`. `:uri` must be named, since a result
    without its URI cannot be fetched. Omitted returns all of them.

# `set_enabled`

```elixir
@spec set_enabled(uri(), boolean()) :: :ok | {:error, term()}
```

Enables or disables the subtree at `uri`.

Disabled is blocked-from-use: excluded from search and default listings,
but still readable and editable. Notifies subscribers so the console
reloads without restart.

# `set_group`

```elixir
@spec set_group(uri(), String.t()) :: :ok | {:error, term()}
```

Assigns the operator group tag for the subtree at `uri`. Empty clears.

# `skill_import_error_message`

```elixir
@spec skill_import_error_message(term()) :: String.t()
```

Why a skill import was refused, as a sentence an operator can act on.

# `skill_import_limits`

```elixir
@spec skill_import_limits() :: %{max_entries: pos_integer(), max_bytes: pos_integer()}
```

The bounds one skill import accepts, for a caller that has to bound its own input.

# `storage_stats`

```elixir
@spec storage_stats() :: map()
```

What the store holds and how much room it takes on disk.

Document and directory counts, documents per top-level subtree, and the
database and write-ahead-log sizes.

# `stored_layers`

```elixir
@spec stored_layers(uri()) ::
  {:ok, %{abstract: content() | nil, overview: content() | nil}}
  | {:error, term()}
```

Reads a document's stored L0/L1 layers without fallback.

# `subscribe`

```elixir
@spec subscribe(uri()) :: :ok | {:error, term()}
```

Subscribes the caller to changes beneath a `viking://` URI.

# `tree`

```elixir
@spec tree(uri(), pos_integer()) :: {:ok, map()} | {:error, term()}
```

A depth-limited projection of the tree at `uri` (default depth 2).

# `unsubscribe`

```elixir
@spec unsubscribe(uri()) :: :ok | {:error, term()}
```

Unsubscribes the caller from a `viking://` URI scope.

# `write`

```elixir
@spec write(uri(), content(), keyword()) :: :ok | {:error, term()}
```

Writes a document at `uri`, creating missing parent directories.

Persists and acknowledges before any embedding or summarization completes, so
a read returns content immediately and searchability arrives progressively.
With `async: false` the write waits instead, and reports whether the work
completed, failed, or is still outstanding.

Options:
  - `:async` - override `async_writes`
  - `:sync_timeout_ms` - how long a synchronous write waits (default 30_000)
  - `:abstract` / `:overview` - the caller's own L0 / L1 layers

---

*Consult [api-reference.md](api-reference.md) for complete listing*
