Skip to content
This repository was archived by the owner on Apr 3, 2026. It is now read-only.
Merged
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
6,392 changes: 5,379 additions & 1,013 deletions Cargo.lock

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
resolver = "3"
members = ["app/*", "crates/*", "llm/*"]
members = ["app/*", "crates/*"]

[workspace.package]
version = "0.0.9"
Expand All @@ -11,15 +11,11 @@ repository = "https://github.com/clearloop/walrus"
keywords = ["llm", "agent", "ai"]

[workspace.dependencies]
deepseek = { path = "llm/deepseek", package = "walrus-deepseek", version = "0.0.9" }
openai = { path = "llm/openai", package = "walrus-openai", version = "0.0.9" }
claude = { path = "llm/claude", package = "walrus-claude", version = "0.0.9" }
mistral = { path = "llm/mistral", package = "walrus-mistral", version = "0.0.9" }
llm = { path = "crates/llm", package = "walrus-llm", version = "0.0.9", default-features = true }
wcore = { path = "crates/core", package = "walrus-core", version = "0.0.9" }
model = { path = "crates/model", package = "walrus-model", version = "0.0.9" }
runtime = { path = "crates/runtime", package = "walrus-runtime", version = "0.0.9" }
sqlite = { path = "crates/sqlite", package = "walrus-sqlite", version = "0.0.9" }
telegram = { path = "crates/telegram", package = "walrus-telegram", version = "0.0.9" }
memory = { path = "crates/memory", package = "walrus-memory", version = "0.0.9" }
channel = { path = "crates/channel", package = "walrus-channel", version = "0.0.9" }
protocol = { path = "app/protocol", package = "walrus-protocol", version = "0.0.9" }
daemon = { path = "app/daemon", package = "walrus-daemon", version = "0.0.9" }
client = { path = "app/client", package = "walrus-client", version = "0.0.9" }
Expand Down Expand Up @@ -57,3 +53,4 @@ clap = { version = "4", features = ["derive"] }
rustyline = "15"
crossterm = "0.29"
dirs = "6"
mistralrs = { version = "0.7", default-features = false }
6 changes: 3 additions & 3 deletions app/cli/src/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ impl Cli {
/// Resolve the socket path from CLI flag or default.
fn resolve_socket(&self) -> PathBuf {
self.socket.clone().unwrap_or_else(|| {
dirs::config_dir()
.expect("no platform config directory")
.join("walrus")
dirs::home_dir()
.expect("no home directory")
.join(".walrus")
.join("walrus.sock")
})
}
Expand Down
10 changes: 5 additions & 5 deletions app/cli/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
//! Configuration resolution for the CLI.
//!
//! Resolves the path to `~/.config/walrus/gateway.toml` for config commands.
//! Resolves the path to `~/.walrus/walrus.toml` for config commands.

use std::path::PathBuf;

/// Resolve the config file path.
pub fn resolve_config_path() -> PathBuf {
dirs::config_dir()
.expect("no platform config directory")
.join("walrus")
.join("gateway.toml")
dirs::home_dir()
.expect("no home directory")
.join(".walrus")
.join("walrus.toml")
}
4 changes: 2 additions & 2 deletions app/cli/src/repl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ impl<R: Runner> ChatRepl<R> {
}
}

/// Resolve the history file path at `~/.config/walrus/history`.
/// Resolve the history file path at `~/.walrus/history`.
fn history_file_path() -> Option<PathBuf> {
dirs::config_dir().map(|d| d.join("walrus").join("history"))
dirs::home_dir().map(|d| d.join(".walrus").join("history"))
}

/// Consume a stream of content chunks and print them to stdout in real time.
Expand Down
2 changes: 1 addition & 1 deletion app/cli/tests/prefs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ use walrus_cli::config::resolve_config_path;
#[test]
fn config_path_global_default() {
let path = resolve_config_path();
assert!(path.ends_with("walrus/gateway.toml"));
assert!(path.ends_with(".walrus/walrus.toml"));
}
8 changes: 4 additions & 4 deletions app/client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ impl Default for ClientConfig {
}
}

/// Default socket path: `~/.config/walrus/walrus.sock`.
/// Default socket path: `~/.walrus/walrus.sock`.
fn default_socket_path() -> PathBuf {
dirs::config_dir()
.expect("no platform config directory")
.join("walrus")
dirs::home_dir()
.expect("no home directory")
.join(".walrus")
.join("walrus.sock")
}

Expand Down
2 changes: 1 addition & 1 deletion app/client/tests/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use walrus_client::{ClientConfig, WalrusClient};
#[test]
fn client_config_defaults() {
let config = ClientConfig::default();
assert!(config.socket_path.ends_with("walrus/walrus.sock"));
assert!(config.socket_path.ends_with(".walrus/walrus.sock"));
}

#[test]
Expand Down
11 changes: 2 additions & 9 deletions app/daemon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,8 @@ path = "src/bin/main.rs"
[dependencies]
runtime = { workspace = true }
wcore = { workspace = true }
deepseek = { workspace = true }
openai = { workspace = true }
claude = { workspace = true }
mistral = { workspace = true }
sqlite = { workspace = true }
llm = { workspace = true }
model = { workspace = true }
memory = { workspace = true }
protocol = { workspace = true }
compact_str = { workspace = true }
serde = { workspace = true }
Expand All @@ -33,9 +29,6 @@ smallvec = { workspace = true }
cron = { workspace = true }
chrono = { workspace = true }
rmcp = { workspace = true }
futures-core = { workspace = true }
futures-util = { workspace = true }
async-stream = { workspace = true }
dirs = { workspace = true }

[dev-dependencies]
Expand Down
2 changes: 1 addition & 1 deletion app/daemon/src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ async fn main() -> Result<()> {
tracing::info!("created config directory at {}", config_dir.display());
}

let handle = walrus_daemon::serve(&config_dir, None).await?;
let handle = walrus_daemon::serve(&config_dir).await?;
tracing::info!("walrusd listening on {}", handle.socket_path.display());

signal::ctrl_c().await?;
Expand Down
138 changes: 32 additions & 106 deletions app/daemon/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
//! Gateway configuration loaded from TOML.
//! Daemon configuration loaded from TOML (DD#67).

use anyhow::{Context, Result};
use compact_str::CompactString;
pub use model::{ProviderConfig, ProviderManager};
use serde::{Deserialize, Serialize};
use std::path::Path;
use std::path::{Path, PathBuf};

/// Config directory name under platform config dir.
pub const CONFIG_DIR: &str = "walrus";
/// Agents subdirectory (contains *.md files).
pub const AGENTS_DIR: &str = "agents";
/// Skills subdirectory.
Expand All @@ -18,23 +17,21 @@ pub const DATA_DIR: &str = "data";
/// SQLite memory database filename.
pub const MEMORY_DB: &str = "memory.db";

/// Resolve the global configuration directory (`~/.config/walrus/` on unix).
pub fn global_config_dir() -> std::path::PathBuf {
dirs::config_dir()
.expect("no platform config directory")
.join(CONFIG_DIR)
/// Resolve the global configuration directory (`~/.walrus/`).
pub fn global_config_dir() -> PathBuf {
dirs::home_dir().expect("no home directory").join(".walrus")
}

/// Top-level gateway configuration.
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct GatewayConfig {
/// Server bind configuration.
pub server: ServerConfig,
/// LLM provider configuration.
pub llm: LlmConfig,
/// Memory backend configuration.
#[serde(default)]
pub memory: MemoryConfig,
/// Pinned socket path (`~/.walrus/walrus.sock`).
pub fn socket_path() -> PathBuf {
global_config_dir().join("walrus.sock")
}

/// Top-level daemon configuration.
#[derive(Debug, Serialize, Deserialize)]
pub struct DaemonConfig {
/// LLM provider configurations (`[[models]]` array).
pub models: Vec<ProviderConfig>,
/// Channel configurations.
#[serde(default)]
pub channels: Vec<ChannelConfig>,
Expand All @@ -43,84 +40,23 @@ pub struct GatewayConfig {
pub mcp_servers: Vec<McpServerConfig>,
}

/// Server configuration.
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct ServerConfig {
/// Custom Unix domain socket path. When `None`, defaults to
/// `<config_dir>/walrus.sock`.
pub socket_path: Option<String>,
}

/// LLM provider configuration.
#[derive(Debug, Serialize, Deserialize)]
pub struct LlmConfig {
/// Which LLM provider to use.
#[serde(default)]
pub provider: ProviderKind,
/// Model identifier.
pub model: CompactString,
/// API key (supports `${ENV_VAR}` expansion).
#[serde(default)]
pub api_key: String,
/// Optional base URL override for the provider endpoint.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub base_url: Option<String>,
}

impl Default for LlmConfig {
impl Default for DaemonConfig {
fn default() -> Self {
Self {
provider: ProviderKind::DeepSeek,
model: "deepseek-chat".into(),
api_key: "${DEEPSEEK_API_KEY}".to_owned(),
base_url: None,
models: vec![ProviderConfig {
model: "deepseek-chat".into(),
api_key: Some("${DEEPSEEK_API_KEY}".to_owned()),
base_url: None,
loader: None,
quantization: None,
chat_template: None,
}],
channels: Vec::new(),
mcp_servers: Vec::new(),
}
}
}

/// Supported LLM provider kinds.
#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ProviderKind {
/// DeepSeek API (default).
#[default]
DeepSeek,
/// OpenAI API.
OpenAI,
/// Grok (xAI) API — OpenAI-compatible.
Grok,
/// Qwen (Alibaba DashScope) API — OpenAI-compatible.
Qwen,
/// Kimi (Moonshot) API — OpenAI-compatible.
Kimi,
/// Ollama local API — OpenAI-compatible, no key required.
Ollama,
/// Claude (Anthropic) Messages API.
Claude,
/// Mistral chat completions API.
Mistral,
}

/// Memory backend configuration.
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct MemoryConfig {
/// Backend type: "in_memory" or "sqlite".
pub backend: MemoryBackendKind,
}

/// Memory backend kind.
#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum MemoryBackendKind {
/// In-memory backend (no persistence).
#[default]
InMemory,
/// SQLite-backed persistent memory.
Sqlite,
}

/// Channel configuration.
#[derive(Debug, Serialize, Deserialize)]
pub struct ChannelConfig {
Expand Down Expand Up @@ -167,8 +103,8 @@ tools:
You are a helpful assistant. Be concise.
"#;

impl GatewayConfig {
/// Parse a TOML string into a `GatewayConfig`, expanding environment
impl DaemonConfig {
/// Parse a TOML string into a `DaemonConfig`, expanding environment
/// variables in supported fields.
pub fn from_toml(toml_str: &str) -> anyhow::Result<Self> {
let expanded = crate::utils::expand_env_vars(toml_str);
Expand All @@ -181,22 +117,12 @@ impl GatewayConfig {
let content = std::fs::read_to_string(path)?;
Self::from_toml(&content)
}

/// Resolve the socket path. Uses the explicit config value if set,
/// otherwise defaults to `<config_dir>/walrus.sock`.
pub fn socket_path(&self, config_dir: &std::path::Path) -> std::path::PathBuf {
self.server
.socket_path
.as_ref()
.map(std::path::PathBuf::from)
.unwrap_or_else(|| config_dir.join("walrus.sock"))
}
}

/// Scaffold the full config directory structure on first run.
///
/// Creates subdirectories (agents, skills, cron, data), writes a default
/// gateway.toml and a default assistant agent markdown file.
/// walrus.toml and a default assistant agent markdown file.
pub fn scaffold_config_dir(config_dir: &Path) -> Result<()> {
std::fs::create_dir_all(config_dir.join(AGENTS_DIR))
.context("failed to create agents directory")?;
Expand All @@ -207,8 +133,8 @@ pub fn scaffold_config_dir(config_dir: &Path) -> Result<()> {
std::fs::create_dir_all(config_dir.join(DATA_DIR))
.context("failed to create data directory")?;

let gateway_toml = config_dir.join("gateway.toml");
let contents = toml::to_string_pretty(&GatewayConfig::default())
let gateway_toml = config_dir.join("walrus.toml");
let contents = toml::to_string_pretty(&DaemonConfig::default())
.context("failed to serialize default config")?;
std::fs::write(&gateway_toml, contents)
.with_context(|| format!("failed to write {}", gateway_toml.display()))?;
Expand Down
2 changes: 1 addition & 1 deletion app/daemon/src/feature/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//! delegation, following the Provider enum pattern (DD#22).

use anyhow::Result;
use sqlite::SqliteMemory;
use memory::SqliteMemory;
use std::future::Future;
use wcore::{InMemory, Memory, MemoryEntry, NoEmbedder, RecallOptions};

Expand Down
Loading