Skip to main content

Agent API

The foundation of Spice Framework - intelligent, autonomous agents that process communications and execute tasks.

Overview

Agent is the core abstraction in Spice Framework. Agents are autonomous entities that:

  • Process communications - Handle incoming messages and generate responses
  • Execute tools - Perform actions through extensible tool system
  • Manage state - Track and persist operational state
  • Coordinate - Work with other agents in complex workflows
  • Monitor - Track metrics and performance data

Spice provides multiple agent implementations:

  • BaseAgent - Foundation for custom agents
  • SwarmAgent - Multi-agent coordinator with 5 execution strategies
  • LLMAgent - AI-powered agents (via providers)

Core Structure

interface Agent : Identifiable {
// Identity
val id: String
val name: String
val description: String
val capabilities: List<String>

// Core operations
suspend fun processComm(comm: Comm): SpiceResult<Comm>
suspend fun processComm(comm: Comm, runtime: AgentRuntime): SpiceResult<Comm>

// Agent capabilities
fun canHandle(comm: Comm): Boolean
fun getTools(): List<Tool>
fun isReady(): Boolean

// Configuration & lifecycle
fun getConfig(): AgentConfig
suspend fun initialize(runtime: AgentRuntime)
suspend fun cleanup()

// Vector stores (RAG support)
fun getVectorStore(name: String): VectorStore?
fun getVectorStores(): Map<String, VectorStore>

// Monitoring
fun getMetrics(): AgentMetrics
}

Creating Agents

The simplest way to create agents with LLM providers:

val agent = buildAgent {
name = "Research Assistant"
description = "Helps with research tasks"
capabilities = listOf("research", "analysis", "summarization")

// LLM provider
llm = anthropic(apiKey = "your-api-key") {
model = "claude-3-5-sonnet-20241022"
temperature = 0.7
maxTokens = 2048
}

// Add tools
tools {
tool("web_search") { /* ... */ }
tool("summarize") { /* ... */ }
}

// Add instructions
instructions = """
You are a research assistant that helps users find and analyze information.
Always cite your sources and provide comprehensive answers.
""".trimIndent()
}

Custom BaseAgent

For advanced use cases, extend BaseAgent:

class CustomAgent(
id: String,
name: String,
description: String,
capabilities: List<String> = emptyList(),
config: AgentConfig = AgentConfig()
) : BaseAgent(id, name, description, capabilities, config) {

override suspend fun processComm(comm: Comm): SpiceResult<Comm> {
log(LogLevel.INFO, "Processing comm: ${comm.id}")

return when (comm.type) {
CommType.TEXT -> handleTextMessage(comm)
CommType.TOOL_CALL -> handleToolCall(comm)
else -> SpiceResult.success(
comm.reply(
content = "Unsupported message type",
from = id
)
)
}
}

private suspend fun handleTextMessage(comm: Comm): SpiceResult<Comm> {
// Your custom processing logic
val response = processText(comm.content)

return SpiceResult.success(
comm.reply(
content = response,
from = id,
data = mapOf(
"processing_time_ms" to "120",
"model" to "custom-v1"
)
)
)
}

private suspend fun handleToolCall(comm: Comm): SpiceResult<Comm> {
val toolName = comm.getToolName() ?: return SpiceResult.success(
comm.error("Missing tool name", from = id)
)

val params = parseToolParams(comm.data["tool_params"] ?: "{}")

return executeTool(toolName, params).fold(
onSuccess = { toolResult ->
SpiceResult.success(
comm.toolResult(
result = toolResult.result,
from = toolName
)
)
},
onFailure = { error ->
SpiceResult.success(
comm.error(
message = "Tool execution failed: ${error.message}",
from = id
)
)
}
)
}

private fun processText(content: String): String {
// Your custom logic here
return "Processed: $content"
}
}

Multi-Agent Swarms

Create sophisticated multi-agent systems:

val swarm = buildSwarmAgent {
name = "Research Team"
description = "Multi-agent research team"

// Shared tools for all members
swarmTools {
tool("web_search", "Search the web") {
parameter("query", "string", "Search query", required = true)
execute(fun(params: Map<String, Any>): String {
val query = params["query"] as String
return searchWeb(query)
})
}

// Built-in coordination tools
aiConsensus(scoringAgent = anthropic(...))
resultAggregator()
}

// Define team members
quickSwarm {
specialist("researcher", "Researcher", "information gathering")
specialist("analyst", "Analyst", "data analysis")
specialist("writer", "Writer", "report writing")
}

// Or use existing agents
members {
agent(researchAgent)
agent(analysisAgent)
agent(writingAgent)
}
}

// Execute with automatic coordination
val result = swarm.processComm(Comm(
content = "Research the impact of AI on healthcare",
from = "user"
))

Agent Methods

processComm()

Process incoming communication and generate response:

// Basic processing
suspend fun processComm(comm: Comm): SpiceResult<Comm>

// With runtime context
suspend fun processComm(comm: Comm, runtime: AgentRuntime): SpiceResult<Comm>

Usage:

val agent = buildAgent { /* ... */ }

// Simple processing
val result = agent.processComm(Comm(
content = "Hello, agent!",
from = "user"
))

result.fold(
onSuccess = { response ->
println("Agent response: ${response.content}")
},
onFailure = { error ->
println("Error: ${error.message}")
}
)

// With runtime context
val runtime = DefaultAgentRuntime(
context = AgentContext.of(
"userId" to "user-123",
"sessionId" to "session-456"
)
)

val result = agent.processComm(comm, runtime)

Return Value:

  • SpiceResult.success(Comm) - Successful response
  • SpiceResult.failure(SpiceError) - Processing error

canHandle()

Check if agent can handle a specific comm:

fun canHandle(comm: Comm): Boolean

Usage:

val comm = Comm(content = "Analyze this data", from = "user")

if (agent.canHandle(comm)) {
val result = agent.processComm(comm)
// Process result
} else {
// Route to different agent
val alternateResult = otherAgent.processComm(comm)
}

Default Implementation (BaseAgent):

  • Returns true for TEXT, PROMPT, SYSTEM, WORKFLOW_START, WORKFLOW_END
  • Returns true for TOOL_CALL if agent has the requested tool
  • Returns false for other types

Override for custom logic:

override fun canHandle(comm: Comm): Boolean {
return when {
comm.type == CommType.TEXT && comm.content.contains("research") -> true
comm.from == "trusted-system" -> true
comm.priority == Priority.CRITICAL -> true
else -> super.canHandle(comm)
}
}

getTools()

Retrieve tools available to the agent:

fun getTools(): List<Tool>

Usage:

val agent = buildAgent {
name = "Assistant"
tools {
tool("calculator") { /* ... */ }
tool("weather") { /* ... */ }
}
}

// List all tools
val tools = agent.getTools()
tools.forEach { tool ->
println("Tool: ${tool.name} - ${tool.description}")
}

// Find specific tool
val calculator = tools.find { it.name == "calculator" }
if (calculator != null) {
val result = calculator.execute(mapOf("a" to 10, "b" to 20, "op" to "+"))
}

// Check tool availability
val hasWeatherTool = tools.any { it.name == "weather" }

isReady()

Check if agent is ready for operation:

fun isReady(): Boolean

Usage:

if (agent.isReady()) {
val result = agent.processComm(comm)
} else {
// Initialize agent first
agent.initialize(runtime)
// Or wait and retry
}

Common reasons for not ready:

  • Dependencies not initialized
  • Required resources unavailable
  • Configuration incomplete
  • Network connectivity issues

Custom readiness checks:

override fun isReady(): Boolean {
return databaseConnected &&
cacheInitialized &&
externalApiAvailable &&
super.isReady()
}

initialize() / cleanup()

Lifecycle management:

suspend fun initialize(runtime: AgentRuntime)
suspend fun cleanup()

Usage:

class DatabaseAgent : BaseAgent(...) {
private lateinit var dbConnection: Connection

override suspend fun initialize(runtime: AgentRuntime) {
super.initialize(runtime)

log(LogLevel.INFO, "Connecting to database")
dbConnection = connectToDatabase()

log(LogLevel.INFO, "Loading cache")
loadCache()
}

override suspend fun cleanup() {
log(LogLevel.INFO, "Closing database connection")
dbConnection.close()

super.cleanup()
}
}

// Usage
val agent = DatabaseAgent(...)
try {
agent.initialize(runtime)

// Use agent
agent.processComm(comm)

} finally {
agent.cleanup()
}

getConfig()

Access agent configuration:

fun getConfig(): AgentConfig

Usage:

val config = agent.getConfig()

println("Max concurrent requests: ${config.maxConcurrentRequests}")
println("Request timeout: ${config.requestTimeoutMs}ms")
println("Retry policy: ${config.retryPolicy.maxRetries} retries")

if (config.monitoring.enableMetrics) {
collectMetrics(agent)
}

getVectorStore() / getVectorStores()

Access vector stores for RAG (Retrieval-Augmented Generation):

fun getVectorStore(name: String): VectorStore?
fun getVectorStores(): Map<String, VectorStore>

Usage:

class RAGAgent : BaseAgent(...) {

override suspend fun initialize(runtime: AgentRuntime) {
super.initialize(runtime)

// Add vector stores
val docsStore = createVectorStore("documentation")
addVectorStore("docs", docsStore)

val knowledgeStore = createVectorStore("knowledge_base")
addVectorStore("knowledge", knowledgeStore)
}

override suspend fun processComm(comm: Comm): SpiceResult<Comm> {
// Retrieve relevant context
val docsStore = getVectorStore("docs")
val relevantDocs = docsStore?.search(comm.content, limit = 5)

// Use context in response
val contextualResponse = generateResponse(comm.content, relevantDocs)

return SpiceResult.success(comm.reply(
content = contextualResponse,
from = id
))
}
}

// Check available stores
val agent = RAGAgent(...)
agent.getVectorStores().forEach { (name, store) ->
println("Vector store: $name (${store.size()} vectors)")
}

getMetrics()

Access performance metrics:

fun getMetrics(): AgentMetrics

Usage:

val metrics = agent.getMetrics()

println("=== Agent Performance ===")
println("Total requests: ${metrics.totalRequests}")
println("Successful: ${metrics.successfulRequests}")
println("Failed: ${metrics.failedRequests}")
println("Success rate: ${metrics.getSuccessRate() * 100}%")
println("Avg response time: ${metrics.getAverageResponseTimeMs()}ms")

// Tool-specific metrics
metrics.toolMetrics.forEach { (toolName, toolMetrics) ->
println("Tool '$toolName':")
println(" Total calls: ${toolMetrics.totalCalls}")
println(" Success: ${toolMetrics.successfulCalls}")
println(" Failed: ${toolMetrics.failedCalls}")
}

BaseAgent Protected Methods

When extending BaseAgent, these protected methods are available:

executeTool()

Execute a tool by name:

protected suspend fun executeTool(
toolName: String,
parameters: Map<String, Any>
): SpiceResult<ToolResult>

Usage:

class MyAgent : BaseAgent(...) {
override suspend fun processComm(comm: Comm): SpiceResult<Comm> {
// Execute calculator tool
val result = executeTool("calculator", mapOf(
"a" to 10,
"b" to 20,
"operation" to "+"
))

return result.fold(
onSuccess = { toolResult ->
if (toolResult.success) {
SpiceResult.success(comm.reply(
content = "Result: ${toolResult.result}",
from = id
))
} else {
SpiceResult.success(comm.error(
message = toolResult.error,
from = id
))
}
},
onFailure = { error ->
SpiceResult.failure(error)
}
)
}
}

log()

Log messages through runtime:

protected fun log(
level: LogLevel,
message: String,
data: Map<String, Any> = emptyMap()
)

Usage:

class MyAgent : BaseAgent(...) {
override suspend fun processComm(comm: Comm): SpiceResult<Comm> {
log(LogLevel.INFO, "Processing comm", mapOf(
"commId" to comm.id,
"from" to comm.from,
"type" to comm.type.name
))

try {
val result = processMessage(comm)

log(LogLevel.INFO, "Processing complete", mapOf(
"duration_ms" to 120
))

return SpiceResult.success(result)

} catch (e: Exception) {
log(LogLevel.ERROR, "Processing failed", mapOf(
"error" to e.message.orEmpty(),
"stack_trace" to e.stackTraceToString()
))

return SpiceResult.failure(SpiceError(
message = "Processing failed: ${e.message}",
code = "PROCESSING_ERROR"
))
}
}
}

callAgent()

Call another agent:

protected suspend fun callAgent(
agentId: String,
comm: Comm
): SpiceResult<Comm>?

Usage:

class CoordinatorAgent : BaseAgent(...) {
override suspend fun processComm(comm: Comm): SpiceResult<Comm> {
log(LogLevel.INFO, "Delegating to specialist agent")

// Call specialist agent
val specialistResult = callAgent("specialist-agent", comm)

return if (specialistResult != null) {
specialistResult.fold(
onSuccess = { response ->
// Process and augment response
SpiceResult.success(response.copy(
content = "Coordinated result: ${response.content}"
))
},
onFailure = { error ->
SpiceResult.failure(error)
}
)
} else {
SpiceResult.success(comm.error(
message = "Specialist agent not available",
from = id
))
}
}
}

publishEvent()

Publish events to the system:

protected suspend fun publishEvent(
type: String,
data: Map<String, Any> = emptyMap()
)

Usage:

class MonitoringAgent : BaseAgent(...) {
override suspend fun processComm(comm: Comm): SpiceResult<Comm> {
// Publish start event
publishEvent("processing.started", mapOf(
"commId" to comm.id,
"timestamp" to System.currentTimeMillis()
))

val result = process(comm)

// Publish completion event
publishEvent("processing.completed", mapOf(
"commId" to comm.id,
"success" to result.isSuccess,
"timestamp" to System.currentTimeMillis()
))

return result
}
}

saveState() / getState()

Persist and retrieve agent state:

protected suspend fun saveState(key: String, value: Any)
protected suspend fun getState(key: String): Any?

Usage:

class StatefulAgent : BaseAgent(...) {
override suspend fun processComm(comm: Comm): SpiceResult<Comm> {
// Load conversation history
val history = getState("conversation_history") as? List<Comm> ?: emptyList()

// Process with context
val response = processWithHistory(comm, history)

// Save updated history
val updatedHistory = history + comm + response
saveState("conversation_history", updatedHistory)

return SpiceResult.success(response)
}

suspend fun clearHistory() {
saveState("conversation_history", emptyList<Comm>())
}
}

Configuration

AgentConfig

Configure agent behavior:

data class AgentConfig(
val maxConcurrentRequests: Int = 10,
val requestTimeoutMs: Long = 30_000,
val retryPolicy: RetryPolicy = RetryPolicy.default(),
val rateLimiting: RateLimiting? = null,
val monitoring: MonitoringConfig = MonitoringConfig()
)

Usage:

val config = AgentConfig(
maxConcurrentRequests = 5,
requestTimeoutMs = 60_000,
retryPolicy = RetryPolicy(
maxRetries = 3,
initialDelayMs = 1000,
maxDelayMs = 10000,
backoffMultiplier = 2.0
),
rateLimiting = RateLimiting(
maxRequestsPerMinute = 100,
maxBurstSize = 20
),
monitoring = MonitoringConfig(
enableMetrics = true,
enableTracing = true,
enableLogging = true,
logSlowRequests = true,
slowRequestThresholdMs = 5000
)
)

val agent = MyAgent(
id = "agent-1",
name = "Configured Agent",
description = "Agent with custom config",
config = config
)

RetryPolicy

Configure retry behavior:

data class RetryPolicy(
val maxRetries: Int = 3,
val initialDelayMs: Long = 1000,
val maxDelayMs: Long = 10000,
val backoffMultiplier: Double = 2.0
)

Examples:

// No retries (fail fast)
val noRetry = RetryPolicy.noRetry()

// Aggressive retries
val aggressive = RetryPolicy(
maxRetries = 10,
initialDelayMs = 100,
maxDelayMs = 5000,
backoffMultiplier = 1.5
)

// Default (balanced)
val balanced = RetryPolicy.default()

RateLimiting

Control request rate:

data class RateLimiting(
val maxRequestsPerMinute: Int,
val maxBurstSize: Int = maxRequestsPerMinute
)

Usage:

// 60 requests per minute, 10 burst
val rateLimiting = RateLimiting(
maxRequestsPerMinute = 60,
maxBurstSize = 10
)

val config = AgentConfig(
rateLimiting = rateLimiting
)

MonitoringConfig

Configure monitoring:

data class MonitoringConfig(
val enableMetrics: Boolean = true,
val enableTracing: Boolean = true,
val enableLogging: Boolean = true,
val logSlowRequests: Boolean = true,
val slowRequestThresholdMs: Long = 5000
)

Runtime & Context

AgentRuntime

Execution environment for agents:

interface AgentRuntime {
val context: AgentContext
val scope: CoroutineScope

suspend fun callAgent(agentId: String, comm: Comm): SpiceResult<Comm>
suspend fun publishEvent(event: AgentEvent)
suspend fun saveState(key: String, value: Any)
suspend fun getState(key: String): Any?
fun log(level: LogLevel, message: String, data: Map<String, Any> = emptyMap())
}

Usage:

val runtime = DefaultAgentRuntime(
context = AgentContext.of(
"userId" to "user-123",
"sessionId" to "session-456",
"locale" to "en-US"
)
)

// Initialize agent with runtime
agent.initialize(runtime)

// Process with runtime context
val result = agent.processComm(comm, runtime)

AgentContext

Flexible context storage:

val context = AgentContext.of(
"userId" to "user-123",
"tenantId" to "tenant-456",
"permissions" to listOf("read", "write")
)

// Add values
context["requestId"] = "req-789"

// Get values
val userId = context.getAs<String>("userId")

// Check existence
if (context.has("permissions")) {
val perms = context.getAs<List<String>>("permissions")
}

// Builder style
val newContext = context
.with("traceId", "trace-123")
.with("correlationId", "corr-456")

// DSL
val context = agentContext {
this["userId"] = "user-123"
this["sessionId"] = "session-456"
}

Standard Context Keys:

object ContextKeys {
const val USER_ID = "userId"
const val SESSION_ID = "sessionId"
const val TENANT_ID = "tenantId"
const val LOCALE = "locale"
const val TIMEZONE = "timezone"
const val CORRELATION_ID = "correlationId"
const val REQUEST_ID = "requestId"
const val TRACE_ID = "traceId"
const val FEATURES = "features"
const val PERMISSIONS = "permissions"
const val METADATA = "metadata"
}

SwarmAgent

Multi-agent coordination system with 5 execution strategies:

Execution Strategies

enum class SwarmStrategyType {
PARALLEL, // Execute all agents simultaneously
SEQUENTIAL, // Execute in sequence, passing results forward
CONSENSUS, // Build consensus through multi-round discussion
COMPETITION, // Compete and select best result
HIERARCHICAL // Hierarchical delegation with levels
}

Usage

val swarm = buildSwarmAgent {
name = "Research Team"

swarmTools {
tool("search") { /* ... */ }
aiConsensus(scoringAgent = llm)
}

quickSwarm {
specialist("researcher", "Researcher", "research")
specialist("analyst", "Analyst", "analysis")
specialist("writer", "Writer", "writing")
}
}

// Process automatically selects strategy
val result = swarm.processComm(Comm(
content = "Research AI safety and write a report",
from = "user"
))

// Check swarm status
val status = swarm.getSwarmStatus()
println("Active operations: ${status.activeOperations}")
println("Success rate: ${status.averageSuccessRate}")

// Get operation history
val history = swarm.getOperationHistory()
history.take(5).forEach { result ->
println("${result.timestamp}: ${result.successRate * 100}% success")
}

Real-World Examples

Research Agent with Tools

val researchAgent = buildAgent {
name = "Research Assistant"
description = "AI-powered research assistant"
capabilities = listOf("research", "analysis", "summarization")

llm = anthropic(apiKey = env["ANTHROPIC_API_KEY"]!!) {
model = "claude-3-5-sonnet-20241022"
temperature = 0.7
}

tools {
tool("web_search", "Search the web") {
parameter("query", "string", "Search query", required = true)
parameter("limit", "number", "Max results", required = false)

execute(fun(params: Map<String, Any>): String {
val query = params["query"] as String
val limit = (params["limit"] as? Number)?.toInt() ?: 10

val results = searchEngine.search(query, limit)
return results.joinToString("\n") { result ->
"${result.title}: ${result.url}"
}
})
}

tool("summarize", "Summarize text") {
parameter("text", "string", "Text to summarize", required = true)
parameter("max_length", "number", "Max summary length", required = false)

execute(fun(params: Map<String, Any>): String {
val text = params["text"] as String
val maxLength = (params["max_length"] as? Number)?.toInt() ?: 500

return summarizer.summarize(text, maxLength)
})
}
}

instructions = """
You are a research assistant that helps users find and analyze information.

When given a research task:
1. Use web_search to find relevant sources
2. Analyze the information critically
3. Use summarize if content is too long
4. Provide comprehensive, well-cited answers

Always cite your sources with URLs.
""".trimIndent()
}

// Use the agent
val result = researchAgent.processComm(Comm(
content = "Research the latest developments in quantum computing",
from = "user"
))

Custom Agent with State Management

class ConversationAgent(
id: String,
name: String = "Conversation Agent",
description: String = "Maintains conversation context"
) : BaseAgent(id, name, description) {

private data class ConversationState(
val history: List<Comm>,
val context: Map<String, Any>
)

override suspend fun processComm(comm: Comm): SpiceResult<Comm> {
// Load state
val state = loadConversationState(comm.conversationId)

// Process with context
val response = processWithContext(comm, state)

// Update state
saveConversationState(
comm.conversationId,
state.copy(
history = state.history + comm + response,
context = extractContext(response)
)
)

return SpiceResult.success(response)
}

private suspend fun loadConversationState(conversationId: String?): ConversationState {
if (conversationId == null) return ConversationState(emptyList(), emptyMap())

val saved = getState("conv:$conversationId") as? ConversationState
return saved ?: ConversationState(emptyList(), emptyMap())
}

private suspend fun saveConversationState(
conversationId: String?,
state: ConversationState
) {
if (conversationId != null) {
saveState("conv:$conversationId", state)
}
}

private fun processWithContext(comm: Comm, state: ConversationState): Comm {
val contextSummary = if (state.history.isEmpty()) {
"New conversation"
} else {
"Previous messages: ${state.history.size}, Context: ${state.context.keys}"
}

log(LogLevel.INFO, "Processing with context", mapOf(
"context_summary" to contextSummary
))

// Generate response using context
val content = generateResponse(comm.content, state)

return comm.reply(
content = content,
from = id,
data = mapOf(
"context_used" to state.context.keys.joinToString(",")
)
)
}

private fun extractContext(comm: Comm): Map<String, Any> {
// Extract entities, intents, etc. from the response
return mapOf(
"last_topic" to extractTopic(comm.content),
"entities" to extractEntities(comm.content)
)
}
}

Multi-Agent Data Pipeline

val dataProcessingSwarm = buildSwarmAgent {
name = "Data Processing Pipeline"
description = "Multi-stage data processing system"

// Shared tools
swarmTools {
tool("validate_data", "Validate data format") {
parameter("data", "string", "Data to validate", required = true)
execute(fun(params: Map<String, Any>): String {
val data = params["data"] as String
return if (isValidJson(data)) "valid" else "invalid"
})
}

tool("transform_data", "Transform data") {
parameter("data", "string", "Data to transform", required = true)
parameter("format", "string", "Target format", required = true)
execute(fun(params: Map<String, Any>): String {
val data = params["data"] as String
val format = params["format"] as String
return transformData(data, format)
})
}

tool("store_data", "Store processed data") {
parameter("data", "string", "Data to store", required = true)
execute(fun(params: Map<String, Any>): String {
val data = params["data"] as String
database.store(data)
return "stored"
})
}
}

// Pipeline stages
members {
// Stage 1: Validation
agent(buildAgent {
name = "Validator"
description = "Validates incoming data"
llm = anthropic(...) { model = "claude-3-5-haiku-20241022" }
})

// Stage 2: Transformation
agent(buildAgent {
name = "Transformer"
description = "Transforms data to target format"
llm = anthropic(...) { model = "claude-3-5-sonnet-20241022" }
})

// Stage 3: Storage
agent(buildAgent {
name = "Archiver"
description = "Stores processed data"
llm = anthropic(...) { model = "claude-3-5-haiku-20241022" }
})
}
}

// Process data through pipeline (automatically uses SEQUENTIAL strategy)
val result = dataProcessingSwarm.processComm(Comm(
content = "Process this data: {\"user\": \"Alice\", \"action\": \"login\"}",
from = "data-source"
))

result.fold(
onSuccess = { response ->
println("Pipeline result: ${response.content}")
println("Stages completed: ${response.data["participating_agents"]}")
},
onFailure = { error ->
println("Pipeline failed: ${error.message}")
}
)

Best Practices

1. Use Appropriate Agent Types

// ✅ Good - Simple single-agent tasks
val agent = buildAgent {
name = "Calculator"
llm = anthropic(...)
tools { tool("calculate") { /* ... */ } }
}

// ✅ Good - Multi-agent coordination
val swarm = buildSwarmAgent {
name = "Analysis Team"
members { /* multiple agents */ }
}

// ❌ Bad - Using swarm for simple tasks
val unnecessarySwarm = buildSwarmAgent {
name = "Calculator Swarm"
members { agent(simpleCalculator) } // Overkill!
}

2. Handle Errors Gracefully

// ✅ Good - Proper error handling
override suspend fun processComm(comm: Comm): SpiceResult<Comm> {
return try {
val result = process(comm)
SpiceResult.success(result)
} catch (e: Exception) {
log(LogLevel.ERROR, "Processing failed", mapOf("error" to e.message.orEmpty()))
SpiceResult.failure(SpiceError(
message = "Processing failed: ${e.message}",
code = "PROCESSING_ERROR",
cause = e
))
}
}

// ❌ Bad - Swallowing errors
override suspend fun processComm(comm: Comm): SpiceResult<Comm> {
val result = process(comm) // Can throw!
return SpiceResult.success(result)
}

3. Use Metrics for Monitoring

// ✅ Good - Monitor performance
class MonitoredAgent : BaseAgent(...) {
override suspend fun processComm(comm: Comm): SpiceResult<Comm> {
val startTime = System.currentTimeMillis()

return try {
val result = process(comm)

val duration = System.currentTimeMillis() - startTime
log(LogLevel.INFO, "Processing complete", mapOf(
"duration_ms" to duration
))

if (duration > 5000) {
publishEvent("slow_request", mapOf(
"duration_ms" to duration,
"commId" to comm.id
))
}

SpiceResult.success(result)
} catch (e: Exception) {
log(LogLevel.ERROR, "Processing failed", mapOf(
"error" to e.message.orEmpty()
))
SpiceResult.failure(SpiceError.from(e))
}
}
}

// Check metrics regularly
fun monitorAgent(agent: Agent) {
val metrics = agent.getMetrics()

if (metrics.getSuccessRate() < 0.95) {
alertOps("Agent ${agent.id} success rate below threshold")
}

if (metrics.getAverageResponseTimeMs() > 10000) {
alertOps("Agent ${agent.id} average response time too high")
}
}

4. Implement Proper Lifecycle Management

// ✅ Good - Clean lifecycle
class ResourceAgent : BaseAgent(...) {
private lateinit var connection: Connection

override suspend fun initialize(runtime: AgentRuntime) {
super.initialize(runtime)
connection = createConnection()
log(LogLevel.INFO, "Agent initialized")
}

override suspend fun cleanup() {
try {
connection.close()
log(LogLevel.INFO, "Agent cleaned up")
} catch (e: Exception) {
log(LogLevel.ERROR, "Cleanup failed", mapOf("error" to e.message.orEmpty()))
} finally {
super.cleanup()
}
}
}

// Usage
val agent = ResourceAgent(...)
try {
agent.initialize(runtime)
// Use agent
} finally {
agent.cleanup() // Always cleanup
}

5. Design for Testability

// ✅ Good - Testable design
class TestableAgent(
id: String,
name: String,
private val processor: MessageProcessor, // Injected dependency
config: AgentConfig = AgentConfig()
) : BaseAgent(id, name, "Testable agent", config = config) {

override suspend fun processComm(comm: Comm): SpiceResult<Comm> {
val result = processor.process(comm.content)
return SpiceResult.success(comm.reply(
content = result,
from = id
))
}
}

// Easy to test with mock processor
@Test
fun testAgent() = runTest {
val mockProcessor = MockMessageProcessor()
val agent = TestableAgent(
id = "test-agent",
name = "Test Agent",
processor = mockProcessor
)

val result = agent.processComm(Comm(
content = "test",
from = "user"
))

assertTrue(result.isSuccess)
}

Next Steps