ARPIA Docs

Reasoning Flows — Configurable Objects Overview

Updated October 26, 2026

Configurable Objects Overview#

Create interactive configuration interfaces for Reasoning Flows objects using HTML forms.


Purpose#

In Reasoning Flows, you can create objects in PHP or Python with an optional interactive configuration interface. By adding a config.html file to your object, you enable users to configure parameters through a form instead of editing code directly.

Form values are passed directly to the startup script (index.py or index.php) at runtime, making it easy for non-technical users to adjust settings without modifying code.

Objects without a config.html file work exactly as before — opening in the standard code editor view.


Object Types#

An object must use one of the following languages:

Language Startup File Runtime
Python index.py Python 3.x
PHP index.php PHP 8.x

The startup file is required and serves as the entry point for execution. It can import or include other files within the same object.


Configuration UI (config.html)#

With config.html#

When an object contains a config.html file:

  • Reasoning Flows renders it as an interactive configuration form
  • User inputs are passed directly to the startup script at execution time
  • You can still access View Code Mode to see and edit the object's files

Without config.html#

When config.html is absent:

  • The object opens in Code Mode by default
  • Users edit code and configuration files manually

How Form Values Reach Your Code#

Each form field's name attribute becomes the key your startup script reads:

Language Values arrive as Read with
Python Environment variables os.environ.get('field_name')
PHP POST parameters $_POST['field_name']

Security note: Form values are user input. Always validate them in your startup script before use — especially anything that ends up in a SQL statement. Use parameterized queries for values, and strict allowlists or pattern checks for identifiers such as table names, which cannot be parameterized.


Creating a Configuration Interface#

Example: config.html#

<!DOCTYPE html>
<html>
<head>
    <title>Data Export Configuration</title>
    <style>
        form { max-width: 400px; margin: 20px; }
        label { display: block; margin-top: 10px; font-weight: bold; }
        input, select { width: 100%; padding: 8px; margin-top: 5px; }
        button { margin-top: 20px; padding: 10px 20px; }
    </style>
</head>
<body>
    <h2>Data Export Settings</h2>
    <form id="config-form">
        <label for="source_table">Source Table:</label>
        <select id="source_table" name="source_table">
            <option value="sales_gold">sales_gold</option>
            <option value="customers_gold">customers_gold</option>
            <option value="inventory_gold">inventory_gold</option>
        </select>

        <label for="row_limit">Row Limit:</label>
        <input type="number" id="row_limit" name="row_limit" value="1000" min="1" max="100000">

        <label for="output_format">Output Format:</label>
        <select id="output_format" name="output_format">
            <option value="csv">CSV</option>
            <option value="json">JSON</option>
        </select>

        <label for="include_headers">Include Headers (CSV):</label>
        <select id="include_headers" name="include_headers">
            <option value="yes">Yes</option>
            <option value="no">No</option>
        </select>

        <button type="submit">Export Data</button>
    </form>
</body>
</html>

Tip: Offering the table name as a dropdown instead of a free-text field improves usability and security — users can't mistype, and your script can verify the value against the same fixed list.

Reading Config Values in Python (index.py)#

import dkconnect
import os
import json

# --- Read form values (environment variables) ---
source_table = os.environ.get('source_table', 'sales_gold')
row_limit = int(os.environ.get('row_limit', 1000))
output_format = os.environ.get('output_format', 'csv')
include_headers = os.environ.get('include_headers', 'yes') == 'yes'

# --- Validate user input before use ---
# Table names cannot be parameterized in SQL, so verify against an allowlist.
ALLOWED_TABLES = {'sales_gold', 'customers_gold', 'inventory_gold'}
if source_table not in ALLOWED_TABLES:
    raise ValueError(f"Invalid source table: {source_table}")

row_limit = max(1, min(row_limit, 100000))  # clamp to a sane range

# --- Connect and query ---
conn = dkconnect.connect()
cursor = conn.cursor()

# Identifier validated above; LIMIT value parameterized.
cursor.execute(
    f"SELECT * FROM {source_table} LIMIT %s",
    (row_limit,)
)
columns = [desc[0] for desc in cursor.description]
rows = cursor.fetchall()

# --- Output based on format selection ---
if output_format == 'json':
    data = [dict(zip(columns, row)) for row in rows]
    print(json.dumps(data, indent=2, default=str))
else:  # csv (default)
    if include_headers:
        print(','.join(columns))
    for row in rows:
        print(','.join(str(val) for val in row))

cursor.close()
conn.close()

Reading Config Values in PHP (index.php)#

<?php
require_once('datakubes-connect.php');

// --- Read form values (POST parameters) ---
$source_table = $_POST['source_table'] ?? 'sales_gold';
$row_limit = (int)($_POST['row_limit'] ?? 1000);
$output_format = $_POST['output_format'] ?? 'csv';
$include_headers = ($_POST['include_headers'] ?? 'yes') === 'yes';

// --- Validate user input before use ---
// Table names cannot be parameterized in SQL, so verify against an allowlist.
$allowed_tables = ['sales_gold', 'customers_gold', 'inventory_gold'];
if (!in_array($source_table, $allowed_tables, true)) {
    http_response_code(400);
    die('Invalid source table.');
}

$row_limit = max(1, min($row_limit, 100000)); // clamp to a sane range

// --- Connect and query ---
$dk_conn = dk_connect();

// Identifier validated above; LIMIT value parameterized.
$stmt = $dk_conn->prepare("SELECT * FROM $source_table LIMIT ?");
$stmt->bind_param('i', $row_limit);
$stmt->execute();
$result = $stmt->get_result();

// Get column names
$columns = [];
foreach ($result->fetch_fields() as $field) {
    $columns[] = $field->name;
}

// --- Output based on format selection ---
if ($output_format === 'json') {
    header('Content-Type: application/json');
    $data = [];
    while ($row = $result->fetch_assoc()) {
        $data[] = $row;
    }
    echo json_encode($data, JSON_PRETTY_PRINT);
} else { // csv (default)
    header('Content-Type: text/csv');
    if ($include_headers) {
        echo implode(',', $columns) . "\n";
    }
    while ($row = $result->fetch_row()) {
        echo implode(',', $row) . "\n";
    }
}

$stmt->close();
$dk_conn->close();

Common Use Cases#

Use Case Description
Data Transformation Pipelines Let users select data sources, filters, or transformation rules before processing
API Integration Configurations Collect authentication keys, endpoints, and query parameters via form inputs
AI & Machine Learning Parameters Adjust model settings, thresholds, or prompts before execution
Report Generation Allow users to specify date ranges, departments, or output formats
Automation & Task Runners Schedule or parameterize recurring backend tasks without code changes
Environment-Specific Settings Switch between dev, staging, and production values dynamically

Who Uses Configurable Objects#

This feature has two distinct roles by design — the person who builds the object and the people who run it:

Role Side How they use it
Platform / data engineers Maker Wrap recurring ETL, exports, and pipelines in a form so parameter changes stop being tickets
ML engineers Maker Expose thresholds, prompts, and model parameters for experimentation without code access
Analysts Consumer Run extracts and reports with their own filters, tables, and output formats
Operations / business staff Consumer Trigger parameterized recurring tasks without touching code
Team leads / security owners Beneficiary Grant run-with-parameters capability without granting code-write access

Note: Every form field is a contract. Once consumers depend on a parameter, renaming or removing it is a breaking change — design forms with the same care as an API.


Comparison#

With config.html Without config.html
UI rendered for configuration Shows file/code browser
Inputs sent directly to main file Edit code/config manually
User-friendly for non-developers Requires code editing
Always starts at index.* Always starts at index.*

Benefits#

Flexibility\ Choose PHP or Python based on your workflow and team expertise.

Ease of Use\ Non-technical users can configure objects via forms instead of editing code.

Predictable Execution\ Always starts from a single entry file (index.py or index.php).

Modularity\ Startup scripts can import helper files, libraries, and shared modules.

Backward Compatibility\ Objects without config.html work exactly as before.


Best Practices#

Keep forms simple. Focus on the parameters users actually need to change.

Provide sensible defaults. Pre-populate form fields with common values.

Validate inputs. Treat every form value as untrusted. Parameterize SQL values; allowlist identifiers like table or column names (see the examples above). Clamp numeric inputs to sane ranges.

Prefer constrained inputs. Dropdowns, number fields with min/max, and fixed option sets prevent invalid input at the source — and make server-side validation trivial.

Use clear labels. Make form fields self-explanatory for non-technical users.

Add help text. Include placeholders or descriptions to explain expected values.

Handle missing values gracefully. Always provide fallback defaults in your code.

Test both modes. Verify your object works correctly with and without the configuration form.


↑↓ to move↵ to openEsc to close