Skip to content

Build an Agent

Use this when you need to assemble an agent with tools, persistence, and a provider into a running AgentRuntime.

  • awaken crate added to Cargo.toml
  • An LlmExecutor implementation (provider) available
  • Familiarity with AgentSpec and AgentRuntimeBuilder
  1. Define the agent spec.
use awaken::AgentSpec;
let spec = AgentSpec::new("assistant")
.with_model_id("claude-sonnet")
.with_system_prompt("You are a helpful assistant.")
.with_max_rounds(10);
  1. Register tools.
use std::sync::Arc;
use async_trait::async_trait;
use serde_json::{Value, json};
use awaken::contract::tool::{Tool, ToolCallContext, ToolDescriptor, ToolError, ToolOutput, ToolResult};
use awaken::{AgentRuntimeBuilder, AgentSpec};
struct SearchTool;
#[async_trait]
impl Tool for SearchTool {
fn descriptor(&self) -> ToolDescriptor {
ToolDescriptor::new("search", "Search", "Search documents")
.with_parameters(json!({"type": "object", "properties": {}}))
}
async fn execute(&self, _args: Value, _ctx: &ToolCallContext) -> Result<ToolOutput, ToolError> {
Ok(ToolResult::success("search", json!([])).into())
}
}
struct CalculatorTool;
#[async_trait]
impl Tool for CalculatorTool {
fn descriptor(&self) -> ToolDescriptor {
ToolDescriptor::new("calculator", "Calculator", "Evaluate expressions")
.with_parameters(json!({"type": "object", "properties": {}}))
}
async fn execute(&self, _args: Value, _ctx: &ToolCallContext) -> Result<ToolOutput, ToolError> {
Ok(ToolResult::success("calculator", json!({"result": 0})).into())
}
}
let spec = AgentSpec::new("assistant");
let builder = AgentRuntimeBuilder::new()
.with_agent_spec(spec)
.with_tool("search", Arc::new(SearchTool))
.with_tool("calculator", Arc::new(CalculatorTool));
  1. Register a provider and a model.
use std::sync::Arc;
use awaken::engine::GenaiExecutor;
use awaken::registry_spec::ModelSpec;
use awaken::AgentRuntimeBuilder;
let builder = AgentRuntimeBuilder::new();
let builder = builder
.with_provider("anthropic", Arc::new(GenaiExecutor::new()))
.with_model(ModelSpec::new("claude-sonnet", "anthropic", "claude-sonnet-4-20250514"));
  1. Attach persistence.
use std::sync::Arc;
use awaken::contract::commit_coordinator::CommitCoordinator;
use awaken::stores::{InMemoryStore, MemoryCommitCoordinator};
use awaken::AgentRuntimeBuilder;
let builder = AgentRuntimeBuilder::new();
let store = Arc::new(InMemoryStore::new());
let coordinator = MemoryCommitCoordinator::wrap(store) as Arc<dyn CommitCoordinator>;
let builder = builder.with_commit_coordinator(coordinator);
  1. Build and validate.
use std::sync::Arc;
use awaken::engine::MockLlmExecutor;
use awaken::registry_spec::ModelSpec;
use awaken::{AgentRuntimeBuilder, AgentSpec};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let builder = AgentRuntimeBuilder::new()
.with_agent_spec(AgentSpec::new("assistant").with_model_id("mock"))
.with_provider("mock", Arc::new(MockLlmExecutor::new()))
.with_model(ModelSpec::new("mock", "mock", "mock"));
let runtime = builder.build()?;
let _runtime = runtime;
Ok(())
}

build resolves every registered agent and catches missing models, providers, or plugins at startup rather than at request time.

  1. Tune agent behavior through config.

AgentSpec is the runtime config object for an agent. The fields and sections below are the same data edited by /v1/config/agents and the admin console:

use serde_json::json;
use awaken::AgentSpec;
let mut spec = AgentSpec::new("assistant")
.with_model_id("claude-sonnet")
.with_system_prompt("You are a careful coding assistant.")
.with_hook_filter("reminder")
.with_section("reminder", json!({
"rules": [{
"tool": "*",
"output": "any",
"message": {
"target": "suffix_system",
"content": "Prefer verifying code changes before final answers.",
"cooldown_turns": 3
}
}]
}));
spec.plugin_ids.push("reminder".into());

Use system_prompt for the base prompt. Use plugin sections such as reminder, generative-ui, permission, and deferred_tools for behavior that should be validated, saved, edited in the page, and applied to later runs. Future prompt semantic hooks should follow the same typed section pattern.

  1. Execute a run.
use awaken::engine::MockLlmExecutor;
use awaken::prelude::*;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let runtime = AgentRuntimeBuilder::new()
.with_agent_spec(AgentSpec::new("assistant").with_model_id("mock"))
.with_provider("mock", Arc::new(MockLlmExecutor::new()))
.with_model(ModelSpec::new("mock", "mock", "mock"))
.build()?;
let user_message = Message::user("Hello");
let request = RunActivation::new("thread-1", vec![user_message])
.with_agent_id("assistant");
// Use runtime.run(..., sink) when callers need streaming events.
let result = runtime.run_to_completion(request).await?;
let _result = result;
Ok(())
}

Call the /health endpoint (if using the server feature) or inspect the returned AgentRunResult to confirm the agent loop completed without errors.

ErrorCauseFix
BuildError::ValidationFailedAgent spec references a model or provider not registered in the builderRegister the missing model/provider before calling build
BuildError::StateDuplicate state key registration across pluginsEnsure each StateKey is registered by exactly one plugin
RuntimeError at run timeProvider returns an inference errorCheck provider credentials and model ID

examples/src/research/ — a research agent with search and report-writing tools.

  • crates/awaken-runtime/src/builder.rsAgentRuntimeBuilder
  • crates/awaken-runtime-contract/src/registry_spec.rsAgentSpec
  • crates/awaken-runtime/src/runtime/agent_runtime/mod.rsAgentRuntime