The 6-Layer Model
skelegent organizes its crates into six layers plus an umbrella crate. Each layer has a clear responsibility. The fundamental rule: higher layers depend on lower layers, never the reverse.
┌──────────────────────────────────────────────────┐
│ skelegent (umbrella crate) │
│ Feature-gated re-exports of all layers │
├──────────────────────────────────────────────────┤
│ LAYER 5 — Cross-Cutting │
│ Security middleware, lifecycle coordination │
├──────────────────────────────────────────────────┤
│ LAYER 4 — Environment │
│ Isolation, credentials, secret backends, │
│ auth backends, crypto backends │
├──────────────────────────────────────────────────┤
│ LAYER 3 — State │
│ Persistence backends (memory, filesystem) │
├──────────────────────────────────────────────────┤
│ LAYER 2 — Orchestration │
│ Multi-agent composition (local, kit) │
├──────────────────────────────────────────────────┤
│ LAYER 1 — Operator Implementations │
│ Providers, tools, operators, context, MCP │
├──────────────────────────────────────────────────┤
│ LAYER 0 — Protocol Traits (layer0) │
│ 4 protocols + 2 interfaces + message types │
│ The stability contract. Changes: almost never. │
└──────────────────────────────────────────────────┘
Layer 0 – Protocol Traits
Crate: layer0
Layer 0 is the stability contract. It defines the four protocol traits (Operator, Orchestrator, StateStore/StateReader, Environment), two cross-cutting interfaces (per-boundary middleware traits, lifecycle events), and all the message types that cross protocol boundaries (OperatorInput, OperatorOutput, Content, Effect, Scope, typed IDs).
Dependencies: serde, async-trait, thiserror, rust_decimal, serde_json. Nothing else. No runtime, no HTTP, no provider-specific types.
Change frequency: Almost never. Adding a method to a protocol trait is a breaking change that ripples through every implementation. The traits were designed with extension points (#[non_exhaustive] enums, serde_json::Value metadata fields) to avoid needing changes.
Layer 1 – Operator Implementations
Crates:
skg-turn– Shared toolkit:Providertrait,InferRequest,InferResponse,TokenUsage, type conversionsskg-turn-kit– Turn decomposition primitives and helpersskg-provider-anthropic– Anthropic Claude API providerskg-provider-openai– OpenAI API providerskg-provider-ollama– Ollama local model providerskg-tool–ToolDyntrait,ToolRegistry,AliasedToolskg-context– Conversation context management and compaction strategiesskg-mcp– MCP (Model Context Protocol) clientskg-context-engine– Composable three-phase context engine (assembly, inference, reaction) with tool executionskg-op-single-shot– Single-shot operator (one model call, no tools)
Layer 1 is where the core agentic loop lives. The Provider trait (defined in skg-turn) is intentionally not object-safe – it uses RPITIT for zero-cost abstraction. The object-safe boundary is layer0::Operator. The bridge is a context engine implementation (generic over the provider type) that implements the object-safe Operator trait.
Layer 2 – Orchestration
Crates:
skg-orch-local– In-process orchestrator using tokio tasksskg-orch-kit– Shared orchestration utilitiesskg-effects-core–EffectExecutortrait and shared effect execution typesskg-effects-local– Local effect interpreter (executes effects in-process)
Layer 2 implements layer0::Orchestrator. The LocalOrch dispatches operator invocations in-process using tokio. It maps OperatorId to Arc<dyn Operator> and handles parallel dispatch via tokio::spawn. The effects crates execute Effect payloads declared by operators — they live at Layer 2 because effect execution is an orchestration concern, not a protocol concern.
Future implementations could include Temporal workflows (durable, replayable) or Restate (durable execution with virtual objects).
Layer 3 – State
Crates:
skg-state-memory– In-memoryHashMapstore (ephemeral, good for tests)skg-state-fs– Filesystem-backed store (durable across restarts)
Layer 3 implements layer0::StateStore. Both backends provide scoped key-value storage with serde_json::Value values. The memory store is ideal for testing and short-lived processes. The filesystem store persists data as files, suitable for CLI tools and local development.
Future implementations could include SQLite (embedded), PostgreSQL (queryable, transactional), or Redis (networked, fast).
Layer 4 – Environment
Crates:
skg-env-local– Local passthrough environment (no isolation)skg-secret– Secret resolution traitskg-secret-vault– HashiCorp Vault secretsskg-auth– Authentication and credential frameworkskg-crypto– Cryptographic primitives
Layer 4 implements layer0::Environment and provides the credential infrastructure that environments use. LocalEnv passes through with no isolation – it holds an Arc<dyn Operator> and calls execute() directly. The secret, auth, and crypto backends provide credential resolution for the EnvironmentSpec’s CredentialRef system.
Layer 5 – Cross-Cutting
Crates:
skg-hook-security– Security middleware (RedactionMiddleware,ExfilGuardMiddleware)
Layer 5 provides security middleware that wraps operator dispatch, store access, and execution boundaries. The per-boundary middleware traits (DispatchMiddleware, StoreMiddleware, ExecMiddleware) are defined in Layer 0 and composed into stacks (DispatchStack, StoreStack, ExecStack). Layer 5 crates supply concrete middleware implementations — for example, RedactionMiddleware scrubs sensitive data from model outputs, and ExfilGuardMiddleware blocks unauthorized data exfiltration through tool calls.
The umbrella crate
Crate: skelegent
The umbrella crate re-exports all layers behind feature flags. It exists so users can write skelegent = { features = ["context-engine", "provider-anthropic"] } instead of depending on 5+ individual crates. See Installation for the full feature flag table.
Dependency rules
- A crate may depend on crates at the same layer or lower layers.
- A crate may never depend on a crate at a higher layer.
- All crates depend on
layer0(directly or transitively). layer0depends on nothing in the workspace.
These rules ensure that any layer can be replaced independently. You can swap your state backend without touching your operator code. You can swap your orchestrator without touching your tools. The protocol traits in Layer 0 are the only shared vocabulary.