← Back to Kontasks
Where Do Decisions Live? Data Model Analysis
Analyzing whether decisions should be top-level objects or embedded in flows
Output
Where Do Decisions Live?
Key Insight
Decisions have multiple access patterns - that's the clue to where they belong.
Access Patterns
1. "What decisions in this flow?" Need: Filter by flowId Speed: Fast (flow context) 2. "All architecture decisions across flows" Need: Filter by type, cross-flow Speed: Fast (doc generation) 3. "Decisions I might want to pivot" Need: Filter by madeBy=claude, status=made Speed: Fast (review workflow) 4. "What decision led to this code?" Need: Lookup by affectedFile Speed: Fast (traceability) 5. "All decisions from today's work" Need: Filter by date range Speed: Moderate (reporting)
Three Options
Option A: Embedded in Flows Only
gvFlow { decisions: gvDecision[] // Embedded array }
✓ Simple, co-located
✗ Cross-flow queries slow (scan all flows)
✗ Can't index by type/file efficiently
Option B: Top-Level gvDecisions (Recommended)
gvDecision { // Own JSONL file flowId: string // FK to flow turnId: string // FK to turn } gvFlow { decisionIds: string[] // References only }
✓ Fast cross-flow queries
✓ Index by type, file, date
✓ Scales independently
△ Extra join for flow context
Option C: Hybrid (Denormalized)
gvDecision { // Own JSONL (source of truth) flowId, turnId, ... } gvFlow { decisions: gvDecisionSummary[] // Denormalized } // id, title, type, status only
✓ Best of both worlds
✓ Fast flow-scoped reads
✓ Fast cross-flow queries
△ Must keep in sync
Recommended: Option B (Top-Level)
Storage: /data/gvflows.jsonl // Flows /data/gvturns.jsonl // Turns (née kontasks) /data/gvdecisions.jsonl // Decisions Relationships: gvDecision.flowId → gvFlow.id gvDecision.turnId → gvTurn.id gvFlow.id ← gvDecision[] (via query)
MCP Tool Design
// Record decision during turn gvHub_record_decision({ flowId: "flow-123", title: "Use JSONL for storage", type: "architecture", chosen: "JSONL append-only files", alternatives: ["SQLite", "PostgreSQL"], rationale: "Consistent with existing patterns", impact: "medium" }) // Query decisions (fast, indexed) gvHub_list_decisions({ flowId?: string, // Filter by flow type?: string, // Filter by type madeBy?: string, // claude | human status?: string, // made | reviewed | pivoted since?: Date // Date filter }) // Get flow with decisions in one call gvHub_get_flow({ flowId: "flow-123", includeDecisions: true // Joins automatically })
The Full Object Graph
gvFlow Top-level work container │ ├── gvSession[] Claude process instances │ └── gvTurn[] Prompt/response pairs │ └── gvDecision[] Key choices (own storage) └── turnId Links back to turn Storage files: gvflows.jsonl ── gvFlow gvsessions.jsonl ── gvSession gvturns.jsonl ── gvTurn (was kontasks.jsonl) gvdecisions.jsonl ── gvDecision
Why Not in Turns?
You identified the problem exactly:
If decisions only live in turns: • Must read all turns to find decisions • MCP tool: slow (scan kontasks.jsonl) • API: slow (parse HTML output?) • Cross-flow: very slow (scan all flows, all turns) With top-level gvDecisions: • Direct lookup by flowId: O(n) on decisions • Index by type: instant • Cross-flow by date: instant • Turn still has decisionIds for context
Decision Summary
gvDecisions should be top-level objects stored in their own JSONL file, with foreign keys to flows and turns.
This gives you:
- Fast queries across all flows
- Indexable by type, maker, status, date
- Independent scaling (decisions grow faster than flows)
- Clean separation for doc generation
Quick Actions
Original Request
No layout configured
Details
Type
General
Status
Completed
Scope
vibetools
Tags
gvshellgvhubdecisionsdata-modelarchitecture
Created
9 Jan 2026, 1:17 pm
Updated
9 Jan 2026, 1:17 pm
Timing
⏱️ 63.6s
0 tools
● TTFE 0%
● Thinking 100%
● Tools 0%
Created By
claude
Raw Data
{
"id": "2d6e2f7b-c7e2-457c-a303-84db80eaf189",
"type": "general",
"status": "completed",
"title": "Where Do Decisions Live? Data Model Analysis",
"description": "Analyzing whether decisions should be top-level objects or embedded in flows",
"context": {
"output": "<div style=\"background:#1e1e1e;color:#e0e0e0;padding:20px;border-radius:8px;font-family:'Consolas','Monaco',monospace;\">\n<h2 style=\"font-family:sans-serif;color:#fff;margin:0 0 20px 0;\">Where Do Decisions Live?</h2>\n\n<div style=\"background:#2d2d2d;padding:16px;border-radius:6px;margin-bottom:20px;border-left:4px solid #61afef;\">\n<p style=\"margin:0;color:#61afef;font-weight:bold;\">Key Insight</p>\n<p style=\"margin:8px 0 0 0;color:#e0e0e0;\">Decisions have multiple access patterns - that's the clue to where they belong.</p>\n</div>\n\n<h3 style=\"font-family:sans-serif;color:#fff;margin:0 0 12px 0;\">Access Patterns</h3>\n\n<div style=\"background:#2d2d2d;padding:16px;border-radius:6px;margin-bottom:16px;\">\n<pre style=\"margin:0;line-height:1.8;color:#e0e0e0;font-size:13px;\">\n<span style=\"color:#7f848e;\">1.</span> <span style=\"color:#c678dd;\">\"What decisions in this flow?\"</span>\n Need: Filter by flowId\n Speed: Fast (flow context)\n\n<span style=\"color:#7f848e;\">2.</span> <span style=\"color:#61afef;\">\"All architecture decisions across flows\"</span>\n Need: Filter by type, cross-flow\n Speed: Fast (doc generation)\n\n<span style=\"color:#7f848e;\">3.</span> <span style=\"color:#e5c07b;\">\"Decisions I might want to pivot\"</span>\n Need: Filter by madeBy=claude, status=made\n Speed: Fast (review workflow)\n\n<span style=\"color:#7f848e;\">4.</span> <span style=\"color:#22c55e;\">\"What decision led to this code?\"</span>\n Need: Lookup by affectedFile\n Speed: Fast (traceability)\n\n<span style=\"color:#7f848e;\">5.</span> <span style=\"color:#f43f5e;\">\"All decisions from today's work\"</span>\n Need: Filter by date range\n Speed: Moderate (reporting)\n</pre>\n</div>\n\n<h3 style=\"font-family:sans-serif;color:#fff;margin:0 0 12px 0;\">Three Options</h3>\n\n<div style=\"display:grid;gap:16px;margin-bottom:20px;\">\n\n<!-- Option A -->\n<div style=\"background:#2d2d2d;padding:16px;border-radius:6px;border-left:4px solid #f43f5e;\">\n<p style=\"margin:0 0 12px 0;color:#f43f5e;font-weight:bold;\">Option A: Embedded in Flows Only</p>\n<pre style=\"margin:0;line-height:1.6;color:#e0e0e0;font-size:12px;background:#1e1e1e;padding:12px;border-radius:4px;\">\n<span style=\"color:#c678dd;\">gvFlow</span> {\n decisions: <span style=\"color:#e5c07b;\">gvDecision</span>[] <span style=\"color:#7f848e;\">// Embedded array</span>\n}\n</pre>\n<p style=\"margin:12px 0 0 0;color:#22c55e;\">✓ Simple, co-located</p>\n<p style=\"margin:4px 0 0 0;color:#f43f5e;\">✗ Cross-flow queries slow (scan all flows)</p>\n<p style=\"margin:4px 0 0 0;color:#f43f5e;\">✗ Can't index by type/file efficiently</p>\n</div>\n\n<!-- Option B -->\n<div style=\"background:#2d2d2d;padding:16px;border-radius:6px;border-left:4px solid #22c55e;\">\n<p style=\"margin:0 0 12px 0;color:#22c55e;font-weight:bold;\">Option B: Top-Level gvDecisions (Recommended)</p>\n<pre style=\"margin:0;line-height:1.6;color:#e0e0e0;font-size:12px;background:#1e1e1e;padding:12px;border-radius:4px;\">\n<span style=\"color:#c678dd;\">gvDecision</span> { <span style=\"color:#7f848e;\">// Own JSONL file</span>\n flowId: <span style=\"color:#98c379;\">string</span> <span style=\"color:#7f848e;\">// FK to flow</span>\n turnId: <span style=\"color:#98c379;\">string</span> <span style=\"color:#7f848e;\">// FK to turn</span>\n}\n\n<span style=\"color:#c678dd;\">gvFlow</span> {\n decisionIds: <span style=\"color:#98c379;\">string</span>[] <span style=\"color:#7f848e;\">// References only</span>\n}\n</pre>\n<p style=\"margin:12px 0 0 0;color:#22c55e;\">✓ Fast cross-flow queries</p>\n<p style=\"margin:4px 0 0 0;color:#22c55e;\">✓ Index by type, file, date</p>\n<p style=\"margin:4px 0 0 0;color:#22c55e;\">✓ Scales independently</p>\n<p style=\"margin:4px 0 0 0;color:#e5c07b;\">△ Extra join for flow context</p>\n</div>\n\n<!-- Option C -->\n<div style=\"background:#2d2d2d;padding:16px;border-radius:6px;border-left:4px solid #e5c07b;\">\n<p style=\"margin:0 0 12px 0;color:#e5c07b;font-weight:bold;\">Option C: Hybrid (Denormalized)</p>\n<pre style=\"margin:0;line-height:1.6;color:#e0e0e0;font-size:12px;background:#1e1e1e;padding:12px;border-radius:4px;\">\n<span style=\"color:#c678dd;\">gvDecision</span> { <span style=\"color:#7f848e;\">// Own JSONL (source of truth)</span>\n flowId, turnId, ...\n}\n\n<span style=\"color:#c678dd;\">gvFlow</span> {\n decisions: <span style=\"color:#e5c07b;\">gvDecisionSummary</span>[] <span style=\"color:#7f848e;\">// Denormalized</span>\n} <span style=\"color:#7f848e;\">// id, title, type, status only</span>\n</pre>\n<p style=\"margin:12px 0 0 0;color:#22c55e;\">✓ Best of both worlds</p>\n<p style=\"margin:4px 0 0 0;color:#22c55e;\">✓ Fast flow-scoped reads</p>\n<p style=\"margin:4px 0 0 0;color:#22c55e;\">✓ Fast cross-flow queries</p>\n<p style=\"margin:4px 0 0 0;color:#e5c07b;\">△ Must keep in sync</p>\n</div>\n\n</div>\n\n<h3 style=\"font-family:sans-serif;color:#fff;margin:0 0 12px 0;\">Recommended: Option B (Top-Level)</h3>\n\n<div style=\"background:#2d2d2d;padding:16px;border-radius:6px;margin-bottom:16px;\">\n<pre style=\"margin:0;line-height:1.8;color:#e0e0e0;font-size:13px;\">\n<span style=\"color:#c678dd;\">Storage:</span>\n /data/gvflows.jsonl <span style=\"color:#7f848e;\">// Flows</span>\n /data/gvturns.jsonl <span style=\"color:#7f848e;\">// Turns (née kontasks)</span>\n /data/gvdecisions.jsonl <span style=\"color:#7f848e;\">// Decisions</span>\n\n<span style=\"color:#c678dd;\">Relationships:</span>\n gvDecision.flowId → gvFlow.id\n gvDecision.turnId → gvTurn.id\n gvFlow.id ← gvDecision[] (via query)\n</pre>\n</div>\n\n<h3 style=\"font-family:sans-serif;color:#fff;margin:0 0 12px 0;\">MCP Tool Design</h3>\n\n<div style=\"background:#2d2d2d;padding:16px;border-radius:6px;margin-bottom:16px;\">\n<pre style=\"margin:0;line-height:1.6;color:#e0e0e0;font-size:12px;background:#1e1e1e;padding:12px;border-radius:4px;\">\n<span style=\"color:#7f848e;\">// Record decision during turn</span>\n<span style=\"color:#61afef;\">gvHub_record_decision</span>({\n flowId: <span style=\"color:#98c379;\">\"flow-123\"</span>,\n title: <span style=\"color:#98c379;\">\"Use JSONL for storage\"</span>,\n type: <span style=\"color:#98c379;\">\"architecture\"</span>,\n chosen: <span style=\"color:#98c379;\">\"JSONL append-only files\"</span>,\n alternatives: [<span style=\"color:#98c379;\">\"SQLite\"</span>, <span style=\"color:#98c379;\">\"PostgreSQL\"</span>],\n rationale: <span style=\"color:#98c379;\">\"Consistent with existing patterns\"</span>,\n impact: <span style=\"color:#98c379;\">\"medium\"</span>\n})\n\n<span style=\"color:#7f848e;\">// Query decisions (fast, indexed)</span>\n<span style=\"color:#61afef;\">gvHub_list_decisions</span>({\n flowId?: <span style=\"color:#98c379;\">string</span>, <span style=\"color:#7f848e;\">// Filter by flow</span>\n type?: <span style=\"color:#98c379;\">string</span>, <span style=\"color:#7f848e;\">// Filter by type</span>\n madeBy?: <span style=\"color:#98c379;\">string</span>, <span style=\"color:#7f848e;\">// claude | human</span>\n status?: <span style=\"color:#98c379;\">string</span>, <span style=\"color:#7f848e;\">// made | reviewed | pivoted</span>\n since?: <span style=\"color:#98c379;\">Date</span> <span style=\"color:#7f848e;\">// Date filter</span>\n})\n\n<span style=\"color:#7f848e;\">// Get flow with decisions in one call</span>\n<span style=\"color:#61afef;\">gvHub_get_flow</span>({\n flowId: <span style=\"color:#98c379;\">\"flow-123\"</span>,\n includeDecisions: <span style=\"color:#d19a66;\">true</span> <span style=\"color:#7f848e;\">// Joins automatically</span>\n})\n</pre>\n</div>\n\n<h3 style=\"font-family:sans-serif;color:#fff;margin:0 0 12px 0;\">The Full Object Graph</h3>\n\n<div style=\"background:#2d2d2d;padding:16px;border-radius:6px;margin-bottom:16px;\">\n<pre style=\"margin:0;line-height:2;color:#e0e0e0;font-size:13px;\">\n<span style=\"color:#c678dd;\">gvFlow</span> <span style=\"color:#7f848e;\">Top-level work container</span>\n <span style=\"color:#7f848e;\">│</span>\n <span style=\"color:#7f848e;\">├──</span> <span style=\"color:#61afef;\">gvSession</span>[] <span style=\"color:#7f848e;\">Claude process instances</span>\n <span style=\"color:#7f848e;\">│ └──</span> <span style=\"color:#e5c07b;\">gvTurn</span>[] <span style=\"color:#7f848e;\">Prompt/response pairs</span>\n <span style=\"color:#7f848e;\">│</span>\n <span style=\"color:#7f848e;\">└──</span> <span style=\"color:#22c55e;\">gvDecision</span>[] <span style=\"color:#7f848e;\">Key choices (own storage)</span>\n <span style=\"color:#7f848e;\">└──</span> turnId <span style=\"color:#7f848e;\">Links back to turn</span>\n\n<span style=\"color:#7f848e;\">Storage files:</span>\n gvflows.jsonl ── <span style=\"color:#c678dd;\">gvFlow</span>\n gvsessions.jsonl ── <span style=\"color:#61afef;\">gvSession</span>\n gvturns.jsonl ── <span style=\"color:#e5c07b;\">gvTurn</span> (was kontasks.jsonl)\n gvdecisions.jsonl ── <span style=\"color:#22c55e;\">gvDecision</span>\n</pre>\n</div>\n\n<h3 style=\"font-family:sans-serif;color:#fff;margin:0 0 12px 0;\">Why Not in Turns?</h3>\n\n<div style=\"background:#2d2d2d;padding:16px;border-radius:6px;margin-bottom:20px;border-left:4px solid #f43f5e;\">\n<p style=\"margin:0 0 8px 0;color:#f43f5e;font-weight:bold;\">You identified the problem exactly:</p>\n<pre style=\"margin:0;line-height:1.8;color:#e0e0e0;font-size:13px;\">\n<span style=\"color:#7f848e;\">If decisions only live in turns:</span>\n • Must read all turns to find decisions\n • MCP tool: <span style=\"color:#f43f5e;\">slow</span> (scan kontasks.jsonl)\n • API: <span style=\"color:#f43f5e;\">slow</span> (parse HTML output?)\n • Cross-flow: <span style=\"color:#f43f5e;\">very slow</span> (scan all flows, all turns)\n\n<span style=\"color:#7f848e;\">With top-level gvDecisions:</span>\n • Direct lookup by flowId: <span style=\"color:#22c55e;\">O(n) on decisions</span>\n • Index by type: <span style=\"color:#22c55e;\">instant</span>\n • Cross-flow by date: <span style=\"color:#22c55e;\">instant</span>\n • Turn still has decisionIds for context\n</pre>\n</div>\n\n<hr style=\"border:none;border-top:1px solid #3d3d3d;margin:0 0 20px 0;\">\n\n<h3 style=\"font-family:sans-serif;color:#fff;margin:0 0 12px 0;\">Decision Summary</h3>\n\n<div style=\"background:#2d2d2d;padding:16px;border-radius:6px;border-left:4px solid #22c55e;\">\n<p style=\"margin:0;color:#e0e0e0;\">\n<strong style=\"color:#22c55e;\">gvDecisions should be top-level objects</strong> stored in their own JSONL file, with foreign keys to flows and turns.\n</p>\n<p style=\"margin:12px 0 0 0;color:#e0e0e0;\">\nThis gives you:\n</p>\n<ul style=\"margin:8px 0 0 0;padding-left:20px;color:#e0e0e0;\">\n<li>Fast queries across all flows</li>\n<li>Indexable by type, maker, status, date</li>\n<li>Independent scaling (decisions grow faster than flows)</li>\n<li>Clean separation for doc generation</li>\n</ul>\n</div>\n\n</div>",
"requestedAt": "2026-01-09T02:45:00.000Z",
"requestId": "080198b1-65b0-4e56-b5ac-dd51df2a6ebc",
"choices": [
{
"label": "Top-level gvDecisions",
"value": "Implement gvDecisions as top-level objects in their own JSONL file with FK to flows/turns",
"primary": true
},
{
"label": "Hybrid approach",
"value": "Use hybrid - top-level storage but denormalize summaries into flows for fast reads"
},
{
"label": "Design MCP tools first",
"value": "Design the full MCP tool API for decisions before implementing storage"
}
],
"turnTiming": {
"totalMs": 63599,
"ttfeMs": 55,
"thinkingMs": 63544,
"toolExecutionMs": 0,
"toolCallCount": 0,
"thinkingPct": 100,
"toolsPct": 0,
"ttfePct": 0
}
},
"createdBy": "claude",
"createdAt": "2026-01-09T03:17:29.798Z",
"updatedAt": "2026-01-09T03:17:35.116Z",
"requestId": "080198b1-65b0-4e56-b5ac-dd51df2a6ebc",
"scope": "vibetools",
"tags": [
"gvshell",
"gvhub",
"decisions",
"data-model",
"architecture"
],
"targetUser": "claude"
}