> For the complete documentation index, see [llms.txt](https://docs.meganova.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.meganova.ai/libra-os/admin-guide.md).

# Admin Guide

Operational reference for admins running Libra OS. Covers the things you do occasionally and rarely — monitoring, binding agents to collections, fixing stalled indexing, troubleshooting. For end-user upload and search workflows see [Documents & Knowledge](/libra-os/documents-and-knowledge.md).

***

## 1. System Status page

URL: `/admin/status.html`

Single-page live dashboard for the whole stack. Auto-refreshes every 5 seconds with a pause button. Shows one card per subsystem:

* **Overview** — uptime, version, last-checked timestamp.
* **PostgreSQL** — connection status + ping latency. Red dot if the DB is unreachable.
* **Knowledge Store** (SurrealDB) — total chunk count, per-collection breakdown, error if the store is unresponsive.
* **Super Nova** — worker pool state, pause flag, queued/running/failed-24h ingest jobs, dropped event counters.
* **Go Runtime** — goroutines, heap size, GC cycles. Useful for spotting leaks.
* **External Services** — probe results for dependencies configured in your environment (LibreChat, Chatwoot, SurrealDB, Ollama, SearXNG, Crawl4AI, Firecrawl, and the SSL reverse proxy self-test). Grey = not configured, green = up, yellow = degraded, red = down.

The page polls `GET /api/admin/status` every 5 seconds, which in turn reads a 30-second-cached snapshot of each external service. First-page-load is live, subsequent auto-refreshes reuse the cache — keeps load low on probed services.

**When to use it:** first stop during any "something's broken" investigation. Most outages surface here within 30 seconds of occurring.

***

## 2. Binding agents to knowledge collections

By default, an agent cannot retrieve from a collection unless it's explicitly bound. "Bound" means there's a row in `nova_agent_collection_bindings` for that (agent, collection) pair. Without a binding, an agent like `bi-assistant` knows the LLM's training data but nothing from your uploads.

Two ways to manage bindings:

### A. On the agent detail page (preferred)

Go to **Agents → click an agent → Overview tab**. Below the persona card and next to the Expertise (packs) section you'll see a **Knowledge Collections** panel:

* Current bindings are listed with individual Remove buttons.
* A dropdown lists every collection not yet bound; pick one and click **Bind**.
* Changes take effect on the agent's next request — no restart needed.

This is the right flow when you're configuring one agent at a time and want to see "what does this agent know?" in context.

### B. Admin bindings page (cross-agent view)

URL: `/admin/bindings.html`

A single page listing every binding across every agent. Useful when:

* You want to audit which agents have access to what
* You need to bulk-add the same collection to many agents
* You're standing up a new deployment and want to see everything at once

Same underlying API — `POST /api/agents/:id/collections` and `DELETE /api/agents/:id/collections/:collectionId`. Rows here are the same rows you see on the agent detail page.

### What to bind

A minimum sensible configuration:

* `super-nova` already uses the `default` collection via its `knowledge_search` tool (no binding needed for that path)
* Bind any persona that should answer from uploaded docs → `default`
* Bind domain agents to their domain collections: `legal-assistant → legal-*`, `hr-assistant → hr-*`, etc.
* Admins can create per-team collections (`sales-q2`, `finance-2026`) and bind only the relevant personas

Bindings are additive. Bind the same agent to 10 collections and retrieval searches all 10 with the same ranking.

***

## 3. Fix Indexing vs Full Rebuild

Both buttons live on the Knowledge page header. They do very different things — pick based on what's actually broken.

### Fix Indexing (amber button)

**Use when:** a specific file or two failed to index (network blip, Ollama restart, Surreal transient error) and you want to retry just the broken ones.

**What it does:** reads the `nova_job_failures` table for rows in `pending`, `retrying`, or `gave_up` status, deletes any partial chunks for those sources, re-emits upload events, and marks the failure rows resolved.

**What it doesn't do:** touch files that are `resolved` — even if they're only partially indexed. If a retry loop was manually cleared or auto-resolved before completion, Fix Indexing is blind to it. For that case use Full Rebuild.

**Scope:** bounded. Only known-broken files. Safe to click whenever; no-op if the failure log is empty.

### Full Rebuild (red button, confirmation required)

**Use when:** the whole knowledge store needs to come back from scratch. Most common reasons:

* You changed the embedder (different model, different dimension)
* You changed the chunker strategy
* The HNSW index got corrupted
* You restored from a backup and want a consistent state

**What it does:** drops the `chunks` and `temporal_neighbor` tables, recreates the schema at the current `embed_dim`, then walks the documents directory and emits an `UploadEvent` for every file on disk. Every file re-embeds from scratch.

**Cost:** real. For a corpus of N files, wall-clock is roughly `N × (parse + chunk + embed + insert)`. With `bge-m3` at 1024 dim, budget \~30-60 seconds per MB of text. The confirmation dialog is there for a reason.

**Safe to run during normal operation?** Yes, but queries will return thin results until the rebuild catches up (reconciler rate-limits at 100ms per file emission to avoid saturating the worker queue).

***

## 4. Auto-reconciliation on startup

Every time Libra OS starts, Super Nova runs a one-shot reconciliation pass:

1. Walk the documents storage root
2. For each file, query `SELECT id FROM chunks WHERE source=$path LIMIT 1`
3. If zero chunks exist → emit an `UploadEvent` (file gets indexed)
4. If any chunks exist → skip (assume already indexed)

The point: files that landed on disk via means other than the upload API (manual scp, pack install, restore) get picked up without manual intervention. Warm boots with fully-indexed corpora are cheap — one LIMIT-1 query per file, \~10 seconds total for 10k files.

**Gotcha:** the reconciler can't tell partial indexing from complete indexing. If a file has some chunks but not all (interrupted ingest), the reconciler skips it. For that case, use Fix Indexing or Full Rebuild.

Log lines to look for on startup:

```
Super Nova reconciler started (root=./data/nova-os/documents)
supernova: reconciler: checked N files, emitted M re-index events in 120ms
```

***

## 5. Retry scheduler

Separate from the reconciler. Runs every 60 seconds, reads `nova_job_failures` for rows in `pending` status where `next_retry_at <= NOW()`, and re-emits upload events for each.

**Backoff schedule:** 1min → 5min → 30min → 2hr → 12hr → 24hr. After 6 failed retries the row transitions to `gave_up` and stays there for audit; the scheduler stops touching it.

**What this gives you:** transient failures self-heal. If Ollama restarts while a file is being embedded, the file ends up in `nova_job_failures` with retry\_count=0 → 1 minute later the scheduler re-queues it → next attempt succeeds → row flips to `resolved`.

**What this doesn't do:** retry `gave_up` rows automatically. If a file truly can't be parsed (corrupt PDF, unsupported format not caught at upload time), it sits in `gave_up` forever. Use Fix Indexing or manually delete+re-upload.

Audit the table anytime:

```sql
SELECT id, path, retry_count, status, last_tried_at, next_retry_at, LEFT(error, 80)
FROM nova_job_failures
ORDER BY last_tried_at DESC;
```

***

## 6. Document → collection routing

On the Documents page, the **"Index into collection"** dropdown next to Upload determines where the file's chunks land:

* **Auto** (default): admin → `default`, regular user → `user_<their_id>`
* Any collection name: chunks carry that name; agents bound to it can retrieve the file

**Security:** regular users can only pick their own user collection. If a non-admin tries to target another collection via API, they get 403. Admins can write to any collection.

**Moving a file to a different collection later:** not supported directly — delete the file, re-upload via the target collection. The delete handler wipes chunks automatically (see next section).

***

## 7. Delete-sync

Deleting a file on the Documents page does three things:

1. Removes the file from disk
2. Emits a `DeleteEvent` per file (folders fan out into one event per file inside)
3. Worker picks up the event and runs `DELETE FROM chunks WHERE source = $path` in SurrealDB

The worker pauses upload events when the pause flag is set, but **always processes deletes** — never leave deleted files searchable for long.

**What doesn't get deleted:** the indexing job history (kept for audit), any generated reports that already cited the document, and the user's observational memory (chat logs are a separate concern from documents).

***

## 8. CSV support

Native parser handles CSVs as plain text. Chunks are grouped in 3200-character windows with 800-char overlap, so typically 20-30 rows fit in one chunk.

**Retrieval behavior for CSVs:**

* **Exact ID lookups** (`EZYV22`, `case 42`) work well — BM25 finds them; if BM25 misses due to stopwords the keyword fallback catches them.
* **Semantic queries** ("certified Master's Asia") don't embed well — `bge-m3` treats tabular rows as low-information text.
* **Analytical questions** ("how many cases were denied") don't work via RAG at all; they need SQL-over-CSV, which Libra OS doesn't have.

**For tabular data that needs analytics, use the Reports tool** — it's much better at synthesizing across rows than naive retrieval. Or upgrade to a row-aware chunker (future work).

***

## 9. Troubleshooting

### "Document Engine Unreachable" banner on Documents page

Usually means the dashboard hit `/api/documents/health` before login. Sign in first, then reload. If it persists post-login, Libra OS itself is down — check `/admin/status.html` or `docker ps` for the process.

### Upload fails with "HTTP 413"

nginx reverse proxy rejected the payload as too large. Default is 1MB, which is tiny for most documents. The deploy script now sets `client_max_body_size 100M` — if yours doesn't, re-run Step 7 of `deploy.sh` or hot-patch the nginx config:

```bash
docker exec nova-ssl-proxy sh -c "sed -i 's|location / {|client_max_body_size 100M;\n    location / {|g' /etc/nginx/conf.d/default.conf && nginx -s reload"
```

### Knowledge page shows "0 chunks" but data exists

Almost always a stale cache. Hard-refresh (Ctrl+Shift+R). If it's actually a backend issue, the `/admin/status.html` Knowledge card will show the real count — if that's also zero, check Surreal connectivity.

### LibreChat shows empty assistant message in saved conversations

Fixed in the current build — the report-generation streaming path now emits the OpenAI-conventional `finish_reason: "stop"` chunk before `[DONE]`, which LibreChat's parser requires to persist the message. If you still see this on old conversations, they were saved before the fix; new conversations work correctly.

### Indexing seems stalled

Check `/admin/status.html` → Super Nova card:

* `Jobs queued` climbing, `Jobs running` at 0 → worker pool died, restart Libra OS
* `Jobs running` but chunk count not moving → Surreal is the bottleneck, check its container state
* `Pause flag = yes` → someone ran `/pause` in Super Nova chat; run `/resume` to continue

### Surreal transactions dropping

SurrealDB 3.x accumulates index-compaction conflicts over long uptimes. If you see `A transaction was dropped without being committed or cancelled` in its container logs, restart the container:

```bash
docker restart nova-surrealdb
```

Data persists (volume mount); only the process state resets. Libra OS's connection pool reconnects automatically.

***

## 10. Where things live

| Topic                      | Where                                                    |
| -------------------------- | -------------------------------------------------------- |
| System status              | `/admin/status.html`                                     |
| Infrastructure settings    | `/admin/infrastructure.html`                             |
| Agent bindings (all)       | `/admin/bindings.html`                                   |
| Agent bindings (per-agent) | `/agents/:id` Overview tab                               |
| Fix / Full Rebuild         | `/knowledge` page header                                 |
| Super Nova chat            | `/super-nova` (embedded iframe, sidebar wraps around it) |
| Failure log                | `nova_job_failures` table in PostgreSQL                  |
| Chunk store                | `chunks` table in SurrealDB                              |
| Bindings store             | `nova_agent_collection_bindings` table in PostgreSQL     |

***

## 11. Safe restart checklist

When you need to restart Libra OS cleanly:

1. Check for in-flight ingests: `/admin/status.html` → Super Nova card → "Jobs running" should be 0, or be okay with those jobs re-running after restart (reconciler will pick them up).
2. `kill -TERM <pid>` — graceful shutdown, flushes call\_log batches.
3. Start with the same env vars; the DB-persisted settings layer handles the rest.
4. On boot, expect:
   * `migrations: applied N statements`
   * `SurrealDB connected: ...`
   * `HNSW vector index created (dim=1024, ...)` — should match your embedder
   * `Super Nova worker pool started (2 workers)`
   * `Super Nova retry scheduler started`
   * `Super Nova reconciler started ...`
   * `http server started on [::]:8900`

If any of those are missing from the startup log within 30 seconds, something's wrong — check the log before proceeding.
