Skip to content

First Agent

Run one agent end-to-end and inspect the final result.

[dependencies]
awaken = { git = "https://github.com/AwakenWorks/awaken" }
tokio = { version = "1", features = ["full"] }
async-trait = "0.1"
serde_json = "1"

Set one model provider key before running:

Terminal window
# OpenAI-compatible models (for gpt-4o-mini)
export OPENAI_API_KEY=<your-key>
# Or DeepSeek models
export DEEPSEEK_API_KEY=<your-key>
use std::sync::Arc;
use serde_json::{json, Value};
use async_trait::async_trait;
use awaken::contract::tool::{Tool, ToolDescriptor, ToolResult, ToolOutput, ToolError, ToolCallContext};
use awaken::contract::message::Message;
use awaken::engine::GenaiExecutor;
use awaken::registry_spec::AgentSpec;
use awaken::registry_spec::ModelSpec;
use awaken::{AgentRuntimeBuilder, RunActivation};
struct EchoTool;
#[async_trait]
impl Tool for EchoTool {
fn descriptor(&self) -> ToolDescriptor {
ToolDescriptor::new("echo", "Echo", "Echo input back to the caller")
.with_parameters(json!({
"type": "object",
"properties": { "text": { "type": "string" } },
"required": ["text"]
}))
}
async fn execute(
&self,
args: Value,
_ctx: &ToolCallContext,
) -> Result<ToolOutput, ToolError> {
let text = args["text"].as_str().unwrap_or_default();
Ok(ToolResult::success("echo", json!({ "echoed": text })).into())
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let agent_spec = AgentSpec::new("assistant")
.with_model_id("gpt-4o-mini")
.with_system_prompt("You are a helpful assistant. Use the echo tool when asked.")
.with_max_rounds(5);
let runtime = AgentRuntimeBuilder::new()
.with_agent_spec(agent_spec)
.with_tool("echo", Arc::new(EchoTool))
.with_provider("openai", Arc::new(GenaiExecutor::new()))
.with_model(ModelSpec::new("gpt-4o-mini", "openai", "gpt-4o-mini"))
.build()?;
let request = RunActivation::new(
"thread-1",
vec![Message::user("Say hello using the echo tool")],
)
.with_agent_id("assistant");
// This tutorial only needs the final result. Use run(..., sink) when
// streaming events to SSE, WebSocket, protocol adapters, or tests.
let result = runtime.run_to_completion(request).await?;
println!("response: {}", result.response);
println!("termination: {:?}", result.termination);
Ok(())
}
Terminal window
cargo run

Expected output includes:

  • response: ...
  • termination: NaturalEnd

This example creates an in-process AgentRuntime and runs one request immediately.

The core object is:

let runtime = AgentRuntimeBuilder::new()
.with_agent_spec(agent_spec)
.with_tool("echo", Arc::new(EchoTool))
.with_provider("openai", Arc::new(GenaiExecutor::new()))
.with_model(ModelSpec::new("gpt-4o-mini", "openai", "gpt-4o-mini"))
.build()?;

After that, the normal entry point is:

let result = runtime.run_to_completion(request).await?;

Common usage patterns:

  • one-shot CLI program: construct RunActivation, call runtime.run_to_completion(...), print the result
  • application service: use runtime.run(...) with an EventSink when callers need streaming events
  • HTTP server: store Arc<AgentRuntime> in app state and expose protocol routes

The example above bakes system_prompt, model_id, and max_rounds into Rust. That’s the simplest path for a one-shot CLI. For any longer-running agent — anywhere you want to tune the prompt without recompiling — move the spec into config.

Keep EchoTool and the provider in code, drop with_agent_spec:

let runtime = AgentRuntimeBuilder::new()
.with_tool("echo", Arc::new(EchoTool))
.with_provider("openai", Arc::new(GenaiExecutor::new()))
.with_model(ModelSpec::new("gpt-4o-mini", "openai", "gpt-4o-mini"))
.build()?; // agent "assistant" will be resolved from the config snapshot

Then expose HTTP/SSE and PUT the agent spec once:

Terminal window
curl -sS -X PUT http://localhost:3000/v1/config/agents/assistant \
-H 'content-type: application/json' \
-d '{
"id": "assistant",
"model_id": "gpt-4o-mini",
"system_prompt": "You are a helpful assistant. Use the echo tool when asked.",
"max_rounds": 5
}'

To tweak the prompt later, just PUT the same id with a new system_prompt. The next POST /v1/runs reads the new snapshot — no rebuild, no restart. See Hot-Tune Prompts for the full loop.

Use the next page based on what you want:

  • Model/provider mismatch: gpt-4o-mini requires a compatible OpenAI-style provider setup.
  • Missing key: set OPENAI_API_KEY or DEEPSEEK_API_KEY before cargo run.
  • Tool not selected: ensure the prompt explicitly asks to use echo.
  • Early termination: check that with_max_rounds is high enough for the model to complete.