# System Architecture

Comprehensive overview of the KOGRAL architecture.

## High-Level Architecture

![Architecture Overview](../diagrams/architecture-overview.svg)

The KOGRAL consists of three main layers:

1. **User Interfaces**: kogral-cli (terminal), kogral-mcp (AI integration), NuShell scripts (automation)
2. **Core Library (kogral-core)**: Rust library with graph engine, storage abstraction, embeddings, query engine
3. **Storage Backends**: Filesystem (git-friendly), SurrealDB (scalable), In-Memory (cache/testing)

## Component Details

### kogral-cli (Command-Line Interface)

**Purpose**: Primary user interface for local knowledge management.

**Commands** (13 total):
- `init`: Initialize `.kogral/` directory
- `add`: Create nodes (note, decision, guideline, pattern, journal)
- `search`: Text and semantic search
- `link`: Create relationships between nodes
- `list`: List all nodes
- `show`: Display node details
- `delete`: Remove nodes
- `graph`: Visualize knowledge graph
- `sync`: Sync filesystem ↔ SurrealDB
- `serve`: Start MCP server
- `import`: Import from Logseq
- `export`: Export to Logseq/JSON
- `config`: Manage configuration

**Technology**: Rust + clap (derive API)

**Features**:
- Colored terminal output
- Interactive prompts
- Dry-run modes
- Validation before operations

### kogral-mcp (MCP Server)

**Purpose**: AI integration via Model Context Protocol.

**Protocol**: JSON-RPC 2.0 over stdio

**Components**:

1. **Tools** (7):
   - `kogral/search`: Query knowledge base
   - `kogral/add_note`: Create notes
   - `kogral/add_decision`: Create ADRs
   - `kogral/link`: Create relationships
   - `kogral/get_guidelines`: Retrieve guidelines with inheritance
   - `kb/list_graphs`: List available graphs
   - `kogral/export`: Export to formats

2. **Resources** (6 URIs):
   - `kogral://project/notes`
   - `kogral://project/decisions`
   - `kogral://project/guidelines`
   - `kogral://project/patterns`
   - `kogral://shared/guidelines`
   - `kogral://shared/patterns`

3. **Prompts** (2):
   - `kogral/summarize_project`: Generate project summary
   - `kogral/find_related`: Find related nodes

**Integration**: Claude Code via `~/.config/claude/config.json`

### NuShell Scripts

**Purpose**: Automation and maintenance tasks.

**Scripts** (6):
- `kogral-sync.nu`: Filesystem ↔ SurrealDB sync
- `kogral-backup.nu`: Archive knowledge base
- `kogral-reindex.nu`: Rebuild embeddings
- `kogral-import-logseq.nu`: Import from Logseq
- `kogral-export-logseq.nu`: Export to Logseq
- `kogral-stats.nu`: Graph statistics

**Features**:
- Colored output
- Dry-run modes
- Progress indicators
- Error handling

## Core Library (kogral-core)

### Models

**Graph**:

```rust
pub struct Graph {
    pub name: String,
    pub version: String,
    pub nodes: HashMap<String, Node>,  // ID → Node
    pub edges: Vec<Edge>,
    pub metadata: HashMap<String, Value>,
}
```

**Node**:

```rust
pub struct Node {
    pub id: String,
    pub node_type: NodeType,
    pub title: String,
    pub content: String,
    pub tags: Vec<String>,
    pub status: NodeStatus,
    pub created: DateTime<Utc>,
    pub modified: DateTime<Utc>,
    // ... relationships, metadata
}
```

**Edge**:

```rust
pub struct Edge {
    pub from: String,
    pub to: String,
    pub relation: EdgeType,
    pub strength: f32,
    pub created: DateTime<Utc>,
}
```

### Storage Trait

```rust
#[async_trait]
pub trait Storage: Send + Sync {
    /// Save a complete graph to storage
    async fn save_graph(&mut self, graph: &Graph) -> Result<()>;

    /// Load a graph from storage
    async fn load_graph(&self, name: &str) -> Result<Graph>;

    /// Save a single node to storage
    async fn save_node(&mut self, node: &Node) -> Result<()>;

    /// Load a node by ID
    async fn load_node(&self, graph_name: &str, node_id: &str) -> Result<Node>;

    /// Delete a node
    async fn delete_node(&mut self, graph_name: &str, node_id: &str) -> Result<()>;

    /// List all graphs
    async fn list_graphs(&self) -> Result<Vec<String>>;

    /// List nodes in a graph, optionally filtered by type
    async fn list_nodes(&self, graph_name: &str, node_type: Option<&str>) -> Result<Vec<Node>>;
}
```

**Implementations**:
1. `FilesystemStorage`: Git-friendly markdown files
2. `MemoryStorage`: In-memory with DashMap
3. `SurrealDbStorage`: Scalable graph database

### Embedding Provider Trait

```rust
#[async_trait]
pub trait EmbeddingProvider: Send + Sync {
    async fn embed(&self, texts: Vec<String>) -> Result<Vec<Vec<f32>>>;
    fn dimensions(&self) -> usize;
    fn model_name(&self) -> &str;
}
```

**Implementations**:
1. `FastEmbedProvider`: Local fastembed
2. `RigEmbeddingProvider`: OpenAI, Claude, Ollama (via rig-core)

### Parser

**Input**: Markdown file with YAML frontmatter

**Output**: `Node` struct

**Features**:
- YAML frontmatter extraction
- Markdown body parsing
- Wikilink detection (`[[linked-note]]`)
- Code reference parsing (`@file.rs:42`)

**Example**:

```markdown
---
id: note-123
type: note
title: My Note
tags: [rust, async]
---

# My Note

Content with [[other-note]] and @src/main.rs:10
```

→

```rust
Node {
    id: "note-123",
    node_type: NodeType::Note,
    title: "My Note",
    content: "Content with [[other-note]] and @src/main.rs:10",
    tags: vec!["rust", "async"],
    // ... parsed wikilinks, code refs
}
```

## Configuration System

### Nickel Schema

```nickel
# schemas/kogral-config.ncl
{
  KbConfig = {
    graph | GraphConfig,
    storage | StorageConfig,
    embeddings | EmbeddingConfig,
    templates | TemplateConfig,
    query | QueryConfig,
    mcp | McpConfig,
    sync | SyncConfig,
  },
}
```

### Loading Process

```text
User writes: .kogral/config.ncl
    ↓ [nickel export --format json]
JSON intermediate
    ↓ [serde_json::from_str]
KbConfig struct (Rust)
    ↓
Runtime behavior
```

**Double Validation**:
1. Nickel contracts: Type-safe, enum validation
2. Serde deserialization: Rust type checking

**Benefits**:
- Errors caught at export time
- Runtime guaranteed valid config
- Self-documenting schemas

## Storage Architecture

### Hybrid Strategy

**Local Graph** (per project):
- Storage: Filesystem (`.kogral/` directory)
- Format: Markdown + YAML frontmatter
- Version control: Git
- Scope: Project-specific knowledge

**Shared Graph** (organization):
- Storage: SurrealDB (or synced filesystem)
- Format: Same markdown (for compatibility)
- Version control: Optional
- Scope: Organization-wide guidelines

**Sync**:

```text
Filesystem (.kogral/)
    ↕ [bidirectional sync]
SurrealDB (central)
```

### File Layout

```text
.kogral/
├── config.toml              # Graph metadata
├── notes/
│   ├── async-patterns.md    # Individual note
│   └── error-handling.md
├── decisions/
│   ├── 0001-use-rust.md     # ADR format
│   └── 0002-surrealdb.md
├── guidelines/
│   ├── rust-errors.md       # Project guideline
│   └── testing.md
├── patterns/
│   └── repository.md
└── journal/
    ├── 2026-01-17.md        # Daily journal
    └── 2026-01-18.md
```

## Query Engine

### Text Search

```rust
let results = graph.nodes.values()
    .filter(|node| {
        node.title.contains(&query) ||
        node.content.contains(&query) ||
        node.tags.iter().any(|tag| tag.contains(&query))
    })
    .collect();
```

### Semantic Search

```rust
let query_embedding = embeddings.embed(vec![query]).await?;
let mut scored: Vec<_> = graph.nodes.values()
    .filter_map(|node| {
        let node_embedding = node.embedding.as_ref()?;
        let similarity = cosine_similarity(&query_embedding[0], node_embedding);
        (similarity >= threshold).then_some((node, similarity))
    })
    .collect();
scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
```

### Cross-Graph Query

```rust
// Query both project and shared graphs
let project_results = project_graph.search(&query).await?;
let shared_results = shared_graph.search(&query).await?;

// Merge with deduplication
let combined = merge_results(project_results, shared_results);
```

## MCP Protocol Flow

```text
Claude Code                kogral-mcp                    kogral-core
    │                         │                         │
    ├─ JSON-RPC request ───→ │                         │
    │  kogral/search              │                         │
    │  {"query": "rust"}      │                         │
    │                         ├─ search() ──────────→  │
    │                         │                         │
    │                         │                    Query engine
    │                         │                    Text + semantic
    │                         │                         │
    │                         │ ←──── results ─────────┤
    │                         │                         │
    │ ←─ JSON-RPC response ──┤                         │
    │  {"results": [...]}     │                         │
```

## Template System

**Engine**: Tera (Jinja2-like)

**Templates**:

1. **Document Templates** (6):
   - `note.md.tera`
   - `decision.md.tera`
   - `guideline.md.tera`
   - `pattern.md.tera`
   - `journal.md.tera`
   - `execution.md.tera`

2. **Export Templates** (4):
   - `logseq-page.md.tera`
   - `logseq-journal.md.tera`
   - `summary.md.tera`
   - `graph.json.tera`

**Usage**:

```rust
let mut tera = Tera::new("templates/**/*.tera")?;
let rendered = tera.render("note.md.tera", &context)?;
```

## Error Handling

**Strategy**: `thiserror` for structured errors

```rust
#[derive(Error, Debug)]
pub enum KbError {
    #[error("Storage error: {0}")]
    Storage(String),

    #[error("Node not found: {0}")]
    NodeNotFound(String),

    #[error("Configuration error: {0}")]
    Config(String),

    #[error("Parse error: {0}")]
    Parse(String),

    #[error("Embedding error: {0}")]
    Embedding(String),
}
```

**Propagation**: `?` operator throughout

## Testing Strategy

**Unit Tests**: Per module (models, parser, storage)

**Integration Tests**: Full workflow (add → save → load → query)

**Test Coverage**:
- kogral-core: 48 tests
- kogral-mcp: 5 tests
- Total: 56 tests

**Test Data**: Fixtures in `tests/fixtures/`

## Performance Considerations

**Node Lookup**: O(1) via HashMap

**Semantic Search**: O(n) with early termination (threshold filter)

**Storage**:
- Filesystem: Lazy loading (load on demand)
- Memory: Full graph in RAM
- SurrealDB: Query optimization (indexes)

**Embeddings**:
- Cache embeddings in node metadata
- Batch processing (configurable batch size)
- Async generation (non-blocking)

## Security

**No unsafe code**: `#![forbid(unsafe_code)]`

**Input validation**:
- Nickel contracts validate config
- serde validates JSON
- Custom validation for user input

**File operations**:
- Path sanitization (no `../` traversal)
- Permissions checking
- Atomic writes (temp file + rename)

## Scalability

**Small Projects** (< 1000 nodes):
- Filesystem storage
- In-memory search
- Local embeddings (fastembed)

**Medium Projects** (1000-10,000 nodes):
- Filesystem + SurrealDB sync
- Semantic search with caching
- Cloud embeddings (OpenAI/Claude)

**Large Organizations** (> 10,000 nodes):
- SurrealDB primary
- Distributed embeddings
- Multi-graph federation

## Next Steps

- **Graph Model Details**: [Graph Model](graph-model.md)
- **Storage Deep Dive**: [Storage Architecture](storage-architecture.md)
- **ADRs**: [Architectural Decisions](adrs/001-nickel-vs-toml.md)
- **Implementation**: [Development Guide](../contributing/development.md)
