The program: graph and runs
The program from inside: the graph, events, variables, nodes and the data that go along them, frames, the run, the checkpoint, groups and calls. Here are the rules by which a program works; what each kind of node does — Kinds of nodes.
Introduction
The document describes the make-up and the carrying out of a working process. The owner of a working process is the function graph. Its place in the platform — The make-up of the platform, file formats — File formats, sections 5, 6, 9, 11, methods — Methods of the graph.
A working process is a live program. A program is switched on, and a program that is on waits for events, keeps its state in variables and answers each event with a run. The model «start a process — get a result» is a particular case: the pulpit event «Run» and the run from it.
1 The make-up of a working process
A working process is kept in the folder data/processes/<process_id>/ (table 1).
Table 1 — The make-up of a working process
| File | What is inside | Owner |
|---|---|---|
process.json |
the graph: nodes, links, groups, variables, shared objects | graph |
workspace.json |
the pulpit | canvas |
pulpit/ |
the pictures of the pulpit and its own looks of its elements (File formats, 7.2) | canvas |
settings.json |
the settings of the process | settings |
state.json |
the values of variables marked «remember», except lists, and the generations of remembered lists | graph |
kept/<id>/ |
the elements of a remembered list: the whole as one document and each added or changed element as its own (File formats, 5.2) | graph |
modules.lock.json |
the exact versions of the modules of the process | modules |
generation.json |
the generation number of the documents of the graph | graph |
.venv |
the Python environment of the process; beside it the merged requirements.txt |
modules |
state/<name>/ |
the memory of a node — its own, named by its identifier, or shared by several nodes of one module: the intermediate state the module of the node saves before unloading (section 13) | the module of the node |
inputs/ |
files the person gave the process; a path to a file outside the process folder is allowed too, and the platform does not copy it | the person |
files/ |
files held by constants and variables: a picture, sound, video, file, data in the window (File formats, 16.3) | graph |
runs/<run_id>/ |
a run: the checkpoint, the journal, the work folder (12.6) | graph, observe, modules |
results/<run_id>/ |
the result files of a run under the names the modules gave them (12.6) | modules |
A process has one Python environment for all its modules (Modules, section 5). Everything that belongs to the process lies in this folder, and deleting the folder takes the process away whole (The make-up of the platform, 3.8.1).
process_id comes from the name of the process, and the folder of the process is named by it: Latin letters and digits are turned to lower case, Russian letters are written in Latin letters — as a name is written in a passport, without signs above letters (ж — zh, х — kh, ц — ts, щ — shch, ю — yu, я — ya; ъ and ь are not written) — everything else — spaces, signs, letters of other alphabets — becomes one hyphen, the length is cut to 64 signs: «Раскладка на лист» lies in raskladka-na-list. If there are no letters and digits in the name, the id is process. A taken id gets the suffix -2, -3 and on. The id is given to a process once, at making, taking in and handing over: a new name of a program does not move its folder — by the id a program is named by the calls of other programs, the panels of the library and the layout of windows.
2 The program
- Switching on. The function of the graph checks the graph, the generation of documents and the versions of modules by
modules.lock.json, as before a run. Variables get their initial values, and variables marked «remember» — their last value fromstate.json(section 7). Service modules come up (section 13). The events «on switching on» fire, timers begin counting. - A program that is on waits for events. Each event causes a run (section 12).
- Switching off. New events are not taken, going runs are stopped (12.3), open questions are closed without an answer, the modules of the program are unloaded (section 13). The «remember» values are already written: they are written at each change.
- The number of programs. The number of programs on at once is not limited; one program is switched on no more than once.
- Changing. A program that is on does not change: saving it is refused (
process_running). A program is switched off, changed and switched on again. Other programs change without limits (Getting started, 7). - After a restart of the platform all programs are off and are switched on by the person. Starting the platform with the shortcut of a program is an action of the person: it switches this program on (The workshop, 6.6).
- A long case — an order, a request, an approval that stretch over days — is a record, not a run. A case lies as a record with its stage in a remembered list (section 7, writing by key), and the runs are short — «the payment came», «the machine is free», «the term ran out»: each reads the record, makes a step and writes the new stage. A run is for work of minutes and hours. Editing a program and restarting the platform do not lose cases: cases lie in a variable, not in runs. There are no versions of the graph for going runs.
3 The rules of the graph
- Data go only along links between named ports.
- A run begins with an event (section 5); a run has no other beginnings.
- Only the body of a frame repeats (section 11). An arrow back bypassing a frame is not taken.
- Several sources at one hot input need an explicitly chosen rule that takes one of the packets: the input rule of the node
any(10.3) or the mergefirst(10.4). Withallthe second packet of the same round would never be taken, so the check and saving refuse such a graph (graph_invalid) and name each such input. A module node without its own rule is judged by the rule of the manifest. Cold links and the returns of a body into its frame do not count as sources here. - A shared object is made once: the graph keeps its meaning, the pulpit only shows it (section 16).
- Repeating a step with an outside action does not count as safe unless the module declared the step idempotent or the person confirmed the repetition.
- A new run does not get the result of a past run by itself. Everything kept between runs is kept in variables (section 7).
- A kind of node is added only if it is a primitive: it cannot be built of the other kinds, or an indivisibility is needed that a composite construction would not have. The rest the person builds as a subgraph (section 18) without changing the platform. A shorthand for readability is allowed if it brings no new meaning; such a kind is named a shorthand in section 4.
- An element that takes work beyond the border of the graph — a module node, a question to the person, an event by the clock, a pause, a file, a call of a process, an element of the pulpit and every new such element — answers five questions: what its term is, what happens on a failure, whether it repeats, how it is cancelled, and whose is what it left. A new border without an answer to any of the five is not added to the platform.
4 Nodes
Which kinds of nodes there are, which of them are primitives and which shorthands, their ports and how many they are — the catalogue of the node system (Kinds of nodes, sections 2, 3, 8 and Appendix A); the catalogue points to the sections of this document where the meaning of each kind is described.
The ports of nodes of the platform are set by their kind; a link can go only through them. A node whose input rule is any or the merge first may give the packets not chosen through the output overflow (10.3). A link through a port the node does not have would never carry a packet, so process.validate and process.save refuse it (graph_invalid) and name all such links at once; for a module node the ports are checked against the manifest. Ports are not copied into the document of a node.
The ports of a module node are the inputs and outputs of its manifest and, beyond them, the output failed, which the graph gives (12.4); the parameters — by the schema of the manifest (Modules, section 10). The node keeps only its own values, the description of the module is not copied into it (File formats, section 5).
The types of ports. The ports of a module have a type in the manifest — a kind of data from the one list (section 24; the names — File formats, 16.1). A link joins ports only if the types fit each other: they are the same, one of them is json, or a picture, sound and video go to a port file — a picture is a file too; a file does not go to a port of a picture: what is in it is not known. json takes and gives anything. Nodes of the platform have no manifest, their ports count as json. A port whose type the manifest did not declare is not checked. Types that do not fit are the error graph_invalid at the check and when the process is saved, with a list of all such links.
Nodes inside a frame are marked by the field region — the id of the frame. Frames nest in one another. A link goes across the border of a frame only so: from outside — to an input of the frame and from an output of the frame; inside — from an inner output of the frame to its nodes and from its nodes to the inner inputs of the frame. A cold link (section 6) from a variable or a constant may go across the border anywhere. There is no event inside a frame.
What is a primitive here and what a shorthand (rule 8 of section 3). A primitive kind carries an operation that cannot be built of the other kinds or that needs indivisibility; so «First time?» tests and remembers in one action, and merge joins two different primitives — meeting-AND and meeting-OR (10.4). Shorthands are built of primitives, and they are kept on purpose:
- «Choice» — a chain of equality tests with named outputs. It brings no new meaning and is kept for readability: a chain of five conditions on the canvas reads worse than one node with five outputs;
- a call of a process — a macro: it is unfolded into the graph before the run and does not exist in the engine (section 18); a call of a process also serves as the mechanism of building everything that is not a primitive;
- «Constant» — the value of a parameter given by a link; needed where a value is given as a cold input;
- «Result» — the second half of a call: together with the process node it disappears on unfolding (section 18);
- «Switch» — a choice whose subject of comparison is not the packet but a variable (section 8). A separate kind is kept because the canvas must show that the fork here is by state, not by data.
The name of a node is the words on its head (the field name, File formats, 5.1). While the person has not given one of their own, the node is called by default: the kind in words, for a module node — the name of the module, for a call of a process — the name of the called process. The name is plain text, like the name of a group (section 17): it has no translation and does not serve as an identifier — in links, the journal and events the node is called by its id.
The description of a node is the person's words about what the node does and why it is here (the field description, File formats, 5.1). A group has the same kind of description: a built node of the second or third level without it is a box whose purpose has to be guessed. A description is plain text of several lines; it does not affect a run, and nothing in the platform reads it — it is said to whoever looks at the graph. When a group is made a process (17.1), its description goes to the call node that took its place.
A group is not a kind of node but a layer over nodes (section 17).
5 Events
An event begins a run. The source of an event is set by the parameter source of the node event (table 2).
Table 2 — Sources of events
source |
When it fires | Packet |
|---|---|---|
start |
once, when the program was switched on | {} |
pult |
a button of the pulpit bound to the event (section 16) | the values of the variables the button carries with it, each under the field name named in the button: { "<field name>": value, … }; for a button without variables — {} |
timer |
every interval_ms while the program is on; no more often than once in graph.timer_min_ms |
{ "time": "<ISO 8601>" } |
schedule |
at at — a time of the form 08:00 by the clock of this machine; every day, unless narrowed to days of the week or dates of the month |
{ "time": "<ISO 8601>" } |
call |
the process was called by a node process from another process (section 18) |
the packet that came to the input of this node |
message |
another program — or this one — sent a message with the name name by a «Send» node (section 23), or the person sent it with the method process.message.send |
the data sent, as they are |
change |
the person wrote the variable named by the parameter variable: with an element of the pulpit, a field on a node, the method process.variable.set, or returned it to its initial value (process.variable.reset, the pulpit button «Return as it was»); writing by a «Write» node does not cause it |
{ "value": the new value } |
An event by the clock. The source schedule fires at the named hour: the parameter at is a time of day of the form 08:00 by the local time of the machine the platform works on. The platform does not keep the time zone and daylight saving time but gets them from the system. These rules hold:
- when the program is switched on, the schedule is aimed at the nearest future moment; a moment that passed today before switching on is not carried out: switching a program on is no ground to carry out what was missed;
- a moment that passed while the program was on and the machine slept or WSL was suspended is carried out once at the first waking;
- several missed in a row give one run, not one for each: the work is done once, not by the number of misses;
- the system clock set forward past the named moment gives one run, like a miss; set back past a moment already carried out, it does not repeat it: the next moment is counted from the last firing. A change of the time zone and daylight saving time the schedule takes at once: a moment is counted by the local time of the day on which it comes.
Days of firing. By default the event fires daily. The days are limited by two optional parameters (table 3).
Table 3 — Days of firing of an event by the clock
| Parameter | What it means |
|---|---|
weekdays |
days of the week as numbers: 1 — Monday … 7 — Sunday (ISO 8601). [1,2,3,4,5] — on weekdays |
month_days |
dates of the month, 1–31, and last — the last date of the month. [1,15] — on the first and the fifteenth, ["last"] — on the last day of the month |
- an empty list and a missing parameter mean the same: every day;
- both parameters at once do not pass the document. The condition «on Mondays and on the first» can be read two ways («and», «or»); in the schedules of other products this is a known source of errors. For both meanings two events are used;
- a date that the month does not have does not happen:
31in April does not come and is not moved anywhere. The last date of the month is called by the wordlast: in April it is the 30th, in February the 28th or 29th;lastand30in a month of thirty days give one run, not two; - the day of the week and the date of the month are counted by the same local clock as
at.
An event by the clock does not pass the document (graph_invalid) if at cannot be parsed, if a day of the week is outside 1–7, if a date of the month is outside 1–31 and not last, or if both lists are named at once.
The time of a timer is counted while the program works. The interval is counted by the work of the platform, not by the wall clock: while the instance is stopped, Windows sleeps or WSL is suspended, the interval does not pile up, and after waking the timer goes on counting from where it stopped. So there are no missed terms: a timer sets an interval, not a term. A schedule by the clock is set by another source — schedule — and is counted by real time.
A service module can also begin a run, giving a packet at its output by itself — for example, «a file appeared in the folder» (Modules, section 4). Such a packet goes along the links of the output as the beginning of a new run.
The overlap rule is the parameter overlap of an event: the behaviour when an event comes while the past run from it is still going.
queue— the default: the new run waits for the past one to end. No more thangraph.queue_depthevents may wait; the extra one is refused with the recordevent.droppedin the journal (Exchange with the kernel, section 7), not lost without a message. The waiting ones begin one at a time and in the order they came: an event is busy while its run goes, and the end of the run at once hands the queue to the next waiting one; what comes at that moment waits behind them and does not begin earlier. Being busy and the queue are one state of the program.skip— the new run does not begin at all while the past one goes; the refusal is written asevent.droppedwith the causeskip(Exchange with the kernel, section 7). Unlike an overflow of the queue, where the event waited and did not fit, here the event is on purpose not put into the queue.replace— the past run is stopped (12.3), a new one begins.parallel— the runs go at the same time.
For a service module that gives packets itself, the overlap rule is the field overlap of its node, like retry and memory, not a parameter of the module.
An event of change (change) names a variable with the parameter variable — a variable of this process; without it the document does not pass (graph_invalid). Each writing by the person begins a run of each event that watches this variable, by its overlap rule — so a program answers input without a button. Writing by a node does not cause the event: a branch that writes what it watches would otherwise start itself endlessly, and what the graph wrote the graph knows anyway. Started by hand without typed data, the event gets the value the variable holds now.
An event by a message (message) names its name with the parameter name — an identifier under which a message is sent. A message begins a run of each event of the program with this name, each by its overlap rule; a program without such an event takes no messages. A program that is off neither takes nor stores messages: the sender learns of it at once (section 23).
6 Hot and cold inputs
- Hot — a packet along an ordinary link. It stands in the queue of the input and may start the node.
- Cold — a link from a node
variableorconstant. The node does not wait for it: at the moment of its work it takes the current value of the variable or constant. Picking parts on the link (parts, 10.1) acts here too: the node gets only the named fields of the value. - A node with a cold input gets an object by the names of its inputs: the hot packet under the name of its input, the values under the names of theirs.
- One input holds either hot links or one cold one; mixing is not allowed (
graph_invalid). - A node without a single hot input will never fire. The document is not refused because of it — the node is not carried out — but
process.validatenames such a node with the warningno_hot_input, and the workshop marks it. The exceptions areevent,variableandconstant: they have no inputs. - A parameter of a node from a variable. The value of a parameter
{ "variable": "<id>" }is read at the moment of the step. So the pulpit changes a parameter of a module: an element of the pulpit writes the variable, the node takes its value.
An example: a generating node is started by a new message from a chat (a hot input), and takes the style that is now chosen on the pulpit (a cold input from the variable «style»).
7 Variables
- Declaration — the array
variablesinprocess.json:id,type— a kind of data from the one list (section 24):text,number,boolean,time,list,image,audio,video,fileorjson, the initial valueinitialand the markremember. A variable of a picture, sound, video and file begins without a file (null) and gets one at work — from the pulpit, from a module, by writing; its initial value need not be typed. - Lifetime. The variables of a program live while the program is on. At switching on each gets
initial, and a variable withremember: true— the last written value fromstate.json. What is written into such a variable goes to disk at once: an element of a remembered list — as its own document, the rest — intostate.json(File formats, 5.2). - A variable of a run — the mark
scope: "run"in the declaration. Each run has its own value of such a variable: a run begins it frominitial, what one run wrote the other runs do not see, and the value goes with the run. So two runs going at once do not wipe out what each refers to, and folding a list piles a value up over the rounds of a frame in its own run. A variable of a run has no mark «remember». A variable without the mark is a variable of the program, common to all runs. How it is made: what the variables of a run hold is written into each checkpoint of the run, and resuming after a failure begins from that (File formats, section 11); the rounds of a frame of one run share one value, so withparallelgreater than one the writes of the rounds go in the order they came; a variable of a run has no single value for the program — it is not on the pulpit, the person does not write it (process.variable.setrefuses withgraph_invalid), the eventchangedoes not listen to it, and a variable node on the canvas shows its initial value. - Sources of writing: the node
writeand the pulpit — an input element bound to the variable (section 16). - The value written is set by the parameter
path: a field of the packet by the rules of section 8, for exampletext; withoutpaththe whole packet is written. A field missing from the packet is an error of the node. - The way of writing is set by the parameter
mode:replacereplaces the value;appendadds an element to a list, a line to a text; for a variable of another type the check of the graph refusesappend;resetreturns to the variable its initial value —initial, and without it the empty value of its type, as at switching on: the packet is not read,pathis not needed. So a variable is cleared at the beginning of a run without a constant that would hold an empty value.spreadwrites several variables at once, named by the listvariables: each gets the field of the packet with its own name — or the field of the recordpath, when it is set — asreplacewould write it. All fields are found before writing: if one is missing, no variable is written, and it is an error of the node. So a record from a file or from a module goes into dozens of variables with one node, not a chain of writes; the check of the graph compares the variables of the list with the declared ones, and renaming a variable reaches it too. - Writing into a list by key:
putandremove— for a list of records, with the parameterkey— the name of a field of a record.putfinds the record whose field equals the field of the one being written and merges them: the fields of the written one replace the former ones, the rest stay; if there is no such record, the written one is added at the end.removeremoves all records with the same value of the field. A written value without the fieldkeyor not a record is an error of the node;putandremoveat a variable that is not a list, and at a node withoutkey, are an error of the check of the graph. So one record of a list — a message, a chat — is changed and removed without rewriting the whole list. What is written may also be a list of records — a batch: each record of the batch merges with its own or is added in the order of the batch,removeremoves all named ones; one record of the batch without the fieldkeyis an error of the node, and nothing is written. So a folder of thousands of pictures goes into a list as one write, not as a thousand steps of «For each». - Keeping the type. A value of another type is not written into a variable — neither by a node nor by the pulpit (
graph_invalid). A variable for values of any kind is declared of typejson. - Order. The writes of one variable go one after another; the last written value is kept. Each write publishes the event
variable.changed, and the pulpit shows the new value at once; a value larger than 16 KiB the event does not carry but only says it is large, and the window reads it itself — once per series of such events: otherwise a list of thousands of records written one at a time would go whole into the bus and the journal at each write. The window's writes into one variable go to the function of the graph in the order the person made them: the next one — when the previous one has been answered. While they are on the way,variable.changedabout this variable is the echo of former ones, and the window does not show it instead of what is typed; if the program wrote the variable meanwhile, the window shows its value when the last write has been answered. - The name. The
idof a variable is the name by which nodes, the faces of groups, the pulpit and the remembered value find it. Renaming is one action, not an edit of a field: the function of the graph changes the name in the declaration and everywhere the document names it — in a node whose kind names a variable, in a parameter taken from it ({ "variable": … }), in the lists of variables on the faces of groups — and writes into the document what the object of the pulpit became (renamed_objects, File formats, section 5). By this record the pulpit finds the variable under its new name, and the program at switching on — the value remembered under the former. A name that is not an identifier or that another variable already has is not given. A call in another process that gives a value to the variable under its former name (section 18) cannot follow the name — it is another document: it is named, and a run of such a call refuses naming the variable. - Parts of a result. While a module gives its result in parts (section 13),
writeshows in the variable what has piled up: a text is glued together, the elements of a list pile up. The final packet puts the final value; in the modeappendan unfinished element is replaced by the final one. - The checkpoint does not roll variables back: what a run wrote stays, even if the run then ended with a refusal.
8 Condition, choice and switch
A condition runs no arbitrary code. The test is chosen from a closed set:
{
"op": "exists | not_empty | equals | not_equals | greater | less | contains | has_part",
"path": "optional.dotted.path",
"value": "for equals, not_equals, greater, less, before, after",
"part": "the name of a part for has_part"
}
path— steps through dots, without filters and expressions. A step in an object is the name of a field, an identifier[A-Za-z_][A-Za-z0-9_]*. A step in a list is the number of an element, beginning with 1, as «For each» numbers its rounds (11.2), orlast— the last element:defects.last.box,rows.2. In an objectlastis an ordinary field name, and a number is not a name, and such a path finds nothing. A number beyond the end of a list,0andlastof an empty list find nothing either;- the value is taken from the data of the packet; if the packet has
parts, the path is looked for first inparts, then in the root; exists— the path is there and the value is notnull;not_empty— the value is there and it is notnull,"",[],{};equals/not_equals— equality of JSON values;greater/less— only numbers; otherwise the node errorgraph_condition_type;before/after— earlier and later: both values are ISO 8601 time (the kindtime, File formats, 16.1), a time without a zone — by the clock of this machine; a field with no time in it — the term is not set — is neither earlier nor later than anything; comparing with something that is not a time is the node errorgraph_condition_type;contains— a text contains the textvalue, capital and small letters alike; a list contains an element equal tovalue. For a value of another kind the test does not pass — the outputfalse, not an error;- a test value from the packet —
"value": { "packet": "<path>" }: a field of the packet that came to the node. For a test of the packet itself it is the same asother_path; for a test of a variable and for «Pick» (section 26) — a comparison with what came; has_part—partshas the keypart;other_path— a path to a second field of the same subject: a field is compared with a field. It is not set together withvalue(graph_invalid);- the outputs are only
trueandfalse; the data of the packet do not change.
The subject of a fork. By default a fork tests the packet. The optional field variable of the node changes the subject: if the field is set, the condition and the choice test the value of this variable, and path looks for the path inside it. The packet does not change and is passed on in its original form: the fork answers about the packet and does not change it. With no variable with such an id the document does not pass the check (graph_invalid).
A fork is one primitive with two axes: the subject of the test (the packet or a variable) and the number of outputs (two or many). A condition — two outputs, a choice — many, a switch — the same as a choice by a variable, kept as a separate kind for readability (section 4).
Writing logical signs. The set of tests is closed on purpose; some signs are missing from it, since they are expressed by the make-up of the graph, not by a new test (table 4).
Table 4 — Writing logical signs
| Sign | How it is written |
|---|---|
«not» (¬) |
the second output of the same node: a test always has two ends, true and false |
«greater or equal» (≥) |
the output false of the test «less»; «less or equal» — false of «greater» |
«and» (∧) |
a chain: the output true of the first test goes to the input of the second |
«or» (∨) |
a chain by false, and both outputs true meet in a merge with the rule first; exactly one branch fires, because both come from one test by different outputs |
| comparison with a value set on the pulpit | the test value can be a variable: "value": { "variable": "<id>" }. It is read at the moment of the step (section 6), so the threshold is changed on the pulpit, and the document does not change |
| comparison of two fields of one packet | other_path of the test |
A choice is a fork into many outputs. Parameters: path — by the same rules, cases — a list { "port": "<name of the output>", "value": <JSON> }. The packet goes to the output of the first case whose value equals the value by path, and if there is none — to other. The name of an output is an identifier; two cases with one value or one name are refused by the check. The data of the packet do not change.
A switch is the same fork as a choice, but the value is taken not from the packet but from a variable: variable — its id, cases — the same { "port": …, "value": … } as for a choice, and the same rules for names and values. The packet goes to the output of the first matching case, otherwise to other, and the data of the packet do not change.
A separate kind beside the choice is justified by the value of a switch being set by the person on the pulpit before the run, and the graph must show that the fork goes by state, not by data. The condition and the choice read only the packet, and the switch reads a variable and shows this plainly by its kind.
The same is expressed without a switch too (a variable as a cold input into a merge, then a condition; 6, 10.4), but a merge wraps the packet into an object: further along the branch { "<input>": packet, "<input>": value } is passed, and all the paths below shift. A switch does not wrap the packet.
The value of the variable is read at the moment of the step, as for a cold input (section 6). The path inside the value is set by path: for a variable of type json a fork by its field is allowed. With no variable with such an id the document does not pass the check (graph_invalid).
Adding a test to the set means a new version of the format ogas.process/*.
9 A question to the person
- A packet at the input
questionstops this branch of the run and opens a question. The pulpit and the node itself on the canvas show the data of the packet and wait for an answer (ask:<node_id>, section 16; 24.2). The other branches of the run go on working. - The parameter
kind:text— an answer as a line; it has the parameterfill— a path to a field of the data of the question (section 8) whose text already stands in the answer field: the person edits the shown text rather than typing it anew;choice— one ofchoices;confirm— yes or no;number— a number;area— an area the person draws by hand on the picture of the question:{ "x", "y", "width", "height" }in shares of the side of the picture, the area inside it, width and height greater than zero; the picture is the one in the data of the question, in a record inside a record too;points— points the person puts on the same picture: a list{ "x", "y", "keep" },xandyin shares of the side,keep— take what is under the point or not; at least one point «take», no more than two hundred points;mask— with a brush: the person paints over what is needed on the same picture, the answer is a reference to a picture of the same size, white where painted and black elsewhere;image,audio,video,file— a reference to a file of this kind (File formats, 16.2). An answer of another kind is refused. - The answer goes to
answertogether with the question:{ "question": <the data of the packet>, "answer": <the answer> }. «No» forconfirmand a refusal to answer go todeclined. When the termtimeout_msruns out (0— no term), the packet goes totimeout. - The term of the answer is the parameter
timeout_ms. When the term runs out, the packet goes to the outputtimeout, and the question closes. The value0means waiting without a term. There are two such places in the platform — a question and waiting for a signal (section 22) — and these are the only two places where a run may not end: all other waits have a term by their make-up. - An open question is written in the checkpoint: after a failure and a resume it is open again, and the term of the answer is counted anew — while the platform did not work, the person did not see the question. A question that cannot be opened again makes the resume a refusal rather than losing its branch; the same for waiting for a signal (section 22).
- Stopping a run and switching the program off close a question without an answer. A question that lost to another branch at a merge
firstcloses too (10.3).
10 Packets and the readiness of a node
10.1 Packets and queues
A packet holds packet_id, run_id, iteration_id, the source, the port, parts and artifacts. Big data are passed as result files (artifacts): the packet holds only the reference artifact_handle — the name of the file, and the file itself lies in the results of the run results/<run_id>/ of the process folder.
The references to artifacts go on together with the packet: nodes of the platform pass them on as they are, and a module gets them in artifact_handles together with the paths to their files (Modules, section 6) and decides itself which to return at its output. A module node also names to the module the input the data came to.
Each input port has a queue of limited length graph.queue_depth: packets are handled in the order they come. A packet leaves the queue only after the step is successfully written. On an error the packet stays for an allowed retry. An overflow of a queue is the error graph_queue_overflow, not a silent loss.
The queue is limited on purpose: with an unlimited queue one cannot answer whether a program has enough memory. Limiting it is safe because a packet cannot circulate, growing the queue: an arrow back bypassing a frame is not taken (section 3, rule 3), and the rounds inside a frame are limited by max_iterations and max_loop_ms (11.1). These two rules are tied: allowing rings outside a frame would need either taking the limit off the queue or allowing a deadlock.
10.2 Fan-out
One link passes a packet to one receiver. Several links from one output are a fan-out: all receivers see one and the same packet_id, each delivery has its own delivery_id. The receivers have no shared changeable data. A packet at an output without links is not passed: the checkpoint of the run writes it into outputs with the code packet_absorbed, and the function of the graph publishes the event packet.absorbed — so the journal of the run says why a result came nowhere.
10.3 The readiness of a node
The readiness of a node to work is set by its input rule — the field input_policy of the node, and for a module node without a rule of its own — the input_policy of its manifest. The rule concerns only hot inputs; the node does not wait for cold ones (section 6).
all— one fitting packet at each required hot input;any— the first packet from any hot input.
With any the node takes the packet delivered first, whichever input it came from. The other packets of the same round — of the same origin (packet_id) and the same round of a frame — are not chosen, whether they waited in the queue or came later. If the node has a link from the output overflow, the packets not chosen go along it; otherwise they are dropped, and the checkpoint writes each into dropped with the cause unselected (File formats, section 11). They do not pile up in the queue.
Losing waits are taken off. A packet of the same round may not yet have reached the node but be waiting on the way to it — for a question to the person (section 9), a signal (section 22) or a moment (section 19). When a node with the rule any or a merge first has taken a packet of the round, such a wait is taken off if all roads from it lead only to this node: the question closes and leaves the pulpit, the wait for a signal and a pause are taken off. The checkpoint writes each wait taken off into dropped with the cause withdrawn and the node that decided (decided_by). So a deferred choice is expressed — «what is earlier: the person answers, a signal comes or the term runs out» — and the run does not hang on a wait that is no longer needed. A wait from which there is a road elsewhere too stays: its branch has work of its own.
With all the packets are matched into a set by join_key, if it is set on the node or the links. Without join_key one packet is taken from each input in the order of arrival, and with sources working at the same time such a result does not count as reproducible.
10.4 Fan-in and join_key
A merge node explicitly chooses a rule:
all— waits for a packet at each input and gives an object{ "<input>": data, ... }— with one input too, so a merge with one input gives the packet a name; packets are matched byjoin_key; the files of all gathered packets go on together, in the order of the inputs, each once;first— the first packet delivered; which comes first is not known in advance. The other packets of the same round — as for the ruleany(10.3). A mergefirstchooses either between the branches of a fork (section 8) or between waits — who answers first, gets a signal or the time; if neither is on the paths to its inputs, the first to come is whoever was queued earlier, and that is chance, not the meaning of the program. About such a merge the check of the graph warns —first_by_chance, as a warning, not a refusal. A wait here is a question to the person, waiting for a signal, a pause, a step of a module, a call of a process, and the beginnings of different runs — events; the common path up to the place where the inputs part does not count: after a common step of a module two instant paths come at once.shape: "record"with the ruleall— as one record: the data of the inputs that are a record are put together field by field into one object, data that are not a record go under the name of their input. A field that came from two inputs is the node errorgraph_invalidwith the names of the inputs: which of the two to take, the person decides by renaming the field with a «Take a field» node. So a message record{ id, time, from, text }is gathered from what is typed, a stamp and a constant, not a nested object by inputs.
join_key is a path through dots by the rules of section 8; the values are compared as JSON. A packet without the key stays in the queue, on an overflow — graph_queue_overflow. Packets with different keys do not get into one set.
There is no rule «the last value» at a merge: a cold input from a variable serves for that (section 6).
A node that will never fire. Two paths from one fork (section 8) do not go together, so a node waiting for packets from both will not fire: the packet will stay in the queue until the end of the run. The check of the graph names such a node with the warning never_fires — before the run. A run that ended with packets in queues writes each of them into the checkpoint with the cause stalled (File formats, section 11): the outcome of the run is completed, since there is nothing more to do, but the unfinished work is shown.
11 Frames
A frame is a node inside which other nodes are placed on the canvas (the body of the frame) (nodes with region = the id of the frame). Packets of different rounds do not mix: the queues of the body are divided by (node, port, iteration_id).
Frames nest in one another. A packet that entered a frame from a round of another frame carries in the body its own packet_id: the former one and the number of that round after «/», for example packet-1/2; leaving through done, the packet gets back its former packet_id and round number. Without this a step of an inner frame in the second round of the outer one would carry the same operation_id as in the first and would count as already done, and the result of the inner frame would return into the wrong round.
11.1 «Repeat» (repeat)
- A packet at
inputbegins the first round:iteration_id = 1, the packet goes into the body throughbody. - The body returns the packet to
again— the next round begins with this packet. So a refined value is carried to the next round: for example, a corrected request to a generating module. - The body returns the packet to
finish— the frame leaves throughdonewith this packet. - Parameters:
max_iterations,max_loop_ms,on_limit(fail— the default, orstop_with_result). Going over the number of rounds or the time is the errorgraph_iteration_limit; with"on_limit": "stop_with_result"a frame that spentmax_iterationsleaves throughdonewith the last packet. The time is counted from the first entry of the packet into the body. The smaller of the limits of the node and the settingsgraph.max_iterations/graph.max_loop_msholds. - A frame without nodes in its body or without a link to
againorfinishis not taken. - The time
max_loop_msis checked when the body returns the packet toagain: a body that returned the packet tofinishafter the limit takes the frame out without an error, and there is no timer that would break off a waiting body. - A round whose packet ended in the body and returned neither to
againnor tofinish— for example, went into an output without links — stops the run with the errorgraph_iteration_limitwith the node of the frame as soon as the run has nothing more to do: there is nobody to wait for such a round. The check of the graph warns in advance withframe_may_not_returnabout an output of a node of the body without links, if the packet reaches this node without a fan-out; after a fan-out another copy of the packet may return, and a branch that ends on purpose does not count as a warning.
An example: generation, checking the result, the condition «does it match?»: «no» — a module refining the request and again; «yes» — finish; the limit — three rounds.
11.2 «For each» (for_each)
- A packet at
listcarries a list: by the parameterpath(by the rules of section 8) or the data of the packet itself. Each element goes into the body throughitemas a separate packet withiteration_id= the number of the element, beginning with 1. - The body returns the result of an element to
result. When the results of all elements have returned, the frame gives throughdonethe list of results in the order of the elements. A branch of an element that ended beforeresultgivesnullin its place. The result of one element is taken from the list by a path: by the number of the element, or the last — with the steplast(section 8). - Files go with the elements. Each element goes into the body with the result files that came with the list (10.1): a module in the body finds its own among them. The list of results leaves through
donewith the files of all results — in the order of the elements, each once. - The parameter
parallel— the number of elements handled at once, 1 by default. The number of elements is limited bygraph.max_iterations: a longer list the frame does not take — the errorgraph_iteration_limit, and no element goes into the body. - An empty list at once gives an empty list at
done. - A frame without nodes in its body or without a link to
resultis not taken (graph_invalid), like the frame «Repeat» (11.1). A branch of an element may end beforeresult, but the body has a road to it: a frame into which nothing returns is almost always a forgotten link.
12 The run
12.1 Beginning
A run begins with an event (section 5) of a program that is on. Starting the whole process uses the named pulpit event, and without a name — the only event links go from: an event without links begins no work. To check a part of the graph one node or group may be started (target = group | node) with what is typed in the fields of events (24.5); such a start is a run too and also needs the program on.
A start returns run_id, attempt_id and trace_id at once; the work goes on, its course is reported by the events run.changed and node.changed. A run knows which event began it (event_node).
12.2 Runs at once and branches
- The runs of different programs go at the same time.
- The runs of one program go by the overlap rules of their events (section 5).
- The branches of one run go at the same time. A step of a module goes beside the run: while the module works, the run takes the steps of other branches, ends pauses, takes answers and signals. So the branches of a fan-out (10.2) do not wait for each other, and the elements of «For each» with
parallelgreater than one are handled truly at the same time (11.2). - What came first goes on first. Before taking the next node, the run takes in what came while it was busy: the end of a step, an answer, the end of a pause, a signal. A node that takes the first packet of a round (10.3) takes the packet that truly came first: a pause of 100 ms gets ahead of a module step of 800 ms in a neighbouring branch.
- The calls of one module go one after another — from its different nodes and from different runs of the program — unless the manifest allows more with the field
parallelism(Modules, section 4). A long module is up as one copy per run, a service per program, and two steps at once would talk to one copy over each other. So one node is not carried out twice at once either.
12.3 Stopping
Stopping a run sends cancel to each step of a module going in it and waits for their end. A step module and a long one the function of modules then stops as an OS process (core.process.stop), waits core.process_stop_grace_ms and, if the process did not end, ends it by force. A service module goes on working: only the step of this run is cancelled at it. Outside actions already done are not rolled back.
A step broken off by a stop is not a refusal: whatever the module answers at that moment, the step is not repeated and does not go to failed. It is written into the checkpoint as broken off — with its input data, like a refused one (File formats, section 11). A stopped run gets the outcome cancelled; a stopped group ends only its branch, and the run goes on. The branches of the run waiting for a question, a signal or the end of a pause stay in the checkpoint, and the question to the person is closed: resuming the stopped run asks it again. Resuming carries out the broken-off step anew; the outcome of a broken-off step is unknown, so for a module non_idempotent or unknown resuming needs confirmation, as after a failure (section 14).
A stopped group takes off its waits. The steps of the modules of its nodes are broken off; branches waiting in it for a question, a signal or the end of a pause are taken off — the question leaves the pulpit; packets that came to its nodes go no further. The checkpoint writes each wait taken off and each such packet into dropped with the cause stopped (File formats, section 11). Otherwise the run would wait for what the group will no longer do, and a question without a term in a stopped group would never let the run end.
12.4 Errors and retry
Only a step with an error the module marked as repeatable is repeated automatically, and no more than max_attempts times. The rules of retry are the field retry of the node, and if it is not there — the retry of the manifest of the module; the graph does not read the parameters of the module for this. The pause between attempts is delay_ms, and with exponential: true it doubles after each attempt. The other errors wait for the person's decision. The branch after a refused node gets no result. A failed run does not switch the program off.
The output failed. Each module node has, besides the ports of the manifest, the output failed: the graph gives it, not the module. A module node whose manifest declares such an output, or overflow, which the graph also gives itself (10.3), does not pass the check of the graph: the module is sound, but its own output would hide the output of the graph. A packet goes along it only if the person drew a link from it:
- there is a link — the refusal goes to
failed, and the run goes on along this branch. The node is marked as refused, and the record of the refusal in the journal stays: the branch handled the refusal, it did not cancel it; - there is no link — the run ends with a refusal, as described above.
The refusal goes to failed after the retries are spent: a non-repeatable error at once, a repeatable one when max_attempts is spent.
A refusal that ends a run breaks off the steps of other branches going at that time (12.2): cancel is sent to the modules, new steps do not begin. Each broken-off step is written into the checkpoint with its input data, and resuming will carry it out anew; a step that managed to end is written as done, and its result waits for the resume in the queue.
The packet of a refusal: { "code": "<error code>", "message": "<text>", "input": <the packet that came to the input> }. A refusal may have no code — then code is empty. input is needed so that the branch knows which of a hundred files caused the refusal.
In the checkpoint a caught step is written as done (status: completed, port: failed, and the refusal itself in the field caught), so resuming an interrupted run does not repeat it (File formats, section 11).
A call node (section 18) has no failed of its own: a refusal is handled where it arose — inside the called process.
12.5 The outcome of a run
The outcome is completed (done), failed (an error), cancelled (stopped by the person, by the rule replace or by switching the program off) or interrupted (broken off by a failure and found at the next start). While a run is being carried out, the outcome is running, also while waiting for the person's answer.
A run with the outcome failed names its cause: the code, the message and the node it stopped on — both in the last checkpoint and in the event run.changed (File formats, section 11). A run with the outcome cancelled names who stopped it (stopped_by): person — the person, replace — the overlap rule of its event, switched_off — switching the program off. If there were several stops, the first is named. A refusal outside a step names its cause too — an overflow of a queue, the limit of a frame, an error of a substitution: its node is the one the run stopped on, and for an overflow — the node whose queue is full.
12.6 Kept results of a run
Past runs do not pile up without limit. A run lies in the folder of its process: the checkpoint, the journal and the work folder — in runs/<run_id>/, the result files under their own names — in results/<run_id>/. The name of a run is the local date and time of its start, 2026-10-08_07-29-12; a second run in the same second — -2. Without clearing both folders grow without limit.
- Clearing after a run. When a run ends, the function of the graph leaves the last
graph.runs_keepruns of this process (20 by default) and deletes the older ones whole: the folder of the run and the folder of its results. Not deleted are: a run the person marked «Keep», a going run, and an interrupted run that can still be resumed. A run stopped by the person can be resumed too, but it is stopped on purpose and is deleted on common grounds.0switches clearing off. - Keep. A run can be marked so that clearing does not touch it, and the mark can be taken off. The mark is a separate document
keep.jsonbeside the checkpoint; the order of runs is taken from the time of the checkpoint, so marking an old run does not make it new. - Clear. The person deletes the past runs of a process or of all processes at once; the marked ones and the one going stay.
- Room taken. The list of runs shows the size of each together with its results and the sum, so that the decision to clear is made knowing the volume.
Files are deleted by the kernel at the request of the function of the graph (core.document.remove): the folder of a run and the folder of its results, but never the whole runs or results in one call. Methods — Methods of the graph.
13 Modules at work and unloading
How long the OS process of a module lives is declared by the manifest (Modules, section 4):
- step (
step) — one call; - long (
long) — within a run; unloaded after the run; - service (
service) — comes up when the program is switched on and works until it is switched off.
Unloading by resources. Memory and video memory are limited. A module declares how much video memory it needs (resources.gpu_mb). The function of modules keeps up the modules of all programs that are on while their sum fits into the setting modules.gpu_budget_mb; at 0 one module uses the video card at a time. When there is no room to start a module, the module that has not worked longest is unloaded. A module is not unloaded during a step: a new module waits for room to free up.
The memory of a node. A module in a run is one for all its nodes, and each node has its own memory: state/<node_id>/ of the process. How a module saves and gets it — Modules, section 4. From the side of the graph:
- a shared memory — several nodes of one module name one memory with the field
memoryof the node, for example the role «drawing» spread over nodes of different frames (a node belongs to one frame). A memory without a name is named by the identifier of the node, so a node may also name the own memory of another node; - a memory is named only by nodes of one module: a module will not read what another saved (
graph_invalid); - a memory lives while at least one node names it: after the process is saved, the function of the graph asks to clear a memory that no module node names — of a deleted node and of nodes that name another one;
- a call of one process from two places and an insertion give nodes with new identifiers and new names of a shared memory (sections 16, 18), so they have memories of their own too.
Parts of a result. A module may give the result of a step in parts — for example, the text of an answer as it is generated. The parts go along the links of its output but act only on nodes write (section 7): so the answer is shown on the pulpit as it forms. The other nodes wait for the final packet of the step.
14 The checkpoint and resuming
The checkpoint is runs/<run_id>/checkpoint.json in the process folder, its own for each run. The first is written before the first step, with the status running and the queues of the beginning: a failure at the very first step leaves a run the next start finds interrupted. The order: the result of a step and the outgoing deliveries are prepared, then the checkpoint is written whole, and only after that the event about the end of the step is published. The outcome of the run is written into the checkpoint at any ending, if writing is possible. Variables are not part of the checkpoint: they are kept in the program and in state.json (section 7).
A run that is not going is resumed. The checkpoint of a going run allows resuming — it is written in case of a failure — but while the run goes there is nothing to resume: begun from it a second time, beside itself, it would carry out steps twice. The function of the graph refuses such a request (run_active, Error codes), and the workshop does not offer «Go on» while it knows that the run is going.
A step of a module going beside the run (12.2) stands in the checkpoint in the queue of its node until it ends: a run broken off by a failure at this time will carry it out anew, and the outcome of the step counts as unknown, as for any step from queued (below).
The checkpoint also keeps the state of frames: for a frame «For each» — its list, the results already gathered and which elements were sent into the body (File formats, section 11). Without this resuming a frame would be impossible: the list comes as one packet and is not kept in queues.
After a failure run_id stays the same, a new attempt_id is made; a repeated step keeps its operation_id and gets a new execution_id. A step whose result was not written in time may be carried out once more. So whether resuming is allowed depends on what the module declared about its steps (execution_semantics):
idempotent— the step is safe to repeat, resuming is allowed by an ordinary command;non_idempotentandunknown— after a step with an unknown outcome they are not resumed automatically. After a failure the outcome of all steps of modules from thequeuedof the last checkpoint counts as unknown, after a stop — the outcome of the broken-off step until it is carried out anew (12.3), and after a refusal — the outcome of a step that ended without an answer of the module: its program closed or answered outside the protocol (module_process_lost), or the step did not fit into the term of the manifest (module_timeout). Such a step may have managed to do its work, an outside action too. A run with such a step is resumed only with an explicit confirmation of the personconfirm_non_idempotent; the checkpoint of a stopped or failed run says so itself —resume.allowed: falseand the causenon_idempotentorunknown.
A step the module refused without a retry has a known outcome — a refusal, and is not repeated by resuming, with confirmation or without: the cause in the checkpoint is refused, the answer to resuming is resume_not_allowed. To repeat a refused step there is the command «Retry» of its event (table 5). A step without an answer of the module is repeated by the same command; for a module non_idempotent and unknown the checkpoint says resume.allowed: false with the cause non_idempotent or unknown, and «Retry» and resuming need confirm_non_idempotent, while for idempotent the step is repeated by an ordinary command.
The function of the graph takes these facts from the manifest of the module (module.list). Resuming a run is allowed while its program is on; after a restart of the platform an interrupted run is resumed by a command of the person when the program is on again.
15 Substitution of parameters
In the parameters of a node a value of a setting of the process can be put in: the whole string of the parameter has the form ${name}, where name is [A-Za-z_][A-Za-z0-9_]* and such a key is in the settings of the process. There are no nested substitutions, filters or expressions. An unknown key is an error of the check of the graph, not an empty string.
The value of a parameter from a variable is { "variable": "<id>" } (section 6); an unknown variable is an error of the check too.
16 Shared objects
A shared object is what the graph gives the pulpit. The meaning of the object belongs to the graph; the pulpit only refers to the object_id, shows the object, changes it and calls it (The workshop, section 6). Objects are not kept as a separate list: they are set by the nodes and variables of the graph.
Table 5 — Shared objects
| Object | object_id |
What it is | What the pulpit does |
|---|---|---|---|
| event | event:<node_id> |
a node event with source: pult |
calls the event and passes the values of the variables the button carries (section 5) |
| stop | cancel:<node_id> |
the going runs of this event | stops them |
| retry | retry:<node_id> |
the last failed run of this event | repeats the failed step and goes on with the same run |
| state | status:<node_id> |
the last run of this event: going, the outcome, the error, what it is busy with now | shows |
| variable | var:<variable_id> |
a variable of the program | shows; an input element writes it; a container is shown or hidden by it, a tab is opened by it, and a tab switched by hand writes it (The workshop, 6.8) |
| question | ask:<node_id> |
the open question of a node ask |
shows the question and sends the answer |
| signal | signal:<node_id> |
the signal a node signal waits for |
with a button sends this signal and passes the values of the variables the button carries; which of the waiting branches it wakes, the node reads from them by its key (section 22) |
| showing | show:<node_id> |
the last thing a «Show» node showed (File formats, 16.5) | shows the same way as the node on the canvas |
| output | out:<node_id>/<output> |
the last thing an output of a node gave — for each output of each node | shows the same way as the node «Show» |
| input | in:<node_id>/<input> |
the last thing that came to an input of a node by any of its links, and for an input a variable gives — its value | shows it the same way as the node «Show» |
| link | edge:<edge_id> |
the last thing that went along a link: what its source gave, and for a link with a choice of parts (10.1) — only those parts | shows it the same way as the node «Show» |
| course of a step | progress:<node_id> |
how far the step of a module node has got, as the module reported (Modules, section 7) | shows as the share of a bar |
| program | program |
whether the program is on | shows; switches it on and off |
- A parameter of a module on the pulpit is a variable the node takes into the parameter (section 6). The workshop makes such a variable when the parameter is taken into the interface (The workshop, 6.4). An output and an input of a node give the interface what they gave and what came themselves: they need no variable.
- Between runs a showing element shows the value of its variable: the last written, and after the program is switched on — the initial or remembered one (section 7).
- What an output of a node gave the program remembers while it is on: the last value of each output of each node, cut the same way as what a «Show» node shows (the setting
graph.show_max_bytes). A program switched off forgets. So a view of the interface is attached to the output of any node without putting a «Show» node (The workshop, 6.8). - The data of a pulpit event are given by the button: the values of the variables named in its binding are taken at the moment of the press and go as the packet of the event. The name of a field of the packet is named by the button, by default it is the same as the name of the variable; renaming the variable does not change the names of the fields, and the nodes that read the field of the packet go on finding it. An input element only writes its variable and calls no events.
- The pulpit refers to the
object_idand does not copy the meaning of the object. The same objects are seen in the window of the process. process.validateandprocess.savecheck the bindings of the pulpit to the graph: an object that is not in the graph is a mark on the pulpit, not a refusal of saving (Getting started, 4).- Inserting another process into the current one adds nodes, links, groups and the variables the inserted nodes refer to, with new identifiers. The events and the pulpit of the source are not copied: the program has its own. This is done by
process.insert(Methods of the graph).
The state on the screen is taken from the checkpoints of runs (process.snapshot) and from the variables of the program. The colour of a node of the graph is set by the last record of executions or queued with its node_id. Looking at the state calls no modules.
17 The group
A group is a record in groups[]: group_id, a name, a list of nodes, a place on the canvas, a sign of being folded, a parent group and a face (17.1). A started group has queues of its own. Stopping a group stops its nodes and the nodes of the groups inside it; the neighbouring branches go on working.
The nesting mark. What is built consists of nodes of any depth mixed, and the number of times it was built says the wrong thing about it; so a node has no level, and what has something lying inside it — a group and a frame with a body — has a mark that it holds nesting. A node with nothing inside has no mark.
The state of a group is set by the states of its nodes: no node started — «standing»; at least one working — «working»; there is an error — «error»; some ended, some not started, no errors — «partly»; all ended — «done»; the group is stopped — «stopped».
17.1 A folded group
A group can be folded: then it is drawn as one node, and its content is hidden. An unfolded group is an area of the canvas with a body inside, like a frame (section 11).
The face is what is seen of a folded group. The person chooses it by marking what is needed at the nodes inside:
- a port of an inner node — becomes a row of the face with a name of its own; a link from outside comes to this row;
- a variable the nodes inside use — is shown on the face and edited in the panel of the chosen. On the node and on the face it stands as a row with a mark of what is done with it: read — a variable of input, written — a variable of output, or both. What a node of a kind does with a variable is said by the kind (Methods of the graph, section 1,
variable_use); a parameter taken from a variable reads it.
Rules:
- The face is declared, not derived. A mark is put by the person and does not change by itself while they work inside: otherwise the face of a block would change at each new link.
- A mark ahead. A node and a group of the top level have no face around them, and the mark is put ahead — for the face of the group that will be built of them: a node marks its ports and the variables it works with, a group — the rows of its face and the variables it shows. When what is marked ends up in a group — it was wrapped or put into it — the marks become its face, and the node or group itself has them no more.
- A link crosses the border of a group only through a marked port. A link to an unmarked port of a node inside would have no visible end at a folded group; the check of the graph refuses such a document (
graph_invalid) and names all such links at once. A link to a node of a group lying inside another crosses both borders, and the port is marked on the face of each. - The face shows what there is. A row of the face is a port of a node of this group or of a group inside it, and the node has such a port; a variable of the face is declared in the program. Otherwise —
graph_invalid. - A group does not own variables but shows them. A variable belongs to the program (section 7); the face is a short way to it, not a scope of its own.
- Nesting without a limit. A group may lie in a group, that one in a third. A folded group behaves like a node, and a working process may consist of nodes of different levels.
- Groups are a tree. A node lies directly in one group: it is named by the field
group_idof the node or the listnode_idsof the group, and if both are named — one and the same. A group lies in one group (group_id_parent) and does not lie in itself, directly or through others. - A group lies in one place of the canvas — at the top level or in the body of one frame — and takes a frame together with its body. A folded group is drawn as one node, and a node has one place: there is no place for a group across the border of a frame. A group in the body of a frame lies inside the groups that hold this frame.
-
Folding changes nothing in a run. For the engine there is no group: the nodes, links and queues are the same whether the group is folded or not. It is a way of showing, not of make-up (rule 8 of section 3).
-
The name of a row of the face is its own. By default it is carried over from the inner port, but the person changes it: inside «Input 1», outside «Picture». Two equal names on a face the platform numbers until the person sets their own.
What is built can be wrapped: the person marks nodes and puts them into a group in one action, rather than making the group empty beforehand. Nodes and groups of one level can be wrapped. The new group lies where they lay and is folded at once: what is built is first of all one compact node with what is needed on its face, and one looks inside by unfolding it. Its face at once shows each port through which a link crosses its border, and what the person marked ahead on what was wrapped — ports and variables; a variable nobody marked does not go on the face by itself. Then the person changes the face (Methods of the graph, section 1, process.group.wrap).
A node and a group are put into a ready group and taken out of it: only what they lie in changes, on the canvas — by carrying across a border (The workshop, 5.3). The face follows the links, as on wrapping: the rows of what was taken out leave the face of the group it was taken from, and a port through which a link now crosses the border of any group goes on its face with its own name; the rest the face showed stays — the person declared it. The marks ahead of what was put go on the face of the group it was put into. One can put only where what is put lies: in the body of the same frame or at the top level; a group — not into itself (process.group.move).
A group can be taken apart: what lay in it stays in place and lies where the group lay — in the group around it or at the top level — and the group itself disappears. The faces of the groups around do not change: the nodes are still inside them (process.group.unwrap). The face of a taken-apart group of the top level becomes marks ahead of what lay in it: a port of a node — a mark on the node, a port of a node from a group inside — a mark on the row of the face of that group, if it shows it, a variable — a mark on the nodes that work with it and on the groups inside that show it; what is built shows the same again. Taking apart is the action opposite to «wrap»: without it a group built by mistake could be removed only together with its nodes.
A mark on the face is put and taken off by the person (process.group.face); a mark ahead — by the same request without a group. A port through which a link crosses the border of a group is not taken off the face while the link is there: the link would have no visible end left; the refusal is graph_face_port_used (Error codes). A row of a port on the face of an outer group is marked at the folded inner one: a folded group is a node of the group it lies in.
A copy. A node and a group are copied: the copy lies beside the original — in the same group and in the body of the same frame. A group is copied with everything in it, a frame — with its body and the groups in the body; the links between what is copied are copied, the links outside — not. The copies have new identifiers (process.node.copy). The name of a shared memory the copy keeps: a copy of a node that shares a memory shares it too; a memory named by the identifier of a copied node is named by the identifier of its copy.
Make a separate process. A group is turned into a separate process in one action, and in its place stays a node that calls this process (section 18): the group lives in one process, and the call goes by reference, and many processes use what is built. What becomes what:
- what lay in the group — nodes, the links between them, the groups inside — becomes the graph of the new process;
- a row of input on the face — an event
callof the new process, a row of output — a «Result» node; their name is that of the row, and for a row without a name — the name of its port. Equal names are numbered; - the variables only the nodes of the group work with go into the new process together with their initial values;
- the call node takes the place of the group — in the same group and in the body of the same frame — and is linked to everything the group was linked to, through inputs and outputs with the same names. The faces of the groups around show these inputs and outputs where they showed the ports of the nodes of the group.
The new process is written at once and lives on like any process; the document the group left stays an edit that is saved as usual, and a step back returns the group to its place. The name of the new process is the name of the group.
The group stays in place, and the platform names the cause in words, when as a separate process it would work differently:
- the group holds an event or a «Result» node of this process: they belong to their process;
- nodes outside the group work with a variable of the group too, or the variable stands on the pulpit: a called process would have its own variable;
- a cold link crosses the border of the group — from a variable or a constant: in a separate process it would become an input the node waits for;
- the nodes of the group take values from the settings of the process (section 15): the new process does not have these settings;
- the face of the group has no input or no output: a process needs an input and an output.
The modules of the new process work in its Python environment (Modules, section 5); if they need libraries of their own, the person installs the environment of the new process, and the window says so at once.
The place of a folded group is the place of its first node: the top left corner of what lies in it. A folded group is carried with everything in it, so it unfolds where it stands, and the links come to it the same way as they came to its nodes. An unfolded group needs more room than a folded one, and its neighbours give it room: on unfolding they move to the right and down, on folding they come back (The workshop, 5.3). So what is built is kept as a compact node and opened where it stands — without a separate canvas for each level.
Deleting a group removes it with everything in it: a folded group is one node, and it is deleted as a node.
Start a part (12.1) works at any level: the group one wants to check is started together with everything inside it, nested groups and the bodies of its frames included. In such a start the group stands alone: the groups around it and its face outside do not enter the run. Which part to check alone is decided by whoever built it.
The name of a group is plain text, like the name of a process, not a dictionary key: it is a word of the person, and it has no translation. A key and a dictionary of its own appear at an element that is delivered ready — so a module is made (Modules, section 2). So a block the person hands to others stops being a record inside a process and becomes an element with its own folder and its own texts; while it exists in one process, it has only a name.
18 A process as a node
A ready process is called from another process by a node process — instead of copying its graph by an insertion (section 16). The node names the process with the parameter process_id. Ready processes for calling and inserting are offered by the library; it is also the set of built nodes (The workshop, 5.6).
A call is a brick, like any node: it has as many inputs and outputs as the called process declared, each under its own name, and values of its parameters of its own. What is built of nodes is then in no way poorer than a module beside it, and there is no need to write a module for convenience.
What becomes what. The called process declares where its inputs are and what it gives:
- an input — a node
eventwithsource: "call". There may be several such nodes; the name of the input is the parameternameof the event, without a name the input is calledinput. A packet that came to the input of the nodeprocesswith this name comes out of the event as out of an ordinary one; - an output — a node
result. There may be several such nodes; the name of the output is the parameternameof the node, without a name the output is calledsuccess. A packet that reached the inputvalueof a noderesultcomes out of the nodeprocessthrough the output with this name; - parameters — the variables of the called process. The node
processgives them values with the parametervalues: with this value the variable begins at this place of the call. Each place of a call has its own variables of the called one, so two calls of one process with different values do not hinder each other. A variable without a value begins with what the process itself set.
The names of the inputs are distinct, and the names of the outputs are distinct: a port has one path behind it. Two inputs or two outputs with one name — and two without a name — are graph_invalid both at the check of the process itself and at the check of whoever calls it.
A process without an input or without an output is not called as a node: the check of the graph refuses the calling document (graph_invalid) and names what is missing. Refused the same way are a link to an input or from an output the called process does not have — the refusal lists those it has — and a value for a variable the process does not have or whose type is other.
A called process works on its own too. Beside the input call it may have its own events — the pulpit, a timer, a schedule; on a call they are not carried over, and without a call they begin a run, as for any process. A node result in such a run ends its branch: the node is marked done, and the packet goes no further. For both an event and the input of a call to feed one node, they meet in a merge first (10.4): on unfolding the event falls away, and the merge is left with one source.
A call is not a separate run. Before the check and before the run the graph of the called process is unfolded into the caller: its nodes, links, groups and the variables they need get into the graph of the run with new identifiers of the form <node>-<identifier>, exactly as at an insertion (section 16). So a call has no checkpoint of its own, no stop of its own and no overlap of its own: all this is the calling run's, and resuming after a failure needs no extra mechanisms.
It follows that:
- The own events of the called one are not carried over. The events
pult,timer,scheduleandstartbelong to their program; only the inputcallgoes into the graph of the caller. The called process need not be switched on for a call. - Edits are seen at the next run. A call goes by reference, not by copy: a change of the called process the caller gets at the next run. This is the difference of a call from an insertion. So each process answers for its modules: when the caller is switched on, the modules of the called one are checked by the lock of the called one, and an edit of the called one does not require saving the caller anew; they work in the Python environment of the called one (Modules, section 5). At the same time they work in the run and the program of the caller: its work folder of the run, its services, its state folders.
- Rings are forbidden. A process cannot call itself, directly or through a chain: the unfolding would not end. A ring of calls is
graph_invalidwith a list of the processes in the ring. - The pulpit of the called one is not carried over. The pulpit belongs to its process (section 16).
- An export carries the called ones. A process goes into an export together with the processes it calls, and on import they become processes of the new instance, and the calls — references to them (File formats, 10.1).
- The unfolded graph lies where the call lay. The nodes of the called one get into the frame and group of the node
process, its groups — inside this group. A row of the face that showed an input of the call shows the nodes the inputcallwith this name fed, and a row of an output — the nodes that fed theresultwith this name. So a call in the body of a frame or in a group passes the check like any other node.
19 Pause
The node wait stops its branch for a set time. The other branches of the run go on working — the same as at a question to the person (section 9). A packet that came to the input input comes out of done unchanged: the pause adds nothing to the data and takes nothing from them.
The length of the pause is set by the parameter wait_ms — an integer number of milliseconds. The parameter is required: without it the pause would have zero length and the node would do nothing, so a document without the parameter does not pass the check (graph_invalid). The value may come by a substitution of a setting or a variable (sections 15, 7).
A pause is counted by real time, not by the working time of the platform. The moment the branch waits until is written into the checkpoint — so:
- a run broken off by a failure and resumed later waits out the rest of the pause, rather than beginning it anew;
- a moment that passed while the instance was stopped or the machine slept frees the branch at once on resuming.
By this a pause differs from the timer of an event, which counts the working time of the platform (section 5): a timer has no term but an interval, and a pause has exactly a moment.
Stopping a run (12.3) takes off a pause together with the run: in a stopped run the branch does not wait out the pause.
How a pause differs from other nodes. A pause is not repeating until success — for that there is the frame «Repeat» (11.1) and the retries of a step of a module (12.4); and it is not a schedule: for work by the clock the event schedule serves (section 5).
20 «First time?»
The node first_time finds out whether a value has been met before. If not, the packet goes to the output true, and the value is at once written into the list; if so, the packet goes to false. The packet itself does not change at either output: the node answers about the packet and does not change it.
The value tested is set by the parameter path — a path through dots by the rules of section 8. Without a path the whole packet counts as the value. The place of keeping is set by the parameter variable: a variable of type list. A variable of another type does not pass the document (graph_invalid), and this is seen in the workshop, not in the middle of a run.
To test and to remember is one action. Two branches that met one and the same value at one moment get different answers: exactly one gets the answer «first time». So this is a node, not a pair «condition plus write»: there must be no gap between the test and the writing.
Why a separate node. A condition reads the packet and nothing else (section 8). A test that reads a variable and changes it would make a condition something else — and deduplication would stop being seen on the canvas.
The platform does not limit the list. It is an ordinary variable of the program: it is seen in the workshop, kept after switching off with remember, and cleared by the person or a node write. A cut of the list by the platform would make a forgotten value come back as new, which contradicts the purpose of the node. The volume of keeping is decided by the author of the process.
21 Take a field
The node take takes one field of the packet and makes it the packet. The rest is dropped.
path— a path through dots by the rules of section 8; the field is required, a field missing from the packet is the node errorgraph_invalid;as— what to call the value in the new packet. Empty — by the last part of the path:user.namegivesname. The name must be an identifier, otherwise it cannot be reached by a path.
The packet stays an object: { "<name>": <value> }, not a value without an object. So nodes, modules and the pulpit read it; a value without an object would need unwrapping in all places.
Why a separate node. A merge can gather a record from several inputs (10.4), and without this node there is nothing to take a record apart with: a field of the packet can only be tested — by a condition, a choice, a path at write. This is the only operation on data that cannot be built of the others, so it is a kind of node and not a composite construction (rule 8 of section 3).
The node does no computation or conversion; it only singles out a field. A sum, a difference, a length are the work of a module, not of the graph.
A close possibility is picking parts on a link (10.1, the field parts): it takes several top-level fields and leaves them their names. take takes one field by a path of any depth and can rename it.
22 A signal from outside
The node signal stops its branch until a signal comes from outside. The other branches go on working — as at a question to the person (section 9) and at a pause (section 19). This is the third form of one primitive of waiting: waiting for time, for the person and for a signal.
The purpose of the node is the scenario «do an action, wait for a file or an answer, go on». An event (section 5) can only begin a run, from a service module too; without this node a waiting branch cannot be continued, and the work would have to be split into two programs with a variable between them.
The awaited signal is set by the parameter name — the identifier the signal is sent with. The addressee is set by the optional key: a path by the rules of section 8 inside the packet that came to the input. Its value tells one standing branch from another when one name is not enough: «the file came» for job 42, not for job 43.
Sending a signal: a module — with the notification signal to its own program (Modules, section 7); another process — with a «Send» node (section 23); the person — with the method process.signal.send (Methods of the graph) from the pulpit or from the workshop. The person sends a signal to a node, not to a name: the node names the name. From the pulpit — with a button of the object signal:<node_id> (section 16): the data of the signal are the variables the button carries, and the node reads the key from them by its path key, as a «Send» node reads it from its data. From the workshop — with a field on the node (24.5): the branch is chosen by the key it waits with — the checkpoint names it — and what is typed goes as the data of the signal.
Rules:
- a signal wakes one branch: the one that has waited for this name with this key longest;
- a signal is not kept. If there is no waiting branch, the signal is not put aside: the method answers plainly
delivered: false, and the record stays in the journal. The branch must already be waiting at the node; - the packet at the output
signalis{ "waited": <the packet it waited with>, "signal": <the data of the signal> }, like an answer that is passed together with the question (section 9); timeout_ms— the term of waiting;0means waiting without a term, and together with a question without a term this is the only case where a run may not end (section 9). When the term runs out, the packet goes totimeoutunchanged;- stopping the run and switching the program off take off the wait without a signal; a wait that lost to another branch at a merge
firstis taken off the same way (10.3); - a waiting branch is written in the checkpoint: after a failure and a resume it waits for the signal again, whose parameters are read from the node.
How a signal differs from an event and a question. An event begins a run, and a signal continues a begun one. A question is shown to the person on the pulpit, and a signal comes from any sender and shows nothing.
23 Send
The node send passes data to another program — or to this one — and does not wait for its work. This is the second relation between processes beside a call: a call (section 18) means «do it and give it back to me», and the graph of the called one becomes part of the run of the caller; sending means «here you are, live with it yourself», and the receiver works as its own program — in its own folder, by its own overlap rule, with its own stop. So a process that lives by its own events — a messenger, a sensor, a folder watcher — cannot be called (its events are not carried over), but it can be written to.
Parameters:
process_id— the receiving program;to— instead ofprocess_id: a field of the packet that names the receiver at the moment of the step. So one program writes to many — a messenger into each chat. No such field, or it is not text — the data go tounsentwith the causeprogram_off. One of the two is set;mode—messageorsignal. A message begins runs of the events of the receiver withsource: "message"and the same name (section 5). A signal wakes the branch of the receiver that has waited for it at a nodesignallongest (section 22);name— a name the receiver knows: an identifier;key— only for a signal: a path to a field of the data whose value finds the right one of the waiting branches, as at the nodesignal.
Outputs. sent — the receiver took it: the data go on as they were. unsent — it did not: { "reason": <cause>, "input": <data> }. The causes: program_off — the program is off or there is no such process; no_receiver — there is no message event with this name; dropped — the event did not take a new run by its overlap rule (skip, a full queue); nobody_waits — nobody waits for the signal. A link from unsent is optional: without it the data are written into the checkpoint as gone nowhere, and the run does not refuse — a message not taken is not an error of the sender.
Files go with the data. A file the data name — from files/ of the sender or from the results of its run — the function of the graph copies into files/ of the receiver, and the reference in the data names the copy: the receiver reads files only in its own folder (File formats, 16.3). A file that could not be copied is a cause for the data not being passed. In its own program a reference to a file from files/ stays as it is.
What does not go. A message is not kept beyond the overlap rule of the receiver, a signal is not kept at all (section 22). An export of the sender does not carry the receivers: the sender works without them, and a message not taken names its cause.
Why this is a kind of node. No node goes beyond its own program: a call is unfolded inside, a module has no access to the platform (Protection). So sending cannot be built of other kinds and by rule 8 of section 3 is a primitive — УП-15 of the catalogue (Kinds of nodes, section 2).
Programs may send to each other endlessly — as a timer begins runs endlessly; each run is finite (ЗС-05). A message to itself is a new run of its own program, not a ring inside a run.
24 The border with the person: kinds of data, input and showing
A ready program takes data from the person and shows data to them through the nodes of the graph; the graphic nodes of its interface are bound to these nodes (section 16; The workshop, 6.8). The same nodes on the canvas serve the check: what is built is checked where it is built (The workshop, 5.5). The make-up for them — the names of kinds of data, the reference to a file, where files lie, the node «Show», the field on a node — is described in File formats, section 16.
24.1 Kinds of data
The list of kinds of data is one — for ports, variables, the pulpit and the canvas. There are nine kinds (table 6).
Table 6 — Kinds of data
| Kind | What it is |
|---|---|
| text | a string of any length; a short and a long text are one kind, the difference is in how it is shown |
| number | an integer or fractional number |
| yes/no | one of two values |
| list | an ordered set of values; a table and a feed of messages are lists of records |
| picture | an image |
| sound | a sound recording |
| video | a recording with or without sound |
| file | any other file |
| anything | data with a structure of their own |
A kind of its own is what is shown to the person in its own way: a picture is looked at, a sound listened to, a file opened. So sound and video stand apart from a file, though inside the platform a picture, sound, video and file are passed alike — as a file (24.4). Date and time, a choice from a list and an area on a picture are not kinds of data but ways of input: a date is typed with a calendar, an area is marked on a picture. They appear as elements of the pulpit and fields on a node.
A port of a module and a variable name any of the nine kinds and date-and-time: the list is one, so a picture is put into a variable, taken in on the pulpit and shown on it the same way as text.
24.2 Input
There is no separate kind of node for input. The person gives a program data in three places, and each of them is a kind of node that already exists:
- an event (section 5) — the input begins a run;
- a question (section 9) — the run has reached a place and waits for the person;
- a variable and a constant (sections 6 and 7) — the value lies there, and the node takes it when its turn comes.
Input need not begin a run: it may be in between. An example: a program makes a video, and from some frame a second character appears in it — its picture is given at this place of the graph, and the next frame takes it. This is the second or third place, not the first.
Each of the three places takes any kind of data (24.1), and its node on the canvas carries a field in which data are set right on the node (The workshop, 5.5). The reason: a fourth node would repeat the three there are and would be superfluous in most graphs.
24.3 Showing
Data are shown to the person by a display node: what came to its input — text, a picture, sound, video, a file, a list — it shows on the canvas, and when put on the pulpit — on the sheet of the pulpit. A display node is part of the program, not a tool of the workshop: showing data is needed both by the ready program and by whoever builds it, and that is one skill, not two. A node not put on the pulpit is not seen by the person who uses the program.
By rule 8 of section 3 this is a primitive: showing to the person cannot be built of the other kinds of nodes: a variable with the element «Value» shows one line of text, not a picture, a sound or a gallery. The kind of node is «Show» (show): the input value, the output success with the same packet, the parameter path — which field to show; a field missing from the packet is shown as missing, and the run goes on (File formats, 16.5).
24.4 A file the person gave
A picture, sound, video and file the person gave a program are copied into the process folder. The program does not depend on the place the original lay in: it can be moved or deleted, and the process goes to another person whole (File formats, 10.1). The price of this — a large file takes room twice.
How long the copy lives is decided by the place the file was given to:
- a constant — while it stands in the graph: the file is part of the program, like a picture of the pulpit;
- a variable — while the variable holds it, and with the mark «remember» between switchings on of the program too;
- an event and a question — together with the run: the copy is cleared with it (12.6).
A file does not change after it appears. The rule concerns any file named in a packet — given by the person and made by a module: whoever needs a changed file makes a new one. A fan-out copies the packet together with the references to files, and two branches get two names of one file; a changeable file one branch would spoil for the other. The platform keeps the rule itself: a file given by the person, taken in with a program, moved from a run into the files of the process, and a file a module gave into the results of a run, are read-only from their appearance — a module that writes into one by mistake gets a refusal of the system and ends its step with a failure, rather than changing the file for another branch. A file is removed together with what holds it: that is the right of its folder. A copy in a packet for another person is theirs: it may be written, and the next packet to the same place replaces it.
24.5 The field on a node and a part started on its own
On the canvas data are set right on the nodes: for a constant the field edits its value in the document, for a variable — writes the value of a program that is on, for an event — types what it is started with, for a question — takes the answer, for waiting for a signal — sends a signal to a waiting branch (section 22). An event declares what it takes with the parameter takes, a question — by the kind of answer kind; a constant — by the kind of value type (File formats, 16.6). From the canvas an event is started for a check with any source, not only from the pulpit.
A node or group started on its own (12.1) gets at its inputs from outside the values of constants and variables and what is typed in the fields of events. An input to which a link from another node leads from outside will get nothing, and the start is refused with words saying which input it is; a start is refused the same way if an event that leads into the part does not have typed what it takes (File formats, 16.7). The window has no common input.
24.6 Notification
A program calls the person with a notification. The node «Notify» (notify) puts a notification over the window — the workshop or the face of a program — whatever tab and window the person is in, and when the person is not at the window — the window is hidden by the clock, minimized, or they are in another program — into the corner of the screen (The workshop, 2.2): a reminder of a task, a new letter, the end of a long work. A notification stays a few seconds and goes by itself or at a click; the four latest are seen at once. The title is the parameter title, without it the name of the program; the text is the field named by the parameter path, without it the field text, and without that everything that came — no longer than 500 signs. The packet goes on unchanged, as at «Show».
This is a primitive by rule 8 of section 3: «Show» shows to whoever looks, and a notification calls whoever does not look — it cannot be built of the other kinds. A notification is not kept: whoever was not at the computer did not see it, and there is nobody to call while the platform does not work; a closed window does not stop the platform (The workshop, 2.1). The workshop itself calls with a notification about a question and a failed run (The workshop, 2.2).
25 Stamp
The node stamp is a source, like a constant: it has no inputs, a link from it is cold (section 6), and the node at the end of the link reads the stamp at the moment of its step. A stamp is { "time": "<start of the run>", "id": "<number>" }:
time— the local date and time of the start of the run with the offset from UTC,2026-10-14T13:21:23+03:00: the moment the event began work — when the person pressed «Send» or a message came;id— a record number that repeats neither in this program nor in another: the name of the process, the run, the node that reads the stamp, and the data its step goes with —process-2.2026-10-14_13-21-23.letter.packet-3; in a frame the round is added to it. The name of the data follows the packet from node to node, so steps are told apart by the node. Two steps of one run get different numbers, two readings in one step — one.
A stamp repeats when a run is resumed (ЗС-06): the time is the start of the same run, the number from the same data; so it is not a clock and not a random number. The time in the middle of a long run is the work of a module.
Why this is a kind of node. The time of a run and the name of the data of a step are known only to the engine: neither an event — only a timer has a time, and only in its packet — nor a variable nor a module sees them. Without a stamp a record has neither a time nor a name by which it is referred to: an answer, an edit, a deletion, «read» (ЗС-10).
26 Pick
The node pick takes from a list the records that pass a test, and counts them.
- The list — a variable
variableread at the moment of the step, or a field of the packetlist; one of the two. - The test — the parameter
testfrom the set of section 8. Itspathis looked for inside a record of the list;value— a value, a variable{ "variable": "<id>" }or a field of the packet that came{ "packet": "<path>" }. - The output
success— the packet that came, to which the fieldasis added (without it —found):{ "items": [records in the order of the list], "count": <how many> }. A packet that is not a record goes undervalue. No list or it is empty —itemsis empty,count0, not an error.
So are built a search (contains by the text of a message), a record by key (equals, the first — found.items.1) and a count: «unread in a chat» — two picks in a row, by the chat, then by the mark «read», and the second takes its list from found.items of the first.
Why this is a kind of node. «For each» with a condition inside gives the fitting records one by one, but neither their list nor their number: the graph can gather them back only by writing into a variable at each round, and that is no longer a query but a change. Pick computes nothing: a test from a closed set and a count of what is found (ЗС-10). Arithmetic and expressions stay with a module (ВГ-01).
27 Time
The node moment — «Time» — counts a moment by the calendar: from a date, after so many days, at such an hour. «Today at 09:00», «in a week», «tomorrow at the same hour».
- From which date — the parameter
from: a field of the packet with a date and time (the kind «time», 24.1, File formats, 16.1). Empty — from the start of the run, as for a stamp (section 25). - After how many days —
days: an integer from −36,500 to 36,500, or a field of the packet that came{ "packet": "<path>" }holding such a number; without it 0. Days are days of the calendar by the clock of this machine, not 24 hours each: a shift across the end of a month, a year and 29 February gives the next date, not the date plus hours. - At which hour —
at:HH:MM; empty — the hour of the source date. - The output
success— the packet that came, to which the fieldasis added (without it —time): the date and time by the clock of this machine without a zone,2026-10-17T09:00, seconds — only if there are any. A packet that is not a record goes undervalue. A date not set in the packet (an empty string or no field) — the moment is not set either, an empty string, and the run goes on.
A field with no date in it, or a field of days with no integer in it, is a refusal of the step graph_invalid naming the field: a moment is not made up. A date with a zone is first brought to the clock of this machine, so «today» is today for the person.
Like a stamp, «Time» is not a clock: from the start of the run it counts the same when the run is resumed after a failure (ЗС-06). So are built the term of a repeated task — the field of the term and its repetition in days — and the reminder «today at 09:00».
Why this is a kind of node. Counting with dates cannot be built of the other kinds: the graph has no arithmetic, and the comparisons «earlier» and «later» (section 8) only compare moments. Arbitrary expressions stay with a module (ВГ-01), but the calendar is one for all: «in a week» does not depend on the program, and without the node every author would write one and the same module. The node computes nothing but this one thing: a shift by the calendar and the hour (ЗС-10).
28 Template
The node template — «Template» — puts fields of the packet into a text: «{found.count} tasks not done», «{author}: {text}».
- The text — the parameter
text. A field stands in braces — a path by the rules of section 8 of any depth, spaces around it do not count;{}— the whole packet that came;{{and}}— the braces themselves. A brace opened and not closed is the document errorgraph_invalid: the check of the graph names the place. - The substitution — the value as it is: a text as text, a number and «yes/no» as written, a record and a list as data. No field, or it is empty (
null) — empty, and the run goes on. - The output
success— the packet that came, to which the fieldasis added (without it —text: this field is shown by «Notify», 24.6). A packet that is not a record goes undervalue.
A template puts in and does not count: in the braces is a field, not an expression. It does not inflect a number and «yes/no» («3 task»), and does not turn a date into words — it stands as it lies in the packet.
Why this is a kind of node. Without a template the graph cannot say in words what lies in the data: a notification, a message, a journal line take a field whole, and a morning summary will not say «3 tasks not done». With a module this would be solved by every author in their own way, while the substitution is one and the same for all and computes nothing: counting, a condition inside a text, the format of numbers and dates stay with a module (ВГ-01, ЗС-10).