Skip to content

BriefingCache invalidation is incomplete (activityHash dead, no facet-aware invalidation) #225

@mgoldsborough

Description

@mgoldsborough

Problem

src/services/briefing-cache.ts — three related issues with cache invalidation:

1. `activityHash` is dead code

```ts
set(briefing: BriefingOutput, activityHash: string): void {
this.entry = { briefing, generatedAt: Date.now(), activityHash, invalidated: false };
}

get(): BriefingOutput | null {
if (!this.entry) return null;
if (this.entry.invalidated) return null;
if (Date.now() - this.entry.generatedAt > this.ttlMs) return null;
return { ...this.entry.briefing, cached: true };
}
```

The hash is stored in `set()` and never read by `get()`. The caller (`core-source.ts`) computes `createHash("md5").update(JSON.stringify(activity.totals))` and passes it in — dead weight.

2. Cache key doesn't capture facet content

Even if `activityHash` were wired into `get()`, the hash is built from `activity.totals` (conversation count, tool call count, error count). Adding entities to bundle apps (CRM rows, tasks, research notes) doesn't change any of those totals. So the cache stays valid for the full TTL even when the underlying app data has been transformed.

3. No auto-invalidation on data-change events

The SSE-event invalidation that `HomeService` was supposed to provide on `data.changed` / `bundle.*` was deleted in PR #219 because `HomeService` had zero runtime consumers (the class existed and was tested but was never wired into the runtime). Today the cache is purely TTL-based, defaults to 5 min, with no escape hatch except a force-refresh.

Symptom

Add entities to your apps, reload the home dashboard — briefing still shows the pre-update state for up to 5 minutes. Operators can't tell whether they're looking at fresh or stale content.

Suggested fixes (one or more)

  • Wire `activityHash` into `get()` so the cache invalidates when activity totals change. Cheap; doesn't fix facet-content changes but catches new-conversation / new-tool-call cases.
  • Hash the facet payload too — include facet counts or a content checksum in the cache key.
  • Subscribe the cache to `data.changed` events at runtime. The `SseEventManager.onEvent` API already exists; just needs an owner that wires the briefing cache into it. (This was `HomeService`'s job before it was deleted; deciding to revive that orchestrator vs. wiring directly is part of this design.)

Context

Surfaced during local testing of PR #219. See PR #219 (commit `d938d29` deleted the unused HomeService) and the discussion thread for more context.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions