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.
This commit is contained in:
@@ -106,6 +106,10 @@ def _agentic_system_prompt(
|
||||
f"You are the user's personal assistant giving a brief {slot} check-in. "
|
||||
"Use tools to see what's changed since this morning. Focus on progress and "
|
||||
"what's still unaddressed.\n\n"
|
||||
"When checking tasks, call list_tasks at least twice:\n"
|
||||
"- once with status=\"in_progress\" to see anything already being worked on "
|
||||
"(regardless of due date — these can quietly drag past their due dates)\n"
|
||||
"- once filtered by due date for what's coming up or overdue today\n\n"
|
||||
"Rules:\n"
|
||||
"- Call tools to see current state. Never assert facts without tool results.\n"
|
||||
"- If nothing meaningful has changed, say so briefly — don't invent progress.\n"
|
||||
@@ -239,6 +243,12 @@ async def run_agentic_briefing(
|
||||
except Exception:
|
||||
arguments = {}
|
||||
|
||||
# Default list_tasks to active statuses only so cancelled/done
|
||||
# items don't slip into briefing prose. The model can still
|
||||
# pass an explicit status filter when it wants something else.
|
||||
if tool_name == "list_tasks" and not arguments.get("status"):
|
||||
arguments["status"] = ["todo", "in_progress"]
|
||||
|
||||
try:
|
||||
if tool_name == "get_rss_items" and rss_override is not None:
|
||||
# Use topic-scored/filtered items already computed by
|
||||
|
||||
@@ -2132,10 +2132,12 @@ async def execute_tool(
|
||||
|
||||
elif tool_name == "get_weather":
|
||||
from fabledassistant.services.weather import (
|
||||
get_cached_weather, geocode, _fetch_open_meteo, parse_forecast
|
||||
get_cached_weather, geocode, _fetch_open_meteo, parse_forecast,
|
||||
refresh_location_cache,
|
||||
)
|
||||
from fabledassistant.services.briefing_pipeline import _get_temp_unit
|
||||
from datetime import timezone as _tz
|
||||
from datetime import timezone as _tz, timedelta as _td
|
||||
import json as _wx_json
|
||||
location = (arguments.get("location") or "").strip()
|
||||
num_days = max(1, min(8, int(arguments.get("days") or 1)))
|
||||
temp_unit = await _get_temp_unit(user_id)
|
||||
@@ -2167,15 +2169,64 @@ async def execute_tool(
|
||||
}]}}
|
||||
except Exception as e:
|
||||
return {"success": False, "error": f"Could not get weather for '{location}': {e}"}
|
||||
# Fall back to cached configured locations
|
||||
# Fall back to cached configured locations. Auto-refresh any
|
||||
# row older than 6 hours so stale forecasts don't slip into
|
||||
# briefings after an outage or missed scheduler run.
|
||||
locations = await get_cached_weather(user_id)
|
||||
if location.lower() not in ("all", ""):
|
||||
locations = [loc for loc in locations if loc["location_key"] == location.lower()]
|
||||
locations = [
|
||||
{**loc, "days": _apply_unit(loc.get("days", []))[:num_days], "temp_unit": temp_unit}
|
||||
for loc in locations
|
||||
]
|
||||
return {"success": True, "data": {"locations": locations}}
|
||||
now_utc = datetime.now(_tz.utc)
|
||||
stale_cutoff = now_utc - _td(hours=6)
|
||||
stale_keys = []
|
||||
for loc in locations:
|
||||
try:
|
||||
fetched = datetime.fromisoformat(loc.get("fetched_at", ""))
|
||||
except Exception:
|
||||
fetched = None
|
||||
if fetched is None or fetched < stale_cutoff:
|
||||
stale_keys.append(loc["location_key"])
|
||||
if stale_keys:
|
||||
try:
|
||||
from fabledassistant.services.settings import get_setting as _wx_get_setting
|
||||
raw_cfg = await _wx_get_setting(user_id, "briefing_config", "{}")
|
||||
cfg = _wx_json.loads(raw_cfg) if isinstance(raw_cfg, str) else (raw_cfg or {})
|
||||
cfg_locs = cfg.get("locations", {}) if isinstance(cfg, dict) else {}
|
||||
for key in stale_keys:
|
||||
meta = cfg_locs.get(key) or {}
|
||||
if meta.get("lat") and meta.get("lon"):
|
||||
try:
|
||||
await refresh_location_cache(
|
||||
user_id=user_id,
|
||||
location_key=key,
|
||||
location_label=meta.get("label", key),
|
||||
lat=meta["lat"],
|
||||
lon=meta["lon"],
|
||||
)
|
||||
except Exception:
|
||||
logger.debug("Weather refresh failed for %s", key, exc_info=True)
|
||||
locations = await get_cached_weather(user_id)
|
||||
if location.lower() not in ("all", ""):
|
||||
locations = [loc for loc in locations if loc["location_key"] == location.lower()]
|
||||
except Exception:
|
||||
logger.debug("Weather staleness refresh failed", exc_info=True)
|
||||
# Mark each location with a stale flag so the model can hedge
|
||||
# or skip instead of faithfully relaying old numbers.
|
||||
stamped_locations = []
|
||||
for loc in locations:
|
||||
days = _apply_unit(loc.get("days", []))[:num_days]
|
||||
try:
|
||||
fetched = datetime.fromisoformat(loc.get("fetched_at", ""))
|
||||
age_h = (now_utc - fetched).total_seconds() / 3600.0
|
||||
except Exception:
|
||||
age_h = None
|
||||
stamped_locations.append({
|
||||
**loc,
|
||||
"days": days,
|
||||
"temp_unit": temp_unit,
|
||||
"cache_age_hours": round(age_h, 1) if age_h is not None else None,
|
||||
"is_stale": age_h is not None and age_h > 12.0,
|
||||
})
|
||||
return {"success": True, "data": {"locations": stamped_locations}}
|
||||
|
||||
elif tool_name == "get_rss_items":
|
||||
from fabledassistant.services.rss import get_recent_items
|
||||
|
||||
Reference in New Issue
Block a user