Flow Membership Design: Weighted Gravity Model
Output
🌌 Flow Membership Design
Weighted Gravity Model for Kontask → Flow Assignment
📋 Executive Summary
Problem: Current implementation treats sessions as flow markers, pulling all session work into flows. This creates noise - server restarts, misc questions, and quick Q&A should NOT be in flows.
Solution: Replace session-based flow assignment with a weighted gravity model where kontasks are orphans by default and only join flows when combined signals exceed a threshold.
Key Insight: Most turns shouldn't create kontasks at all. For those that do, kontasks should remain orphans unless there's strong evidence they belong to a flow.
🧲 The Gravity Calculator
Signal Weights
// Threshold: 50 points to join a flow const FLOW_MEMBERSHIP_THRESHOLD = 50; // Signal weights const WEIGHTS = { // Instant inclusion (Claude explicitly links) EXPLICIT_FLOW_ID: +50, // flowId provided → immediate join // Strong signals DECISION_DEPENDENCY: +40, // kontask references a decision in flow PRIMARY_TOPIC_MATCH: +25, // same primary topic as flow PRIMARY_DOMAIN_MATCH: +25, // same primary domain as flow // Medium signals TEMPORAL_5H: +15, // within 5 hours of flow activity TEMPORAL_24H: +10, // within 24 hours TEMPORAL_7D: +5, // within 7 days // Weak signals SECONDARY_TOPIC_MATCH: +10, // touches same topic SECONDARY_DOMAIN_MATCH: +10, // touches same domain };
Calculation Algorithm
function calculateGravity(kontask, candidateFlows): FlowMatch[] { // 1. If explicit flowId, instant match if (kontask.flowId) { return [{ flowId: kontask.flowId, score: 50, reason: 'explicit' }]; } // 2. Score each candidate flow const matches = candidateFlows.map(flow => { let score = 0; const reasons: string[] = []; // Decision dependency (check if kontask refs a decision in this flow) if (kontask.decisionId && flowHasDecision(flow, kontask.decisionId)) { score += WEIGHTS.DECISION_DEPENDENCY; reasons.push('decision-dep'); } // Topic overlap if (kontask.primaryTopic === flow.primaryTopic) { score += WEIGHTS.PRIMARY_TOPIC_MATCH; reasons.push(`topic:${kontask.primaryTopic}`); } else if (hasTopicOverlap(kontask, flow)) { score += WEIGHTS.SECONDARY_TOPIC_MATCH; reasons.push('topic-secondary'); } // Domain overlap if (kontask.primaryDomain === flow.primaryDomain) { score += WEIGHTS.PRIMARY_DOMAIN_MATCH; reasons.push(`domain:${kontask.primaryDomain}`); } else if (hasDomainOverlap(kontask, flow)) { score += WEIGHTS.SECONDARY_DOMAIN_MATCH; reasons.push('domain-secondary'); } // Temporal proximity (based on flow's lastActivityAt) const hoursAgo = hoursSince(flow.lastActivityAt); if (hoursAgo <= 5) { score += WEIGHTS.TEMPORAL_5H; reasons.push('temporal-5h'); } else if (hoursAgo <= 24) { score += WEIGHTS.TEMPORAL_24H; reasons.push('temporal-24h'); } else if (hoursAgo <= 168) { // 7 days score += WEIGHTS.TEMPORAL_7D; reasons.push('temporal-7d'); } return { flowId: flow.id, score, reasons }; }); // 3. Filter by threshold and sort return matches .filter(m => m.score >= FLOW_MEMBERSHIP_THRESHOLD) .sort((a, b) => b.score - a.score); }
Examples
✅ Joins flow (score ≥50):
- Explicit flowId: +50 = 50 ✓
- Primary topic + Primary domain: 25+25 = 50 ✓
- Primary topic + Temporal 5h + Secondary domain: 25+15+10 = 50 ✓
- Decision dependency + Temporal 24h: 40+10 = 50 ✓
❌ Stays orphan (score <50):
- Temporal 5h only: +15 = 15 ✗
- Secondary topic + Secondary domain: 10+10 = 20 ✗
- Primary topic only: +25 = 25 ✗
- Primary topic + Temporal 7d: 25+5 = 30 ✗
🔧 Where Gravity Lives: Konui vs Claude
| Responsibility | Konui (Server) | Claude (In-Turn) |
|---|---|---|
| Explicit linking | — | ✅ Provides flowId in kontask creation |
| Topic classification | ✅ classifyText() in gv-topic-store |
— |
| Domain classification | ✅ classifyDomain() in gv-domain-store |
— |
| Temporal proximity | ✅ Compare timestamps | — |
| Decision dependency | ✅ Check edge graph | May provide decisionId hint |
| Gravity calculation | ✅ Run algorithm on kontask creation | — |
| Flow curation | Stores edges, provides suggestions | ✅ Reviews orphan pool, merges flows |
Key Design Decisions
- Konui runs gravity — All scoring happens server-side at kontask creation time
- Claude provides hints — Can specify
flowIdfor instant inclusion, ordecisionIdfor dependency signal - Orphan pool for curation — Kontasks below threshold stay orphans; Claude can review and link later
- No session binding — Sessions are invisible to flow membership; removed entirely
🗑️ Code to Remove (Session-Based Flow Logic)
gv-flow-store.ts — Functions to DELETE
// ❌ DELETE: Session binding (lines ~180-220) linkSessionToFlow(sessionId, flowId, role) unlinkSessionFromFlow(sessionId, flowId) getSessionsByFlow(flowId) getFlowsBySession(sessionId) // ❌ DELETE: Emergent flow from session (lines ~430-500) getOrCreateFlowForSession(sessionId, prompt) getFlowIdForSession(sessionId) // Called from router.ts:704 // ❌ DELETE: Simple keyword matching (lines ~550-600) suggestMatchingFlows(prompt) // Replace with gravity calculator
gv-flow-types.ts — Types to REMOVE
// ❌ REMOVE: Session binding types SessionFlowLink FlowSessionRole // 'owner' | 'contributor' // ❌ REMOVE from GvFlow interface: sessionIds: string[] // Denormalized session list ownerSessionId?: string // Session that started flow
gv-edge-store.ts — Edge Types to REMOVE
// ❌ REMOVE: Session-related edges createSessionFlowEdge(sessionId, flowId, role) removeSessionFlowEdge(sessionId, flowId) // ❌ REMOVE from EdgeIndex: sessionToFlows: Map<string, string[]> flowToSessions: Map<string, string[]>
router.ts — Logic to REPLACE (lines 700-750)
// ❌ REMOVE: Session-based flow lookup if (!resolvedFlowId && body.sessionId) { resolvedFlowId = await gvFlowStore.getFlowIdForSession(body.sessionId); } // ✅ REPLACE WITH: Gravity calculation const gravityMatches = await calculateGravity(kontask, activeFlows); if (gravityMatches.length > 0) { resolvedFlowId = gravityMatches[0].flowId; } else { // Kontask stays orphan }
🆕 New Components to BUILD
1. gv-gravity.ts — The Gravity Calculator
/** * Gravity calculator for flow membership. * Evaluates kontask signals against candidate flows. */ export interface GravityInput { kontaskId: string; title: string; description?: string; flowId?: string; // Explicit link (instant join) decisionId?: string; // Decision dependency hint createdAt: string; } export interface GravityMatch { flowId: string; score: number; reasons: string[]; // ['topic:bugfix', 'temporal-5h', ...] meetsThreshold: boolean; } export function calculateGravity(input: GravityInput): Promise<GravityMatch[]> export function getActiveFlowsForGravity(): Promise<GvFlow[]> export function classifyKontask(title: string, description?: string): { topics: TopicMatch[]; domains: DomainMatch[]; }
2. Orphan Pool Management
// MCP tools for orphan curation konui_list_orphan_kontasks // Get kontasks not in any flow konui_get_gravity_suggestions // Get borderline matches (score 30-49) konui_link_orphan_to_flow // Manual curation by Claude
3. Enhanced Kontask Creation
// Add to kontask creation in router.ts async function handleCreateKontask(body) { // 1. Classify kontask text const { topics, domains } = classifyKontask(body.title, body.description); // 2. Store classification on kontask body.primaryTopic = topics[0]?.topicId; body.primaryDomain = domains[0]?.domainId; // 3. Create kontask const kontask = kontaskStore.createKontask(body); // 4. Run gravity calculator const matches = await calculateGravity({ kontaskId: kontask.id, title: body.title, description: body.description, flowId: body.flowId, decisionId: body.decisionId, createdAt: kontask.createdAt, }); // 5. Link to best flow if threshold met if (matches[0]?.meetsThreshold) { await linkKontaskToFlow(kontask.id, matches[0].flowId, 'evidence'); } // Otherwise: kontask stays orphan return kontask; }
📐 Schema Integration
The gravity model integrates with existing schemas:
| Schema | Used For | Signal Weight |
|---|---|---|
gvTopicSchema |
12 lifecycle topics (WHAT phase) | Primary +25, Secondary +10 |
gvDomainSchema |
12 tech domains (WHERE in stack) | Primary +25, Secondary +10 |
gvDecisionSchema |
Design/action/deferred decisions | Dependency +40 |
gvGraphSchema |
Edge relationships between entities | Stores kontask→flow links |
gvFlowSchema |
Flow lifecycle (active/completed/blocked) | Temporal via lastActivityAt |
Flow Data Model Changes
interface GvFlow { // KEEP: Core identity id: string; title: string; type: FlowType; status: FlowStatus; priority: FlowPriority; // KEEP: Timestamps for temporal signals createdAt: string; updatedAt: string; lastActivityAt: string; // Used for temporal proximity // KEEP: Classification (for gravity matching) primaryTopic?: string; // Auto-classified from title primaryDomain?: string; // Auto-classified from title // KEEP: Denormalized kontask/decision counts kontaskIds: string[]; // Linked kontasks decisionIds: string[]; // Decisions made in flow // ❌ REMOVE: Session binding // sessionIds: string[]; // ownerSessionId?: string; }
🗂️ File Summary
/konnectvol/konui/src/ ├── gv-flow-store.ts # MAJOR REFACTOR: Remove session binding, keep flow CRUD ├── gv-flow-types.ts # MODIFY: Remove session types, add primaryTopic/Domain ├── gv-edge-store.ts # MODIFY: Remove session-flow edges ├── router.ts # MODIFY: Replace session lookup with gravity ├── gv-gravity.ts # NEW: Gravity calculator ├── gv-orphan.ts # NEW: Orphan pool management ├── gv-topic-store.ts # KEEP: Already has classifyText() ├── gv-domain-store.ts # KEEP: Already has classifyDomain() ├── kontask-store.ts # KEEP: Core kontask CRUD └── kontask-types.ts # MODIFY: Add primaryTopic/primaryDomain fields /konnectvol/gv/docs/ ├── gvFlowSchema.md # UPDATE: Remove session sections ├── gvGravitySchema.md # NEW: Document gravity algorithm └── gvGraphSchema.md # UPDATE: Note session edges deprecated
🚀 Implementation Phases
Phase 1: Create gv-gravity.ts
- Implement gravity calculator with signal weights
- Add classifyKontask() wrapper for topic+domain
- Write comprehensive tests
Phase 2: Integrate into router.ts
- Replace session-based flow lookup with gravity
- Add topic/domain classification at kontask creation
- Store primaryTopic/primaryDomain on kontasks
Phase 3: Remove session-flow binding
- Delete session binding functions from gv-flow-store
- Remove session-flow edge types
- Clean up GvFlow interface
Phase 4: Orphan pool + curation MCP tools
- Add konui_list_orphan_kontasks
- Add konui_get_gravity_suggestions
- Add konui_link_orphan_to_flow
📊 Expected Outcomes
- Cleaner flows — Only meaningful work joins flows; noise stays orphaned
- Sessions irrelevant — Session boundaries don't affect flow membership
- Semantic grouping — Topics and domains drive membership, not timing alone
- Decision chains — Related decisions naturally group together
- Claude curation — Borderline cases can be manually reviewed
- Transparency — Each link shows why it joined (score + reasons)
Quick Actions
Original Request
No layout configured
Details
📋 Claude's Context
ses_7c4eba4a...
Raw Data
{
"id": "c4413070-da54-49e6-a4d6-0a6c685f19d9",
"type": "general",
"status": "completed",
"title": "Flow Membership Design: Weighted Gravity Model",
"context": {
"output": "<div style=\"background:#1e1e1e;color:#e0e0e0;padding:24px;border-radius:8px;font-family:'Consolas','Monaco',monospace;line-height:1.6;\">\n\n<h1 style=\"font-family:sans-serif;color:#fff;margin:0 0 8px 0;font-size:28px;\">🌌 Flow Membership Design</h1>\n<p style=\"color:#7f848e;margin:0 0 24px 0;font-size:14px;\">Weighted Gravity Model for Kontask → Flow Assignment</p>\n\n<hr style=\"border:none;border-top:1px solid #3d3d3d;margin:20px 0;\">\n\n<h2 style=\"font-family:sans-serif;color:#61afef;margin:24px 0 12px 0;\">📋 Executive Summary</h2>\n<div style=\"background:#2d2d2d;padding:16px;border-radius:6px;margin-bottom:20px;\">\n<p style=\"margin:0 0 12px 0;\"><strong style=\"color:#98c379;\">Problem:</strong> Current implementation treats sessions as flow markers, pulling all session work into flows. This creates noise - server restarts, misc questions, and quick Q&A should NOT be in flows.</p>\n<p style=\"margin:0 0 12px 0;\"><strong style=\"color:#98c379;\">Solution:</strong> Replace session-based flow assignment with a <span style=\"color:#e5c07b;\">weighted gravity model</span> where kontasks are <strong>orphans by default</strong> and only join flows when combined signals exceed a threshold.</p>\n<p style=\"margin:0;\"><strong style=\"color:#98c379;\">Key Insight:</strong> Most turns shouldn't create kontasks at all. For those that do, kontasks should remain orphans unless there's strong evidence they belong to a flow.</p>\n</div>\n\n<hr style=\"border:none;border-top:1px solid #3d3d3d;margin:20px 0;\">\n\n<h2 style=\"font-family:sans-serif;color:#61afef;margin:24px 0 12px 0;\">🧲 The Gravity Calculator</h2>\n\n<h3 style=\"font-family:sans-serif;color:#c678dd;margin:20px 0 10px 0;\">Signal Weights</h3>\n<pre style=\"background:#2d2d2d;padding:16px;border-radius:6px;overflow-x:auto;\">\n<span style=\"color:#7f848e;\">// Threshold: 50 points to join a flow</span>\n<span style=\"color:#e5c07b;\">const FLOW_MEMBERSHIP_THRESHOLD = 50;</span>\n\n<span style=\"color:#7f848e;\">// Signal weights</span>\n<span style=\"color:#c678dd;\">const WEIGHTS = {</span>\n <span style=\"color:#98c379;\">// Instant inclusion (Claude explicitly links)</span>\n <span style=\"color:#e5c07b;\">EXPLICIT_FLOW_ID:</span> <span style=\"color:#d19a66;\">+50</span>, <span style=\"color:#7f848e;\">// flowId provided → immediate join</span>\n \n <span style=\"color:#98c379;\">// Strong signals</span>\n <span style=\"color:#e5c07b;\">DECISION_DEPENDENCY:</span> <span style=\"color:#d19a66;\">+40</span>, <span style=\"color:#7f848e;\">// kontask references a decision in flow</span>\n <span style=\"color:#e5c07b;\">PRIMARY_TOPIC_MATCH:</span> <span style=\"color:#d19a66;\">+25</span>, <span style=\"color:#7f848e;\">// same primary topic as flow</span>\n <span style=\"color:#e5c07b;\">PRIMARY_DOMAIN_MATCH:</span> <span style=\"color:#d19a66;\">+25</span>, <span style=\"color:#7f848e;\">// same primary domain as flow</span>\n \n <span style=\"color:#98c379;\">// Medium signals</span>\n <span style=\"color:#e5c07b;\">TEMPORAL_5H:</span> <span style=\"color:#d19a66;\">+15</span>, <span style=\"color:#7f848e;\">// within 5 hours of flow activity</span>\n <span style=\"color:#e5c07b;\">TEMPORAL_24H:</span> <span style=\"color:#d19a66;\">+10</span>, <span style=\"color:#7f848e;\">// within 24 hours</span>\n <span style=\"color:#e5c07b;\">TEMPORAL_7D:</span> <span style=\"color:#d19a66;\">+5</span>, <span style=\"color:#7f848e;\">// within 7 days</span>\n \n <span style=\"color:#98c379;\">// Weak signals</span>\n <span style=\"color:#e5c07b;\">SECONDARY_TOPIC_MATCH:</span> <span style=\"color:#d19a66;\">+10</span>, <span style=\"color:#7f848e;\">// touches same topic</span>\n <span style=\"color:#e5c07b;\">SECONDARY_DOMAIN_MATCH:</span> <span style=\"color:#d19a66;\">+10</span>, <span style=\"color:#7f848e;\">// touches same domain</span>\n<span style=\"color:#c678dd;\">};</span>\n</pre>\n\n<h3 style=\"font-family:sans-serif;color:#c678dd;margin:20px 0 10px 0;\">Calculation Algorithm</h3>\n<pre style=\"background:#2d2d2d;padding:16px;border-radius:6px;overflow-x:auto;\">\n<span style=\"color:#c678dd;\">function</span> <span style=\"color:#61afef;\">calculateGravity</span>(kontask, candidateFlows): FlowMatch[] {\n <span style=\"color:#7f848e;\">// 1. If explicit flowId, instant match</span>\n <span style=\"color:#c678dd;\">if</span> (kontask.flowId) {\n <span style=\"color:#c678dd;\">return</span> [{ flowId: kontask.flowId, score: <span style=\"color:#d19a66;\">50</span>, reason: <span style=\"color:#98c379;\">'explicit'</span> }];\n }\n\n <span style=\"color:#7f848e;\">// 2. Score each candidate flow</span>\n <span style=\"color:#c678dd;\">const</span> matches = candidateFlows.map(flow => {\n <span style=\"color:#c678dd;\">let</span> score = <span style=\"color:#d19a66;\">0</span>;\n <span style=\"color:#c678dd;\">const</span> reasons: string[] = [];\n\n <span style=\"color:#7f848e;\">// Decision dependency (check if kontask refs a decision in this flow)</span>\n <span style=\"color:#c678dd;\">if</span> (kontask.decisionId && flowHasDecision(flow, kontask.decisionId)) {\n score += WEIGHTS.DECISION_DEPENDENCY;\n reasons.push(<span style=\"color:#98c379;\">'decision-dep'</span>);\n }\n\n <span style=\"color:#7f848e;\">// Topic overlap</span>\n <span style=\"color:#c678dd;\">if</span> (kontask.primaryTopic === flow.primaryTopic) {\n score += WEIGHTS.PRIMARY_TOPIC_MATCH;\n reasons.push(<span style=\"color:#98c379;\">`topic:${kontask.primaryTopic}`</span>);\n } <span style=\"color:#c678dd;\">else if</span> (hasTopicOverlap(kontask, flow)) {\n score += WEIGHTS.SECONDARY_TOPIC_MATCH;\n reasons.push(<span style=\"color:#98c379;\">'topic-secondary'</span>);\n }\n\n <span style=\"color:#7f848e;\">// Domain overlap</span>\n <span style=\"color:#c678dd;\">if</span> (kontask.primaryDomain === flow.primaryDomain) {\n score += WEIGHTS.PRIMARY_DOMAIN_MATCH;\n reasons.push(<span style=\"color:#98c379;\">`domain:${kontask.primaryDomain}`</span>);\n } <span style=\"color:#c678dd;\">else if</span> (hasDomainOverlap(kontask, flow)) {\n score += WEIGHTS.SECONDARY_DOMAIN_MATCH;\n reasons.push(<span style=\"color:#98c379;\">'domain-secondary'</span>);\n }\n\n <span style=\"color:#7f848e;\">// Temporal proximity (based on flow's lastActivityAt)</span>\n <span style=\"color:#c678dd;\">const</span> hoursAgo = hoursSince(flow.lastActivityAt);\n <span style=\"color:#c678dd;\">if</span> (hoursAgo <= <span style=\"color:#d19a66;\">5</span>) {\n score += WEIGHTS.TEMPORAL_5H;\n reasons.push(<span style=\"color:#98c379;\">'temporal-5h'</span>);\n } <span style=\"color:#c678dd;\">else if</span> (hoursAgo <= <span style=\"color:#d19a66;\">24</span>) {\n score += WEIGHTS.TEMPORAL_24H;\n reasons.push(<span style=\"color:#98c379;\">'temporal-24h'</span>);\n } <span style=\"color:#c678dd;\">else if</span> (hoursAgo <= <span style=\"color:#d19a66;\">168</span>) { <span style=\"color:#7f848e;\">// 7 days</span>\n score += WEIGHTS.TEMPORAL_7D;\n reasons.push(<span style=\"color:#98c379;\">'temporal-7d'</span>);\n }\n\n <span style=\"color:#c678dd;\">return</span> { flowId: flow.id, score, reasons };\n });\n\n <span style=\"color:#7f848e;\">// 3. Filter by threshold and sort</span>\n <span style=\"color:#c678dd;\">return</span> matches\n .filter(m => m.score >= FLOW_MEMBERSHIP_THRESHOLD)\n .sort((a, b) => b.score - a.score);\n}\n</pre>\n\n<h3 style=\"font-family:sans-serif;color:#c678dd;margin:20px 0 10px 0;\">Examples</h3>\n<div style=\"background:#2d2d2d;padding:16px;border-radius:6px;\">\n<p style=\"margin:0 0 8px 0;\"><strong style=\"color:#98c379;\">✅ Joins flow (score ≥50):</strong></p>\n<ul style=\"margin:0 0 16px 0;padding-left:20px;color:#e0e0e0;\">\n<li>Explicit flowId: <span style=\"color:#d19a66;\">+50</span> = 50 ✓</li>\n<li>Primary topic + Primary domain: <span style=\"color:#d19a66;\">25+25</span> = 50 ✓</li>\n<li>Primary topic + Temporal 5h + Secondary domain: <span style=\"color:#d19a66;\">25+15+10</span> = 50 ✓</li>\n<li>Decision dependency + Temporal 24h: <span style=\"color:#d19a66;\">40+10</span> = 50 ✓</li>\n</ul>\n<p style=\"margin:0 0 8px 0;\"><strong style=\"color:#f43f5e;\">❌ Stays orphan (score <50):</strong></p>\n<ul style=\"margin:0;padding-left:20px;color:#e0e0e0;\">\n<li>Temporal 5h only: <span style=\"color:#d19a66;\">+15</span> = 15 ✗</li>\n<li>Secondary topic + Secondary domain: <span style=\"color:#d19a66;\">10+10</span> = 20 ✗</li>\n<li>Primary topic only: <span style=\"color:#d19a66;\">+25</span> = 25 ✗</li>\n<li>Primary topic + Temporal 7d: <span style=\"color:#d19a66;\">25+5</span> = 30 ✗</li>\n</ul>\n</div>\n\n<hr style=\"border:none;border-top:1px solid #3d3d3d;margin:20px 0;\">\n\n<h2 style=\"font-family:sans-serif;color:#61afef;margin:24px 0 12px 0;\">🔧 Where Gravity Lives: Konui vs Claude</h2>\n\n<div style=\"background:#2d2d2d;padding:16px;border-radius:6px;margin-bottom:20px;\">\n<table style=\"width:100%;border-collapse:collapse;color:#e0e0e0;\">\n<tr style=\"border-bottom:1px solid #3d3d3d;\">\n<th style=\"text-align:left;padding:8px;color:#61afef;\">Responsibility</th>\n<th style=\"text-align:left;padding:8px;color:#98c379;\">Konui (Server)</th>\n<th style=\"text-align:left;padding:8px;color:#e5c07b;\">Claude (In-Turn)</th>\n</tr>\n<tr style=\"border-bottom:1px solid #3d3d3d;\">\n<td style=\"padding:8px;\"><strong>Explicit linking</strong></td>\n<td style=\"padding:8px;\">—</td>\n<td style=\"padding:8px;\">✅ Provides <code style=\"background:#3d3d3d;padding:2px 6px;border-radius:4px;\">flowId</code> in kontask creation</td>\n</tr>\n<tr style=\"border-bottom:1px solid #3d3d3d;\">\n<td style=\"padding:8px;\"><strong>Topic classification</strong></td>\n<td style=\"padding:8px;\">✅ <code style=\"background:#3d3d3d;padding:2px 6px;border-radius:4px;\">classifyText()</code> in gv-topic-store</td>\n<td style=\"padding:8px;\">—</td>\n</tr>\n<tr style=\"border-bottom:1px solid #3d3d3d;\">\n<td style=\"padding:8px;\"><strong>Domain classification</strong></td>\n<td style=\"padding:8px;\">✅ <code style=\"background:#3d3d3d;padding:2px 6px;border-radius:4px;\">classifyDomain()</code> in gv-domain-store</td>\n<td style=\"padding:8px;\">—</td>\n</tr>\n<tr style=\"border-bottom:1px solid #3d3d3d;\">\n<td style=\"padding:8px;\"><strong>Temporal proximity</strong></td>\n<td style=\"padding:8px;\">✅ Compare timestamps</td>\n<td style=\"padding:8px;\">—</td>\n</tr>\n<tr style=\"border-bottom:1px solid #3d3d3d;\">\n<td style=\"padding:8px;\"><strong>Decision dependency</strong></td>\n<td style=\"padding:8px;\">✅ Check edge graph</td>\n<td style=\"padding:8px;\">May provide decisionId hint</td>\n</tr>\n<tr style=\"border-bottom:1px solid #3d3d3d;\">\n<td style=\"padding:8px;\"><strong>Gravity calculation</strong></td>\n<td style=\"padding:8px;\">✅ Run algorithm on kontask creation</td>\n<td style=\"padding:8px;\">—</td>\n</tr>\n<tr>\n<td style=\"padding:8px;\"><strong>Flow curation</strong></td>\n<td style=\"padding:8px;\">Stores edges, provides suggestions</td>\n<td style=\"padding:8px;\">✅ Reviews orphan pool, merges flows</td>\n</tr>\n</table>\n</div>\n\n<h3 style=\"font-family:sans-serif;color:#c678dd;margin:20px 0 10px 0;\">Key Design Decisions</h3>\n<ul style=\"margin:0;padding-left:20px;color:#e0e0e0;line-height:2;\">\n<li><strong style=\"color:#98c379;\">Konui runs gravity</strong> — All scoring happens server-side at kontask creation time</li>\n<li><strong style=\"color:#98c379;\">Claude provides hints</strong> — Can specify <code style=\"background:#3d3d3d;padding:2px 6px;border-radius:4px;\">flowId</code> for instant inclusion, or <code style=\"background:#3d3d3d;padding:2px 6px;border-radius:4px;\">decisionId</code> for dependency signal</li>\n<li><strong style=\"color:#98c379;\">Orphan pool for curation</strong> — Kontasks below threshold stay orphans; Claude can review and link later</li>\n<li><strong style=\"color:#98c379;\">No session binding</strong> — Sessions are invisible to flow membership; removed entirely</li>\n</ul>\n\n<hr style=\"border:none;border-top:1px solid #3d3d3d;margin:20px 0;\">\n\n<h2 style=\"font-family:sans-serif;color:#61afef;margin:24px 0 12px 0;\">🗑️ Code to Remove (Session-Based Flow Logic)</h2>\n\n<h3 style=\"font-family:sans-serif;color:#c678dd;margin:20px 0 10px 0;\">gv-flow-store.ts — Functions to DELETE</h3>\n<pre style=\"background:#2d2d2d;padding:16px;border-radius:6px;overflow-x:auto;\">\n<span style=\"color:#f43f5e;\">// ❌ DELETE: Session binding (lines ~180-220)</span>\n<span style=\"color:#7f848e;\">linkSessionToFlow(sessionId, flowId, role)</span>\n<span style=\"color:#7f848e;\">unlinkSessionFromFlow(sessionId, flowId)</span>\n<span style=\"color:#7f848e;\">getSessionsByFlow(flowId)</span>\n<span style=\"color:#7f848e;\">getFlowsBySession(sessionId)</span>\n\n<span style=\"color:#f43f5e;\">// ❌ DELETE: Emergent flow from session (lines ~430-500)</span>\n<span style=\"color:#7f848e;\">getOrCreateFlowForSession(sessionId, prompt)</span>\n<span style=\"color:#7f848e;\">getFlowIdForSession(sessionId)</span> <span style=\"color:#7f848e;\">// Called from router.ts:704</span>\n\n<span style=\"color:#f43f5e;\">// ❌ DELETE: Simple keyword matching (lines ~550-600)</span>\n<span style=\"color:#7f848e;\">suggestMatchingFlows(prompt)</span> <span style=\"color:#7f848e;\">// Replace with gravity calculator</span>\n</pre>\n\n<h3 style=\"font-family:sans-serif;color:#c678dd;margin:20px 0 10px 0;\">gv-flow-types.ts — Types to REMOVE</h3>\n<pre style=\"background:#2d2d2d;padding:16px;border-radius:6px;overflow-x:auto;\">\n<span style=\"color:#f43f5e;\">// ❌ REMOVE: Session binding types</span>\n<span style=\"color:#7f848e;\">SessionFlowLink</span>\n<span style=\"color:#7f848e;\">FlowSessionRole</span> <span style=\"color:#7f848e;\">// 'owner' | 'contributor'</span>\n\n<span style=\"color:#f43f5e;\">// ❌ REMOVE from GvFlow interface:</span>\n<span style=\"color:#7f848e;\">sessionIds: string[]</span> <span style=\"color:#7f848e;\">// Denormalized session list</span>\n<span style=\"color:#7f848e;\">ownerSessionId?: string</span> <span style=\"color:#7f848e;\">// Session that started flow</span>\n</pre>\n\n<h3 style=\"font-family:sans-serif;color:#c678dd;margin:20px 0 10px 0;\">gv-edge-store.ts — Edge Types to REMOVE</h3>\n<pre style=\"background:#2d2d2d;padding:16px;border-radius:6px;overflow-x:auto;\">\n<span style=\"color:#f43f5e;\">// ❌ REMOVE: Session-related edges</span>\n<span style=\"color:#7f848e;\">createSessionFlowEdge(sessionId, flowId, role)</span>\n<span style=\"color:#7f848e;\">removeSessionFlowEdge(sessionId, flowId)</span>\n\n<span style=\"color:#f43f5e;\">// ❌ REMOVE from EdgeIndex:</span>\n<span style=\"color:#7f848e;\">sessionToFlows: Map<string, string[]></span>\n<span style=\"color:#7f848e;\">flowToSessions: Map<string, string[]></span>\n</pre>\n\n<h3 style=\"font-family:sans-serif;color:#c678dd;margin:20px 0 10px 0;\">router.ts — Logic to REPLACE (lines 700-750)</h3>\n<pre style=\"background:#2d2d2d;padding:16px;border-radius:6px;overflow-x:auto;\">\n<span style=\"color:#f43f5e;\">// ❌ REMOVE: Session-based flow lookup</span>\n<span style=\"color:#7f848e;\">if (!resolvedFlowId && body.sessionId) {</span>\n<span style=\"color:#7f848e;\"> resolvedFlowId = await gvFlowStore.getFlowIdForSession(body.sessionId);</span>\n<span style=\"color:#7f848e;\">}</span>\n\n<span style=\"color:#98c379;\">// ✅ REPLACE WITH: Gravity calculation</span>\n<span style=\"color:#7f848e;\">const gravityMatches = await calculateGravity(kontask, activeFlows);</span>\n<span style=\"color:#7f848e;\">if (gravityMatches.length > 0) {</span>\n<span style=\"color:#7f848e;\"> resolvedFlowId = gravityMatches[0].flowId;</span>\n<span style=\"color:#7f848e;\">} else {</span>\n<span style=\"color:#7f848e;\"> // Kontask stays orphan</span>\n<span style=\"color:#7f848e;\">}</span>\n</pre>\n\n<hr style=\"border:none;border-top:1px solid #3d3d3d;margin:20px 0;\">\n\n<h2 style=\"font-family:sans-serif;color:#61afef;margin:24px 0 12px 0;\">🆕 New Components to BUILD</h2>\n\n<h3 style=\"font-family:sans-serif;color:#c678dd;margin:20px 0 10px 0;\">1. gv-gravity.ts — The Gravity Calculator</h3>\n<pre style=\"background:#2d2d2d;padding:16px;border-radius:6px;overflow-x:auto;\">\n<span style=\"color:#7f848e;\">/**</span>\n<span style=\"color:#7f848e;\"> * Gravity calculator for flow membership.</span>\n<span style=\"color:#7f848e;\"> * Evaluates kontask signals against candidate flows.</span>\n<span style=\"color:#7f848e;\"> */</span>\n\n<span style=\"color:#c678dd;\">export interface</span> GravityInput {\n kontaskId: string;\n title: string;\n description?: string;\n flowId?: string; <span style=\"color:#7f848e;\">// Explicit link (instant join)</span>\n decisionId?: string; <span style=\"color:#7f848e;\">// Decision dependency hint</span>\n createdAt: string;\n}\n\n<span style=\"color:#c678dd;\">export interface</span> GravityMatch {\n flowId: string;\n score: number;\n reasons: string[]; <span style=\"color:#7f848e;\">// ['topic:bugfix', 'temporal-5h', ...]</span>\n meetsThreshold: boolean;\n}\n\n<span style=\"color:#c678dd;\">export function</span> calculateGravity(input: GravityInput): Promise<GravityMatch[]>\n<span style=\"color:#c678dd;\">export function</span> getActiveFlowsForGravity(): Promise<GvFlow[]>\n<span style=\"color:#c678dd;\">export function</span> classifyKontask(title: string, description?: string): {\n topics: TopicMatch[];\n domains: DomainMatch[];\n}\n</pre>\n\n<h3 style=\"font-family:sans-serif;color:#c678dd;margin:20px 0 10px 0;\">2. Orphan Pool Management</h3>\n<pre style=\"background:#2d2d2d;padding:16px;border-radius:6px;overflow-x:auto;\">\n<span style=\"color:#7f848e;\">// MCP tools for orphan curation</span>\n<span style=\"color:#e5c07b;\">konui_list_orphan_kontasks</span> <span style=\"color:#7f848e;\">// Get kontasks not in any flow</span>\n<span style=\"color:#e5c07b;\">konui_get_gravity_suggestions</span> <span style=\"color:#7f848e;\">// Get borderline matches (score 30-49)</span>\n<span style=\"color:#e5c07b;\">konui_link_orphan_to_flow</span> <span style=\"color:#7f848e;\">// Manual curation by Claude</span>\n</pre>\n\n<h3 style=\"font-family:sans-serif;color:#c678dd;margin:20px 0 10px 0;\">3. Enhanced Kontask Creation</h3>\n<pre style=\"background:#2d2d2d;padding:16px;border-radius:6px;overflow-x:auto;\">\n<span style=\"color:#7f848e;\">// Add to kontask creation in router.ts</span>\n\n<span style=\"color:#c678dd;\">async function</span> handleCreateKontask(body) {\n <span style=\"color:#7f848e;\">// 1. Classify kontask text</span>\n <span style=\"color:#c678dd;\">const</span> { topics, domains } = classifyKontask(body.title, body.description);\n \n <span style=\"color:#7f848e;\">// 2. Store classification on kontask</span>\n body.primaryTopic = topics[<span style=\"color:#d19a66;\">0</span>]?.topicId;\n body.primaryDomain = domains[<span style=\"color:#d19a66;\">0</span>]?.domainId;\n \n <span style=\"color:#7f848e;\">// 3. Create kontask</span>\n <span style=\"color:#c678dd;\">const</span> kontask = kontaskStore.createKontask(body);\n \n <span style=\"color:#7f848e;\">// 4. Run gravity calculator</span>\n <span style=\"color:#c678dd;\">const</span> matches = <span style=\"color:#c678dd;\">await</span> calculateGravity({\n kontaskId: kontask.id,\n title: body.title,\n description: body.description,\n flowId: body.flowId,\n decisionId: body.decisionId,\n createdAt: kontask.createdAt,\n });\n \n <span style=\"color:#7f848e;\">// 5. Link to best flow if threshold met</span>\n <span style=\"color:#c678dd;\">if</span> (matches[<span style=\"color:#d19a66;\">0</span>]?.meetsThreshold) {\n <span style=\"color:#c678dd;\">await</span> linkKontaskToFlow(kontask.id, matches[<span style=\"color:#d19a66;\">0</span>].flowId, <span style=\"color:#98c379;\">'evidence'</span>);\n }\n <span style=\"color:#7f848e;\">// Otherwise: kontask stays orphan</span>\n \n <span style=\"color:#c678dd;\">return</span> kontask;\n}\n</pre>\n\n<hr style=\"border:none;border-top:1px solid #3d3d3d;margin:20px 0;\">\n\n<h2 style=\"font-family:sans-serif;color:#61afef;margin:24px 0 12px 0;\">📐 Schema Integration</h2>\n\n<div style=\"background:#2d2d2d;padding:16px;border-radius:6px;margin-bottom:20px;\">\n<p style=\"margin:0 0 12px 0;\">The gravity model integrates with existing schemas:</p>\n<table style=\"width:100%;border-collapse:collapse;color:#e0e0e0;\">\n<tr style=\"border-bottom:1px solid #3d3d3d;\">\n<th style=\"text-align:left;padding:8px;color:#61afef;\">Schema</th>\n<th style=\"text-align:left;padding:8px;color:#98c379;\">Used For</th>\n<th style=\"text-align:left;padding:8px;color:#e5c07b;\">Signal Weight</th>\n</tr>\n<tr style=\"border-bottom:1px solid #3d3d3d;\">\n<td style=\"padding:8px;\"><code style=\"background:#3d3d3d;padding:2px 6px;border-radius:4px;\">gvTopicSchema</code></td>\n<td style=\"padding:8px;\">12 lifecycle topics (WHAT phase)</td>\n<td style=\"padding:8px;\">Primary +25, Secondary +10</td>\n</tr>\n<tr style=\"border-bottom:1px solid #3d3d3d;\">\n<td style=\"padding:8px;\"><code style=\"background:#3d3d3d;padding:2px 6px;border-radius:4px;\">gvDomainSchema</code></td>\n<td style=\"padding:8px;\">12 tech domains (WHERE in stack)</td>\n<td style=\"padding:8px;\">Primary +25, Secondary +10</td>\n</tr>\n<tr style=\"border-bottom:1px solid #3d3d3d;\">\n<td style=\"padding:8px;\"><code style=\"background:#3d3d3d;padding:2px 6px;border-radius:4px;\">gvDecisionSchema</code></td>\n<td style=\"padding:8px;\">Design/action/deferred decisions</td>\n<td style=\"padding:8px;\">Dependency +40</td>\n</tr>\n<tr style=\"border-bottom:1px solid #3d3d3d;\">\n<td style=\"padding:8px;\"><code style=\"background:#3d3d3d;padding:2px 6px;border-radius:4px;\">gvGraphSchema</code></td>\n<td style=\"padding:8px;\">Edge relationships between entities</td>\n<td style=\"padding:8px;\">Stores kontask→flow links</td>\n</tr>\n<tr>\n<td style=\"padding:8px;\"><code style=\"background:#3d3d3d;padding:2px 6px;border-radius:4px;\">gvFlowSchema</code></td>\n<td style=\"padding:8px;\">Flow lifecycle (active/completed/blocked)</td>\n<td style=\"padding:8px;\">Temporal via lastActivityAt</td>\n</tr>\n</table>\n</div>\n\n<h3 style=\"font-family:sans-serif;color:#c678dd;margin:20px 0 10px 0;\">Flow Data Model Changes</h3>\n<pre style=\"background:#2d2d2d;padding:16px;border-radius:6px;overflow-x:auto;\">\n<span style=\"color:#c678dd;\">interface</span> GvFlow {\n <span style=\"color:#7f848e;\">// KEEP: Core identity</span>\n id: string;\n title: string;\n type: FlowType;\n status: FlowStatus;\n priority: FlowPriority;\n \n <span style=\"color:#7f848e;\">// KEEP: Timestamps for temporal signals</span>\n createdAt: string;\n updatedAt: string;\n lastActivityAt: string; <span style=\"color:#7f848e;\">// Used for temporal proximity</span>\n \n <span style=\"color:#7f848e;\">// KEEP: Classification (for gravity matching)</span>\n primaryTopic?: string; <span style=\"color:#7f848e;\">// Auto-classified from title</span>\n primaryDomain?: string; <span style=\"color:#7f848e;\">// Auto-classified from title</span>\n \n <span style=\"color:#7f848e;\">// KEEP: Denormalized kontask/decision counts</span>\n kontaskIds: string[]; <span style=\"color:#7f848e;\">// Linked kontasks</span>\n decisionIds: string[]; <span style=\"color:#7f848e;\">// Decisions made in flow</span>\n \n <span style=\"color:#f43f5e;\">// ❌ REMOVE: Session binding</span>\n <span style=\"color:#f43f5e;\">// sessionIds: string[];</span>\n <span style=\"color:#f43f5e;\">// ownerSessionId?: string;</span>\n}\n</pre>\n\n<hr style=\"border:none;border-top:1px solid #3d3d3d;margin:20px 0;\">\n\n<h2 style=\"font-family:sans-serif;color:#61afef;margin:24px 0 12px 0;\">🗂️ File Summary</h2>\n\n<pre style=\"background:#2d2d2d;padding:16px;border-radius:6px;overflow-x:auto;line-height:1.8;\">\n<span style=\"color:#56b6c2;font-weight:bold;\">/konnectvol/konui/src/</span>\n├── <span style=\"color:#f43f5e;font-weight:bold;\">gv-flow-store.ts</span> <span style=\"color:#7f848e;\"># MAJOR REFACTOR: Remove session binding, keep flow CRUD</span>\n├── <span style=\"color:#f43f5e;font-weight:bold;\">gv-flow-types.ts</span> <span style=\"color:#7f848e;\"># MODIFY: Remove session types, add primaryTopic/Domain</span>\n├── <span style=\"color:#f43f5e;font-weight:bold;\">gv-edge-store.ts</span> <span style=\"color:#7f848e;\"># MODIFY: Remove session-flow edges</span>\n├── <span style=\"color:#f43f5e;font-weight:bold;\">router.ts</span> <span style=\"color:#7f848e;\"># MODIFY: Replace session lookup with gravity</span>\n├── <span style=\"color:#98c379;font-weight:bold;\">gv-gravity.ts</span> <span style=\"color:#7f848e;\"># NEW: Gravity calculator</span>\n├── <span style=\"color:#98c379;font-weight:bold;\">gv-orphan.ts</span> <span style=\"color:#7f848e;\"># NEW: Orphan pool management</span>\n├── <span style=\"color:#e5c07b;\">gv-topic-store.ts</span> <span style=\"color:#7f848e;\"># KEEP: Already has classifyText()</span>\n├── <span style=\"color:#e5c07b;\">gv-domain-store.ts</span> <span style=\"color:#7f848e;\"># KEEP: Already has classifyDomain()</span>\n├── <span style=\"color:#e5c07b;\">kontask-store.ts</span> <span style=\"color:#7f848e;\"># KEEP: Core kontask CRUD</span>\n└── <span style=\"color:#e5c07b;\">kontask-types.ts</span> <span style=\"color:#7f848e;\"># MODIFY: Add primaryTopic/primaryDomain fields</span>\n\n<span style=\"color:#56b6c2;font-weight:bold;\">/konnectvol/gv/docs/</span>\n├── <span style=\"color:#f43f5e;font-weight:bold;\">gvFlowSchema.md</span> <span style=\"color:#7f848e;\"># UPDATE: Remove session sections</span>\n├── <span style=\"color:#98c379;font-weight:bold;\">gvGravitySchema.md</span> <span style=\"color:#7f848e;\"># NEW: Document gravity algorithm</span>\n└── <span style=\"color:#e5c07b;\">gvGraphSchema.md</span> <span style=\"color:#7f848e;\"># UPDATE: Note session edges deprecated</span>\n</pre>\n\n<hr style=\"border:none;border-top:1px solid #3d3d3d;margin:20px 0;\">\n\n<h2 style=\"font-family:sans-serif;color:#61afef;margin:24px 0 12px 0;\">🚀 Implementation Phases</h2>\n\n<div style=\"background:#2d2d2d;padding:16px;border-radius:6px;\">\n<p style=\"margin:0 0 12px 0;\"><strong style=\"color:#d19a66;\">Phase 1:</strong> <span style=\"color:#98c379;\">Create gv-gravity.ts</span></p>\n<ul style=\"margin:0 0 16px 0;padding-left:20px;color:#e0e0e0;\">\n<li>Implement gravity calculator with signal weights</li>\n<li>Add classifyKontask() wrapper for topic+domain</li>\n<li>Write comprehensive tests</li>\n</ul>\n\n<p style=\"margin:0 0 12px 0;\"><strong style=\"color:#d19a66;\">Phase 2:</strong> <span style=\"color:#98c379;\">Integrate into router.ts</span></p>\n<ul style=\"margin:0 0 16px 0;padding-left:20px;color:#e0e0e0;\">\n<li>Replace session-based flow lookup with gravity</li>\n<li>Add topic/domain classification at kontask creation</li>\n<li>Store primaryTopic/primaryDomain on kontasks</li>\n</ul>\n\n<p style=\"margin:0 0 12px 0;\"><strong style=\"color:#d19a66;\">Phase 3:</strong> <span style=\"color:#98c379;\">Remove session-flow binding</span></p>\n<ul style=\"margin:0 0 16px 0;padding-left:20px;color:#e0e0e0;\">\n<li>Delete session binding functions from gv-flow-store</li>\n<li>Remove session-flow edge types</li>\n<li>Clean up GvFlow interface</li>\n</ul>\n\n<p style=\"margin:0 0 12px 0;\"><strong style=\"color:#d19a66;\">Phase 4:</strong> <span style=\"color:#98c379;\">Orphan pool + curation MCP tools</span></p>\n<ul style=\"margin:0;padding-left:20px;color:#e0e0e0;\">\n<li>Add konui_list_orphan_kontasks</li>\n<li>Add konui_get_gravity_suggestions</li>\n<li>Add konui_link_orphan_to_flow</li>\n</ul>\n</div>\n\n<hr style=\"border:none;border-top:1px solid #3d3d3d;margin:20px 0;\">\n\n<h2 style=\"font-family:sans-serif;color:#61afef;margin:24px 0 12px 0;\">📊 Expected Outcomes</h2>\n\n<div style=\"background:#2d2d2d;padding:16px;border-radius:6px;\">\n<ul style=\"margin:0;padding-left:20px;color:#e0e0e0;line-height:2;\">\n<li><strong style=\"color:#98c379;\">Cleaner flows</strong> — Only meaningful work joins flows; noise stays orphaned</li>\n<li><strong style=\"color:#98c379;\">Sessions irrelevant</strong> — Session boundaries don't affect flow membership</li>\n<li><strong style=\"color:#98c379;\">Semantic grouping</strong> — Topics and domains drive membership, not timing alone</li>\n<li><strong style=\"color:#98c379;\">Decision chains</strong> — Related decisions naturally group together</li>\n<li><strong style=\"color:#98c379;\">Claude curation</strong> — Borderline cases can be manually reviewed</li>\n<li><strong style=\"color:#98c379;\">Transparency</strong> — Each link shows why it joined (score + reasons)</li>\n</ul>\n</div>\n\n</div>",
"choices": [
{
"label": "Approve design",
"value": "The gravity model design looks good. Proceed with Phase 1: implement gv-gravity.ts with the signal weights and gravity calculator.",
"primary": true
},
{
"label": "Adjust weights",
"value": "I want to discuss the signal weights. Let's review the threshold and individual weights before proceeding."
},
{
"label": "Write schema doc",
"value": "Before implementing, create gvGravitySchema.md in /gv/docs/ to formally document the gravity algorithm and flow membership model."
},
{
"label": "Show code details",
"value": "Show me the specific code that needs to be removed from gv-flow-store.ts with exact line numbers and function signatures."
}
],
"requestId": "ab4888f4-477c-425e-aca7-3d36aff5b2da",
"turnTiming": {
"totalMs": 274580,
"ttfeMs": 53,
"thinkingMs": 274527,
"toolExecutionMs": 0,
"toolCallCount": 0,
"thinkingPct": 100,
"toolsPct": 0,
"ttfePct": 0
}
},
"createdBy": "claude",
"createdAt": "2026-01-10T12:26:31.926Z",
"updatedAt": "2026-01-10T12:26:43.838Z",
"requestId": "ab4888f4-477c-425e-aca7-3d36aff5b2da",
"scope": "greatvibe",
"tags": [
"flows",
"design",
"architecture"
],
"sessionId": "ses_7c4eba4a-596",
"flowId": "flow_01KEHQ5A6Y7SHWCMHHZYGBC592",
"flowLinks": [
{
"flowId": "flow_01KEHQ5A6Y7SHWCMHHZYGBC592",
"edgeType": "evidence",
"createdAt": "2026-01-10T12:26:31.926Z"
}
]
}