V1 Web PWA · Journey

Journey: Assistant handoff and context carry

A source-reconciled journey record for V1 Web PWA — shipped behavior, state boundaries, failure modes, and the automation evidence available today.

automated · 6 specs verdict: partial· 2026-05-29
9sections13 minread

On this page
Journey at a glance
ActorsSigned-in customer, mid-Tara-sitSigned-in customer, in VeritasMemory-off customerAnonymous user
Automation6 E2E spec(s) · verdict: partial· 2026-05-29

Journey flow#

Generated from the authored steps below — click a node to jump to that section.

flowchart TD s1["1. Open the source surface and dock"] s2["2. Invoke the assistant"] s3["3. Context handoff card + disclosure strip"] s4["4. Session creation + Iris recall"] s5["5. Reply with grounding + disclosure"] s6["6. Cross-domain persona switch"] s7["7. Navigate the domain boundary"] s8["8. Continuity disclosure + dismiss"] s1 --> s2 --> s3 --> s4 --> s5 --> s6 --> s7 --> s8 click s1 href "#1-open-the-source-surface-and-dock" click s2 href "#2-invoke-the-assistant" click s3 href "#3-context-handoff-card--disclosure-strip" click s4 href "#4-session-creation--iris-recall" click s5 href "#5-reply-with-grounding--disclosure" click s6 href "#6-cross-domain-persona-switch" click s7 href "#7-navigate-the-domain-boundary" click s8 href "#8-continuity-disclosure--dismiss" s8 --> v(["verdict: partial"]) click v href "../results/assistant-handoff-context-carry.html" style v stroke:#d6a531,color:#d6a531

User invokes the global assistant dock from any domain surface; the panel opens with a typed context handoff (active route, current artifact, evidence state, persona identity, memory scope); the user switches persona across a domain boundary (e.g., Tara teacher → Veritas explainer); Iris recalls permitted prior context; and the transcript labels it as Remembered context, with explicit profile/notebook lines when those scopes apply. Catches broken handoff payloads, persona switches that drop memory, and disclosure strips that de-sync from the active grounding state without promising obsolete continuity copy.

Personas#

  • Signed-in customer, mid-Tara-sit — primary; invokes from /tara/sit/[id] then navigates to Veritas with the assistant attached
  • Signed-in customer, in Veritas — opens from /veritas or /veritas/claim and asks a contemplative question that routes to a Tara persona handoff
  • Memory-off customer — chose memoryScope='off' in onboarding (see onboarding-ten-steps.md); disclosure strip must reflect "memory off" on every reply; recall must not happen
  • Anonymous user — no assistant dock; invocation points hidden / inert

Pre-conditions#

  • Signed-in session — see shell/04-auth-session.md
  • memoryScope is one of off | session | profile
  • Assistant BFF routes healthy: POST /v1/assistant/sessions, POST /v1/assistant/sessions/:sessionId/message, GET /v1/assistant/sessions, DELETE /v1/assistant/sessions/:sessionId
  • Viewport ≥ ASSISTANT_DOCK_MIN_WIDTH (1360 px) for the dock; below that, the panel uses overlay presentation

Steps#

1. Open the source surface and dock#

  • User on /tara/sit/[id] (or any Veritas / Nisaba / Metis surface)
  • Collapsed AssistantDock visible top-right (ASSISTANT_DOCK_COLLAPSED_WIDTH = 128) when viewport ≥ 1360 px
  • Verify: customer/03-tara/tara.md surface mounted

2. Invoke the assistant#

  • Click data-shell-assistant-dock-expand (or global shortcut)
  • Dock transitions collapsedexpanded (ASSISTANT_DOCK_EXPANDED_WIDTH = 360), 200 ms unless reduced-motion
  • AssistantPanel mounts with presentation='dock'
  • Shell builds AssistantContextHandoff from libs/oshun/shell-assistant/src/context-handoff.ts carrying activeRoute, activeDomain, artifact, personaIdentity, evidenceState, memoryScope, disclosureState, permittedToolGrants
  • Handoff runs through the sanitiser: caps selection / seed length, strips email / US phone / SSN patterns, rejects entities whose platformShell mismatches the launch intent's shell

3. Context handoff card + disclosure strip#

  • AssistantPanel paints data-assistant-context-handoff chips Artifact: <label>, Evidence: <status>, Memory: <scope>, Persona: <displayName>, Tools: <count>
  • ShellPersistentContextStrip mirrors that set with secondary "Back to " and primary "Return to " (or thread label)
  • AssistantDisclosureStrip renders memory / persona / grounding / voice / avatar indicators

4. Session creation + Iris recall#

  • First message creates a session via POST /v1/assistant/sessions with userPreferences.memoryScope, memoryConsentGranted, enabledMemoryCategories, suppressionRules attached
  • If memoryScope !== 'off', Iris recall hydrates profile (durable), session (rolling 30 d raw / 90 d summarized), and notebook-linked memory bound to the active artifact per V1/features.md § Iris Memory and Identity
  • If off, no recall; disclosure strip reflects "Memory: off" on every reply

5. Reply with grounding + disclosure#

  • When recall is available, the reply uses the current BFF contract: Remembered context: followed by permitted profile/notebook facts; it does not depend on the older aspirational "I noticed you were just sitting…" sentence
  • Reply carries AssistantTurnGrounding (level, supportCount, contradictionCount, optional primarySource + citations); "Inspect evidence" opens VeritasInspectionOverlay with a model from buildAssistantTurnInspectionModel
  • Disclosure indicators stay in sync with active grounding / memory / persona / voice state per turn

6. Cross-domain persona switch#

User on /tara/sit/[id] with persona tara-guide (teacher role) asks a fact-grounded question; panel offers a Veritas-leaning explainer handoff.

  • personaHandoffState (from buildAssistantPersonaSwitchingModel + buildAssistantPersonaHandoffState) surfaces options with status: 'active' | 'recommended' | 'available' | 'restricted'
  • User picks a recommended persona (e.g., source-lineage-guide or stoic-scholar); panel paints new activePersona.id / role / memoryBoundary.kind
  • AssistantPersonaHandoffAuditEvent appended (visible via data-assistant-persona-handoff-audit-count)
  • Memory boundary honoured: profile-scope memory carries across; sensitive-category memory not licensed for the new persona is suppressed per V1/features.md § Privacy-Aware Suppression

7. Navigate the domain boundary#

  • User navigates from /tara/sit/[id] to /veritas (or /veritas/claim) without dismissing the assistant
  • Dock stays attached; new AssistantContextHandoff is computed; prior handoff recorded in persistent context for "return to" affordances
  • Context handoff card refreshes with new Artifact / Evidence / possibly a different Persona recommendation
  • Disclosure strip re-renders for the new domain's grounding state
  • Verify: customer/05-veritas/veritas.md

8. Continuity disclosure + dismiss#

  • The first reply on the new surface may include prior context only when the memory scope permits it; recalled facts remain visibly prefixed as Remembered context:
  • If memory is off, the assistant still gets the current-screen handoff but cannot reference the prior Tara sit
  • Click data-shell-assistant-dock-dismiss (X) → dock returns to collapsed; thread state persists in sessionStorage under ASSISTANT_THREAD_STORAGE_KEY (oshun-web-assistant-thread)
  • updateOshunPersistentShellContext updates so re-invocation can offer "Return to " / "Back to "

Post-conditions#

  • Live session exists with turn count recorded
  • Session's userPreferences.memoryScope matches the user's profile
  • Transcript + active context handoff persisted in sessionStorage
  • Disclosure indicators match actual subsystem states each turn
  • PersonaHandoffAuditEvent recorded for any persona switch

Failure modes#

  • Sanitiser drops the artifact — artifact's platformShell mismatched launch intent; panel receives null artifact, shows "no current artifact" copy instead of an empty chip
  • Sanitiser drops a mismatched entity — a wrong-shell entity is omitted before session creation; the handoff carries entity: null, preserves the current artifact, and shows "Entity: removed (shell mismatch)" without exposing the dropped entity label
  • POST /v1/assistant/sessions returns 403 (no domain access) — panel falls back to AssistantSafeFallbackState from buildAssistantSafeFallbackState; user sees "Assistant unavailable on this surface"
  • Memory scope mismatchcontextHandoff.memoryScope disagrees with userPreferences.memoryScope; panel uses effectiveMemoryScope = contextHandoff?.memoryScope ?? memoryScope (most-restrictive of the two)
  • Restricted persona switch — option state === 'restricted'; UI hard-blocks; audit records the attempt with outcome: 'fallback'; panel reverts to previous active persona
  • Cross-tenant entity leak — sanitiser rejects with 403 forbidden_cross_tenant_scope; INC fires in operator admin
  • Viewport shrinks below 1360 px mid-session — dock transitions to overlay; thread state persists; persistent-context strip changes layout
  • Crisis-frame suppression activates — Lilith intercepts (see crisis-aware-tone-policy.md); no new memory written, existing memory suppressed from recall except safety-critical; crisis surface mounts in-place

E2E coverage#

  • Backed by apps/oshun/web/e2e/assistant-continuity-journey.spec.ts, apps/oshun/web/e2e/shell-utility-dock.spec.ts, apps/oshun/web/e2e/shell-boundary.spec.ts, and apps/oshun/web/e2e/assistant-handoff-deepening.spec.ts — the continuity spec drives assistant open from library and explore CTAs and from a domain surface, asserting persona switching, voice-identity strip, disclosure strip, memory-scope chip, and the return-thread / return-anchor persistent-context links; the utility-dock spec confirms the dock persists across nav and surfaces correctly at desktop and mobile viewports; the boundary spec asserts the admin-shell context handoff carries memory-scope=off and the studio-operator-copilot persona with safe-fallback state attributes; the handoff-deepening spec opens the assistant from /library, expands the persona disclosure, switches to the stoic-scholar persona, and asserts the active-persona label updates to "Stoic Research Guide" and that the handoff-state div carries data-assistant-persona-handoff-audit-count="1" with an audit-event row whose data-assistant-persona-handoff-audit-outcome="applied". The handoff-deepening spec also drives the restricted-persona failure mode: an admin-only persona (allowedShells:['admin']) renders data-assistant-persona-state="restricted" with data-assistant-persona-disabled="true" and disabled in the customer shell, and force-activating it is a no-op (active-persona label unchanged, no new handoff audit row). It also opens the assistant from /profile, /library, and /domains/tara?origin=home to prove customer permittedToolGrants are scoped by active domain: profile exposes only navigation.open, library adds library.save, and Tara swaps that for domain.action.execute. The continuity spec also drives the customer memory-off branch over real BFF preferences: it patches /v1/preferences to assistant.memory.scope='off', waits for the browser preference store to hydrate that remote value, opens the customer assistant from /library, and asserts the real /v1/assistant/sessions create payload, BFF continuity response, context handoff chip, disclosure indicator, transcript surface, and first turn all carry memoryScope='off'. It also selects real browser text containing email / phone / SSN-shaped content before opening the assistant from /explore, then asserts the live session-create payload's contextHandoff.selection contains [redacted-email], [redacted-phone], and [redacted-ssn] without the raw PII. It also opens the browser-visible /explore null-artifact launch path and asserts the handoff carries data-assistant-context-artifact="none" plus the exact "Artifact: no current artifact" chip copy. It now also opens /search with a fixed wrong-shell entity preview, asserts the live session-create payload carries entity: null plus artifact.metadata.entitySanitization='entity-shell-mismatch', and verifies the browser panel exposes data-assistant-context-entity="none", data-assistant-context-entity-state="shell-mismatch", and the exact "Entity: removed (shell mismatch)" chip without exposing the dropped admin entity label. It also covers the cross-domain navigation-depth branch by starting on /domains/tara?origin=explore, clicking the browser-visible Veritas domain shortcut, then opening the assistant from the Veritas context strip and asserting the live session-create payload, assistant persistent-context strip, continuity rail copy, and return-thread / return-anchor links preserve origin=explore&stack=tara. It now also covers the browser-visible profile + notebook Iris recall continuity variant: the test grants real Iris consents, enables profile memory through /v1/preferences, writes profile and notebook memories through /v1/iris/adapter/remember, opens the assistant from a live Nisaba notebook route, verifies the session-create handoff carries artifact.kind='domain.notebook' and artifact.metadata.notebookId, sends a live assistant message, then asserts both the real response.memoryRecall envelope and the mounted transcript render "Remembered context:", "Profile memory:", "Notebook memory:", and the seeded Tara / Dhammapada recall copy with Memory: profile visible. It now also drives a live Veritas assistant turn through the real /v1/assistant/sessions/:sessionId/message route, asserts the BFF returns a grounded response.grounding projection sourced from Veritas article cards, then clicks the browser-visible Inspect evidence action and verifies the shared Veritas inspection overlay names the International Energy Agency source, support count, evidence citations, and source-directory action. The shell-utility-dock spec now also proves the dock/thread storage boundary in the real customer shell: expanding the desktop dock writes oshun-web-assistant-dock-mode='expanded', collapsing writes collapsed, same-tab reload restores that collapsed mode and the typed transcript from sessionStorage['oshun-web-assistant-thread'], a second tab inherits only the localStorage dock mode while seeding an independent sessionStorage-backed assistant session, and the original tab's prompt is absent from that second tab's transcript.
  • Backed by apps/oshun/web/e2e/assistant-session-bff.spec.tsstep 4's post-condition over REAL HTTP: create a real session (POST /v1/assistant/sessions → sessionId + authorizedDomains + continuity disclosure), send two messages and assert the engine's turnCount grows 1 → 2, and that GET /v1/assistant/sessions mirrors the recorded turn count (the "live session exists with the turn count recorded" post-condition the client specs could not assert). It also proves the route-level Iris bridge bootstrap + recall fold over the shared real BFF store: real /v1/preferences enables assistant profile memory, real /v1/iris/adapter/consents grants the Iris side, prior /remember profile and notebook facts remain visible in /review, POST /v1/assistant/sessions returns memory.scope='profile', the first assistant message returns a response.memoryRecall envelope containing both scoped facts, the answer text folds the remembered Tara reset and Nisaba notebook cues into the reply, and the first assistant turn is durably recorded back into the same /v1/iris/adapter/review feed under the live assistant session id. It now also proves the persona recommendation BFF endpoint: GET /v1/assistant/persona-handoffs/recommendations returns the canonical customer candidate set for /library, blocks a requested admin-only persona with a fallback handoff, enforces customer domain scope (domain:tara cannot request Veritas recommendations), and serves admin-shell candidates only to an admin-scoped bearer while preserving the admin session-only memory boundary. It also proves the mismatched-shell artifact failure mode at the BFF boundary: a valid customer Veritas handoff changes live session continuity (grounding.mode='required', sourceDomain='veritas', recentDomains=['veritas']), while the same customer launch carrying an admin.review-package artifact is sanitized out before continuity merge and falls back to the default assistant grounding context. Plus 401 (no bearer), 400 (empty text), 404 (unknown session), 403 (a session does not belong to another user), and DELETE → ended (no longer listed). It also drives the crisis-frame suppression branch: POST /v1/admin/safety/crisis-frame/activate publishes a real operator frame for the member, GET /v1/safety/crisis-frames/mine confirms the assistant projection, and a benign assistant message returns crisis.source='operator-frame', shouldSpeak=false, no cards/actions, and turnCount=0 instead of normal synthesis.
  • Backed by apps/oshun/web/e2e/assistant-not-entitled-fallback.spec.ts — the downstream not-entitled degradation branch: a 403 (entitlement / authz denial) must degrade HONESTLY, never fabricate. Session-create 403 (real "No domain access" gate, driven by re-issuing to the BFF with a profile:read bearer) flips the downstream safe-fallback subsystem to fallback with a not-entitled reason (distinct from the generic outage copy); a message 403 (real ownership gate) surfaces an honest "isn't available on your current access" notice instead of a fabricated generateSimulatedResponse answer. This corrects the earlier "403 → safe-fallback is NOT achievable" note — the downstream subsystem is session-error driven.
  • Coverage depth: deep — see coverage.md.
  • Coverage closure notes:
    • Step 4's profile-scope and notebook-linked Iris recall fold is now asserted at both the real BFF layer and the browser layer: the mounted assistant transcript now renders the exact profile + notebook "Remembered context" continuity copy after a live Nisaba notebook handoff.
    • Mismatched-shell artifact rejection is now real-HTTP covered at the BFF boundary, and the browser-visible null-artifact copy is covered from /explore; the browser-visible entity mismatch variant is covered from /search with a sanitized entity: null payload and safe mismatch chip; cross-domain navigation depth is covered by the Tara → Veritas browser route with assistant return links preserving origin=explore&stack=tara. The downstream POST /v1/assistant/sessions 403 safe-fallback branch is covered by assistant-not-entitled-fallback; operator crisis-frame suppression is covered by assistant-session-bff.
    • Persistent thread storage scope and dock mode lifecycle are now browser-covered: sessionStorage restores the same-tab transcript after reload, does not leak it into a second tab, and localStorage mirrors the dock expanded / collapsed / closed lifecycle.
    • Per-domain customer permittedToolGrants policy enforcement is now covered in ShellLayout unit tests and the real browser assistant-handoff spec.
    • Persona handoff candidate recommendations are now real-HTTP covered at the BFF boundary for customer, admin, fallback, and authorization-scope branches.

Per-view files touched by this journey#

Assistant is global; entry can be any signed-in surface. Examples:

Cross-references#

  • Sibling journeys: onboarding-ten-steps.md sets memoryScope and the persona/tone preferences read here; tara-to-nisaba-handoff.md covers an adjacent cross-domain bridge that does not use the dock; and crisis-aware-tone-policy.md covers Lilith interception inside the panel
  • Feature spec: V1/features.md § Assistant Experience; § Iris Memory and Identity (recall, scope hierarchy, suppression, conflict resolution)
  • Component sources: apps/oshun/web/src/components/assistant-dock/AssistantDock.tsx; apps/oshun/web/src/components/assistant/AssistantPanel.tsx; apps/oshun/web/src/components/ShellPersistentContextStrip.tsx; libs/oshun/shell-assistant/src/context-handoff.ts; libs/oshun/shell-assistant/src/persona-handoffs.ts; libs/oshun/shell-assistant/src/safe-fallbacks.ts
  • BFF routes: apps/oshun/bff/src/routes/assistant.ts; apps/oshun/bff/src/routes/assistant-metis-handoff.ts

Open questions / known gaps#

  • Exact profile + notebook recall/disclosure copy variant — browser now proves the mounted transcript renders the real Remembered context: profile/notebook copy after a Nisaba notebook handoff. The older aspirational "I noticed you were just sitting in …" copy is not the current BFF contract.
  • Iris recall pipeline endpoint — recall currently runs during the live message route through IrisMemoryBridge.recallRelevantMemory; there is no separate browser-visible recall endpoint in the current implementation.
  • Persistent thread storage scope — sessionStorage under oshun-web-assistant-thread; verify multi-tab behaviour
  • Dock mode lifecycle (closed/collapsed/expanded) mirror to localStorage under ASSISTANT_DOCK_MODE_KEY
  • permittedToolGrants per customer domain — ShellLayout now grants navigation.open everywhere, adds library.save only on save-capable domains, and adds domain.action.execute only on action-capable domains; unit and browser tests cover profile, library, and Tara.
  • Recommendation BFF endpoint for persona handoff candidates — implemented as GET /v1/assistant/persona-handoffs/recommendations and covered by the real-dev-infra assistant-session-bff Playwright API spec for customer, admin, fallback, and authz-scope branches.