Skip to main content

Module System Reference

The Kuetix Engine module system provides an extensible plugin architecture for custom functionality. This guide covers creating, registering, and using modules.

Overview

Modules extend the engine with custom transitions and services. They are organized under the modules/ directory and automatically registered via the dependency injection system.

Module Structure

Directory Layout

modules/
├── di.go # Generated DI configuration
├── meta.go # Generated function metadata
├── modules.go # Module registration
└── my_module/ # Your custom module
└── transitions/ # Transition handlers
└── my_transitions.go

Service Modules

modules/
└── services/
└── common/
└── transitions/
└── response.go # Common response handlers

Creating a Module

Step 1: Create Directory Structure

mkdir -p modules/my_module/transitions

Step 2: Implement Transition Handler

Create modules/my_module/transitions/my_transitions.go:

package transitions

import (
"github.com/kuetix/engine/engine/domain"
"github.com/kuetix/engine/engine/domain/interfaces"
"github.com/kuetix/engine/engine/workflow"
)

// MyTransitions handles custom transitions
type MyTransitions struct {
workflow.BaseServiceTransition
}

// NewMyTransitions creates a new instance
func NewMyTransitions() interfaces.ServiceTransitions {
return &MyTransitions{}
}

// DoSomething performs a custom action
// Can be called in WSL as: action my_module/my_transitions.DoSomething(...)
func (t *MyTransitions) DoSomething(p *workflow.WorkerSessionContext, input string) domain.FlowStepResult {
// Access session context
t.SetSession(p)

// Get property from workflow context
userId := t.Property("userId")

// Process
result := process(input, userId)

// Set response
t.SetResponse(map[string]interface{}{
"result": result,
}, 200)

return domain.FlowStepResult{
Success: true,
StatusCode: 200,
}
}

func process(input string, userId interface{}) string {
return "processed: " + input
}

Step 3: Regenerate Module Cache

After creating or modifying modules, regenerate the module cache:

# Using kue update
kue update --verbose

This generates:

  • modules/meta.go - Function metadata cache
  • modules/di.go - Dependency injection configuration
  • modules.json - Module metadata

Step 4: Use in WSL

module my_app

import my_module

workflow example {
start: Process

state Process {
action my_module/my_transitions.DoSomething(input: "hello") as Result
on success -> Finish(Result)
}

state Finish(Result) {
action services/common/response.ResponseValue(data: $Result)
end ok
}
}

BaseServiceTransition

The BaseServiceTransition struct provides common functionality for transition handlers.

Structure

type BaseServiceTransition struct {
Ctx *WorkerSessionContext
}

Methods

MethodDescription
SetSession(ctx)Set the worker session context
GetSession()Get the current session context
GetContext()Get the workflow context map
S()Shorthand for refresh + return context
SetResponse(response, statusCode...)Set worker response
SetStatusCode(code)Set HTTP status code
SetValue(name, value)Store value in context
GetValue(name)Retrieve value from context
Property(key)Get property from context
SetError(issue, statusCode)Set single error
SetErrors(issues, statusCode)Set multiple errors
HandleError(err, statusCode)Process and set error

Example Usage

func (t *MyTransitions) MyMethod(p *workflow.WorkerSessionContext) domain.FlowStepResult {
// Set session context
t.SetSession(p)

// Access the context shorthand
ctx := t.S()

// Get properties
value := t.Property("myKey")
userId := t.Property("userId")

// Store values
t.SetValue("processed", true)

// Set response
t.SetResponse(map[string]interface{}{
"value": value,
}, 200)

return domain.FlowStepResult{
Success: true,
StatusCode: 200,
}
}

FlowStepResult

Transition methods return FlowStepResult:

type FlowStepResult struct {
Success bool // Execution success
Next string // Override next state (optional)
Error error // Error if any
Response interface{} // Response data
StatusCode int // HTTP status code
}

Success Response

return domain.FlowStepResult{
Success: true,
Response: map[string]interface{}{"data": result},
StatusCode: 200,
}

Error Response

return domain.FlowStepResult{
Success: false,
Error: errors.New("something went wrong"),
StatusCode: 500,
}

Override Next State

return domain.FlowStepResult{
Success: true,
Next: "SpecialState", // Go to SpecialState instead of normal transition
}

WorkerSessionContext

The session context provides access to workflow data:

Property Resolution

// Direct lookup
value := p.Value("key")

// String with default
name := p.String("name", "default")

// Integer with ok check
count, ok := p.Int("count", 0)

// Boolean
enabled := p.Bool("enabled")

Property Syntax

SyntaxDescriptionExample
keyDirect property lookupusername
key.nestedNested property accessuser.profile.name
key|modifierProperty with modifiercount|int
key??defaultDefault valuename??"Unknown"

Setting Values

// Set a value
p.SetValue("key", value)

// Set context
p.SetContext("key", value)

// Return value for capture
p.Return(result)

Dependency Injection

How It Works

The kueinit tool generates DI configuration in modules/di.go:

di.DependencyInjection["my_module"] = func(name string) {
di.ToResolve(defines.TransitionPrefix+"my_module"+"/"+"my_transitions", func() interface{} {
return workflow.ServiceTransitionMapping{
ServiceName: name,
Name: "my_transitions",
Impl: NewMyTransitions(),
}
})
}

ServiceTransitionMapping

type ServiceTransitionMapping struct {
ServiceName string // Module path
Name string // Transition file name
Impl interfaces.ServiceTransitions // Implementation instance
}

Function Metadata

The module cache generation tool creates metadata in modules/meta.go:

boot.AddMetaFunctionCache(map[string]map[string]map[string]interfaces.FunctionMetadata{
"my_module": {
"my_transitions": {
"DoSomething": {
Name: "DoSomething",
NumIn: 2,
NumOut: 1,
ArgTypes: []string{"*workflow.WorkerSessionContext", "string"},
ReturnTypes: []string{"domain.FlowStepResult"},
ArgNames: []string{"p", "input"},
},
},
},
})

This metadata enables reflection-based method invocation.

Built-in Modules

services/common

Common response handlers:

action services/common/response.ResponseValue(message: "Hello", statusCode: 200)
action services/common/response.ResponseError(message: "Error", code: 500)

converse

Conversation handlers:

action converse/speak.Say(on: message, v: {response: "hello"})

Best Practices

1. Keep Methods Focused

Each transition method should do one thing well:

// Good
func (t *MyTransitions) ValidateEmail(p *workflow.WorkerSessionContext, email string) domain.FlowStepResult

// Avoid
func (t *MyTransitions) ValidateAndProcessAndSave(p *workflow.WorkerSessionContext, data interface{}) domain.FlowStepResult

2. Use Proper Error Handling

func (t *MyTransitions) ProcessData(p *workflow.WorkerSessionContext, data string) domain.FlowStepResult {
result, err := doSomething(data)
if err != nil {
t.HandleError(err, 500)
return domain.FlowStepResult{
Success: false,
Error: err,
StatusCode: 500,
}
}

t.SetResponse(result, 200)
return domain.FlowStepResult{
Success: true,
StatusCode: 200,
}
}

3. Document Your Methods

// ValidateUser validates user credentials and returns validation result
//
// WSL usage:
// action auth/validation.ValidateUser(email: $input.email, password: $input.password)
//
// Returns:
// - Success: true if valid, false otherwise
// - Response: { valid: bool, message: string }
func (t *AuthTransitions) ValidateUser(p *workflow.WorkerSessionContext, email string, password string) domain.FlowStepResult

4. Regenerate After Changes

Always run kue update after modifying modules:

kue update --verbose