Architecture Overview
Cargo workspace (Edition 2024, resolver 3) with 10 crates + binary root.
Requires Rust 1.88+. Native async traits are used throughout — no async-trait crate.
Workspace Layout
zeph (binary) — thin CLI/channel dispatch, delegates to AppBuilder
├── zeph-core Agent loop, bootstrap/AppBuilder, config, config hot-reload, channel trait, context builder
├── zeph-llm LlmProvider trait, Ollama + Claude + OpenAI + Candle backends, orchestrator, embeddings
├── zeph-skills SKILL.md parser, registry with lazy body loading, embedding matcher, resource resolver, hot-reload
├── zeph-memory SQLite + Qdrant, SemanticMemory orchestrator, summarization
├── zeph-channels Telegram adapter (teloxide) with streaming
├── zeph-tools ToolExecutor trait, ShellExecutor, WebScrapeExecutor, CompositeExecutor, TrustLevel
├── zeph-index AST-based code indexing, hybrid retrieval, repo map (always-on)
├── zeph-mcp MCP client via rmcp, multi-server lifecycle, unified tool matching (optional)
├── zeph-a2a A2A protocol client + server, agent discovery, JSON-RPC 2.0 (optional)
└── zeph-tui ratatui TUI dashboard with real-time metrics (optional)
Dependency Graph
zeph (binary)
├── zeph-core (orchestrates everything)
│ ├── zeph-llm (leaf)
│ ├── zeph-skills (leaf)
│ ├── zeph-memory (leaf)
│ ├── zeph-channels (leaf)
│ ├── zeph-tools (leaf)
│ ├── zeph-index (leaf)
│ ├── zeph-mcp (optional, leaf)
│ └── zeph-tui (optional, leaf)
└── zeph-a2a (optional, wired by binary, not by zeph-core)
zeph-core is the only crate that depends on other workspace crates. All leaf crates are independent and can be tested in isolation. zeph-a2a is feature-gated and wired directly by the binary — zeph-core does not depend on it. Sub-agent lifecycle state (SubAgentState) is defined inside zeph-core to keep the core agent loop self-contained.
Agent Loop
The agent loop processes user input in a continuous cycle:
- Read initial user message via
channel.recv() - Build context from skills, memory, and environment (summaries, cross-session recall, semantic recall, and code RAG are fetched concurrently via
try_join!) - Stream LLM response token-by-token
- Execute any tool calls in the response
- Drain queued messages (if any) via
channel.try_recv()and repeat from step 2
Queued messages are processed sequentially with full context rebuilding between each. Consecutive messages within 500ms are merged to reduce fragmentation. The queue holds a maximum of 10 messages; older messages are dropped when full.
Key Design Decisions
- Generic Agent:
Agent<C: Channel>— generic over channel only. The provider is resolved at construction time (AnyProviderenum dispatch). Tool execution usesBox<dyn ErasedToolExecutor>for object-safe dynamic dispatch, eliminating the formerT: ToolExecutorgeneric parameter. Internal state is grouped into five domain structs (MemoryState,SkillState,ContextState,McpState,IndexState) with logic decomposed intostreaming.rs,persistence.rs, and three dedicated subsystems:ContextManager(budget / compaction),ToolOrchestrator(doom-loop detection / iteration limit), andLearningEngine(self-learning reflection state) - TLS: rustls everywhere (no openssl-sys)
- Bootstrap:
AppBuilderinzeph-core::bootstrap/(split intomod.rs,config.rs,health.rs,mcp.rs,provider.rs,skills.rs) handles config/vault resolution, provider creation, memory setup, skill matching, tool executor composition, and graceful shutdown wiring.main.rs(26 LOC) is a thin entry point delegating torunner.rsfor channel/mode dispatch - Binary structure:
zephbinary is decomposed into focused modules —runner.rs(dispatch),agent_setup.rs(tool executor + MCP + feature extensions),tracing_init.rs,tui_bridge.rs,channel.rs,cli.rs(clap args),acp.rs,daemon.rs,scheduler.rs,commands/(vault/skill/memory subcommands),tests.rs - Errors:
thiserrorfor all crates with typed error enums (ChannelError,AgentError,LlmError, etc.);anyhowonly for top-level orchestration inrunner.rs - Lints: workspace-level
clippy::all+clippy::pedantic+clippy::nursery;unsafe_code = "deny" - Dependencies: versions only in root
[workspace.dependencies]; crates inherit viaworkspace = true - Feature gates: optional crates (
zeph-mcp,zeph-a2a,zeph-tui) are feature-gated in the binary;zeph-indexis always-on with all tree-sitter language grammars (Rust, Python, JS/TS, Go) compiled unconditionally - Context engineering: proportional budget allocation, semantic recall injection, message trimming, runtime compaction, environment context injection, progressive skill loading, ZEPH.md project config discovery
- Graceful shutdown: Ctrl-C triggers ordered teardown — the agent loop exits cleanly, MCP server connections are closed, and pending async tasks are drained before process exit
- LoopbackChannel: headless
Channelimplementation using two linked tokio mpsc pairs (input_tx/input_rxfor user messages,output_tx/output_rxforLoopbackEventvariants). Auto-approves confirmations. Used by daemon mode to bridge the A2A task processor with the agent loop - Streaming TaskProcessor:
ProcessorEventenum (StatusUpdate,ArtifactChunk) replaces the former synchronousProcessResult. TheTaskProcessor::processmethod accepts anmpsc::Sender<ProcessorEvent>for per-token SSE streaming to connected A2A clients