Commit Graph

774 Commits

Author SHA1 Message Date
bvandeusen 02138f5728 fix(ci): use venv for ruff install — pipx not on runner image
The py3.12-node22 runner doesn't ship pipx, so the previous commit's
pipx install ruff failed with command not found. Switched to the same
venv pattern the test job uses.
2026-04-11 16:31:36 -04:00
bvandeusen 61f95fb9ed style(knowledge): align task card theming with note/list pattern
Task card was the outlier in the Knowledge view: half-width fade-to-
transparent gradient bar while note/list use full-width two-stop
gradients, and the type badge was purple while the border+bar were gold.

- k-card--task::before: full width with gold→amber gradient (#d4a017
  → #fbbf24) matching the two-stop pattern
- badge--task: switched from purple to amber so the badge matches
  the card chrome
2026-04-11 16:30:36 -04:00
bvandeusen cc74901761 ci: add concurrency, registry cache, pipx ruff, least-privilege perms
- concurrency: cancel in-progress runs on new pushes (non-tag only);
  tag runs are never cancelled so releases can't kill themselves
- permissions: contents: read default, packages: write scoped to build
- docker build: explicit cache-from/cache-to against :cache tag so
  BuildKit warmth survives local runner cleanup
- lint: pipx install ruff instead of --break-system-packages
- actions/cache@v3 → @v4
- triggers main branch pushes for gate runs (build stays dev/tag only)
- comments added on the non-obvious bits (http-ece isolation, docker
  prune two-step, registry cache mode=max)
2026-04-11 16:11:27 -04:00
bvandeusen 9f6608fc8f fix(briefing): stale weather auto-refresh + task filter + overdue prompt
Three briefing quality fixes surfaced by reading today's 2026-04-11
compilation output:

- **Stale weather**: get_weather was returning 48h-old cache data
  after a missed scheduler run. Tool now auto-refreshes any cached
  location older than 6h (fetching fresh data from Open-Meteo), and
  stamps each location with cache_age_hours + is_stale so the model
  can hedge instead of faithfully relaying old numbers.

- **Cancelled tasks leaking into prose**: briefing loop now defaults
  list_tasks calls to status=["todo","in_progress"] when the model
  doesn't specify, so cancelled/done items stop showing up in the
  summary. Localized to the briefing path — chat still sees full
  history.

- **Overdue in-progress tasks missed by midday check-in**: tightened
  the check-in prompt to explicitly require two list_tasks calls —
  one for in_progress (catches items dragging past their due date)
  and one filtered by due date — so long-running tasks stop getting
  silently dropped.
2026-04-11 13:32:52 -04:00
bvandeusen e63b4634ea feat(briefing): surface news items in compilation via get_rss_items
The compilation prompt mentioned "news themes" but didn't name the
tool, and the model was never calling get_rss_items. Result: today's
briefing had zero news coverage despite the tool being wired up and
in the allowlist.

- Explicitly list the tools to call in the compilation prompt so
  get_rss_items gets invoked alongside list_tasks/list_events/get_weather.
- When the model calls get_rss_items during a compilation run,
  intercept and return the already-scored/filtered items (topic prefs
  + reaction-weighted) instead of the raw feed dump execute_tool
  would return. Aligns the model's view of news with the sidebar's
  rss_item_ids metadata.
2026-04-11 13:30:26 -04:00
bvandeusen 593d737e26 feat(briefing): UI polish for agentic briefing messages
Three visual improvements for the briefing conversation:

1. Intermediate tool-call messages (empty content, briefing_intermediate:
   true) now render as a compact dashed status row with per-tool pills
   showing result counts, instead of an empty assistant bubble followed
   by a stack of ToolCallCards. Click to expand the full cards.

2. Slot badge on non-intermediate briefing messages — "Full Briefing"
   for compilation, "Morning/Midday/Afternoon Update" for slot
   injections. Slot updates get a softer bubble treatment (transparent
   background, muted border, smaller text) so the compilation stays
   visually dominant.

3. Slot separator now triggers on briefing_slot metadata (not the
   compilation-only rss_item_ids), and uses a look-behind so it only
   fires when there's a prior slot message — no separator above the
   first briefing of the day.

New component: BriefingToolStatusRow.vue handles the intermediate
pill row and delegates to ToolCallCard when expanded.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 22:10:31 -04:00
bvandeusen 06dbc0e27a test(briefing): drop tests for removed legacy helpers
format_task, compute_task_hash, and split_changed_tasks were deleted
in 9eba6ac when the legacy one-shot briefing path was ripped out.
Their tests went with them.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 20:48:37 -04:00
bvandeusen 7e0938fe7d feat(fable-mcp): add reset_today_briefing tool
Wipes today's briefing messages (keeps the conversation row) and
optionally re-fires the compilation slot to regenerate. Pairs with
the new POST /api/briefing/reset-today backend route.

Also drops the stale briefing_mode reference from trigger_briefing's
docstring now that legacy mode is gone.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 20:42:48 -04:00
bvandeusen 9eba6ac107 refactor(briefing)!: remove legacy one-shot synthesis, agentic-only
Deletes ~760 lines of legacy briefing code: format_task, compute_task_hash,
upsert_task_snapshots, _gather_internal, _gather_weekly_review,
_llm_synthesise, and the unified prompt helpers. run_compilation and
run_slot_injection are now agentic-tool-use-loop only.

briefing_scheduler and user_profile migrated from the deleted helper to
services.llm.generate_completion (retry + keep_alive baked in).

routes/briefing.manual_trigger now persists agentic tool-call receipts
via _persist_agentic_messages (previously silently dropped them) and
adds POST /api/briefing/reset-today to wipe today's briefing messages.

BREAKING: briefing_mode setting no longer honored; no legacy fallback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 20:42:42 -04:00
bvandeusen 66f906a629 fix(briefing): exclude paused-project tasks from briefing tooling
list_notes gains exclude_paused_projects; list_tasks tool sets it when
no explicit project filter is given. Paused projects no longer leak
tasks into briefings or list_tasks results.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 20:42:33 -04:00
Bryan Van Deusen 5f4759a5e8 feat(chat): ground factual claims in tool results, be honest when empty
Applies the grounding discipline from the agentic briefing work to the
main chat system prompt. The regular chat pipeline was already agentic
(it uses stream_chat_with_tools), but its system prompt never told the
model "only assert facts from tool results" or "if a tool returns
nothing, say so honestly." That left room for the same class of
hallucinations the briefings had — calling list_events, getting an
empty array, and then confidently mentioning a meeting anyway.

Adds two new static rules to the tool guidance block in llm.build_context:

GROUNDING — when the user asks about their own data, call the relevant
tool to see what exists. Never assert from memory or assumption.

HONESTY WHEN EMPTY — if a tool returns empty results, tell the user
plainly. No fabricated example items, no invented meetings, no generic
suggestions dressed up as real data.

Both rules are in the static (KV-cache-stable) portion of the system
prompt so they cost nothing on repeated requests for the same user.

Carries the hallucination fix from the briefing work directly into
every chat turn, not just chat that happens inside a briefing thread.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:54:01 -04:00
Bryan Van Deusen 25c767ddf2 fix(test): update run_slot_injection mock for new tuple return
PR 1.5 (commit 4168167) changed run_slot_injection's signature from
returning str to returning tuple[str, dict] so the scheduler can get
at the agentic message list for receipt persistence. The
test_run_slot_morning_runs_on_work_day mock was still returning the
old plain-string value, which unpacked to a ValueError at runtime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:22:06 -04:00
Bryan Van Deusen 7e06e58ab6 chore(fable-mcp): bump version to 0.2.6 for briefing introspection tools
Manual bump because the pre-commit hook that normally handles this
didn't run — its scripts had lost their executable bits earlier in
the session. Permissions have been restored in this commit's tree,
so subsequent commits touching fable-mcp will auto-bump again.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:07:54 -04:00
Bryan Van Deusen 2fd914916f feat(fable-mcp): briefing introspection and manual trigger tools
Adds the MCP tools needed to debug the agentic briefing pipeline
without waiting for scheduled slots to fire:

- fable_list_briefings — list briefing conversations newest first
- fable_get_today_briefing — fetch today's briefing with all messages
- fable_get_briefing_messages(conv_id) — message list for a specific
  briefing conversation, including tool_calls with embedded results
- fable_trigger_briefing(slot) — manually run a slot via
  POST /api/briefing/trigger (fires RSS/weather refresh, same as
  the scheduler)
- fable_get_conversation(conv_id) — generic conversation read for
  chat or briefing threads, full messages + tool_calls + metadata

All of these hit existing REST endpoints (/api/briefing/conversations,
/api/briefing/conversations/<id>/messages, /api/briefing/trigger,
/api/chat/conversations/<id>) so no API surface changed — the gap
was purely on the MCP side. Bearer token auth works across both
blueprints because login_required already accepts API keys.

Enables a full "trigger → inspect → tune prompt → repeat" loop from
Claude without touching the UI, which is necessary for iterating on
agentic briefing prompts when the scheduled slot is hours away.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:06:55 -04:00
Bryan Van Deusen 4168167f24 feat(briefing): agentic slot injections + persist tool-call receipts
PR 1.5 + PR 2 of the agentic briefing rollout. Extends the agentic
path to cover the morning/midday/afternoon check-in slots in addition
to the 4am compilation, and persists the full tool-call sequence
into the briefing conversation so chat follow-ups see the receipts.

run_slot_injection now honors the briefing_mode setting the same way
run_compilation does: agentic mode routes through run_agentic_briefing
with a check-in system prompt variant, legacy falls back automatically
if the new path returns empty. Signature changed from str to
(str, dict) to surface the agentic message list to the caller.

_agentic_system_prompt now takes the user's local-day window and
timezone, pre-computed by run_agentic_briefing, and embeds them in
the prompt. This eliminates a whole class of "wrong day" bugs that
would otherwise happen when the model tried to translate "today" into
ISO 8601 ranges without knowing the user's timezone.

briefing_scheduler._run_slot_for_user now calls _persist_agentic_messages
after each slot, which walks the agentic message list and stores
every intermediate assistant turn (with its tool calls and results
folded into the flat storage format the existing chat loader expects)
as a real message row. The synthetic "[Midday briefing update]"
user-role messages are no longer created — final prose is written
with metadata.briefing_slot so the UI can still identify it as a
scheduled entry. The agentic user-trigger ("Generate my morning
briefing…") is deliberately skipped so it doesn't recreate the same
fake-user-message problem we're trying to remove.

briefing_conversations.post_message now accepts tool_calls, matching
the schema the Message model already supports. This lets scheduled
briefings write structured tool-use history without reaching into
the model layer.

Net effect with briefing_mode="agentic" on:
- All four slots are grounded in tool results, no more hallucinated
  events or tasks
- Chat follow-ups in the briefing conversation see morning's
  list_events → [] receipt (and everything else), so "what meeting?"
  gets an honest "nothing on the calendar" reply grounded in data
- No more fake [Briefing update] user messages in the chat scroll

Still to come (PR 3): UI polish — tool-call status row, inline cards
for list_events/list_tasks results, visual treatment for slot messages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:01:23 -04:00
Bryan Van Deusen 22a13636e8 fix(events): null end_dt no longer matches events outside the window
services/events.py::list_events treated any event with end_dt IS NULL
as perpetually matching, so a point-event created last week would be
returned as "happening today" whenever the briefing or list_events
tool queried today's window. That manifested as briefings claiming
past events were scheduled for the current day.

Split the null-end branch: point events now only match when start_dt
falls within [date_from, date_to]. Events with an end_dt continue to
use standard overlap logic.

This bug affected both the legacy briefing synthesis and the agentic
list_events tool path, since both call the same events service.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:01:01 -04:00
Bryan Van Deusen aebb6baa2c feat(briefing): agentic compilation path behind feature flag (PR 1/N)
First cut of the agentic briefing redesign. Morning compilation can now
route through a tool-call loop that grounds every factual claim in an
actual tool result, eliminating the hallucinated meetings, tasks, and
news items the legacy one-shot path was producing. Behind a per-user
`briefing_mode` setting (default "legacy"); falls back to the legacy
path automatically if the new path returns empty (e.g. model too weak
to drive tool calls reliably).

New: services/briefing_tools.py — explicit read-only allowlist of 10
tools (tasks, events, weather, rss, projects, notes). New tools added
to tools.py must be opted in by name. Excludes all mutating tools and
external search tools (search_images, search_web, research_topic) which
are neither useful nor safe for a scheduled background job.

New: briefing_pipeline.run_agentic_briefing — wraps the existing
stream_chat_with_tools loop with slot-specific system prompts that tell
the model to only assert facts from tool results and to be honest when
tools return nothing. Max 8 rounds, per-round exception handling,
returns the full message list so tool-call receipts can be persisted
alongside the prose in a later PR.

Sentence-count floors bumped: compilation 6–10 (was 4–8), check-ins
3–5 (was 2–3). Weekly review 5–8.

Design doc: docs/2026-04-10-agentic-briefing-design.md

Out of scope for this PR (future PRs): slot-injection migration,
persisting tool-call receipts into the conversation so chat follow-ups
see them, UI polish for tool-call status, sidecar storage for
briefings. See the design doc's migration path for details.

Enable on an account with:
  UPDATE settings SET value='agentic'
  WHERE user_id=<id> AND key='briefing_mode';
or insert the row if missing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 14:13:55 -04:00
Bryan Van Deusen 3f3156db07 feat(ollama): configurable per-model keep_alive durations
Replace the hardcoded "2h" keep_alive everywhere with a helper that
returns OLLAMA_KEEP_ALIVE_MAIN (default 30m) for the interactive model
and OLLAMA_KEEP_ALIVE_BACKGROUND (default 10m) for the background
model. Lets the main model release VRAM during long idle periods
while keeping it warm enough for bursty chat use, and stops the
sporadic background model from camping VRAM it rarely needs.

Seven call sites updated to route through llm.keep_alive_for(model):
the streaming helpers, generate_completion, the two startup warmers,
the settings KV-cache primer, and the chat warmer endpoint.

Override via env vars: OLLAMA_KEEP_ALIVE_MAIN, OLLAMA_KEEP_ALIVE_BACKGROUND.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 14:13:32 -04:00
Bryan Van Deusen 102c0b74a0 feat(settings): tabbed MCP install instructions with Claude Code flow
Replace the hand-edit-JSON instructions in Settings → API Keys with a
tabbed UI (Claude Code / Claude Desktop / Other). The Claude Code tab
leads with the `claude mcp add` command, pre-filled with FABLE_URL and
the most recently generated API key, plus copy-to-clipboard buttons on
every snippet. Recommend `uv tool install` or `pipx install` over bare
`pip install` so fable-mcp reliably lands on PATH under PEP 668.

Also fix incorrect priority values in fable-mcp tool docstrings — the
enum is `none|low|medium|high`, not `low|normal|high`.

DRY pass: extract shared `copyToClipboard()` helper used by both
`copyApiKey` and the new snippet buttons; reuse existing
`btn btn-secondary btn-sm` for the copy buttons instead of a bespoke
class.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 12:35:50 -04:00
bvandeusen 8fb81bc1ed Merge pull request 'Release v26.04.10.1' (#28) from dev into main v26.04.10.1 2026-04-10 15:05:15 +00:00
bvandeusen c1bc73da8e fix(briefing): remove redundant current-conditions block; patch live temp into WeatherCard
The standalone current-conditions div showed just a bare temperature with no
forecast context, making the weather panel look incomplete. Now the live
temperature from /weather/current is patched directly into the WeatherCard's
current_temp so it stays fresh without a second display block.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 08:43:30 -04:00
bvandeusen 3b8d40fea3 feat: overall completion bar on project cards; auto-collapse done milestones
ProjectListView: add an overall task completion bar above the per-milestone
bars showing the percentage of all project tasks that are done.

ProjectView: milestones where every task is complete now start collapsed
by default, keeping the view clean for projects with many finished stages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 08:38:02 -04:00
bvandeusen 2f5abc034e style: update email templates to Modern Fable visual identity
Replace indigo (#6366f1) with deep violet (#7c3aed) gradient header,
violet-tinted card border and background, refined border radius and
spacing, and violet-accented footer to match the app's design language.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 08:31:16 -04:00
bvandeusen 7a01733334 fix: coerce null project fields to empty string to avoid NOT NULL violation 2026-04-09 21:34:14 -04:00
bvandeusen 7d5611d00b fix: add 'paused' tab to project list filter 2026-04-09 19:33:50 -04:00
bvandeusen e5821ef3f8 fix: allow 'paused' in project status validation 2026-04-09 18:17:48 -04:00
bvandeusen 43231f44d2 fix(search): hybrid keyword + semantic search — exact title/body matches rank first 2026-04-09 12:28:55 -04:00
bvandeusen ab0b9c3199 fix(lint): add timezone import, use timedelta directly, remove unused variable 2026-04-09 08:46:34 -04:00
bvandeusen d12fc5d282 feat: paused project status with auto-pause (14 days), auto-reactivate on activity; fix back button context 2026-04-09 08:37:08 -04:00
bvandeusen 4c3d67994b fix: back button returns to project when note/task has a project, otherwise to Knowledge 2026-04-09 08:18:46 -04:00
bvandeusen 126bad7d99 fix(header): darken nav text in light mode — use text-secondary for inactive, primary-solid for active 2026-04-08 23:43:25 -04:00
bvandeusen 28fae75258 Merge pull request 'Release v26.04.09.1 — Briefing intelligence overhaul' (#27) from dev into main v26.04.09.1 2026-04-09 03:30:17 +00:00
bvandeusen 058bd76719 feat(briefing): multi-article topic discussion endpoint for deep thematic analysis 2026-04-08 22:53:23 -04:00
bvandeusen d56b57ee40 feat(briefing): cluster-level preference filtering — rank themes by user interest, suppress disliked topics 2026-04-08 22:51:13 -04:00
bvandeusen 8762552234 feat(briefing): project continuity — yesterday's activity and stale project warnings 2026-04-08 22:19:38 -04:00
bvandeusen 6cbf9be052 feat(briefing): weekly review — 7-day recap with task/note/project summary and upcoming preview 2026-04-08 22:18:19 -04:00
bvandeusen 3687fbeeb9 feat(briefing): differentiated check-in slots — midday progress nudge, afternoon wrap-up 2026-04-08 22:09:18 -04:00
bvandeusen a6543c1dc5 feat(briefing): cluster news by topic for thematic synthesis instead of flat headlines 2026-04-08 22:07:24 -04:00
bvandeusen 4558dd578a feat(briefing): weather × calendar cross-reference — rain warnings for outdoor events 2026-04-08 22:03:18 -04:00
bvandeusen 8647f52fbc feat(weather): live current conditions endpoint + polling display in briefing view 2026-04-08 21:33:27 -04:00
bvandeusen 304affb837 feat(briefing): daily planning — prioritized focus list and calendar gap analysis 2026-04-08 20:59:06 -04:00
bvandeusen 2e3f90384d feat(briefing): actionable intelligence — days overdue, project context, suggest next steps 2026-04-08 20:33:16 -04:00
bvandeusen 16b9ed9392 Merge pull request 'Release v26.04.08.3 — Type editors, calendar UX, DRY theme, contrast' (#26) from dev into main v26.04.08.3 2026-04-08 18:52:25 +00:00
bvandeusen f9c6802939 feat(calendar): linked start/end times, 1h default duration, smart rounding, past event hint 2026-04-08 13:47:15 -04:00
bvandeusen f2debd8a5b feat(knowledge): show organization for person cards, category for place cards
Exposes birthday, organization, address (person) and website, category (place)
from entity_meta in the knowledge API response; updates KnowledgeView cards
to display organization and category as visible meta chips/text.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 13:32:15 -04:00
bvandeusen f8f14eea0f feat(editor): place form-first layout and list builder with Enter-to-add
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 13:30:26 -04:00
bvandeusen 64c19932a7 feat(editor): person form-first layout with structured fields and collapsible notes
When note type is 'person', replace the main TipTap editor with a contact card form
(Relationship, Birthday, Email, Phone, Organization, Address). The TipTap editor moves
to a collapsible 'Notes' section below the form, auto-expanded when existing body content
is present. Person fields are removed from the sidebar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 13:27:22 -04:00
bvandeusen 317d8f25d0 feat(editor): skip toolbar in tab order; auto-focus title; type-dependent placeholders
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 13:25:07 -04:00
bvandeusen 87f74b1cd5 docs: add specialized note type editors implementation plan 2026-04-08 13:21:20 -04:00
bvandeusen f7fda0adca docs: add specialized note type editors design spec 2026-04-08 13:08:01 -04:00