clawseed-agent — Agent Core Runtime¶
Overview¶
clawseed-agent is the core agent crate, responsible for tool dispatch, hook execution, security policy, cron scheduling, and more. It is the hub connecting Provider, Tool, Memory, and Hook.
Note: Beyond orchestration, this crate also owns runtime assembly.
Agent::from_config_with_registry()directly instantiates provider (viaProviderFactoryRegistry), memory (viaclawseed_memory::create_memory()), and tools (viaclawseed_tools::registry::all_tools()), then selects a dispatcher based onprovider.supports_native_tools(). Tools depend on memory being constructed first; dispatcher depends on provider capabilities.
Core Structures¶
Agent — Agent Registry¶
pub struct Agent {
provider: Arc<dyn Provider>,
tool_registry: Arc<dyn ToolRegistry>,
memory: Arc<dyn Memory>,
observer: Arc<dyn Observer>,
tool_dispatcher: Box<dyn ToolDispatcher>,
capabilities: HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
workspace_dir: PathBuf,
// ...
}
Agent is a registry that manages all tool sources (built-in, MCP, remote) through the ToolRegistry trait, and manages the Hook pipeline through HookRunner. Core code has no knowledge of specific tool implementations — extensions simply add entries to the registry.
Note on MCP: All MCP types (
McpRegistry,DeferredMcpToolSet,McpToolWrapper,ToolSearchTool) incrates/clawseed-agent/src/tools.rsare stubs — they return empty collections or errors. TheToolSource::Mcpenum variant andMcpConfigschema exist, but there is no actual MCP protocol client. Do not treat MCP as a usable capability.Note on provider field: The
providerfield isArc<dyn Provider>, notBox. This enables gateway connections to share a single provider instance (with its HTTP connection pools) across all WebSocket/webhook sessions.AgentBuilder.provider()acceptsBox<dyn Provider>and wraps it asArc;shared_provider()acceptsArc<dyn Provider>directly for gateway use.
AgentBuilder — Builder Pattern¶
let agent = Agent::builder()
.provider(provider) // Box<dyn Provider> → wrapped as Arc
.shared_provider(arc_provider) // Arc<dyn Provider> directly (gateway use)
.tools(tools) // Option 1: pass tool list, auto-builds DefaultToolRegistry
.tool_registry(registry) // Option 2: pass pre-built ToolRegistry (takes priority)
.memory(memory)
.observer(observer)
.tool_dispatcher(dispatcher)
.workspace_dir(path)
.allowed_tools(Some(vec!["file_*".into()])) // Glob pattern tool allowlist
.denied_tools(Some(vec!["shell".into()])) // Glob pattern tool denylist
.mcp_tool_filters(Some(filters)) // Per-MCP-server filtering
.hook_runner(Some(Arc::new(hook_runner))) // Hook pipeline
.build()?;
// Build from config (optionally with a custom ProviderFactoryRegistry) — CLI/embedded use
let agent = Agent::from_config(&config).await?;
let agent = Agent::from_config_with_registry(&config, Some(provider_factory_registry)).await?;
// Build with shared components — gateway use (reuses AppState provider/memory/observer/builtin-tools)
let agent = Agent::from_config_with_shared_components(
&config, state.provider, state.mem, state.observer, state.model, state.temperature,
Some(state.shared_builtin_tools)
).await?;
Module Architecture¶
agent_loop.rs — Agent Loop Entry Point¶
Provides the gateway-compatible turn() call interface:
- Receive user message
- Build system prompt (calls
prompt.rs) - Send to LLM
- Parse response
- If tool calls present → enter tool loop
- Return final text response
tool_loop.rs — Tool Loop¶
Manages the tool loop execution flow:
- Parse tool calls from LLM response
- Execute before_hook for each tool call
- Execute tool
- Execute after_hook
- Format results, send back to LLM
- Repeat until LLM returns text-only
tool_execution.rs — Single Tool Execution¶
- Tools are looked up via
tool_registry.get_tool(name)(returnsArc<dyn Tool>, O(1) hash lookup) - Wraps tool execution with observer event recording, duration measurement, error handling, and cancellation support
dispatcher.rs — Tool Dispatcher¶
Two implementations:
| Dispatcher | Use Case | How It Works |
|---|---|---|
NativeToolDispatcher |
Providers with native tool calling | Extracts tool_calls directly from response |
XmlToolDispatcher |
Providers without native tool calling | Tries ◁▷ format first, falls back to multi-format parser |
parser.rs — Tool Call Parser¶
Multi-format tool call parsing supporting 12+ LLM output formats:
- OpenAI native JSON
tool_callsarray - XML tags:
◁▷,<toolcall>,<tool-call>,<invoke> - MiniMax
<invoke>format - Markdown code blocks (
```tool_call) - Anthropic
<FunctionCall>tags - GLM shortened format
- Perl/hash-ref style
- xAI grok
```tool <name>format
XmlToolDispatcher::parse_response() tries ◁▷ format first (deterministic prompt-guided parsing), then falls back to parser::parse_tool_calls() with the original response text for multi-format parsing.
Security design: Raw JSON without explicit wrappers is never extracted, preventing prompt injection attacks.
context.rs — Tool Context¶
pub struct AgentToolContext {
workspace_dir: PathBuf,
capabilities: HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
}
impl ToolContext for AgentToolContext {
fn workspace_dir(&self) -> &Path { &self.workspace_dir }
fn get<T: 'static>(&self) -> Option<&T> {
self.capabilities.get(&TypeId::of::<T>())
.and_then(|arc| arc.downcast_ref::<T>())
}
}
hooks.rs — Hook Runner¶
Execution flow:
1. run_before_tool_call() — Iterates hooks sequentially
- First hook to return Cancel stops the pipeline
- Modify passes the modified call to the next hook
2. fire_after_tool_call() — Notifies all hooks, observation only, no modification
Declarative hook chain: Hooks can be declared in the [hooks] config section. HookFactoryRegistry creates Hook instances by hook_type. In from_config(), SecurityPolicy is always auto-registered as the first hook in the pipeline.
pub trait HookFactory: Send + Sync {
fn hook_type(&self) -> &str;
fn create(&self, config: &serde_json::Value) -> Option<Box<dyn Hook>>;
}
tool_registry.rs — Tool Registry Implementation¶
DefaultToolRegistry is the default implementation of the ToolRegistry trait:
- Uses
DashMapfor lock-free concurrent access, safe in async contexts ToolSpeccaching with write-time invalidation to avoid recomputation- Three-layer glob pattern filtering: denied takes precedence → allowed allowlist → per-MCP-server filtering
register_all()for bulk registration,register_all_arc()for bulk registration with sharedArc<dyn Tool>instances (avoids re-construction in gateway),unregister_by_source()for bulk removal by source
Dual Registry Note: At runtime there are two independent
ToolRegistryinstances. The gateway-levelAppState.tool_registryserves/api/toolsendpoint visibility; each Agent'stool_registryserves actual tool dispatch. Remote tools must be registered in both. See the "Dual Tool Registry" section in Architecture Overview for details.
security/ — Security Policy¶
mod.rs—SecurityPolicystruct- Autonomy levels (ReadOnly / Supervised / Full)
- Command allowlists
- Medium-risk command list (touch, rm, cp, mv, mkdir, chmod, chown, kill)
- Path restrictions (
/etc/passwd,/etc/shadow,/etc/ssh,/root/.ssh) - Action rate limiting (
max_actions_per_hour) - Implements
Hooktrait:before_tool_call()checks autonomy level, rate limits, command allowlists, and path guards;after_tool_call()records action count. SecurityPolicy is always registered as the first hook in the pipeline, and is no longer injected as a Capability pairing.rs—PairingGuardfor device pairing verification with constant-time comparisonsecrets.rs—SecretStorecredential management,WebAuthnManagersupport
cron/ — Cron Scheduling¶
scheduler.rs— Job execution engine, tracks jobs and run historystore.rs— Persistent storageadd_agent_job()/add_shell_job()— Create cron jobsupdate_job()/remove_job()— Modify/deletelist_jobs()/due_jobs()— Queryrecord_run()/list_runs()— Run historysync_declarative_jobs()— Sync from config filestypes.rs—CronJob,Schedule,SessionTarget,DeliveryConfigmod.rs— Security integrationvalidate_shell_command()— Security policy checkadd_shell_job_with_approval()— Approval before persistence
prompt.rs — Modular System Prompt Builder¶
The system prompt is assembled from pluggable PromptSection implementations via SystemPromptBuilder:
SystemPromptBuilder::with_defaults()
├── IdentitySection — AIEOS identity + personality markdown files
├── PlatformSection — Platform and environment info
├── WorkspaceSection — Working directory path
├── StableMemorySection — Core memories injected into system prompt
├── ToolsSection — Available tool descriptions
├── MemorySection — Memory system instructions
├── SafetySection — Safety rules (autonomy-level-aware)
├── ToolHonestySection — Tool honesty constraints
├── SkillsIndexSection — Available skill definitions
└── ActiveSkillsSection — Currently active skills
Note: DateTimeSection was removed from the system prompt. Current time is now provided via the [YYYY-MM-DD HH:MM:SS TZ] prefix on each user message, keeping the system prompt 100% stable across turns for prefix caching optimization (see Prompt Cache Optimization).
Custom sections can be added via SystemPromptBuilder::add_section().
personality.rs — Personality File Loader¶
Loads well-known markdown files from the workspace directory:
| File | Purpose |
|---|---|
SOUL.md |
Core personality and behavioral guidelines |
IDENTITY.md |
Name, role, background |
USER.md |
User preferences and context |
AGENTS.md |
Multi-agent coordination rules |
TOOLS.md |
Tool usage guidelines |
HEARTBEAT.md |
Periodic self-check instructions |
BOOTSTRAP.md |
First-run initialization instructions |
MEMORY.md |
Memory management guidelines |
Files are truncated at 20K characters. A default SOUL.md is auto-generated on first run.
identity.rs — AIEOS Identity System¶
Supports AIEOS v1.1 (AI Entity Object Specification) — a structured JSON format for portable AI identity. Covers identity, psychology, linguistics, motivations, capabilities, physicality, history, and interests.
load_aieos_identity()— loads from file or inline JSONaieos_to_system_prompt()— renders AIEOS identity to markdown- Handles both official generator shape and simplified JSON formats via normalization
See Personality & Identity Tutorial for full documentation.
Other Modules¶
| Module | Responsibility |
|---|---|
cost.rs |
Token-based cost tracking |
observer.rs |
Event emission (NoopObserver by default, defined locally in clawseed-agent) |
observability.rs |
Re-exports Observer types for external consumers |
approval.rs |
Approval workflow for risky operations |
history.rs |
Conversation history management |
parser.rs |
Multi-format tool call parsing (12+ LLM output formats) |
health.rs |
Health check stubs |