Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .docs/summary-Cargo.toml.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Cargo.toml Summary

## Purpose
Defines the workspace configuration and dependencies for the Terraphim AI project, managing multiple crates and external dependencies.

## Key Functionality
- Configures a Rust workspace with multiple member crates
- Sets up workspace-level dependencies (tokio, reqwest, serde, etc.)
- Defines profile configurations for different build scenarios (release, ci, etc.)
- Specifies excluded crates (experimental, Python bindings, desktop-specific)
- Applies patches for specific dependencies (genai, self_update)

## Important Details
- Workspace includes crates/, terraphim_server, terraphim_firecracker, terraphim_ai_nodejs
- Excludes experimental crates like terraphim_agent_application, terraphim_truthforge, etc.
- Uses edition 2024 and version 1.14.0
- Configured for async/await with tokio runtime
- Features conditional compilation via cfg attributes
- Manages cross-platform builds with specific exclusions
24 changes: 24 additions & 0 deletions .docs/summary-crates-terraphim_agent-src-client.rs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# terraphim_agent/src/client.rs Summary

## Purpose
Provides HTTP client functionality for communicating with the Terraphim server API, handling requests for configuration, chat, document summarization, thesaurus access, and VM management.

## Key Functionality
- Creates HTTP client with configurable timeouts and user agent
- Fetches server configuration via GET /config endpoint
- Resolves role strings to RoleName objects using server config (with fallback)
- Handles chat interactions with LLM models via POST /chat
- Summarizes documents via POST /documents/summarize
- Accesses thesaurus data via GET /thesaurus/{role_name}
- Provides autocomplete suggestions via GET /autocomplete/{role_name}/{query}
- Manages VM operations (listing, status, execution, metrics)
- Supports async document summarization and task management

## Important Details
- Role resolution falls back to creating RoleName from raw string if not found in config (line 69)
- Uses async/await pattern with Tokio for non-blocking HTTP requests
- Implements proper error handling with anyhow::Result
- Includes configurable timeout via TERRAPHIM_CLIENT_TIMEOUT environment variable
- Provides both live API methods and dead-code-allowed variants for testing
- Handles URL encoding for query parameters in various endpoints
- Supports VM pool management, execution, and monitoring capabilities
24 changes: 24 additions & 0 deletions .docs/summary-crates-terraphim_agent-src-service.rs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# terraphim_agent/src/service.rs Summary

## Purpose
Provides the TUI service layer that manages application state, configuration, and business logic for the Terraphim agent's text-based user interface, coordinating between configuration persistence and the core TerraphimService.

## Key Functionality
- Manages configuration loading with priority: CLI flag → settings.toml → persistence → embedded defaults
- Resolves role strings to RoleName objects by searching configuration (name first, then shortname)
- Provides access to thesaurus data for roles via embedded automata
- Handles chat interactions with LLM providers based on role configuration
- Implements document search, extraction, summarization, and connectivity checking
- Manages role graph operations and topological analysis
- Supports configuration persistence and reloading from JSON files
- Provides checklist validation functionality for various domains (code review, security, etc.)

## Important Details
- Role resolution returns error if role not found in config (line 251), unlike client.rs which falls back to raw string
- Uses Arc<Mutex<TerraphimService>> for shared state access across async contexts
- Implements bootstrap-then-persistence pattern for role_config in settings.toml
- Supports multiple search modes: simple term search and complex SearchQuery with operators
- Provides thesaurus-backed text operations: extraction, finding matches, autocomplete, fuzzy suggestions
- Includes connectivity checking for knowledge graph relationships between matched terms
- Handles device settings with fallback to embedded defaults in sandboxed environments
- Manages configuration updates and persistence through save_config() method
59 changes: 59 additions & 0 deletions .docs/summary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Terraphim AI Project Summary

## Overview
Terraphim AI is a Rust-based AI agent system featuring a modular architecture with multiple crates, providing both online (server-connected) and offline (embedded) capabilities for knowledge work, document processing, and AI-assisted tasks.

## Architecture
- **Workspace Structure**: Cargo workspace with multiple member crates including `terraphim_agent`, `terraphim_server`, `terraphim_firecracker`, and `terraphim_ai_nodejs`
- **Modular Design**: Separation of concerns between client communication, service logic, and core functionality
- **Async Runtime**: Built on Tokio for asynchronous operations throughout the codebase
- **Configuration System**: Multi-layer config loading with priority: CLI flags → settings.toml → persistence → embedded defaults

## Key Components

### Communication Layer (`terraphim_agent/src/client.rs`)
- HTTP client for server API communication
- Features configurable timeouts and user agent
- Role resolution that falls back to creating RoleName from raw string when not found in server config
- Supports chat, document summarization, thesaurus access, autocomplete, and VM management operations

### Service Layer (`terraphim_agent/src/service.rs`)
- TUI service managing application state and business logic
- Coordinates between configuration persistence and core TerraphimService
- Role resolution that returns error when role not found in config (contrasts with client.rs)
- Provides thesaurus-backed text operations, search, extraction, summarization, and connectivity checking
- Implements bootstrap-then-persistence pattern for role configuration

### Dependencies & Tooling
- Core dependencies: tokio, reqwest, serde, thiserror, anyhow, async-trait
- Conditional compilation via cfg attributes for feature flags
- Cross-platform build management with specific exclusions
- Development tooling includes Clippy, Rustfmt, and custom validation scripts

## Notable Patterns & Characteristics
1. **Role Resolution Inconsistency**:
- Online mode (client.rs): Falls back to raw role string when not found in config
- Offline mode (service.rs): Returns error when role not found in config
- This creates different behavior between connected and disconnected states

2. **Configuration Loading**: Sophisticated multi-source configuration system with fallback hierarchy

3. **Error Handling**: Consistent use of `anyhow::Result` and `thiserror` for error propagation

4. **Async/Await**: Extensive use of asynchronous patterns with proper timeout handling

5. **Testing Approach**: Emphasis on integration testing with real services rather than mocks

## Current Focus Areas
Based on recent documentation and code review activities:
- Cross-mode consistency between online and offline operations
- Role resolution and validation improvements
- Test coverage and compilation fixes
- Documentation maintenance and accuracy
- Performance optimization and benchmarking

## Build & Development
- Standard Rust toolchain with cargo workspace
- Features: openrouter, mcp-rust-sdk for conditional compilation
- Profile configurations for release, CI, and LTO-optimized builds
- Pre-commit hooks for code quality assurance
4 changes: 2 additions & 2 deletions crates/terraphim_agent/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ readme = "../../README.md"

[features]
default = ["repl-interactive"]
repl = ["dep:rustyline", "dep:colored", "dep:comfy-table", "dep:dirs"]
repl = ["dep:rustyline", "dep:colored", "dep:comfy-table"]
repl-interactive = ["repl"]
# NOTE: repl-sessions re-enabled for local development (path dependency)
repl-full = ["repl", "repl-chat", "repl-mcp", "repl-file", "repl-custom", "repl-web", "repl-interactive", "repl-sessions"]
Expand Down Expand Up @@ -62,7 +62,7 @@ dialoguer = "0.12" # Interactive CLI prompts for onboarding wizard
rustyline = { version = "17.0", optional = true }
colored = { version = "3.0", optional = true }
comfy-table = { version = "7.0", optional = true }
dirs = { version = "5.0", optional = true }
dirs = { version = "5.0" }
terraphim_types = { path = "../terraphim_types", version = "1.0.0" }
terraphim_settings = { path = "../terraphim_settings", version = "1.0.0" }
terraphim_persistence = { path = "../terraphim_persistence", version = "1.0.0" }
Expand Down
23 changes: 23 additions & 0 deletions crates/terraphim_agent/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,29 @@ impl ApiClient {
Ok(body)
}

/// Resolve a role string (name or shortname) to a RoleName using server config.
/// Falls back to RoleName::new if no match found (server will validate).
pub async fn resolve_role(&self, role: &str) -> Result<terraphim_types::RoleName> {
use terraphim_types::RoleName;
let config_res = self.get_config().await?;
let role_lower = role.to_lowercase();
let selected = &config_res.config.selected_role;
if selected.to_string().to_lowercase() == role_lower {
return Ok(selected.clone());
}
for (name, role_cfg) in &config_res.config.roles {
if name.to_string().to_lowercase() == role_lower {
return Ok(name.clone());
}
if let Some(ref sn) = role_cfg.shortname {
if sn.to_lowercase() == role_lower {
return Ok(name.clone());
}
}
}
Ok(RoleName::new(role))
}

pub async fn update_selected_role(&self, role: &str) -> Result<ConfigResponse> {
let url = format!("{}/config/selected_role", self.base);
#[derive(Serialize)]
Expand Down
18 changes: 11 additions & 7 deletions crates/terraphim_agent/src/learnings/capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ impl CorrectionEvent {
}

/// Set session ID.
#[allow(dead_code)]
pub fn with_session_id(mut self, session_id: String) -> Self {
self.session_id = Some(session_id);
self
Expand Down Expand Up @@ -720,6 +721,7 @@ fn timestamp_millis() -> u64 {
}

/// List recent learnings from storage.
#[allow(dead_code)]
pub fn list_learnings(
storage_dir: &PathBuf,
limit: usize,
Expand Down Expand Up @@ -754,6 +756,7 @@ pub fn list_learnings(
Ok(learnings)
}

#[allow(dead_code)]
/// Query learnings by pattern (simple text search).
pub fn query_learnings(
storage_dir: &PathBuf,
Expand Down Expand Up @@ -820,27 +823,28 @@ pub enum LearningEntry {
}

impl LearningEntry {
pub fn captured_at(&self) -> DateTime<Utc> {
match self {
LearningEntry::Learning(l) => l.context.captured_at,
LearningEntry::Correction(c) => c.context.captured_at,
}
}

pub fn source(&self) -> &LearningSource {
match self {
LearningEntry::Learning(l) => &l.source,
LearningEntry::Correction(c) => &c.source,
}
}

#[allow(dead_code)]
pub fn id(&self) -> &str {
match self {
LearningEntry::Learning(l) => &l.id,
LearningEntry::Correction(c) => &c.id,
}
}

pub fn captured_at(&self) -> chrono::DateTime<chrono::Utc> {
match self {
LearningEntry::Learning(l) => l.context.captured_at,
LearningEntry::Correction(c) => c.context.captured_at,
}
}

/// Summary line for display.
pub fn summary(&self) -> String {
match self {
Expand Down
6 changes: 2 additions & 4 deletions crates/terraphim_agent/src/learnings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,9 @@ mod procedure;
mod redaction;

pub use capture::{
CorrectionEvent, CorrectionType, LearningEntry, LearningSource, capture_correction,
capture_failed_command, correct_learning, list_all_entries, list_learnings, query_all_entries,
query_learnings,
CorrectionType, LearningSource, capture_correction, capture_failed_command, correct_learning,
list_all_entries, query_all_entries,
};

// Re-export for testing - not used by CLI yet
#[allow(unused_imports)]
pub use capture::{CapturedLearning, LearningContext, LearningError};
Expand Down
54 changes: 11 additions & 43 deletions crates/terraphim_agent/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1120,11 +1120,7 @@ async fn run_offline_command(
role,
limit,
} => {
let role_name = if let Some(role) = role {
RoleName::new(&role)
} else {
service.get_selected_role().await
};
let role_name = service.resolve_role(role.as_deref()).await?;

let results = if let Some(additional_terms) = terms {
// Multi-term query with logical operators
Expand Down Expand Up @@ -1278,11 +1274,7 @@ async fn run_offline_command(
Ok(())
}
Command::Graph { role, top_k } => {
let role_name = if let Some(role) = role {
RoleName::new(&role)
} else {
service.get_selected_role().await
};
let role_name = service.resolve_role(role.as_deref()).await?;

let concepts = service.get_role_graph_top_k(&role_name, top_k).await?;
for concept in concepts {
Expand All @@ -1295,11 +1287,7 @@ async fn run_offline_command(
prompt,
model,
} => {
let role_name = if let Some(role) = role {
RoleName::new(&role)
} else {
service.get_selected_role().await
};
let role_name = service.resolve_role(role.as_deref()).await?;

let response = service.chat(&role_name, &prompt, model).await?;
println!("{}", response);
Expand All @@ -1310,11 +1298,7 @@ async fn run_offline_command(
role,
exclude_term,
} => {
let role_name = if let Some(role) = role {
RoleName::new(&role)
} else {
service.get_selected_role().await
};
let role_name = service.resolve_role(role.as_deref()).await?;

let results = service
.extract_paragraphs(&role_name, &text, exclude_term)
Expand Down Expand Up @@ -1350,11 +1334,7 @@ async fn run_offline_command(
}
};

let role_name = if let Some(role) = role {
RoleName::new(&role)
} else {
service.get_selected_role().await
};
let role_name = service.resolve_role(role.as_deref()).await?;

let link_type = match format.as_deref() {
Some("markdown") => terraphim_hooks::LinkType::MarkdownLinks,
Expand Down Expand Up @@ -1474,11 +1454,7 @@ async fn run_offline_command(
}
};

let role_name = if let Some(role) = role {
RoleName::new(&role)
} else {
service.get_selected_role().await
};
let role_name = service.resolve_role(role.as_deref()).await?;

if connectivity {
let result = service.check_connectivity(&role_name, &input_text).await?;
Expand Down Expand Up @@ -1559,11 +1535,7 @@ async fn run_offline_command(
}
};

let role_name = if let Some(role) = role {
RoleName::new(&role)
} else {
service.get_selected_role().await
};
let role_name = service.resolve_role(role.as_deref()).await?;

let suggestions = service
.fuzzy_suggest(&role_name, &input_query, threshold, Some(limit))
Expand Down Expand Up @@ -1606,11 +1578,7 @@ async fn run_offline_command(
}
};

let role_name = if let Some(role) = role {
RoleName::new(&role)
} else {
service.get_selected_role().await
};
let role_name = service.resolve_role(role.as_deref()).await?;

// Parse input JSON
let input_value: serde_json::Value = serde_json::from_str(&input_json)
Expand Down Expand Up @@ -1955,7 +1923,7 @@ async fn run_offline_command(
async fn run_learn_command(sub: LearnSub) -> Result<()> {
use learnings::{
CorrectionType, LearningCaptureConfig, capture_correction, capture_failed_command,
correct_learning, list_all_entries, list_learnings, query_all_entries, query_learnings,
correct_learning, list_all_entries, query_all_entries,
};
let config = LearningCaptureConfig::default();

Expand Down Expand Up @@ -2070,7 +2038,7 @@ async fn run_learn_command(sub: LearnSub) -> Result<()> {
let ct: CorrectionType = correction_type
.parse()
.unwrap_or(CorrectionType::Other(correction_type.clone()));
let mut correction = capture_correction(ct, &original, &corrected, &context, &config);
let correction = capture_correction(ct, &original, &corrected, &context, &config);
if let Some(ref sid) = session_id {
// We need to read the file and update it with session_id
// For now, just print the session_id
Expand Down Expand Up @@ -2113,7 +2081,7 @@ async fn run_server_command(
} => {
// Get selected role from server if not specified
let role_name = if let Some(role) = role {
RoleName::new(&role)
api.resolve_role(&role).await?
} else {
let config_res = api.get_config().await?;
config_res.config.selected_role
Expand Down
Loading