Skip to content

Add first-class Agent definitions to YAML schema #250

@treblereel

Description

@treblereel

Context

Currently, AI Agents are defined inline within Worker definitions:

spec:
  workers:
    - name: sentiment-analyzer
      capabilities:
        - analyzeSentiment
      agent:
        systemPrompt: "You are a sentiment analyzer..."
        inputSchema: "{ text: .text }"
        outputSchema: "{ sentiment: .sentiment }"
        model:
          openai:
            apiKey: "${OPENAI_API_KEY}"
            modelName: "gpt-4"

Current implementation:

  • Agent is a property of Worker (PR Add AI Agent support as Worker execution type #244)
  • No reusability: same agent config must be duplicated across workers
  • Agents are not first-class participants in the Case model
  • Location: schema/src/main/resources/schema/CaseDefinition.yaml

Problem

1. No Reusability
If multiple workers need the same LLM configuration, the entire agent block must be duplicated:

workers:
  - name: sentiment-worker
    agent:
      systemPrompt: "Analyze sentiment"
      model:
        openai:
          apiKey: "${OPENAI_API_KEY}"
          modelName: "gpt-4"
          temperature: 0.3
  
  - name: classification-worker
    agent:
      systemPrompt: "Classify documents"  # Different prompt
      model:
        openai:
          apiKey: "${OPENAI_API_KEY}"      # Duplicated config
          modelName: "gpt-4"                # Duplicated config
          temperature: 0.3                  # Duplicated config

2. Agents are not first-class participants
According to docs/Case Hub, design.md:

A Worker is an autonomous participant in a Case that can perform work or modify the CaseContext. A Worker may be a person, a service, a workflow, a rule, or an autonomous agent.

Currently, agents can only exist as part of a worker. There's no way to define an autonomous agent as a standalone participant.

3. No agent registry/catalog

  • No way to list available agents
  • No way to reference agents by name
  • No agent lifecycle management (deploy, undeploy, version)

Proposed Solution

Option 1: Reusable Agent Definitions (Minimal)

Define agents in use.agents and reference them by name:

use:
  agents:
    - name: gpt4-analyzer
      model:
        openai:
          apiKey: "${OPENAI_API_KEY}"
          modelName: "gpt-4"
          temperature: 0.3
    
    - name: local-llama
      model:
        ollama:
          baseUrl: "http://localhost:11434"
          modelName: "llama2"

spec:
  workers:
    - name: sentiment-worker
      capabilities:
        - analyzeSentiment
      agent:
        use: gpt4-analyzer  # Reference by name
        systemPrompt: "Analyze sentiment"
        inputSchema: "{ text: .text }"
        outputSchema: "{ sentiment: .sentiment }"
    
    - name: classification-worker
      capabilities:
        - classify
      agent:
        use: gpt4-analyzer  # Reuse same model config
        systemPrompt: "Classify documents"
        inputSchema: "{ content: .document }"
        outputSchema: "{ category: .category }"

Schema changes:

CaseDefinitionUse:
  properties:
    agents:
      type: array
      items:
        $ref: "#/$defs/AgentDefinition"

AgentDefinition:
  type: object
  required: [name, model]
  properties:
    name: { type: string }
    model: { $ref: "#/$defs/AgentModel" }

Agent:
  properties:
    use: { type: string }  # Reference to AgentDefinition.name
    systemPrompt: { type: string }
    inputSchema: { type: string }
    outputSchema: { type: string }
    model:  # Optional: can override model from 'use'
      $ref: "#/$defs/AgentModel"

Option 2: First-class Agents (Full)

Agents as top-level participants alongside Workers:

spec:
  agents:
    - name: sentiment-analyzer
      capabilities:
        - analyzeSentiment
      systemPrompt: "Analyze sentiment"
      inputSchema: "{ text: .text }"
      outputSchema: "{ sentiment: .sentiment }"
      model:
        openai:
          apiKey: "${OPENAI_API_KEY}"
          modelName: "gpt-4"
    
    - name: document-classifier
      capabilities:
        - classify
      systemPrompt: "Classify documents"
      inputSchema: "{ content: .document }"
      outputSchema: "{ category: .category }"
      model:
        use: gpt4-analyzer  # Can still reference shared config

  workers:
    - name: http-service
      capabilities:
        - fetchData
      # Traditional service worker

Schema changes:

CaseDefinitionSpec:
  properties:
    capabilities: ...
    workers: ...
    agents:  # New top-level section
      type: array
      items:
        $ref: "#/$defs/AgentWorker"

AgentWorker:
  type: object
  required: [name, capabilities, systemPrompt, inputSchema, outputSchema, model]
  properties:
    name: { type: string }
    description: { type: string }
    capabilities:
      type: array
      items: { type: string }
    systemPrompt: { type: string }
    inputSchema: { type: string }
    outputSchema: { type: string }
    userMessageTemplate: { type: string }
    model:
      oneOf:
        - type: string  # Reference to use.agents[].name
        - $ref: "#/$defs/AgentModel"  # Inline definition
    executionPolicy:
      $ref: "#/$defs/ExecutionPolicy"

Option 3: Hybrid (Recommended)

Combine both approaches:

  • use.agents for reusable model configurations
  • spec.agents for autonomous agent participants
  • spec.workers[].agent for inline agent configs (backward compatible)
use:
  agents:
    - name: gpt4-turbo
      model:
        openai:
          apiKey: "${OPENAI_API_KEY}"
          modelName: "gpt-4-turbo"
          temperature: 0.3

spec:
  # Autonomous agents (first-class participants)
  agents:
    - name: sentiment-analyzer
      capabilities: [analyzeSentiment]
      model:
        use: gpt4-turbo
      systemPrompt: "Analyze sentiment"
      inputSchema: "{ text: .text }"
      outputSchema: "{ sentiment: .sentiment }"
  
  # Service workers with embedded agents
  workers:
    - name: data-processor
      capabilities: [process]
      agent:
        use: gpt4-turbo
        systemPrompt: "Process data"
        inputSchema: "{ data: .input }"
        outputSchema: "{ result: .output }"

Implementation Plan

Phase 1: Reusable Agent Definitions

  • Add use.agents[] to schema
  • Update Agent to support use field (reference by name)
  • Implement agent reference resolution in YAML mapper
  • Validation: referenced agent must exist
  • Tests for agent reusability

Phase 2: First-class Agents

  • Add spec.agents[] to schema
  • Create AgentWorker type
  • Update case execution to treat agents as participants
  • Agent lifecycle: provision, execute, terminate
  • Tests for autonomous agents

Phase 3: Advanced Features

  • Agent versioning (name + version)
  • Agent catalog/registry
  • Hot-reload agent configurations
  • Agent metrics and observability

Acceptance Criteria

Phase 1 (Minimal):

  • Agents can be defined in use.agents
  • Workers can reference agents via agent.use: "name"
  • Model configuration is reusable across workers
  • Backward compatible: inline agent.model still works
  • Validation: referenced agent must exist in use.agents
  • Tests cover reference resolution
  • Documentation with examples

Phase 2 (Full):

  • spec.agents[] supported in schema
  • Agents are autonomous participants (not tied to workers)
  • Agent execution integrated with case lifecycle
  • Yellow Pages updated for agent capabilities
  • Tests for autonomous agent execution
  • Documentation for agent vs worker decision

Design Considerations

1. Agent vs Worker

When to use spec.agents vs spec.workers[].agent?

Use Case Pattern
Pure AI participant (no custom code) spec.agents
Service with AI augmentation spec.workers[].agent
Reusable model config use.agents

2. Reference Resolution

// In CaseDefinitionYamlMapper:
if (agent.getUse() != null) {
  AgentDefinition def = caseDefinition.getUse()
    .getAgents().stream()
    .filter(a -> a.getName().equals(agent.getUse()))
    .findFirst()
    .orElseThrow(() -> new IllegalArgumentException(
      "Agent '" + agent.getUse() + "' not found in use.agents"));
  
  // Merge: explicit model overrides referenced model
  if (agent.getModel() == null) {
    agent.setModel(def.getModel());
  }
}

3. Backward Compatibility

All existing YAML files must continue to work:

  • Inline agent.model configurations remain valid
  • New use and reference patterns are optional
  • Migration path: extract common models to use.agents

Related

Open Questions

  1. Naming: use.agents or use.models? (agents vs LLM configurations)
  2. Versioning: Should agent definitions include version field?
  3. Scoping: Can agents be defined at namespace level and shared across cases?
  4. Lifecycle: Should autonomous agents have explicit start/stop/pause states?

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions