Skip to content

feat(memory): persistent default user + session user-tagging foundation#1224

Open
pbranchu wants to merge 2 commits into
RightNow-AI:mainfrom
pbranchu:pr/memory-foundation
Open

feat(memory): persistent default user + session user-tagging foundation#1224
pbranchu wants to merge 2 commits into
RightNow-AI:mainfrom
pbranchu:pr/memory-foundation

Conversation

@pbranchu

@pbranchu pbranchu commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Sessions become consistently user-scoped. First boot generates a persistent default user UUID; every session is now tagged with the user it belongs to. Foundation for future per-user features (memory, isolation, audit, etc.).

Standalone improvement — no behavior change for single-user installs (everyone is the default user, one session per agent, same as today). The data model just becomes honest about which user owns what.

This is PR 1 of a 4-PR memory series. Each PR is reviewable on its own.

# Title Status
1 This PR — Persistent default user + session user-tagging here
2 Structured memory storage + per-agent opt-in gate + control API next
3 Structured memory producer — extract + mini_dream + dreamer follows
4 Dashboard UI — opt-in selector + memory management follows

What this PR adds

  • MessageSource enum + additive Message::source field (foundation; no consumer in PR 1 — PR 2 consumes it for extraction filtering)
  • Session::user_id: UserId (required, defaults to persistent default user) and Session::parent_session_id: Option<SessionId>
  • v9 schema migration adding user_id (NOT NULL, default nil-UUID) + parent_session_id columns + indexes
  • UserConfig::is_default: bool — let [[users]] blocks attach metadata to the default user
  • AuthManager::new_with_default(configs, Option<UserId>) — respects is_default or first-user-wins
  • Kernel bootstrap:
    • bootstrap_default_user() — load/generate UUID from kv_store, install via set_default_user_id, run nil-UUID rewrite if not yet done (sentinel-guarded for exactly-once semantics)
    • resolve_user_id(Option<&str>) -> UserId — public HTTP-boundary version that folds "test" and nil-UUID to default with warn!
    • resolve_user_id_internal(...) — raw mapper for test code only
  • MemorySubstrate::rewrite_nil_user_sessions(target) — atomic transaction wrapping the sessions UPDATE

Why nil UUID is rejected

The HTTP-boundary resolve_user_id folds nil UUID (00000000-0000-0000-0000-000000000000) to the persistent default user. Without this, an API-key holder could write sessions attributed to the nil bucket — which would then be unreachable via future per-user controls (PR 2's parse_user_id rejects nil at the HTTP layer). Closes the back door at the kernel layer too so all call paths agree.

resolve_user_id_internal keeps the raw mapping for test-harness code that needs the test-user bucket directly.

Compatibility

  • Migration v9 is additive (ALTER TABLE ADD COLUMN). Forward-compatible from v8 — pre-existing rows survive with user_id = nil-UUID, then the kernel bootstrap rewrites them to the persistent default user UUID on first boot after upgrade.
  • No behavior change for single-user installs: all sessions on a pre-upgrade install end up with the same user_id (the default user), so lookups still return one session per agent.
  • SessionStore::create_session(agent_id)create_session(agent_id, user_id) is the only signature change. All in-tree callers updated.

Test plan

  • cargo fmt --check clean
  • cargo clippy --workspace --tests --all-targets -- -D warnings clean
  • cargo build --workspace clean
  • cargo test -p openfang-types --lib 393/393 pass
  • cargo test -p openfang-memory --lib 50/50 pass (incl. new v9 migration, v8→v9 upgrade, atomic rewrite_nil_user_sessions, default/test user distinct, no collision with shared_memory_agent_id)
  • cargo test -p openfang-kernel --lib 296/298 (only the pre-existing test_referenced_providers_* failures, unrelated)
  • cargo test -p openfang-api --lib 92/92 pass
  • cargo test -p openfang-runtime --lib 939/939 pass (skipping process_manager/subprocess_sandbox)
  • Persistence test reads kv_store directly — not tautological against the OnceLock
  • Includes lettre 0.11.22 upgrade (RUSTSEC-2026-0141)

Reviewed independently

This PR was carved from a working branchu deployment and reviewed by an independent agent across two rounds. The agent flagged 1 blocker and 5 issues — all addressed with traceable commits before submission.

Philippe Branchu and others added 2 commits June 3, 2026 05:22
Lays the groundwork for per-user memory by giving every install a stable
default-user UUID and tagging every session with an owning user.

Sessions are now consistently user-scoped:
- `Session::user_id: UserId` (required, not Option) — defaults to the
  kernel's persistent default user
- `Session::parent_session_id: Option<SessionId>` — foundation for future
  tree-scoped cascade deletion of forked sessions (no producer yet)
- `MessageSource` enum + optional `Message::source` — additive type that
  later PRs (structured extraction filtering) will read; no consumer here
- `UserConfig::is_default: bool` — `[[users]]` blocks can attach display
  name and channel bindings to the persistent default identity

Kernel boots the default user once and caches it process-wide:
- `bootstrap_default_user` — load-or-generate the UUID from
  `kv_store[shared, "default_user_uuid"]`, install via
  `set_default_user_id`, then run a one-shot rewrite of legacy nil-UUID
  sessions, gated by the `default_user_bootstrap_done` sentinel
- `resolve_user_id` (strict, HTTP boundary) — folds the deprecated "test"
  alias and the nil UUID to the default user with `warn!` logs so
  reserved-bucket abuse is auditable
- `resolve_user_id_internal` (raw mapper) — preserves the pre-fix
  behaviour for in-process test callers
- `AuthManager::new_with_default` — binds the `is_default = true` user
  (or the first user) to the persistent UUID

Storage and migration:
- Schema v9 adds `user_id` (NOT NULL, default nil UUID) and
  `parent_session_id` (nullable) to `sessions`, plus
  `(agent_id, user_id)` and `parent_session_id` indexes
- `MemorySubstrate::rewrite_nil_user_sessions` — atomic transaction
  wrapping the legacy-bucket UPDATE; the kernel only sets the
  bootstrap-done sentinel after a clean rewrite, so a failure leaves the
  retry path intact

Single-user installs see no behaviour change: everyone is the default
user, one session per agent, same as today.

Tests:
- `MessageSource` deserialises cleanly from pre-field payloads (JSON +
  msgpack) and survives full round-trips
- `UserConfig::is_default` defaults to `false` for existing configs
- `AuthManager::new_with_default` honours `is_default`, falls back to
  first user, and is a no-op for empty configs
- Migration v9 adds the columns and indexes, and a v8-built DB upgrades
  cleanly to v9 with pre-existing rows preserved
- `default_user_id`/`test_user_id` are distinct
- `create_session(agent, user)` round-trips through SQLite — both the
  default and an explicit user
- `rewrite_nil_user_sessions` is idempotent, targeted, and atomic
- Kernel: default-user UUID persists across kernel restarts; the strict
  filter folds `"test"` and nil to default while passing other UUIDs
  through; the internal mapper preserves the raw `"test"` alias

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cargo update bumps lettre from 0.11.21 to 0.11.22 to clear
RUSTSEC-2026-0141. Pulls in transitive dependency updates as a side
effect (mostly windows-sys/socket2 version consolidation) — no API
surface change in our own code.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant