FULL deployment profile¶
Nexus's full profile is the all-feature shared hub for a team. The shared/demo preset stack provisions PostgreSQL + Dragonfly (plus the Nexus server), the complete brick set, and local inference. Keyword search uses BM25S; Zoekt is an optional, separately-run code-search backend the preset does not start (see the user guide, "What about Zoekt?"). Use this profile for a shared node that exposes the full CLI/RPC surface; use sandbox for per-agent clients that connect to it.
Three things called "profile" (read this first)¶
| Term | Where | What it controls |
|---|---|---|
Docker Compose profile (core, cache) | nexus up / docker-compose.yml | Which containers start |
| CLI connection profile | nexus profile use <name> (~/.nexus/config.yaml) | Which hub the CLI talks to |
Deployment profile (full) | nexusd --profile full / NEXUS_PROFILE | Which bricks/drivers are enabled |
nexus up runs the FULL deployment profile because docker-compose.yml sets NEXUS_PROFILE=full. No nexus init preset is literally named full; the shared and demo presets both run FULL.
What you get¶
| Surface | FULL |
|---|---|
| Storage | PostgreSQL |
| Cache | Dragonfly / Redis |
| Keyword search | BM25S (Zoekt optional, not started by the preset) |
| Bricks | LITE + search, pay, llm, mcp, workspace, snapshot, versioning, identity, delegation, share_link, portability, task_manager, observability, … (see contract test) |
| Federation | OFF (that is the cloud profile) |
| Auth | static (NEXUS_API_KEY) or database (DatabaseAPIKeyAuth) |
| Remote clients | profile=remote SDK; requires gRPC, not just HTTP |
Running¶
Via the daemon directly (supported)¶
nexusd --profile full --host 0.0.0.0 --port 2026 \
--data-dir ./nexus-data --auth-type static --api-key "$NEXUS_API_KEY"
nexusd --profile remote is rejected: a daemon cannot be a thin client of another daemon.
Via the managed stack (known issue — see below)¶
nexus init --preset shared
nexus up # ⚠ currently exits rc=1 (see note)
eval $(nexus env)
nexus status
Known issue (Bug B, tracked):
nexus up --preset sharedcurrently returns a non-zero exit code because thenexus uphealth gate waits on azoektservice that thesharedpreset does not start. The hub itself boots and serves correctly (/health,/api/v2/features, gRPC all work) — only thenexus upwrapper's aggregate exit status is wrong. This is a pre-existingnexus uphealth-gate defect, out of this docs/test issue's scope, tracked in the #4132 design spec ("Bug B"). Until it is fixed, prefer the direct daemon path above; if you use the stack, the containers are healthy despite the rc=1 (verify withnexus status/ a directcurl $URL/health).
Auth¶
- static:
--api-key/NEXUS_API_KEY/NEXUS_API_KEY_FILE. Request without a key → 401; with key → 200. - database:
--auth-type database+--database-url(orPOSTGRES_URL) →DatabaseAPIKeyAuth. Use for multi-user key issuance/revocation.
Remote client¶
from nexus.sdk import connect
nx = connect(config={"profile": "remote",
"url": "http://hub:2026",
"api_key": "..."})
Set NEXUS_GRPC_PORT if the server's gRPC port is non-default. The HTTP URL alone is not sufficient.
Correctness check you can run¶
The FULL contract is locked by tests/unit/core/test_full_profile.py. Run:
You can also verify a running hub's resolved contract directly:
It prints JSON with a _sources map marking each field's provenance:
- hub-authoritative (from the hub's
/api/v2/features):deployment_profile,bricks,disabled_bricks,mode,version. - client-inferred (NOT hub-authoritative — derived from the hub's profile name via this CLI's
DeploymentProfile; may differ under CLI/server version skew):client_inferred_drivers. - local/contextual:
auth_modereflects the localnexus.yamlonly for the locally-managed stack; for an explicit remote target (--url/NEXUS_URL/ global--profile) it is"unknown". - invariant:
grpc_requiredis alwaystrue(the remote SDK path requires gRPC, not just HTTP).
nexus profile contract --url <hub> --api-key <key> targets a remote hub; nexus --profile <name> profile contract uses a saved connection profile.
Benchmark guidance¶
Boot time and idle RSS are setup-path metrics, not CI gates; the FULL stack (PostgreSQL + Dragonfly + the Nexus server) targets multi-GB RSS and a 15–60 s boot. health / features / Ping are control-plane calls with sub-100 ms expectations on a warm hub. There is no steady-state data-plane hot path in the startup story.
Troubleshooting¶
- Remote SDK hangs / connection refused: gRPC port unreachable — set
NEXUS_GRPC_PORT, confirmnexus statusshows gRPC healthy. - 401 from every call: static auth with no
NEXUS_API_KEY, or database auth with no issued key.
Filesystem surface¶
FULL exposes the file API over two transports against the Python nexusd daemon (shared/demo presets):
- HTTP RPC —
POST /api/nfs/{method}(genericCall) and the typedPOST /api/v2/files/{write,read,exists,batch-read,…}routes. This is the only wire the hub actually binds. - Kernel syscalls — in-process calls when you embed
NexusFS.
The CLI (nexus cat/write/stat/...) currently constructs a gRPC client when invoked against a remote URL (nexus.connect(profile= "remote") → RPCTransport). Against shared/demo that wire isn't bound, so the CLI is local-stack-only on the hub presets — remote use of the CLI requires the nexusd-cluster federation binary (Rust), which IS the only thing that binds typed gRPC (Ping/Read/Write/Delete/BatchRead — see rust/transport/src/grpc.rs). The standalone hub maps the gRPC port for compose compatibility but does not bind it (commit 607ae89b5 "delete legacy Python gRPC bridge"); dialing it returns "Connection reset by peer". The HTTP RPC POST /api/nfs/{method} is marked deprecated (sunset 2026-06-25, Issue #1133) for the day the hub adds gRPC; until then it remains the canonical wire for script/CLI HTTP clients.
| Group | RPC | CLI |
|---|---|---|
| Read | read, read_range, read_bulk, read_batch | cat (+--offset/--length/--stream), read-bulk |
| Write | write, write_stream, write_batch, append, edit | write (+--stream), write-batch, append, edit |
| Metadata | stat, stat_bulk, metadata_batch, exists_batch | stat, metadata, exists |
| Mutate | rename_batch, delete_batch, rename, delete | rename-batch, rm-batch, move, rm |
| Stream | stream, stream_range | cat --stream |
| Locks | sys_lock, sys_unlock, lock_acquire, release_lock | lock list/info/release |
| Admin | backfill_directory_index, flush_write_observer | admin fs backfill-index, admin fs flush-write-observer |
Semantics that matter:
read_range(start, end)is start-inclusive, end-exclusive. End past EOF returns the available bytes (bounded, not an error).rename_batch/delete_batch/write_batchare per-item independent (not atomic) — the result maps each literal path to{success, ...}or{success, error}.content_idis stable acrosswrite/stat/readfor identical bytes; use the CAS helpers innexus.lib.occto compose If-Match writes (a stalecontent_idis rejected).- Admin ops (
backfill_directory_index,flush_write_observer) require admin; non-admin callers are refused server-side. - macOS gRPC clients:
nexus.connect(profile="remote", url="http://localhost:...")resolveslocalhostto127.0.0.1automatically. Docker Desktop / OrbStack only bind IPv4 (0.0.0.0) on host port maps; without this pin, macOS happy-eyeballs to::1first and gets "Socket closed". - Stream commands (
cat --stream,write --stream) honor UnixSIGPIPE = SIG_DFL— piping intohead,tee, or any reader that closes early exits cleanly (status 141), no traceback.
CLI ↔ RPC mapping (verified by tests/unit/cli/test_fs_parity.py):
| CLI | RPC method | Parity test |
|---|---|---|
nexus stat <path>... | stat / stat_bulk | test_stat_single_parity, test_stat_multi_uses_stat_bulk |
nexus metadata <path>... | metadata_batch | test_metadata_extended_parity |
nexus exists <path>... | exists_batch | test_exists_batch_parity_and_exit |
nexus read-bulk <path>... | read_bulk / read_batch | test_read_bulk_parity, test_read_bulk_atomic_raises_on_missing |
nexus rename-batch a:b ... | rename_batch | test_rename_batch_per_item_independent |
nexus rm-batch <path>... | delete_batch | test_rm_batch_per_item_independent |
nexus cat --offset N --length M | read_range | test_cat_range_equals_slice, test_range_out_of_bounds_is_bounded |
nexus cat --stream / write --stream | stream / write_stream | test_cat_stream_matches_full, test_write_stream_from_stdin, test_cat_stream_survives_broken_pipe |
nexus admin fs backfill-index | backfill_directory_index | test_admin_fs_flush_and_backfill, test_admin_only_metadata_is_set |
nexus admin fs flush-write-observer | flush_write_observer | test_admin_fs_flush_and_backfill, test_admin_only_metadata_is_set |
Verification status (PR #4173) — maps to the 8 spec correctness assertions:
| # | Assertion | Status | Evidence |
|---|---|---|---|
| 1 | Round-trip byte-identity + stable content_id | ✅ | test_inproc_fixture_roundtrips, test_write_roundtrips_content_id, E2E |
| 2 | Range correctness + OOB bounded | ✅ | test_cat_range_equals_slice, test_range_out_of_bounds_is_bounded |
| 3 | Batch independence (per-item success/error) | ✅ | test_read_bulk_*, test_rename_batch_*, test_rm_batch_*, E2E |
| 4 | Lock semantics (second acquirer refused) | ✅ | test_lock_contention_second_acquirer_refused (raises NexusError("contention") on second acquire; first holder releases; fresh acquire returns new lid) |
| 5 | Cross-path parity (syscall == generic Call == CLI) | ✅ | test_cross_path_parity_syscall_rpc_cli (sys_read vs dispatch_kernel_syscall("read") vs nexus cat — byte-identical; same content_id/size from stat) |
| 6 | Deprecated HTTP /api/nfs/{method} parity | ✅ | E2E uses HTTP /api/nfs/read|stat|read_range|read_bulk|exists_batch|metadata_batch|rename_batch|sys_lock|sys_unlock|delete_batch|backfill_directory_index|flush_write_observer against a booted stack |
| 7 | Auth denial: 401 unauth + 403 unpermitted + admin-only | ✅ | test_auth_denial_401_unauth_and_403_admin_only exercises require_auth/require_admin directly; test_admin_only_dispatch_rejects_non_admin exercises the kernel-side gate via dispatch_method |
| 8 | ETag / If-Match (OCC) stale-content_id rejection | ✅ | test_etag_if_match_occ_conflict (occ_write_sync with stale if_match → ConflictError; matching id → succeeds; bytes update, version advances) |
Additional coverage:
| Layer | Status | Evidence |
|---|---|---|
| Auth CLI parity | ✅ verified | 4 tests in test_auth_cli_parity.py |
| Admin-only @rpc_expose metadata | ✅ verified | test_admin_only_metadata_is_set (source-level) |
| Stream broken-pipe exit | ✅ verified | test_cat_stream_survives_broken_pipe (subprocess + real pipe) |
| Smoke regression (cat / write existing) | ✅ verified | 34 tests in test_commands_smoke.py |
| Concurrent multi-thread FS stress | ✅ verified | test_concurrent_fs_stress: 200 files × 4 ops × 16 threads, no errors, post-state correct |
| Sustained soak (opt-in) | ✅ verified | test_sustained_fs_soak gated by NEXUS_SOAK=1: 1000 files × 32 threads × 60s, 0 errors |
| Large-file >10 MiB CLI cat (auto-stream) | ✅ verified | test_cat_large_file_above_stream_threshold: 11 MiB write, CLI cat byte-identical (triggers STREAM_THRESHOLD branch) |
| Benchmark medians above | ✅ executed | tests/benchmarks/bench_read_write_overhead.py with --benchmark-min-rounds=20 |
| Over-the-wire (real Docker stack) | ✅ verified | test_full_profile_fs.py::test_full_fs_lifecycle_batch_range_lock (12 RPC methods, HTTP wire, ~80s; worktree CLI used so the historical Bug B from #4132 — older shared preset including zoekt — never fires) |
IPv4 pin for localhost gRPC | ✅ verified | 3 unit tests in tests/unit/remote/test_grpc_target.py (test_localhost_pinned_to_ipv4, test_ipv6_loopback_pinned_to_ipv4, test_non_loopback_host_untouched) |
| Worktree CLI resolution (PYTHONPATH=src) | ✅ verified | test_worktree_cli_resolves_to_src_with_pythonpath ensures subprocess pytest harnesses run the worktree, not a stale system install |
| Typed gRPC Ping/Read/Write/Delete/BatchRead (real cluster) | ✅ verified | tests/integration/test_typed_grpc_cluster.py::test_typed_grpc_ping_write_read_delete_batch boots nexus-cluster --no-tls, connects via NexusVFSServiceStub, asserts byte-identity + content_id stability + Delete success + BatchRead per-item shape |
Out of #4133 scope (covered by sibling suites):
| Layer | Owner |
|---|---|
ReBAC path-level deny (enforce=True) | tests/unit/bricks/rebac/ — needs permission_hook wired through the rebac brick |
| Multi-zone / cross-zone isolation | tests/unit/server/test_zone_* + federation suite — zones are a federation concern (Raft + ZoneManager), not part of the FS contract |
Typed gRPC WriteRequest.content_id (If-Match) | rust/transport/src/grpc.rs — the field is declared in the proto but the Rust VfsServiceImpl::write handler currently ignores it. A stale content_id over nexusd-cluster still overwrites; HTTP OCC is fully covered above. Followup tracked under the rust/transport issue queue. |
Benchmark guidance (dev-laptop medians on Apple Silicon, in-process kernel; from tests/benchmarks/bench_read_write_overhead.py, --benchmark-min-rounds=20). Numbers are reference points, not CI gates:
| Operation | Median | Rounds | Class |
|---|---|---|---|
Typed nx.read (1 KiB file) | ~595 µs | 1460 | hot path |
read_range(64 KiB) of 1 MiB | ~3.1 ms | 191 | hot path |
stat_bulk of 100 files | ~1.9 ms | 381 | hot path (≈19 µs/path) |
sys_lock + sys_unlock cycle | ~956 µs | 561 | control plane |
backfill_directory_index | — | — | not perf-sensitive |
flush_write_observer | — | — | not perf-sensitive |