Skip to content

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) — agentParams is not passed; derive from servicePlan.service_plan_template.agent_config.agent_params inside context. - Expose a single serviceAgent per module; keep canonical functions as private helpers called by serviceAgent. - Multiple versions are resolved via models/battery-swap/setup-data/agent-versions.json and 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!