# SANDBOX deployment profile

Nexus's `sandbox` profile is the lightweight runtime for running one Nexus
inside each AI-agent sandbox. It boots with **zero external services**
(SQLite + in-process LRU + BM25S; no PostgreSQL, Dragonfly, or Zoekt).

Target: ~300-400 MB RSS, <5 s warm boot.

## When to use

- You want per-agent isolation: one Nexus instance per sandbox, with its
  own storage and policy boundary.
- The sandbox's outer orchestrator (e.g.
  [agentenv](https://github.com/windoliver/agentenv)) provisions the
  sandbox and injects `NEXUS_URL` / `NEXUS_API_KEY` so the sandbox can
  federate to a peer Nexus or hub.
- You don't want to operate PostgreSQL + Dragonfly inside every sandbox.

Use the `full` profile for a shared Nexus hub; use `sandbox` for the
per-sandbox clients that talk to it.

## What you get

| Surface | SANDBOX | FULL |
|---|---|---|
| Storage (metastore + records) | SQLite | PostgreSQL |
| Cache | In-process LRU | Dragonfly / Redis |
| Keyword search | BM25S mmap | BM25S + Zoekt |
| Semantic search | Local (sqlite-vec) or federated (peers); BM25S fallback | Local txtai + federation |
| HTTP surface | `/health`, `/api/v2/features` | Full `/api/v2/*` |
| MCP | Yes | Yes |
| Target RSS | <400 MB | Multi-GB |
| Boot time | <5 s (warm) | 15-60 s |

## Running

### From pip

```bash
pip install 'nexus-ai-fs[sandbox]'
nexusd --profile sandbox --data-dir ~/.nexus/sandbox --port 8000 --host 127.0.0.1
```

> The long-running server entrypoint is `nexusd`. The workspace `nexus` CLI
> does not expose a `serve` subcommand. `NEXUS_PROFILE=sandbox` works as an
> equivalent env-var override if you prefer.

### From Docker

```bash
docker run --rm \
  -e NEXUS_PROFILE=sandbox \
  -e NEXUS_DATA_DIR=/data \
  -v sandbox-data:/data \
  -p 8000:8000 \
  ghcr.io/nexi-lab/nexus:sandbox
```

### Config file

```yaml
profile: sandbox
# SANDBOX defaults fill these in automatically; override only if needed:
#   backend: path_local
#   data_dir: ~/.nexus/sandbox
#   db_path: ~/.nexus/sandbox/nexus.db
#   cache_size_mb: 64
# Opt in to local vector search (requires an embedding API key, see below):
# enable_vector_search: true

features:
  # Everything off by default except SANDBOX's required set.
  # Re-enable specific bricks:
  # workflows: true
```

## Local vector search (opt-in)

SANDBOX can do real vector search locally, without federation, using:

* **`sqlite-vec`** — a tiny (~3 MB) SQLite extension that adds a `vec0`
  virtual table with KNN. The vector data lives in the same `nexus.db`
  file as the rest of SANDBOX state; nothing new to operate.
* **`litellm`** — provider-agnostic embeddings. Bring your own API key
  for OpenAI / Cohere / Anthropic / Azure / etc. Default model is
  `text-embedding-3-small` (1536 dim). Override with
  `NEXUS_EMBEDDING_MODEL`.

Both are bundled with the `[sandbox]` extra:

```bash
pip install 'nexus-ai-fs[sandbox]'
```

It's **off by default** so SANDBOX still boots without an embedding
key. To enable, opt in via the config or env:

```yaml
profile: sandbox
enable_vector_search: true
```

```bash
# any provider supported by litellm
export OPENAI_API_KEY=sk-...
# (optional) override the embedding model
# export NEXUS_EMBEDDING_MODEL=text-embedding-3-large
NEXUS_ENABLE_VECTOR_SEARCH=true nexusd --profile sandbox \
  --data-dir ~/.nexus/sandbox --port 8000 --host 127.0.0.1
```

When enabled, the SANDBOX semantic search path becomes:

1. **Primary** — local sqlite-vec KNN inside the active zone. Hits
   come back without any degraded flag.
2. **Secondary** — federation to configured peer zones (if any).
3. **Tertiary** — BM25S keyword search with `semantic_degraded=true`
   stamped on every result.

When the `[sandbox]` extra isn't installed (or no API key is set), the
factory logs a single WARNING naming the missing package and falls
through to steps 2 and 3 transparently — boot does not fail.

## Federation

SANDBOX delegates semantic search to configured peer zones. Point it at
a hub zone via the federation config:

```yaml
federation:
  peers:
    - zone_id: main-hub
      url: https://nexus.example.com
      token: ${NEXUS_HUB_TOKEN}
```

When all peers are unreachable, search returns BM25S keyword results
stamped with `semantic_degraded=true` on each result. The MCP client
can surface this to the agent so it knows the results are keyword-only
for that request.

## What's off by default in SANDBOX

The following bricks are NOT enabled in SANDBOX. Re-enable individually
with `features.<brick>: true`:

`pay`, `llm`, `workflows`, `sandbox` (the sandbox-provisioning brick,
distinct from this profile), `observability`, `uploads`, `resiliency`,
`access_manifest`, `catalog`, `delegation`, `identity`, `share_link`,
`versioning`, `workspace`, `portability`, `snapshot`, `task_manager`,
`acp`, `discovery`, `memory`, `skills`.

Enabled in SANDBOX (10 bricks = LITE + SEARCH + MCP + PARSERS):
`eventlog`, `namespace`, `permissions`, `cache`, `ipc`, `scheduler`,
`agent_runtime`, `search`, `mcp`, `parsers`.

Note: federation is auto-detected from ZoneManager / peer config; it
does not require a brick flag.

## Troubleshooting

- **Boot fails with `'PyKernel' object has no attribute 'agent_registry'`**:
  the loaded Rust extension predates the `agent_registry` getter. Rebuild
  with `maturin develop -m rust/nexus-cdylib/Cargo.toml --features full`
  (running from a fresh `git pull` on `main` requires this when the kernel
  ABI moves).
- **Boot fails with `ModuleNotFoundError: bm25s`**: install the extras
  with `pip install 'nexus-ai-fs[sandbox]'`.
- **Boot tries to connect to Postgres/Redis**: you have a leftover
  `NEXUS_DATABASE_URL` or `NEXUS_DRAGONFLY_URL` in your env. Unset them
  or explicitly set `NEXUS_CACHE_BACKEND=inmem`.
- **Semantic search returns `semantic_degraded=true`**: no peer is
  reachable. Check `federation.peers` in your config + network access.
- **Boot slower than 5 s**: Python interpreter cold-start on first run.
  Subsequent boots (warm) should hit target.
