---
title: Journal (freeform and guided reflection)
---

# Journal (freeform and guided reflection)

## What it is

**Journal** is a virtual project in Omniscio for personal reflection. It supports two modes:

- **Freeform** — a simple text editor with title, body, auto-save (1.5 s debounce), and tags. A live word count and estimated reading time are shown in the toolbar. Write whatever comes to mind.
- **Guided** — a Socratic chat with Claude (Haiku) that follows a structured template. Five built-in templates exist: General (Coach), Gratitude, Daily Reflection, Problem Solving, and Emotional Processing. The new-entry screen shows a **"Just Write"** option followed by a **template picker grid** displaying all five guided templates with their emoji icon, name, and description. Each template shapes a system prompt that guides the AI mentor's questioning style. Remaining reply count is shown above the input area; when the per-entry or daily limit is reached, the input disables with an explanation.

Entries are stored locally in the SQLite database. Guided mode calls the Anthropic Messages API directly (no spawned session) using the user's configured API key. Entries can be marked as favorites and filtered/sorted in the sidebar.

## Where to find it

### Where it lives

Journal is opt-in and off by default. Turn it on in **Settings → Lab → Built-in apps → Journal** (setting `journalEnabled`); it then appears as a virtual project in the left sidebar (amber NotebookPen icon). Gate enforcement exists at two layers — IPC handlers (`assertJournalEnabled()` wrapping every handler) and CLI routes (`journalPreamble()` returning 403) — and both refuse until the toggle is on, because the unreleased-feature registry holds Journal at `status: 'in-development'` by the owner's decision.

## How it behaves

### How to use it

1. **Open it.** Click **Journal** in the left projects sidebar. The layout splits into a sub-sidebar (entry list) and a content pane, following the same pattern as AI Coaching. If the entry list cannot be loaded, the sub-sidebar says so and offers a **Retry** button instead of showing an empty list.
2. **Create an entry.** Click the **+** button in the sub-sidebar header. A **"Just Write"** option appears at the top, followed by a **template picker grid** showing all five guided templates (Gratitude, Daily Reflection, Problem Solving, Emotional Processing, Coach) with emoji icons and descriptions.
3. **Freeform mode ("Just Write").** Type a title (optional) and body. Changes auto-save after 1.5 seconds of inactivity; pending saves flush on unmount so nothing is lost when switching entries. A live word count and estimated reading time appear in the toolbar. A save made from an out-of-date copy of the entry (another window or an agent changed it first) is refused rather than overwriting the newer text; the sidebar's refresh picks up the current copy and the next edit saves on top of it.
4. **Guided mode (pick a template).** Choose any template from the grid. The AI coach's opening prompt appears, tailored to the selected template; for the Coach template it is a personalized, context-aware greeting — it reviews your recent journal entries, coaching artifacts, active goals, life inventory scores, core profile, and milestones, then presents 2-3 specific conversation starters. If no context is available (new user or API unavailable), it falls back to a generic but helpful opener offering specific conversation types; when the personalized opening could not be fetched, a **Retry personalized opening** link sits under the fallback. Type your response and press Enter (Shift+Enter for newlines). Claude responds with Socratic follow-ups; while it thinks the chat shows **Reflecting...**, which becomes **Taking longer than expected...** after 10 seconds. The full conversation is persisted as `guidedMessages` on the entry. The remaining reply count is shown above the input; when the per-entry (20/hr) or daily (100/day) limit is reached, the input disables with a clear message.
5. **Favorites.** Click the heart icon in the editor toolbar to mark an entry as a favorite. Favorited entries show a heart indicator in the sidebar entry list. Click the heart icon in the sidebar header to filter to favorites only.
6. **Sort & filter.** Click the sort icon (↕) in the sidebar header to choose between Newest first, Oldest first, or Recently updated. Click the calendar icon to reveal date-range inputs that filter entries by creation date.
7. **Tags.** Click the tag icon in the header to add tags (Enter to confirm). Tags are normalized to lowercase and deduplicated. Filter entries by tag in the sub-sidebar. When more tags exist than can be shown, a +N pill appears with a tooltip listing the hidden tags.
8. **Search.** Click the search icon in the sub-sidebar header. Searches title and content with a 250 ms debounce. Minimum query length is 2 characters.
9. **Writing stats.** Click the bar-chart icon in the sidebar header to view a stats dashboard: total entries, total words, current writing streak, entries this week, longest streak, and average words per entry.
10. **Delete.** Click the trash icon, then confirm. Entries are soft-deleted (`is_deleted = 1`). An undo toast appears after deletion — clicking **Undo** restores the entry immediately, tags included (a deleted entry's tags are hidden, not removed, until the trash purge deletes the entry). If the restore itself fails, a toast says so and the entry stays deleted.

### Cost and privacy

- **Freeform mode** uses no API calls. All data stays local.
- **Guided mode** sends the conversation history to the Anthropic Messages API on each turn. Uses Haiku (cheapest model). Max 512 tokens per response. Costs are paid from the user's own API key balance. Rate-limited to 20 messages/hour per entry and 100 messages/day total to cap spend.
- All journal data is stored in the local SQLite database. Nothing is synced externally.

## For agents

### Under the hood

### Database

Two tables created by migration (in the project's SQLite database):

- **`journal_entries`** — `id` (UUID), `mode` (freeform|guided), `title`, `content`, `template_id`, `guided_messages` (JSON text), `is_favorite` (INTEGER NOT NULL DEFAULT 0), `word_count` (INTEGER nullable, computed on create/update), `created_at`, `updated_at`, `is_deleted` (soft delete), `version` (INTEGER NOT NULL DEFAULT 1, bumped by every write; an update must name the version it read and is refused with a `StaleWriteError` conflict when the row has moved on).
- **`journal_entry_tags`** — `entry_id`, `tag`, `created_at`. Junction table with composite primary key.

Indexes:

- `idx_journal_entry_tags_entry_id` — on `journal_entry_tags(entry_id)` for fast tag lookups and cascade deletes.
- `idx_journal_entries_soft_delete_created` — on `journal_entries(is_deleted, created_at DESC)` for the default list query.
- `idx_journal_entries_favorites` — partial index on `journal_entries(is_favorite)` WHERE `is_deleted = 0 AND is_favorite = 1` for fast favorites filter.

The `setTags` operation (DELETE + INSERT) runs inside a SQLite transaction to prevent partial tag loss on crash.

### File inventory

| Layer           | File                                                       | Purpose                                                                                                                           |
| --------------- | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Shared types    | `src/shared/journal-types.ts`                              | `GuidedMessage`, `JournalTemplate`, `JournalEntry`, `JournalStats`, `JournalRateLimitStatus`, `JournalSortOrder`                  |
| Shared utility  | `src/shared/word-count.ts`                                 | `countWords()` — shared between renderer (live display) and main (persistence)                                                    |
| Shared channels | `src/shared/ipc-channels/journal.ts`                       | 15 IPC channel constants                                                                                                          |
| Shared schemas  | `src/shared/ipc-schemas/journal.ts`                        | Zod validation schemas for all IPC inputs                                                                                         |
| Response map    | `src/shared/ipc-response-map/journal.ts`                   | `JournalResponses` type map                                                                                                       |
| Templates       | `src/main/services/journal/journal-guided.ts`              | 5 built-in templates (general, gratitude, daily-reflection, problem-solving, emotional-processing), system prompt builder         |
| DB layer        | `src/main/services/journal/journal-db.ts`                  | Parameterized SQLite queries (CRUD, search, tags, favorites, stats)                                                               |
| Rate limits     | `src/main/services/journal/journal-rate-limits.ts`         | Per-entry (20/hr) and daily (100/day) guided message limits                                                                       |
| Coach opener    | `src/main/services/journal/journal-coach-opener.ts`        | Context-aware personalized opening for the Coach template (gathers 6 data sources, calls Haiku, graceful fallback)                |
| Service         | `src/main/services/journal/journal-service.ts`             | Orchestration, feature gate, push events, Claude API                                                                              |
| IPC handlers    | `src/main/ipc/journal-handlers.ts`                         | 15 handlers (auto-discovered, no manual wiring)                                                                                   |
| CLI routes      | `src/main/services/cli/cli-server-journal-routes.ts`       | REST routes for the control server                                                                                                |
| Store           | `src/renderer/src/stores/journal-store.ts`                 | Zustand store; reads via `createFetchAction`, mutations apply locally, filter/sort prefs persisted via Zustand persist middleware |
| Sub-sidebar     | `src/renderer/src/features/journal/JournalSubSidebar.tsx`  | Entry list, search, tag filter, sort, date-range filter, favorites filter                                                         |
| Panel           | `src/renderer/src/features/journal/JournalPanel.tsx`       | Panel router (new entry, editor, chat, stats, empty)                                                                              |
| Editor          | `src/renderer/src/features/journal/JournalEntryEditor.tsx` | Freeform editor with auto-save, word count, favorite toggle                                                                       |
| Guided chat     | `src/renderer/src/features/journal/JournalGuidedChat.tsx`  | Chat widget for guided reflections with rate-limit display                                                                        |
| Stats view      | `src/renderer/src/features/journal/JournalStatsView.tsx`   | Writing stats dashboard (entries, words, streaks)                                                                                 |
| Tag bar         | `src/renderer/src/features/journal/components/TagBar.tsx`  | Shared tag management bar (add/remove tags)                                                                                       |
| UI registry     | `src/renderer/src/integrations/ui-registry.ts`             | `NotebookPen` icon, `text-amber-500` color                                                                                        |
| Manifest        | `src/shared/integrations/journal.ts`                       | Integration manifest                                                                                                              |

### IPC channels

`journal:list-entries`, `journal:get-entry`, `journal:create-entry`, `journal:update-entry`, `journal:delete-entry`, `journal:search-entries`, `journal:list-tags`, `journal:set-tags`, `journal:list-templates`, `journal:send-guided-message`, `journal:get-coach-opening`, `journal:restore-entry`, `journal:toggle-favorite`, `journal:get-stats`, `journal:get-rate-limit-status`.

Push channel: `journal:entries-changed` (emitted on every mutation).

### Guided mode internals

- Model: `claude-haiku-4-20250414`, max tokens 512, 30 s timeout via AbortController.
- API key resolved via `resolveApiKeyForAiFeatures()`.
- API calls routed through `getOrCreateAnthropicClient` + `callAnthropicText` so every token lands in `api_cost_log` (spend-tracking chokepoint).
- System prompt built by `buildGuidedSystemPrompt(template)` — includes template-specific guidance and Socratic questioning instructions.
- Full conversation history (`GuidedMessage[]`) is persisted as JSON in `guided_messages`. Only the most recent 20 messages are sent to the API (history cap) while the complete history is retained in the database.
- Rate limits enforced in the service layer (shared by IPC and CLI entry points): 20 messages/hour per entry, 100 messages/day total.
- API error messages are sanitized before surfacing — API key patterns are redacted and messages are truncated to 500 characters.
- **Coach opener**: when a fresh General (Coach) template entry is created, the renderer calls `journal:get-coach-opening`. The handler gathers context from 6 sources (recent journal entries, core profile, coaching artifacts, goals/check-ins, life inventory scores, milestones), calls Haiku with a system prompt instructing it to produce 2-3 warm conversation starters, and returns the personalized opening. Each data source is independently try/caught so partial data still works. Falls back to an improved generic opener on any failure (no API key, empty context, API error). Tagged `journal-coach-opening` in `api_cost_log`. Only the General template uses this — the other four keep their static `openingPrompt`.

### CLI routes

All routes are prefixed with `/journal`. Feature gate returns 403 when disabled. Reads of journal content (entries, search, tags, export, stats and the coach opener) need the full-access CLI token; an agent session's scoped token gets 401. Templates and the reply budget are readable with either.

| Method | Path                                        | Description                                                                                         |
| ------ | ------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| GET    | `/journal/entries`                          | List entries (paginated, optional `?tag=`)                                                          |
| GET    | `/journal/entries/search`                   | Search by content/title (`?q=`)                                                                     |
| GET    | `/journal/entries/:entryId`                 | Get single entry                                                                                    |
| POST   | `/journal/entries`                          | Create entry                                                                                        |
| PATCH  | `/journal/entries/:entryId`                 | Update entry (body needs `expectedVersion`, the entry's current `version`; a stale one returns 409) |
| DELETE | `/journal/entries/:entryId`                 | Soft-delete entry                                                                                   |
| POST   | `/journal/entries/:entryId/restore`         | Undo a soft delete                                                                                  |
| POST   | `/journal/entries/:entryId/toggle-favorite` | Toggle the favourite flag                                                                           |
| GET    | `/journal/tags`                             | List all tags                                                                                       |
| PUT    | `/journal/entries/:entryId/tags`            | Set tags for entry                                                                                  |
| GET    | `/journal/templates`                        | List guided templates                                                                               |
| POST   | `/journal/guided/:entryId/message`          | Send guided message                                                                                 |
| GET    | `/journal/export`                           | Export entries as JSON, or markdown with `?format=markdown` (optional `?tag=`)                      |
| GET    | `/journal/stats`                            | Journal statistics                                                                                  |
| GET    | `/journal/rate-limit-status`                | Guided replies left today and for an entry (optional `?entryId=`)                                   |
| GET    | `/journal/coach-opening`                    | AI-written coaching opener built from recent entries (uses your AI key)                             |

## Related

- [AI Coaching](ai-coaching.md) — structured interview sessions with artifact capture
- [Daily Journal Check-In](daily-journal.md) — AI Coaching check-in nudges (separate feature)
