Skip to content

Template-Driven Architecture Implementation

Overview

This document describes the implementation of the template-driven architecture for ServicePlan management, which simplifies agent logic configuration and ensures contract stability.

Updates (Sept 2025) - ServicePlanTemplate aligns with current schema: country_code, legal_jurisdiction, billing_currency, contract_terms, service_cycle_fsm, payment_cycle_fsm, and agent_config. - Removed legacy fields: default_plan_params, default_agent_params, agent, agent_version. - ServicePlan embeds plan_state, service_account, payment_account, and has service_state/payment_state initialized from FSMs; no plan-level currency. - No overrides in createServicePlanFromTemplate; only template_id and customer_id. - Agents expose a single serviceAgent; CalculationFunction is (request, servicePlan, context) and agent params are derived from servicePlan.service_plan_template.agent_config.agent_params.

Key Changes Made

1. Schema Updates

ServicePlanTemplate (current schema)

type ServicePlanTemplate {
  id: ID!
  name: String!
  description: String!
  version: String!
  status: String!

  country_code: String!
  legal_jurisdiction: String!
  billing_currency: String!

  contract_terms: CommonTerms!
  service_cycle_fsm: MealyFSM!
  payment_cycle_fsm: MealyFSM!
  agent_config: AgentConfig!

  created_at: DateTime!
  updated_at: DateTime!
  created_by: String!
  change_log: [TemplateChange!]!
}

ServicePlan (current schema highlights)

type ServicePlan {
  id: ID!
  customer_id: ID!
  template_id: ID!
  template_version: String!
  plan_state: CommonState!
  service_state: String!
  payment_state: String!
  agent_state: JSON @domainSpecific
  service_account: ServiceAccount!
  payment_account: PaymentAccount!
}

Added Template Operations

type Query {
  servicePlanTemplates: [ServicePlanTemplate!]!
  servicePlanTemplate(id: ID!): ServicePlanTemplate
}

type Mutation {
  createServicePlanTemplate(input: CreateTemplateInput!): ServicePlanTemplate!
  updateServicePlanTemplate(id: ID!, input: UpdateTemplateInput!): ServicePlanTemplate!
  deprecateServicePlanTemplate(id: ID!): ServicePlanTemplate!
  createServicePlanFromTemplate(input: CreateServicePlanFromTemplateInput!): ServicePlanCloneResult!
}

2. Removed Complex Handler System

Deleted Files

  • src/core/commands.ts - Command registry system
  • src/core/runtime.ts - Dynamic handler runtime
  • src/core/handlers/ - Individual handler files
  • src/core/registries/ - Registry system

Simplified Agent Logic

The agent now directly uses ServicePlan data without complex handler abstraction:

export class BssPlanAgent extends BaseAgent {
  verifyServiceQuota(input: QuotaCheckInput) {
    // 🔥 Use ServicePlan's own data in computation
    const planParams = this.params.plan_params;
    const agentParams = this.params.agent_params;
    const planState = this.params.plan_state;
    const agentState = this.state; // ServicePlan's agent_state

    // 🔥 Apply ServicePlan-specific logic based on its params
    const strictMode = agentParams?.quota_validation?.strict_mode || false;

    // ... computation logic ...

    // 🔥 Update ServicePlan's own agent_state
    this.updateState(updatedState);
  }
}

3. Template Service Implementation

Created src/core/template-service.ts

export class ServicePlanTemplateService {
  // Create new template
  async createTemplate(input: CreateTemplateInput): Promise<ServicePlanTemplate>

  // Create ServicePlan from template
  async createServicePlanFromTemplate(input: CreateServicePlanFromTemplateInput): Promise<ServicePlanCloneResult>

  // Refine template (create new version)
  async refineTemplate(templateId: string, refinements: any): Promise<ServicePlanTemplate>

  // Validate template refinements don't break existing contracts
  async validateTemplateRefinement(template: ServicePlanTemplate, refinements: any): Promise<void>
}

4. Updated Resolvers

Template Operations

export const resolvers = {
  Query: {
    async servicePlanTemplates() { /* ... */ },
    async servicePlanTemplate(_: any, { id }: { id: string }) { /* ... */ }
  },

  Mutation: {
    async createServicePlanTemplate(_: any, { input }: { input: any }) {
      return await templateService.createTemplate(input);
    },

    async createServicePlanFromTemplate(_: any, { input }: { input: any }) {
      return await templateService.createServicePlanFromTemplate(input);
    }
  }
}

ServicePlan Data Integration

function createAgent(servicePlan: any): BssPlanAgent {
  const config: BaseAgentConfig = {
    // ... agent config ...

    // ServicePlan's own data
    params: {
      plan_params: servicePlan.plan_params,
      plan_state: servicePlan.plan_state,
      agent_params: servicePlan.agent_params,
      service_account: servicePlan.service_account,
      payment_account: servicePlan.payment_account
    },

    // ServicePlan's own agent_state
    initial_state: servicePlan.agent_state || {}
  };
  return new BssPlanAgent(config);
}

Benefits Achieved

1. Simplified Architecture

  • Removed complex handler system - No more dynamic registry building
  • Direct template logic - Agents follow template configuration directly
  • Clear data flow - ServicePlan data flows directly into agent computation

2. Contract Stability

  • Template version pinning - ServicePlan instances pin their template version
  • Immutable contracts - Running ServicePlans maintain their original behavior
  • Template refinement - New template versions can be created without affecting existing contracts

3. Behavior Consistency

  • Template-driven behavior - All ServicePlans from same template use same logic
  • Instance-specific data - Each ServicePlan uses its own data (plan_params, plan_state, agent_params, agent_state)
  • Clear separation - Template defines behavior, instance provides data

4. Schema Stability

  • JSON fields remain unchanged - @domainSpecific directive maintains flexibility
  • Backward compatibility - Existing ServicePlan structure preserved
  • Minimal breaking changes - Only added template reference fields

Usage Examples

Creating a Template

mutation {
  createServicePlanTemplate(input: {
    name: "BSS Premium Plan"
    description: "Premium battery swap service plan"
    country_code: "KE"
    legal_jurisdiction: "Nairobi County, Kenya"
    contract_terms_id: "terms-kenya-premium"
    service_cycle_fsm_id: "fsm-bss-service-cycle-v1"
    payment_cycle_fsm_id: "fsm-bss-payment-cycle-v1"
    agent_config_id: "bss-agent-config-001"
    created_by: "admin"
  }) {
    id
    name
    version
  }
}

Creating ServicePlan from Template

mutation {
  createServicePlanFromTemplate(input: {
    template_id: "template-123"
    customer_id: "customer-456"
  }) {
    cloned_plan {
      id
      template_id
      template_version
    }
  }
}

Next Steps

  1. Implement data persistence - Connect template service to actual database
  2. Add template validation - Validate template refinements against existing contracts
  3. Enhance agent logic - Add more sophisticated computation using ServicePlan data
  4. Add template versioning - Implement semantic versioning for templates
  5. Add template migration - Tools to migrate ServicePlans between template versions

Conclusion

The template-driven architecture successfully simplifies the agent logic implementation while maintaining the flexibility to evolve behavior through template refinement. The key insight is that templates provide the structural restrictions needed to prevent type explosion, eliminating the need for complex handler systems.