---
title: AI Coaching sidebar (interview prompts and artifacts) (part 3)
---

# AI Coaching sidebar (interview prompts and artifacts) (part 3)

## What it is

This is part 3 of the [AI Coaching sidebar (interview prompts and artifacts)](ai-coaching.md) page. It covers what the coach remembers and what can be read back out of it — free-form (non-interview) coaching sessions, the conversation compaction and durable memory facts behind them, the synthesized Core Profile, the two Life Inventory surfaces, and the validation telemetry the memory system emits.

## Where to find it

AI Coaching is the virtual project in the left sidebar, and the [parent page](ai-coaching.md) covers turning it on and working the Dashboard. Within it the Core Profile has its own pane, the Memory Explorer sits in the AI Coaching sidebar section and the Life Inventory panes sit alongside them; the rest of this part describes work you never click — summaries, extracted facts and cost caps that happen in the background.

## How it behaves

### Free-form (non-interview) coaching sessions

Not every session in the AI Coaching project is a structured interview. If you open a **generic** session there — a plain "+ New", or a CLI / deep-link spawn (anything whose session `source` is NOT prefixed `ai-coaching:`) — the coach still gets your **current** context: a short **Core Profile** summary plus the artifact **index** (every artifact as a one-line gist + per-level token costs, pulled on demand), the same lean pull menu an interview uses, but with **no** interview scaffolding (no fixed script, no opening-message template, no "produce an artifact" closing). It's a free-form coaching conversation.

That context is built fresh from your live artifacts at spawn ([`buildCoachingBaseContext`](/src/main/services/ai/ai-coaching/context/claudemd-builder.ts) → [`writeCoachingBaseContext`](/src/main/services/ai/ai-coaching/context/base-context.ts)) and written to the shared `~/Claude/ai-coaching/CLAUDE.md` right before the CLI launches — so a free-form session can never read a stale CLAUDE.md an earlier interview left behind (the bug this fixes: a plain session was reading a month-old file). The write is **gated** in [session-create.ts](/src/main/services/session/session-create.ts) to fire ONLY for the AI Coaching project AND a non-`ai-coaching:` source (`isCoachingFeatureSource` is the gate), so it can never clobber a structured interview's own richer CLAUDE.md — the interview path writes its file before `createSessionWithPrompt` runs. It is fail-open (a context-write failure never blocks the spawn). Because all coaching sessions share ONE `CLAUDE.md`, two spawning in the same instant race it (pre-existing; base contexts are identical so it's usually benign — a per-session context dir is a possible future fix). Locked by [ai-coaching-base-context.test.ts](/tests/unit/ai-coaching-base-context.test.ts) + [ai-coaching-base-context-write.test.ts](/tests/unit/ai-coaching-base-context-write.test.ts).

### Conversation compaction (segment summaries)

Long coaching interviews are divided into segments. When you reach a segment boundary and the system pauses, a background process summarizes the conversation so far into a compact summary. This summary is stored and, when you resume the session later, injected into the CLAUDE.md as a `## Conversation So Far` section. Claude reads this summary to understand the conversation history without needing the full transcript in its context window.

The summary is generated by a Haiku call (fast, low-cost) and capped at 1,024 tokens. The input conversation is capped at 60,000 characters. If the summary generation fails for any reason (no API key, rate limit, network error), the failure is logged silently and the session continues without a summary — it never blocks or crashes the segment pause flow.

Cost for compaction calls is tracked under the label `ai-coaching-compaction` in the API cost log. A daily cost cap of **$0.50** (`DEFAULT_COACHING_COMPACTION_CAP_USD`) prevents runaway spend — once the cap is reached, further compaction calls are skipped for the rest of the day. The cap check fails open on DB errors so a transient issue never silently blocks summaries. The API key fallback chain is: your active API key account → your first API key account → graceful failure.

### Conversation memory (durable facts)

After each compaction summary is generated, a separate background pass extracts **durable facts** from the conversation segment — things worth remembering across sessions (goals the user mentioned, values they expressed, decisions they made, preferences they stated). These facts are stored in the `ai_coaching_memory` table and persist independently of the compaction summary.

The extraction uses its own Haiku call with its own lock/reservation cycle — `withDailyCapAccountLock` is NOT reentrant, so memory extraction fires AFTER the compaction summary's lock releases. It's wired as a fire-and-forget call from `generateCompactionSummary` (errors are caught internally, never propagated to the compaction flow).

A second extraction path fires from `triggerPostArtifactSavePipeline` — when an artifact is saved, `extractMemoriesFromArtifact()` extracts durable facts from the artifact content using the same Haiku prompt and parser. It uses the artifact's `source_session_id` for dedup scoping (falling back to `'artifact-extract'` when no source session exists), so overlapping extractions from the live-session marker path and the artifact path correctly deduplicate. This path is debounced (3s) with the same pending-map + in-flight guard pattern as goal extraction.

Each extracted fact has:

- **`factType`** — one of `value`, `goal`, `preference`, `decision`, `event`, `insight`.
- **`content`** — a concise 1–2 sentence statement.
- **`confidence`** — `high`, `medium`, or `low`.
- **`sourceSegment`** — which compaction segment the fact was extracted from.

Safety guards:

- **Minimum transcript length** — transcripts shorter than 200 characters are skipped (too little signal).
- **Per-fact schema validation** — each fact is validated individually; malformed or invalid facts are silently dropped while valid ones are kept.
- **Fact cap** — at most 20 facts per extraction, preferring higher-confidence facts.
- **Cost cap** — shares the session pool ($0.35/day) with compaction summaries, so memory extraction never competes with profile generation.
- **Reservation (F062)** — uses the same `reserveAiCoachingCost('session')` mechanism as compaction to prevent concurrent overshoot.

Cost is tracked under the label `ai-coaching-memory` in the API cost log. The `AI_COACHING_MEMORY_SOURCE` is included in the session pool's `checkSessionCostCap` sources array.

#### Surfacing memories in coaching prompts

Extracted memories are injected into every coaching session's CLAUDE.md as a `#### Personal Context` section, placed between Core Profile and Key Moments (bookmarks). At prompt-build time, `formatMemoriesForContext()` in [ai-coaching-claudemd-builder.ts](/src/main/services/ai/ai-coaching/context/claudemd-builder.ts) queries the most recent memories via `listAllMemories(db, { limit: COACHING_MEMORY_CONTEXT_LIMIT })` (limit is 20, defined once in [ai-coaching-claudemd-text.ts](/src/main/services/ai/ai-coaching/context/claudemd-text.ts)), deduplicates them against the core profile (case-insensitive substring match — if a memory's content already appears in the profile, it's dropped), groups survivors by category (`Facts`, `Goals`, `Preferences`, `Decisions`, `Relationships`), and truncates to a `MEMORY_BUDGET_MAX` (2,000-character) budget. When no memories survive dedup (or none exist), the section is omitted entirely — no empty heading.

The memory block is included in `baselineChars` so it counts against the total 120K CLAUDE.md budget. All four call sites wire it: three in [ai-coaching-service.ts](/src/main/services/ai/ai-coaching/session/service.ts) (deferred interview, immediate interview, refresh) via the `tryListMemories` helper, and one in [ai-coaching-base-context.ts](/src/main/services/ai/ai-coaching/context/base-context.ts) (free-form sessions) with inline try/catch. All call sites are **fail-open** — a `listAllMemories` error is logged as a warning and the session proceeds without memories (empty array fallback). Telemetry fields `memoryCount` and `memoryChars` are emitted with every `memory_context_built` event.

Implementation: [ai-coaching-memory.ts](/src/main/services/ai/ai-coaching/generation/memory.ts) (extraction service), [memory.ts](/src/main/db/queries-ai-coaching/memory.ts) (queries: `insertMemoryFacts`, `listMemoryFacts`, `softDeleteMemoryFact`), migration [20260730184927-ai-coaching-conversation-memory-table.ts](/src/main/db/migrations/20260730184927-ai-coaching-conversation-memory-table.ts). Tests: [ai-coaching-memory-parser.test.ts](/tests/unit/services/ai-coaching-memory-parser.test.ts) / [-error-surfacing.test.ts](/tests/unit/services/ai-coaching-memory-error-surfacing.test.ts) (service) + memory fact tests in [queries-ai-coaching.test.ts](/tests/unit/db/queries/queries-ai-coaching.test.ts).

#### Memory Explorer

The **Memory Explorer** panel lets users see, search, edit, delete, and manually create the memories the coaching engine has extracted from their conversations. It's accessible from the AI Coaching sidebar section.

Features:

- **Category filter tabs** — filter by All, Facts, Decisions, Preferences, Relationships, Goals, with per-category counts. Each category shows a brief description of what belongs there.
- **FTS5 full-text search** — debounced (300ms) search with highlighted snippet results, scoped to the active category filter. Minimum query length is 3 characters.
- **Manual creation** — "Add Memory" button opens an inline form; the memory is saved under the active category (defaults to Facts when "All" is selected) with `sessionId: 'manual'` and `confidence: 'high'`.
- **Inline editing** — click the edit icon to modify a memory's content in-place; save or cancel.
- **Delete with confirmation** — destructive action routed through `ConfirmDialog`.
- **Push-driven refresh** — listens on `AI_COACHING_MEMORIES_CHANGED` to update the list when new memories are extracted in the background, guarding against clobbering in-flight edits.

IPC channels: `ai-coaching:memories-by-category` (paginated list + counts), `ai-coaching:search-memories` (FTS5 search), `ai-coaching:delete-memory`, `ai-coaching:update-memory`, `ai-coaching:create-memory` (manual creation). All are in the IPC response map and mobile-WS-baselined.

Implementation: [MemoryExplorerPane.tsx](/src/renderer/src/features/ai-coaching/memory/MemoryExplorerPane.tsx), backend queries in [memory-queries.ts](/src/main/db/queries-ai-coaching/memory.ts) and [memory-search.ts](/src/main/db/queries-ai-coaching/memory-search.ts).

#### Cross-artifact insight injection

When the coaching engine discovers patterns that span multiple artifacts — contradictions, evolutions, or recurring themes — it stores them as **cross-artifact insights** in the `ai_coaching_insights` table. These insights are injected into every coaching session's CLAUDE.md as a `#### Cross-Artifact Insights` section, positioned between the journal block and the goals block.

At prompt-build time, `formatInsightsForContext()` in [ai-coaching-claudemd-builder.ts](/src/main/services/ai/ai-coaching/context/claudemd-builder.ts) loads active (non-dismissed, non-deleted) insights via `listActiveInsights(db, accountId)`, renders each insight's `content.summary` as a bullet with its artifact-pair label (e.g. `*(Career History ↔ Core Values)*`), and greedy-packs them within a `INSIGHT_BUDGET_MAX` (2,000-character) budget. When no insights exist or none fit the budget, the section is omitted entirely — no empty heading.

Insights are account-scoped, so loading requires a resolved API key (`resolveApiKeyForAiFeatures()`). If no key is available or the query fails, insights are silently skipped — the session proceeds without them (empty array fallback, same fail-open pattern as memories and goals). Telemetry fields `insightCount` and `insightChars` are emitted with every `memory_context_built` event.

The insight block is included in `baselineChars` so it counts against the total 120K CLAUDE.md budget. Both call sites wire it: the interview path via `buildAICoachingClaudeMd()` and the free-form path via `buildCoachingBaseContext()` / `writeCoachingBaseContext()`.

Implementation: [insights.ts](/src/main/db/queries-ai-coaching/insights.ts) (`listActiveInsights` query), [ai-coaching-claudemd-builder.ts](/src/main/services/ai/ai-coaching/context/claudemd-builder.ts) (`formatInsightsForContext`, `INSIGHT_BUDGET_MAX`), [ai-coaching-base-context.ts](/src/main/services/ai/ai-coaching/context/base-context.ts) (insight loading for free-form sessions). Tests: insight integration tests in [ai-coaching-claudemd-builder.test.ts](/tests/unit/ai-coaching-claudemd-builder.test.ts).

### Core profile (synthesized context)

As you complete more interviews, the system builds a **core profile** — a synthesized ~1,200-character document that captures who you are across all your artifacts: primary themes and values, key life facts, tensions and contradictions, and open questions worth exploring. Think of it as the difference between reading 15 flash cards about someone and reading a single paragraph that captures the person.

The core profile is generated automatically after every artifact save, once you have at least 3 completed artifacts. It uses a fast, low-cost Haiku call to read all your artifacts and produce the synthesis. If you have fewer than 3 artifacts, no profile is generated — individual artifacts provide sufficient context at that stage.

When the core profile exists, it's injected at the very top of the `## What You Already Know` section in every coaching session's CLAUDE.md — before any individual artifact blocks. This gives Claude a cohesive understanding of who you are before diving into per-artifact details. The profile is always loaded in full (never truncated), and its ~1,200 characters reduce the budget available for individual artifacts by that amount.

The profile is a single document that gets replaced each time it regenerates — there's no version history (your individual artifacts ARE the history). If profile generation fails for any reason (no API key, cost cap hit, network error), the system works exactly as before — no profile block, individual artifacts fill the budget. The profile is purely additive. When auto-refresh is blocked, the Core Profile pane shows a contextual banner: a daily cost-cap hit shows "Auto-refresh paused — daily usage limit reached. Will retry automatically" (retries at midnight UTC); other errors show "Auto-refresh paused — service temporarily unavailable."

When newer interview artifacts arrive after the profile was last generated, the **Core Profile** pane surfaces a _"your profile may be out of date"_ banner with a one-click **Refresh Profile** button that re-runs the synthesis on demand. Staleness is detected at read time (the `isStale` flag on the GET-core-profile response, computed by comparing the profile's saved artifact fingerprint against the current artifacts) — there is no background polling, and the banner only appears in view mode when the profile is genuinely stale.

Cost for profile generation is tracked under the label `ai-coaching-profile` in the API cost log and shares the same `$0.50/day` cap as conversation compaction and artifact summaries.

#### Auto-regeneration block surfacing

When automatic profile regeneration fails (daily cost cap hit, no API key, API error), the Core Profile pane shows **why** it failed instead of the generic "may be out of date" message. The block status is ephemeral (resets on app restart) and stored in memory via `ProfileRegenStatus`. The stale banner changes from "Your profile may be out of date" to "Auto-refresh blocked: daily cost cap reached" (or the relevant reason). Clicking "Refresh Profile" clears the block status and attempts a manual regeneration.

Block reasons:

- `cost_cap` — daily profile cost cap ($0.15) exceeded; retriable after UTC midnight
- `no_credential` — no API key available for Haiku calls
- `api_error` — the Haiku API call itself failed

Implementation: `getProfileRegenStatus()` / `clearProfileRegenStatus()` in [artifacts.ts](/src/main/services/ai/ai-coaching/session/artifacts.ts), `generateCoreProfile` return type in [generation.ts](/src/main/services/ai/ai-coaching/generation/generation.ts), store state `profileRegenStatus` in [ai-coaching-store.ts](/src/renderer/src/stores/ai-coaching-store.ts), UI in [CoreProfilePane.tsx](/src/renderer/src/features/ai-coaching/profile/CoreProfilePane.tsx).

### Memory usage instructions

The `SHARED_CONTEXT` block — injected into every coaching session's CLAUDE.md — contains a `<memory_usage>` section that teaches Claude how to use the memory infrastructure described above. It covers:

- **Context Layers** — explains what each layer is (Core Profile, **Personal Context** from conversation memories, Key Moments/bookmarks, **Pinned Context** loaded in full, and **Your artifacts** — the pull menu the coach reads from on demand) so Claude understands the purpose of each block it receives.
- **How to Reference What You Know** — cite naturally when it deepens conversation, use knowledge silently when it shapes questions, never recite context back, reference bookmarked moments with care.
- **Handling Growth and Change** — notice evolution without assuming consistency, treat tensions as exploration opportunities, newer context takes priority over older when they conflict.
- **Avoiding Redundant Questions** — don't re-ask what's in prior context unless there's a strategic reason (time-based check-in, different angle, deeper dive, consistency check).
- **Skipping Covered Ground** — abbreviate sections already covered in prior artifacts with a brief acknowledgment.
- **The Goal** — by interview #5 the user should feel known; by #15, the AI should feel like a thoughtful companion who remembers themes and patterns.

The with-artifacts preamble (the text at the top of `## What You Already Know` when artifacts exist) also references the memory layers explicitly: a Core Profile synthesis, personal context (conversation memories), bookmarked moments, any artifacts pinned for this interview (loaded in full), and a menu of every other artifact the coach can pull on demand. The first-interview preamble (no artifacts) remains simple: "first coaching interview, no prior context, build from scratch."

### Validation instrumentation (telemetry)

The memory system emits telemetry events via `trackEvent('ai_coaching', action, metadata)` so you can verify it's working by querying the `feature_events` table. All events are local SQLite inserts — $0 AI cost.

| Event                      | Fires when                                                                                                                                                                        | Key metadata                                                                                                                                                                                         |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `memory_context_built`     | Every `buildAICoachingClaudeMd()` call, every free-form `writeCoachingBaseContext()` write (immediate path), and every free-form deferred stash (both emit with `pinnedCount: 0`) | `artifactCount`, `coreProfilePresent`, `bookmarkCount`, `memoryCount`, `memoryChars`, `claudeMdChars`, `pinnedCount`, `indexCount`, `spilledCount`, `budgetExceeded`, `budgetOverageChars`, `isRedo` |
| `compaction_generated`     | Compaction summary successfully created                                                                                                                                           | `sessionId`, `segment`, `inputTokens`, `outputTokens`                                                                                                                                                |
| `compaction_failed`        | Compaction Haiku call errors                                                                                                                                                      | `sessionId`, `segment`, `errorClass`                                                                                                                                                                 |
| `compaction_cost_cap_hit`  | Compaction skipped due to daily cost cap                                                                                                                                          | `spentUsd`, `capUsd`                                                                                                                                                                                 |
| `core_profile_generated`   | Core profile successfully created                                                                                                                                                 | `artifactCount`, `profileChars`, `inputTokens`, `outputTokens`                                                                                                                                       |
| `core_profile_skipped`     | Core profile skipped (< 3 artifacts or cost cap)                                                                                                                                  | `artifactCount`, `reason` (`'below_threshold'` or `'cost_cap'`)                                                                                                                                      |
| `memory_extracted`         | Memory facts successfully extracted from a compaction segment                                                                                                                     | `sessionId`, `factCount`, `rawCount`, `validCount`, `inputTokens`, `outputTokens`                                                                                                                    |
| `memory_extraction_failed` | Memory extraction Haiku call errors                                                                                                                                               | `sessionId`, `errorClass`                                                                                                                                                                            |
| `memories_extracted_from_artifact` | Memory facts successfully extracted from a saved artifact                                                                                                                   | `artifactId`, `entryCount`, `inputTokens`, `outputTokens`                                                                                                                                            |
| `artifact_memory_extraction_failed` | Artifact memory extraction Haiku call errors                                                                                                                               | `artifactId`, `errorClass`                                                                                                                                                                           |
| `memory_cost_cap_hit`      | Memory extraction skipped due to session cost cap                                                                                                                                 | `spentUsd`, `capUsd`                                                                                                                                                                                 |
| `memory_feedback_eligible` | Artifact saved and user had prior context (≥ 2 artifacts)                                                                                                                         | `sessionId`, `artifactCount`, `hadCoreProfile`                                                                                                                                                       |

The `snack_started` and `snack_start_failed` events (already firing) are also registered in the action list.

These events are locked by tests in `ai-coaching-claude-md.test.ts`, `ai-coaching-compaction.test.ts`, `ai-coaching-core-profile.test.ts`, and `ai-coaching-memory.test.ts`.

### Life Inventory trend line in profile summary

When a Life Inventory profile summary is generated for injection into coaching context, the system now checks whether there are **prior completed takes** for each inventory type. If at least one type shows a meaningful change (score delta ≥ 2 between the current and previous take), a **trend line** is appended to the profile summary:

> _Trends since last take: Career satisfaction improved; Financial stability worsened_

The trend line lists categories whose scores changed by ≥ 2 points, grouped into "improved" and "worsened" buckets. Categories are deduplicated (each appears at most once even if multiple inventory types cover it). If no category crosses the threshold, or if there are no prior takes to compare against, the trend line is omitted — the profile summary reads exactly as before.

The trend line is **budget-aware**: the profile summary has a hard 800-character cap (`LIFE_INVENTORY_SUMMARY_CAP`). `buildProfileSummary` first tries to fit the summary WITH the trend line; if that exceeds the cap, it drops the trend line and falls back to the base summary alone. If even the base summary exceeds the cap, it hard-truncates with `…`.

Implementation: `buildTrendLine()` and updated `buildProfileSummary()` in [life-inventory-service.ts](/src/main/services/ai/ai-coaching/data/life-inventory-service.ts). Tests: [life-inventory-trends.test.ts](/tests/unit/life-inventory-trends.test.ts).

### Life Inventory import

The Life Inventory pane has an **Import** tab that lets users import a previously exported markdown inventory as a completed take. The flow is two-step: preview then commit.

1. **Preview** (`LIFE_INVENTORY_IMPORT_PREVIEW`) — the user picks a `.md` / `.txt` file. The handler parses markdown table rows via [life-inventory-import-parser.ts](/src/main/services/ai/life-inventory-import-parser.ts) (`parseMarkdownInventory`), matches each row against the catalog by normalized text (`matchParsedRows`), and returns a preview with match/unmatch counts and a sample of the first 10 rows. No DB write.
2. **Commit** (`LIFE_INVENTORY_IMPORT_COMMIT`) — creates an already-completed take and bulk-inserts the matched ratings in a single SQLite transaction. Refuses if an in-progress take exists for the same inventory type. Regenerates the detail file and emits `LIFE_INVENTORY_CHANGED`.

Content is capped at 5 MB by the Zod schema. The parser tolerates bold/italic rating markers (`**4**`, `*4*`) and normalizes smart quotes. Unmatched rows (items not in the catalog) are reported but skipped.

UI: [LifeInventoryImportView.tsx](/src/renderer/src/features/ai-coaching/life-inventory/LifeInventoryImportView.tsx) renders the file picker + preview + commit button. Wired into [LifeInventoryPane.tsx](/src/renderer/src/features/ai-coaching/life-inventory/LifeInventoryPane.tsx) as the third tab (`take` / `results` / `import`). Tests: [life-inventory-import-parser.test.ts](/tests/unit/services/life-inventory-import-parser.test.ts) (25 tests).

## Related

The artifacts these memories and profiles are built from are in [part 2](ai-coaching-part-2.md), and the CLI surface plus the implementation pointers are in [part 4](ai-coaching-part-4.md); the [AI Coaching sidebar (interview prompts and artifacts)](ai-coaching.md) page is the parent.
