Skip to main content

sochdb_core/
predefined_views.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// SochDB - LLM-Optimized Embedded Database
3// Copyright (C) 2026 Sushanth Reddy Vanagala (https://github.com/sushanthpy)
4//
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU Affero General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU Affero General Public License for more details.
14//
15// You should have received a copy of the GNU Affero General Public License
16// along with this program. If not, see <https://www.gnu.org/licenses/>.
17
18//! Predefined SochQL Views (Task 7: Stable Identifiers)
19//!
20//! This module defines canonical views over the core memory schema
21//! (episodes, events, entities) that provide:
22//!
23//! 1. **Stable naming**: LLMs and humans learn consistent patterns
24//! 2. **Self-documenting**: View names encode semantics directly
25//! 3. **Reduced complexity**: O(1) concepts instead of O(T) tables
26//!
27//! ## Views
28//!
29//! | View | Purpose | Common Use |
30//! |------|---------|------------|
31//! | `conversation_view` | Chat messages | "Show recent conversation" |
32//! | `tool_calls_view` | Tool invocations | "What tools were used?" |
33//! | `error_view` | Error events | "Show recent errors" |
34//! | `episode_summary_view` | Episode overviews | "List recent tasks" |
35//! | `entity_directory_view` | Entity catalog | "Find user/project" |
36
37use std::collections::HashMap;
38
39/// A predefined view definition
40#[derive(Debug, Clone)]
41pub struct ViewDefinition {
42    /// View name (e.g., "conversation_view")
43    pub name: &'static str,
44    /// Human-readable description
45    pub description: &'static str,
46    /// SochQL definition (SELECT statement)
47    pub definition: &'static str,
48    /// Output columns
49    pub columns: &'static [&'static str],
50    /// Base table(s) this view queries
51    pub base_tables: &'static [&'static str],
52    /// Usage hints for LLMs
53    pub usage_hints: &'static [&'static str],
54}
55
56/// Get all predefined views
57pub fn get_predefined_views() -> Vec<ViewDefinition> {
58    vec![
59        // =====================================================================
60        // Conversation View - Chat history
61        // =====================================================================
62        ViewDefinition {
63            name: "conversation_view",
64            description: "Conversation history with role, content, and timestamp. \
65                          Use for displaying chat transcripts or analyzing dialogue patterns.",
66            definition: "SELECT episode_id, seq, role, content, ts \
67                         FROM events \
68                         WHERE event_type = 'message' \
69                         ORDER BY ts",
70            columns: &["episode_id", "seq", "role", "content", "ts"],
71            base_tables: &["events"],
72            usage_hints: &[
73                "Filter by episode_id to get a single conversation",
74                "Use LAST N to get recent messages",
75                "Filter by role to get only user or assistant messages",
76            ],
77        },
78        // =====================================================================
79        // Tool Calls View - Tool invocations
80        // =====================================================================
81        ViewDefinition {
82            name: "tool_calls_view",
83            description: "Tool invocations with inputs, outputs, and timing. \
84                          Use for debugging tool usage or analyzing patterns.",
85            definition: "SELECT episode_id, seq, tool_name, input, output, \
86                         tokens_in, tokens_out, latency_ms, ts \
87                         FROM events \
88                         WHERE event_type = 'tool_call' \
89                         ORDER BY ts",
90            columns: &[
91                "episode_id",
92                "seq",
93                "tool_name",
94                "input",
95                "output",
96                "tokens_in",
97                "tokens_out",
98                "latency_ms",
99                "ts",
100            ],
101            base_tables: &["events"],
102            usage_hints: &[
103                "Filter by tool_name to analyze specific tool usage",
104                "Sum tokens_in + tokens_out for cost analysis",
105                "Use AVG(latency_ms) GROUP BY tool_name for performance",
106            ],
107        },
108        // =====================================================================
109        // Error View - Errors and failures
110        // =====================================================================
111        ViewDefinition {
112            name: "error_view",
113            description: "Error events with context and stack traces. \
114                          Use for debugging failures or monitoring error rates.",
115            definition: "SELECT episode_id, seq, error_type, message, \
116                         stack_trace, ts \
117                         FROM events \
118                         WHERE event_type = 'error' \
119                         ORDER BY ts DESC",
120            columns: &[
121                "episode_id",
122                "seq",
123                "error_type",
124                "message",
125                "stack_trace",
126                "ts",
127            ],
128            base_tables: &["events"],
129            usage_hints: &[
130                "Use LAST N for recent errors",
131                "Group by error_type for error categorization",
132                "Join with episodes for context on when errors occurred",
133            ],
134        },
135        // =====================================================================
136        // Episode Summary View - Task/conversation overviews
137        // =====================================================================
138        ViewDefinition {
139            name: "episode_summary_view",
140            description: "Episode summaries for quick browsing. \
141                          Use for listing recent tasks or finding similar past work.",
142            definition: "SELECT episode_id, episode_type, summary, tags, \
143                         ts_start, ts_end \
144                         FROM episodes \
145                         ORDER BY ts_start DESC",
146            columns: &[
147                "episode_id",
148                "episode_type",
149                "summary",
150                "tags",
151                "ts_start",
152                "ts_end",
153            ],
154            base_tables: &["episodes"],
155            usage_hints: &[
156                "Use memory.search_episodes for semantic search",
157                "Filter by episode_type for specific task types",
158                "Calculate ts_end - ts_start for duration",
159            ],
160        },
161        // =====================================================================
162        // Entity Directory View - Entity catalog
163        // =====================================================================
164        ViewDefinition {
165            name: "entity_directory_view",
166            description: "Entity directory organized by kind. \
167                          Use for finding users, projects, services, or documents.",
168            definition: "SELECT entity_id, kind, name, description, updated_at \
169                         FROM entities \
170                         ORDER BY kind, name",
171            columns: &["entity_id", "kind", "name", "description", "updated_at"],
172            base_tables: &["entities"],
173            usage_hints: &[
174                "Use memory.search_entities for semantic search",
175                "Filter by kind = 'user' or 'project' etc.",
176                "Use memory.get_entity_facts for full details",
177            ],
178        },
179        // =====================================================================
180        // Session Timeline View - Full session history
181        // =====================================================================
182        ViewDefinition {
183            name: "session_timeline_view",
184            description: "Complete session timeline combining messages and tool calls. \
185                          Use for full session replay or context building.",
186            definition: "SELECT episode_id, seq, event_type, role, content, \
187                         tool_name, ts \
188                         FROM events \
189                         WHERE event_type IN ('message', 'tool_call', 'tool_result') \
190                         ORDER BY episode_id, seq",
191            columns: &[
192                "episode_id",
193                "seq",
194                "event_type",
195                "role",
196                "content",
197                "tool_name",
198                "ts",
199            ],
200            base_tables: &["events"],
201            usage_hints: &[
202                "Filter by episode_id for a single session",
203                "Use logs.tail for recent events",
204                "Interleaves messages and tool calls chronologically",
205            ],
206        },
207        // =====================================================================
208        // Metrics View - Performance and cost metrics
209        // =====================================================================
210        ViewDefinition {
211            name: "metrics_view",
212            description: "Aggregated metrics per episode. \
213                          Use for cost analysis, performance monitoring.",
214            definition: "SELECT episode_id, \
215                         COUNT(*) as event_count, \
216                         SUM(tokens_in) as total_tokens_in, \
217                         SUM(tokens_out) as total_tokens_out, \
218                         AVG(latency_ms) as avg_latency_ms, \
219                         MIN(ts) as first_event, \
220                         MAX(ts) as last_event \
221                         FROM events \
222                         GROUP BY episode_id",
223            columns: &[
224                "episode_id",
225                "event_count",
226                "total_tokens_in",
227                "total_tokens_out",
228                "avg_latency_ms",
229                "first_event",
230                "last_event",
231            ],
232            base_tables: &["events"],
233            usage_hints: &[
234                "Join with episodes for episode metadata",
235                "Use for cost estimation and budgeting",
236                "Calculate total_tokens_in + total_tokens_out for total usage",
237            ],
238        },
239    ]
240}
241
242/// Get a view by name
243pub fn get_view(name: &str) -> Option<ViewDefinition> {
244    get_predefined_views().into_iter().find(|v| v.name == name)
245}
246
247/// Build a HashMap of views for O(1) lookup
248pub fn build_view_map() -> HashMap<&'static str, ViewDefinition> {
249    get_predefined_views()
250        .into_iter()
251        .map(|v| (v.name, v))
252        .collect()
253}
254
255// ============================================================================
256// Naming Conventions
257// ============================================================================
258
259/// Standard column naming conventions
260pub mod naming {
261    /// ID columns use `<entity>_id` format
262    pub const ID_SUFFIX: &str = "_id";
263
264    /// Timestamp columns use `ts_<event>` or just `ts`
265    pub const TS_PREFIX: &str = "ts_";
266
267    /// Count columns use `<thing>_count`
268    pub const COUNT_SUFFIX: &str = "_count";
269
270    /// Boolean columns use `is_<state>` or `has_<thing>`
271    pub const BOOL_IS_PREFIX: &str = "is_";
272    pub const BOOL_HAS_PREFIX: &str = "has_";
273
274    /// Standard role values
275    pub const ROLES: &[&str] = &["user", "assistant", "system", "tool", "external"];
276
277    /// Standard event types
278    pub const EVENT_TYPES: &[&str] = &[
279        "message",
280        "tool_call",
281        "tool_result",
282        "error",
283        "start",
284        "end",
285        "checkpoint",
286        "observation",
287    ];
288
289    /// Standard entity kinds
290    pub const ENTITY_KINDS: &[&str] = &[
291        "user",
292        "project",
293        "document",
294        "service",
295        "agent",
296        "organization",
297        "concept",
298    ];
299
300    /// Standard episode types
301    pub const EPISODE_TYPES: &[&str] = &[
302        "conversation",
303        "task",
304        "workflow",
305        "debug",
306        "agent_interaction",
307        "batch_job",
308    ];
309}
310
311// ============================================================================
312// Tests
313// ============================================================================
314
315#[cfg(test)]
316mod tests {
317    use super::*;
318
319    #[test]
320    fn test_get_predefined_views() {
321        let views = get_predefined_views();
322        assert!(views.len() >= 5, "Should have at least 5 predefined views");
323
324        // Check conversation_view exists
325        let conv = views.iter().find(|v| v.name == "conversation_view");
326        assert!(conv.is_some());
327
328        let conv = conv.unwrap();
329        assert!(conv.columns.contains(&"role"));
330        assert!(conv.columns.contains(&"content"));
331    }
332
333    #[test]
334    fn test_get_view() {
335        let view = get_view("tool_calls_view");
336        assert!(view.is_some());
337
338        let view = view.unwrap();
339        assert!(view.columns.contains(&"tool_name"));
340        assert!(view.base_tables.contains(&"events"));
341    }
342
343    #[test]
344    fn test_build_view_map() {
345        let map = build_view_map();
346        assert!(map.contains_key("error_view"));
347        assert!(map.contains_key("episode_summary_view"));
348    }
349
350    #[test]
351    fn test_naming_conventions() {
352        use naming::*;
353
354        assert!(ROLES.contains(&"user"));
355        assert!(ROLES.contains(&"assistant"));
356        assert!(EVENT_TYPES.contains(&"tool_call"));
357        assert!(ENTITY_KINDS.contains(&"project"));
358    }
359}