AI Coaching sidebar (interview prompts and artifacts) (part 4)
Part 4 of the AI Coaching sidebar page: the CLI control-server surface for AI Coaching, the developer-only interview prompt editor, and the implementation pointers for agents working on the feature.
What it is
This is part 4 of the AI Coaching sidebar (interview prompts and artifacts) 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 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.
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.
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) 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) 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/ and src/renderer/src/stores/):
- AICoachingSubSidebar.tsx — the four-section sub-sidebar. Sessions subgrouping logic + precedence chain lives in
bucketSessionandGROUP_ORDERhere. Mirrors the predicates in SessionsSidebar.tsx — read that before changing precedence. - 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
CompletedCardcollapses to a compact single row viauseIsMobile()— ai-coaching-dashboard-cards.tsx). The first-runAICoachingWelcomeis gated onshowWelcome(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. Lazy-loaded. The grouping/filter/search view-model is the pure ai-coaching-picker.ts (buildPickerView); the theme bar + search are CoachingBrowseBar.tsx; theme order/colors/labels/subtitles are the single source in ai-coaching-prompt-labels.ts. - AICoachingRightPane.tsx — section-aware router for the right pane. On desktop: Dashboard / artifact pane / empty prompt. On mobile: a bottom tab bar (AICoachingMobileTabBar.tsx, a feature-local
role="tablist"— NOT the global appMobileTabBar) drivesactiveSection, and the pane routes it to Dashboard / Sessions (SessionsGroups; a tap callsnavigateTo('session-detail')via the new optionalonAfterSelectprop) / Artifacts (ArtifactsListwith anonSelectthat opens the read-only mobile view) / Bookmarks (BookmarkPanewithisMobile— read-only) / Profile (CoreProfilePane); non-tab sections (archived/prompts) fall back to Dashboard, so the pane is never blank. The tabbed mobile branch is aflex flex-col h-fullwith content in aflex-1 overflow-y-autoscroll 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 ownshandleViewArtifact(maps a completed prompt → its artifact by a tolerant title match — case /&-vs-and/ punctuation-insensitive, the sharedartifactTitlesMatchfrom ai-coaching-artifact-title.ts, so a drifted-title summary still opens): desktopselectArtifacts into the sub-sidebar'sArtifactPane; mobile opens AICoachingMobileArtifactView.tsx (read-only, view-only compression-level + version switchers) via localmobileArtifactId, leavingselectedArtifactIduntouched. Note: it does NOT mount the per-sessionSessionPanelitself — Dashboard's keep-aliveSessionPanelpool already handles that wheneveruseSessionStore.activeSessionIdis set, andselectSessionsyncs into that store. Behavior locked by ai-coaching-mobile-layout-contract.md. - stores/ai-coaching-sidebar-store.ts — Zustand store with
persistmiddleware. Persistence keyamc.aiCoaching.sidebar.v1, partialize keeps onlyactiveSectionandexpanded. - stores/ai-coaching-store.ts — backing store for prompts and artifacts (separate from the sidebar UI store). Owns the
loadVersions,loadVersion,updateArtifact,deleteArtifact, andexportCoachingDataactions consumed by the pane. - 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 — the Profile tab pane: view / edit / regenerate the user's Core Profile markdown. Shows a stale-indicator banner (
role="status") whenprofileStaleness === 'stale'(the profile'supdatedAtpredates the newest artifact) with a one-click Refresh Profile button that re-triggersregenerateCoreProfile. The banner hides during loading, in edit mode, and when the profile is fresh or unknown. Desktop shows text-label buttons; mobile (isMobileprop) shows icon-only buttons witharia-label. Dirty-edit guard viaConfirmDialog; Ctrl+S save. Tests: core-profile-pane.test.tsx. Staleness detection: getProfileStaleness comparesprofile.updatedAtvs latest artifact timestamp. - 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_ARTIFACTIPC to save viaimportArtifactForPrompt()in ai-coaching-service.ts, which callssaveArtifactVersion+ fires the post-save pipeline. Desktop-only (overflow menu hidden on mobile viauseIsMobile()). - BulkImportDialog.tsx — multi-file import dialog with auto-matching confirmation grid. Uses
matchFileToPrompts()from 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 — bookmark list with blockquote content, label, date, copy button, delete via
ConfirmDialog, auto-reload viaAI_COACHING_BOOKMARKS_CHANGEDpush listener. Includes a label filter (Selectdropdown), 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 optionalisMobileprop: when true, renders a read-only list (content + label + date, no search/filter/edit/delete) — the mobile Bookmarks tab routes here viaAICoachingRightPane. - 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, returnsnullwhen analytics is null or empty. Query:getCoachingAnalytics()in 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(+ theAI_COACHING_PINS_CHANGEDpush),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, schemas in src/shared/ipc-schemas.ts, handlers in src/main/ipc/ai-coaching-handlers.ts and src/main/ipc/ai-coaching/import-handlers.ts. The CLI session-artifact save, Update, Delete, and Reject each emitAI_COACHING_ARTIFACTS_CHANGED; bookmark mutations emitAI_COACHING_BOOKMARKS_CHANGED. There is noAI_COACHING_SAVE_FROM_TRANSCRIPTchannel — 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. The route runs the coaching mutation preamble (kill-switch + auth + rate-limit), thenbindReadSession(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. That service resolves the session'sproducesArtifactTitle(null → not a coaching session → 404), persists through the sharedpersistCoachingArtifactContent()sink (content-dedup, theAI_COACHING_ARTIFACTS_CHANGEDpush, 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 (service path) + 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 helpersartifactFileSlug+MAX_ARTIFACT_FILE_CHARS+resolveAICoachingArtifactsDirsurvive for the level-file cache + free-form base context. - Display marker hygiene:
stripCoachingMarkersForSession()in ai-coaching-service.ts (over the purestripCoachingMarkers/hasCoachingMarkerin src/shared/ai-coaching-markers.ts) strips<artifact>/[PHASE:N]/[SEGMENT:N]markers from the textfinalizeStreamingMessagepersists + 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 RAWstreamingAccumulatoris still what the phase / segment parsers read. Tests: ai-coaching-markers.test.ts. - Save button (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.viaSESSION_SEND_RESPONSE, withdisplayText: "Save artifact"so the message bubble in the transcript shows that label instead of the full prompt. Debounced 5s via auseRef-backed timestamp. Disabled when status ∉ {running,needs_you,ready,starting}. - Teaching Claude the format: src/main/services/ai/ai-coaching-service.ts
buildAICoachingClaudeMd()composes the project-level CLAUDE.md that's written into~/Claude/ai-coaching/CLAUDE.mdonAI_COACHING_START_INTERVIEW. It includes (a) the shared context block, (b) the active interview prompt body, (c) a## Your First Responsesection with the opening message template (initial creation only), (d) a## Conversation So Farsection with the latest compaction summary (resume only), (e) a## What You Already Knowsection 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 viastartSnackFromPromptId()with the snack prompt body + existing artifact as context. The whole CLAUDE.md is bounded byTOTAL_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 byPull: 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>.mdon 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~tokenfigures use the sharedestimateTokens(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 byTOTAL_CLAUDEMD_BUDGET; on overflowselectPinnedRenderingcondenses 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.buildAICoachingClaudeMdis called at three sites (immediate start, deferred start, and resume viarefreshCoachingClaudeMdForSession`). Locked by tests/unit/ai-coaching-claude-md.test.ts. - Level-file cache: src/main/services/ai/ai-coaching-level-files.ts projects each artifact's generated levels into a markdown file under
~/Claude/ai-coaching/.artifact-levels/— a SIBLING ofartifacts/(deliberately NOT inside it, kept separate from the artifacts dir; Omniscio writes these viafs, 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.tmpfiles, capping each file atMAX_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. - Pinned context (per-prompt + per-session): which artifacts are pinned lives in the
ai_coaching_pinned_contexttable (migration 20260623014012-ai-coaching-pinned-context.ts) — ONE JSON row per(scope, scope_id), so "configured to none" (a present row withtitles_json = []) is DISTINCT from "never configured" (no row → the service seeds from the prompt's authoredprimaryContext). QueriesgetPinnedTitles/setPinnedTitlesin src/main/db/queries-ai-coaching/index.ts;resolveEffectivePinnedTitles(db, prompt, sessionId, artifacts)unions the prompt pins (or theprimaryContextseed) 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 opensPinnedContextPicker, which is built onDialogShelland therefore renders as a bottom sheet on a phone; it was hidden behind anisMobilegate 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. - Start-interview flow (the "Clicking the card spawns a new Claude session pre-loaded with that prompt's body" line above):
AI_COACHING_START_INTERVIEWwrites the prompt body into~/Claude/ai-coaching/CLAUDE.md(which Claude Code auto-loads as project-level instructions), then callscreateSessionWithPromptwithinitialPrompt: "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 kickoffprocessManager.launchhits 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 asserts the kickoff is passed. - Project sentinel:
AI_COACHING_PROJECT_ID = '__ai_coaching__'in src/shared/types.ts. Dashboard routes that sentinel throughisAICoachingSelectedin src/renderer/src/features/dashboard/Dashboard.tsx. - Multi-level generation + pin (the "Choosing a context level" section above):
generateArtifactLevels()in ai-coaching-service.ts replaces the old single-summary call intriggerPostArtifactSavePipeline— 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 (Promise.allSettled, anti-inflation drop of any level not shorter than source,sourceHashidempotency), persisted viaupdateArtifactLevels. The human pin rides theAI_COACHING_SET_ARTIFACT_LEVELchannel →setArtifactPreferredLevel(preferred_levelper(id, version),null= AI chooses); theArtifactPaneDetail-level control (a labeledSegmentedControl) previews + pins. Columnskey_points_text/micro_text/levels_meta/preferred_levelvia migration20260608051459-…. Tests: ai-coaching-artifact-levels-generation.test.ts + the migration/queries tests under tests/unit/db/. (LegacyAICoachingLibrary.tsx+ArtifactViewModal.tsxwere 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 (
buildArtifactContextBlock(artifacts, { totalCap })— budget-aware 4-level selection underPROFILE_BLOCK_TOTAL_CAP; returnsnullwhen the list is empty). createSessionWithPrompt calls it after the SEED_BUNDLE andinjectInAppTokenbranches, gated onsettings.aiCoachingEnabled,!project.folderPath.startsWith('__'), and thelaunchPrompt === input.initialPrompt"no other seed fired" predicate. With aninitialPrompt, the block is prepended to it; without one, it is passed as the new 7th-positionalfirstSendPrefixarg to processManager.launch, which seeds it intosession.pendingPromptsowriteToStdin's existing merge folds it into the user's first stdin message. Tests: tests/unit/artifact-context-builder.test.ts (builder shape) and 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) page: its artifacts in part 2 and its memory and profile in part 3. CLI control is the companion page for driving coaching from an agent.
Last verified 2026-09-27