Agent Specification Guide - Function-Based Approach¶
Overview¶
📚 Related Documentation: This guide focuses on agent development. For a comprehensive overview of the platform architecture, see ABS Platform Design Guide. This guide shows you how to specify complex agent logic using functions instead of simple string rules. You write actual business logic code, the architect handles the infrastructure.
Updates (Sept 2025) - CalculationFunction:
(request, servicePlan, context)—agentParamsis not passed; derive fromservicePlan.service_plan_template.agent_config.agent_paramsinside context. - Expose a singleserviceAgentper module; keep canonical functions as private helpers called byserviceAgent. - Multiple versions are resolved viamodels/battery-swap/setup-data/agent-versions.jsonand name-agnostic export detection.
Where You Specify Agent Logic¶
1. Write Calculation Functions¶
Create models/[business-model]/[agent-name]-functions.ts:
// Example: Complex service availability calculation
export const calculateServiceAvailability: CalculationFunction = async (
request,
servicePlan,
context
): Promise<AgentResult> => {
const { utils } = context;
// Get current service state
const serviceState = utils.getServiceState(request.data.service_id);
// Check if service is in battery issue state
const isBatteryIssue = serviceState.status === 'BATTERY_ISSUE';
// Calculate days since issue started
const daysInWaiting = utils.getDaysSince(new Date(serviceState.issue_started_at));
// Check battery availability at location
const batteryAvailability = await utils.getBatteryAvailability(request.data.location_id);
// Complex business logic
if (isBatteryIssue && daysInWaiting > 15 && batteryAvailability.count < 3) {
return {
fsmInputs: [{ cycle: 'service_cycle', input: 'SERVICE_LIMITED' }],
signals: ['SERVICE_AVAILABILITY_LIMITED'],
metadata: {
reason: 'Battery issue + long wait + low inventory',
days_in_waiting: daysInWaiting,
available_batteries: batteryAvailability.count
}
};
}
// More conditions...
return {
fsmInputs: [{ cycle: 'service_cycle', input: 'SERVICE_AVAILABLE' }],
signals: ['SERVICE_AVAILABLE'],
metadata: { battery_availability: batteryAvailability.count }
};
};
2. Define Agent Configuration¶
Create models/[business-model]/[agent-name]-definition.ts:
export const BSSAgentDefinition: AgentDefinition = {
agent_type: "BSSAgent",
version: "1.0.0",
calculations: {
serviceAgent: {
description: "Single delegation function that calls private helpers",
inputs: ["plan_id", "request_type", "data"],
compute: calculateServiceAvailability, // Example wiring; in practice compute: serviceAgent
outputs: ["signals", "metadata"]
}
},
fsm_mappings: {
"SERVICE_AVAILABLE": "service_cycle.SERVICE_AVAILABLE",
"SERVICE_LIMITED": "service_cycle.SERVICE_LIMITED"
},
agent_params: {
max_waiting_days: 15,
min_battery_availability: 3
}
};
Available Utilities¶
The context.utils provides helper functions for complex logic:
Data Access¶
// Get service state
const serviceState = utils.getServiceState(serviceId);
// Calculate quota usage
const quotaUsage = utils.calculateQuotaUsage(serviceId);
// Returns: { used: number, quota: number, percentage: number }
// Check location permission
const isAllowed = utils.isLocationAllowed(locationId);
Time Calculations¶
// Calculate days since a date
const daysSince = utils.getDaysSince(someDate);
External Data¶
// Get battery availability (async)
const batteryAvailability = await utils.getBatteryAvailability(locationId);
// Returns: { available: boolean, count: number }
Complex Business Logic Examples¶
Example 1: Multi-Condition Service Availability¶
// "if the current service_state is at battery issue, and the number of days in waiting has exceed 15, and the battery availability is low, then return service availability as limited."
if (isBatteryIssue && daysInWaiting > 15 && batteryAvailability.count < 3) {
return {
fsmInputs: [{ cycle: 'service_cycle', input: 'SERVICE_LIMITED' }],
signals: ['SERVICE_AVAILABILITY_LIMITED'],
metadata: {
reason: 'Battery issue + long wait + low inventory',
days_in_waiting: daysInWaiting,
available_batteries: batteryAvailability.count
}
};
}
Example 2: Usage Pattern Detection¶
// Check for unusual usage patterns
const lastUsage = serviceState.last_usage_timestamp;
if (lastUsage) {
const hoursSinceLastUsage = utils.getDaysSince(new Date(lastUsage)) * 24;
if (hoursSinceLastUsage < 1) {
return {
fsmInputs: [{ cycle: 'service_cycle', input: 'USAGE_TOO_FREQUENT' }],
signals: ['USAGE_TOO_FREQUENT'],
metadata: {
hours_since_last: hoursSinceLastUsage,
reason: 'Usage too frequent - possible abuse'
}
};
}
}
Example 3: Complex Asset Allocation¶
// Check multiple conditions for asset allocation
if (!utils.isLocationAllowed(locationId)) {
return { /* ACCESS_DENIED */ };
}
if (context.plan_state.status !== 'ACTIVE') {
return { /* PLAN_INACTIVE */ };
}
const quotaUsage = utils.calculateQuotaUsage(serviceId);
if (quotaUsage.percentage >= 100) {
return { /* QUOTA_EXHAUSTED */ };
}
const batteryAvailability = await utils.getBatteryAvailability(locationId);
if (!batteryAvailability.available) {
return { /* NO_INVENTORY */ };
}
// All checks passed - allocate
return {
fsmInputs: [{ cycle: 'service_cycle', input: 'ASSET_ALLOCATED' }],
signals: ['ALLOCATION_OK'],
metadata: { asset_id: allocatedAssetId }
};
Your Role vs. Architect Role¶
You Write (Business Logic):¶
- Complex calculation functions with if/then/else logic
- Business rules and conditions
- Data validation and error handling
- External service calls via utils
- FSM input mapping decisions
Architect Provides (Infrastructure):¶
- Calculation engine that executes your functions
- Utility functions for common operations
- Context management and data access
- FSM execution engine
- Plugin registration system
Benefits of Function-Based Approach¶
✅ Express complex logic naturally with if/then/else ✅ Use async/await for external service calls ✅ Access rich context with utility functions ✅ Type safety with TypeScript ✅ Testable - functions can be unit tested ✅ Maintainable - clear, readable business logic
Migration from String Rules¶
Old (String-based):
{
"condition": "service_state.used >= service_state.quota",
"action": "return ACCESS_DENIED with reason 'QUOTA_EXHAUSTED'"
}
New (Function-based):
const quotaUsage = utils.calculateQuotaUsage(serviceId);
if (quotaUsage.percentage >= 100) {
return {
fsmInputs: [{ cycle: 'service_cycle', input: 'ACCESS_DENIED' }],
signals: ['QUOTA_EXHAUSTED'],
metadata: { quota_usage: quotaUsage.percentage }
};
}
The function-based approach gives you the full power of programming while maintaining the data-driven architecture!