Appearance
Actor integration and IR pipeline
This page describes the contract between FlowPad and the actors it executes flows against.
CanonicalIR
projects/matrix-3/packages/flowpad/src/runtime/queryPipeline.ts defines the IR (queryPipeline.ts:42-60):
ts
export type CanonicalIR = IQueryExecutionIR | IComponentSchemaIR;
export interface IQueryExecutionIR {
kind: 'query-plan';
language: QuerySourceLanguage;
source: string;
input: unknown;
steps: QueryStep[];
adapterPath: string[];
}
export interface IComponentSchemaIR {
kind: 'component-schema';
language: QuerySourceLanguage;
source: string;
sourceFormat: SerializationFormat;
schema: IComponentSchema;
adapterPath: string[];
}QueryStep is one of:
ts
type QueryStep = IRemoteCallStep | ILocalMapStep | ILocalFilterStep;
interface IRemoteCallStep { kind: 'remote-call'; mount: string; operation: string; }
interface ILocalMapStep { kind: 'local-map'; expression: string; }
interface ILocalFilterStep{ kind: 'local-filter'; expression: string; }adapterPath is a flat list of mount addresses the input value will pass through, in order. Status panes can render this as breadcrumbs without re-walking steps.
Source languages
QuerySourceLanguage covers everything the editor or an upstream caller might supply:
flow-dsl, flow-json, lisp, scheme,
json-command, json-payload, sql,
serialization-html, serialization-fluent, serialization-json,
serialization-sexpr, serialization-topicsdetectLanguage (runtime/queryPipeline.ts:97+) chooses based on sourceFormatHint, then modeHint, then a JSON-shape probe.
How a typed flow becomes an IR
mermaid
sequenceDiagram
participant Editor as PipelineEditor
participant Shell as FlowpadAppShell
participant App as FlowpadApp
participant Pipeline as queryPipeline.ts
Editor->>Editor: detect mode (flow / lisp)
Editor->>App: requestRun { code, mode }
App->>Pipeline: compileSourceToCanonicalIR({ source, defaultTargetMount, modeHint })
Pipeline-->>App: { language, ir, adapterPath }
App->>App: emit executionPlanned
App->>Pipeline: executeCanonicalIR(context, ir)
Pipeline-->>App: result
App->>App: emit flowCompletedFor LISP/Scheme input, the editor first wraps the source as flow().fromValue({ "eval": null, "expr": "..." }).viaRemote("./vlm-eval", "eval"). The wrapper is not stored back in the editor — it's a runtime transformation invisible to the user. After wrapping, the rest of the pipeline is identical to Flow-mode execution.
Actor contract for viaRemote targets
For a remote-call step viaRemote(mount, operation), FlowPad will:
- Resolve
./mountagainst the calling actor's mount root. RequestReply.execute(context, mount, operation, currentInput, { timeoutMs })—currentInputis the previous step's output.- Use the result as the next stage's input.
The target actor must:
- Declare the
operationinstatic acceptswith a parameter schema that's compatible withcurrentInput. - Reply via the standard Matrix request/reply pattern (
$reply.{correlationId}envelope on the bus).
Per-stage timeout policy is the same as documented in Running flows: 30s cross-root, 10s VLM, 5s otherwise.
Bootstrapped service contracts
The eleven services FlowpadApp mounts as children all conform to the contract above. Examples:
ts
// MockDbService accepts:
{ Query: { table: 'string', limit: 'number?' } }
// SchemeEval accepts:
{ eval: { source: 'string?', expr: 'any?' } }
// SecurityRealmService accepts:
{ getDefaultPolicy, mintCapability, attenuate, revokeCapability,
validateCapability, listCapabilities, cascadeDemo }The full list lives in each service's source file under src/services/.
The FlowRunner path
onRunFlow (the example-execution path, separate from onRunFromCode) uses FlowBuilder (from @open-matrix/core/flow/FlowBuilder) to construct a plan, then sends it to the flow-runner child:
ts
// FlowpadApp.ts:373-412
const builder = FlowBuilder.start(this._context!);
let chain = builder.fromValue(input);
for (let i = 1; i < example.stages.length; i++) {
const stage = example.stages[i];
if (stage.kind === 'remote-rr') {
chain = chain.viaRemote(mount, stage.op);
} else if (stage.kind === 'local-map') {
chain = chain.mapLocal(stage.fn);
}
}
const plan = chain.build();
const result = await RequestReply.execute(
this._context!,
this._context!.mount + '.flow-runner',
'RunFlow',
{ plan, input, flowId }
);FlowRunnerService (src/services/FlowRunnerService.ts) is what receives that RunFlow op. The two paths exist because onRunFlow predates onRunFromCode — they will likely converge as the IR becomes more capable. Today, onRunFromCode goes through executeCanonicalIR directly (no flow-runner round-trip), onRunFlow goes through the runner service.
Five-representation parity
When the editor input parses as a component schema (HTML, Fluent, S-expr, Topics, or JSON), compileSourceToCanonicalIR produces an IComponentSchemaIR instead of a query plan. The pipeline can convert any one of those formats to any other using @open-matrix/core/serialization — see the DSL examples 'Serialize: S-expr', 'Serialize: HTML', 'Serialize: Fluent', 'Serialize: Topics', 'Serialize: JSON'. This is the codepath for treating FlowPad as a serialization workbench rather than a pipeline runner.
Targeting agents and oracles
FlowpadApp.onSnapshotTreeOracle (FlowpadApp.ts:264-304) returns the page's surface as a tree:
ts
{
mount,
tag: 'flowpad-app',
accepts: [ { op, params: [[name, type], ...] }, ... ],
emits: [...],
cognitiveState: { selectedExample, targetMount, availableExamples },
children: [ { mount, tag, accepts, emits, ... }, ... ],
}An LLM or external orchestrator that has FlowPad as $TARGET calls snapshot.tree.oracle (or introspect) to learn the available ops and the example library. From there it can synthesise runFromCode calls.
See also
Source:
projects/matrix-3/packages/flowpad/src/runtime/queryPipeline.tsandprojects/matrix-3/packages/flowpad/src/FlowpadApp.ts:336-412.