Files
library-desk/CHANGELOG.md
T
jpmschweitzerandClaude Fable 5 041a0cafb8 perf: move search persistence off the hot path as one atomic write
Phase 6 persistence gated every /query/hybrid response with ~21+
sequential auto-commit Neo4j queries (SearchQuery node, then one query
per FOUND document link, then one per WebResult). The search_id is now
generated up front and returned immediately; the persistence runs as a
background asyncio task (strong references held against mid-flight GC).

The write itself is collapsed into ONE UNWIND-based execute_write
transaction with aggregating CALL subqueries (so an empty doc-link list
cannot swallow the web-result branch), meaning a mid-way failure can no
longer leave a partial SearchQuery graph behind.

The persisted shape consumed by the consolidation repair loop is
unchanged - SearchQuery {id, query, user, timestamp, processed:false,
total_results, web_count, keywords}, tenant labels, FOUND {rank,
rrf_score} -> WebResult {url, title, content} - and is now pinned by
tests/test_search_persistence.py against exactly what
consolidation_service queries. Tenant scoping of the document MATCH is
preserved and asserted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QbFZyDvYksazX6nYQYZ67L
2026-07-14 14:14:41 +02:00

35 KiB

Changelog

All notable changes to Library Desk will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

Added

  • Stub endpoints implemented (/ingest/check-updates, /ingest/status/{job_id}, /ingest/repo-status/{repository}, /deduplicate/check) — all previously returned canned "not yet implemented" responses; all now require an explicit user (Phase B rule):
    • /ingest/check-updates compares the content_hash now recorded on the tenant's Neo4j Document nodes at ingestion time against the SHA-256 of current Wiki.js page content in a single UNWIND Cypher query, returning changed / new / deleted page lists (auto-generated entity stubs excluded; documents whose stored hash predates hash tracking are flagged stored_hash_missing and count as changed).
    • /ingest/status/{job_id} is backed by the Redis JobManager (jobs are tenant-scoped; other tenants' jobs return 404). /ingest/page, /ingest/batch and /ingest/all now record job entries and return a job_id.
    • /ingest/repo-status/{repository} reports wiki page count vs indexed Document-node count under users/{tenant}/{repository} plus the tenant's Redis job statistics.
    • /deduplicate/check runs a tenant-scoped Qdrant similarity scan: wiki chunk pairs above the threshold (default 0.9 cosine) grouped per page pair with best score, matching chunk-pair count, and page references. Read-only.
  • Job + Scheduler task plumbing — In-process hourly job_cleanup_loop (started at app startup, cancelled at shutdown) reclaims expired Redis job-set memberships (JobManager.cleanup_expired_jobs). docs/scheduler-tasks.md defines the four production Scheduler task payloads for the deploy checklist (nightly integrity 04:30, weekly quality report Sunday 03:00, daily Paperless orphan-cleanup 05:00 on the existing endpoint, and disabling test_example_task) with exact HTTP bodies (explicit user=jpmschweitzer, ${LIBRARY_API_KEY} auth placeholder). scripts/register_scheduler_tasks.py reads the Scheduler API location from SCHEDULER_URL and registers them — dry-run by default (prints payloads), --execute gated and requiring LIBRARY_API_KEY.
  • Weekly quality reportPOST /maintenance/quality-report {user} runs the duplicate scan, flags stale pages (not updated in N days AND ≤ M SearchQuery hits from the graph data), lists pages missing tags/description, folds in the latest integrity-check results (Redis-cached or run inline), and writes a dated report page to users/{user}/system/quality-reports/YYYY-MM-DD (same-day reruns update the same page — the page id is remembered in Redis because the Wiki.js listing lags page creation). Response returns the full report content + page path. Verified end-to-end against the local dev server as llm_tester.
  • Nightly integrity checkPOST /maintenance/integrity-check {user} (read-only: reports, never auto-fixes) reports per tenant: wiki pages with ZERO vectors in Qdrant (silent-skip reindex victims), orphaned vectors whose wiki page no longer exists, unexpected Qdrant collections (test-tenant residue and unknown namespaces flagged; other services' collections counted as foreign), Neo4j Document nodes without wiki counterparts, plus counts and duration. The latest report is cached in Redis (30 days) so the weekly quality report can fold it in.

Changed (performance)

  • Search persistence off the hot path, one atomic transaction - HybridRAG Phase 6 (_persist_search_for_librarian) no longer gates the /query/hybrid response: the search_id is generated up front and returned immediately while the Neo4j write runs as a background task (strong task references held so tasks are not GC'd mid-flight). The write itself collapsed from ~21+ sequential auto-commit queries (SearchQuery node + per-document FOUND links + per-web-result WebResult nodes) into ONE UNWIND-based execute_write transaction, so a mid-way failure can no longer leave a partial SearchQuery graph behind. The persisted shape (SearchQuery properties incl. processed: false, tenant labels, FOUND relationship properties, WebResult properties) is unchanged and pinned by tests/test_search_persistence.py against exactly what the consolidation service queries. timing.persistence_ms now reports 0 (no longer on the request path).
  • Batched embeddings + delete-last reindex - OllamaClient.embed_batch now sends ONE batched /api/embed request (verified against the live Ollama; the old "batch" looped one /api/embeddings call per chunk) with a per-text fallback preserving partial-success semantics. VectorService.update_from_page embeds all chunks in that single call and upserts them in one Qdrant batch, and the reindex order is reversed: new points are upserted BEFORE stale points are pruned (deterministic uuid5 chunk ids make the overwrite safe), so a mid-way failure can no longer leave a page with zero vectors — the old order deleted everything first. The summary now reports status (success/partial/failed) and chunks_skipped instead of unconditional success=True; a fully failed embedding pass keeps the old vectors and reports failure. Measured on a real 7-chunk page ingest as llm_tester against the local server: ~375ms → ~181ms median (3 runs each).
  • Document sync indexing fixed - DocumentSyncService._index_vectors now awaits ensure_collection (the coroutine was created but never ran, so fresh tenants had no collection at upsert time), filters out None entries from embed_batch so one failed chunk embedding no longer aborts the whole document upsert (all-failed still reports failure), and routes the raw client.delete/client.upsert calls through the async wrapper (delete_by_filter / new batch upsert_points). Offline unit tests added.
  • Async Qdrant client - QdrantClientWrapper now uses AsyncQdrantClient with an explicit timeout (QDRANT_TIMEOUT, default 30s). Every vector call previously ran on the synchronous client inside async wrapper methods, blocking the FastAPI event loop for the duration of each Qdrant round-trip. The wrapper API is unchanged (all methods were already async), so call sites only gained real awaits. The HybridRAG document leg was moved off the deprecated raw client.search onto the wrapper's search_vectors (fixing a latent AttributeError: it called the nonexistent ollama.embed_text, so the leg always reported failed), and the health check awaits get_collections.

Changed

  • BREAKING: user is now required on every tenant-data endpoint - The implicit jpmschweitzer default tenant (DEFAULT_USER) has been removed everywhere. All endpoints that read or write tenant data (/query/*, /wiki/*, /vector/*, /graph/*, /ingest/*, /volatile/*, /documents/*, /stats, /rag/search) now reject requests without an explicit, non-empty, non-whitespace user (HTTP 422), matching the existing /maintenance/* pattern. A shared validator (require_user dependency / RequiredUser model type) also rejects blank users. The Wiki.js change listener now skips changes whose notification email yields no user instead of attributing them to the production tenant. Caller coordination required: tatlock and the Scheduler ingest/prefetch/consolidation tasks must send an explicit user on every call — see the deploy checklist.

Fixed (security)

  • Cross-tenant leaks in HybridRAG legs and ingestion closed - A live probe as user=llm_tester returned jpmschweitzer pages. Root causes fixed:

    • Ingestion namespace enforcement: vector and graph update_from_page now refuse pages whose wiki path is outside users/{user}/ (previously any tenant could ingest any page id — including another tenant's — into its own collection/labels, which is how foreign content ended up in the vector leg). /ingest/all clamps path_prefix to the caller's namespace (400 on cross-tenant prefixes) and defaults to users/{user}.
    • Search persistence: the FOUND link in HybridRAG phase 6 matched (d:Document {page_id}) unscoped, attaching the caller's SearchQuery to other tenants' Document nodes; it now matches only User_{Tenant}_Document nodes.
    • Graph enrichment/consolidation queries scoped: _get_entity_mention_count, entity-stub generation, orphan-entity find/purge, and cleanup_broken_relationships matched unscoped Document/SearchQuery nodes; all now use the tenant's labels. Entity-page existence checks list only the tenant's wiki namespace.
    • Volatile collections sanitized: volatile_{user} collection names now use the sanitized user id (same scheme as document collections).
    • Namespace matching hardened: is_path_in_user_namespace now enforces a path-segment boundary (users/llm_tester2 is no longer inside llm_tester's namespace) and compares sanitized tenant segments.
    • Offline unit tests added per leg (vector, graph, volatile, documents, enrichment, persistence, ingestion) asserting the tenant-scoped collection/label/path is used.
  • /query/graph and /graph/query hardened to read-only - The documented "automatic user scoping" was a no-op (a live probe confirmed any user string could read the whole graph) and the client permitted writes. Raw Cypher queries are now (1) rejected with 400 when they contain write clauses (CREATE/MERGE/DELETE/DETACH/SET/REMOVE/DROP/FOREACH/LOAD CSV) or any CALL procedure (conservative denylist on the uppercased query), and (2) executed through a Neo4j session opened with default_access_mode=READ_ACCESS so the database itself refuses writes as a backstop. The endpoints are now honestly documented as admin/debug, unscoped read-only: results are not restricted to the caller's tenant labels — use /graph/nodes for tenant-scoped access.

Added

  • Degradation signaling - HybridRAGResponse now includes source_status (per-leg 'ok'/'failed'/'disabled' for vector, graph, web, volatile, documents) and degraded (true when any enabled leg failed). Retrieval legs report errors instead of silently swallowing them; failed legs are logged at WARNING. Both fields are additive and optional, so clients that ignore them are unaffected.
  • Hard-isolated test-tenant lifecycle for the test suite - tests/conftest.py rewritten: the production host default (192.168.86.149) is gone (TEST_HOST env, safe localhost default; the API under test is the local wakeup server via LIBRARY_DESK_URL, never the production container on 8089). The suite is pinned to the reserved test tenant llm_tester; a session guard aborts the entire run if the effective tenant is jpmschweitzer or outside the reserved llm_tester* namespace. Integration tests are marked and only run with RUN_INTEGRATION_TESTS=1 (plus a passing guard). A session-scoped teardown deletes ALL llm_tester artifacts created during the run — Qdrant *_llm_tester collections, Neo4j User_Llm_Tester*-labelled nodes, the users/llm_tester wiki subtree, and llm_tester Redis keys on the service DB — with hard tenant assertions before every delete. Legacy integration tests were pinned to the test tenant (no more production-namespace reads).
  • Live tenant-isolation test - tests/test_tenant_isolation_live.py (integration-marked, guard-gated): creates and ingests a wiki page as llm_tester against the local wakeup server + shared services, asserts /query/hybrid as llm_tester retrieves its own content with ZERO results from the jpmschweitzer tenant, and asserts a third nonexistent tenant (llm_tester_void, still inside the reserved namespace — nothing is ever written as jpmschweitzer) gets zero results entirely. Teardown removes everything it created.
  • Test-residue purge tooling - scripts/purge_test_artifacts.py: dry-run by DEFAULT (--execute required for real deletion), targets only confirmed test residue (Qdrant library_desk_llm_tester / test_user / library_desk_test_user / memories_llm_tester / volatile_llm_tester / core_ai_user_test_* / anything containing llm_tester; Neo4j User_Llm_Tester*-labelled nodes and legacy users/llm* Document nodes; llm_tester Redis keys on the service DB), prints counts per target, hard-aborts if any target rule ever matches a jpmschweitzer-namespaced identifier, and documents the snapshot prerequisite (Qdrant snapshot API + neo4j-admin database dump) in its docstring.
  • Offline unit tests - New mock-based tests (no live services) for model-name resolution under the env collision, per-leg failure signaling, the /stats page-count prefix, LLM-call timeouts, and Wiki.js listing pagination.

Fixed

  • Ollama generation model env collision - Renamed the generation-model setting ollama_model to ollama_llm_model (env: OLLAMA_LLM_MODEL, default gemma4:e2b). The container env OLLAMA_MODEL=nomic-embed-text (meant for embeddings) was shadowing the generation model, breaking Phase 0 keyword extraction and Phase 4 LLM re-ranking on every request. Startup now logs the resolved generation model.
  • LLM call timeouts - Phase 0 keyword extraction and Phase 4 re-ranking are wrapped in a 12s asyncio.wait_for with graceful fallback, so a hung Ollama call can no longer gate retrieval for the full 120s client timeout.
  • /stats wiki page count - The endpoint passed the bare user name as path prefix (matching nothing) and always reported 0 pages; it now counts pages under users/{user}.
  • Wiki.js page listing - list_pages applied the API-side limit before client-side path/tag filters, dropping matching pages that sort late; the limit now applies after filtering. list_all_pages replaced its fake pagination loop with a real limit-growth loop (Wiki.js 2.x pages.list has no offset argument) that fetches until the API returns fewer pages than requested.
  • Consolidation loop silently drained its queue on LLM failure - Root cause of the 30-minute knowledge-consolidation loop processing 0 searches ("No unprocessed searches found" in prod): the OLLAMA_MODEL env collision (see below) made every consolidation /api/generate call fail with HTTP 400 ("nomic-embed-text" does not support generate — confirmed in prod logs and by direct Ollama probe), classification returned empty, and the loop STILL marked every SearchQuery processed: true — permanently consuming the queue with zero pages ever created (live Neo4j: 197/200 SearchQuery nodes processed with no output). LLM-infrastructure failure now raises ConsolidationLLMUnavailableError: the affected searches stay unprocessed (retried next run), the batch aborts after the first failure, and the response reports searches_deferred. Every run now logs searches_processed and duration_ms (also new response fields). The lookback boundary is now timezone-aware UTC. Regression tests added.
  • Wiki.js update_page without tags - Wiki.js 2.x requires tags on the update mutation (the server unconditionally maps over it); every update_page(page_id, content=...) call without tags failed with Cannot read properties of undefined (reading 'map') — this silently broke the consolidation service's page-update path too. The client now preserves the page's current tags when the caller does not supply any.
  • Wiki.js listing completeness under pre-filter limits - Observed live: pages.list(limit=100) returned 43 pages while 140 existed (limit=500 returned all) — Wiki.js applies the limit BEFORE its own visibility filtering, so "fewer pages than requested" does not mean the listing is complete and the limit-growth loop stopped early, silently truncating listings (page counts, cleanups, integrity scans). The loop now grows the limit until the returned count stops increasing (fixed point), at the cost of one confirming fetch.

[1.7.3] - 2026-01-07

Fixed

  • Endpoint TTL defaults - Updated all fetch endpoint defaults to match namespace TTLs (2x refresh interval)

[1.7.2] - 2026-01-07

Fixed

  • Volatile TTL doubled - TTL now 2x refresh interval to survive missed/delayed scheduler runs

[1.7.1] - 2026-01-07

Fixed

  • CI workflow - Updated Gitea Actions to trigger on tag push (matching core-api)

[1.7.0] - 2026-01-07

Added

  • Combined Environment Endpoint - POST /volatile/fetch/environment/{city}
    • Fetches weather and air quality concurrently with asyncio.gather()
    • Single geocode lookup shared between both API calls
    • More efficient than calling weather and air_quality separately
    • Reduces wall-clock time and eliminates redundant geocoding

Fixed

  • Scheduler executor name - Fixed rest_apirest_api_executor in SchedulerTask model and register_volatile_fetch() to prevent "Executor module not found" errors

[1.6.2] - 2025-12-30

Added

  • System Statistics Endpoint - GET /stats

    • Neo4j: node counts by type (Document, Entity, Collection, Search)
    • Qdrant: collection counts, total vectors, per-collection breakdown
    • Wiki.js: total page count
    • Paperless: documents, tags, correspondents, document types
  • Weather/Forecast Separation - Split weather into two distinct namespaces

    • POST /volatile/fetch/weather/{city} - Current conditions only (1hr TTL)
    • POST /volatile/fetch/forecast/{city} - 7-day outlook (12hr TTL)
    • Different update frequencies for efficient caching
    • FORECAST namespace added to volatile namespaces

Changed

  • Weather namespace TTL changed from 30 minutes to 1 hour (current conditions)
  • Forecast data now stored separately with 12 hour TTL

[1.6.1] - 2025-12-30

Added

  • Weather Forecast Support - Enhanced weather fetch with 7-day daily forecasts
    • Current conditions now include UV index
    • Daily forecasts with high/low temps, conditions, precipitation chance, UV max
    • Natural language text summary with multi-day outlook
  • Sun Times Endpoint - POST /volatile/fetch/sun/{city}
    • Sunrise and sunset times (HH:MM and ISO formats)
    • Daylight duration in seconds and hours
    • Separate volatile namespace with 24hr TTL
    • Useful for home automation light triggers
  • Air Quality Endpoint - POST /volatile/fetch/air_quality/{city}
    • European and US AQI indices
    • Pollutants: PM2.5, PM10, ozone, nitrogen dioxide, sulphur dioxide, carbon monoxide
    • Pollen data (grass, birch, alder) for European locations (seasonal)
    • Hourly refresh (1hr TTL)
  • New Base Models
    • SunTimes dataclass for sunrise/sunset data
    • AirQuality dataclass with AQI and pollutants
    • AirQualityProvider abstract interface
  • New Volatile Namespace - SUN for sunrise/sunset times (86400s default TTL)

Changed

  • Weather fetch now uses get_forecast() instead of get_current() for richer data
  • OpenMeteoProvider now implements both WeatherProvider and AirQualityProvider

[1.6.0] - 2025-12-29

Added

  • Memory System Implementation - Complete three-tier memory architecture

    • Volatile Fetch Endpoints - Scheduler-driven prefetch for ephemeral data
      • POST /volatile/fetch/{namespace}/{key} - Fetch and cache external data
      • Weather, news, and financial data providers integrated
      • Auto-caching with namespace-specific TTLs
    • Unified Memory Routing - LLM-based classification of web results
      • Routes content to wiki (stable), volatile (ephemeral), file (documents), or prefetch (scheduled)
      • Integrated into consolidation service post-processor
    • Document Recall in HybridRAG - Paperless documents as fourth retrieval source
      • Documents searched alongside wiki, volatile, and web in parallel
      • New config: enable_documents, document_limit, document_threshold
      • paperless_id field in results for document attribution
      • document_ms timing in performance breakdown
  • Scheduler Integration - External scheduler service for prefetch task management

    • SchedulerClient - Full REST API client for task CRUD operations
    • register_volatile_fetch() convenience method for prefetch registration
    • Consolidation service now creates scheduled tasks for prefetch-worthy content
    • Health checks integrated into startup/shutdown lifecycle

Changed

  • HybridRAG now searches 4 sources in parallel (wiki, volatile, documents, web)
  • Consolidation service uses external scheduler instead of settings storage for prefetch

[1.5.0] - 2025-12-26

Added

  • Central Settings Database - Tatlock-wide configuration via PostgreSQL

    • SettingsClient for async access to system_settings database
    • User-scoped settings with global fallback
    • API config storage with enabled toggle and per-source category filters
    • JSON Schema support for future UI rendering
  • External API Providers - Modular src/apis/ package with swappable implementations

    • OpenMeteoProvider - Weather with geocoding (free, no API key)
    • NOSProvider - Dutch news RSS (16 categories including sports)
    • BBCProvider - English news RSS (21 categories including sports)
    • AggregatedNewsProvider - Merges sources chronologically with category filtering
    • AlphaVantageProvider - Stock/crypto quotes (API key from settings DB)
    • Abstract base classes for provider interoperability
  • Provider Dependency Injection

    • WeatherProviderDep, NewsProviderDep, AlphaVantageProviderDep type aliases
    • Async initialization with settings database integration
    • Lifecycle management in shutdown_clients()
  • Development Dependencies - requirements-dev.txt

    • pip-audit for security vulnerability scanning
    • ruff for code quality
    • Testing packages moved from main requirements

Changed

  • News sources configurable via news.sources setting
  • Per-source category filtering via api.{source}.categories
  • Categories default to all if not specified

[1.4.8] - 2025-12-25

Added

  • Paperless Orphan Cleanup - POST /maintenance/cleanup/paperless endpoint
    • Detects documents deleted from Paperless but still indexed in Library Desk
    • Removes orphaned vectors and graph nodes
    • Supports dry_run=true for preview mode

[1.4.7] - 2025-12-25

Fixed

  • Paperless Custom Field Update - Fixed 400 error when marking documents as indexed
    • Paperless API requires field ID (integer) not field name (string)
    • Now looks up library_indexed field ID before updating
    • Webhook params format: doc_url and title from Jinja templates

Added

  • Webhook Debug Endpoint - POST /documents/webhook-capture for development testing

[1.4.6] - 2025-12-25

Fixed

  • Paperless Webhook Payload Format - Updated model to match Paperless include_document=true format
    • Paperless sends id instead of document_id
    • Paperless sends full document data including content, title, tags, etc.
    • Webhook now uses content from payload, skipping extra Paperless API call
    • Added extra = "ignore" to handle additional Paperless fields

[1.4.5] - 2025-12-25

Added

  • Document Storage Integration - Paperless-ngx integration for PDFs, images, and documents
    • Event-driven architecture via Paperless webhooks
    • POST /documents/webhook - Receive document events from Paperless workflows
    • POST /documents/upload - Upload files directly to Paperless
    • POST /documents/upload-url - Download and upload documents from URL
    • POST /documents/search - Semantic search across indexed documents
    • GET /documents/health - Paperless connectivity health check
  • DocumentSyncService - Indexes Paperless documents into vectors and graph
    • Fetches document content via Paperless API
    • Chunks text and generates embeddings for Qdrant
    • Creates Document nodes in Neo4j knowledge graph
    • Supports multi-tenancy via user parameter in webhook URL
  • PaperlessClient - REST API client for Paperless-ngx
    • Document retrieval, upload, and update operations
    • Health check support
  • Paperless Workflow Configuration
    • Production workflow: Document Added (NOT tagged llm-test) → webhook to Library Desk
    • Test workflow: Document Added (tagged llm-test) → webhook with test user

Changed

  • Updated src/config.py with Paperless configuration settings
  • Added PaperlessDep dependency injection for document endpoints

[1.4.4] - 2025-12-24

Added

  • Test Data Cleanup Endpoint - POST /maintenance/cleanup/test-data
    • Purges LLM test data from wiki, graph, and vectors
    • Security-restricted to test user namespace only (users/llm-tester/*, users/llm_tester/*)
    • Supports dry_run=true (default) to preview before deleting
    • Scheduler task configured for weekly cleanup (Sunday 3:00 AM)

[1.4.3] - 2025-12-24

Changed

  • Volatile Cache System Refactored to Vector Storage
    • Backend migrated from Redis to Qdrant for semantic search capability
    • Data converted to natural language for embedding and semantic retrieval
    • Collection naming: volatile_{user} for per-user isolation
    • TTL implemented via ttl_expiry timestamp in vector payload
    • Simplified endpoints:
      • GET /volatile/search?q=... - Semantic search across volatile data
      • POST /volatile/store?namespace=...&key=... - Store with query params
      • GET /volatile/{namespace}/{key} - Get specific record
      • DELETE /volatile/{namespace}/{key} - Delete record
    • Removed namespace-specific URL patterns (simpler API for LLM tool use)

Added

  • HybridRAG Volatile Integration - Volatile cache now included in multi-source search
    • Volatile results get priority boost in RRF fusion (current data ranks higher)
    • New config options: enable_volatile, volatile_limit (default 1), volatile_threshold
    • Timing breakdown includes volatile_ms
  • Volatile Cleanup Endpoint - POST /maintenance/cleanup/volatile
    • Purges expired records across all volatile_* collections
    • Scheduler task for every 10 minutes recommended
    • Returns per-collection cleanup counts
  • Natural Language Conversion - Structured data converted for embedding
    • Template-based conversion for each namespace (weather, news, financial, etc.)
    • Fallback for custom namespaces

[1.4.2] - 2025-12-24

Added

  • Volatile Cache System - Ephemeral data storage with TTL
    • GET /volatile/{namespace}/{key} - Retrieve cached record
    • POST /volatile/{namespace}/{key} - Store/update record with TTL
    • DELETE /volatile/{namespace}/{key} - Remove record
    • GET /volatile/{namespace} - List keys in namespace
    • DELETE /volatile/{namespace} - Clear all records in namespace
    • GET /volatile/stats - Cache statistics by namespace
    • GET /volatile/scheduled - Records needing refresh (for scheduler)
    • GET /volatile/namespaces - List available namespaces with default TTLs
  • Volatile Namespaces - Predefined categories with appropriate TTLs:
    • weather (30min) - Weather conditions and forecasts
    • news (1hr) - Headlines and breaking news
    • financial (5min) - Stock prices, exchange rates
    • transit (5min) - Train/bus schedules, delays
    • traffic (10min) - Commute times, road conditions
    • air_quality (1hr) - Pollution, pollen counts
    • sports (1min) - Live scores, matches
    • social (10min) - Social notifications
    • system (1min) - Service health status
    • context (1hr) - Session state
    • custom (1hr) - User-defined data
  • Refresh Schedule Support - Optional cron expressions for scheduler integration

[1.4.1] - 2025-12-24

Fixed

  • Wiki.js API token now optional - GraphQL API works without authentication
  • Container startup failure when WIKI_GRAPHQL_API env var not set

[1.4.0] - 2025-12-24

Added

  • Maintenance Router - New /maintenance endpoints for system health and cleanup
    • GET /maintenance/health - Lightweight health check (detailed mode available)
    • POST /maintenance/cleanup/all - Full orphan cleanup (vectors + graph)
    • POST /maintenance/cleanup/vectors - Purge orphan vector chunks
    • POST /maintenance/cleanup/graph - Purge orphan graph nodes
    • POST /maintenance/reconcile-index - Combined cleanup + reindex missing pages
  • Bidirectional Orphan Detection - Cross-validate vectors and graph nodes
    • find_documents_without_vectors() - Graph nodes missing vector chunks
    • find_chunks_without_graph_nodes() - Vector chunks missing graph nodes
  • Qdrant Client Methods - Bulk operations for maintenance
    • scroll_all_points() - Iterate all points with pagination
    • delete_by_ids() - Batch delete by point IDs
  • Graph Service Cleanup - Node deletion methods
    • delete_document_node() - Remove document and relationships
    • delete_collection_node() - Remove collection and contained documents
    • get_all_document_references() - Get all document references for validation
  • Redis Timestamp Tracking - last_cleanup timestamp for scheduler integration
  • Memory System Plan - Documented three-tier architecture (volatile/documents/knowledge)

Changed

  • Wiki.js Authentication - Switched from username/password to API token
    • New WIKI_GRAPHQL_API environment variable for JWT token
    • Deprecated WIKIJS_USERNAME and WIKIJS_PASSWORD (kept for backwards compatibility)
  • Service Dependencies - Added VectorServiceDep and GraphServiceDep type aliases

Fixed

  • Wiki.js client now properly handles API token auth without login flow

[1.3.3] - 2025-12-23

Added

  • Temperature parameter to OllamaClient.generate_text() for controlling output determinism
  • TODO.md tracking remaining stub endpoints to implement
  • Wired /query/semantic endpoint to VectorService
  • Wired /query/graph endpoint to GraphService

Changed

  • Improved LLM prompts based on llm-findings.md recommendations:
    • Keyword extraction: temperature 0.0, negative constraints
    • LLM re-ranking: temperature 0.0, explicit rules
    • Conflict detection: temperature 0.0, analysis steps (CoT)
    • Wiki page creation: temperature 0.3, anti-hallucination constraints
    • Page reconstruction: temperature 0.2, preservation constraints
    • Web results analysis: temperature 0.0, conservative approach
  • Test fixtures now use configurable host (TEST_HOST) instead of Docker hostnames

Removed

  • Dead code: unused get_default_user() function
  • Unused imports from routers (wiki.py, graph.py, hybrid_rag.py)
  • Stub endpoints shadowed by real implementations (/stats, /ingest/document, /ingest/batch)

[1.3.2] - 2025-12-22

Changed

  • Consolidated Ollama model configuration - All LLM operations now use single OLLAMA_MODEL environment variable
    • Removed separate reranker_model setting
    • HybridRAG re-ranking, consolidation analysis, and wiki page writing all use the same model
    • Improves VRAM efficiency by keeping one model hot
  • Added OLLAMA_EMBEDDING_MODEL environment variable for embedding model (previously overloaded OLLAMA_MODEL)
  • Updated WikiPageWriter to accept settings instead of hardcoded model name

[1.3.1] - 2025-12-16

Fixed

  • Smart create endpoint missing content_extractor dependency causing 500 errors on POST /wiki/pages/smart-create

[1.3.0] - 2025-12-15

Changed

  • Two-Stage RRF Architecture - Major refactor to level the playing field between wiki and web results

    • Stage 1: Vector and graph results merged into single "wiki" ranking using mini-RRF
    • Stage 2: Final RRF between wiki (single source) and web (single source)
    • Wiki pages no longer get 2x advantage from appearing in both vector and graph searches
    • Multi-source confirmation still determines wiki internal ranking
  • Skip synonyms in graph search - LLM-generated synonyms (e.g., "author") no longer match unrelated graph entities (e.g., "author2000")

    • Vector search still uses synonyms for semantic similarity
    • Graph search uses only core keywords for exact entity matching

Added

  • VECTOR_SIMILARITY_THRESHOLD config setting (default: 0.7) to filter weak vector matches
  • Deduplication in graph search to prevent same document appearing multiple times

Fixed

  • Graph search duplicate entity bug where same document could appear twice if entity linked multiple times

[1.2.1] - 2025-12-15

Fixed

  • HybridRAG router missing content_extractor dependency causing 500 errors on /query/hybrid endpoint

[1.2.0] - 2025-12-15

Added

  • RAG Search Endpoint (POST /rag/search)

    • Web, news, and image search via SearXNG
    • Full content extraction using Trafilatura (F1 score 0.958)
    • Redis caching with configurable TTL
    • Markdown sources summary for LLM consumption
    • Returns both extracted content and original snippets
  • Content Extraction Endpoints (/content/*)

    • POST /content/extract - Extract content from a single URL
    • POST /content/extract/batch - Batch extraction (up to 20 URLs)
    • Reusable ContentExtractor client for use across the codebase
  • HybridRAG Content Extraction Enhancement

    • Web search results now include full extracted content via Trafilatura
    • Falls back to original snippets if extraction fails
    • Improves context quality for LLM re-ranking and consumption

Changed

  • Added new configuration options:
    • SEARCH_CACHE_TTL - Search cache TTL in seconds (default: 300)
    • SEARCH_TIMEOUT - SearXNG timeout (default: 10s)
    • CONTENT_EXTRACTION_TIMEOUT - Per-URL extraction timeout (default: 5s)
    • CONTENT_MAX_LENGTH - Max extracted content length (default: 2000)
    • SEARCH_DEFAULT_LIMIT - Default search results (default: 10)

Dependencies

  • Added trafilatura~=1.12.0 for content extraction

[1.1.3] - 2025-12-14

Added

  • Watchtower update trigger in Gitea workflow after successful build

[1.1.2] - 2025-12-14

Fixed

  • Updated registry login URL in Gitea workflow (git.schweitz.net → git.schweitz.internal)

[1.1.1] - 2025-12-14

Fixed

  • Updated container registry tag URLs in Gitea workflow (git.schweitz.net → git.schweitz.internal)

Added

  • Tests for Smart Page Creation feature (test_smart_create.py)
    • Model validation tests for WikiSmartCreateRequest/Response
    • WikiService.smart_create_page method tests
    • Bidirectional entity linking utility tests
    • Endpoint validation tests

[1.1.0] - 2025-12-11

Added

  • Smart Page Creation Endpoint (POST /wiki/pages/smart-create)

    • Combines HybridRAG research with LLM content generation
    • Searches existing wiki, knowledge graph, and web for topic context
    • Uses WikiPageWriter to synthesize findings into structured wiki content
    • Auto-generates page path from topic if not provided
    • Returns research summary with source counts
  • Bidirectional Entity Linking

    • New shared utility (entity_linking_utils.py) for reusable entity linking
    • Forward links: Links entities mentioned in new pages to existing entity pages
    • Backward links: Updates existing pages that mention the new entity
    • Runs automatically in background after smart page creation
  • Version Management

    • Added pyproject.toml with project metadata and version
    • Version is now read from pyproject.toml (single source of truth)
    • Health check endpoint returns current version
    • FastAPI docs show current version

Changed

  • Updated config.py to read version from pyproject.toml
  • Updated main.py to use centralized version

[1.0.0] - 2025-12-10

Added

  • Initial release extracted from portainer-core
  • Wiki page management (/wiki/pages CRUD endpoints)
  • HybridRAG search (/query/hybrid) with vector, graph, and web search
  • Knowledge graph operations (/graph/*)
  • Vector search operations (/vector/*)
  • Knowledge consolidation from search results (/consolidate/knowledge)
  • Entity linking and extraction
  • Wiki.js change listener for auto-processing user edits
  • Multi-tenant architecture with user namespace isolation