Build Your First Agent
This guide builds a simple task-tracking agent that receives instructions, calls tools, and stores results durably.
What you’ll build
Section titled “What you’ll build”A Cosmictron WASM module that:
- Receives task instructions over WebSocket
- Records tool invocations with full idempotency
- Pushes real-time state deltas back to the client
- Survives server restarts without losing state
Project structure
Section titled “Project structure”my-agent/├── Cargo.toml├── src/│ └── lib.rs # Your agent logic (WASM module)└── client/ └── index.ts # TypeScript clientDefine your schema
Section titled “Define your schema”In src/lib.rs, declare your tables and reducers:
use cosmictron_sdk::prelude::*;
/// Durable record of every agent message#[table(name = "agent_state", public)]pub struct AgentMessage { #[primary_key] #[auto_inc] pub id: u64, pub session_id: String, pub role: String, // "user" | "assistant" | "system" pub content: String, pub created_at: Timestamp,}
/// Tracks MCP tool invocations with idempotency#[table(name = "tool_calls")]pub struct ToolCall { #[primary_key] pub call_id: String, // client-provided idempotency key pub tool_name: String, pub arguments: String, // JSON-encoded pub status: String, // "pending" | "completed" | "failed" pub result: Option<String>, pub invoked_at: Timestamp,}Write reducers
Section titled “Write reducers”Reducers are state-mutating functions that execute inside the database:
/// Record a new message from user or assistant#[reducer]pub fn record_agent_message( ctx: &ReducerContext, session_id: String, role: String, content: String,) { AgentMessage::insert(AgentMessage { id: 0, // auto_inc session_id, role, content, created_at: ctx.timestamp, }) .expect("insert failed");}
/// Start a tool call — idempotent by call_id#[reducer]pub fn record_tool_call( ctx: &ReducerContext, call_id: String, tool_name: String, arguments: String,) { // Idempotency: skip if already recorded if ToolCall::filter_by_call_id(&call_id).is_some() { return; } ToolCall::insert(ToolCall { call_id, tool_name, arguments, status: "pending".to_string(), result: None, invoked_at: ctx.timestamp, }) .expect("insert failed");}
/// Complete a tool call with result#[reducer]pub fn complete_tool_call( _ctx: &ReducerContext, call_id: String, result: String, success: bool,) { if let Some(mut call) = ToolCall::filter_by_call_id(&call_id) { call.status = if success { "completed" } else { "failed" }.to_string(); call.result = Some(result); ToolCall::update(call); }}Build and deploy
Section titled “Build and deploy”# Build to WASMcargo build --target wasm32-unknown-unknown --release
# Deploy to running servercosmictron-cli deploy \ target/wasm32-unknown-unknown/release/my_agent.wasm \ --name my-agentConnect from TypeScript
Section titled “Connect from TypeScript”import { CosmictronClient } from '@cosmictron/client';
const client = new CosmictronClient('ws://localhost:3000');
// Authenticate (email/password, passkey, or magic link)await client.signIn({ email: 'dev@example.com', password: 'secret' });
// Subscribe to real-time agent stateconst sub = await client.subscribe( 'SELECT * FROM agent_state WHERE session_id = :sid ORDER BY id', { sid: 'session-abc' });
sub.on('update', ({ inserts, deletes }) => { console.log('New messages:', inserts);});
// Send a user messageawait client.callReducer('record_agent_message', { session_id: 'session-abc', role: 'user', content: 'Summarise the Q1 sales report',});
// Record a tool call (idempotent)await client.callReducer('record_tool_call', { call_id: 'tool-unique-id-001', tool_name: 'read_file', arguments: JSON.stringify({ path: '/reports/q1.pdf' }),});
// Mark the tool call completeawait client.callReducer('complete_tool_call', { call_id: 'tool-unique-id-001', result: JSON.stringify({ pages: 42, summary: '...' }), success: true,});What happens next
Section titled “What happens next”Once this module is deployed:
- State persists across server restarts via the WAL and snapshot system.
- All clients subscribed to
agent_statereceive real-time deltas when new messages are inserted. - Audit trail — if you have signing enabled, every reducer call is signed with Ed25519 and linked into the hash chain. See Event Signing.
Next steps
Section titled “Next steps”- Agents & Sessions — session lifecycle, supervisor handoff, voice provider support
- Events & Replay — deterministic replay and audit
- API Reference — full SDK macro and reducer reference