Files
personamanagmentlayer__pcl/docs/api/EVENTS.md
ANGX fb8288d2e8 feat: implement Phase 1.0D workflow validation tests
Created comprehensive test suite for workflow validation features:

- 10/10 tests passing for implemented workflow validations
- Tests for unreachable code detection in conditionals
- Tests for parallel branch count limits
- Tests for complex workflow structures

Key additions:
- Fixed ParenthesizedExpression handling in evaluateConstantCondition()
- Created workflow-validation-core.test.mjs with 10 passing tests
- Tests verify rich error messages include suggestions

Test coverage:
✓ Unreachable else branch detection (condition always true)
✓ Unreachable then branch detection (condition always false)
✓ Dynamic conditions allowed without warnings
✓ Boolean expression constant evaluation
✓ Negation operator constant evaluation
✓ Parallel branch count warnings (>10 branches)
✓ No warnings for reasonable parallel branches (≤10)
✓ Nested grouped workflow validation
✓ Sequential then parallel workflow validation
✓ Choice workflow validation

Note: Loop validation tests deferred (awaiting parser implementation)

All workflow validation tests pass with proper error messages and suggestions.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-17 05:35:38 +01:00

13 KiB

Events API Reference

Status: Proposed (Phase 2 Implementation) Last Updated: 2026-01-17

Complete reference for PCL Runtime event system.


Table of Contents

  1. Overview
  2. Event Emitter API
  3. Persona Events
  4. Workflow Events
  5. LLM Events
  6. Event Data Types

Overview

The PCL Runtime extends Node.js EventEmitter to provide lifecycle hooks for observability, logging, analytics, and integration.

import { PCLRuntime } from '@pcl/sdk';

const runtime = new PCLRuntime();

// Subscribe to events
runtime.on('persona:before', (persona, query) => {
  console.log(`Executing: ${persona.id}`);
});

// Execute personas, workflows, etc.
await runtime.execute('ARCHI', 'Design system');

Event Emitter API

on(event, listener)

Subscribes to an event. Listener is called every time the event is emitted.

runtime.on(event: string, listener: (...args: any[]) => void): this

Example:

runtime.on('persona:before', (persona, query) => {
  console.log(`Starting ${persona.id}`);
});

Returns: The runtime instance (for chaining).


once(event, listener)

Subscribes to an event, but automatically unsubscribes after first emission.

runtime.once(event: string, listener: (...args: any[]) => void): this

Example:

runtime.once('workflow:complete', (workflow, result) => {
  console.log('First workflow completed!');
});

Returns: The runtime instance (for chaining).


off(event, listener)

Unsubscribes a listener from an event.

runtime.off(event: string, listener: (...args: any[]) => void): this

Example:

const listener = (persona) => console.log(persona.id);
runtime.on('persona:before', listener);

// Later...
runtime.off('persona:before', listener);

Returns: The runtime instance (for chaining).


removeAllListeners(event?)

Removes all listeners for a specific event, or all listeners if no event specified.

runtime.removeAllListeners(event?: string): this

Example:

// Remove all 'persona:before' listeners
runtime.removeAllListeners('persona:before');

// Remove ALL listeners
runtime.removeAllListeners();

Returns: The runtime instance (for chaining).


emit(event, ...args)

Emits an event (typically called internally by the runtime).

runtime.emit(event: string, ...args: any[]): boolean

Returns: true if event had listeners, false otherwise.


listenerCount(event)

Returns the number of listeners for a specific event.

runtime.listenerCount(event: string): number

Example:

const count = runtime.listenerCount('persona:before');
console.log(`${count} listeners registered`);

listeners(event)

Returns an array of listeners for a specific event.

runtime.listeners(event: string): Function[]

Example:

const listeners = runtime.listeners('persona:before');
console.log(`${listeners.length} listeners`);

Persona Events

persona:before

Emitted before a persona begins execution.

runtime.on('persona:before', (persona: Persona, query: string) => void)

Parameters:

  • persona: Persona - The persona being executed
  • query: string - The user query/prompt

Example:

runtime.on('persona:before', (persona, query) => {
  console.log(`▶️ ${persona.id}: ${query}`);
  console.log(`Skills: ${persona.skills.map((s) => s.id).join(', ')}`);
});

Use Cases:

  • Logging execution start
  • Metrics collection
  • Request validation
  • Rate limiting

persona:after

Emitted after a persona completes execution successfully.

runtime.on('persona:after', (persona: Persona, result: PersonaResult) => void)

Parameters:

  • persona: Persona - The executed persona
  • result: PersonaResult - Execution results

Result Type:

interface PersonaResult {
  query: string; // Original query
  response: string; // Persona's response
  duration: number; // Execution time (ms)
  tokens: {
    input: number;
    output: number;
    total: number;
  };
  provider: string; // LLM provider used
  model: string; // Model name
  metadata: {
    timestamp: Date;
    cost?: number;
  };
}

Example:

runtime.on('persona:after', (persona, result) => {
  console.log(`✅ ${persona.id} completed in ${result.duration}ms`);
  console.log(`Tokens: ${result.tokens.total}`);
  console.log(`Response: ${result.response.substring(0, 100)}...`);
});

Use Cases:

  • Logging results
  • Cost tracking
  • Database persistence
  • Analytics

persona:error

Emitted when a persona execution fails.

runtime.on('persona:error', (persona: Persona, error: Error) => void)

Parameters:

  • persona: Persona - The persona that failed
  • error: Error - The error that occurred

Example:

runtime.on('persona:error', (persona, error) => {
  console.error(`❌ ${persona.id} failed: ${error.message}`);
  console.error(error.stack);

  // Send to error tracking
  Sentry.captureException(error, {
    tags: { personaId: persona.id },
  });
});

Use Cases:

  • Error logging
  • Error tracking (Sentry, Rollbar)
  • Retry logic
  • Fallback strategies

Workflow Events

workflow:start

Emitted when a workflow begins execution.

runtime.on('workflow:start', (workflow: Workflow) => void)

Parameters:

  • workflow: Workflow - The workflow starting

Workflow Type:

interface Workflow {
  id: string;
  name: string;
  steps: WorkflowStep[];
  metadata: {
    startTime: Date;
    estimatedDuration?: number;
  };
}

Example:

runtime.on('workflow:start', (workflow) => {
  console.log(`🚀 Starting workflow: ${workflow.name}`);
  console.log(`Steps: ${workflow.steps.length}`);
  progressBar.start(workflow.steps.length, 0);
});

workflow:step

Emitted after each workflow step completes.

runtime.on('workflow:step', (step: WorkflowStep, result: any) => void)

Parameters:

  • step: WorkflowStep - The completed step
  • result: any - Step execution result

Step Type:

interface WorkflowStep {
  id: string;
  personaId: string;
  query: string;
  dependencies: string[];
  metadata: {
    stepNumber: number;
    totalSteps: number;
  };
}

Example:

runtime.on('workflow:step', (step, result) => {
  const progress = (step.metadata.stepNumber / step.metadata.totalSteps) * 100;
  console.log(
    `📍 Step ${step.metadata.stepNumber}/${step.metadata.totalSteps} (${progress.toFixed(0)}%)`
  );
  progressBar.increment();
});

workflow:complete

Emitted when a workflow completes successfully.

runtime.on('workflow:complete', (workflow: Workflow, result: WorkflowResult) => void)

Parameters:

  • workflow: Workflow - The completed workflow
  • result: WorkflowResult - Final results

Result Type:

interface WorkflowResult {
  workflowId: string;
  steps: StepResult[];
  totalDuration: number;
  totalTokens: number;
  totalCost: number;
  success: boolean;
  metadata: {
    startTime: Date;
    endTime: Date;
  };
}

Example:

runtime.on('workflow:complete', (workflow, result) => {
  console.log(`🎉 Workflow complete: ${workflow.name}`);
  console.log(`Duration: ${result.totalDuration}ms`);
  console.log(`Total cost: $${result.totalCost.toFixed(4)}`);
  progressBar.stop();
});

workflow:error

Emitted when a workflow fails.

runtime.on('workflow:error', (workflow: Workflow, error: Error) => void)

Parameters:

  • workflow: Workflow - The failed workflow
  • error: Error - The error that occurred

Example:

runtime.on('workflow:error', (workflow, error) => {
  console.error(`❌ Workflow failed: ${workflow.name}`);
  console.error(`Error: ${error.message}`);
  progressBar.stop();

  // Notify team
  slack.send({
    channel: '#alerts',
    text: `Workflow ${workflow.name} failed: ${error.message}`,
  });
});

LLM Events

llm:call

Emitted before making an LLM API call.

runtime.on('llm:call', (provider: string, prompt: string) => void)

Parameters:

  • provider: string - LLM provider name ('anthropic', 'openai', etc.)
  • prompt: string - The prompt being sent

Example:

runtime.on('llm:call', (provider, prompt) => {
  console.log(`📡 Calling ${provider}`);
  console.log(`Prompt length: ${prompt.length} chars`);
});

llm:response

Emitted after receiving an LLM response.

runtime.on('llm:response', (provider: string, response: LLMResponse) => void)

Parameters:

  • provider: string - LLM provider name
  • response: LLMResponse - The response data

Response Type:

interface LLMResponse {
  content: string;
  model: string;
  usage: {
    input_tokens: number;
    output_tokens: number;
    total_tokens: number;
  };
  metadata: {
    latency: number;
    cost?: number;
  };
}

Example:

runtime.on('llm:response', (provider, response) => {
  console.log(`✅ ${provider} responded in ${response.metadata.latency}ms`);
  console.log(`Tokens: ${response.usage.total_tokens}`);

  // Track cost
  const cost = calculateCost(provider, response.usage);
  totalCost += cost;
});

llm:error

Emitted when an LLM call fails.

runtime.on('llm:error', (provider: string, error: Error) => void)

Parameters:

  • provider: string - LLM provider name
  • error: Error - The error that occurred

Example:

runtime.on('llm:error', (provider, error) => {
  console.error(`❌ ${provider} call failed: ${error.message}`);

  if (error.message.includes('rate_limit')) {
    console.log('⏳ Rate limited, retrying in 60s...');
  }
});

Event Data Types

Complete Type Definitions

// Core types
interface Persona {
  id: string;
  name: string;
  intent: string;
  skills: Skill[];
  extends?: string[];
  implements?: string[];
  metadata: Record<string, any>;
}

interface Skill {
  id: string;
  name: string;
  type: 'foundation' | 'technical' | 'specialized';
  description: string;
}

interface PersonaResult {
  query: string;
  response: string;
  duration: number;
  tokens: {
    input: number;
    output: number;
    total: number;
  };
  provider: string;
  model: string;
  metadata: {
    timestamp: Date;
    cost?: number;
  };
}

interface Workflow {
  id: string;
  name: string;
  steps: WorkflowStep[];
  metadata: {
    startTime: Date;
    estimatedDuration?: number;
  };
}

interface WorkflowStep {
  id: string;
  personaId: string;
  query: string;
  dependencies: string[];
  metadata: {
    stepNumber: number;
    totalSteps: number;
  };
}

interface WorkflowResult {
  workflowId: string;
  steps: StepResult[];
  totalDuration: number;
  totalTokens: number;
  totalCost: number;
  success: boolean;
  metadata: {
    startTime: Date;
    endTime: Date;
  };
}

interface LLMResponse {
  content: string;
  model: string;
  usage: {
    input_tokens: number;
    output_tokens: number;
    total_tokens: number;
  };
  metadata: {
    latency: number;
    cost?: number;
  };
}

// Event listener types
type PersonaBeforeListener = (persona: Persona, query: string) => void;
type PersonaAfterListener = (persona: Persona, result: PersonaResult) => void;
type PersonaErrorListener = (persona: Persona, error: Error) => void;

type WorkflowStartListener = (workflow: Workflow) => void;
type WorkflowStepListener = (step: WorkflowStep, result: any) => void;
type WorkflowCompleteListener = (
  workflow: Workflow,
  result: WorkflowResult
) => void;
type WorkflowErrorListener = (workflow: Workflow, error: Error) => void;

type LLMCallListener = (provider: string, prompt: string) => void;
type LLMResponseListener = (provider: string, response: LLMResponse) => void;
type LLMErrorListener = (provider: string, error: Error) => void;

// Complete event map
interface RuntimeEvents {
  'persona:before': PersonaBeforeListener;
  'persona:after': PersonaAfterListener;
  'persona:error': PersonaErrorListener;

  'workflow:start': WorkflowStartListener;
  'workflow:step': WorkflowStepListener;
  'workflow:complete': WorkflowCompleteListener;
  'workflow:error': WorkflowErrorListener;

  'llm:call': LLMCallListener;
  'llm:response': LLMResponseListener;
  'llm:error': LLMErrorListener;
}

See Also


Questions? Open an issue on GitHub