Per-extension state directory (Phase 82.6)

Extensions need a stable place to put SQLite databases, vault files, and per-tenant artifacts. Phase 82.6 formalises the convention and ships a CLI helper so authors and operators agree on the path layout.

Canonical path

$NEXO_HOME/extensions/<extension-id>/state/

NEXO_HOME falls back to $HOME/.nexo when unset, then to the current working directory if even $HOME is missing (rare; covers minimal CI containers).

For an extension agent-creator on a typical install:

~/.nexo/extensions/agent-creator/state/

CLI

# Print the path (no filesystem touch).
nexo ext state-dir agent-creator
# /home/operator/.nexo/extensions/agent-creator/state

# Create the directory if missing (idempotent).
nexo ext state-dir agent-creator --ensure

Operators pipe the output into cd, sqlite3 .backup, etc. The base form is pure path resolution — useful in scripts that want to compute paths without side effects. --ensure is the moral equivalent of mkdir -p.

Programmatic access

nexo-extensions exposes:

#![allow(unused)]
fn main() {
use nexo_extensions::{ensure_state_dir, state_dir_for};

// Compute the path without touching disk.
let path = state_dir_for("agent-creator");

// Materialise it (idempotent).
let path = ensure_state_dir("agent-creator")?;
}

The daemon calls ensure_state_dir at extension first spawn so microapps can rely on the directory existing by the time their initialize handshake runs. The path is also exposed via the NEXO_EXTENSION_STATE_ROOT env var injected into the extension's process environment (constant EXTENSION_STATE_ROOT_ENV in the same module).

Backup procedure

The state dir is a regular filesystem location — operators back it up with the same tooling they use for other on-disk state:

# Whole-extension snapshot.
tar czf agent-creator-state-$(date +%F).tgz \
    -C "$(nexo ext state-dir agent-creator)" .

# SQLite-aware online backup (preferred for live DBs).
sqlite3 "$(nexo ext state-dir agent-creator)/db.sqlite" \
    ".backup '/var/backups/agent-creator-$(date +%F).db'"

Isolation

Each extension owns its own subtree. nexo does not enforce namespacing inside state/ — that's the extension's responsibility. v1 microapps that store per-tenant artifacts typically sub-divide as state/tenants/<tenant-id>/…. The framework treats the whole subtree as opaque.