Adding a New Agent to a Rust Golem Component
Overview
An agent is a durable, stateful unit of computation in Golem. Each agent type is defined as a trait annotated with #[agent_definition] and implemented on a struct annotated with #[agent_implementation].
Steps
- Create the agent module — add a new file
src/<agent_name>.rs - Define the agent trait — annotate with
#[agent_definition] - Implement the agent — annotate with
#[agent_implementation] - Re-export from
lib.rs— addmod <agent_name>;andpub use <agent_name>::*; - Build — run
golem buildto verify
Agent Definition
use golem_rust::{agent_definition, agent_implementation};
#[agent_definition]
pub trait MyAgent {
// Constructor parameters form the agent's identity.
// Two agents with the same parameters are the same agent.
fn new(name: String) -> Self;
// Agent methods — can be sync or async
fn get_count(&self) -> u32;
fn increment(&mut self) -> u32;
async fn fetch_data(&self, url: String) -> String;
}
struct MyAgentImpl {
name: String,
count: u32,
}
#[agent_implementation]
impl MyAgent for MyAgentImpl {
fn new(name: String) -> Self {
Self { name, count: 0 }
}
fn get_count(&self) -> u32 {
self.count
}
fn increment(&mut self) -> u32 {
self.count += 1;
self.count
}
async fn fetch_data(&self, url: String) -> String {
// Use wstd::http for HTTP requests
todo!()
}
}Custom Types
All parameter and return types must implement the Schema trait. For custom types, derive it along with IntoValue and FromValueAndType:
use golem_rust::Schema;
use serde::{Serialize, Deserialize};
#[derive(Clone, Schema, Serialize, Deserialize)]
pub struct MyData {
pub field1: String,
pub field2: u32,
}Returning Failures
Agent methods should distinguish between domain errors (expected failure outcomes) and uncaught errors:
- Uncaught errors (panics, unwrapped
Err, etc.) are not returned to the caller as a failed invocation. Golem treats them as crashes: the invocation is retried according to the agent’s retry policy, and if the retries are exhausted the agent itself becomes failed. - Domain errors that the caller should observe as a normal failure result must be expressed in the method’s return type using
Result<T, E>. The error typeEmust deriveSchemalike any other custom type.
use golem_rust::{agent_definition, agent_implementation, Schema};
use serde::{Serialize, Deserialize};
#[derive(Clone, Debug, Schema, Serialize, Deserialize)]
pub enum WithdrawError {
InsufficientFunds { available: u64 },
AccountClosed,
}
#[agent_definition]
pub trait Wallet {
fn new(owner: String) -> Self;
fn withdraw(&mut self, amount: u64) -> Result<u64, WithdrawError>;
}
struct WalletImpl {
owner: String,
balance: u64,
closed: bool,
}
#[agent_implementation]
impl Wallet for WalletImpl {
fn new(owner: String) -> Self {
Self { owner, balance: 0, closed: false }
}
fn withdraw(&mut self, amount: u64) -> Result<u64, WithdrawError> {
if self.closed {
return Err(WithdrawError::AccountClosed);
}
if amount > self.balance {
return Err(WithdrawError::InsufficientFunds { available: self.balance });
}
self.balance -= amount;
Ok(self.balance)
}
}Returning Err(WithdrawError::...) completes the invocation successfully — the caller receives the error as a value. Panicking with unwrap(), expect(), panic!(), or returning from ? on an unrelated error type will instead trigger a retry and eventually fail the whole agent.
Key Constraints
- All agent method parameters are passed by value (no references)
- All custom types need
Schemaderive (plusIntoValueandFromValueAndType, whichSchemaimplies) - Constructor parameters form the agent identity — two agents with the same parameters are the same agent
- Agents are created implicitly on first invocation — no separate creation step
- Invocations are processed sequentially in a single thread — no concurrency within a single agent
- Never use
block_on— all agent methods run in an async context. Useasyncmethods and.awaitinstead of blocking on futures
Last updated on