Building a Cogniscript App

This tutorial walks you through creating, deploying, and running a complete Cogniscript application in Maitento. We will build a Unit Converter app that accepts numeric values and converts between different measurement units.

What You Will Learn

  • How to structure a Cogniscript app with inputs and outputs
  • Using built-in functions for calculations and data manipulation
  • Deploying your app to Maitento
  • Running the app via the Maitento Shell
  • Executing the app programmatically using the SDK

Prerequisites

Before starting, ensure you have:

  • Access to a Maitento tenant with app creation permissions
  • The Maitento SDK installed (for programmatic execution)
  • Familiarity with basic Cogniscript syntax (see Cogniscript Language Guide)

Part 1: Understanding App Structure

Every Cogniscript app follows a consistent structure:

  1. Inputs - Parameters passed to the app when started
  2. Logic - Cogniscript code that processes the inputs
  3. Outputs - Results produced by the app for consumers

Reading Inputs

Apps receive inputs through the appState.getInput() syscall:

// Read a string input
string conversionType = appState.getInput("conversionType");

// Read a numeric input (returned as string, convert as needed)
string valueStr = appState.getInput("value");
decimal value = StringToDecimal(valueStr);

Setting Outputs

Apps produce outputs using the appState.setOutput*() syscalls:

// Set outputs of different types
appState.setOutputDecimal("result", 42.5);
appState.setOutputString("unit", "meters");
appState.setOutputBool("success", true);

Part 2: Building the Unit Converter App

Let’s build a practical Unit Converter that supports temperature, length, and weight conversions.

Complete Cogniscript Code

// Unit Converter App
// Converts values between different measurement units

// Temperature conversion functions
decimal celsiusToFahrenheit(decimal celsius) {
    return((celsius * 9 / 5) + 32);
}

decimal fahrenheitToCelsius(decimal fahrenheit) {
    return((fahrenheit - 32) * 5 / 9);
}

decimal celsiusToKelvin(decimal celsius) {
    return(celsius + 273.15);
}

decimal kelvinToCelsius(decimal kelvin) {
    return(kelvin - 273.15);
}

// Length conversion functions
decimal metersToFeet(decimal meters) {
    return(meters * 3.28084);
}

decimal feetToMeters(decimal feet) {
    return(feet / 3.28084);
}

decimal kilometersToMiles(decimal km) {
    return(km * 0.621371);
}

decimal milesToKilometers(decimal miles) {
    return(miles / 0.621371);
}

// Weight conversion functions
decimal kilogramsToPounds(decimal kg) {
    return(kg * 2.20462);
}

decimal poundsToKilograms(decimal lbs) {
    return(lbs / 2.20462);
}

decimal gramsToOunces(decimal grams) {
    return(grams * 0.035274);
}

decimal ouncesToGrams(decimal ounces) {
    return(ounces / 0.035274);
}

// Get conversion result based on type
decimal performConversion(string conversionType, decimal value) {
    // Temperature conversions
    if (conversionType == "celsius_to_fahrenheit") {
        return(celsiusToFahrenheit(value));
    } else if (conversionType == "fahrenheit_to_celsius") {
        return(fahrenheitToCelsius(value));
    } else if (conversionType == "celsius_to_kelvin") {
        return(celsiusToKelvin(value));
    } else if (conversionType == "kelvin_to_celsius") {
        return(kelvinToCelsius(value));
    }
    // Length conversions
    else if (conversionType == "meters_to_feet") {
        return(metersToFeet(value));
    } else if (conversionType == "feet_to_meters") {
        return(feetToMeters(value));
    } else if (conversionType == "km_to_miles") {
        return(kilometersToMiles(value));
    } else if (conversionType == "miles_to_km") {
        return(milesToKilometers(value));
    }
    // Weight conversions
    else if (conversionType == "kg_to_lbs") {
        return(kilogramsToPounds(value));
    } else if (conversionType == "lbs_to_kg") {
        return(poundsToKilograms(value));
    } else if (conversionType == "grams_to_oz") {
        return(gramsToOunces(value));
    } else if (conversionType == "oz_to_grams") {
        return(ouncesToGrams(value));
    }

    // Unknown conversion type
    throw("Unknown conversion type: " + conversionType);
}

// Get the output unit label
string getOutputUnit(string conversionType) {
    if (conversionType == "celsius_to_fahrenheit") {
        return("Fahrenheit");
    } else if (conversionType == "fahrenheit_to_celsius") {
        return("Celsius");
    } else if (conversionType == "celsius_to_kelvin") {
        return("Kelvin");
    } else if (conversionType == "kelvin_to_celsius") {
        return("Celsius");
    } else if (conversionType == "meters_to_feet") {
        return("feet");
    } else if (conversionType == "feet_to_meters") {
        return("meters");
    } else if (conversionType == "km_to_miles") {
        return("miles");
    } else if (conversionType == "miles_to_km") {
        return("kilometers");
    } else if (conversionType == "kg_to_lbs") {
        return("pounds");
    } else if (conversionType == "lbs_to_kg") {
        return("kilograms");
    } else if (conversionType == "grams_to_oz") {
        return("ounces");
    } else if (conversionType == "oz_to_grams") {
        return("grams");
    }
    return("unknown");
}

// Main entry point
void main() {
    PrintLine("=== Unit Converter App ===");
    PrintLine("");

    // Read inputs from the app state
    string conversionType = appState.getInput("conversionType");
    string valueStr = appState.getInput("value");

    // Validate inputs
    if (StringIsEmpty(conversionType)) {
        throw("Missing required input: conversionType");
    }

    if (StringIsEmpty(valueStr)) {
        throw("Missing required input: value");
    }

    // Convert value string to decimal
    decimal inputValue = StringToDecimal(valueStr);

    PrintLine("Input: " + valueStr);
    PrintLine("Conversion: " + conversionType);
    PrintLine("");

    // Perform the conversion
    try {
        decimal result = performConversion(conversionType, inputValue);
        string outputUnit = getOutputUnit(conversionType);

        // Round to 4 decimal places for cleaner output
        result = MathRound(result, 4);

        // Set the outputs
        appState.setOutputDecimal("result", result);
        appState.setOutputString("unit", outputUnit);
        appState.setOutputBool("success", true);

        // Print result to console
        PrintLine("Result: " + DecimalToString(result, "F4") + " " + outputUnit);
        PrintLine("");
        PrintLine("Conversion completed successfully!");

    } catch {
        string error = GetLastError();
        PrintLine("Error: " + error);

        appState.setOutputDecimal("result", 0);
        appState.setOutputString("unit", "error");
        appState.setOutputBool("success", false);
        appState.setOutputString("errorMessage", error);
    }
}

Code Walkthrough

1. Conversion Functions

The app defines dedicated functions for each conversion type. This keeps the code organized and testable:

decimal celsiusToFahrenheit(decimal celsius) {
    return((celsius * 9 / 5) + 32);
}

2. Input Validation

Always validate inputs before processing:

if (StringIsEmpty(conversionType)) {
    throw("Missing required input: conversionType");
}

3. Error Handling

Use try/catch to handle errors gracefully and still produce meaningful outputs:

try {
    decimal result = performConversion(conversionType, inputValue);
    // ... success handling
} catch {
    string error = GetLastError();
    appState.setOutputBool("success", false);
    appState.setOutputString("errorMessage", error);
}

4. Multiple Output Types

The app demonstrates setting different output types:

appState.setOutputDecimal("result", result);      // Numeric result
appState.setOutputString("unit", outputUnit);     // Unit label
appState.setOutputBool("success", true);          // Success flag

Part 3: Deploying the App

Step 1: Create the App Resource

First, create the app in your namespace using the Shell:

# Create a new app in the 'production' namespace
app-create unit-converter --namespace production

Step 2: Upload the Cogniscript Code

Save the Cogniscript code to a file (e.g., unit-converter.cog) in your Maitento project directory, then build and deploy:

# Build the app (compiles and creates a new version)
app-build unit-converter --namespace production --source ./unit-converter.cog

The build process:

  1. Compiles the Cogniscript source into bytecode
  2. Extracts input definitions from appState.getInput() calls
  3. Creates a new immutable version
  4. Stores the compiled binary

Step 3: Verify the Deployment

# Get app details to confirm deployment
app-get unit-converter --namespace production

This shows version information, including detected inputs and creation date.


Part 4: Running the App via Shell

Basic Execution (Fire and Forget)

Start the app without waiting for completion:

app-start unit-converter --namespace production \
    --input-conversionType "celsius_to_fahrenheit" \
    --input-value "100"

This returns immediately with a process ID.

Synchronous Execution (Wait for Result)

Use app-run to wait for completion and see outputs:

app-run unit-converter --namespace production \
    --input-conversionType "celsius_to_fahrenheit" \
    --input-value "100"

Expected Output:

=== Unit Converter App ===

Input: 100
Conversion: celsius_to_fahrenheit

Result: 212.0000 Fahrenheit

Conversion completed successfully!

Process finished successfully.
Outputs:
  result: 212
  unit: Fahrenheit
  success: true

More Example Conversions

# Temperature: Fahrenheit to Celsius
app-run unit-converter --namespace production \
    --input-conversionType "fahrenheit_to_celsius" \
    --input-value "32"

# Length: Kilometers to Miles
app-run unit-converter --namespace production \
    --input-conversionType "km_to_miles" \
    --input-value "10"

# Weight: Kilograms to Pounds
app-run unit-converter --namespace production \
    --input-conversionType "kg_to_lbs" \
    --input-value "75"

Monitoring Running Processes

# List active processes in the namespace
app-ps --namespace production

# Attach to a specific process to see real-time output
app-attach <process-id>

Part 5: Running the App via SDK (C#)

Setup

First, install the Maitento SDK and create a client:

using Maitento.Sdk;
using Maitento.Entities.Apps;
using Maitento.Entities.Database;

// Create the client with your API key
IMaitentoClient client = MaitentoClient.Create("your-api-key");

Starting and Monitoring the App

// Define inputs
NameValuePair[] inputs = new[]
{
    new NameValuePair("conversionType", "celsius_to_fahrenheit"),
    new NameValuePair("value", "100")
};

// Start the app process
AppProcess process = await client.Apps.StartAsync(
    "production",           // namespace
    "unit-converter",       // app name
    inputs
);

Console.WriteLine($"Started process: {process.Id}");
Console.WriteLine($"Status: {process.Status}");

// Poll for completion
while (process.Status == AppProcessStatus.Running ||
       process.Status == AppProcessStatus.New)
{
    await Task.Delay(500);
    process = await client.Apps.GetProcessAsync(process.Id) ?? process;
    Console.WriteLine($"Status: {process.Status}");
}

// Check final status
if (process.Status == AppProcessStatus.Finished)
{
    Console.WriteLine("\nProcess completed successfully!");
    Console.WriteLine($"Return Value: {process.ReturnValue}");

    // Access outputs
    foreach (AppOutput output in process.Outputs)
    {
        Console.WriteLine($"  {output.Name}: {output.Value} ({output.Type})");
    }
}
else if (process.Status == AppProcessStatus.Error)
{
    Console.WriteLine($"\nProcess failed: {process.FailureDetails}");
}

// Display console output
Console.WriteLine("\nConsole Output:");
foreach (AppConsoleOutput consoleOutput in process.ConsoleOutputs)
{
    Console.WriteLine(consoleOutput.Text);
}

Complete SDK Example with Error Handling

using Maitento.Sdk;
using Maitento.Sdk.Exceptions;
using Maitento.Entities.Apps;
using Maitento.Entities.Database;

public class UnitConverterExample
{
    private readonly IMaitentoClient _client;

    public UnitConverterExample(string apiKey)
    {
        _client = MaitentoClient.Create(apiKey);
    }

    public async Task<ConversionResult> ConvertAsync(
        string conversionType,
        decimal value)
    {
        try
        {
            // Prepare inputs
            NameValuePair[] inputs = new[]
            {
                new NameValuePair("conversionType", conversionType),
                new NameValuePair("value", value.ToString())
            };

            // Start the conversion
            AppProcess process = await _client.Apps.StartAsync(
                "production",
                "unit-converter",
                inputs
            );

            // Wait for completion (with timeout)
            int maxAttempts = 60;  // 30 seconds max
            int attempts = 0;

            while ((process.Status == AppProcessStatus.Running ||
                    process.Status == AppProcessStatus.New) &&
                   attempts < maxAttempts)
            {
                await Task.Delay(500);
                process = await _client.Apps.GetProcessAsync(process.Id)
                    ?? process;
                attempts++;
            }

            // Check result
            if (process.Status == AppProcessStatus.Finished)
            {
                var resultOutput = process.Outputs
                    .FirstOrDefault(o => o.Name == "result");
                var unitOutput = process.Outputs
                    .FirstOrDefault(o => o.Name == "unit");

                return new ConversionResult
                {
                    Success = true,
                    Result = decimal.Parse(resultOutput?.Value ?? "0"),
                    Unit = unitOutput?.Value ?? "unknown",
                    ProcessId = process.Id
                };
            }
            else
            {
                return new ConversionResult
                {
                    Success = false,
                    ErrorMessage = process.FailureDetails ?? "Unknown error",
                    ProcessId = process.Id
                };
            }
        }
        catch (MaitentoValidationException ex)
        {
            return new ConversionResult
            {
                Success = false,
                ErrorMessage = string.Join("; ",
                    ex.ValidationErrors.SelectMany(e => e.Value))
            };
        }
        catch (MaitentoApiException ex)
        {
            return new ConversionResult
            {
                Success = false,
                ErrorMessage = $"API error ({ex.StatusCode}): {ex.ResponseBody}"
            };
        }
    }
}

public class ConversionResult
{
    public bool Success { get; set; }
    public decimal Result { get; set; }
    public string? Unit { get; set; }
    public string? ErrorMessage { get; set; }
    public Guid? ProcessId { get; set; }
}

// Usage
var converter = new UnitConverterExample("your-api-key");

var result = await converter.ConvertAsync("celsius_to_fahrenheit", 100);

if (result.Success)
{
    Console.WriteLine($"100 Celsius = {result.Result} {result.Unit}");
}
else
{
    Console.WriteLine($"Conversion failed: {result.ErrorMessage}");
}

Part 6: Advanced Topics

Chaining Apps with app.start()

Cogniscript apps can start other apps for complex workflows:

void main() {
    // Get input to convert
    string valueStr = appState.getInput("value");

    // First conversion: Celsius to Fahrenheit
    jsonObject inputs1 = JsonObjectCreate();
    inputs1 = JsonObjectSetString(inputs1, "conversionType", "celsius_to_fahrenheit");
    inputs1 = JsonObjectSetString(inputs1, "value", valueStr);

    string processId1 = AppStart("unit-converter@production", inputs1);

    // Wait for the first conversion
    string process1 = AppProcessGet(processId1);
    // ... extract result and continue with next conversion
}

Storing Results in VFS

Write conversion logs to the Virtual File System:

void main() {
    // ... conversion logic ...

    // Log the conversion
    string timestamp = DateTimeToString(DateGetUtc(), "yyyy-MM-dd HH:mm:ss");
    string logEntry = timestamp + " | " + conversionType + " | " +
                      valueStr + " -> " + DecimalToString(result, "F4") + " " + outputUnit;

    VfsAppendText("logs", "production", "/conversions.log", logEntry + "\n");
}

Scheduling Regular Conversions

Use Automated Actions to run conversions on a schedule:

# Create an automated action that runs daily
action-create daily-conversion \
    --namespace production \
    --actions '[{
        "$type": "app",
        "version": {"id": "<app-id>", "version": 1},
        "inputs": [
            {"name": "conversionType", "value": "km_to_miles"},
            {"name": "value", "value": "100"}
        ]
    }]'

# Add a daily schedule at midnight
action-schedule-add --action-name daily-conversion \
    --namespace production \
    --minute 0 --hour 0 \
    --schedule-name "midnight-run"

Supported Conversion Types Reference

Conversion TypeFromTo
celsius_to_fahrenheitCelsiusFahrenheit
fahrenheit_to_celsiusFahrenheitCelsius
celsius_to_kelvinCelsiusKelvin
kelvin_to_celsiusKelvinCelsius
meters_to_feetMetersFeet
feet_to_metersFeetMeters
km_to_milesKilometersMiles
miles_to_kmMilesKilometers
kg_to_lbsKilogramsPounds
lbs_to_kgPoundsKilograms
grams_to_ozGramsOunces
oz_to_gramsOuncesGrams

Summary

In this tutorial, you learned how to:

  1. Structure a Cogniscript app with inputs, processing logic, and outputs
  2. Use built-in functions like StringToDecimal(), MathRound(), and DecimalToString()
  3. Handle errors gracefully with try/catch blocks
  4. Deploy the app using Shell commands
  5. Run the app via Shell with app-start and app-run
  6. Execute programmatically using the .NET SDK with proper error handling

Next Steps