Architecture
EdgeCrab is a Rust 2024 edition workspace. Every crate has a single responsibility and the dependency graph is a strict DAG — no circular dependencies, no feature flags that reverse the graph.
Workspace Layout
Section titled “Workspace Layout”edgecrab/ Cargo.toml -- workspace manifest crates/ edgecrab-types/ -- shared types (no deps on other crates) edgecrab-security/ -- SSRF, path-safety, injection scanning edgecrab-state/ -- SQLite WAL session storage + FTS5 edgecrab-cron/ -- cron scheduler engine edgecrab-tools/ -- tool registry, toolsets, all tool impls edgecrab-core/ -- agent loop, config, context, LLM client edgecrab-cli/ -- ratatui TUI, CLI args, commands, themes edgecrab-gateway/ -- messaging adapters (10 platforms) edgecrab-acp/ -- ACP JSON-RPC 2.0 stdio adapter edgecrab-migrate/ -- schema migrations + one-shot migrate CLICrate Dependency Graph
Section titled “Crate Dependency Graph”Arrows point from dependent to dependency. The graph is a strict DAG.
edgecrab-cli ------+edgecrab-gateway --+---> edgecrab-coreedgecrab-acp ------+ |edgecrab-migrate --+ +--> edgecrab-tools | | | +--> edgecrab-security | +--> edgecrab-state | +--> edgecrab-cron | +--> edgecrab-types | +--> edgecrab-security +--> edgecrab-state +--> edgecrab-typesKey constraint: edgecrab-types has zero edgecrab dependencies.
edgecrab-security depends only on types. This ensures the security
layer can never accidentally bypass compile-time type safety.
Crate Responsibilities
Section titled “Crate Responsibilities”edgecrab-types
Section titled “edgecrab-types”Shared data structures:
Message, ToolCall, ToolResult, ModelResponse, Provider,
Session, ContentBlock, SanitizedPath, SafeUrl.
No business logic — pure serde-serializable types.
SanitizedPath and SafeUrl are distinct Rust types, not aliases
for String. Functions that accept file paths require SanitizedPath;
functions that accept URLs require SafeUrl. Passing an unsanitized value
is a compile error.
edgecrab-security
Section titled “edgecrab-security”Six modules:
edgecrab-security/ path_jail.rs -- path traversal prevention (SanitizedPath) url_safety.rs -- SSRF guard (SafeUrl, private IP + metadata blocklist) command_scan.rs -- dangerous command scanning (Aho-Corasick + regex) injection.rs -- prompt injection heuristics in tool results redact.rs -- secret / token redaction in output approval.rs -- manual / smart approval policy engine normalize.rs -- ANSI strip + NFKC normalization before scanningThe command scanner runs two passes:
- Aho-Corasick O(n) literal scan across 8 danger categories
- Regex scan for non-contiguous patterns (e.g. DELETE FROM without WHERE)
edgecrab-state
Section titled “edgecrab-state”SQLite WAL-mode session database:
- Session CRUD (create, read, update, delete)
- Message storage and retrieval
- FTS5 full-text search index over message content
- Checkpoint metadata storage
- Schema managed by
edgecrab-migrate
edgecrab-cron
Section titled “edgecrab-cron”- Cron expression parser and scheduler (
croncrate) - Job storage in
~/.edgecrab/cron/*.json - Spawns
AgentLoopinvocations for scheduled tasks - Timezone-aware scheduling
edgecrab-tools
Section titled “edgecrab-tools”- Tool registry:
ToolRegistrywithget_definitions()for LLM JSON schema anddispatch()for invocation - Toolsets:
toolsets.rs— alias resolution,CORE_TOOLS,ACP_TOOLS, expansion logic - All tool implementations: file, terminal, web, browser, memory, skills, MCP, delegation, code execution, session, checkpoint, cron, Honcho, TTS/STT/vision, Home Assistant
edgecrab-core
Section titled “edgecrab-core”AppConfig— layered config (defaults → YAML → env → CLI)ContextBuilder— assembles system prompt from SOUL.md, AGENTS.md, memories, and Honcho contextAgentLoop— ReAct iteration: LLM → parse tool calls → security check → checkpoint → dispatch → injection scan → inject results → loopLlmClient— unified OpenAI-compatible HTTP client with streaming, prompt caching, and retry logicCompressionEngine— context window summarisation when threshold hit- Provider setup: detect API keys, build
ModelConfigfor 11 providers (9 cloud + 2 local)
edgecrab-cli
Section titled “edgecrab-cli”CliArgs(clap) — all CLI subcommands and flagsApp— ratatui TUI main loopCommandRegistry— slash command dispatch (O(1) HashMap lookup)CommandResultenum — 58+ variants for typed command outcomesTheme+SkinConfig— TUI colors, symbols, personalities- Background session manager; worktree creation and cleanup
edgecrab-gateway
Section titled “edgecrab-gateway”HTTP API server (Axum) plus platform adapters:
Platform Source file Env vars needed------------------------------------------------------Telegram telegram.rs TELEGRAM_BOT_TOKENDiscord discord.rs DISCORD_BOT_TOKENSlack slack.rs SLACK_BOT_TOKENSignal signal.rs SIGNAL_HTTP_URL + SIGNAL_ACCOUNTWhatsApp whatsapp.rs WHATSAPP_ENABLED (bridge)Matrix matrix.rs MATRIX_HOMESERVER + MATRIX_TOKENMattermost mattermost.rs MATTERMOST_URL + MATTERMOST_TOKENDingTalk dingtalk.rs DINGTALK_WEBHOOKSMS sms.rs (provider-specific)Email email.rs SMTP / IMAP credentialsHome Assistant homeassistant.rs HA_URL + HA_TOKENPlatform adapters:
- Approval workflow (inline buttons on Telegram / Discord)
- Proactive home-channel messaging
- Per-platform
allowed_usersallowlists - Delivery confirmation and retry
edgecrab-acp
Section titled “edgecrab-acp”ACP (Agent Communication Protocol) JSON-RPC 2.0 adapter over stdio.
Exposes EdgeCrab to VS Code, Zed, and JetBrains via edgecrab acp.
Uses ACP_TOOLS subset — no clarify, send_message,
generate_image, or text_to_speech (interactive-only tools that
do not make sense in an editor integration).
edgecrab-migrate
Section titled “edgecrab-migrate”- SQLite schema migration engine (idempotent, append-only)
edgecrab migrateCLI subcommand- One-shot hermes-agent → EdgeCrab state importer
Agent Loop Data Flow
Section titled “Agent Loop Data Flow”User types message | vContextBuilder.build() -- SOUL.md + AGENTS.md -- ~/.edgecrab/memories/*.md -- Honcho context (cross-session user model) | vAgentLoop.run() +----- Step 1: LlmClient.complete(messages) ----+ | streaming tokens --> TUI token feed | | | | Step 2: Parse tool_calls from response | | | | Step 3: SecurityChecker per tool call | | -- url_safety check (SSRF guard) | | -- path_jail check (traversal guard) | | -- command_scan check (injection guard) | | | | Step 4: Build checkpoint if op is destructive | | (write_file / patch / dangerous terminal) | | | | Step 5: ToolRegistry.dispatch(tool_call) | | (parallel dispatch where allowed) | | | | Step 6: injection.check(result) | | (scan tool results for prompt injection) | | | | Step 7: Append ToolResult to messages | | | | Step 8: Check max_iterations; loop -> Step 1 | +------------------------------------------------+ | vauto_flush memory (if enabled) | vhoncho_conclude (if enabled) | vSession saved to SQLite (WAL commit)Key Design Decisions
Section titled “Key Design Decisions”1. Single binary. Everything compiles into one statically-linked executable. No Python venv, no Node.js, no shared libraries except the OS. Browser tools require Chrome — that is the only soft dependency. Cold start: ~38 ms.
2. Security at the type level. SanitizedPath and SafeUrl are
distinct Rust types in edgecrab-types. Functions that touch the
filesystem accept only SanitizedPath. Bypassing the sanitizer is
a compile error, not a runtime risk.
3. SQLite over file-based state. WAL mode gives concurrent reads
with atomic writes. FTS5 enables instant full-text search across all
session history without an external search service. The database file
is ~/.edgecrab/state.db.
4. Toolset as policy, not registry. The registry owns what tools exist. Toolsets own which tools are active per session. Policy changes never require touching tool implementations.
5. No Python. EdgeCrab is a ground-up Rust rewrite of hermes-agent. Result: 38 ms cold start (vs 1.2 s), 14 MB resident (vs 87 MB), no GC pauses, no venv to manage.
6. Config resolution order. AppConfig::default() → config.yaml
→ EDGECRAB_* env vars → CLI flags. Later layers always win.
This makes container and CI deployments predictable: set env vars,
never edit files.
Pro Tips
Section titled “Pro Tips”- Read
edgecrab-typesfirst: All shared data structures live there. UnderstandingMessage,SanitizedPath, andSafeUrlbefore touching any other crate saves a lot of friction. - Check the DAG before adding a dep: If crate A already transitively depends on crate B, adding an explicit dep from B to A creates a cycle and won’t compile. Use
cargo tree -p <crate>to verify. edgecrab-securityis your first read for security work: It owns path jail, SSRF guard, command scanning, injection detection, and the approval engine — all in one place.- The compression threshold is 0.50 by default: If sessions feel slow on large contexts, check
compression.thresholdin config — lowering it triggers earlier summarisation. cargo doc --workspace --no-deps --open: Generates and opens the full local doc tree. Faster than reading individual files when exploring a new crate.
Why 10 separate crates instead of one?
Each crate maps to a distinct responsibility with a clear interface. The benefit is clean security boundaries: edgecrab-security can never depend on edgecrab-core, so security logic can’t be accidentally bypassed by core code.
Why SQLite instead of a file-based session store? FTS5 full-text search, WAL-mode concurrent access, and atomic transactions. Session search across thousands of messages is instant without an external search service.
Why is the binary 38 MB instead of smaller? Static linking embeds all dependencies (TLS, SQLite, the Aho-Corasick scanner, etc.) into a single executable. No dynamic library dependencies means the binary runs on any Linux distro without managing shared libraries.
How do I find which crate owns a given feature?
See the Crate Responsibilities table above, or search: grep -rn "fn my_function" crates/ to find the implementation.
See Also
Section titled “See Also”- Contributing — build, test, and PR process
- Configuration Reference —
AppConfigfields and defaults - Security Model — runtime security layers in detail