NeuCharFramework (NCF)
  • NCF - NeuCharFramework
  • Projects

    • Preparation
    • NcfPackageSources Source Guide
    • DynamicWebApiEngine
    • Deployment
    • MCP (Model Context Protocol) Module
    • Senparc.AI
  • AIKernel Local Fine-Tuning
  • Help

    • Online Resources
    • Q&A Community
    • QQ Group (147054579)
    • Senparc WeChat SDK
  • Gitee
  • GitHub
  • English
  • 简体中文
GitHub
  • NCF - NeuCharFramework
  • Projects

    • Preparation
    • NcfPackageSources Source Guide
    • DynamicWebApiEngine
    • Deployment
    • MCP (Model Context Protocol) Module
    • Senparc.AI
  • AIKernel Local Fine-Tuning
  • Help

    • Online Resources
    • Q&A Community
    • QQ Group (147054579)
    • Senparc WeChat SDK
  • Gitee
  • GitHub
  • English
  • 简体中文
GitHub
  • Source Development

    • NcfPackageSources Beginner Quickstart (60 Minutes)
    • NcfPackageSources Source Guide
    • Project Relationships, Synchronization, and Release
    • Version Upgrade Notes
    • NcfPackageSources_Include_NcfSimulatedSite.sln Project Map
  • XNCF Source Analysis

    • NCF Capability Source Deep Dive (Practical)
    • XNCF Extension Library Guide (Senparc.Xncf.xxx)
    • XNCF Module Documentation Map
    • Senparc.Web and Admin
    • AIKernel Local Fine-Tuning
    • XncfBuilder Isolated Development: Source Analysis
    • NeuChar Workflow: Operations and {{= ... }} Expressions
    • Senparc.Xncf.Sandbox Environment Setup
  • NCF Library Source Analysis

    • Senparc.Ncf.Core
    • Senparc.Ncf.Database (Database Foundation)
    • Senparc.Ncf.Repository
    • Senparc.Ncf.Service
    • Senparc.Ncf.SMS
    • Senparc.Ncf.Mvc.UI
    • Senparc.Ncf.Log
    • Senparc.Ncf.Utility
    • Senparc.Ncf.XncfBase
    • Senparc.Ncf.AreaBase
    • Senparc.Ncf.DatabasePlant
  • Core Interface Source Analysis

    • IXncfRegister Interface (Current)

NeuChar Workflow: Operations and {{= ... }} Expressions

Applies to Senparc.Xncf.NeuCharWorkflow 0.1.0-preview1, checked against the NcfPackageSources development 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 version 0.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/Index for design, save, test, and triggering.
  • Task list: /Admin/NeuCharWorkflow/Tasks for 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.

ScopeCurrent behavior
TriggersManual, 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 runDrafts may retain disconnected nodes, but such a draft is treated as disabled and cannot be run as an interval or Webhook workflow.
Run recordsThe 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 codeUp to 30 workflow variables; the Safe Code node assigns only declared variables and cannot run arbitrary JavaScript.
Human interactionThe 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.
  • SaveRuntimeStartedAsync uses the base repository's SavePropertiesAsync to save only LastRunAt, NextRunAt, and LastUpdateTime.
  • SaveRuntimeCompletedAsync saves only LastSucceeded, LastError, and LastUpdateTime, without writing stale GraphJson or Revision values 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

  1. Install and enable Senparc.Xncf.NeuCharWorkflow in XncfModuleManager, then open NeuChar Workflow from the Admin menu.
  2. Create a workflow, set its name, description, and trigger. The canvas keeps one trigger node matching that selection.
  3. Add Functions, objects, or system nodes from the palette and connect them into a directed graph reachable from the trigger. Cycles are not supported.
  4. Start with a Manual trigger, supply test input, and run it. Save and run both validate edges, parameters, module references, and object state.
  5. 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.
  6. 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

NodePurpose and runtime behavior
Manual / Interval / Webhook triggerChoose one. Interval is configured in seconds, from 60 seconds to 365 days; Webhook starts an asynchronous server-side run.
FunctionInvokes 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 / A2ASends the resolved Prompt to an available object. Validation fails if its module is closed, or the object was removed or disabled.
DelayDelays and then passes input through.
ConditionCompares left and right values and continues through its matching/non-matching branch while retaining the input.
ParallelFans the same input out to every downstream branch, which can run independently.
AggregateWaits 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.
MergeDoes not wait for all inputs; it emits each input downstream independently. Combined loop and merge stream activations are capped at 500 per run.
For loopExecutes downstream sequentially a finite number of times, from 1–100. There is no while and no graph back-edge.
Sub-workflowRuns 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 CodeAssigns declared vars values with restricted templates during this run only, then passes the original input through.
ConsoleRenders its template to the page Console and does not change the original input passed downstream.
NeuBellCreates a workflow notification. Opening it from the Footer enters the task list and applies the node's consumption setting.
Wait for human inputCreates 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.
EndEnds 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 Accepted and a runId; 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:

  1. Workflow creates a one-time pending request associated with the current run, node, administrator, and request ID.
  2. A Workflow NeuBell notifies the current administrator. The Workflow run panel can accept text and continue, or reject the request.
  3. 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.

FormMeaning
{{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.variableNameReads 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.

CategorySupported form
Valuesinput, vars.customerName, input.items[0], value_1
Unary!value, -number
Arithmetic / concatenation+, -; + adds two numbers and otherwise concatenates text.
Comparison==, !=, >, >=, <, <=
Logic&&, ||
Conditionalcondition ? 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.

GroupFunctionsExample / note
Branchingif(condition, yes, no), coalesce(a, b, ...){{= if(input.vip, 'VIP', 'Standard') }}; coalesce returns the first non-empty value.
Searchcontains(value, sought), startsWith(text, prefix), endsWith(text, suffix)String search is case-insensitive; contains can also test an array member.
Textlength(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 / joinsplit(text, separator), join(array, separator){{= join(input.tags, ' / ') }}.
Elementfirst(array), last(array), at(array, index)An out-of-range access returns an empty value.
NumbertoNumber(value), sum(array), min(array), max(array)Numeric functions require numeric values.
Array transformsort(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/timenow(), 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

SymptomCheck first
Function or Agent node cannot runReferenced XNCF module is installed/enabled, Function remains scanned, and object remains available.
Saved workflow is not enabledDisconnected nodes, invalid references, missing required parameters, loop count, or a sub-workflow reference error.
Interval does not runEnabled state, interval trigger, due next-run time, then the module background-service logs.
Webhook returns 401 / 400 / 405Token, required parameters, and method. 405 means the configuration allows only GET or POST.
Human input does not resumeCheck 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 unboundUse only input, vars, and UI-inserted upstream binding tokens; verify the name and upstream connection.
Console differs from downstream resultExpected: Console changes presentation only; the original input continues downstream.

Related documentation:

  • XNCF Extension Library Guide
  • NCF Capability Source Deep Dive
  • AgentsManager HIL and Workflow integration
Edit this page on GitHub
Last Updated:
Contributors: JeffreySu
Prev
XncfBuilder Isolated Development: Source Analysis
Next
Senparc.Xncf.Sandbox Environment Setup