A prod event surfaced today with `start_dt=2026-05-01T12:00Z` and `end_dt=2026-03-30T12:00Z` — end was 32 days BEFORE start, almost certainly from an earlier tool-call mishap (Fable #161). The list_events filter trusted the bogus end_dt and excluded the event from every read path that hit the upcoming window, even though start_dt was correctly in range. The event stayed visible in the calendar grid (different range) but vanished from "Upcoming", search, briefings, and journal prep events list. This is the hotfix half of the response. The structural follow-up is Fable #160 — replace end_dt with a duration column so invalid state becomes inexpressible. ## A. Filter robustness in list_events Treat `end_dt <= start_dt` as if no end_dt exists. The filter now splits into two branches: - valid duration: end_dt IS NOT NULL AND end_dt > start_dt AND end_dt >= date_from - no/invalid duration: (end_dt IS NULL OR end_dt <= start_dt) AND start_dt >= date_from Same change applied to the recurring-event expansion's `duration` calculation, which was producing negative timedeltas for corrupted rows and computing nonsensical occurrence end times. ## B. Write-side validation in create/update `create_event` and `update_event` now raise ValueError when the resulting state would have end_dt <= start_dt. Update validates against the *post-update* state, not just the field being changed — so pushing start_dt past an existing end_dt also fails loudly. Bad data shouldn't be persistable from any write path. Surfaced cleanly: - Calendar tool wrappers (create_event_tool / update_event_tool) catch ValueError and return `{success: false, error: ...}`, which the model can read and self-correct. - Route handlers (POST /api/events, PATCH /api/events/<id>) catch and return HTTP 400 with the validator's message instead of letting it bubble to a 500. 4 new tests in test_events_service.py: - create rejects end before start - create rejects equal start/end (zero duration) - update validates the post-update state (start pushed past existing end) - list_events surfaces an event whose end_dt is before its start_dt 34 event-related tests pass; ruff clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fabled Scribe
A self-hosted second brain and project management application with integrated LLM capabilities. Write, organise, and act on your notes and tasks with the help of a local AI assistant — all running on your own hardware.
Features
Notes and tasks with a Markdown editor, sub-tasks, milestones, and kanban project workspaces. AI chat with streaming responses, RAG over your notes, and tool use (web search, calendar, weather). A daily briefing that digests your tasks, RSS feeds, and weather on a schedule. Knowledge graph, per-user/group sharing, PWA with push notifications, an MCP server for external AI clients, and an Android companion app.
Quick Start
Prerequisites: Docker and Docker Compose. 8 GB+ RAM recommended for LLM inference.
Download docker-compose.quickstart.yml from this repo, then:
# Optional but recommended — set a secret key
export SECRET_KEY=your-random-secret-here
docker compose -f docker-compose.quickstart.yml up -d
Open http://localhost:5000. The first user to register becomes admin. Go to Settings → General to pull an LLM model — qwen3:8b or llama3.1:8b are good starting points.
GPU: Ollama runs CPU-only by default. See the comments in
docker-compose.quickstart.ymlto enable NVIDIA GPU passthrough.
Development: To build from source, see Development.
Documentation
| Doc | Contents |
|---|---|
| Architecture | Stack, design decisions, data models, key services |
| Configuration | Environment variables, Docker Compose, production setup, security |
| Features | Detailed feature breakdown and keyboard shortcuts |
| Development | Dev workflow, CI/CD, migrations, release process |
| API Keys & MCP | API key management and Fable MCP install guide |
| SSO / OAuth | OIDC setup for Authentik, Keycloak, and other providers |
| API Reference | All REST API endpoints |
| Android App | Flutter companion app architecture and feature status |
License
This project is privately maintained.