NeuChar Workflow: Operations and {{= ... }} Expressions
Applies to
Senparc.Xncf.NeuCharWorkflow0.1.0-preview1, checked against theNcfPackageSourcesdevelopment line on 2026-08-16. This describes the current server-side module. Available nodes depend on the enabled XNCF modules, the current site, and the administrator's permissions. The data-access/runtime-state section was separately added on 2026-10-02 against development project version0.4.3.
NeuCharWorkflow is a server-side visual orchestration module. It connects enabled XNCF Functions, Agent / Agent-group / A2A objects, and built-in system nodes as a directed graph executed by the server. It is not a browser scripting engine and it never evaluates arbitrary JavaScript in a template.
1. Capabilities and Boundaries
Installation runs the module's database migrations and adds two Admin entries:
- NeuChar Workflow:
/Admin/NeuCharWorkflow/Indexfor design, save, test, and triggering. - Task list:
/Admin/NeuCharWorkflow/Tasksfor run status, aborting active runs, and read-only replay.
The pages require a signed-in administrator. When troubleshooting, check module installation/enabled state, administrator permission, the state of referenced XNCF modules, and whether the referenced Function or object still exists.
| Scope | Current behavior |
|---|---|
| Triggers | Manual, interval, and Webhook. Each graph must contain exactly one matching trigger. |
| Executable capabilities | [FunctionRender] Functions from enabled modules, available Agent / Agent-group / A2A objects, and built-in system nodes. |
| Save and run | Drafts may retain disconnected nodes, but such a draft is treated as disabled and cannot be run as an interval or Webhook workflow. |
| Run records | The task list shows status and summary. A completed run can be replayed read-only from its captured graph without changing the current definition. |
| Variables and code | Up to 30 workflow variables; the Safe Code node assigns only declared variables and cannot run arbitrary JavaScript. |
| Human interaction | The native Wait for human input node is available; when AgentsManager is enabled, Agent / Agent-group HIL requests also appear in the same Workflow handling panel. |
1.1 Data Access and Runtime State Persistence (2026-10-02)
- Workflow Service queries reuse the existing
RepositoryBase.GeAll(...), preserving database-side projections, paging, and cancellation. The underlying context centrally applies tenant and soft-delete filters. SaveRuntimeStartedAsyncuses the base repository'sSavePropertiesAsyncto save onlyLastRunAt,NextRunAt, andLastUpdateTime.SaveRuntimeCompletedAsyncsaves onlyLastSucceeded,LastError, andLastUpdateTime, without writing staleGraphJsonorRevisionvalues over a newer definition saved by an administrator during execution.- Partial saves require an already persisted entity tracked by the current context. Do not manipulate ChangeTracker inside the module Service or replace the partial update with a full save of the stale entity.
See the Repository Guide for API constraints and Multi-Tenant Configuration and Background Work for host-wide background tenant scope conventions.
2. Create and Safely Run a Workflow
- Install and enable
Senparc.Xncf.NeuCharWorkflowin XncfModuleManager, then open NeuChar Workflow from the Admin menu. - Create a workflow, set its name, description, and trigger. The canvas keeps one trigger node matching that selection.
- Add Functions, objects, or system nodes from the palette and connect them into a directed graph reachable from the trigger. Cycles are not supported.
- Start with a Manual trigger, supply test input, and run it. Save and run both validate edges, parameters, module references, and object state.
- Use the Console for presentation output and the task list for active runs, failures, and read-only replay. An administrator can abort their own active run.
- Enable interval or Webhook execution only after the graph is valid. Saves create revisions; a replay can be copied as a new editable workflow.
2.1 Node Reference
| Node | Purpose and runtime behavior |
|---|---|
| Manual / Interval / Webhook trigger | Choose one. Interval is configured in seconds, from 60 seconds to 365 days; Webhook starts an asynchronous server-side run. |
| Function | Invokes an enabled XNCF [FunctionRender] Function. A parameter can bind a complete upstream output, or a single-value text parameter can use a template. |
| Agent / Agent group / A2A | Sends the resolved Prompt to an available object. Validation fails if its module is closed, or the object was removed or disabled. |
| Delay | Delays and then passes input through. |
| Condition | Compares left and right values and continues through its matching/non-matching branch while retaining the input. |
| Parallel | Fans the same input out to every downstream branch, which can run independently. |
| Aggregate | Waits for every activated upstream branch, builds an input array in edge order, and emits once. A sole {{input}} preserves that array as a JSON value. |
| Merge | Does not wait for all inputs; it emits each input downstream independently. Combined loop and merge stream activations are capped at 500 per run. |
| For loop | Executes downstream sequentially a finite number of times, from 1–100. There is no while and no graph back-edge. |
| Sub-workflow | Runs another saved, enabled workflow owned by the current administrator and returns its final output. Direct/indirect recursion is rejected; nesting is capped at 8 levels. |
| Safe Code | Assigns declared vars values with restricted templates during this run only, then passes the original input through. |
| Console | Renders its template to the page Console and does not change the original input passed downstream. |
| NeuBell | Creates a workflow notification. Opening it from the Footer enters the task list and applies the node's consumption setting. |
| Wait for human input | Creates a pending request and NeuBell, pauses the current branch, then emits the submitted text after a human response. An external WebAPI can optionally resume it. |
| End | Ends that path. |
2.2 Interval and Webhook
The module's background service scans due, enabled interval workflows. The minimum interval is 60 seconds, so it is suitable for periodic work rather than a sub-second real-time queue.
After saving a Webhook workflow, the editor shows a URL and an access token. Send the token in X-NeuChar-Webhook-Token (recommended) or the token query parameter. The method may be limited to GET, POST, or unrestricted.
curl -X POST 'https://example.test/api/Senparc.Xncf.NeuCharWorkflow/neuchar-workflow/webhook/123' \
-H 'X-NeuChar-Webhook-Token: <workflow-webhook-token>' \
-H 'Content-Type: application/json' \
-d '{"customerName":"Ada","amount":42}'- Configured parameters are read from query values, form fields, and JSON-object fields; required parameters are validated.
- With no configured parameters, the entire request becomes workflow input. A non-JSON request body is stored as
_body. - The request body limit is 1 MB. A valid request returns
202 Acceptedand arunId; execution continues asynchronously on the server. - Webhook is anonymous by design. Treat its URL, token, and logs as production secrets, and use HTTPS, least privilege, rate limiting, and audit controls.
2.3 Wait for Human Input and AgentsManager HIL
Wait for human input is a native Workflow system node for approvals, missing information, and human confirmation. When execution reaches it:
- Workflow creates a one-time pending request associated with the current run, node, administrator, and request ID.
- A Workflow NeuBell notifies the current administrator. The Workflow run panel can accept text and continue, or reject the request.
- On approval, the submitted text becomes the node's string output and flows downstream. On rejection, the current branch ends.
When an Agent or Agent-group run triggers humanTurn (a human conversation turn) or toolApproval (tool approval) in AgentsManager, the AgentsManager-to-Workflow HIL bridge associates the request with the current Workflow run. Workflow status polling merges native requests and AgentsManager requests into one panel. Resolution still goes through AgentsManager's access check, one-time completion, and NeuBell-consumption path. See AgentsManager HIL and Workflow integration for the complete integration guide.
Resuming native human input from an external program
Enable Allow external resume in the node inspector and generate a resume key. The key is encrypted at rest and is not returned when the editor is reopened; leaving the field blank when saving preserves the stored key. The API does not require an Admin login, but every call must include the node's resume key:
GET /api/Senparc.Xncf.NeuCharWorkflow/neuchar-workflow/human-input/pending/{workflowId}
X-NeuChar-Workflow-Resume-Key: <node-resume-key>The response lists pending request IDs for the workflow. Submit the one-time input with:
curl -X POST 'https://example.test/api/Senparc.Xncf.NeuCharWorkflow/neuchar-workflow/human-input/<requestId>' \
-H 'X-NeuChar-Workflow-Resume-Key: <node-resume-key>' \
-H 'Content-Type: application/json' \
-d '{"approved":true,"input":"Approved; ticket T-100 added"}'approved defaults to true; set it to false to reject the request, optionally including a reason. A request ID becomes invalid immediately after successful handling. This API currently resumes only the native Wait for human input node; AgentsManager HIL continues to use its Admin entry and the Workflow quick-handling panel.
Runtime boundary
Both the native human-input queue and the AgentsManager HIL queue currently live in the Host process so they can hold the active execution handle. An application restart, routing a request to a different instance, or running without sticky routing can leave an existing wait unable to continue. Before production use, evaluate persistent checkpoints, shared coordination, HTTPS, key rotation, rate limiting, and audit controls.
3. Text Templates and Variables
Fields with Edit formula text may combine literal text, input placeholders, restricted expressions, and upstream bindings inserted by the UI. They include single-value Function text parameters, condition values, Agent prompts, sub-workflow input, NeuBell title/summary, aggregate output, and Console output.
| Form | Meaning |
|---|---|
{{input}} | Built-in current-node input. In an Aggregate node, using this alone preserves the aggregate array; other text fields render it as text. |
{{= expression }} | Evaluates a restricted expression and inserts its result. It never evaluates JavaScript. |
{{value_1}} | A visible binding token inserted by Edit formula text after choosing an upstream output. Its name is generated by the UI and it may also be used in an expression, for example {{= upper(value_1) }}. |
vars.variableName | Reads a workflow variable. Names use letters, digits, and underscores, start with a letter or underscore, and cannot be input or vars. |
Declare default variables in Workflow settings. They are evaluated in declaration order, so a later definition may read an earlier vars.name; a Safe Code node can update them for this run. They are discarded when the run ends and are not global configuration.
The UI saves upstream fields as a $template with binding-source records. Select bindings through the editor instead of fabricating those records manually. A manually written {{= ... }} expression can access only current input, vars, and already inserted binding tokens.
4. {{= ... }} Syntax
4.1 Basics
Each expression is limited to 512 characters; a text field may contain at most 32 expressions; and one rendered expression result may not exceed 8000 characters. Whitespace, parentheses, single/double quoted strings (with \n, \r, and \t escapes), numbers, true, false, and null are supported.
| Category | Supported form |
|---|---|
| Values | input, vars.customerName, input.items[0], value_1 |
| Unary | !value, -number |
| Arithmetic / concatenation | +, -; + adds two numbers and otherwise concatenates text. |
| Comparison | ==, !=, >, >=, <, <= |
| Logic | &&, || |
| Conditional | condition ? whenTrue : whenFalse |
There is no *, /, %, assignment, loop, reflection, host object, network access, or JavaScript API. Property access applies only to JSON objects and indexing only to JSON arrays; missing properties and out-of-range indexes yield an empty value.
4.2 Built-in Functions
Function names are case-insensitive. In the examples, value, array, and text are expressions.
| Group | Functions | Example / note |
|---|---|---|
| Branching | if(condition, yes, no), coalesce(a, b, ...) | {{= if(input.vip, 'VIP', 'Standard') }}; coalesce returns the first non-empty value. |
| Search | contains(value, sought), startsWith(text, prefix), endsWith(text, suffix) | String search is case-insensitive; contains can also test an array member. |
| Text | length(value), substring(text, start[, length]), trim(text), lower(text), upper(text), replace(text, old, new) | {{= upper(vars.customerName) }}; length returns array length for arrays. |
| Split / join | split(text, separator), join(array, separator) | {{= join(input.tags, ' / ') }}. |
| Element | first(array), last(array), at(array, index) | An out-of-range access returns an empty value. |
| Number | toNumber(value), sum(array), min(array), max(array) | Numeric functions require numeric values. |
| Array transform | sort(array[, path[, direction]]), orderBy(array[, path[, direction]]), reverse(array), take(array, count), skip(array, count), unique(array) | {{= orderBy(input.items, 'price', 'desc') }}; path permits letters, digits, underscores, and dots; direction is asc or desc. |
| Date/time | now(), formatDate(value[, format]) | now() returns UTC ISO 8601; {{= formatDate(now(), 'yyyy-MM-dd HH:mm') }} uses a .NET date format limited to 80 characters. |
4.3 Template Examples
Hello, {{= upper(vars.customerName) }}!
Date: {{= formatDate(now(), 'yyyy-MM-dd') }}
Order count: {{= length(input.orders) }}
Tier: {{= if(input.vip, 'VIP', 'Standard') }}
Tags: {{= join(unique(input.tags), ', ') }}
Highest-priced item: {{= first(orderBy(input.items, 'price', 'desc')).name }}For an Aggregate node, these respectively preserve the array and render a text summary:
{{input}}
{{= length(input) }} item(s): {{= join(input, ', ') }}For Console, the default {{input}} controls only what is printed. The original node input always continues downstream.
5. Troubleshooting
| Symptom | Check first |
|---|---|
| Function or Agent node cannot run | Referenced XNCF module is installed/enabled, Function remains scanned, and object remains available. |
| Saved workflow is not enabled | Disconnected nodes, invalid references, missing required parameters, loop count, or a sub-workflow reference error. |
| Interval does not run | Enabled state, interval trigger, due next-run time, then the module background-service logs. |
| Webhook returns 401 / 400 / 405 | Token, required parameters, and method. 405 means the configuration allows only GET or POST. |
| Human input does not resume | Check that Workflow/AgentsManager are enabled, the run still exists, the request was not already handled, and the resume key and instance route are correct. |
| Expression says variable is unbound | Use only input, vars, and UI-inserted upstream binding tokens; verify the name and upstream connection. |
| Console differs from downstream result | Expected: Console changes presentation only; the original input continues downstream. |
Related documentation: