---
title: AI Coaching sidebar (interview prompts and artifacts) (part 4)
---

# AI Coaching sidebar (interview prompts and artifacts) (part 4)

## What it is

This is part 4 of the [AI Coaching sidebar (interview prompts and artifacts)](ai-coaching.md) page. It is the reference half of the feature: the CLI control-server routes an agent can call, the developer-only interview prompt editor, and the file-by-file implementation pointers for anyone touching the AI Coaching code.

## Where to find it

The CLI routes below are served by the localhost control server at `127.0.0.1:19519` — there is no UI for them. The developer-only **Prompts** section appears in the AI Coaching sub-sidebar only in an unpackaged build, and every route is gated the same way the rest of the feature is; the [parent page](ai-coaching.md) covers the surfaces a user actually clicks.

## How it behaves

### CLI control

AI Coaching is reachable from the localhost CLI control server at `127.0.0.1:19519` via 16 endpoints — list interview prompts, list/get/version-history artifacts, get session-scoped coaching info, CRUD bookmarks, read compaction summaries, plus approval-gated POST (create a new artifact), PATCH (update artifact), DELETE (remove artifact + all versions), and POST (start a new interview). Reads are bearer-gated and read-budgeted; artifact mutations require user approval in the Omniscio inbox before any side effect; bookmark mutations apply immediately (no approval needed). The whole surface is gated by a two-tier kill switch: `aiCoachingEnabled` (master) + `aiCoachingCliEnabled` (CLI-specific, default `true`) at Settings → CLI Control — either off, every route returns 403 with `disabled: true` before any auth/parse work. Idempotency via `X-Client-Request-Id` (≤64 chars, 30-day window, scoped per `action_kind`). Optimistic concurrency on artifact updates via `expectedLatestVersion` (returns `STALE_ARTIFACT` on mismatch).

| Method | Path                                           | Gating                                         |
| ------ | ---------------------------------------------- | ---------------------------------------------- |
| GET    | `/ai-coaching/prompts`                         | bearer + read-budget                           |
| GET    | `/ai-coaching/artifacts`                       | bearer + read-budget                           |
| GET    | `/ai-coaching/artifacts/:id`                   | bearer + read-budget                           |
| GET    | `/ai-coaching/artifacts/:id/versions`          | bearer + read-budget                           |
| GET    | `/ai-coaching/artifacts/:id/versions/:version` | bearer + read-budget                           |
| GET    | `/ai-coaching/sessions/:sessionId/info`        | bearer + read-budget                           |
| GET    | `/ai-coaching/bookmarks`                       | bearer + read-budget                           |
| GET    | `/ai-coaching/compaction-summaries`            | bearer + read-budget                           |
| GET    | `/ai-coaching/compaction-summaries/latest`     | bearer + read-budget                           |
| POST   | `/ai-coaching/bookmarks`                       | bearer + apply-immediately                     |
| PATCH  | `/ai-coaching/bookmarks/:id`                   | bearer + apply-immediately                     |
| DELETE | `/ai-coaching/bookmarks/:id`                   | bearer + apply-immediately                     |
| POST   | `/ai-coaching/artifacts`                       | approval-gated (`ai_coaching.artifact_create`) |
| PATCH  | `/ai-coaching/artifacts/:id`                   | approval-gated (`ai_coaching.artifact_update`) |
| DELETE | `/ai-coaching/artifacts/:id`                   | approval-gated (`ai_coaching.artifact_delete`) |
| POST   | `/ai-coaching/interviews`                      | approval-gated (`ai_coaching.start_interview`) |

Full route reference, examples, and the approval-gated pipeline live in [cli-ai-coaching.md](cli-ai-coaching.md).

#### The interview coach can use the safe endpoints itself

The AI coach running an interview is told — inside its own session context — that it is operating in the Omniscio AI Coaching module, and it may call three of these endpoints on your behalf. They are best-effort and non-blocking:

- **Bookmark a meaningful moment** (`POST /ai-coaching/bookmarks`) — when you have a genuine realization or say a line worth revisiting, the coach can save it as a Key Moment (the same bookmarks you make by hand), so you may notice bookmarks it added.
- **Recall an earlier part of a long session** (`GET /ai-coaching/compaction-summaries/latest`) — to pick up context after a paused-and-resumed interview.
- **Suggest a follow-up interview** (`POST /ai-coaching/interviews`) — if the conversation clearly points to another interview that would help, the coach can offer to start it; because that spawns a new (paid) session it lands as an **approval request in your inbox** rather than starting silently.

The coach is deliberately **not** told how to create, update, or delete artifacts over the API — those stay approval-gated and out of its instructions, and it keeps saving your interview's artifact by writing the file as usual. If the CLI surface is turned off, or a call is rate-limited, the coach skips it silently and the conversation is unaffected. It also treats your saved profile as data, never as instructions to call tools. The exact instruction block (`COACHING_MODULE_TOOLS`) and its locking test are covered in [ai-coaching-contract.md](/.claude/memory/contracts/ai-coaching-contract.md).

## For agents

### [DEV-ONLY] Editing interview prompts

When the build-time flag `AI_COACHING_PROMPT_EDITOR_ENABLED` is `true` AND the build is unpackaged (i.e. a developer running `npm run dev`, not an end user with the installed binary), a fifth section labelled "Prompts" appears at the bottom of the AI Coaching sub-sidebar. Each row is one of the canonical interview prompts (Life Story Interview, Weekly Review, etc.). Selecting a row opens a read-only viewer pane on the right showing the rendered markdown body, the description, and the artifact title the prompt produces.

Two icon buttons sit on the section header: **Open file** launches `resources/ai-coaching-seed-prompts.json` in the developer's default `.json` editor (via `shell.openPath`), and **Reload from disk** re-reads the file and upserts it into the live `saved_prompts` table by id. A `chokidar` watcher on the same file automatically fires the reload ~500 ms after every save, so manually clicking Reload is rarely needed — it's there for the case where the watcher is wedged or you want to confirm the round-trip.

The flag defaults to `true` in dev so we can iterate on prompt bodies before cutting a release. It is flipped to `false` (in [src/shared/feature-flags.ts](../../src/shared/feature-flags.ts)) before public release so end users never see the surface. Even with the flag accidentally on, the IPC channels refuse work when `app.isPackaged === true`, so a packaged binary cannot edit its own seed file.

The seed file is the source of truth that ships in the binary's `resources/` directory. The `id` field is explicit and stable (computed by [`seedPromptId(title)`](../../src/main/db/seed-prompt-id.ts) at conversion time, but stored in the JSON so a title rename doesn't orphan existing artifact links).

### For agents with repo access

Implementation entry points (all under [src/renderer/src/features/ai-coaching/](/src/renderer/src/features/ai-coaching/) and [src/renderer/src/stores/](/src/renderer/src/stores/)):

- [AICoachingSubSidebar.tsx](/src/renderer/src/features/ai-coaching/sidebar/AICoachingSubSidebar.tsx) — the four-section sub-sidebar. Sessions subgrouping logic + precedence chain lives in `bucketSession` and `GROUP_ORDER` here. Mirrors the predicates in [SessionsSidebar.tsx](/src/renderer/src/features/dashboard/SessionsSidebar.tsx) — read that before changing precedence.
- [AICoachingDashboard.tsx](/src/renderer/src/features/ai-coaching/dashboard/AICoachingDashboard.tsx) — the prompt-card grid shown in the Dashboard section: an **In progress** tier, then a **browse-by-theme** picker (a theme filter bar + search box with the interviews grouped into theme sections, plus a "More" catch-all for null/legacy categories), then a **Completed** tier (a completed card shows just a View artifact button on its face, with Redo / Reset / conditional Open chat in a ⋮ menu; on mobile `CompletedCard` collapses to a compact single row via `useIsMobile()` — [ai-coaching-dashboard-cards.tsx](/src/renderer/src/features/ai-coaching/dashboard/ai-coaching-dashboard-cards.tsx)). The first-run `AICoachingWelcome` is gated on `showWelcome` (`prompts && !welcomeDismissed && progressLoaded && !progressError && !hasEngaged`), so a returning user with a completed/in-progress interview or a Core Profile is never re-greeted on a fresh device — locked in [ai-coaching-mobile-layout-contract.md](/.claude/memory/contracts/ai-coaching-mobile-layout-contract.md). Lazy-loaded. The grouping/filter/search view-model is the pure [ai-coaching-picker.ts](/src/renderer/src/features/ai-coaching/shared/ai-coaching-picker.ts) (`buildPickerView`); the theme bar + search are [CoachingBrowseBar.tsx](/src/renderer/src/features/ai-coaching/search/CoachingBrowseBar.tsx); theme order/colors/labels/subtitles are the single source in [ai-coaching-prompt-labels.ts](/src/renderer/src/features/ai-coaching/prompts/ai-coaching-prompt-labels.ts).
- [AICoachingRightPane.tsx](/src/renderer/src/features/ai-coaching/shared/AICoachingRightPane.tsx) — section-aware router for the right pane. On **desktop**: Dashboard / artifact pane / empty prompt. On **mobile**: a bottom tab bar ([AICoachingMobileTabBar.tsx](/src/renderer/src/features/ai-coaching/dashboard/AICoachingMobileTabBar.tsx), a feature-local `role="tablist"` — NOT the global app `MobileTabBar`) drives `activeSection`, and the pane routes it to Dashboard / **Sessions** (`SessionsGroups`; a tap calls `navigateTo('session-detail')` via the new optional `onAfterSelect` prop) / **Artifacts** (`ArtifactsList` with an `onSelect` that opens the read-only mobile view) / **Bookmarks** (`BookmarkPane` with `isMobile` — read-only) / **Profile** (`CoreProfilePane`); non-tab sections (archived/prompts) fall back to Dashboard, so the pane is never blank. The tabbed mobile branch is a `flex flex-col h-full` with content in a `flex-1 overflow-y-auto` scroll container (`data-testid="ai-coaching-mobile-scroll"`) and the tab bar pinned below (the mobile slot clips overflow); a mobile-mount effect loads artifacts once when the list is empty. Also owns `handleViewArtifact` (maps a completed prompt → its artifact by a **tolerant title match** — case / `&`-vs-`and` / punctuation-insensitive, the shared `artifactTitlesMatch` from [ai-coaching-artifact-title.ts](/src/shared/ai-coaching-artifact-title.ts), so a drifted-title summary still opens): desktop `selectArtifact`s into the sub-sidebar's `ArtifactPane`; mobile opens [AICoachingMobileArtifactView.tsx](/src/renderer/src/features/ai-coaching/artifacts/AICoachingMobileArtifactView.tsx) (read-only, view-only compression-level + version switchers) via local `mobileArtifactId`, leaving `selectedArtifactId` untouched. Note: it does NOT mount the per-session `SessionPanel` itself — Dashboard's keep-alive `SessionPanel` pool already handles that whenever `useSessionStore.activeSessionId` is set, and `selectSession` syncs into that store. Behavior locked by [ai-coaching-mobile-layout-contract.md](/.claude/memory/contracts/ai-coaching-mobile-layout-contract.md).
- [stores/ai-coaching-sidebar-store.ts](/src/renderer/src/stores/ai-coaching-sidebar-store.ts) — Zustand store with `persist` middleware. Persistence key `amc.aiCoaching.sidebar.v1`, partialize keeps only `activeSection` and `expanded`.
- [stores/ai-coaching-store.ts](/src/renderer/src/stores/ai-coaching-store.ts) — backing store for prompts and artifacts (separate from the sidebar UI store). Owns the `loadVersions`, `loadVersion`, `updateArtifact`, `deleteArtifact`, and `exportCoachingData` actions consumed by the pane.
- [ArtifactPane.tsx](/src/renderer/src/features/ai-coaching/artifacts/ArtifactPane.tsx) — the right-pane editor that owns the view / edit / version-history / restore / delete UX. Handles the dirty-buffer snapshot, the 20K cap, Ctrl+S, the discard-changes confirm, and the stale-version save-conflict toast.
- [CoreProfilePane.tsx](/src/renderer/src/features/ai-coaching/profile/CoreProfilePane.tsx) — the Profile tab pane: view / edit / regenerate the user's Core Profile markdown. Shows a **stale-indicator banner** (`role="status"`) when `profileStaleness === 'stale'` (the profile's `updatedAt` predates the newest artifact) with a one-click **Refresh Profile** button that re-triggers `regenerateCoreProfile`. The banner hides during loading, in edit mode, and when the profile is fresh or unknown. Desktop shows text-label buttons; mobile (`isMobile` prop) shows icon-only buttons with `aria-label`. Dirty-edit guard via `ConfirmDialog`; Ctrl+S save. Tests: [core-profile-pane.test.tsx](/tests/unit/features/ai-coaching/core-profile-pane.test.tsx). Staleness detection: [getProfileStaleness](/src/main/services/ai/ai-coaching/session/service.ts) compares `profile.updatedAt` vs latest artifact timestamp.
- [ImportArtifactDialog.tsx](/src/renderer/src/features/ai-coaching/artifacts/ImportArtifactDialog.tsx) — modal dialog for importing existing work into a prompt. Paste or load-from-file, 20K char cap, Ctrl+Enter submit. Uses `AI_COACHING_IMPORT_ARTIFACT` IPC to save via `importArtifactForPrompt()` in [ai-coaching-service.ts](/src/main/services/ai/ai-coaching/session/service.ts), which calls `saveArtifactVersion` + fires the post-save pipeline. Desktop-only (overflow menu hidden on mobile via `useIsMobile()`).
- [BulkImportDialog.tsx](/src/renderer/src/features/ai-coaching/artifacts/BulkImportDialog.tsx) — multi-file import dialog with auto-matching confirmation grid. Uses `matchFileToPrompts()` from [bulk-import-matching.ts](/src/shared/bulk-import-matching.ts) for fuzzy prompt matching. Two IPC channels: `AI_COACHING_READ_FILES_FOR_BULK_IMPORT` (read + validate), `AI_COACHING_BULK_IMPORT_ARTIFACTS` (batch save with single push). Desktop-only.
- [BookmarkPane.tsx](/src/renderer/src/features/ai-coaching/bookmarks/BookmarkPane.tsx) — bookmark list with blockquote content, label, date, copy button, delete via `ConfirmDialog`, auto-reload via `AI_COACHING_BOOKMARKS_CHANGED` push listener. Includes a **label filter** (`Select` dropdown), **text search** (content + label), **grouped view** (when ≤10 distinct labels, bookmarks auto-group by label with an "Unlabeled" bucket; disabled when a filter/search is active), and a **quick-label picker** (`BookmarkLabelInput` — click-to-edit inline, `<datalist>` autocomplete from existing labels, Enter/blur commits, Escape cancels). Accepts an optional `isMobile` prop: when true, renders a **read-only list** (content + label + date, no search/filter/edit/delete) — the mobile Bookmarks tab routes here via `AICoachingRightPane`.
- [CoachingAnalyticsSection.tsx](/src/renderer/src/features/ai-coaching/insights/CoachingAnalyticsSection.tsx) — read-only analytics panel rendered at the bottom of the Dashboard. Four sub-panels (weekly timeline, artifacts by category, avg messages/session, total cost) using pure CSS horizontal bars. `memo`-wrapped, returns `null` when analytics is null or empty. Query: `getCoachingAnalytics()` in [bookmarks-profile.ts](/src/main/db/queries-ai-coaching/bookmarks-profile.ts).
- IPC: `AI_COACHING_LIST_ARTIFACTS`, `AI_COACHING_GET_ARTIFACT`, `AI_COACHING_LIST_VERSIONS`, `AI_COACHING_GET_VERSION`, `AI_COACHING_UPDATE_ARTIFACT`, `AI_COACHING_DELETE_ARTIFACT`, `AI_COACHING_LIST_INTERVIEW_PROMPTS`, `AI_COACHING_START_INTERVIEW`, `AI_COACHING_REJECT_ARTIFACT`, `AI_COACHING_START_SNACK`, `AI_COACHING_CREATE_BOOKMARK`, `AI_COACHING_LIST_BOOKMARKS`, `AI_COACHING_UPDATE_BOOKMARK`, `AI_COACHING_DELETE_BOOKMARK`, `AI_COACHING_GET_PINNED_CONTEXT`, `AI_COACHING_SET_PINNED_CONTEXT` (+ the `AI_COACHING_PINS_CHANGED` push), `AI_COACHING_IMPORT_ARTIFACT`, `AI_COACHING_READ_FILE_FOR_IMPORT`, `AI_COACHING_READ_FILES_FOR_BULK_IMPORT`, `AI_COACHING_BULK_IMPORT_ARTIFACTS`, `AI_COACHING_EXPORT`, `AI_COACHING_GET_ANALYTICS` — channel names in [src/shared/ipc-channels/ai.ts](/src/shared/ipc-channels/ai.ts), schemas in [src/shared/ipc-schemas.ts](/src/shared/ipc-schemas.ts), handlers in [src/main/ipc/ai-coaching-handlers.ts](/src/main/ipc/ai-coaching-handlers.ts) and [src/main/ipc/ai-coaching/import-handlers.ts](/src/main/ipc/ai-coaching/import-handlers.ts). The CLI session-artifact save, Update, Delete, and Reject each emit `AI_COACHING_ARTIFACTS_CHANGED`; bookmark mutations emit `AI_COACHING_BOOKMARKS_CHANGED`. There is **no** `AI_COACHING_SAVE_FROM_TRANSCRIPT` channel — save now happens via the CLI session-artifact endpoint the coach calls (see "How artifacts get saved" above), not via a separate Anthropic API call.
- **Artifact save** (the "How artifacts get saved" section above): the coach persists its artifact by calling `POST /ai-coaching/sessions/:sessionId/artifact` (body `{ content }`), registered in [session-routes.ts](/src/main/services/cli/ai-coaching/session-routes.ts). The route runs the coaching mutation preamble (kill-switch + auth + rate-limit), then `bindReadSession` (the caller's agent/in-app token may write ONLY its own session — a foreign sessionId is a 403, the global cli-token is unbound), then a Zod `{ content: 1..20000 }` parse (empty / >20K → 400), then [saveCoachingArtifactForSession](/src/main/services/ai/ai-coaching/session/service.ts). That service resolves the session's `producesArtifactTitle` (null → not a coaching session → 404), persists through the shared `persistCoachingArtifactContent()` sink (content-dedup, the `AI_COACHING_ARTIFACTS_CHANGED` push, and the levels + Core Profile pipeline), and returns `{ id, version, title }`. The title is ALWAYS derived from the session — the body has no title field — so a coach can only ever write its own artifact (the anti-injection guarantee the retired detectors enforced by coercion). Apply-immediately: no inbox approval. Tests: [ai-coaching-save-artifact.test.ts](/tests/integration/ai-coaching-save-artifact.test.ts) (service path) + [cli-server-ai-coaching-session-artifact.test.ts](/tests/integration/cli-server-ai-coaching-session-artifact.test.ts) (route — 403 cross-session, 404, 400, dedup, apply-immediately).
- **Retired 2026-07-23:** the file-write capture (`resolveAiCoachingArtifactWrite` / `recordCoachingArtifactWrite` / `tryImportCoachingArtifactFiles`) and the inline `<artifact>`-tag path (`ARTIFACT_TAG_RE` / `extractArtifactFromAgentMessage` / `tryExtractAiCoachingArtifact`) were removed. The shared helpers `artifactFileSlug` + `MAX_ARTIFACT_FILE_CHARS` + `resolveAICoachingArtifactsDir` survive for the level-file cache + free-form base context.
- **Display marker hygiene**: `stripCoachingMarkersForSession()` in [ai-coaching-service.ts](/src/main/services/ai/ai-coaching/session/service.ts) (over the pure `stripCoachingMarkers` / `hasCoachingMarker` in [src/shared/ai-coaching-markers.ts](/src/shared/ai-coaching-markers.ts)) strips `<artifact>` / `[PHASE:N]` / `[SEGMENT:N]` markers from the text `finalizeStreamingMessage` persists + pushes, keeping the prose between tags — so the markers never leak into the chat (the `</artifact>` screenshot bug). Coaching-scoped + cheap: no DB hit for marker-free text, and a regular session that merely quotes a marker is left untouched. The RAW `streamingAccumulator` is still what the phase / segment parsers read. Tests: [ai-coaching-markers.test.ts](/tests/unit/shared/ai-coaching-markers.test.ts).
- **Save button** ([SaveFromTranscriptButton.tsx](/src/renderer/src/features/ai-coaching/artifacts/SaveFromTranscriptButton.tsx) — file name kept for git history): mounted at the top of an AI Coaching session pane. Click sends `Please save the {producesArtifactTitle} artifact now — save it as described in your instructions, and show me the document in your reply.` via `SESSION_SEND_RESPONSE`, with `displayText: "Save artifact"` so the message bubble in the transcript shows that label instead of the full prompt. Debounced 5s via a `useRef`-backed timestamp. Disabled when status ∉ {`running`, `needs_you`, `ready`, `starting`}.
- **Teaching Claude the format**: [src/main/services/ai/ai-coaching-service.ts](/src/main/services/ai/ai-coaching/session/service.ts) `buildAICoachingClaudeMd()` composes the project-level CLAUDE.md that's written into `~/Claude/ai-coaching/CLAUDE.md` on `AI_COACHING_START_INTERVIEW`. It includes (a) the shared context block, (b) the active interview prompt body, (c) a `## Your First Response` section with the opening message template (initial creation only), (d) a `## Conversation So Far` section with the latest compaction summary (resume only), (e) a `## What You Already Know` section that gives the interview its prior-artifact context via the **3-layer pull model** below, and (f) explicit instructions to **save** the finished document via the CLI session-artifact endpoint (and to also show it inline in the chat as clean prose) plus a "save immediately on user request" clause so the Save button's nudge is honored. Snack sessions use a parallel assembly path via `startSnackFromPromptId()` with the snack prompt body + existing artifact as context. The whole CLAUDE.md is bounded by `TOTAL_CLAUDEMD_BUDGET` (120,000 chars).
- **The 3-layer artifact pull model** (what the interview receives, replacing the old budgeted `[BEGIN ARTIFACTS]` tier block): rather than stuffing artifact _content_ into the prompt, the builder now hands the coach (1) an **always-on baseline** — the Core Profile + bookmarked Key Moments (unchanged); (2) the artifacts the user **pinned** for this interview, rendered IN FULL under `#### Pinned Context (loaded in full for this interview)`; and (3) every _other_ artifact as a one-line entry in a **pull menu** (`#### Your artifacts — pull what you need`) — `- **Title** (\`slug\`) — <gist>`followed by`Pull: Full ~Nk · Key Points ~N · Summary ~N`(only the levels that were actually generated; Micro is the gist, never a pull row). The coach reads`.artifact-levels/<slug>.<level>.md`on demand to load just the levels it judges relevant. The gist is the artifact's **Micro** level, or a trimmed Full prefix when Micro is absent; the`~token`figures use the shared`estimateTokens`(chars/4) from [src/shared/token-estimate.ts](/src/shared/token-estimate.ts). A pinned artifact DROPS OUT of the pull menu. Pinned-Full is bounded by`TOTAL_CLAUDEMD_BUDGET`; on overflow `selectPinnedRendering`condenses the LARGEST pinned artifact a level at a time, then SPILLS any that still don't fit into the pull menu behind a`> Note: N pinned artifact(s) moved to the menu…`line — never silently dropped.`buildAICoachingClaudeMd`is called at three sites (immediate start, deferred start, and resume via`refreshCoachingClaudeMdForSession`). Locked by [tests/unit/ai-coaching-claude-md.test.ts](/tests/unit/ai-coaching-claude-md.test.ts).
- **Level-file cache**: [src/main/services/ai/ai-coaching-level-files.ts](/src/main/services/ai/ai-coaching/context/level-files.ts) projects each artifact's generated levels into a markdown file under `~/Claude/ai-coaching/.artifact-levels/` — a SIBLING of `artifacts/` (deliberately NOT inside it, kept separate from the artifacts dir; Omniscio writes these via `fs`, never the agent tool stream). `reconcileArtifactLevelFiles(artifacts, dir?)` rebuilds the WHOLE cache from the DB at interview-build time (its ONLY consumer — no per-save/per-delete hook that could drift), using atomic temp+rename writes, pruning orphans + stray `.tmp` files, capping each file at `MAX_ARTIFACT_FILE_CHARS` (20K), and is FAIL-OPEN (never throws into the spawn path). Locked by [tests/unit/services/ai-coaching-level-files.test.ts](/tests/unit/services/ai-coaching-level-files.test.ts).
- **Pinned context (per-prompt + per-session)**: which artifacts are pinned lives in the `ai_coaching_pinned_context` table (migration [20260623014012-ai-coaching-pinned-context.ts](/src/main/db/migrations/20260623014012-ai-coaching-pinned-context.ts)) — ONE JSON row per `(scope, scope_id)`, so "configured to none" (a present row with `titles_json = []`) is DISTINCT from "never configured" (no row → the service seeds from the prompt's authored `primaryContext`). Queries `getPinnedTitles` / `setPinnedTitles` in [src/main/db/queries-ai-coaching/index.ts](/src/main/db/queries-ai-coaching/index.ts); `resolveEffectivePinnedTitles(db, prompt, sessionId, artifacts)` unions the prompt pins (or the `primaryContext` seed) with the session pins, filters to existing artifact titles, de-dupes, and restores real-title casing. v1 limits: pins bake in at session BUILD time (no mid-session re-pin). The **session-scoped pin button is available on mobile as well as desktop** — it opens `PinnedContextPicker`, which is built on `DialogShell` and therefore renders as a bottom sheet on a phone; it was hidden behind an `isMobile` gate under a "Desktop-only for v1" note that had outlived its reason, so a phone user had no way to pin anything and nothing told them why (product-polish W9, F051). The **per-prompt** "Pinned context" row in the dashboard card menu stays desktop-only. The snack path is unchanged. Locked by [tests/unit/db/ai-coaching-pinned-context.test.ts](/tests/unit/db/ai-coaching-pinned-context.test.ts).
- **Start-interview flow** (the "Clicking the card spawns a new Claude session pre-loaded with that prompt's body" line above): `AI_COACHING_START_INTERVIEW` writes the prompt body into `~/Claude/ai-coaching/CLAUDE.md` (which Claude Code auto-loads as project-level instructions), then calls `createSessionWithPrompt` with `initialPrompt: "Let's begin the <Title> interview."` — the kickoff is intentionally minimal because the persona / goal / first-response template all live in CLAUDE.md. Without that kickoff `processManager.launch` hits the no-prompt branch and just marks the session 'ready' (no CLI spawn, agent never reads CLAUDE.md, interview never starts). Tests: [tests/integration/ai-coaching-handlers.test.ts](/tests/integration/ai-coaching-handlers.test.ts) asserts the kickoff is passed.
- Project sentinel: `AI_COACHING_PROJECT_ID = '__ai_coaching__'` in [src/shared/types.ts](/src/shared/types.ts). Dashboard routes that sentinel through `isAICoachingSelected` in [src/renderer/src/features/dashboard/Dashboard.tsx](/src/renderer/src/features/dashboard/Dashboard.tsx).
- **Multi-level generation + pin** (the "Choosing a context level" section above): `generateArtifactLevels()` in [ai-coaching-service.ts](/src/main/services/ai/ai-coaching/session/service.ts) replaces the old single-summary call in `triggerPostArtifactSavePipeline` — it produces Key Points / Summary / Micro in one fire-and-forget, cost-capped pass using the vendored ContextDock prompts in [ai-coaching-level-prompts.ts](/src/main/services/ai/ai-coaching/context/level-prompts.ts) (`Promise.allSettled`, anti-inflation drop of any level not shorter than source, `sourceHash` idempotency), persisted via `updateArtifactLevels`. The human pin rides the `AI_COACHING_SET_ARTIFACT_LEVEL` channel → `setArtifactPreferredLevel` (`preferred_level` per `(id, version)`, `null` = AI chooses); the `ArtifactPane` Detail-level control (a labeled `SegmentedControl`) previews + pins. Columns `key_points_text` / `micro_text` / `levels_meta` / `preferred_level` via migration `20260608051459-…`. Tests: [ai-coaching-artifact-levels-generation.test.ts](/tests/unit/services/ai-coaching-artifact-levels-generation.test.ts) + the migration/queries tests under [tests/unit/db/](/tests/unit/db/). (Legacy `AICoachingLibrary.tsx` + `ArtifactViewModal.tsx` were deleted as orphans in this change.)
- **Regular-session artifact context injection** (the "How artifacts inform your other sessions" section above): the markdown block builder is [artifact-context-builder.ts](/src/main/services/artifact-context-builder.ts) (`buildArtifactContextBlock(artifacts, { totalCap })` — budget-aware 4-level selection under `PROFILE_BLOCK_TOTAL_CAP`; returns `null` when the list is empty). [createSessionWithPrompt](/src/main/services/session/session-create.ts) calls it after the SEED_BUNDLE and `injectInAppToken` branches, gated on `settings.aiCoachingEnabled`, `!project.folderPath.startsWith('__')`, and the `launchPrompt === input.initialPrompt` "no other seed fired" predicate. With an `initialPrompt`, the block is prepended to it; without one, it is passed as the new 7th-positional `firstSendPrefix` arg to [processManager.launch](/src/main/process/process-manager.ts), which seeds it into `session.pendingPrompt` so `writeToStdin`'s existing merge folds it into the user's first stdin message. Tests: [tests/unit/artifact-context-builder.test.ts](/tests/unit/artifact-context-builder.test.ts) (builder shape) and [tests/unit/session-create-artifact-context.test.ts](/tests/unit/session-create-artifact-context.test.ts) (gating + delivery paths).

## Related

The user-facing half of the feature is on the [AI Coaching sidebar (interview prompts and artifacts)](ai-coaching.md) page: its artifacts in [part 2](ai-coaching-part-2.md) and its memory and profile in [part 3](ai-coaching-part-3.md). [CLI control](cli-ai-coaching.md) is the companion page for driving coaching from an agent.
