Skip to content

Cross-session task grouping: research and design options for repeated-work analysis #253

Description

@mando

Description

Argus extracts per-session tasks (#88/#91) but has no way to see patterns across sessions. The goal: group semantically similar tasks across sessions to surface repeated work, then suggest consolidations — a skill, slash command, workflow, or CLAUDE.md addition — that would save the user time. Grouping should ideally run on-device (cross-platform, inside the bun build --compile binary); cloud options including the Hub are on the table.

This issue records the research and the candidate designs.

Current state

  • Tasks live in resolved_tasks as per-session task_json (TaskFact: description, outcome, frustration, signals), with FTS5 over description + outcomeReason + signals. Every task read method is session-scoped — there is no cross-session task read model.
  • The LLM layer (src/llm/) does structured-JSON completions through pluggable providers (default claude-cli → haiku) but has no embeddings capability.
  • Per-group analytics are cheap once groups exist: resolved_interactions.task_seq links tasks to usage/invocations, so occurrences, sessions, projects, tokens/cost, outcome/frustration mix, and top tools per group are a join away.
  • computeRecommendations (src/api/recommendations.ts) is a pure rule-array — a natural surface for "you repeat this task N times — make a skill" suggestions.
  • 📎 Doc discrepancy found during research: docs/internals/task-interpretation.md says TaskFact is local-only, but push.ts uploads resolved_tasks (and resolved_interactions with task_seq). The doc is stale; worth fixing regardless of which option we pick. This also makes the Hub option viable without a wire-contract change.

Shared output layer (all options)

Cluster tasks → per-cluster stats → LLM pass that names each cluster and proposes a consolidation → new store read method + /api/task-groups endpoint + web view, feeding recommendations. The options differ only in how similarity is computed and where it runs.

Options

1. LLM-only grouping through the existing LLM layer (no embeddings)

Batch descriptions ~15 at a time through complete(): batches propose labels against a growing canonical-label registry → merge pass consolidates near-duplicate labels → classification pass assigns every task. Published prior art: Text Clustering as Classification with LLMs (beat embedding-based clustering on 3–6k short-text datasets); essentially Clio minus the embedding step.

  • Cost: ~$0.10–0.30 per 1k tasks on haiku API; free-but-slower (~1–2 min/1k) via claude -p on a subscription. Fits the throttled interpret-drain shape.
  • Pros: zero new deps, zero binary-size impact, works with every existing provider, human-readable labels for free, same privacy posture as task extraction.
  • Cons: non-deterministic across runs (mitigate: temp 0, fixed order, persist the label registry and classify incrementally); requires the LLM enabled (default off).

2. Fully local embeddings: vendored Model2Vec + brute-force cosine + community detection ⭐ recommended core

Embed each description with a Model2Vec static model (potion-base-8M: pure JS token→vector lookup + mean pooling, ~31 MB asset, no ONNX, no native addons — the only local embedding approach that's high-confidence inside bun build --compile today; ~92% of MiniLM on MTEB). Store vectors as Float32Array BLOBs in argus.db; brute-force cosine (1–4 ms per query at a few thousand tasks — no ANN needed). Cluster with a ~40-line port of sentence-transformers' community_detection (threshold-based, unknown k, leftovers = noise, free exemplar per cluster). LLM labels clusters from exemplars — optional; degrades to unlabeled groups when the LLM is off.

  • Pros: deterministic, incremental (embed once, cache in DB), fully offline with LLM off, no per-run cost, no native-code risk.
  • Cons: ~31 MB asset (embed in binary or one-time download to $ARGUS_CACHE_DIR); the JS port (@yarflam/potion-base-8m) is single-author — vendor/fork it (format is simple: tokenizer + one safetensors table); static embeddings are weaker on heavy paraphrase.
  • Rejected sub-variants: transformers.js is broken under bun build --compile (transformers.js#1672); fastembed-js archived; onnxruntime-node native bindings don't survive single-file compile; sqlite-vec blocked by macOS bun:sqlite extension loading (bun#31247) and is unnecessary at this scale anyway.

3. Cloud embeddings via BYO key, local everything else

Same storage/clustering/labeling as option 2, but embedding goes through a new embed() capability in the LLM layer: OpenAI text-embedding-3-small ($0.02/MTok — fractions of a cent for thousands of tasks), OpenRouter (now proxies embeddings; provider already exists), Voyage, or Gemini. Keys via the existing secrets.ts flow.

  • Pros: near-zero cost, zero binary-size impact, best quality, small delta on the provider registry.
  • Cons: sends task descriptions off-device — a real posture change ("all parsing is local"), must be clearly opt-in. Anthropic has no first-party embeddings (they point at Voyage, whose ToS grants a training license by default, opt-out required; Gemini's free tier trains on data).

4. Hub-side clustering (Clio-style, org-wide)

Since sync already uploads resolved_tasks, the Hub can cluster across users in an org — "three engineers keep hand-rolling the same release checklist, ship a shared skill" — which no on-device option can see. Workers AI has embedding models + Vectorize; a scheduled Worker embeds new task rows, clusters, LLM-labels, and the dashboard shows org-level repeated-work patterns.

  • Pros: the only option that finds cross-person patterns (highest-value consolidation flavor); no CLI binary impact.
  • Cons: lives in argus-hub, not this repo; only helps syncing users; server-side clustering of task text raises the privacy bar (copy Clio's minimum-cluster-size aggregation discipline); does nothing for local-only serve users.

5. Lexical baseline: no ML

Normalize descriptions (lowercase, strip paths/identifiers), group by TF-IDF cosine or MinHash over shingles, using resolved_tasks_fts for retrieval; greedy leader clustering over a high threshold.

  • Pros: ~zero deps, deterministic, offline, shippable in days; catches the strongest signal (near-identical prompts typed week after week — exactly the "should be a skill" case).
  • Cons: misses paraphrases, so it undercounts patterns; low enough ceiling that it'd likely be replaced rather than built on.

Recommendation

Option 2 as the core, option 1's LLM pass reduced to cluster labeling + consolidation suggestions, option 3 as an optional provider behind the same seam. Concretely: add an embed() capability to src/llm/ with a local (Model2Vec) default provider and cloud providers opt-in; vectors cached in argus.db; community_detection in pure TS; cluster naming and suggestions through the existing complete() path so they inherit provider config and degrade gracefully. Option 4 is the natural phase 2 in argus-hub (same facets, same pipeline shape).

De-risking spikes

  • Vendor potion-base-8M and validate grouping quality on a few hundred real extracted tasks at thresholds ~0.70–0.80.
  • Confirm the ~31 MB model asset embeds cleanly via bun build --compile asset imports (or settle on first-run download to $ARGUS_CACHE_DIR).

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions