O.G.A.S. Русский

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

3 The rules of the graph

  1. Data go only along links between named ports.
  2. A run begins with an event (section 5); a run has no other beginnings.
  3. Only the body of a frame repeats (section 11). An arrow back bypassing a frame is not taken.
  4. 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 merge first (10.4). With all the 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.
  5. A shared object is made once: the graph keeps its meaning, the pulpit only shows it (section 16).
  6. 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.
  7. A new run does not get the result of a past run by itself. Everything kept between runs is kept in variables (section 7).
  8. 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.
  9. 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:

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:

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 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.

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

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

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"
}

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

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).

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:

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)

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)

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

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:

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.

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):

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:

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):

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

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:

Rules:

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:

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 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:

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:

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:

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.

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:

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:

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:

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 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>" }:

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.

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».

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}».

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).