Experimental Convertigo project used by the Flow POC.
The Java kernel resolves lib_flow_engine.Engine to:
libs/flow/Engine.js
Engine.js exposes:
run(requestJson) -> responseJson
analyze(requestJson) -> analysisJson
context(requestJson) -> contextJson
catalog(requestJson) -> catalogJson
search(requestJson) -> rg-like Flow search results
describeTree(requestJson) -> virtual tree JSON
applyMutation(requestJson) -> mutated Flow model + tree
outputSchema(requestJson) -> JSON schema
types(requestJson) -> property type descriptors
The Java side passes flowSource as an opaque string. This project owns parsing, catalog, analysis and execution.
For the POC, Engine.js is evaluated without the Rhino compiled-script cache so
runtime engine changes can be picked up after the Java bootstrap is restarted.
For source control, a Convertigo Flow should not serialize its full source as an escaped bean property. On the spike-flowscript branch, FlowScript is the preferred canonical sidecar in the owning project:
libs/flows/<FlowName>.flow.js
The loader only accepts this .flow.js sidecar. Legacy libs/flows/<FlowName>.flow.yaml sidecars were used during the spike migration and must not be treated as a runtime fallback. The bean property remains an in-memory editor bridge. On save/export the property is removed from Convertigo serialization and the sidecar file is written instead.
The runtime core is intentionally small. Concrete behavior is implemented by block descriptors in:
libs/flow/blocks/*.block.js
Control flow is also implemented as blocks, for example if and forEach.
The flow.call block calls another Flow sidecar inside the Flow engine. This is
the preferred low-overhead composition path when a project wants subflows
without going through Convertigo requestable/XML execution.
On spike-flowscript, blocks use *.block.js as the canonical source. The
file contains a small _meta object for the visible contract and one
implementation body:
const _meta = {
description: "Decorates a message.",
icon: "mdi:format-text",
properties: {
message: { kind: "template", type: "string" }
},
outputs: {
out: { type: "string" }
}
}
function decorate({ input, config, result }) {
const text = `*** ${input.message} ***`
return text
}The block id is derived from the path, for example
libs/flow/blocks/demo/decorate.block.js becomes demo.decorate.
Rhino-backed blocks use the same *.block.js file: _meta.runtime declares
"rhino" and the body is an IIFE returning run. Legacy *.block.yaml
descriptors are still accepted as read-only migration fallback. When a
.block.js sibling exists, it wins and the YAML descriptor is ignored. Future
implementation kinds (java, kotlin, etc.) should keep the same logical
contract shape exposed in the tree and MCP APIs.
Flow-backed blocks are regular catalog blocks. At runtime the engine exposes
evaluated instance properties through input and a private mutable local
scope for the block implementation. JS hooks/raw implementations can still call
ctx.props(node) to inspect the raw instance, but props.* is not an expression
scope. An internal return stops only the
composite block, then the parent Flow continues normally.
The fragment.use block expands a reusable graph inline from:
<current-project>/libs/flow/fragments/<FragmentName>.fragment.yaml
A fragment is not a requestable and does not create a new scope. It behaves like
the fragment nodes were written at that exact position, so it can read and write
input, config, local, result and current directly. The Flow tree and
analysis expand fragment children logically while the disk representation keeps
the fragment as a separate source file.
At runtime, the engine loads blocks and optional shared helper libraries from two places:
lib_flow_engine/libs/flow/blocks/**/*.block.js # core block definitions
<current-project>/libs/flow/blocks/**/*.block.js # project-local block definitions
<current-project>/libs/flow/fragments/*.fragment.yaml # project-local fragments
lib_flow_engine/libs/flow/lib/*.js # core helper libraries
<current-project>/libs/flow/lib/*.js # project-local helper libraries
<current-project>/libs/flow/resources/**/* # editable docs/data resources
Project-local blocks are meant for application-specific vocabulary. They cannot
silently override core blocks; a name collision is reported as an error.
Blocks can call ctx.lib("name") to load libs/flow/lib/name.js once per Flow
execution context. Blocks should declare that dependency with uses: [name] so
the library is visible in the catalog/treeview and patchable through MCP. When a reusable behavior has a clear input/output contract,
prefer a block over a helper library: blocks can be used in Flow graphs and can
also be called from JavaScript implementations with ctx.callBlock(name, props).
The POC now has one project-level FlowEngine DatabaseObject, similar in
spirit to MobileApplication or UrlMapper.
It is the project anchor for:
- the selected runtime engine;
- project-local blocks, additive with the runtime/core catalog;
- global contract bindings;
- configuration scopes;
- shared fragments.
The Java object stays intentionally small. Its editable YAML body is stored in:
<current-project>/libs/flow/engine.yaml
Example:
version: 1
engineQName: lib_flow_engine.Engine
bindings:
weather.currentTemperature@1: WeatherTemperatureOpenMeteo
config:
openMeteo:
baseUrl: https://api.open-meteo.com/v1/forecastFlow remains the requestable/executable object. It always uses the selected
engine from the project FlowEngine. It can override bindings, config values
and fragments locally when needed, but the common project defaults belong to the
project FlowEngine.
The current catalog includes low-level composable blocks for:
- HTTP:
http.get,http.request; - JSON/object data:
json.select,json.push,json.parse,json.stringify,object.pick,object.merge; - lists:
list.filter,list.map,list.sort; - control flow:
if,forEach,use,return,throw; - output and text:
set,email.mock; - Convertigo runtime:
log,requestable.call,session.get,session.set,session.remove.
lib_flow_engine should stay the standard runtime vocabulary. A block belongs
there only when it is generally useful in normal application Flows. Tooling
blocks, MCP server plumbing, benchmark helpers or migration helpers belong in a
separate library project such as lib_flow_mcp. The Flow MCP block lives there,
not in the standard catalog.
In the FlowEngine virtual tree and Eclipse palette, blocks are grouped by
origin: core engine, current project, then external libraries. The runtime still
receives one flat block name, but the authoring UI should make it clear whether
a block comes from lib_flow_engine, the application project, or a future
library project.
Block names use a light namespace convention. Short names such as set,
return and forEach are reserved for core primitives. Domain and integration
blocks should use dotted names such as json.select, requestable.call,
email.send or flow.node.add. Catalog metadata may also expose package,
namespace and private.
A private block is visible inside the project that defines it, but must not be
advertised to projects that reference that project as a library. This gives
authors and agents a safe place for local implementation details, generated
helpers and one-off glue without polluting the public palette.
Catalog APIs hide private blocks by default; diagnostic callers can pass
includePrivate: true when they intentionally inspect implementation details.
Direct Rhino code inside a Flow should remain an escape hatch, not the normal
model. The preferred equivalent of a small function is a project-local custom
block, often private: true, with a tiny catalog descriptor and optional
analyze() method. A generic script block would be useful only for debugging or
advanced migration cases because it hides intent, weakens schemas and encourages
SequenceJS-style low-code bypasses.
Custom Rhino blocks should also stay primitive-sized. They must not hide a whole
backend feature such as HTTP fetch, parsing, normalization, sorting and response
mapping in one JavaScript file. Use FlowScript for orchestration and standard
blocks such as http.get, requestable.call, list.* and json.*; keep Rhino
for the one missing bridge or algorithm.
New project-local blocks should use the FlowScript *.block.js source format:
static metadata lives in _meta, and behavior lives in one function. Rhino ES6
JavaScript is only the implementation runtime when a block needs JVM/Java
integration or algorithmic code. It may use Java classes through Packages, for
example for integration adapters, but it must not assume Node.js APIs such as
require, npm modules or browser globals. A Rhino block is still one
*.block.js: _meta carries the contract, the implementation is an IIFE
returning run, and optional dynamic display/analysis code belongs in
hooks.file. Legacy descriptor-backed Rhino remains readable only as a
migration fallback. Rhino implementations defining catalog(), displayName()
or analyze() directly are rejected.
The FlowEngine virtual tree also exposes Catalog / Types. Types are
first-class engine descriptors stored as libs/flow/types/*.type.yaml: docs,
validation/read/write hooks and web editor fragments belong there. Block
property descriptors reference this vocabulary with kind, and the catalog can
still keep usage counts as secondary information.
The spike-flowscript branch adds an experimental code-like authoring view for
LLMs. For project Flows, .flow.js is now the canonical sidecar on this branch:
the engine lists and loads it first, compiles it into the internal Flow
definition, validates block names/properties, and executes that model. Legacy
.flow.yaml Flow sidecars are obsolete and must be migrated or removed instead
of being loaded at runtime.
Use tools/migrate-flow-js-canonical.sh during the spike to report existing
pairs and, once a .flow.js sibling exists, remove obsolete YAML sidecars:
tools/migrate-flow-js-canonical.sh /path/to/Project/libs/flows
tools/migrate-flow-js-canonical.sh --remove-yaml /path/to/Project/libs/flowsUse tools/migrate-block-js-canonical.py to validate or migrate project-local
Flow-backed blocks through the live Flow MCP endpoint. It returns a complete
_meta + function FlowScript block source, validates it, and writes only when
--write is passed:
tools/migrate-block-js-canonical.py --project lib_flow_mcp
tools/migrate-block-js-canonical.py --project lib_flow_mcp --name mcp.handle
tools/migrate-block-js-canonical.py --project lib_flow_mcp --writeFor new Flows, prefer the natural code-like form:
function GetFeedSorted({ input, config, result }) {
const feed = requestable.call(".RSSConnector.GetFeed");
const sortedItems = list.sort(feed.rss.channel.item, {
by: current.title,
direction: "asc"
});
const news = list.map(sortedItems, {
title: current.title,
description: current.description,
imageUrl: current.enclosure.attr.url
});
result.news = news;
result.count = news.length;
return result;
}This is syntax sugar, not free-form JavaScript. const name = block(...)
becomes out: local.name, paths like feed.rss.channel.item become
local.feed.rss.channel.item, object-style list.map expands to
forEach/json.object/json.push, and result.key = value writes the response
scope. The lower-level canonical call form remains valid for precise
edits:
list.sort({ id: "sort", items: "local.feed.rss.channel.item", by: "current.title", out: "local.sorted" })Agent-facing core blocks:
flow.code.getflow.code.setflow.code.patchflow.code.rg
Compiler/debug core blocks:
flow.source.getflow.source.validateflow.source.patch
This is meant to compare Flow MCP blocks versus FlowScript via MCP on the
same benchmark, not to freeze a final DSL.
Type editors are standard web components loaded from libs/flow/types/editors.
For a property kind: "path", the host looks for flow-path-editor; for
kind: "requestable", it looks for flow-requestable-editor, and so on. The
Java side only provides the JxBrowser host and a small bridge.
An editor component should implement:
element.setState(state)
element.value
element.dispatchEvent(new CustomEvent("flow-value", { detail: { value } }))The host also assigns element.flowHost, with:
flowHost.request("context", {})
flowHost.request("requestables", {})
flowHost.setValue(value)This keeps editor behavior hot-reloadable from the Flow engine project instead of baking each property type into Eclipse Java code.
Core blocks should stay small, generic and predictable. The default move is to add JSON/scope/list/control blocks that compose well, then promote project-local blocks only when the vocabulary is clearly reusable.
Good first-class candidates from legacy Steps:
LogStep->log;SequenceStep/TransactionStep->requestable.callfor the KISS path;GetRequestHeaderStep,SetResponseHeaderStep,SetResponseStatusStep-> request/response blocks, once the request and response scopes are finalized;SessionGetStep,SessionSetStep,SessionRemoveStep->session.get,session.set,session.remove;SmtpStep-> not as a direct clone, but as anemail.sendcontract backed by SMTP config scopes;- JSON file helpers (
ReadJSONStep,WriteJSONStep) can become file blocks later, but need a clear project sandbox first.
Poor candidates for the initial core:
- XML/XPath/XSD-heavy Steps, because they would bring the legacy data model into a JSON-first engine before the bridge is designed;
- process and filesystem mutation blocks (
ProcessExecStep,DeleteStep,MoveFileStep, etc.) before security and deployment boundaries are defined; - authentication and context-removal Steps before runtime ownership is clear.
Block descriptors expose one display icon through icon. Use mdi:* or another
Iconify id for shared/core icons, a file path relative to the block file for
custom project/library icons, or an HTTPS URL when the icon must be cached from a
remote source. The Flow engine resolves the authoring value to concrete local
files when they exist in the provider project's cache.
Generated icon caches are intentionally ignored by Git:
libs/flow/icons/iconify/<provider>/<name>.svg
libs/flow/icons/iconify/<provider>/<name>_16x16.png
libs/flow/icons/iconify/<provider>/<name>_32x32.png
libs/flow/icons/url/<sha256>.<ext>
For example, mdi:power resolves to
libs/flow/icons/iconify/mdi/power_16x16.png when the cache has been
populated. Run tools/generate-mdi-icon-cache.js to populate the local cache
from an Iconify MDI icons.json pack. The tool reuses Convertigo's
convertigo-svg-icons Batik converter, so generated PNGs follow the same path
as NGX dynamic component icons.
If no return block is executed, the Flow returns the result scope
implicitly. Use return only to stop early or return something other than
result. Use throw inside error branches to stop with a structured error.
Most Flow data should remain JSON-serializable, but some low-level blocks need
to keep live runtime objects between nodes: Java DBO instances, file writers,
OpenDocument handles, XLS workbooks, JDBC connections or transactions. Model
those values as typed handles such as handle<dbo>, handle<file.writer>,
handle<xls.workbook> or handle<jdbc.transaction>.
Handles may live in execution scopes such as local, current and
request. They must not be returned in result, persisted in Flow YAML, learned
as JSON schemas, or sent through MCP/SDK responses. When a trace or picker needs
to show a handle, show a small serializable summary: handle type, label, state
and optional QName/path. Never serialize the Java object itself.
Prefer scoped with* blocks for resources that must be closed, mirroring Java
try-with-resources:
- block: file.withWriter
path: local.outputPath
as: local.writer
nodes:
- block: forEach
items: local.lines
nodes:
- block: file.write
writer: local.writer
value: "{{ current }}"The with* block opens the handle, writes it to the requested scope path, runs
child nodes, then closes the handle in a finally-style cleanup. Explicit
open / close blocks may exist for advanced cases, but standard libraries
should prefer the scoped form so authors and agents do not need to manage
cleanup manually.
The same pattern works for readers and other Java-backed resources. For
example, file.withReader exposes a handle<file.reader> and file.forEachLine
iterates over it while setting current to the current line:
- block: file.withReader
path: local.inputPath
as: local.reader
nodes:
- block: file.forEachLine
reader: local.reader
nodes:
- block: json.push
path: result.lines
value: "{{ current }}"Use requestable.call when the target must go through the regular Convertigo
requestable path, like the SDK does. It accepts a sequence, Flow or transaction
target and unwraps the historical XML-to-JSON document wrapper so Flow authors
work with direct JSON data.
For requestable.call to be useful to pickers and agents, target Flows should
expose a small static contract:
input:
name: string
output:
message: string
source: string
nodes:
...flow-analyze uses this output shape without running the child Flow. A node
such as out: local.custom then advertises local.custom.message and
local.custom.source as produced paths.
Engine.search() is the compact discovery API for agents. It searches Flow
sidecars, nodes, catalog blocks/types and learned schemas, then returns
references that can be passed to other tools.
Multi-word queries match unordered tokens, so GetFeed requestable call can
find a requestable.call node targeting AAAProject.GetFeed.
Node matches return:
flowQName + nodeId + JSON Pointer path
Use nodeId for semantic edit tools and the /nodes/... path for low-level
mutations. context behaves like rg -C: it adds nearby parent, previous,
children and next summaries without returning the whole Flow.
Engine.applyMutation() accepts both low-level JSON Pointer paths and semantic
node targets:
{ "op": "replace", "nodeId": "setMessage", "property": "value", "value": "Done" }Use beforeNodeId, afterNodeId, or parentNodeId + slot for insertions when
the target is clear; an agent should not depend on array indexes. JSON Pointer
paths remain the escape hatch for exact structural edits.
For broader model-debug edits, flow-get returns the parsed Flow definition,
and flow-set, flow-run, flow-test, flow-tree, flow-apply and
flow-output-schema accept that same definition object. Normal authoring should
prefer flow-code-*, because FlowScript is the canonical source on this spike.
Blocks can be listed/read from core, shared libraries and the project. Creation
and editing are project-local. For FlowScript blocks, prefer
blockCodeSet({ name, code, properties, outputs }); it writes
<name>.block.js and removes obsolete YAML fallbacks for that block.
blockCreate({ name, descriptorSource|descriptor, implementationSource })
remains a compatibility facade and writes canonical *.block.js for new
project-local blocks.
blockGet({ name })reads any visible block as one logical unit. Canonical FlowScript blocks returncode,codeRevision,descriptorandimplementationRuntime. Legacy YAML-backed blocks may still returndescriptorSourceduring migration.blockCodeSet({ name, code, properties, outputs })creates or replaces a project-local FlowScript block stored as<name>.block.js.blockCodeGet({ name })reads canonical block code with its revision for patch workflows.blockCodeRg({ pattern, name?, namespace? })searches FlowScript block code and returns small extracts with block revisions.blockCodePatch({ name, revision, codepatch|code })applies a revision-checked patch or replacement to a project-local FlowScript block.blockCreate({ name, descriptorSource|descriptor|definition, implementationSource })creates or migrates a project-local block through the canonical format. PreferblockCodeSetfor new work.blockDuplicate({ fromName, toName })copies a visible block into the project using the canonical format.blockEdit({ name, descriptorSource|descriptor|definition, implementationSource })edits a project-local block and rewrites the canonical source.
Core/shared blocks are intentionally not editable in place.
For code-like maintenance, the engine also exposes project-local text resource APIs:
resourceSearch({ query })searches whitelisted resources, like a smallrg.resourceGet({ path })reads content and returns ahash.resourcePatch({ path, baseHash, patch })applies a unified diff, then validates block/library JavaScript, Flow block/type descriptors and parses Flow/fragment YAML by default. Hunk line numbers may be approximate when the surrounding context is unique.
The writable surface is intentionally narrow:
libs/flow/blocks/**/*.js
libs/flow/blocks/**/*.block.js
libs/flow/blocks/**/*.flow.yaml
libs/flow/fragments/**/*.fragment.yaml
libs/flow/lib/**/*.js
libs/flow/resources/**/*.{md,txt,json,yaml,yml}
libs/flow/types/**/*.type.yaml
libs/flow/types/**/*.js
libs/flow/types/editors/**/*.{html,css,js}
Flow graph changes should still use flow-edit/flow-set mutations. Resource
patching is for block implementations, composite blocks, helper libraries,
custom property editors and project-local documentation/data resources.
Engine.context() returns the scope paths visible at a specific point in a
Flow. It is meant to feed Studio pickers and compact MCP/LLM guidance from the
same runtime analysis.
Request shape:
{
"flowSource": "version: 1\nnodes:\n...",
"node": "notify",
"property": "body",
"include": ["local", "result"],
"detail": "normal"
}node can be a node id, uid, name or tree path such as
nodes[3].nodes[0]. include is optional; when absent, all visible scope roots
are returned. Keep include to root scope names only, for example local,
current, input or config. detail is normal by default; use compact
when an LLM only needs the paths.
Normal response:
{
"ok": true,
"target": {
"id": "notify",
"block": "email.mock",
"property": "body",
"propertyDefinition": { "kind": "template", "type": "string" }
},
"scopes": {
"local": {
"paths": [
{ "path": "local.weather", "type": "unknown", "confidence": "inferred" }
]
}
}
}Compact response:
{
"ok": true,
"scopes": {
"local": ["local", "local.weather", "local.metropoles"]
}
}The analysis is positional. For the default position: "before", paths written
after the target node are not returned. Inside a forEach, the current scope
keeps a producer reference to the iterated source path.
Flows learn their final result structure on the first successful named run
when no declared output contract and no schema file exist yet. http.request
and http.get also learn their node output structure on the first successful
run when no node schema file exists yet. Stored files contain only types and
object keys, never response values:
libs/flow/schemas/<flowName>/result.out.schema.json
libs/flow/schemas/<flowName>/<nodeId>.out.schema.json
If a file exists, it is reused and never overwritten by runtime execution.
To relearn, delete it through the schemaReset() API or the MCP
flow-schema-reset tool; the next successful run will create it again.
Flow output schema resolution is static-first. Engine.outputSchema() starts by
analyzing the Flow graph, propagates known block output schemas through values,
merges result.* writes, and honors explicit return blocks when their value
schema is known. Learned result schemas are only a fallback when static analysis
does not know enough.
Engine.context() and Engine.analyze() read these files and expose deeper
paths such as local.weather.body.metropoles.city to Studio pickers and LLM
guidance. When a forEach iterates over an array with a known schema, the
picker context exposes the iterated item under current.*; for example
current.city and current.temperature.
Call blocks also expose known output shapes during analysis when their target is
static. requestable.call reads Flow output contracts directly. For legacy
sequences and transactions, it asks Convertigo's schemaManager, converts the
generated DOM sample to JSON, then infers a Flow schema and unwraps the
historical document container.
Block descriptors must say how each property is interpreted. A string is not automatically a scope path or a template anymore.
Current kinds:
text literal string
template string or object with {{ expression }} slots
expression pure JS-like value expression
path scope path to read or write
literal raw JSON value, no resolution
schema data shape metadata
The property type may be a runtime handle type, for example
handle<file.writer>. A consumer block should only accept compatible handles,
and a producer block should expose handle outputs through its out or as
property documentation.
Examples:
- block: http.request
method: GET
url: "{{ config.weatherUrl }}"
headers:
X-Api-Key: "{{ config.apiKey }}"
out: local.weather
- block: if
condition: current.temperature >= config.threshold
- block: json.push
path: result.hotCities
value: "{{ current.city }}"
- block: set
path: result.message
value: Weather alert computedUse value for literals and dynamic values. A string containing only
{{ expression }} returns the expression value with its native type; mixed text
uses the same syntax as a string template.
A Flow can call an intention instead of a technical implementation through
use.
The smallest contract shape is:
contracts:
weather.currentTemperature@1:
description: Current temperature for one city.
input:
city: string
latitude: number
longitude: number
unit: C|F
output:
city: string
temperature: number
unit: C|F
provider: string
defaultImplementation: WeatherTemperatureOpenMeteodefaultImplementation keeps the contract executable even when the project has
not declared a binding yet. It can point to a real provider or to an explicit
mock. A contract without a default is allowed for design work, but the runtime
will fail with NO_IMPLEMENTATION_FOR_CONTRACT if no binding is provided.
Resolution order is intentionally small:
node implementation
flow bindings
request config.bindings
project FlowEngine bindings
contract defaultImplementation
Example call site:
- id: getTemperature
block: use
contract: weather.currentTemperature@1
input:
city: current.city
latitude: current.latitude
longitude: current.longitude
unit: local.unit
out: local.temperatureThe chosen implementation is a named Flow sidecar. It receives the call input in
input and inherits the caller config scope, so provider URLs, API
keys and environment choices stay injectable.
WeatherAlertContract demonstrates this style: the main alert flow manipulates
weather.currentTemperature@1, while WeatherTemperatureOpenMeteo owns the
Open-Meteo request and response mapping.
WeatherContractBinding demonstrates a Flow-level binding override: the same
contract resolves to WeatherTemperatureMock without changing the use call
site.
WeatherProjectBinding demonstrates a project-level binding override coming
from libs/flow/engine.yaml.
The project contains a weather-alert fixture:
fixtures/weather-alert.json
It is used to validate a realistic Flow shape:
- HTTP GET with headers;
- JSON selection;
- iteration over a list;
- comparison;
- conditional push into
result.hotCities; - templated email fields;
- mock email notification;
- direct returned JSON payload.
The smoke test also validates a compact variant using the richer catalog:
- generic
http.request; list.filteroncurrent.temperature;list.sortoncurrent.city;list.mapto produce the returned city names;- implicit
resultreturn.
Run the standalone Rhino smoke test from the Convertigo source tree:
java -cp /Users/nicolas/git/convertigo/engine/build/libs/dependencies-8.5.0-beta.jar \
org.mozilla.javascript.tools.shell.Main \
-version 200 \
/Users/nicolas/git/lib_flow_engine/tests/smoke.js \
/Users/nicolas/git/lib_flow_engine/libs/flowWhen the project is loaded in Convertigo, the inline runtime validation can be called through the regular requestable URL:
curl -sS --get \
--data-urlencode '__sequence=WeatherAlertInline' \
'http://localhost:18080/convertigo/projects/AAAProject/.json'Expected payload:
{
"hotCities": ["Paris", "Marseille"],
"notification": {
"sent": true,
"to": "ops@example.com",
"subject": "Weather alert",
"body": "Hot cities over 35C: [\"Paris\",\"Marseille\"]"
},
"message": "Weather alert computed"
}WeatherAlert is the same scenario using config.weatherUrl,
config.apiKey and config.threshold. The Flow engine detects those
config.* references and, when no explicit request config is provided, reads
matching values from the Rhino scope already populated by Convertigo sequence
variables. This keeps the Java bridge unaware of Flow variable semantics.
The request scope also exposes runtime metadata useful for portable examples:
request.convertigoUrl
request.projectUrl
request.engineDir
request.engineProjectDir
request.projectDir
WeatherAlertInline reads the fixture through a local file:// URL derived
from request.engineProjectDir. This keeps the deterministic smoke test
portable in Studio and Docker, where the external HTTP host/port may not be
reachable from inside the JVM.
WeatherAlertOpenMeteo is the same type of alert against the public
Open-Meteo forecast API. It calls https://api.open-meteo.com/v1/forecast
for a small hardcoded list of French cities, reads
body.current.temperature_2m, and returns the cities above the threshold.
It is useful for a real-network demo, while WeatherAlertInline remains the
deterministic no-network smoke test.
lib_flow_engine also carries its own requestable qualification Flows:
QualifCoreDataexercises the core JSON/object blocks;QualifWeatherInlineexercises HTTP, JSON selection, list filtering, sorting and mapping against the local fixture.
Engine.analyze() is intentionally owned by the JS runtime. Java does not parse
the Flow source.
The current analysis returns:
- all known scope paths;
- global
reads; - global
writes; - property-kind based reads and writes instead of guessing every string;
- one entry per node with
id,block,props,reads,writesand child groups; - learned JSON schemas, when available, attached to the scope path produced by the node;
- legacy requestable schemas for static
requestable.calltargets when a live Convertigo engine is available; - a first
errorsarray for future static diagnostics.
This is the first shape needed by a future Flow picker or MCP tool. Deep JSON paths are exposed when a node has a declared or learned schema.
The MCP-facing experiment lives in lib_flow_mcp. It uses the same runtime
APIs, catalog and context analysis as a normal Flow project, but keeps protocol
plumbing out of the standard engine vocabulary.
The agent authoring cycle is documented by lib_flow_mcp, which owns the MCP
tool names and protocol surface.