Skip to content
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
287 changes: 225 additions & 62 deletions Cargo.lock

Large diffs are not rendered by default.

24 changes: 8 additions & 16 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ gateway = { path = "crates/gateway", package = "walrus-gateway", version = "0.0.
cli = { path = "crates/cli", package = "openwalrus", version = "0.0.10" }
daemon = { path = "crates/daemon", package = "walrus-daemon", version = "0.0.10" }
model = { path = "crates/model", package = "walrus-model", version = "0.0.10" }
socket = { path = "crates/socket", package = "walrus-socket", version = "0.0.10" }
tcp = { path = "crates/tcp", package = "walrus-tcp", version = "0.0.10" }
transport = { path = "crates/transport", package = "walrus-transport", version = "0.0.10" }
wcore = { path = "crates/core", package = "walrus-core", version = "0.0.10" }
wsearch = { path = "apps/search", package = "walrus-search", version = "0.0.10", default-features = false }

Expand All @@ -26,22 +25,18 @@ crabtalk-provider = "0.0.6"

# crates.io
anyhow = "1"
arrow-array = "56"
arrow-schema = "56"
async-stream = "0.3"
chrono = "0.4"
clap = { version = "4", features = ["derive"] }
compact_str = { version = "0.8", features = ["serde"] }
console = "0.15"
crossterm = "0.28"
dialoguer = "0.11"
dirs = "6"
dotenvy = "0.15"
heck = "0.5"
futures = "0.3"
futures-core = "0.3"
openssl = { version = "0.10", features = ["vendored"] }
futures-util = "0.3"
lru = "0.14"
openssl-sys = { version = "0.9", features = ["vendored"] }
percent-encoding = "2"
rand = "0.9"
ratatui = "0.29"
Expand All @@ -54,15 +49,13 @@ reqwest = { version = "0.12", default-features = false, features = [
"rustls-tls",
] }
rmcp = { version = "0.16", features = ["client", "transport-child-process"] }
rusqlite = { version = "0.34", features = ["bundled"] }
rustyline = { version = "15", features = ["derive"] }
schemars = "1.1.0"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
smallvec = { version = "1", features = ["serde"] }
serde_yml = "0.0.12"
syntect = { version = "5", default-features = false, features = ["default-fancy"] }
thiserror = "2"
tokenizers = { version = "0.21", default-features = false, features = ["onig"] }
tokio = { version = "1", features = [
"rt-multi-thread",
"macros",
Expand All @@ -73,12 +66,11 @@ tokio = { version = "1", features = [
"time",
] }
teloxide = { version = "0.17", default-features = false, features = ["rustls"] }
textwrap = "0.16"
toml = "0.8"
proc-macro2 = "1"
quote = "1"
toml_edit = "0.22"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-subscriber = "0.3"
url = "2"

[profile.prod]
Expand All @@ -93,4 +85,4 @@ overflow-checks = false
strip = true

[workspace.metadata.conta]
packages = ["wcore", "socket", "gateway", "model", "daemon", "cli"]
packages = ["wcore", "transport", "gateway", "model", "daemon", "cli"]
18 changes: 3 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,38 +17,27 @@ Or `cargo install openwalrus`. See the [installation guide][install] for details

```bash
# Start the daemon
walrus daemon
walrus daemon install

# Chat with your agent
walrus attach
```

Point it at any LLM — [Ollama][providers], [OpenAI, Anthropic, DeepSeek][remote], or any OpenAI-compatible API.

```toml
[system.walrus]
model = "qwen3:4b"

[model.qwen3]
base_url = "http://localhost:11434/v1"
```

Full config reference: [configuration][config].

## How It Works

Walrus is a daemon that runs [agents] and dispatches tools. The daemon
ships with built-in [tools] (file I/O, shell, task delegation),
ships with built-in [tools] (shell, task delegation, memory),
[MCP][mcp] server integration, and [skills] (Markdown prompt files).

Heavier capabilities live outside the daemon as [extensions][services] —
managed child processes you add or remove in config:

| Service | What it does |
| ------------------ | -------------------------------------------- |
| [Memory][memory] | Graph memory — LanceDB + semantic embeddings |
| [Search][search] | Meta-search aggregator |
| [Gateway][gateway] | Telegram, Discord adapters |
| [Gateway][gateway] | Telegram adapter |

The daemon stays small. Services scale independently.

Expand Down Expand Up @@ -84,7 +73,6 @@ GPL-3.0
[agents]: https://openwalrus.xyz/docs/development/concepts/agents
[runtime]: https://openwalrus.xyz/docs/development/concepts/runtime
[services]: https://openwalrus.xyz/docs/walrus/extensions
[memory]: https://openwalrus.xyz/docs/walrus/extensions/memory
[search]: https://openwalrus.xyz/docs/walrus/extensions/search
[gateway]: https://openwalrus.xyz/docs/walrus/extensions/gateway
[tools]: https://openwalrus.xyz/docs/development/tools/built-in
Expand Down
1 change: 1 addition & 0 deletions _typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
mmaped = "mmaped"
ratatui = "ratatui"
ba = "ba"
hel = "hel"
12 changes: 10 additions & 2 deletions apps/search/src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@ use walrus_search::cmd::App;

#[tokio::main]
async fn main() {
if std::env::var_os("RUST_LOG").is_some() {
if let Some(level) = std::env::var("RUST_LOG").ok().map(|v| {
match v.rsplit('=').next().unwrap_or(&v).to_lowercase().as_str() {
"trace" => tracing::Level::TRACE,
"debug" => tracing::Level::DEBUG,
"info" => tracing::Level::INFO,
"error" => tracing::Level::ERROR,
_ => tracing::Level::WARN,
}
}) {
tracing_subscriber::fmt()
.with_writer(std::io::stderr)
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.with_max_level(level)
.init();
}

Expand Down
1 change: 0 additions & 1 deletion apps/telegram/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ cli = ["dep:clap", "dep:tracing-subscriber"]
[dependencies]
anyhow.workspace = true
clap = { workspace = true, optional = true }
compact_str.workspace = true
futures-util.workspace = true
gateway.workspace = true
serde_json.workspace = true
Expand Down
16 changes: 13 additions & 3 deletions apps/telegram/src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,19 @@ use walrus_telegram::cmd::{App, Command};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();
let level = std::env::var("RUST_LOG")
.ok()
.map(
|v| match v.rsplit('=').next().unwrap_or(&v).to_lowercase().as_str() {
"trace" => tracing::Level::TRACE,
"debug" => tracing::Level::DEBUG,
"info" => tracing::Level::INFO,
"error" => tracing::Level::ERROR,
_ => tracing::Level::WARN,
},
)
.unwrap_or(tracing::Level::WARN);
tracing_subscriber::fmt().with_max_level(level).init();
let app = App::parse();
match app.command {
Command::Serve { daemon, config } => {
Expand Down
9 changes: 4 additions & 5 deletions apps/telegram/src/cmd/serve.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//! Telegram gateway serve command.

use compact_str::CompactString;
use gateway::{
COMMAND_HINT, DaemonClient, GatewayConfig, GatewayMessage, KnownBots, StreamAccumulator,
StreamResult, attachment_summary, parse_command,
Expand Down Expand Up @@ -48,15 +47,15 @@ pub async fn run(daemon_socket: &str, config_json: &str) -> anyhow::Result<()> {
async fn spawn_telegram(
token: &str,
allowed_users: &[i64],
agent: CompactString,
agent: String,
client: Arc<DaemonClient>,
known_bots: KnownBots,
) {
let bot = Bot::new(token);

match bot.get_me().await {
Ok(me) => {
let bot_sender: CompactString = format!("tg:{}", me.id.0).into();
let bot_sender = format!("tg:{}", me.id.0);
tracing::info!(platform = "telegram", %bot_sender, "registered bot identity");
known_bots.write().await.insert(bot_sender);
}
Expand Down Expand Up @@ -100,7 +99,7 @@ async fn spawn_telegram(
async fn telegram_loop(
mut rx: mpsc::UnboundedReceiver<GatewayMessage>,
bot: Bot,
agent: CompactString,
agent: String,
client: Arc<DaemonClient>,
known_bots: KnownBots,
allowed_users: std::collections::HashSet<i64>,
Expand All @@ -109,7 +108,7 @@ async fn telegram_loop(
while let Some(msg) = rx.recv().await {
let chat_id = msg.chat_id;
let content = msg.content.clone();
let sender: CompactString = format!("tg:{}", msg.sender_id).into();
let sender = format!("tg:{}", msg.sender_id);

if known_bots.read().await.contains(&sender) {
tracing::debug!(%sender, chat_id, "dropping message from known bot");
Expand Down
5 changes: 1 addition & 4 deletions apps/telegram/src/telegram/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
pub mod command;
pub mod markdown;

use compact_str::CompactString;
use futures_util::StreamExt;
use gateway::message::{Attachment, AttachmentKind, GatewayMessage};
use teloxide::prelude::*;
Expand Down Expand Up @@ -46,9 +45,7 @@ fn convert_update(update: Update) -> Option<GatewayMessage> {
let chat_id = msg.chat.id.0;
let sender = msg.from.as_ref();
let sender_id = sender.map(|u| u.id.0 as i64).unwrap_or(0);
let sender_name = sender
.map(|u| CompactString::from(u.first_name.as_str()))
.unwrap_or_default();
let sender_name = sender.map(|u| u.first_name.clone()).unwrap_or_default();
let is_bot = sender.is_some_and(|u| u.is_bot);
let is_group = matches!(msg.chat.kind, ChatKind::Public(_));
let content = msg.text().unwrap_or("").to_owned();
Expand Down
10 changes: 6 additions & 4 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,25 @@ path = "src/bin/main.rs"

[dependencies]
daemon.workspace = true
socket.workspace = true
tcp.workspace = true
transport.workspace = true
wcore.workspace = true

# crates.io
anyhow.workspace = true
dirs.workspace = true
async-stream.workspace = true
clap.workspace = true
console.workspace = true
crossterm.workspace = true
dialoguer.workspace = true
openssl-sys.workspace = true
compact_str.workspace = true
futures-core.workspace = true
futures-util.workspace = true
heck.workspace = true
ratatui.workspace = true
rustyline.workspace = true
serde_json.workspace = true
syntect.workspace = true
textwrap.workspace = true
tokio.workspace = true
toml.workspace = true
toml_edit.workspace = true
Expand Down
24 changes: 19 additions & 5 deletions crates/cli/src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,36 @@
use anyhow::Result;
use clap::Parser;
use openwalrus::Cli;
use tracing_subscriber::EnvFilter;

#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();

let filter = match cli.log_filter() {
let level = match cli.log_filter() {
Some(f) => {
// Set RUST_LOG so spawned child services inherit the same level.
// SAFETY: called in main before spawning any threads.
unsafe { std::env::set_var("RUST_LOG", f) };
EnvFilter::new(f)
parse_level(f)
}
None => EnvFilter::from_default_env(),
None => std::env::var("RUST_LOG")
.ok()
.map(|v| parse_level(&v))
.unwrap_or(tracing::Level::WARN),
};
tracing_subscriber::fmt().with_env_filter(filter).init();
tracing_subscriber::fmt().with_max_level(level).init();

cli.run().await
}

/// Extract the most specific level from a filter string like "walrus=debug".
fn parse_level(s: &str) -> tracing::Level {
let level_str = s.rsplit('=').next().unwrap_or(s);
match level_str.to_lowercase().as_str() {
"trace" => tracing::Level::TRACE,
"debug" => tracing::Level::DEBUG,
"info" => tracing::Level::INFO,
"error" => tracing::Level::ERROR,
_ => tracing::Level::WARN,
}
}
5 changes: 3 additions & 2 deletions crates/cli/src/cmd/attach.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use crate::cmd::auth::PRESETS;
use crate::repl::{ChatRepl, runner::Runner};
use anyhow::Result;
use clap::Args;
use compact_str::CompactString;
use dialoguer::{Input, Select, theme::ColorfulTheme};
use std::path::Path;
use toml_edit::{Array, DocumentMut, Item, Table, value};
Expand All @@ -21,7 +20,7 @@ pub struct Attach {

impl Attach {
/// Enter the interactive REPL with the given runner and agent.
pub async fn run(self, runner: Runner, agent: CompactString) -> Result<()> {
pub async fn run(self, runner: Runner, agent: String) -> Result<()> {
let mut repl = ChatRepl::new(runner, agent)?;
repl.run().await
}
Expand Down Expand Up @@ -119,7 +118,9 @@ fn default_model_for(provider: &str) -> &str {
"anthropic" => "claude-sonnet-4-5-20250514",
"openai" => "gpt-4o",
"deepseek" => "deepseek-chat",
"google" => "gemini-2.5-pro",
"ollama" => "llama3",
"azure" => "gpt-4o",
_ => "default",
}
}
18 changes: 14 additions & 4 deletions crates/cli/src/cmd/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,32 @@ pub(crate) const PRESETS: &[Preset] = &[
Preset {
name: "openai",
base_url: "https://api.openai.com/v1",
standard: "openai",
standard: "openai_compat",
},
Preset {
name: "deepseek",
base_url: "https://api.deepseek.com/v1",
standard: "openai",
standard: "openai_compat",
},
Preset {
name: "google",
base_url: "",
standard: "google",
},
Preset {
name: "ollama",
base_url: "http://localhost:11434/v1",
standard: "openai",
standard: "ollama",
},
Preset {
name: "azure",
base_url: "",
standard: "azure",
},
Preset {
name: "custom",
base_url: "",
standard: "openai",
standard: "openai_compat",
},
];

Expand Down
Loading
Loading