Aleph
Architecture

Executor

Tool execution engine based on the ToolRegistry trait, exposed to the Harness Think→Act loop via RegistryToolAdapter.

Executor

The Executor is the tool execution engine. It receives tool decisions from the gateway execution engine (src/gateway/execution_engine/) and routes them to the correct handler; the unified BuiltinToolRegistry in src/executor/ is the production implementation.

Location: src/executor/


Relationship to the Harness

Thinker Decision (tool_use)


Harness loop (harness::agent::loop_graph / harness::agent::think)


ToolService (scoped tool stack: src/tools/scoped/builder.rs)
     │   Adapter: src/tools/adapters/registry_adapter
     │   Exposes Arc<dyn ToolService> as a LoopTool


Executor

     ├─ BuiltinToolRegistry  (src/executor/builtin_registry/)
     │     Surfaces Arc<ToolRegistry> to the tool stack

     └─ ToolRegistry trait   (src/executor/tool_registry.rs)
           Resolves a name to a UnifiedTool, executes the call

Exec-class tools (code_exec, bash_exec) route through an additional Sandbox layer (src/sandbox/). The sandbox owns per-session workspace provisioning, capability enforcement, and OS-level seatbelt isolation (macOS sandbox-exec / Windows job-object / Linux cgroups-v2 / seccomp).


The ToolRegistry trait (src/executor/tool_registry.rs)

pub trait ToolRegistry: Send + Sync {
    fn get_tool(&self, name: &str) -> Option<&UnifiedTool>;

    fn execute_tool(
        &self,
        tool_name: &str,
        arguments: Value,
    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Value>> + Send + '_>>;

    // Optional per-run shared handles (default None):

    /// Shared workspace handle: written by the execution engine after workspace
    /// resolution, read by tools like `memory_search`.
    fn workspace_handle(&self) -> Option<Arc<tokio::sync::RwLock<String>>> { None }

    /// Shared per-agent SmartRecallConfig map: Two-Phase Smart Recall for `memory_search`.
    fn smart_recall_config_handle(
        &self,
    ) -> Option<Arc<tokio::sync::RwLock<HashMap<String, SmartRecallConfig>>>> { None }

    /// Shared SessionContext: agent management tools bind agent switches to
    /// the correct conversation.
    fn session_context_handle(
        &self,
    ) -> Option<Arc<tokio::sync::RwLock<SessionContext>>> { None }

    /// Shared ToolContextHandle: workspace-scoped output paths.
    fn tool_context_handle(&self) -> Option<crate::tools::ToolContextHandle> { None }

    /// Shared session-key handle: `memory_search` `scope=current_session` filter.
    fn session_key_handle(&self) -> Option<Arc<tokio::sync::RwLock<String>>> { None }
}

The production implementor is BuiltinToolRegistry (src/executor/builtin_registry/registry/). src/tools/adapters/registry_adapter::RegistryToolAdapter wraps any ToolRegistry implementor as a LoopTool, and the gateway's ScopedToolService exposes it to the harness. The handle accessors let the execution engine hand workspace- and session-scoped context to tools without threading it through every call signature.


BuiltinToolRegistry (src/executor/builtin_registry/)

impl ToolRegistry for BuiltinToolRegistry {
    fn get_tool(&self, name: &str) -> Option<&UnifiedTool> { /* … */ }
    fn execute_tool(&self, name: &str, args: Value) -> BoxFuture<'_, Result<Value>> { /* … */ }
    // workspace_handle / tool_context_handle / session_key_handle return real Arcs
}

pub struct BuiltinToolConfig {
    pub gateway_context: Option<Arc<GatewayContext>>,
    pub tool_catalog: Option<Arc<ToolCatalog>>,
    /* other dependencies */
}

impl BuiltinToolRegistry {
    pub async fn new() -> Result<Arc<Self>>;
    pub async fn with_config(config: BuiltinToolConfig) -> Result<Arc<Self>>;
}

Public types (src/executor/builtin_registry/definitions.rs + groups.rs):

TypePurpose
BUILTIN_TOOL_DEFINITIONSAll builtin tools' JSON Schemas / argument definitions (single source)
TOOL_CATEGORIESTools grouped by function (Settings UI)
BuiltinToolConfigOptional dependencies for constructing a registry (gateway context, tool catalog, …)
get_builtin_tool_names()List every registered name
create_tool_boxed()Type-erased tool factory

Tests (tests/gateway_feature_gate.rs::BuiltinToolRegistry::test_resolve_plugin_handler_*) verify that plugin tools resolve through the plugin:<plugin_id>:<name> direct fast path and stay in sync via ExtensionManager::sync_runtime_snapshots.


Tool metadata source

Every tool in the registry is a UnifiedTool (src/tool_metadata/types/unified/):

pub struct UnifiedTool {
    pub id: String,                          // "builtin:search" | "mcp:fs:read_file" | "plugin:diagnostics:ping" | …
    pub name: String,                        // command name (no namespace prefix)
    pub source: ToolSource,                  // Builtin | Native | Mcp { server } | Skill { id } | Plugin { .. } | Custom { .. }
    pub routing_regex: Option<String>,       // reverses into a RoutingRuleConfig
    pub routing_strip_prefix: bool,          // strip prefix after slash-command match
    pub routing_system_prompt: Option<String>,
    pub routing_capabilities: Vec<String>,   // skill allowed-tools whitelist
    pub routing_intent_type: Option<String>,
    pub routing_context_format: Option<String>,
    pub aliases: Vec<String>,                // fuzzy matching + slash-command parsing
    pub visible_channels: Vec<ChannelType>,
    pub sort_order: u32,
    pub is_active: bool,
    pub is_builtin: bool,
    /* … */
}

ToolCatalog::get_builtin_routing_rules (src/tool_metadata/registry/query.rs) reverses UnifiedTool.routing_regex into RoutingRuleConfig — single source of truth, so the config module never hardcodes command rules.


Public result types (src/executor/types.rs)

pub struct ExecutionResult { content, tool_calls, task_results, execution_time_ms, success, error }
pub struct ToolCallRecord { tool_name, parameters, result, success, execution_time_ms }
pub struct TaskExecutionResult { task_id, description, success, output, error, execution_time_ms }
pub struct ExecutionContext { app_context, window_title, session_id, stream }
pub enum ExecutorError { ExecutionFailed, ToolError, TaskFailed { task_id, error }, Timeout, Cancelled }

ExecutionResult.task_results carries per-task outcomes for multi-task plans (TaskExecutionResult) — it's a data structure, not an independent scheduler. TerminateReason::DiminishingReturns in src/orchestrator/summary_format.rs is a stop-reason label the loop emits when output plateaus; the hard-stop detector was deleted under R10, and the loop is bounded by max_iterations, the tool-loop verifier, and the model's own stop instead.


Middleware stack

Every tool call passes through the standard middleware stack in src/tools/scoped/:

ExecAuditLayer → PermissionLayer → ContextRuleLayer → TimeoutLayer → CoreDispatch
LayerPurpose
ExecAuditLayerAudit logging
PermissionLayerPermission checks (SmartFilter)
ContextRuleLayerContext-based rules
TimeoutLayerTimeout enforcement
CoreDispatchInnermost layer holding the ToolRegistry

The slash fast path (execute_slash_command_fast_path), tools.invoke, and the background-continuation bypass are all forced through the src/tools/scoped/ approval gate — this is the 26.7.15+ fix that makes the approval dialog see the actual command.


Exec-class tools & sandbox

  • code_exec, bash_exec route through the Sandbox layer
  • Sandbox trait → WorkspaceSandboxOsSandboxDriver
  • Per-session workspace at ~/.aleph/workspaces/{hash(session_id)}/
  • Tool-level permissions (SmartFilter) gate whether a call runs
  • The sandbox's capability check gates what the subprocess can do once allowed

Three-tier execution permissions

TierBehavior
askPrompt for approval before every tool execution; unknown tools fail closed
autoIdempotent tools auto-allow; destructive tools still need approval
fullAll auto-allow (besides the hard security filter)

Process-isolation kernel

Sandbox restricted-token / job-object / AppContainer / integrity-level / SID·ACL syscalls are issued in-place at spawn (R1 process-isolation carve-out).


See Also

On this page