Modules
What a module is and how the platform deals with it: the folder and versions, the modes of life and unloading, the Python environment, the work folder, the memory of a node, parameters, neural networks.
Introduction
The document describes a module, how it is built into a working process, where its dependencies are put and what OGAS SDK is for. The owner of modules is the function modules. The format of the manifest — File formats, section 4, the messages of a module and the methods of the function — Methods of modules.
1 The purpose of a module
A module is a separate program that does one task: it gets a packet at its input and gives a result at its output. The purpose of a module is not limited. A person writes a module themselves or gets it ready from another person.
- A module does not know of the platform and other modules. Everything needed to build it in, it declares in its manifest: inputs, outputs, parameters (section 10), whether its steps may be repeated, the time limit.
- A module is started as a separate OS process: its failure does not stop the platform.
- A module works in the work folder of a run, not beside its code (section 6).
- A module is trusted code and works with the rights of the user. A module got from another person is the same as any downloaded program: the platform does not check what it does (Protection, 3.1).
2 The module folder
modules/<module_id>/
manifest.json the manifest ogas.module/1
main.py the entry point
requirements.txt Python dependencies, if any
uv.lock | pylock.toml exact versions of dependencies, if the author put them; the platform does not read them (section 5)
requirements.freeze.txt what pip installed, for diagnostics
locales/{ru,en}.json the name of the module, the texts of its parameters and ports (section 10)
test_<module_id>.py checks of the module without the platform, if the author wrote them
A ready module is added in one of two ways: its folder is put into modules/, or it is added together with a process from an export (process.inspect, then process.import, Building, installing and maintenance, 5.6). The list of modules is read again from the folder at each request; no restart is needed. The texts of a new module are seen at once too: at introduction the function of modules declares to the kernel the dictionary folder modules/*/locales, and the kernel finds the dictionaries of modules anew at each question (Exchange with the kernel, section 4). Then come the check of the manifest, installing dependencies, a test call and adding a node to the graph (Getting started, 6). All these actions are done in the workshop without copying by hand. A module is made and deleted, and whether the column of the graph offers it is decided, in the library of the workshop, and the environment of a program is installed in the window «Modules» of that program (The workshop, 5.6).
3 Versions
In one installation each module_id has one version. The version of a module in a process is written in one place — in modules.lock.json; the node of the graph does not keep it. It is written by the function of modules when the process is saved: it takes the version and the print from the installed module. A module that is not installed is not written into modules.lock.json. Before a run the function of the graph compares the written version with the installed one and passes it to module.execute; on a mismatch the start is refused (module_version_mismatch).
The print of the content. The files of a module can be changed without changing its version. For such a change to be found, the process remembers together with the version the print of the module — sha256 over the content of its files, without the environment, caches, diagnostics and symbolic links (a link is not part of the module and is not carried by an export). Before a run the print is compared with the installed module:
- when the prints differ, the start is refused with
module_changedand a hint to save the process if the change is intended; - saving the process rewrites
modules.lock.jsontogether with the print. Saving is accepting the changes: there is no separate «draft» mode, since there are no modes in the platform; - with no print in
modules.lock.jsonthe start checks only the version.
The print shows a change of files but does not name the author of the change: it is not a signature, and the module stays trusted code (Protection, section 1).
4 Modes, unloading and parallelism
How long the OS process of a module lives is declared by the field process of the manifest:
step— the OS process of the module lives for one call.long— the OS process keeps its state between the calls of one run. On a failure of the module the state is lost; only the module itself can restore it — from its inputs or from result files. A long module belongs to one run: after the run its long modules are unloaded with the commandmodule.releasewithrun_id, and the modules of other runs, even of the same module, stay (The program: graph and runs, section 13).service— a service: the OS process is started when the program is switched on and works until it is switched off, serving the steps of all its runs. So works, for example, a language model whose loading for every message would take too long. A service can also give packets at its outputs by itself (the notificationemit, section 7) — then it is a source of events: such a packet begins a new run (The program: graph and runs, section 5). What a service serves — which folder to watch, which port to listen on — it learns before the first step: each time it comes up it gets the nodes of its module with their parameters (configure, section 7); a service of several nodes names inemitthe node the packet is for. The overlap rule of the packets of a service is the fieldoverlapof its node (The program: graph and runs, section 5).
Unloading by resources. The function of modules keeps count of the started modules of all programs that are on and knows how much video memory each takes: the field resources.gpu_mb of the manifest. While the sum fits into the setting modules.gpu_budget_mb, the modules work at once; at 0 one module with gpu_mb > 0 uses the video card at a time. When there is no room for the next module, the function of modules unloads the one that has not worked longest — a long one or a service of any program that is on. A module is not unloaded during a step: a step that needs the video card waits for room no longer than its own term (timeout_ms), and if it does not get it, ends with the refusal module_gpu_full.
The memory of a node. A module is a tool, a node is its use: «writer» and «judge» on one model are two nodes and two memories. A module in a run is one for all its nodes, and the weights are loaded once, but each node has its own memory — the folder data/processes/<process_id>/state/<node_id>/; how nodes name a shared memory and how long it lives — The program: graph and runs, section 13. From the side of the module:
- the folder of the memory comes with each step of the node in the field
state_dirtogether with the signresumed(section 7); - a module with
suspend: truegetssuspendbefore unloading and saves the memory of each node it served into the folder of that node; a module withoutsuspendends, and its nodes begin from the start next time; - the function of modules confirms the saving with a mark beside the folder of each node; before asking the module to save, it removes the former marks, so a memory whose writing broke off has no mark (File formats, 5.3);
resumed: truecomes if the mark of the folder is there and names the same module, version and print — in any run and after a restart of the platform: a chat bot frees the video card for an image module and then goes on with the conversation;- what to save and how, the module decides; the platform does not read the folder;
- an unloaded service is started again when a step comes; while it is unloaded, it gives no packets by itself.
Parts of a result. A module can pass the result of a step in parts while it works (the notification partial, section 7): the text of an answer as it is generated, finished elements of a list. The parts are passed along the links of the output and are shown on the pulpit at once through a variable (The program: graph and runs, sections 7, 13). The answer to execute stays the final result of the step.
By default the calls of one module go one after another — from different runs of one program too. Calls at the same time are possible only if the manifest declares parallelism > 1: this is a permission of the module, not a promise of the platform.
Resources for all modules are set by the person in the settings of modules (the section «Resources for modules»):
modules.parallel_max— how many modules on the whole computer do a step at the same time. A step past that waits for its turn before its module is raised, and a stop of the run takes it off while it waits;modules.memory_max_mbandmodules.cpu_max— the memory and the processor cores the modules take all together. Linux holds the limits: the function of modules starts every module in one group with these limits (limitsincore.process.spawn, Exchange with the kernel, 5.4). A module that took the group past its limit of memory is stopped by Linux, and the step fails withmodule_memory_limit; what the module had done by then is not known, as when a module is lost. Without systemd the limits are not held, and a module with limits is not started: the refusal says what to do.
The term of a step is set by the author of the module with the field timeout_ms of the manifest — an integer number of milliseconds, no less than 1. This term is the only one: the platform does not shorten it with a limit of its own and sets no terms of its own. The term is kept by the function of modules: if the module did not answer in time, it stops it (core.process.stop, then core.process.kill) and answers module_timeout. The function of the graph and the workshop wait for the answer to a step without a term of their own: the answer comes with the result, with module_timeout or with function_lost on a failure of the function of modules itself.
5 The Python environment
One Python environment is used for a working process, shared by all its modules. A process is a finished whole: one installation of dependencies, one set of versions. The modules stay separate OS processes.
The environment is made by uv, and identical files lie on disk once. The environments of processes are hard links to the files of packages in the common cache data/cache/uv/: each process has its own environment and its own set of versions, and a package that is the same in two processes takes room once. The cache lies in the data folder to be on the same disk as the environments: there is no hard link between disks. uv is the program named by the setting modules.uv (uv by default, looked for in PATH); if it is not there, installing ends with the refusal module_uv_not_found. uv does not download its own Python: the environment is made with the interpreter found by modules.python_candidates.
Where the environment lies and what happens on a clash of versions:
- Place.
data/processes/<process_id>/.venv. Beside it lie the commonrequirements.txtof the process, gathered from the requirements of the modules, andrequirements.freeze.txtfor diagnostics. These files come from the modules, so they are not part of an export of the process. - Installing. The command
module.installwithprocess_idgathers the requirements of all Python modules of the process into one list, makes.venv(uv venv) and installs the packages into it (uv pip install --link-mode hardlink). Which modules the process has, the function of modules takes from itsmodules.lock.json(section 3), and does not read the document of the process — the graph. Installing is not done at a run. - A clash of versions is a refusal. If two modules ask for different versions of one package, installing is refused with
module_environment_conflictand a list: which module asks for what. There is no separate environment around the clash: such an exception would mean several environments for one process, and a process has one environment. The person chooses: change the version of a module or split the processes. - Requirements, not pinned versions. The source is the
requirements.txtof the modules.uv.lockandpylock.tomldescribe the environment of one module and do not go into the merge: the locks of different modules in one environment would clash, and an author who needs exact versions pins them inrequirements.txt. - The interpreter — the first found in the system from the list
modules.python_candidatesthat fits theruntime_pythonof the manifest; if the manifest named no version — any Python 3.11 or newer. A module is started with the interpreter of the environment of the process, if there is an environment. If no program of the list starts, installing and a step of a Python module end with the refusalmodule_python_not_found. The requirementruntime_pythonis conditions through commas (>=,>,<=,<,==,!=and a version), the version is compared by the parts named in the condition: any 3.11 fits==3.11. The environment of a process is made with an interpreter that fits the requirements of all its modules; a requirement that cannot be read is an error of the manifest (module_invalid). The refusalmodule_python_not_foundnames which versions were found and what was asked for. - Requirements without an environment. When there are requirements and no environment, the refusal
module_needs_environmentis given with the name of the process whose environment is missing, rather than installing at a run. - A module of a called process works in the environment of the process it came from — both a step and a service (The program: graph and runs, section 18). A called process answers for its modules whole: the versions are written in its
modules.lock.json, the dependencies stand in its environment, and they are installed with the commandmodule.installfor it. The environment of the caller does not hold the modules of the called one, and no clash of their versions with its modules arises.
6 The work folder and result files
The OS process of a module is started in the work folder of the run in the folder of its process: data/processes/<process_id>/runs/<run_id>/work. A test call without a process gets its own folder data/module-tests/<module_id>/work. A service works longer than a run, so it is started in the memory folder of the process state/, and gets the work folder of the run in each execute in the field work_dir and puts result files there.
- A module finds its own files by the path to its folder. The full path to it comes in the variable
OGAS_MODULE_DIR, the path to the folder of the run inOGAS_RUN_DIR; the paths to the neural networks its parameters chose come with each step in the fieldmodels(section 11). The SDK hasmodule_path()for this: a relative path in the parameters of a node is counted from the module folder, not from the work folder. - A module names a result file in the field
artifacts— as a path inside the work folder. The function of modules moves the file into the results of the rundata/processes/<process_id>/results/<run_id>/under the name the module gave:sheet-1.svg, not a service reference. A folder in the path becomes part of the name (out/a.png→out-a.png), a name already taken in this run gets a number (sheet-1-2.svg). The name of the file is the reference to it: the function returns{ artifact_handle, kind, size, media_type, name }, whereartifact_handleis this name (Methods of modules, section 1). - A file named in
artifactstwice is one file: it is moved once, and the references to it name it alike (two identical photographs share one thumbnail). The type of the content, if the module did not name it, the function of modules takes from the ending of the name — for a picture, sound, video, text and document; otherwiseapplication/octet-stream, and the window shows the file as a file, not as a picture or a player. - A module names a file in its output with a reference with the same path —
{ "file": "out/a.png" }inoutputandout/a.pnginartifacts. Having moved the file, the function of modules puts its name among the results into the reference and addsname,media_typeandsize: the output carries a reference to the file, like any data (File formats, 16.2), and the module does not know under which name the file went. A reference to a path that is not inartifactsstays as it is. - The size limit is the setting
modules.artifact_max_bytes, 64 MiB by default; the setting is raised if needed. A file over the limit is not taken: the step ends with the errorartifact_too_large. - A result file of another module and a file from the data the module reads by the path from
artifact_filesinexecute: the function of the graph names to the module the artifacts of the packet and the files of the references in the data of the step — a picture from a variable, an answer to a question — and the function of modules gives the path to those of them that lie among the results of this run or in the files of the processfiles/(Methods of modules, 1.2). - A result file does not change after it appears: a module that needs a changed file makes a new one (The program: graph and runs, 24.4).
- A file in a folder of the person is not an artifact. A module that by its task lays files out into a folder of the person gives their path in its output and leaves
artifactsempty: an artifact is a file of the run that the platform keeps and clears together with the run.
7 The protocol of a module
A module exchanges JSON-RPC 2.0 messages with the function of modules, one per line, through stdin and stdout (table 1).
Table 1 — The protocol of a module
| Method | Meaning |
|---|---|
init |
the language of the interface; the module answers ready, the version of the protocol and its capabilities. Before the answer execute is not sent |
execute |
facts about the run, the input data, the parameters of the node (section 10), result files, the memory folder of the node state_dir and the sign resumed — the memory in it is saved whole by this same module (section 4); the answer is the output data and the port |
configure |
only to a service: the nodes of its module in this process with their parameters — [{ node_id, parameters }], the parameters as a step would get them (section 10) — at each coming up of the service: when the program is switched on and again after unloading, before the first step. A module that does not know the method answers method_not_found and works without it; another refusal — the service does not come up |
cancel |
cancelling a particular execution_id |
ping |
checking that a long module or a service works: the function of modules asks each one that is up once every modules.ping_interval_ms; one that did not answer within modules.ping_timeout_ms is stopped as lost — a step going ends with module_process_lost, a service comes up again for the next step — and the function tells of it with the event function.work_failed. A module must answer ping in the middle of a step too |
suspend |
a module with suspend: true saves the memory of each node it served into the folder state_dir of that node and answers when it has saved; after the answer the function of modules ends it (section 4) |
A module may send the notifications progress (the share of readiness), log, partial — a part of the result of a step (execution_id, port, data; section 4), signal — a signal to its own program (name, optional key and data): a branch that waits for it at a node signal goes on (The program: graph and runs, section 22) — and, if it is a service, emit — a packet at an output outside a step, the beginning of a new run (port, data and an optional node_id: a service of several nodes names which of them the packet is for; without a name the packet goes to all nodes of the module that have a link from this output). stdout belongs to the protocol: debugging text in it is not allowed and counts as damage of the protocol; diagnostics go to stderr.
The answer to execute and the notifications partial and emit name one of the outputs of the manifest; a module without outputs names no output. A refusal carries a code: a code of the contract of a module or a cause of the module itself, whose text lies in the dictionaries of the module (table 2). A module does not refuse in the name of the platform: a code of the platform it names becomes a cause of the module. An answer outside these rules does not count as a success — the step ends with module_process_lost; a notification outside them is not passed on, and this is reported. The fields of an answer and a refusal — Methods of modules, 1.2.
The stream stdout is limited the same way as the bus: a line longer than 4 MiB or more than 256 unread answers end the step with the error transport_overflow. So a module cannot quietly take all the memory of the platform.
A module declares whether its steps may be repeated: idempotent, non_idempotent or unknown. On this depend whether a run may be retried and resumed (The program: graph and runs, section 14). The platform promises that execute with one operation_id arrives at least once, not «exactly once». Guarding an outside action against being done again is the business of the module itself.
8 Modules in other languages
A module in any language is an executable file with the same protocol through stdin / stdout (runtime: "native"): the platform starts it directly. The protocol does not depend on the language; ready libraries exist for Python and for Rust (section 9), in another language the protocol is written by Methods of modules.
9 OGAS SDK
OGAS SDK is a set of tools for the authors of modules that keeps the modules of different authors compatible. There is an SDK for Python and one for Rust.
- The SDK carries out the contract of a module — the manifest and the protocol — and does not replace it: a module without the SDK working by the same protocol is equal to a module with the SDK.
- The SDK is distributed under the Apache License 2.0, and the template of a new module without conditions (0BSD), though the platform is under the AGPL: a module that takes the SDK or is made from the template is licensed by its author as they wish, closed and paid too.
- The SDK for Python is
sdk/python/ogas_runtime.py: the exchange by the protocol for a step module (run_step) and a long one (run_long), reading a step, the answer, a refusal, words for the person, the memory of a node; it stands on Python alone.ogas_models.pyis what modules with neural networks and pictures share; its libraries are dependencies of the module itself. The SDK for Rust is the packageogas-moduleinsdk/rust: the same names and the same behaviour. The reference and the order of work — OGAS SDK. - The manifest is the only source of the contract: it is written as a file, and the SDK carries it out and does not generate it from code — two sources of one truth drift apart. The kinds of data at ports are in the contract, one list for the whole platform (The program: graph and runs, 24.1). A module without the platform is checked by
sdk/python/ogas_check.py: by its files — the contract (section 10, table 2) and the rules of the platform any module keeps that can be seen by its files: the network is declared, packages are not installed by the module itself, a secret is not a parameter, a failure is not skipped silently, the module names no documents of the platform, does not write tostdoutbeside the SDK, does not reach inside the platform, does not hide a value that affects the result as a bare number, and all its texts are in both dictionaries (OGAS SDK, section 10);make ciasks the same; by starting it — the protocol. The check only names a breach. A module is packed as its folder. - Secrets — service keys, tokens — a module keeps in the file
.envof its folder and reads withsecret(name)of the SDK. The file is part of neither the print of the module nor its handing over: the key of one person does not travel to another, and replacing a key does not change the module. There is no signature of a module beyond its print: trust in a module is a question of the workshop before the first code without a sandbox (Protection, 3.1).
10 The parameters of a node
Everything the person sets for a module is expressed by one notion — the parameters of a node. There are no separate «module settings».
- The schema is declared by the module — the field
parametersof the manifest: name, type, default, limits or a closed set (File formats, section 4). The types and the check of values are the same as for settings (Settings, section 2); beyond them a parameter of a module has the typemodel— a neural network of one kind, chosen by the node (section 11). - The values are kept by the node — the field
parametersof the node inprocess.json. Two nodes of one module in a process set their parameters independently. A node keeps only what the person set: there are no copies of the schema, the ports and the version in it. - The default is put in by the function of modules. Before
executeit adds the defaults of the manifest to the parameters of the node; a parameter withoutdefaultis optional and is not passed if the node did not set it. So there is no second default in the code of a module: a parameter is read by name, and0and an empty string set by the person stay their value. A module without the SDK gets the same. - A value may refer to a setting of the process —
${name}(The program: graph and runs, section 15). - The texts are in the dictionaries of the module, and are named by the same rule as the texts of settings; the names of the texts are not written in the manifest (table 2).
Table 2 — The texts of a module in the dictionaries
| Text | Key of the dictionary | Required |
|---|---|---|
| the name of a parameter | module.<id>.param.<name>.title |
yes |
| the explanation of a parameter: purpose, allowed values, behaviour without it | module.<id>.param.<name>.help |
yes |
| the name of a value from a closed set | module.<id>.param.<name>.choice.<value> |
yes, if there are choices |
| the name of an input | module.<id>.input.<port>.title |
yes |
| the name of an output | module.<id>.output.<port>.title |
yes |
| the hint of an input | module.<id>.input.<port>.help |
yes |
| the hint of an output | module.<id>.output.<port>.help |
yes |
| the text of a refusal for a cause of the module itself | module.<id>.error.<code> |
no; without it the refusal is named by the common text of module_failed |
These hints are shown by the sign «?» in the graph window (The workshop, section 5). The name of a port stands on the node as a row (The workshop, 5.3), so it is required; with the hint of a port the workshop says what a link to it will give, so that is required too. The check in make ci does not pass if a parameter or port of a module lacks a required text in Russian or English, or a text is written in the manifest.
The properties panel of a node in the workshop builds its fields by this schema: each parameter has a field of its type, an empty field means «the default of the manifest holds» (The workshop, section 5). For a network parameter the field is a choice among the networks of its kind in the library; a chosen network that is no longer there stays in the choice with a mark that it is not in the folder of networks. A parameter taken from a variable names it in the empty field and stays taken when other parameters of the node are saved — until a value is typed or chosen in its field.
11 Neural networks
A neural network is a neural network or other big data modules of different programs use: weights, dictionaries, voices. It lies in the library in one copy, not as a copy at each module or in the environment of each program (The workshop, 5.6). Which network a module takes and what else goes with it — an addition, a dictionary, a voice — the node chooses, in the workshop and on the pulpit.
- The folder of networks —
models/besidemodules/, in the installation folder, by default. The settingmodules.models_dirnames another — as a path from the installation folder or a full path as WSL or Windows writes it (D:\AI\models): networks are taken from where they already lie. - The kind of a network — a subfolder of the folder of networks:
checkpoints/,loras/,voices/— as modules call them: small Latin letters, digits,_and-, as for a module. The platform has no list of kinds of its own: what a checkpoint or an addition is, modules know. The subfolder of a kind an installed module takes the function of modules makes itself when it reads the folder of networks — so the person sees where to put things; the folder of networks itself it makes only if it is the default folder. - A network is a file or a folder in the subfolder of its kind, or a link to a file or folder elsewhere — then it is not copied at all. Its name is the name of the file or folder as it is, without
/and\and not beginning with a dot: a hidden file is something half-downloaded or a service file, not a network. A network has no description file of its own: what is inside, the module knows, and the platform counts only the size — of the file or of the files of the folder, without links inside. - A module takes a network by a parameter of type
modelwith its kindkindand, if it wants, a default networkdefault(File formats, section 4). As many networks — as many parameters: the main one, an addition, a dictionary — each by its own parameter with its own kind. There is no list of networks a module always takes in the manifest: it would choose for the node. - The node chooses a network among the networks of its kind, like any parameter (The workshop, 5.1); an empty choice leaves the default network. A parameter, like any, is taken from a variable, and then the person makes the choice on the pulpit: the element «Choice» with a kind of networks offers the networks of this kind from the library (The workshop, 6.2).
- The path comes with the step. The function of modules finds the chosen network before a step and gives the module its full path in the field
models— «name of the parameter → path» — and to a service in each node ofconfigure(Methods of modules, section 1); in the SDK the path is given bychosen_model(params, name). A network chosen neither by the node nor by the manifest is not given to the module. - Without the chosen network the step does not go. If the chosen network is not there, the step, the service and the test call refuse with
module_model_missingbefore the module learns of them, and name the missing ones by kind and name — rather than the module falling over a wrong path. - A network is deleted in the library. A network an installed module takes by default is not deleted: the refusal
model_in_usenames the modules. A network chosen on a node can be deleted: the step of this node will refuse and name it. A link is deleted without what it points to. - To put a network is to copy its file or folder, or to put a link, into the subfolder of its kind; the library shows the folder of networks as Windows sees it and reads it again at a button.
- To download a network — from a source in the library (The workshop, 5.6). The function of sources downloads the files into its downloads folder
downloads/, and puts them into the folder of networks through the owner of the folder, the function of modules (module.model.take): into the subfolder of the kind the person chose, under the name they gave — only a new one; an existing one it does not replace (model_exists). - Where to get the default network a module may name in its manifest with the field
sourcesof a parameter of typemodel: the source and what to take in it (File formats, section 4). By it the library of a program shows which networks its modules lack, and downloads a missing one under the default name (module.model.needs); a module withoutsourcessimply says which network is missing. - How much video memory is needed is said by
resources.gpu_mbof the manifest — how much video memory a module that is up takes with its network. By it the function of modules shares the video card (section 4), and the library says whether the module and a program with it will start on this computer (The workshop, 5.6). What the computer has — video cards with their memory, memory, cores, free room — the function of modules learns from the system:nvidia-smi, if it is there,/proc/meminfo, the room on disk (module.hardware).