• bvandeusen released this 2026-05-13 19:28:34 -04:00 | 237 commits to main since this release

    Forty-five commits across four feature groups: the journal closeout work from earlier in the day, the LLM tool-use fixes from the 2026-05-08 prod conversation investigation, and two new fully-specced projects (task-as-durable-record and note version pinning).

    Project A — Task as durable record

    The task body becomes the canonical record of what happened, not just what was initially asked.

    • Schema: new description field on Note (user-stated goal). New consolidated_at timestamp. Migration 0044 copies existing body content into description for existing tasks.
    • Body semantics shift for tasks: body is now LLM-maintained, written by a background consolidation pipeline from the task's accumulated work logs. Description is user-owned and never edited by the pipeline. Embeddings continue to index title + body, so semantic search now hits the work-done summary rather than the initial goal text — tasks become discoverable by what was actually done.
    • Consolidation pipeline: debounced after 3 new log_work entries, OR fired immediately on task status transition to done/cancelled. Uses background_model so the chat model's KV cache stays warm. Per-task asyncio lock prevents concurrent runs. Failures leave body untouched; next trigger retries.
    • Tool layer enforcement: create_note tool drops the body argument when status is set (i.e., creating a task); update_note tool rejects body writes on tasks with an error that nudges toward log_work. HTTP routes still accept body so the frontend editor works pre-consolidation.
    • Manual control: POST /api/tasks/:id/consolidate endpoint + auto_consolidate_tasks user setting (General tab toggle). Manual endpoint bypasses the toggle.
    • Frontend: "Goal" textarea added above the body in TaskEditorView; TaskViewerView renders the Goal block subordinate to the body. TipTap body editor is gated to read-only once consolidated_at is set; a "Re-consolidate" button calls the manual endpoint.

    Project B — Note version pinning

    The existing rolling 50-version snapshot system grows two retention tiers above the baseline.

    • Schema: pin_kind (NULL = rolling, 'auto', 'manual') and pin_label columns on note_versions. Migration 0045 leaves all existing rows as rolling.
    • Three FIFO buckets:
      • Rolling autosaves: cap 50 per note (unchanged from before, but the prune query now filters pin_kind IS NULL so pinned rows are not counted).
      • Auto-pinned: cap 25 per note. A daily 03:00 UTC APScheduler job walks each note's version chain and pins versions that are followed by another version ≥2 days later (or the latest version if it's ≥2 days old with no successor). Auto-label is generated from the stability window.
      • Manually declared: unlimited. Never pruned.
    • API: POST /api/notes/:id/versions/:vid/pin accepts an optional label; DELETE /api/notes/:id/versions/:vid/pin clears pin_kind/pin_label (downgrades back to rolling — does NOT delete the row).
    • Backup export/import roundtrip: both new fields persist through /api/export and restore. .get() on import preserves backward compatibility with older backup files.
    • Frontend: HistoryPanel.vue extended with kind-aware badges (filled circle = manual pin, half-filled = auto-pin), label rendering, and a pin/unpin/edit-label control row above the diff view.

    LLM tool-use fixes (2026-05-08 investigation)

    Five fixes shipped together because they share a root failure mode in colloquial project/task references.

    • search_projects_tool now returns success: True so the dispatcher doesn't mislabel valid results as errors.
    • list_notes strips filler type-nouns (task, tasks, note, notes, project, projects) from q before the ILIKE AND-filter; search_notes tool description nudges the model to use type/project parameters instead of stuffing those words into query.
    • New shared score_project_match helper unifies ranking between search_projects_tool and resolve_project. Tiered scoring (1.0 exact / 0.85 substring either way / 0.70 query-in-description / SequenceMatcher fallback against the title). score_project_match also strips project / projects filler from queries so "famous supply project" substring-matches "Famous-Supply Work topics" at 0.85 instead of 0.228.
    • record_moment tool description tightens task_titles / note_titles to require values from a prior search_notes call in the same turn.
    • Journal calibration prompt + chat tool_lines static block both gain a "search-first when user references existing work" clause.

    Journal closeout

    Nightly per-user closeout extracts profile observations from the day's journal.

    • services/journal_closeout.py runs once per user per day at the user's day_rollover_hour. Reads yesterday's /journal conversation, filters out daily_prep-metadata messages at the SQL layer (hard data-exclusion guard against the briefing-era feedback-loop pathology), and asks the background model for 2–5 bullet observations. Appends to observations_raw.
    • closeout_enabled config key (default ON) wired to Settings → Profile.
    • GET /api/profile/observations + collapsible "Recent observations (14)" panel for transparency.
    • Catch-up on startup if today's day_rollover_hour already passed and yesterday's entry is missing.

    Deploy notes

    • Two schema migrations (0044, 0045) — both additive and safe.
    • No frontend build required beyond standard CI.
    • The auto-pin scan first fires at 03:00 UTC after deploy. Existing notes won't see auto-pinned versions until then (or earlier if you manually pin via the new UI).
    • The consolidation pipeline only fires for tasks that get new log_work entries OR status transitions after deploy. Existing tasks retain their current body until they accumulate enough new logs to trigger consolidation.

    Verification

    • All backend pytest tests pass (CI green on PR #50)
    • Migrations apply cleanly on the dev environment
    • End-to-end on the dev environment: task description roundtrip, consolidation trigger after 3 work logs, body editor gating, version pinning + auto-pin scan, journal closeout settings UI

    Known follow-ups

    • Project D (fable-dev #167) — journal prompt simplification + WRITE_FALSE_CLAIM reconciler + tool description tightening + model-swap safety check. Recommended next; high priority. M effort.
    • Project E (fable-dev #168) — pipeline decoupling to enable journal model swap. Deferred.
    Downloads