feat: integrate external scheduler for prefetch task registration

Add SchedulerClient to communicate with external scheduler service for
registering volatile prefetch tasks discovered during HybridRAG searches.

- Add scheduler_client.py with full REST API for task CRUD operations
- Add scheduler_url config setting (default: http://scheduler:8090)
- Update consolidation service to use scheduler for prefetch registration
- Add scheduler health checks to startup/shutdown lifecycle

When HybridRAG classifies web content as prefetch-worthy, it now creates
scheduled tasks that periodically refresh the volatile cache via the
external scheduler service.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-29 20:42:37 +01:00
co-authored by Claude Opus 4.5
parent c01033505b
commit 910b289c9e
4 changed files with 425 additions and 20 deletions
+61 -20
View File
@@ -46,6 +46,7 @@ class ConsolidationService:
ingestion_service: Optional["IngestionService"] = None,
volatile_service: Optional["VolatileCacheService"] = None,
settings_client: Optional["SettingsClient"] = None,
scheduler_client: Optional["SchedulerClient"] = None,
):
self.neo4j = neo4j
self.ollama = ollama
@@ -54,7 +55,8 @@ class ConsolidationService:
self.wiki_page_writer = WikiPageWriter(ollama_client=ollama, settings=settings)
self.ingestion_service = ingestion_service # Optional to avoid circular dependency
self.volatile_service = volatile_service # For ephemeral data caching
self.settings_client = settings_client # For prefetch registration
self.settings_client = settings_client # For prefetch registration (fallback)
self.scheduler_client = scheduler_client # For scheduler-driven prefetch
async def consolidate_knowledge(
self,
@@ -1233,7 +1235,7 @@ JSON:"""
user: str,
) -> bool:
"""
Register a prefetch pattern with the scheduler.
Register a prefetch pattern with the external scheduler service.
Args:
classification: The classification with prefetch info
@@ -1243,31 +1245,70 @@ JSON:"""
Returns:
True if successfully registered, False otherwise
"""
if not self.settings_client:
logger.warning("Settings client not configured, skipping prefetch registration")
if not self.scheduler_client:
logger.warning("Scheduler client not configured, skipping prefetch registration")
return False
cron = classification.prefetch_cron or "0 * * * *" # Default: hourly
endpoint = classification.prefetch_endpoint or ""
# Parse cron pattern into scheduler schedule format
# Format: "minute hour day_of_month month day_of_week"
# Scheduler uses -1 for "every"
cron = classification.prefetch_cron or "0 * * * *"
schedule = self._parse_cron_to_schedule(cron)
if not endpoint:
logger.warning(f"No prefetch endpoint specified for {web_result.get('url')}")
# Determine namespace and key from classification
namespace = classification.volatile_namespace or "custom"
key = classification.volatile_key or web_result.get('url', '').split('/')[-1].split('?')[0]
if not key:
logger.warning(f"Could not determine prefetch key for {web_result.get('url')}")
return False
try:
# Store prefetch configuration in settings
prefetch_key = f"prefetch.{user}.{classification.volatile_key or 'auto'}"
prefetch_config = {
"cron": cron,
"endpoint": endpoint,
"source_url": web_result.get('url', ''),
"enabled": True,
}
# Use the scheduler client's convenience method to register volatile fetch
success = await self.scheduler_client.register_volatile_fetch(
namespace=namespace,
key=key,
user=user,
schedule=schedule,
description=f"Auto-prefetch: {classification.title or web_result.get('title', 'Unknown')}",
)
await self.settings_client.set(prefetch_key, prefetch_config, user=user)
logger.info(f"Registered prefetch: {prefetch_key} ({cron})")
return True
if success:
logger.info(f"Registered scheduler task: volatile_{namespace}_{key}_{user}")
return success
except Exception as e:
logger.error(f"Failed to register prefetch: {e}")
logger.error(f"Failed to register prefetch with scheduler: {e}")
return False
def _parse_cron_to_schedule(self, cron: str) -> dict:
"""
Parse cron string to scheduler schedule dict.
Args:
cron: Cron-style string (e.g., "0 6 * * *" = 6:00 AM daily)
Returns:
Dict with minute, hour, day_of_month, month, day_of_week
where -1 means "every"
"""
parts = cron.strip().split()
if len(parts) != 5:
# Default to hourly if invalid
return {"minute": 0, "hour": -1}
def parse_part(part: str) -> int:
if part == "*":
return -1
try:
return int(part)
except ValueError:
return -1
return {
"minute": parse_part(parts[0]),
"hour": parse_part(parts[1]),
"day_of_month": parse_part(parts[2]),
"month": parse_part(parts[3]),
"day_of_week": parse_part(parts[4]),
}