Crates
Each workspace crate has a focused responsibility. All leaf crates are independent and testable in isolation; only zeph-core depends on other workspace members.
zeph (binary)
Thin entry point (26 LOC main.rs) that delegates all work to focused submodules:
runner.rs— top-level dispatch: reads CLI flags, selects mode (ACP, TUI, CLI, daemon), and drives theAnyChannelloopagent_setup.rs— composes theToolExecutorchain, initialises the MCP manager, and wires feature-gated extensions (code index, candle-stt, whisper-stt, response cache, cost tracker, summary provider)tracing_init.rs— configures thetracing-subscriberstack (env filter, JSON/pretty format)tui_bridge.rs— TUI event forwarding and TUI session runnerchannel.rs— constructs the runtimeAnyChanneland CLI history buildercli.rs— clap argument definitionsacp.rs— ACP server/client startup logicdaemon.rs— daemon mode bootstrapscheduler.rs— scheduler bootstrapcommands/— subcommand handlers forvault,skill, andmemorymanagementtests.rs— unit tests for the binary crate
zeph-core
Agent loop, bootstrap orchestration, configuration loading, and context builder.
AppBuilder— bootstrap orchestrator inzeph-core::bootstrap/, decomposed into:mod.rs(278 LOC) —AppBuilderstruct and orchestration entry points:from_env(),build_provider()with health check,build_memory(),build_skill_matcher(),build_registry(),build_tool_executor(),build_watchers(),build_shutdown(),build_summary_provider()config.rs— config file resolution and vault argument parsinghealth.rs— health check and provider warmup logicmcp.rs— MCP manager and Qdrant tool registry creationprovider.rs— provider factory functionsskills.rs— skill matcher and embedding model helperstests.rs— unit tests for bootstrap logic
Agent<C>— main agent loop generic over channel only. Tool execution usesBox<dyn ErasedToolExecutor>for object-safe dynamic dispatch (noTgeneric). Provider is resolved at construction time (AnyProviderenum dispatch, noPgeneric). Streaming support, message queue drain. Internal state is grouped into five domain structs (MemoryState,SkillState,ContextState,McpState,IndexState); logic is decomposed intostreaming.rs,persistence.rs, and three dedicated subsystem structs described belowContextManager— owns context budget configuration,token_counter(Arc<TokenCounter>), compaction threshold (80%), compaction tail preservation, prune-protect token floor, and token safety margin. Exposesshould_compact()used by the agent loop before each LLM callToolOrchestrator— ownsdoom_loop_history(rolling hash window),max_iterations(default 10), summarize-tool-output flag, andOverflowConfig. Exposespush_doom_hash(),clear_doom_history(), andis_doom_loop()(returnstruewhen lastDOOM_LOOP_WINDOWhashes are identical)LearningEngine— ownsLearningConfigand per-turnreflection_usedflag. Exposesis_enabled(),mark_reflection_used(),was_reflection_used(), andreset_reflection()called at the start of each agent turnSubAgentState— state enum for sub-agent lifecycle (Idle,Working,Completed,Failed,Cancelled); defined inzeph-core::subagent::state, eliminating the former dependency onzeph-a2afor state typesAgentError— typed error enum covering LLM, memory, channel, tool, context, and I/O failures (replaces prioranyhowusage)Config— TOML config loading with env var overridesChanneltrait — abstraction for I/O (CLI, Telegram, TUI) withrecv(),try_recv(),send_queue_count()for queue management. ReturnsResult<_, ChannelError>with typed variants (Io,ChannelClosed,ConfirmationCancelled)- Context builder — assembles system prompt from skills, memory, summaries, environment, and project config
- Context engineering — proportional budget allocation, semantic recall injection, message trimming, runtime compaction
EnvironmentContext— runtime gathering of cwd, git branch, OS, model nameproject.rs— ZEPH.md config discovery (walk up directory tree)VaultProvidertrait — pluggable secret resolutionMetricsSnapshot/MetricsCollector— real-time metrics viatokio::sync::watchfor TUI dashboardDaemonSupervisor— component lifecycle monitor with health polling, PID file management, restart trackingLoopbackChannel/LoopbackHandle/LoopbackEvent— headless channel for daemon mode using paired tokio mpsc channels; auto-approves confirmationsLoopbackHandle::cancel_signal—Arc<Notify>shared between the ACP session and the agent loop; callingnotify_one()interrupts the running agent turnhash::content_hash()— BLAKE3-based utility returning a hex-encoded content hash for any byte slice; used for delta-sync checks and integrity verification across crates; available aszeph_core::content_hashDiffData— re-exported fromzeph_tools::executor::DiffDataaszeph_core::DiffData; thezeph-core::diffmodule has been removed in favour of this direct re-export
zeph-llm
LLM provider abstraction and backend implementations.
LlmProvidertrait —chat(),chat_typed(),chat_stream(),embed(),supports_streaming(),supports_embeddings(),supports_vision()MessagePart::Image— image content part (raw bytes + MIME type) for multimodal inputEmbedFuture/EmbedFn— canonical type aliases for embedding closures, re-exported by downstream crates (zeph-skills,zeph-mcp)OllamaProvider— local inference via ollama-rsClaudeProvider— Anthropic Messages API with SSE streamingOpenAiProvider— OpenAI + compatible APIs (raw reqwest)CandleProvider— local GGUF model inference via candleAnyProvider— enum dispatch for runtime provider selection, generated viadelegate_provider!macroSpeechToTexttrait — async transcription interface returningTranscription(text + duration + language)WhisperProvider— OpenAI Whisper API backend (feature-gated:stt)ModelOrchestrator— task-based multi-model routing with fallback chains
zeph-skills
SKILL.md loader, skill registry, and prompt formatter.
SkillMeta/Skill— metadata + lazy body loading viaOnceLockSkillRegistry— manages skill lifecycle, lazy body accessSkillMatcher— in-memory cosine similarity matchingQdrantSkillMatcher— persistent embeddings with BLAKE3 delta syncformat_skills_prompt()— assembles prompt with OS-filtered resourcesformat_skills_catalog()— description-only entries for non-matched skillsresource.rs—discover_resources()+load_resource()with path traversal protection and canonical path validation; lazy resource loading (resources resolved on first activation, not at startup)- File reference validation — local links in skill bodies are checked against the skill directory; broken references and path traversal attempts are rejected at load time
sanitize_skill_body()— escapes XML-like structural tags in untrusted (non-Trusted) skill bodies before prompt injection, preventing prompt boundary confusionTrustLevel— re-exported fromzeph-tools::trust_levelfor use by skill trust logic; the canonical definition lives inzeph-tools- Filesystem watcher for hot-reload (500ms debounce)
zeph-memory
SQLite-backed conversation persistence with Qdrant vector search.
SqliteStore— conversations, messages, summaries, skill usage, skill versions, ACP session persistence (acp_sessions.rs)QdrantOps— shared helper consolidating common Qdrant operations (ensure_collection, upsert, search, delete, scroll), used byQdrantStore,CodeStore,QdrantSkillMatcher, andMcpToolRegistryQdrantStore— vector storage and cosine similarity search withMessageKindenum (Regular|Summary) for payload classificationSemanticMemory<P>— orchestrator coordinating SQLite + Qdrant + LlmProviderEmbeddabletrait — generic interface for types that can be embedded and synced to Qdrant (providesid,content_for_embedding,content_hash,to_payload)EmbeddingRegistry<T: Embeddable>— generic Qdrant sync/search engine: delta-syncs items by BLAKE3 content hash, performs cosine similarity search, and returns scored resultsVectorStoretrait — object-safe abstraction over vector database operations (ensure_collection,upsert_points,search,delete_points,scroll_points); implemented byQdrantOps.zeph-indexuses this trait instead of depending onqdrant-clientdirectly, keeping the crate decoupled from the Qdrant client library- Automatic collection creation, graceful degradation without Qdrant
DocumentLoadertrait — async document loading withload(&Path)returningVec<Document>, dyn-compatible viaPin<Box<dyn Future>>TextLoader— plain text and markdown loader (.txt,.md,.markdown) with configurablemax_file_size(50 MiB default) and path canonicalizationPdfLoader— PDF text extraction viapdf-extractwithspawn_blocking(feature-gated:pdf)TextSplitter— configurable text chunking withchunk_size,chunk_overlap, and sentence-aware splittingIngestionPipeline— document ingestion orchestrator: load → split → embed → store viaQdrantOpsTokenCounter— BPE-based token counting via tiktoken-rscl100k_base, DashMap cache (10K cap), 64 KiB input guard, OpenAI tool schema token formula,chars/4fallback
zeph-channels
Channel implementations for the Zeph agent.
AnyChannel— enum dispatch over all channel variants (Cli, Telegram, Discord, Slack, Tui, Loopback), used by the binary for runtime channel selectionCliChannel— stdin/stdout with immediate streaming output, blocking recv (queue always empty)TelegramChannel— teloxide adapter with MarkdownV2 rendering, streaming via edit-in-place, user whitelisting, inline confirmation keyboards, mpsc-backed message queue with 500ms merge windowChannelErroris not defined in this crate; usezeph_core::channel::ChannelErrordirectly. The duplicate definition that previously existed inzeph-channels::errorhas been removed.
zeph-tools
Tool execution abstraction and shell backend. This crate has no dependency on zeph-skills.
ToolExecutortrait +ErasedToolExecutor—ErasedToolExecutoris an object-safe wrapper enablingBox<dyn ErasedToolExecutor>for dynamic dispatch inAgent<C>ToolRegistry— typed definitions for built-in tools (bash, read, edit, write, find_path, list_directory, create_directory, delete_path, move_path, copy_path, grep, web_scrape, fetch, diagnostics), injected into system prompt as<tools>catalogToolCall/execute_tool_call()— structured tool invocation with typed parameters alongside legacy bash extraction (dual-mode)FileExecutor— sandboxed file operations (read, write, edit, find_path, list_directory, create_directory, delete_path, move_path, copy_path, grep) with ancestor-walk path canonicalization and lstat-based symlink safetyShellExecutor— bash block parser, command safety filter, sandbox validation; exposescheck_blocklist()andDEFAULT_BLOCKED_COMMANDSas public API so ACP executors apply the same blocklistWebScrapeExecutor— HTML scraping with CSS selectors (web_scrape) and plain URL-to-text (fetch), both with SSRF protectionDiagnosticsExecutor— runscargo check/cargo clippy --message-format=json, returns structured diagnostics capped at configurable max; usestokio::process::CommandCompositeExecutor<A, B>— generic chaining with first-match-wins dispatch, routes structured tool calls bytool_idto the appropriate backend; used to place ACP executors ahead of local tools so IDE-proxied operations take priorityDynExecutor— newtype wrappingArc<dyn ErasedToolExecutor>so a heap-allocated erased executor can be used anywhere a concreteToolExecutoris required; enables runtime composition without static type chainsTrustLevel— canonical trust tier enum (Trusted,Verified,Quarantined,Blocked) used byTrustGateExecutorto enforce per-skill tool access restrictions; re-exported byzeph-skillsfor convenienceTrustGateExecutor— wraps anyToolExecutorand blocks tool calls that exceed the active skill’sTrustLevelDiffData— structured diff payload; re-exported aszeph_core::DiffDataviapub use zeph_tools::executor::DiffDatainzeph-coreAuditLogger— structured JSON audit trail for all executionstruncate_tool_output()— head+tail split at 30K chars with UTF-8 safe boundaries
zeph-index
AST-based code indexing, semantic retrieval, and repo map generation (always-on — no feature flag). All tree-sitter language grammars (Rust, Python, JavaScript/TypeScript, Go, and config formats) are compiled unconditionally. This crate does not depend directly on qdrant-client; all vector operations go through the VectorStore trait from zeph-memory, keeping the crate decoupled from the Qdrant client library.
Langenum — supported languages with tree-sitter grammar registrychunk_file()— AST-based chunking with greedy sibling merge, scope chains, import extractioncontextualize_for_embedding()— prepends file path, scope, language, imports to code for better embedding qualityCodeStore— dual-write storage: vector store viaVectorStoretrait (zeph_code_chunkscollection) + SQLite metadata with BLAKE3 content-hash change detection; vector operations are delegated toQdrantOpswhich implementsVectorStoreCodeIndexer<P>— project indexer orchestrator: walk, chunk, embed, store with incremental skip of unchanged chunksCodeRetriever<P>— hybrid retrieval with query classification (Semantic / Grep / Hybrid), budget-aware chunk packinggenerate_repo_map()— compact structural view via tree-sitter ts-query, extractingSymbolInfo(name, kind, visibility, line) for all supported languages; injected unconditionally for all providers regardless of Qdrant availabilityhover_symbol_at()— tree-sitter hover pre-filter for LSP context injection; resolves the symbol under cursor for any supported language (replaces previous Rust-only regex)
zeph-gateway
HTTP gateway for webhook ingestion (optional, feature-gated).
GatewayServer– axum-based HTTP server with fluent builder APIPOST /webhook– accepts JSON payloads (channel,sender,body), forwards to agent loop viampsc::Sender<String>GET /health– unauthenticated health endpoint returning uptime- Bearer token auth middleware with constant-time comparison (blake3 +
subtle) - Per-IP rate limiting with 60s sliding window and automatic eviction at 10K entries
- Body size limit via
tower_http::limit::RequestBodyLimitLayer - Graceful shutdown via
watch::Receiver<bool>
zeph-scheduler
Cron-based periodic task scheduler with SQLite persistence (optional, feature-gated).
Scheduler– tick loop checking due tasks every 60 secondsScheduledTask– task definition with 5 or 6-field cron expression (viacroncrate; 5-field seconds default to 0)TaskKind– built-in kinds (memory_cleanup,skill_refresh,health_check,update_check) andCustom(String)TaskHandlertrait – async execution interface receivingserde_json::ValueconfigJobStore– SQLite-backed persistence trackinglast_runtimestamps and status- Graceful shutdown via
watch::Receiver<bool>
zeph-mcp
MCP client for external tool servers (optional, feature-gated).
McpClient/McpManager— multi-server lifecycle managementMcpToolExecutor— tool execution via MCP protocolMcpToolRegistry— tool embeddings in Qdrant with delta sync- Dual transport: Stdio (child process) and HTTP (Streamable HTTP)
- Dynamic server management via
/mcp add,/mcp remove
zeph-a2a
A2A protocol client and server (optional, feature-gated).
A2aClient— JSON-RPC 2.0 client with SSE streamingAgentRegistry— agent card discovery with TTL cacheAgentCardBuilder— construct agent cards from runtime config- A2A Server — axum-based HTTP server with bearer auth, rate limiting with TTL-based eviction (60s sweep, 10K max entries), body size limits
TaskManager— in-memory task lifecycle managementProcessorEvent— streaming event enum (StatusUpdate,ArtifactChunk) for per-token SSE delivery;TaskProcessor::processacceptsmpsc::Sender<ProcessorEvent>
zeph-acp
Agent Client Protocol server — IDE integration via ACP (optional, feature-gated).
- Rich content — ACP prompts may contain multi-modal content blocks. Image blocks are forwarded to LLM providers that support vision (Claude, OpenAI, Ollama). Resource content blocks (embedded text from IDE) are appended to the user prompt. Tool output includes
ToolCallLocationfor IDE navigation (file path, line range). ZephAcpAgent—acp::Agentimplementation; manages concurrent sessions with LRU eviction (max_sessions, default 4), forwards prompts to the agent loop, and emitsSessionNotificationupdates back to the IDEAcpContext— per-session bundle of IDE-proxied capabilities passed toAgentSpawner:file_executor: Option<AcpFileExecutor>— reads/writes routed to the IDE filesystem proxyshell_executor: Option<AcpShellExecutor>— shell commands routed through the IDE terminal proxypermission_gate: Option<AcpPermissionGate>— confirmation requests forwarded to the IDE UIcancel_signal: Arc<Notify>— shared withLoopbackHandle; firing it interrupts the running agent turn
SessionContext— per-session struct carryingsession_id,conversation_id, andworking_dir; ensures each ACP session maps to exactly one Zeph conversation in SQLiteAgentSpawner—Arc<dyn Fn(LoopbackChannel, Option<AcpContext>, SessionContext) -> ...>factory that the main binary supplies; wiresAcpContextandSessionContextinto the agent loopAcpPermissionGate— permission gate backed byacp::Connection; cache key usestool_call_idas fallback whentitleisNoneto prevent distinct untitled tools from sharing a cached decision.AllowAlways/RejectAlwaysdecisions are persisted to a TOML file (~/.config/zeph/acp-permissions.tomlby default, configurable viaacp.permission_fileorZEPH_ACP_PERMISSION_FILE). The file is written atomically with0o600permissions on Unix. Persisted rules are loaded on startup and saved on each decision changeAcpFileExecutor/AcpShellExecutor— IDE-proxied file and shell backends; each spawns a local task for the connection handler- Model switching —
set_session_config_optionwithconfig_id = "model"validates the requested model againstavailable_modelsallowlist, resolves it viaProviderFactory(Arc<dyn Fn(&str) -> Option<AnyProvider>>), and stores the result in a sharedprovider_override: Arc<RwLock<Option<AnyProvider>>>that the agent loop checks on each turn. RwLock usesPoisonError::into_innerfor poison recovery - Extension methods —
ext_methoddispatches custom JSON-RPC methods:_agent/mcp/add,_agent/mcp/remove,_agent/mcp/listdelegate toMcpManagerfor runtime MCP server management - HTTP+SSE transport (feature
acp-http) — axum-based POST/acpaccepts JSON-RPC requests and returns SSE response streams; GET/acpreconnects SSE notifications withAcp-Session-Idheader routing. Includes 1 MiB body limit, UUID session ID validation, CORS deny-all, and SSE keepalive pings (15s) - WebSocket transport (feature
acp-http) — GET/acp/wsupgrades to bidirectional WebSocket with 1 MiB message limit and max_sessions enforcement (503) - Duplex bridge —
tokio::io::duplexconnects axum handlers to the ACP SDK’sAsyncRead+AsyncWriteinterface. Each HTTP/WS connection spawns a dedicated OS thread withLocalSet(required because Agent trait is!Send) AcpTransportenum (Stdio/Http/Both) andhttp_bindconfig field control which transports are active
Session Lifecycle
ZephAcpAgent supports multi-session concurrency with configurable max_sessions (default 4). Sessions are tracked in an LRU map; when the limit is reached, the least-recently-used session is evicted and its agent task cancelled.
- Persistence — session state and events are persisted to SQLite via
acp_sessionsandacp_session_eventstables. Each session links to aconversation_id(migration 026) so that message history is isolated per-session. Onload_session, the existing conversation is restored; onfork_session, messages are copied to a new conversation. - Idle reaper — a background task periodically scans sessions and removes those idle longer than
session_idle_timeout_secs(default 1800). - Configuration —
AcpConfigexposesmax_sessionsandsession_idle_timeout_secs, with env overridesZEPH_ACP_MAX_SESSIONSandZEPH_ACP_SESSION_IDLE_TIMEOUT_SECS.
AcpContext wiring
When a new ACP session starts, ZephAcpAgent::new_session calls build_acp_context, which constructs the three proxied executors from the IDE capabilities advertised during initialize. The context is passed to AgentSpawner alongside the LoopbackChannel. The spawner builds a CompositeExecutor with ACP executors as the primary layer and local ShellExecutor/FileExecutor as fallback:
CompositeExecutor
├── primary: AcpShellExecutor / AcpFileExecutor (IDE-proxied, used when AcpContext present)
└── fallback: ShellExecutor / FileExecutor (local, used in non-ACP sessions)
Cancellation
LoopbackHandle::cancel_signal (Arc<Notify>) is cloned into AcpContext at session creation. When the IDE calls cancel, ZephAcpAgent::cancel fires notify_one() on the signal and removes the session. The agent loop polls this notifier and aborts the current turn. AgentBuilder::with_cancel_signal() wires the signal into the agent so a new Notify is not created internally.
zeph-tui
ratatui-based TUI dashboard (optional, feature-gated).
TuiChannel— Channel trait implementation bridging agent loop and TUI render loop via mpsc, oneshot-based confirmation dialog, bounded message queue (max 10) with 500ms merge windowApp— TUI state machine with Normal/Insert/Confirm modes, keybindings, scroll, live metrics polling viawatch::Receiver, queue badge indicator[+N queued], Ctrl+K to clear queue, command palette with fuzzy matchingEventReader— crossterm event loop on dedicated OS thread (avoids tokio starvation)- Side panel widgets:
skills(active/total),memory(SQLite, Qdrant, embeddings),resources(tokens, API calls, latency) - Chat widget with bottom-up message feed, pulldown-cmark markdown rendering, scrollbar with proportional thumb, mouse scroll, thinking block segmentation, and streaming cursor
- Splash screen widget with colored block-letter banner
- Conversation history loading from SQLite on startup
- Confirmation modal overlay widget with Y/N keybindings and focus capture
- Responsive layout: side panels hidden on terminals < 80 cols
- Multiline input via Shift+Enter
- Status bar with mode, skill count, tokens, Qdrant status, uptime
- Panic hook for terminal state restoration
- Re-exports
MetricsSnapshot/MetricsCollectorfrom zeph-core