# Journey: Psyche text tutor and server-side graded memory record

The visible `/metis/tutor` experience and the durable graded-memory contract are
both real, but the customer UI does not connect them. The page renders a fixed
Epictetus transcript, real citation links, disclosure/context cards, and a
composer that appends outgoing text to localStorage. Separately,
`POST /v1/metis/tutor-session-memories` accepts a server/agent turn assessment,
folds it through the real learner-mastery engine, and durably stores the
resulting owner-scoped memory, recovery, governance, Iris-link, and mastery
snapshot. There is no live-voice control, browser turn emitter, library write,
or tutor-session teacher-review lane today.

## Personas

- **Text learner** — reads the fixture tutor transcript, follows citations, and
  queues a local outgoing message.
- **Server-side tutor agent** — supplies assessed understanding and optional
  continuity/runtime/governance metadata to the memory API.
- **Returning learner** — sees the latest stored topic reflected in the Metis
  room and activity projection.
- **Teacher/operator** — can inspect the existing Metis operator dashboard, but
  has no wired tutor-session review queue or verdict action.

## Pre-conditions

- Use an authenticated session with `domain:metis` or `domain:*` for the memory
  API. Learner identity comes from the auth context, not the request body.
- `/metis/tutor` currently reads `getMetisTutorFixture()` from
  `apps/oshun/web/src/lib/lilith-data/metis-depth.ts`.
- The BFF startup wires `tutorSessionMemoryStore` to the durable snapshot store;
  without that production binding, an isolated test instance uses in-memory
  state.
- Keep a browser-composer action separate from an agent-runtime API action: the
  component does not call the memory endpoint.

## Steps

### 1. Open the text tutor

Visit `/metis/tutor`. The header, six transcript turns, two citation chips,
session context, disclosure, and end-of-session note are fixture content. The
header's **Transcript** and **End session** controls are presentational and have
no handler. The subtitle **saved to library** and note label **auto · saves to
library** have no backing library-store write.

### 2. Use the composer within its shipped scope

Type a message and activate **Send** or press ⌘/Ctrl+Enter. `MetisTutorComposer`
saves a draft under `oshun.metis.tutor.draft.v1.<sessionId>` and appends
submitted text to the associated `.queue` key, capped at 50 entries. After a
short local delay it shows **Queued · the tutor will pick this up** and clears
the draft.

No consumer reads that queue and no request reaches the BFF. The confirmation
means browser-local intent was recorded, not that a tutor received, answered,
graded, or persisted the turn.

### 3. Follow a citation

Citation chips are real links to
`/nisaba?passage=<source>&from=metis-tutor&ref=<reference>`. Navigation works,
but the compact Nisaba room currently ignores those query parameters and opens
its curated reading desk. The link is not proof that the requested citation was
resolved or that back-stack continuity was stored.

### 4. Create a graded memory through the server contract

An authorized agent/service can post a session id, topic, status, optional
summary and state blocks, plus a turn assessment with understanding level,
confidence, concepts, mastered concepts, gaps, and misconceptions. The route
validates the envelope, derives mastery evidence, applies the real posterior
update, builds `TutorSessionMemory`, and persists the memory together with the
mastery state and recent diffs.

The route accepts assessed turn metadata, not the visible chat transcript or raw
voice/audio events. A successful 201 therefore proves a graded memory record,
not transcript auto-save or a live Psyche transport.

### 5. Verify owner-scoped read-back

Read the record through `GET /v1/metis/tutor-session-memories/:sessionId` and
list the learner's records newest first. A different authenticated learner
cannot read that record because the route always keys by auth subject. Missing,
malformed, unauthorized, and wrong-scope requests fail closed.

### 6. Verify the customer projection

The Metis in-process adapter reads the same memory store. `/v1/metis/room` uses
the latest session for its resume nudge and reports tutor activity as a session
count; Activity can also include recent tutor records. Opening `/metis` after a
server-side memory write proves this read path. It does not cause the separate
fixture `/metis/tutor` transcript to change.

### 7. Record the unshipped Psyche and review stages

There is no microphone/live-voice toggle, transport state UI, barge-in,
reconnect buffer, or Psyche envelope on the customer page. `/operator/metis`
renders fixture KPIs, a generated-lesson queue, and fixture Themis flags (with
real OneRoster overlay where available); it does not list
`TutorSessionMemoryReviewState` rows or provide uphold/dismiss/escalate actions
for tutor sessions. Named live-session, Iris, mastery-verdict, library, and
teacher-review telemetry in the old journey is not emitted by this path.

## Post-conditions

- Browser draft/queue state is reported as local intent only.
- A directly posted BFF memory is durable when the snapshot store is wired,
  owner scoped, and accompanied by real mastery posterior/diff state.
- The latest server memory can influence `/metis` and activity read surfaces.
- No transcript auto-save, live voice, library save, automatic Iris bridge,
  teacher review, or telemetry chain is claimed.
- The fixture tutor page and server memory may legitimately show different
  topics and turn counts because no front-door bridge joins them.

## Failure modes

- **Local queue mistaken for delivery** — the composer says queued, but no
  consumer or network request exists.
- **Memory API auth/scope failure** — missing auth returns 401 and missing Metis
  scope returns 403.
- **Invalid assessment** — schema violations return 400 without a graded record.
- **Unknown session** — owner-scoped GET returns 404 for an absent id.
- **Cross-learner access attempt** — the same session id under another auth
  subject does not expose the first learner's memory.
- **Snapshot backend unavailable** — the runtime cannot provide restart-durable
  read-back; do not downgrade silently to a durability claim.
- **Fixture projection mismatch** — `/metis/tutor` keeps the Epictetus fixture
  even after a different server record is created.
- **Review/library overclaim** — static labels and operator fixtures are treated
  as real writes or adjudication.

## E2E coverage

- [`apps/oshun/web/e2e/metis-tutor-session.spec.ts`](../../apps/oshun/web/e2e/metis-tutor-session.spec.ts)
  deeply covers the visible fixture transcript, disclosure, context, citation
  navigation, local composer queue, negative live-voice assertion, and real
  `/metis` read-back of a seeded memory.
- [`apps/oshun/web/e2e/psyche-tutor-memory-bff.spec.ts`](../../apps/oshun/web/e2e/psyche-tutor-memory-bff.spec.ts)
  proves real HTTP validation, mastery folding, runtime/recovery/governance/Iris
  metadata preservation, newest-first listing, and owner isolation.
- Coverage is **deep for the two shipped seams and partial for the title's full
  product arc** because no browser emitter, voice transport, library write, or
  operator review lane connects them.

## Per-view files touched

- [`customer/08-metis/metis-tutor.md`](../customer/08-metis/metis-tutor.md) —
  fixture transcript and local composer.
- [`customer/08-metis/metis.md`](../customer/08-metis/metis.md) — latest-memory
  resume nudge and session count.
- [`operator/operator-metis.md`](../operator/operator-metis.md) — lesson queue,
  KPIs, OneRoster, and fixture flags.
- [`customer/07-nisaba/nisaba.md`](../customer/07-nisaba/nisaba.md) — generic
  citation destination.
- [`customer/02-home-discovery/activity.md`](../customer/02-home-discovery/activity.md)
  — recent tutor-memory projection.

## Cross-references

- [`metis-lesson-completion.md`](./metis-lesson-completion.md) — adjacent
  fixture lesson and local assessment behavior.
- [`nisaba-scholarly-read.md`](./nisaba-scholarly-read.md) — citation-query
  destination boundary.
- [`assistant-handoff-context-carry.md`](./assistant-handoff-context-carry.md) —
  implemented continuity record for comparison.
- Code: `apps/oshun/bff/src/routes/metis-tutor-memory.ts`,
  `apps/oshun/bff/src/metis/tutor-session-memory-store.ts`,
  `apps/oshun/web/src/components/lilith/MetisTutorComposer.tsx`, and
  `libs/metis/models/src/session-memory/tutor-session-memory.ts`.

## Open questions

- Which customer/agent runtime will send an actual tutor turn to the memory API,
  and how will it reconcile retries and turn ordering?
- Should the API accept a governed transcript/checkpoint contract, or should a
  separate event store own raw text and voice data?
- What Psyche transport, disclosure, interruption, and reconnect UI will ship?
- When will **End session** create a summary and real Library item?
- How will `/operator/metis` list, isolate, and adjudicate tutor-session review
  records without exposing learner data to the wrong tenant?
