11 KiB
Design Philosophy
The principles and values guiding KOGRAL design and implementation.
Core Tenets
1. Config-Driven, Not Hardcoded
Principle: All behavior should be configurable via schemas, not baked into code.
Why: Flexibility without code changes. Users adapt KOGRAL to their workflows, not vice versa.
Example:
// ❌ Bad: Hardcoded
impl EmbeddingProvider {
fn new() -> Self {
FastEmbedProvider::new("BAAI/bge-small-en-v1.5") // Can't change
}
}
// ✅ Good: Config-driven
impl EmbeddingProvider {
fn from_config(config: &EmbeddingConfig) -> Result<Box<dyn EmbeddingProvider>> {
match config.provider {
'fastembed => Ok(Box::new(FastEmbedProvider::new(&config.model)?)),
'openai => Ok(Box::new(OpenAIProvider::new(&config.model)?)),
}
}
}
Benefits:
- Switch embedding providers without recompilation
- Different configs for dev/prod
- User choice, not developer mandate
2. Type-Safe Configuration
Principle: Validate configuration before runtime, not during.
Why: Catch errors early, reduce runtime failures.
Implementation: Nickel contracts + serde validation = double validation
# Schema defines valid values
EmbeddingProvider = [| 'openai, 'claude, 'fastembed |]
# Typo caught at export time, not runtime
{ provider = 'opena1 } # Error: Invalid variant
Benefits:
- Errors found during
nickel export, not during execution - Self-documenting: schema is the spec
- Refactoring safe: change schema, find all usages
3. Local-First, Cloud-Optional
Principle: Core functionality works offline, cloud is enhancement.
Why: Privacy, cost control, offline development.
Examples:
| Feature | Local | Cloud |
|---|---|---|
| Storage | Filesystem | SurrealDB |
| Embeddings | fastembed | OpenAI/Claude |
| Search | Text-based | Semantic |
Benefits:
- Works on planes, trains, areas with poor internet
- No API costs for small projects
- Privacy-sensitive projects keep data local
- Production can use cloud for scale
4. Git-Friendly by Default
Principle: Knowledge should version alongside code.
Why: Knowledge describes code, should evolve with it.
Implementation:
- Markdown + YAML frontmatter (text-based, diffable)
- One file per node (granular commits)
- Wikilinks preserved (works in Logseq, Obsidian)
Benefits:
# Review knowledge changes in PRs
git diff .kogral/decisions/
# Knowledge follows branches
git checkout feature-x
# .kogral/ reflects feature-x decisions
# Knowledge merges
git merge feature-x
# Merge conflicts = knowledge conflicts (resolve intentionally)
5. AI-Native, Human-Readable
Principle: Optimized for AI consumption, readable by humans.
Why: Best of both worlds - AI-assisted discovery, human verification.
Implementation:
- Structured: YAML frontmatter for AI parsing
- Semantic: Embeddings for AI queries
- Readable: Markdown for human consumption
- Linked: Typed relationships for AI traversal
Example:
---
id: note-auth
type: note
title: Authentication Implementation
tags: [security, auth]
relates_to: [guideline-security, pattern-jwt]
---
# Authentication Implementation
Humans read this markdown normally.
AI can:
- Parse frontmatter for metadata
- Extract tags for filtering
- Follow relates_to links
- Generate embeddings for semantic search
6. Composition Over Inheritance
Principle: Build systems by composing small, focused components.
Why: Flexibility, testability, maintainability.
Implementation:
// Small, focused traits
trait Storage { ... }
trait EmbeddingProvider { ... }
trait TemplateEngine { ... }
// Compose into systems
struct KnowledgeBase {
storage: Box<dyn Storage>,
embeddings: Option<Box<dyn EmbeddingProvider>>,
templates: TemplateEngine,
}
Benefits:
- Swap storage without affecting embeddings
- Disable embeddings without breaking storage
- Test components in isolation
- Add new providers by implementing trait
7. Fail Fast, Fail Clearly
Principle: Detect errors early, provide clear messages.
Why: Developer experience - fast feedback, actionable errors.
Implementation:
// ❌ Bad: Silent failure
fn load_config(path: &Path) -> Option<Config> {
std::fs::read_to_string(path)
.ok()
.and_then(|s| serde_json::from_str(&s).ok())
}
// ✅ Good: Explicit errors
fn load_config(path: &Path) -> Result<Config, ConfigError> {
let content = std::fs::read_to_string(path)
.map_err(|e| ConfigError::ReadFailed(path.to_path_buf(), e))?;
serde_json::from_str(&content)
.map_err(|e| ConfigError::ParseFailed(path.to_path_buf(), e))
}
Error messages:
❌ "Failed to load config"
✅ "Failed to read config file '/path/to/config.ncl': Permission denied"
8. Convention Over Configuration (With Escape Hatches)
Principle: Sane defaults, but everything customizable.
Why: Easy to start, flexible as you grow.
Examples:
# Minimal config (uses conventions)
{ graph = { name = "my-project" } }
# Defaults: filesystem storage, no embeddings, standard templates
# Full config (explicit everything)
{
graph = { name = "my-project" },
storage = { primary = 'surrealdb, /* ... */ },
embeddings = { provider = 'openai, /* ... */ },
templates = { templates_dir = "custom" },
}
Conventions:
.kogral/directory in project root- Filesystem storage by default
- YAML frontmatter + markdown body
- Standard template names (note.md.tera, decision.md.tera)
Escape hatches:
--kogral-dirto use different location- Configure alternative storage backends
- Custom frontmatter schemas
- Override any template
9. Documentation as Code
Principle: Documentation lives with code, versioned together.
Why: Outdated docs are worse than no docs.
Implementation:
- ADRs in
.kogral/decisions/(alongside code) - Guidelines in
.kogral/guidelines/(versioned with code) - Patterns in
.kogral/patterns/(evolve with implementations)
Benefits:
# Code and docs branch together
git checkout old-version
# .kogral/ reflects that version's decisions
# Code and docs merge together
git merge feature
# Merge includes new patterns, updated guidelines
# Code and docs reviewed together
# PR shows code + decision + guideline updates
10. Optimize for Discoverability
Principle: Knowledge is useless if you can't find it.
Why: The point is to use knowledge, not just store it.
Features:
- Text search: Find exact keywords
- Semantic search: Find related concepts
- Graph traversal: Follow relationships
- Tag filtering: Narrow by category
- MCP integration: AI-assisted discovery
Example:
User doesn't remember exact term, but knows the concept:
"Find anything about handling errors gracefully"
KOGRAL finds (semantically):
- "Error Handling with thiserror" (pattern)
- "Result Type Best Practices" (guideline)
- "Panic Recovery" (note)
- "Graceful Degradation" (pattern)
No exact keyword match needed, concept match sufficient.
11. Build for Humans, Enable AI
Principle: Humans are the primary users, AI is the assistant.
Why: AI should enhance human workflows, not replace them.
Implementation:
- Human-readable formats: Markdown, YAML
- Human-editable: Any text editor works
- Human-discoverable:
ls .kogral/notes/shows files - AI-enhanced: MCP for AI-assisted queries
Example:
# Human workflow
vim .kogral/notes/my-note.md # Edit directly
git add .kogral/notes/my-note.md
git commit -m "Add note about X"
# AI-enhanced workflow
# (in Claude Code)
"Add a note about X with tags Y, Z"
# AI creates file, human reviews
12. Embrace the Graph
Principle: Knowledge is interconnected, embrace the relationships.
Why: Context comes from connections, not isolation.
Implementation:
- Typed relationships (not just "related")
- Bidirectional traversal
- Relationship strength (0.0-1.0)
- Multi-hop queries
Example:
Find all patterns that:
- Are implemented by current project
- Depend on shared guidelines
- Were added in the last 6 months
# Graph query, not sequential file search
Anti-Patterns to Avoid
1. ❌ Hardcoding Behavior
// Don't
const EMBEDDING_MODEL: &str = "BAAI/bge-small-en-v1.5";
// Do
let model = config.embeddings.model.as_str();
2. ❌ Runtime Schema Validation
// Don't validate at runtime
let provider = config.provider; // Might be invalid
// Do validate at export time (Nickel contracts)
provider | [| 'openai, 'claude, 'fastembed |]
3. ❌ Opaque Errors
// Don't
Err("Failed".into())
// Do
Err(KbError::NodeNotFound(id.to_string()))
4. ❌ Coupling Components
// Don't
impl KnowledgeBase {
fn search(&self) -> Vec<Node> {
let embeddings = FastEmbedProvider::new(); // Hardcoded!
// ...
}
}
// Do
impl KnowledgeBase {
fn search(&self) -> Vec<Node> {
if let Some(provider) = &self.embeddings {
// Use configured provider
}
}
}
5. ❌ Proprietary Formats
// Don't
Binary blob: [0x4B, 0x42, 0x01, ...]
// Do
Markdown + YAML:
---
id: note-1
---
# Content
Influences
KOGRAL draws inspiration from:
- Logseq: Outliner, graph view, wikilinks
- Obsidian: Markdown-first, local storage, plugins
- Zettelkasten: Atomic notes, links, emergence
- ADR: Decision records, context preservation
- Git: Version control, branching, merging
- Nickel: Type-safe configuration, contracts
- MCP: AI integration protocol
- SurrealDB: Graph database, relationships
Implementation Principles
Rust
- Zero unsafe code (
#![forbid(unsafe_code)]) - No
unwrap()in production code - Always use
Result<T>for fallibility - Comprehensive error types with
thiserror - Full test coverage (100+ tests)
Nickel
- Contracts for validation
- Defaults in schemas
- Documentation in contracts
- Composition via imports
NuShell
- Structured data pipelines
- Error handling in scripts
- Colored output for UX
- Dry-run modes
Evolution Strategy
KOGRAL follows these guidelines for evolution:
- Backward Compatibility: Don't break existing configs
- Deprecation Period: Warn before removal (1 major version)
- Migration Tools: Provide automated migrations
- Semantic Versioning: MAJOR.MINOR.PATCH strictly
Conclusion
These principles guide every decision:
✅ Config-driven: Behavior in schemas, not code ✅ Type-safe: Validate before runtime ✅ Local-first: Works offline, cloud optional ✅ Git-friendly: Knowledge versions with code ✅ AI-native: Optimized for AI, readable by humans ✅ Composable: Small pieces, loosely coupled ✅ Fast feedback: Fail early, clear errors ✅ Discoverable: Easy to find what you need
The goal: Knowledge management that developers actually use.
Next Steps
- See it in action: Use Cases
- Understand architecture: System Overview
- Start using: Quick Start