Skip to content

Commit b72ee67

Browse files
committed
refactor(workspace): migrate to hexagonal architecture, add alice-cli, adapters, and core
1 parent 3af8063 commit b72ee67

50 files changed

Lines changed: 4157 additions & 1040 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
version = "0.1.0"
33
edition = "2024"
44
license = "Apache-2.0"
5-
repository = "TODO"
5+
repository = "https://github.com/longcipher/alice"
66

77
[workspace]
88
members = ["bin/*", "crates/*"]
@@ -11,9 +11,9 @@ resolver = "3"
1111

1212
[workspace.dependencies]
1313
async-trait = "0.1.89"
14-
bob-adapters = "0.1.2"
15-
bob-core = "0.1.2"
16-
bob-runtime = "0.1.2"
14+
bob-adapters = "0.2.0"
15+
bob-core = "0.2.0"
16+
bob-runtime = "0.2.0"
1717
clap = "4.5.60"
1818
config = "0.15.19"
1919
eyre = "0.6.12"

README.md

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,21 @@ Alice is a minimal, reusable AI agent application built with a hexagonal archite
55
## What Alice Includes
66

77
- Bob runtime orchestration (`bob-core`, `bob-runtime`, `bob-adapters`)
8-
- CLI interaction (`alice` command behavior in `bin/cli-app`)
9-
- Local memory system in `crates/common` with:
8+
- Hexagonal architecture with clean layer boundaries
9+
- Skill system integration — auto-selects relevant `SKILL.md` files per turn
10+
- Multi-channel adapters: CLI REPL, Discord, and Telegram
11+
- Local memory system with:
1012
- SQLite persistence
1113
- FTS5 full-text recall
1214
- sqlite-vec vector search
1315
- hybrid ranking (BM25 + vector similarity)
1416

1517
## Workspace Layout
1618

17-
- `bin/cli-app`: Alice CLI composition root
18-
- `crates/common`: shared memory domain/service/SQLite adapter
19+
- `bin/alice-cli`: thin CLI binary — clap parsing and dispatch
20+
- `crates/alice-core`: innermost layer — domain types, port traits, service logic (zero adapter deps)
21+
- `crates/alice-adapters`: concrete implementations — SQLite memory store, CLI/Discord/Telegram channel adapters
22+
- `crates/alice-runtime`: composition root — config, bootstrap, commands, skill wiring, channel runner
1923
- `specs/`: design and task specs
2024

2125
## Quick Start
@@ -29,21 +33,71 @@ just format
2933
just lint
3034
just test
3135

32-
# Run Alice (interactive)
33-
cargo run -p cli-app -- --config alice.toml
36+
# Run Alice (interactive chat)
37+
cargo run -p alice-cli -- --config alice.toml chat
3438

3539
# Run one prompt and exit
36-
cargo run -p cli-app -- --config alice.toml --once "summarize our current memory setup"
40+
cargo run -p alice-cli -- --config alice.toml run "summarize our current memory setup"
41+
42+
# Run with multi-channel support (CLI + enabled adapters)
43+
cargo run -p alice-cli -- --config alice.toml channel
3744
```
3845

3946
## Configuration
4047

4148
Use `alice.toml` in repo root as a starting point. Key sections:
4249

43-
- `[runtime]`: model and turn limits
50+
- `[runtime]`: model, turn limits, dispatch mode
4451
- `[memory]`: sqlite path and hybrid recall weights
52+
- `[skills]`: skill system — enable/disable, source directories, token budget
53+
- `[channels.discord]` / `[channels.telegram]`: channel adapters (require env vars)
4554
- `[[mcp.servers]]`: optional MCP tool servers
4655

47-
## Current Scope
56+
### Skills
57+
58+
Place `SKILL.md` files in a directory and configure the path in `alice.toml`:
59+
60+
```toml
61+
[skills]
62+
enabled = true
63+
max_selected = 3
64+
token_budget = 1800
65+
66+
[[skills.sources]]
67+
path = "./skills"
68+
recursive = true
69+
```
70+
71+
### Channels
72+
73+
Enable Discord and/or Telegram alongside the default CLI REPL:
74+
75+
```toml
76+
[channels.discord]
77+
enabled = true
78+
79+
[channels.telegram]
80+
enabled = true
81+
```
82+
83+
Set the corresponding environment variables:
84+
85+
```bash
86+
export ALICE_DISCORD_TOKEN="your-discord-bot-token"
87+
export ALICE_TELEGRAM_TOKEN="your-telegram-bot-token"
88+
```
89+
90+
## Building with Channel Features
4891

49-
Alice is intentionally CLI-first and local-first. Web gateways, multi-channel bots, and distributed memory are out of scope for this phase.
92+
Discord and Telegram adapters are behind feature flags:
93+
94+
```bash
95+
# Build with Discord support
96+
cargo build -p alice-cli --features discord
97+
98+
# Build with Telegram support
99+
cargo build -p alice-cli --features telegram
100+
101+
# Build with both
102+
cargo build -p alice-cli --features discord,telegram
103+
```

alice.toml

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,28 @@ vector_weight = 0.7
1212
vector_dimensions = 384
1313
enable_vector = true
1414

15-
# Optional MCP servers
15+
# Skill system configuration — auto-selects relevant SKILL.md files per turn.
16+
# [skills]
17+
# enabled = true
18+
# max_selected = 3
19+
# token_budget = 1800
20+
#
21+
# [[skills.sources]]
22+
# path = "./skills"
23+
# recursive = true
24+
25+
# Channel adapters — enable multi-channel input beyond the default CLI REPL.
26+
# Discord requires ALICE_DISCORD_TOKEN env var.
27+
# Telegram requires ALICE_TELEGRAM_TOKEN env var.
28+
# [channels.discord]
29+
# enabled = false
30+
# [channels.telegram]
31+
# enabled = false
32+
33+
# Built-in tools (local/file_read, local/file_write, local/file_list,
34+
# local/shell_exec) are always available. Additional MCP servers can be
35+
# configured below.
36+
1637
# [[mcp.servers]]
1738
# id = "filesystem"
1839
# command = "npx"

bin/alice-cli/Cargo.toml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[package]
2+
name = "alice-cli"
3+
version.workspace = true
4+
edition.workspace = true
5+
6+
[[bin]]
7+
name = "alice"
8+
path = "src/main.rs"
9+
10+
[dependencies]
11+
alice-runtime = { path = "../../crates/alice-runtime" }
12+
clap = { workspace = true, features = ["derive"] }
13+
eyre.workspace = true
14+
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
15+
tracing-subscriber.workspace = true
16+
17+
[dev-dependencies]
18+
alice-adapters = { path = "../../crates/alice-adapters" }
19+
alice-core = { path = "../../crates/alice-core" }
20+
async-trait.workspace = true
21+
bob-adapters.workspace = true
22+
bob-core.workspace = true
23+
bob-runtime.workspace = true
24+
parking_lot.workspace = true
25+
26+
[lints]
27+
workspace = true

bin/alice-cli/src/main.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//! Alice CLI binary.
2+
//!
3+
//! Supports three modes:
4+
//! - `run` — one-shot prompt execution
5+
//! - `chat` — interactive REPL with slash command routing
6+
//! - `channel` — message channel runtime (Telegram, Discord, etc.)
7+
8+
use std::sync::Arc;
9+
10+
use clap::{Parser, Subcommand};
11+
12+
/// Alice — a collaborative AI agent built on the Bob framework.
13+
#[derive(Debug, Parser)]
14+
#[command(name = "alice", version, about = "Alice CLI agent")]
15+
struct Cli {
16+
/// Path to configuration file.
17+
#[arg(short, long, default_value = "alice.toml", global = true)]
18+
config: String,
19+
20+
#[command(subcommand)]
21+
command: Option<Commands>,
22+
}
23+
24+
#[derive(Debug, Subcommand)]
25+
enum Commands {
26+
/// Run a single prompt and exit.
27+
Run {
28+
/// The prompt to execute.
29+
prompt: String,
30+
31+
/// Session identifier for memory continuity.
32+
#[arg(long, default_value = "alice-once")]
33+
session_id: String,
34+
},
35+
36+
/// Start an interactive REPL session.
37+
Chat {
38+
/// Session identifier for memory continuity.
39+
#[arg(long, default_value = "alice-session")]
40+
session_id: String,
41+
},
42+
43+
/// Run enabled message channels (Telegram, Discord, etc.).
44+
Channel,
45+
}
46+
47+
#[tokio::main]
48+
async fn main() -> eyre::Result<()> {
49+
tracing_subscriber::fmt().with_target(false).init();
50+
51+
let cli = Cli::parse();
52+
let cfg = alice_runtime::config::load_config(&cli.config)?;
53+
let context = Arc::new(alice_runtime::bootstrap::build_runtime(&cfg).await?);
54+
55+
match cli.command {
56+
Some(Commands::Run { prompt, session_id }) => {
57+
alice_runtime::commands::cmd_run(&context, &session_id, &prompt).await
58+
}
59+
Some(Commands::Chat { session_id }) => {
60+
alice_runtime::commands::cmd_chat(Arc::clone(&context), &session_id).await
61+
}
62+
Some(Commands::Channel) => {
63+
alice_runtime::commands::cmd_channel(Arc::clone(&context), &cfg.channels).await
64+
}
65+
// Default to chat when no subcommand is given.
66+
None => alice_runtime::commands::cmd_chat(Arc::clone(&context), "alice-session").await,
67+
}
68+
}

0 commit comments

Comments
 (0)