|
1 | 1 | --- |
2 | 2 | title: Threads |
3 | | -description: Track unresolved work across sessions with GitMem threads. |
| 3 | +description: Track unresolved work across sessions with lifecycle management, vitality scoring, and semantic deduplication. |
4 | 4 | --- |
5 | 5 |
|
| 6 | +import { Callout } from 'fumadocs-ui/components/callout' |
| 7 | + |
6 | 8 | # Threads |
7 | 9 |
|
8 | | -**Threads** track unresolved work that carries across sessions. When you can't finish something in the current session, create a thread so the next session picks it up. |
| 10 | +**Threads** are persistent work items that carry across sessions. They track what's unresolved, what's blocked, and what needs follow-up — surviving session boundaries so nothing gets lost. |
| 11 | + |
| 12 | +## Why Threads Exist |
| 13 | + |
| 14 | +Sessions end, but work doesn't. Before threads, open items lived as plain strings inside session records. They had no IDs, no lifecycle, no way to mark something as done. You'd see the same stale item surfaced session after session with no way to clear it. |
| 15 | + |
| 16 | +Threads give open items identity (`t-XXXXXXXX`), lifecycle status, vitality scoring, and a resolution trail. |
9 | 17 |
|
10 | 18 | ## Creating Threads |
11 | 19 |
|
12 | 20 | ``` |
13 | 21 | create_thread({ text: "Auth middleware needs rate limiting before production deploy" }) |
14 | 22 | ``` |
15 | 23 |
|
16 | | -Threads include: |
17 | | -- A unique thread ID (e.g., `t-a1b2c3d4`) |
18 | | -- Description text |
19 | | -- Creation timestamp |
20 | | -- Optional Linear issue link |
| 24 | +Threads are created in three ways: |
21 | 25 |
|
22 | | -## Semantic Deduplication |
23 | | - |
24 | | -GitMem uses cosine similarity (threshold > 0.85) to prevent duplicate threads. If you try to create a thread that's semantically identical to an existing one, GitMem returns the existing thread instead. |
| 26 | +1. **Explicitly** via `create_thread` — mid-session when you identify a new open item |
| 27 | +2. **Implicitly** via `session_close` — when the closing payload includes `open_threads` |
| 28 | +3. **Promoted** from a suggestion via `promote_suggestion` — when a recurring topic is confirmed |
25 | 29 |
|
26 | 30 | ## Thread Lifecycle |
27 | 31 |
|
| 32 | +Threads progress through a 5-stage state machine based on vitality scoring and age: |
| 33 | + |
28 | 34 | ``` |
29 | | -create → surface at session_start → resolve |
| 35 | +create_thread / session_close payload |
| 36 | + | |
| 37 | + v |
| 38 | + [ EMERGING ] -- first 24 hours, high visibility |
| 39 | + | |
| 40 | + v (age > 24h) |
| 41 | + [ ACTIVE ] -- vitality > 0.5, actively referenced |
| 42 | + | |
| 43 | + v (vitality decays) |
| 44 | + [ COOLING ] -- 0.2 <= vitality <= 0.5, fading from use |
| 45 | + | |
| 46 | + v (vitality < 0.2) |
| 47 | + [ DORMANT ] -- vitality < 0.2, no recent touches |
| 48 | + | |
| 49 | + v (dormant 30+ days) |
| 50 | + [ ARCHIVED ] -- auto-archived, hidden from session_start |
| 51 | +
|
| 52 | +Any state --(explicit resolve_thread)--> [ RESOLVED ] |
30 | 53 | ``` |
31 | 54 |
|
32 | | -1. **Create** — `create_thread` during a session |
33 | | -2. **Surface** — Open threads appear in the next `session_start` banner |
34 | | -3. **Resolve** — `resolve_thread` with a resolution note when complete |
| 55 | +### Transitions |
35 | 56 |
|
36 | | -## Managing Threads |
| 57 | +| Transition | Condition | |
| 58 | +|-----------|-----------| |
| 59 | +| any -> emerging | Thread age < 24 hours | |
| 60 | +| emerging -> active | Thread age >= 24 hours, vitality > 0.5 | |
| 61 | +| active -> cooling | Vitality drops to [0.2, 0.5] | |
| 62 | +| cooling -> active | Touch refreshes vitality above 0.5 | |
| 63 | +| cooling -> dormant | Vitality drops below 0.2 | |
| 64 | +| dormant -> active | Touch refreshes vitality above 0.5 | |
| 65 | +| dormant -> archived | Dormant for 30+ consecutive days | |
| 66 | +| any -> resolved | Explicit `resolve_thread` call | |
37 | 67 |
|
38 | | -| Tool | Purpose | |
39 | | -|------|---------| |
40 | | -| `list_threads` | See all open threads | |
41 | | -| `resolve_thread` | Mark a thread as done | |
42 | | -| `cleanup_threads` | Triage by health (active/cooling/dormant) | |
| 68 | +**Terminal states:** Archived and resolved threads do not transition. To reopen an archived topic, create a new thread. |
43 | 69 |
|
44 | | -### Thread Health |
| 70 | +## Vitality Scoring |
45 | 71 |
|
46 | | -`cleanup_threads` categorizes threads by vitality: |
| 72 | +Every thread has a vitality score (0.0 to 1.0) computed from two components: |
47 | 73 |
|
48 | | -- **Active** — Recently created or referenced |
49 | | -- **Cooling** — Not referenced in a while |
50 | | -- **Dormant** — Untouched for 30+ days (auto-archivable) |
| 74 | +``` |
| 75 | +vitality = 0.55 * recency + 0.45 * frequency |
| 76 | +``` |
51 | 77 |
|
52 | | -### Suggested Threads |
| 78 | +### Recency |
53 | 79 |
|
54 | | -`session_start` may suggest threads based on session context. You can: |
55 | | -- **Promote** — `promote_suggestion` converts it to a real thread |
56 | | -- **Dismiss** — `dismiss_suggestion` suppresses it (3 dismissals = permanent suppression) |
| 80 | +Exponential decay based on thread class half-life: |
| 81 | + |
| 82 | +``` |
| 83 | +recency = e^(-ln(2) * days_since_touch / half_life) |
| 84 | +``` |
| 85 | + |
| 86 | +| Thread Class | Half-Life | Use Case | |
| 87 | +|-------------|-----------|----------| |
| 88 | +| operational | 3 days | Deploys, fixes, incidents, blockers | |
| 89 | +| backlog | 21 days | Research, long-running improvements | |
| 90 | + |
| 91 | +Thread class is auto-detected from keywords in the thread text ("deploy", "fix", "debug", "hotfix", "urgent", "broken", "incident", "blocker" = operational). |
| 92 | + |
| 93 | +### Frequency |
| 94 | + |
| 95 | +Log-scaled touch count normalized against thread age: |
| 96 | + |
| 97 | +``` |
| 98 | +frequency = min(log(touch_count + 1) / log(days_alive + 1), 1.0) |
| 99 | +``` |
| 100 | + |
| 101 | +### Status Thresholds |
| 102 | + |
| 103 | +| Vitality Score | Status | |
| 104 | +|---------------|--------| |
| 105 | +| > 0.5 | active | |
| 106 | +| 0.2 - 0.5 | cooling | |
| 107 | +| < 0.2 | dormant | |
| 108 | + |
| 109 | +Threads touched during a session have their `touch_count` incremented and `last_touched_at` refreshed, which revives decayed vitality. |
| 110 | + |
| 111 | +## Carry-Forward |
| 112 | + |
| 113 | +On `session_start`, open threads appear with vitality info: |
| 114 | + |
| 115 | +``` |
| 116 | +Open threads (3): |
| 117 | + t-abc12345: Fix auth timeout [ACTIVE 0.82] (operational, 2d ago) |
| 118 | + t-def67890: Improve test coverage [COOLING 0.35] (backlog, 12d ago) |
| 119 | + t-ghi11111: New thread just created [EMERGING 0.95] (backlog, today) |
| 120 | +``` |
| 121 | + |
| 122 | +## Resolution |
| 123 | + |
| 124 | +Threads are resolved via `resolve_thread`: |
| 125 | +- **By ID** (preferred): `resolve_thread({ thread_id: "t-a1b2c3d4" })` |
| 126 | +- **By text match** (fallback): `resolve_thread({ text_match: "package name" })` |
| 127 | + |
| 128 | +Resolution records a timestamp, the resolving session, and an optional note. Knowledge graph triples are written to track the resolution relationship. |
| 129 | + |
| 130 | +## Semantic Deduplication |
| 131 | + |
| 132 | +When `create_thread` is called, the new thread text is compared against all open threads using embedding cosine similarity before creation. |
| 133 | + |
| 134 | +| Threshold | Value | Meaning | |
| 135 | +|-----------|-------|---------| |
| 136 | +| Dedup similarity | 0.85 | Above this = duplicate | |
| 137 | + |
| 138 | +**Dedup methods** (in priority order): |
| 139 | +1. **Embedding-based** — cosine similarity of text embeddings (when Supabase available) |
| 140 | +2. **Text normalization fallback** — exact match after lowercasing, stripping punctuation, collapsing whitespace |
| 141 | + |
| 142 | +When a duplicate is detected, the existing thread is returned (with `deduplicated: true`) and touched to keep it vital. |
| 143 | + |
| 144 | +## Suggested Threads |
| 145 | + |
| 146 | +At `session_close`, session embeddings are compared to detect recurring topics that should become threads. |
| 147 | + |
| 148 | +### Detection Algorithm |
| 149 | + |
| 150 | +1. Compare current session embedding against the last 20 sessions (30-day window) |
| 151 | +2. Find sessions with cosine similarity >= 0.70 |
| 152 | +3. If 3+ sessions cluster (current + 2 historical): |
| 153 | + - Check if an open thread already covers the topic (>= 0.80) -> skip |
| 154 | + - Check if a pending suggestion already matches (>= 0.80) -> add evidence |
| 155 | + - Otherwise, create a new suggestion |
| 156 | + |
| 157 | +Suggestions appear at `session_start`: |
| 158 | + |
| 159 | +``` |
| 160 | +Suggested threads (2) -- recurring topics not yet tracked: |
| 161 | + ts-a1b2c3d4: Recurring auth timeout pattern (3 sessions) |
| 162 | + ts-e5f6g7h8: Build performance regression (4 sessions) |
| 163 | + Use promote_suggestion or dismiss_suggestion to manage. |
| 164 | +``` |
| 165 | + |
| 166 | +| Action | Tool | Effect | |
| 167 | +|--------|------|--------| |
| 168 | +| Promote | `promote_suggestion` | Converts to a real thread | |
| 169 | +| Dismiss | `dismiss_suggestion` | Suppresses (3x = permanent) | |
| 170 | + |
| 171 | +## Knowledge Graph Integration |
| 172 | + |
| 173 | +Thread creation and resolution generate knowledge graph triples: |
| 174 | + |
| 175 | +| Predicate | Subject | Object | When | |
| 176 | +|-----------|---------|--------|------| |
| 177 | +| `created_thread` | Session | Thread | Thread created | |
| 178 | +| `resolves_thread` | Session | Thread | Thread resolved | |
| 179 | +| `relates_to_thread` | Thread | Issue | Thread linked to Linear issue | |
| 180 | + |
| 181 | +Use `graph_traverse` to query these relationships with 4 lenses: `connected_to`, `produced_by`, `provenance`, `stats`. |
| 182 | + |
| 183 | +## Managing Threads |
| 184 | + |
| 185 | +| Tool | Purpose | |
| 186 | +|------|---------| |
| 187 | +| [`create_thread`](/docs/tools/create-thread) | Create a new open thread | |
| 188 | +| [`resolve_thread`](/docs/tools/resolve-thread) | Mark a thread as done | |
| 189 | +| [`list_threads`](/docs/tools/list-threads) | See all open threads | |
| 190 | +| [`cleanup_threads`](/docs/tools/cleanup-threads) | Triage by health (active/cooling/dormant) | |
| 191 | +| [`promote_suggestion`](/docs/tools/promote-suggestion) | Convert suggestion to real thread | |
| 192 | +| [`dismiss_suggestion`](/docs/tools/dismiss-suggestion) | Suppress a suggestion | |
| 193 | + |
| 194 | +## Storage |
| 195 | + |
| 196 | +| Location | Purpose | Tier | |
| 197 | +|----------|---------|------| |
| 198 | +| `.gitmem/threads.json` | Runtime cache / free tier SOT | All | |
| 199 | +| `.gitmem/suggested-threads.json` | Pending suggestions | All | |
| 200 | +| Supabase `threads` table | Source of truth (full vitality, lifecycle, embeddings) | Pro/Dev | |
| 201 | +| Supabase `sessions.open_threads` | Legacy fallback | Pro/Dev | |
| 202 | + |
| 203 | +<Callout type="info" title="Free vs Pro"> |
| 204 | +On free tier, `.gitmem/threads.json` IS the source of truth. On pro/dev tier, it's a cache — Supabase is authoritative. |
| 205 | +</Callout> |
0 commit comments