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