OGAS SDK
A guide for those who write modules and build programs by script: what OGAS SDK holds, how to write a module in Python and in Rust, how to check it and try it without the platform, how to build a program and put a thing on a showcase. Here, too, is how the platform itself is extended: a function, a kind of node, an element of the pulpit, a window.
Introduction
Everything needed to write a part of the platform without looking into finished ones is gathered here. If the work made you look into someone else's module, that is a flaw of this document, and it is the document that gets fixed. The contract of the messages — Methods of modules, the format of the manifest — File formats, section 4, the notions — Modules. Anything new is agreed with Rules of development, 4.7.
What OGAS SDK holds. The folder sdk/ of the installation:
sdk/
ogas-sdk the command, with nothing to install
python/ the code of a module in Python and the package ogas
rust/ the package ogas-module for a module in Rust
templates/ templates of modules
networks/ networks of the assistant — ready modules
examples/ examples
tests/ checks of the SDK and test modules
A module in Python writes from ogas_runtime import …: the platform puts sdk/python on the PYTHONPATH of every module. The package ogas is for programs that talk to the platform from outside: build scripts, editors, checks. To install it as a Python package — pip install -e sdk/python, and the command is then simply ogas-sdk.
Table 1 — The command ogas-sdk
| Command | What it does |
|---|---|
new <id> --template <template> --name <name> |
a module from a template: python-basic, python-network, python-service, rust-basic |
check <folders> |
whether a module keeps its contract — the same checks as in make ci (table 4) |
try <folder> --input <JSON> --params <JSON> |
one step of a module without the platform, the way the platform gives it; a native module too |
pack <folder> --out <folder> --author <name> --license <SPDX> |
a thing for a showcase: <id>/<version>/item.json and files/ with sha256 sums; .env, .venv and what Python left behind are not taken; a module that failed the check is not packed |
status |
whether the working platform is reachable through the door for programs |
catalog |
what programs are built of: each kind of node and element of the pulpit with its make-up and words |
run <script> |
run a build script of a program (section 9) |
With the key --json the command answers with one JSON document: that is how another program calls it.
Table 2 — What is written in what
| What | What for | In what | Where it lies | Section |
|---|---|---|---|---|
| module | work that cannot be built of nodes: a neural network, reading a file, an outside service | Python or Rust | modules/<id>/ |
1–4 |
| function of the platform | a new part of the platform itself with its own methods | Rust | functions/<id>/ |
5 |
| kind of node | a new primitive of the graph | Rust | functions/graph/src/kinds/<id>/ |
6 |
| element of the pulpit | a new way to show or set a value | Rust and JavaScript | functions/canvas/src/elements/<id>/ and functions/ui/window/ui/pulpit/elements/ |
7 |
| window of the workshop | a new tool in a window | JavaScript | functions/ui/window/ui/host/<id>/ |
8 |
| program | to build a program of nodes and a pulpit — by hand in the workshop or by script | graph, pulpit; the script — Python, package ogas |
data/processes/<id>/; the script — anywhere |
9, 12 |
| network of the assistant | to give the assistant of the workshop one more provider of networks | Python | modules/<id>/, with "role": "assistant_network" |
13 |
1 A module in Python: the first module
1.1 The folder
The template is made by module.create; by hand, a folder is put into modules/<id>/ (Modules, section 2):
modules/shout/
manifest.json ports and parameters: the only source of the contract
main.py the code
requirements.txt Python dependencies, each with its version: name==1.2.3
locales/ru.json words for the person
locales/en.json
1.2 The manifest
{
"format": "ogas.module/1",
"id": "shout",
"version": "1.0.0",
"name_key": "module.shout.name",
"description_key": "module.shout.description",
"runtime": "python",
"entrypoint": "main.py",
"inputs": [{ "name": "input", "required": true, "type": "text" }],
"outputs": [{ "name": "success", "type": "text" }],
"input_policy": "all",
"parallelism": 1,
"process": "step",
"execution_semantics": "idempotent",
"retry": { "max_attempts": 1, "delay_ms": 0, "exponential": false },
"parameters": {
"times": { "type": "integer", "default": 2, "min": 1, "max": 10 },
"loud": { "type": "boolean", "default": true },
"mark": { "type": "string", "default": "excl", "choices": ["excl", "dot"] }
},
"permissions_info": { "network": false, "filesystem": "module-and-run-only" },
"timeout_ms": 10000,
"runtime_python": ">=3.11"
}
Types of a parameter: string, integer, number, string[], color, boolean — yes or no, model — a neural network of one kind ({ "type": "model", "kind": "llm", "default": "name" }). choices is a closed set of codes. In process: step — an OS process for a step, ending after the step; long — lives on and keeps the network in memory; service — lives while the program is on and gives data by itself. resources.gpu_mb is how much video memory the module takes together with its neural network: by it the function of modules unloads those that stand idle, and the library tells the person whether the module, and a program with it, will start on their video card; a module that takes the video card names it honestly (1.9). suspend: true — the module keeps the memory of its node and saves it before it is unloaded (1.8).
1.3 The code
from ogas_runtime import Refusal, extract_text, parameters, reply, run_step
MARKS = {"excl": "!", "dot": "."}
def handle(params: dict) -> dict:
options = parameters(params)
text = extract_text(params.get("input"))
if not text.strip():
raise Refusal("Nothing to shout", code="no_text")
said = (text.upper() if options["loud"] else text) * options["times"]
return reply("success", {"text": said + MARKS[options["mark"]]})
if __name__ == "__main__":
run_step(handle)
stdout belongs to the protocol: a module writes its own things to stderr or with send_log. print in a module breaks the exchange.
1.4 Parameters: one default
A default lives only in the manifest: the platform puts it in before the step, and a parameter is read by its name.
| So | Not so | Why |
|---|---|---|
options["steps"] |
options.get("steps") or 30 |
or turns the person's zero into the default: «Likeness 0» becomes 60 |
options["negative"] |
options.get("negative", NEGATIVE) |
a field the person cleared must stay empty |
options.get("seed") |
options.get("seed") or 0 |
a parameter without default is optional: it is not there until the node has set it |
options["speckle"] or by_detail |
— | allowed when 0 in the manifest means «the module decides», and the hint says so |
A closed set comes as a code, not a word. The word lies in the dictionary of the module; on the pulpit the element «Choice» keeps the code in options and the word in titles (File formats, table 6). There are no tables of pulpit words in the code of a module. A pulpit switch comes as true or false into a parameter of type boolean.
A value from a variable ({ "variable": "id" }) is put in by the platform at the moment of the step and is not checked against choices. The module gets the code the pulpit put there.
1.5 What comes in
params["input"] depends on the inputs of the node (The program: graph and runs, section 6):
- one hot input — the data as it is;
- there is a cold input, or the rule
allgathered several — a record by the names of the inputs:{ "request": …, "image": … }; there is no fieldinput_portthen; - several links to one input are waited for only with the rule of the node
input_policy: "any"or through «Merge»: otherwise the second piece of data is not taken.
The data come from someone else's node and are sometimes deeper than the module expects: the answer of a question — { "question": <data>, "answer": <answer> }, a selection — a record with mask, cutout and picture, a button of the pulpit — a record by the names of the fields of carries. So a file and a value are searched for, not addressed:
picture = find_file(given, "picture", "image", media="image/")
mask = find_file(given, "mask", media="image/")
path = file_path(params, picture)
words = find_value(given, "words", "target")
find_file first looks for the named fields at any depth, then for any file. Only file_path gives the path to a file; a file from someone else's step is only read (The program: graph and runs, 24.4).
A folder or a file the person typed on the pulpit is read through person_path(said): D:\Photos becomes /mnt/d/Photos.
1.6 What a step gives
name = "frame.png"
picture.save(new_file(params, name))
made = file_ref(name)
return reply("image", {"picture": made, "width": picture.width}, [made])
A result file is written only into the work folder (new_file) and named in the third argument of reply: so it becomes a result of the run, and the reference in the output a real one. Writing into a folder of the person does not count as a result.
1.7 Refusal and words
Refusal("a text for the journal in English", code="no_face") — a refusal with its reason; the text for the person lies in module.<id>.error.no_face of both dictionaries. retryable=True — repeat by retry.max_attempts. Any other exception is a failed step.
The words a module shows in data are taken from the dictionary: say("said.remove", target=target) reads module.<id>.said.remove in the language of the interface and fills the places {target}.
Table 3 — Keys of the dictionary of a module
| Key | What | Required |
|---|---|---|
module.<id>.name, .description — the keys named by name_key and description_key of the manifest |
name and description | the name — yes, the description — if the manifest names it |
module.<id>.param.<name>.title, .help |
the name of a parameter and its explanation: what it sets and which way to turn it | yes |
module.<id>.param.<name>.choice.<code> |
the word for a code of a closed set | yes, if there are choices |
module.<id>.input.<port>.title, .output.<port>.title |
names of the ports | yes |
module.<id>.input.<port>.help, .output.<port>.help |
hints of the ports: what a link to the port gives | yes |
module.<id>.error.<code> |
the text of a refusal | yes, for each code the module names |
module.<id>.<key> |
a text said by say |
yes, for each key the module names |
Each dictionary speaks its own language in the person's words: the name of a sampler is written with an explanation — «Euler a — lively, with scatter».
1.8 A long module and the memory of a node
run_long(handle, on_suspend=save) keeps the module up; the handler gets cancelled as well — a threading.Event. It is looked at between parts of the work, raising Cancelled. send_progress(share) moves the bar on the pulpit.
A module is one for all its nodes, so memory is kept by node:
memories = NodeMemories(lambda folder, was_saved: Node(folder, was_saved))
def handle(params, cancelled):
node = memories.of(params)
...
def save():
for node in memories:
write_memory(node.folder, node.values, mark)
run_long(handle, on_suspend=save)
Rules of memory:
- memory lies in
state_dir(params)and is read only whenresumed(params): otherwise it may be someone else's or half-written; - what depends on the weights is checked by
model_mark(path): other weights give another mark, andread_memoryreturnsNone; - writing is only whole:
write_memoryorwith whole_file(path) as aside:; suspend: truein the manifest is a promise: a module without memory does not set this field;- at
on_suspendthe module is no longer in the middle of a step: the platform does not unload a working module.
1.9 Neural networks
A model is chosen by a parameter of type model; chosen_model(params, "network") gives the path or None. Neural networks lie in models/<kind>/<name> — a file, a folder or a link (Modules, section 11).
A module is written for a family of models, not for a version: the family is recognised by the files of the network (model_index.json, the keys of the weights), common loaders are taken, and anything unfamiliar is refused in words: Refusal(…, code="unknown_network"). Heavy libraries are imported inside functions, so that the module comes up and answers ping quickly. At the start of the file stands os.environ.setdefault("HF_HUB_OFFLINE", "1"): a step downloads nothing.
Where to get the default neural network, a module says in the field sources of its parameter — then the library of the program shows that it is missing and downloads it at a button (Modules, section 11):
"network": {
"type": "model", "kind": "llm", "default": "Qwen2.5-7B-Instruct-bnb-4bit",
"sources": [{ "source": "huggingface", "id": "unsloth/Qwen2.5-7B-Instruct-bnb-4bit" }]
}
source is the kind of source: huggingface, civitai or ogas; id — how the source names the thing; the optional files — which files to take; without it, those the source offers itself: .safetensors and what comes with them. The default name is the one under which the network will stand in the folder of networks. The form of the field is checked by the platform when it accepts the module (module.validate).
ogas_models gives what is common: TensorMemory — memory with tensors, drawing_progress — the bar and cancelling for diffusers pipelines, thumbnails — thumbnails of pictures from disk for the pulpit, hub_snapshot — weights from the Hugging Face cache, text_network, chat_prompt, layers_of and Light — for text networks. Its dependencies (torch, safetensors, pillow, transformers) the module names in its own requirements.txt.
1.10 What a module brings with it
A program — in bin/, the path to it is given by module_dir(); licences — in licenses/. What was built on your own machine must not keep the home folder: Rust is built with RUSTFLAGS="--remap-path-prefix=$HOME=/build".
1.11 The check
sdk/ogas-sdk check modules/shout
sdk/ogas-sdk try modules/shout --input '{"text": "hello"}'
The check reads the files of the module without starting it and names the place and what to do (table 4). The same for all modules of the repository is asked by make ci.
Your own tests lie in test_<id>.py beside main.py. ogas_check.execute(folder, input, parameters) carries one step from start to stop, putting in the defaults of the manifest as the platform does; for a function of the module called directly, the parameters are given through with_defaults(manifest, parameters).
Table 4 — What ogas_check.py names
| Breach | How to fix it |
|---|---|
| a second default of a parameter | read it by name (1.4) |
| a parameter not in the manifest; a parameter of the manifest nobody reads | declare it or remove it |
chosen_model on a parameter not of type model |
declare the type |
a loop of the wrong mode: run_step with process: long |
bring it in line with the manifest |
suspend: true without memory |
on_suspend, or remove the promise |
| a table of pulpit words → codes of a parameter | the code in the value, the word in titles |
| a refusal with a Russian text, or with a code that has no text in the dictionaries | the text in module.<id>.error.<code> |
say with a key that has no text |
add it to both dictionaries |
| a dependency without a version | name==1.2.3 |
bin/ without licenses/; the home folder inside a program |
1.10 |
the same function in several modules (make ci, live tasks) |
move it into the SDK |
the code goes to the network, and permissions_info.network keeps silent about it |
say so in the manifest: the person reads it before installing |
the module installs packages itself: pip, uv pip, ensurepip |
the package — in requirements.txt; the platform installs it at the person's command |
a parameter named like a secret: password, api_key, bot_token and the like |
a secret — in .env of the module folder, read by secret(name) |
an except holding only pass or continue, with no word why |
the reason on the except line, or a refusal |
a reference to a document of the platform in code or a dictionary: ОГАС with a number, docs/, a section sign |
the reason in your own words |
the module takes the video card — cuda in the code or a network through text_network of the SDK — and the manifest has no resources.gpu_mb or it is 0 |
name how much video memory the module takes with its neural network: otherwise the sharing of the video card and the library know nothing of it |
a module with the SDK writes to stdout: print without file=, sys.stdout.write |
send_log or print(…, file=sys.stderr): stdout belongs to the exchange; a module that carries the exchange itself, without the SDK, owns its stdout |
reaching inside the platform: OGAS_SOCKET, OGAS_LAUNCH_TOKEN, core/data, config/user.json, layout.json, functions.json |
take what is needed from the step, your own folder, the folder of the run and the memory of the node (1.5, 1.8) |
a bare number in a call of a network that affects the result: guidance_scale=6.0, temperature=0.7, num_inference_steps=30 and the like |
a parameter of the manifest — the person turns it; an inner quantity — a constant with a name and a reason |
| a module, parameter, choice or port has no name or hint in one of the dictionaries; a text in the manifest | the text named by name_key (and description_key, if any), module.<id>.param.<name>.title and .help, .choice.<value>, module.<id>.input.<name>.title and .help — in both dictionaries (table 3) |
| the dictionaries hold different texts | one and the same set in ru.json and en.json |
| a text of the Russian dictionary in Latin letters without a single Russian letter; a word of development in it — «пакет», «прогон», «итерация», «слот» and the like | write it in Russian in the person's words: «данные», «запуск», «круг» |
2 Reference of the SDK for Python
It lies in sdk/python, on the PYTHONPATH of a module.
Table 5 — ogas_runtime: the loop and the exchange
| Name | What it does |
|---|---|
run_step(handle) |
the loop of a step module: handle(params) -> reply |
run_long(handle, on_init, on_suspend, on_configure) |
the loop of a long module and of a service: handle(params, cancelled) -> reply |
send_progress(ratio, execution_id=None) |
the bar: a share from 0 to 1 |
send_log(level, message) |
a line of the journal: info, warn, error |
send_partial(execution_id, port, data) |
a part of the result of a step under way |
send_emit(port, data, node_id=None) |
data of a service outside a step |
send_signal(name, data=None, key=None) |
a signal to its own program |
Refusal(message, retryable=False, code=None) |
a refusal of a step |
Cancelled |
raised when cancelled is set |
write_message(message), write_result(request_id, result), write_error(request_id, message, error_code, retryable) |
the exchange by hand: only for a module with its own loop instead of run_step and run_long |
Table 6 — ogas_runtime: reading a step
| Name | What it gives |
|---|---|
parameters(params) |
the parameters of the step with the defaults of the manifest |
find_file(given, *names, media="") |
a reference to a file at any depth |
find_value(given, *names) |
the first non-empty value under these names |
extract_text(payload) |
the text of the input; an input without text — a refusal |
file_path(params, reference) |
the path to the file of a reference, or None |
chosen_model(params, name) |
the path to the neural network of a parameter, or None |
person_path(said) |
a folder of the person as the module sees it; refusal no_folder |
module_dir(), module_path(path) |
the folder of the module; a path relative to it |
secret(name) |
a secret of the module — a service key, a token — from the file .env of its folder, a line NAME=value; None if it is not there |
run_dir() |
the folder of the run |
Table 7 — ogas_runtime: the answer, memory, words
| Name | What it does |
|---|---|
reply(port, output=None, files=()) |
the answer of a step |
file_ref(name, media_type=None) |
a reference to a file of the work folder |
new_file(params, name), work_dir(params) |
the path of a new file, with its folders made; the work folder |
state_dir(params), resumed(params) |
the folder of the memory of the node; whether it is whole |
NodeMemories(make) |
memory by node: .of(params), iteration, .clear() |
read_memory(folder, mark=""), write_memory(folder, values, mark="") |
the small memory of a node in JSON |
whole_file(path) |
writing whole or not at all, a with block |
model_mark(*paths) |
the mark of the weights for memory |
say(key, **values) |
a text of the dictionary of the module in the language of the interface |
parameter_value(name, value) |
the value of a parameter as the node takes it: type, limits and set of the manifest; otherwise ValueError — a module that sets a parameter itself takes nothing the person could not have given |
titled_setting(said, settings, key) |
the setting named by its own name or by its label on the pulpit, in any language of the module; None if there is none |
axis_values(said, current), sweep_grid(axes) |
the values of an axis of a sweep as the person writes them: through «;», a range «from..to/step», words through «|», «yes; no»; all combinations of the axes |
Table 8 — ogas_models
| Name | What it does |
|---|---|
TensorMemory(folder, was_saved) |
the memory of a node with values and tensors: .made_with(mark), .write(); made through NodeMemories(TensorMemory) |
drawing_progress(cancelled, steps) |
the value of callback_on_step_end of a diffusers pipeline |
thumbnails(params, paths, side=512) |
thumbnails of files from disk: references for viewing on the pulpit |
hub_snapshot(repo) |
the folder of weights in the Hugging Face cache; refusal no_weights |
text_network(path, precision) |
a text network and its tokenizer from a folder of the library, the precision of numbers bfloat16, float16, float32 or auto; refusal unknown_network |
chosen_layers(said, count) |
layers of the network from a word: auto — the middle of the network, 14, 10-18, 12, 15, 18; refusal bad_layers |
chat_prompt(tokenizer, messages) |
a conversation as the network reads it: a last line of the network closes its turn, otherwise the turn is open; a template without the role system gets it in the first line of the person; the thinking mode is off |
layers_of(model) |
the decoder layers of a text network of any family; refusal unknown_network |
Light(vectors, about) |
an addition to the outputs of layers at every token: .on(model) — a with block, .strength, .answer_only, .mask, .with_other(light, share), .only(layers), .write(path), Light.read(path, mark); an addition from other weights — refusal other_network |
Table 9 — ogas_check
| Name | What it does |
|---|---|
module_problems(folder) |
what breaks the contract, by the files of the module (table 4) |
rules_problems(folder) |
what breaks the rules of the platform any module keeps, by its files (tables 4 and 12) |
words_problems(folder) |
what is missing from the texts the person reads about the module, and what is wrong in them (table 4) |
development_words(text), untranslated(key, text) |
words of development in a text for the person; a Russian text left untranslated |
copies(folders) |
functions repeated in several modules |
execute(module, payload, params, iteration, work, models) |
one step from start to stop |
with_defaults(manifest, given) |
the parameters as the platform gives them |
start(module, work), init(process), send(process, message), read_json(process), stop(process) |
a conversation by the protocol, by hand |
module_environment(folder, work) |
the environment the platform gives |
folder_of(module) |
the folder of a module by name or by path |
main(folders) |
the command ogas_check.py |
3 A module in Rust
A module in Rust is an executable file with "runtime": "native" and "entrypoint" — the path to it in the module folder. The SDK is the package ogas-module in sdk/rust; it depends only on serde_json and is built apart from the platform:
[dependencies]
ogas-module = { path = "../runtime/rust" }
serde_json = "1"
use ogas_module::{reply, run_step, Refusal, Reply, Step};
use serde_json::json;
fn handle(step: &Step) -> Result<Reply, Refusal> {
let text = step.input()["text"].as_str();
let text = text.ok_or_else(|| Refusal::new("Nothing to shout").code("no_text"))?;
let said = text.repeat(step.integer("times")? as usize);
Ok(reply("success", json!({ "text": said }), &[]))
}
fn main() {
run_step(handle);
}
The manifest, dictionaries, folder, memory and rules are the same as in section 1; rules 1.4–1.10 do not depend on the language. A panic in a step is a failed step, not a fallen module. A full example — a step module and a long one with the memory of a node — is sdk/rust/examples/double.rs. The example above is the file sdk/rust/examples/shout.rs, the example of section 5 — core/protocol/examples/function.rs: cargo test builds them, and make ci compares the text here with these files word for word.
Table 10 — The SDK for Rust beside the SDK for Python
| Python | Rust |
|---|---|
run_step(handle) |
run_step(handle) |
run_long(handle, on_suspend=…) |
Long::new(handle).on_suspend(…).run(); also on_init, on_configure |
cancelled.is_set() and Cancelled |
cancel.check()? or cancel.is_set(): Cancel |
options["steps"] |
step.integer("steps")?, step.number(…), step.text(…), step.boolean(…), step.parameters() |
params.get("input"), params.get("input_port") |
step.input(), step.input_port() |
chosen_model(params, name), file_path(params, ref) |
step.chosen_model(name), step.file_path(&reference) |
state_dir(params), resumed(params), work_dir(params) |
step.state_dir(), step.resumed(), step.work_dir() |
new_file(params, name) |
step.new_file(name)? |
find_file(given, "mask", media="image/") |
find_file(&given, &["mask"], "image/") |
find_value(given, "words") |
find_value(&given, &["words"]) |
person_path(said) |
person_path(said)? |
file_ref(name), reply(port, output, files) |
file_ref(name, None), reply(port, output, &[file]); the answer is read through Reply::value |
Refusal("…", code="x", retryable=True) |
Refusal::new("…").code("x").retryable() |
say("key", target=x) |
say("key", &[("target", x)])? |
NodeMemories(make) |
NodeMemories::new(make): .of(step), .memories() |
read_memory, write_memory, whole_file, model_mark |
read_memory, write_memory, whole_file(path, write), model_mark(&[path]) |
send_progress, send_log, send_emit, send_signal |
the same names |
send_partial(execution_id, port, data) |
send_partial(step, port, data) |
module_dir() |
module_dir() |
secret(name), run_dir() |
secret(name), run_dir() — Option |
| — | constants MODULE_FAILED, MODULE_CANCELLED |
The check: cargo test in the package of the module; the contract by files — the same ogas_check.py (the manifest, dependencies, what is brought along). How the SDK checks itself — sdk/rust/tests/protocol.rs.
4 A module in another language
The protocol is read in Methods of modules, section 1: lines of JSON through stdin and stdout, init with { "status": "ready", "protocol": 1, "capabilities": […] }, execute, cancel, ping, suspend, configure. The model of behaviour is the sources of the SDK; a conversation with a module is checked by ogas_check.start, send and read_json.
5 A function of the platform
A function is a separate OS process and a Cargo package; the folder of a new function enters the workspace by itself. The rules — Rules of development, 2.3–2.6. The make-up of the folder — The make-up of the platform, Appendix A, A.3.
- The folder
functions/<id>/withCargo.toml,src/main.rs,src/state.rs,src/codes.rs,src/constants.rs,locales/{ru,en}.json. function.json— what the function is (File formats, section 3):requires,optional,contract,restart.- An entry in the register
core/data/functions.json. main.rsonly starts and hands out:
use ogas_protocol::{run_function_loop, RpcError, StartedFunction};
use serde_json::{json, Value};
fn handle(method: &str, _params: Value) -> Result<Value, RpcError> {
match method {
"function.health" => Ok(json!({ "status": "ok" })),
_ => Err(RpcError::domain(
ogas_protocol::codes::METHOD_NOT_FOUND,
format!("Unknown method {method}"),
false,
)),
}
}
fn main() {
let StartedFunction { client, .. } =
ogas_protocol::start_function(env!("CARGO_PKG_VERSION"), |spec, _| {
spec.queries = vec!["function.health".into()];
spec.commands = vec![];
spec.events = vec![];
});
run_function_loop(
client,
move |method, params, _| handle(method, params),
|_| {},
);
}
start_function reads the environment, connects to the kernel and introduces itself; if that fails, the function ends with its reason. The line spec.commands = vec![…] is read by the check of the contract too, so the names are listed here and not gathered by code. The description gets spec and the installation folder: spec.locales, spec.surfaces, spec.capabilities are added there as well.
The handler of each request runs in its own thread. Other functions are called through client.request(method, parameters, QUERY_TIMEOUT): there are no direct ties between functions. A refusal is RpcError::domain(code, text, retryable); codes are declared in src/codes.rs, and given their text in Error codes and the dictionaries. Settings — settings.schema.json and its registration at the start of main; without the function of settings, the defaults of the schema work.
Every method and event is written into the contract, docs/contract/: make ci compares the list of the function with the documents. Code common to two functions goes into core/protocol, not copied.
6 A kind of node
A kind is justified only if it is a primitive: that is rule ЗС-10 in Kinds of nodes. The folder functions/graph/src/kinds/<id>/ holds:
- the description
kind.json— what the kind says of itself:family,inputs,outputs,template.parameters— what a new node begins with,fields— what the person sets, the iconglyph; the engine, the checks and the window ask it and keep no tables; - the code
mod.rs—pub(crate) const KIND: Kind = Kind { id: ID, description: include_str!("kind.json"), step: Some(step), ..NOTHING };and the function of the step;check— the node's own check,hand_out— the value of a cold input; - the words of the kind in
locales/{ru,en}.jsonand the checks intests.rs.
A kind is entered by a line in KINDS of the file kinds/mod.rs, the only list, and by a card in Kinds of nodes.
7 An element of the pulpit
The set is closed and is extended by the platform (The workshop, 6.2).
- The folder
functions/canvas/src/elements/<id>/:element.json—kind,family(input,show,action,decoration),bindings,parameters,size,min_size,states;locales/{ru,en}.json. A parameter hashow:number,text,choice,list,image,model_kind. - An entry in
ELEMENTSof the fileelements/mod.rs. - The drawing —
functions/ui/window/ui/pulpit/elements/<id>.jswithdraw(state, element, { name })and a line inindex.js; what it shows for a look —shows. - A row in table 6 of File formats and a description of the element in the stub
functions/ui/window/ui/tests/fixtures.js: otherwise the page tests draw it as unsound.
A binding to a container is declared as "objects": ["group"]: the check, the mark and the carrying over into a piece work by the description of the kind. A label and a hint (label, hint) are there on every element except a decoration.
8 A window of the workshop
A window is the folder functions/ui/window/ui/host/<id>/ with index.js (WINDOW: kind, drawing, the view, the panel, fields, tests), words locales/{ru,en}.json, a style and checks; a line in host/index.js, the only list of windows. Colours and sizes are tokens of style.css, words go through t(key), requests through rpc of lib/rpc.js, writing the state through commit(). The page is built into the window program: after a change make window is needed. The page tests run in the window itself: in the stub stubbedPlatform the page calls the platform by itself as well, so a call is looked for by its method, not taken as the last one.
9 A program by script
A script builds a program in a working platform through the door for programs. The door is closed until the person opens it in the settings of the platform («Door for programs» → «Open»); the platform writes the port and the key into gate.json of the data folder, and Platform.connect() finds it by itself — in the data folder of the installation where the SDK lies, or in the one named by the variable OGAS_DATA_DIR. A program with the key of the door can do in the platform everything the person does in the workshop, except stopping the platform and changing the door itself (Methods of the platform services, section 5).
from ogas import Platform, Program
program = Program("Hello", "Writes the name into the answer.")
program.variable("name", "text", "")
program.variable("said", "text", "")
program.event("go", source="pult")
program.node("say", "write", {"variable": "said", "mode": "replace"})
program.edge("go", "say", target_port="value")
window = program.window("window_1", "Hello", sheet=(560, 300))
box = program.group("g_main", "Hello", window)
program.element("field_name", "field", box, {"value": "var:name"}, "Name", "Whom to greet.", (24, 48, 480, 52), writes="typing")
program.element("button_go", "button", box, {"act": "event:go", "carries": {"text": "var:name"}}, "Say", "Write the name into the answer.", (24, 112, 200, 52))
with Platform.connect() as platform:
print(program.save(platform))
sdk/ogas-sdk run build.py
Table 11 — The package ogas
| Name | What it does |
|---|---|
Platform.connect(data_dir=None, port=None, key=None) |
enter the platform; without a port and a key they are read from gate.json |
Platform(port, key, host, client) |
the same, when the port and the key are known |
platform.call(method, params, timeout, command=True) |
one request; a command gets its own command_id; a refusal is raised as the exception Refused |
platform.attempt(method, params) |
the same request, a refusal as the answer { error, message, data } |
platform.programs(), platform.program_id(name) |
the person's programs; the id of a program by its name |
platform.catalog(language) |
kinds of nodes with inputs, outputs, fields and words, elements of the pulpit, types of variables |
platform.events |
the last events of the platform that came to the connection |
Refused |
a refusal of the platform: method, code, message, data |
data_folders(), read_gate(data_dir) |
where to look for the data folder; what gate.json says |
Program(name, description) |
a program: node, event, module, edge, variable — the graph; window, tab, group, element — the pulpit |
program.lay_out() |
place the nodes the script gave no place, from left to right by their distance from the events |
program.save(platform) |
switch the program off if it is on, write the graph, check the frames of the groups, write the pulpit, check the program; the answer — { process_id, nodes, edges, elements, validate } |
FramesMeet |
an exception of save: frames of groups run into each other or out of the sheet; crossing names them |
frames_meeting(groups, shown) |
the same check of frames by the answer of canvas.preview |
What to remember in the graph:
- a node of a module has one hot input; the rest are cold ones from «Variable» nodes, or parameters
{ "variable": "id" }; - a write where several paths meet — with
input_policy: "any"; - what the next step reads is written in a chain: the output of a «Write» node carries the same data, and a choice comes after the write;
- a button carries data in the field
carries; input fields write their variable themselves; - a step after which what it was about must be read again gives that in its output too — a number, a key: the next step gets the data of its output, not of the button.
What to remember when building and trying:
- a program that is on is not saved:
process.saveanswersprocess_running— firstprocess.program.off(Program.savedoes it itself); - the module was changed after the program was saved — save the program again: a module that differs from what is written in
modules.lock.jsonis not started by the platform, and the window says that the files of the module changed; - an open face of a program keeps the pulpit read when it was opened: what a script wrote shows when the face is opened anew;
- a «Label» is one line: a paragraph is made of several labels one under another;
- the frame of a group on the page is counted by the function of the pulpit — a head and margins around what lies in the group (File formats, section 7) — so groups side by side are placed with a gap wider than two margins, or the frames run into one another;
Program.savelooks at them before writing, by theboxof the containers in the answer ofcanvas.preview(Methods of the workshop and the pulpit).
10 The rules of the platform in a module
A module is a part of the platform and keeps its rules (Rules of development, 1.2.1); how each rule applies to a module is shown by Appendix A of the Rules of development, and the threats a module guards against — by Protection, section 3. Table 12 says what follows from them for the author of a module and who keeps it: what can be seen by the files is named by ogas_check.py (table 4), the rest is kept by the author. The check only names a breach and changes nothing in the module.
Table 12 — The rules of the platform and a module
| Rule | What follows for the module | Who keeps it |
|---|---|---|
| Rules of development, 2.1 | a module is one task; main.py starts the module and hands the step to whoever's task it is; a subtask is a file of its own |
the author |
| Rules of development, 2.2 | the default of a parameter — only in the manifest, words — in the dictionaries of the module; a value that affects the result is a parameter, an inner quantity is a constant with a name and a reason | ogas_check.py — a second default, a text in the manifest, a bare number in a call of a network; the rest, the author |
| Rules of development, 2.3, 2.6; Protection, T-02, T-03 | a module knows only the protocol: it takes files from the step, its own folder, the folder of the run and the memory of the node, not from others' folders and the data of the platform; it does not connect to the kernel | ogas_check.py — names of the kernel and of files of the platform in the code; a path beyond the allowed folder is refused by the platform; the rest, the author |
| Rules of development, 2.4 | a module writes only its own: result files, the memory of its node, a folder the person named; files that came in the data it only reads | the platform — data files come read-only; the rest, the author |
| Rules of development, 2.5, 2.10 | a module is a folder: manifest, code, dictionaries, its own checks; to add a module is to put the folder, to delete it is to remove the folder | the platform reads the list of modules from the folder; the make-up of the folder — ogas_check.py and the author |
| Rules of development, 2.7 | everything that affects the result is named as a parameter; the course of a long work — send_progress, what is said for the journal — send_log, a failure of a service between steps — send_log with the level error |
ogas_check.py — a parameter nobody reads, and a bare number; the rest, the author |
| Rules of development, 2.8, 2.9 | a parameter nobody reads is removed; what several modules share is taken into the SDK | ogas_check.py |
| Rules of development, 2.11 | in code, comments and dictionaries there are no references to documents of the platform: the reason is told in your own words | ogas_check.py |
| Rules of development, 3.1; Protection, T-07 | packages — only from requirements.txt with versions, installed by the platform at the person's command; a module deletes the person's data only at their request or by a parameter the person sees |
ogas_check.py — installing and versions; deleting — the author |
| Rules of development, 3.2 | not done — a refusal with a code and a text; a failure skipped on purpose is explained on the except line; the unfamiliar is not read as familiar; a refusal leaves nothing half-written without a mark |
ogas_check.py — a silent skip and the text of a refusal; the rest, the author |
| Rules of development, 2.7, 3.3 | what the module needs from the computer is said in the manifest: video memory — resources.gpu_mb |
ogas_check.py — a module with the video card and without gpu_mb |
| Rules of development, 3.3; Protection, T-01, T-06 | the network is declared in permissions_info.network; execution_semantics is honest: what sends letters, pays or writes outside is non_idempotent |
ogas_check.py — the network; repeating — the author |
| Rules of development, 3.4 | the name, the description, the labels and hints of parameters, choices and ports, the texts of refusals and of say — in both dictionaries, one set; Russian is written in Russian, in the person's words |
ogas_check.py |
| Rules of development, 3.5 | a file that outlives a step is written whole: into a temporary one beside it and by renaming (whole_file, write_memory); unloading saves what the work will go on from |
ogas_check.py — suspend without memory; the rest, the author |
| Rules of development, 4.5 | ready means checked: ogas_check.py is clean, the own checks test_<id>.py pass, a step is carried through execute and the result looked at with your own eyes |
the author |
| Protection, T-04 | stdout belongs to the exchange: a module with the SDK writes its own to stderr or send_log |
ogas_check.py; a line that is not a message is refused by the platform |
| Protection, T-05 | a secret — in .env of the module folder, read by secret(name); not a parameter, not a journal entry and not result data |
ogas_check.py — the name of a parameter; the rest, the author |
| Protection, T-08 | big data go as result files, not in the data of a step; a long work tells its course and hears the cancel | the limits are kept by the platform; the rest, the author |
Sections 4 and 5 of the Rules of development — the order of development and the documentation — are kept by whoever runs the platform; for a module, of them follows only the confirmation of readiness by a check (4.5).
11 What is checked by itself
Table 13 — Checks
| Command | What it checks |
|---|---|
python3 sdk/python/ogas_check.py <folders> |
the contract of a module and the rules of the platform, by files (tables 4 and 12) |
make ci |
format, clippy, the rules of scripts/check_rules.py, tests; the reference of the SDK in this guide is compared with the code: a name of the SDK, a kind of node, a source of an event or an element of the pulpit that is missing here does not pass |
make build |
required before a live check: the functions are taken from target/debug |
make window, then the page checks in the window of the sandbox (functions/ui/window/ui/tests/run.js) |
the page in the window itself: styles, layout, events |
sdk/tests/live/run.sh <task> |
a whole live task |
12 The program: documents, nodes and the pulpit
A reference of what a program is built of — by hand in the workshop or by script (section 9). Everything there is is named here, with its form in the document; the exact rules of each are where the link leads.
12.1 The documents of a program
A program is the folder data/processes/<process_id>/ (File formats, section 5):
- the graph —
process.json, formatogas.process/4:id,name,description,nodes,edges,variables,groups; the owner is the function of the graph, it is written byprocess.saveand the workshop (File formats, 5.1); - the pulpit —
workspace.json, formatogas.workspace/3:groups— containers,elements— elements, the optionalthemeandbindings; the owner is the function of the pulpit, it is written bycanvas.saveand the workshop (File formats, section 7); - the settings of this process over the common ones —
settings.json(File formats, section 8); the versions and prints of modules —modules.lock.json(File formats, section 9); - the remembered values of variables (
remember: true) —state.json, written by the platform (File formats, 5.2); the files of the process, pictures and looks of the pulpit lie beside (File formats, 16.3, 7.2).
A program is handed over as an export folder (File formats, 10.1): with the modules it takes and the processes it calls. A showcase hands out programs, modules, neural networks and pieces of interface as things ogas.catalog/1 (File formats, 17.1); a piece is a container of the pulpit with everything in it, the document ogas.piece/1 (File formats, 7.4).
12.2 Node, link, variable
{
"nodes": [
{ "id": "go", "kind": "event", "x": 0, "y": 200, "parameters": { "source": "pult", "overlap": "skip" } },
{ "id": "read", "kind": "module", "module_id": "cdr_svg", "x": 280, "y": 200, "parameters": { "precision": { "variable": "precision" } } },
{ "id": "keep", "kind": "write", "x": 560, "y": 200, "parameters": { "variable": "parts", "mode": "replace", "path": "count" } }
],
"edges": [
{ "id": "go.event-read.drawing", "source_node": "go", "source_port": "event", "target_node": "read", "target_port": "drawing" },
{ "id": "read.drawing-keep.value", "source_node": "read", "source_port": "drawing", "target_node": "keep", "target_port": "value" }
],
"variables": [
{ "id": "precision", "type": "number", "initial": 1, "remember": true },
{ "id": "parts", "type": "number", "initial": 0, "remember": false }
]
}
- A node —
id,kind(12.3), its placex,y,parameters; a module also hasmodule_id; optional arename,description,input_policy(all— wait for all inputs,any— work with each),region— the frame the node stands in (The program: graph and runs, sections 4, 6, 11). - A parameter is taken from a variable by the record
{ "variable": "<id>" }: the value is put in at the moment of the step (The program: graph and runs, section 15). - A link goes from an output to an input:
source_node,source_port,target_node,target_port; the ports are named by the kind of the node (12.3) or the manifest of the module, and a module has one more output,failed(The program: graph and runs, 12.4). - A variable —
id,type— one of the kinds of data:text,number,boolean,time,list,image,audio,video,file,json;initial,remember, the optionalscope: "run"— its own for each run (The program: graph and runs, section 7). - A group is a layer over nodes with a face of its own (The program: graph and runs, section 17); a call of another program by a
processnode is unfolded before the run (The program: graph and runs, section 18).
12.3 Kinds of nodes
Each kind of node is described by a card in the catalogue — Kinds of nodes, Appendix A: what it does, the inputs and outputs with what comes to each, what the person sets, how many ports there are and where the rules are. An overview of the catalogue is there too, tables 4–13. What a kind gets when it is placed and what choices its fields have is its description functions/graph/src/kinds/<kind>/kind.json: inputs, outputs, template.parameters, fields with choices; the workshop shows the same on the node.
Five kinds are enough for a first program: event — the work begins with it (for a button of the pulpit source: pult), module — the work of a module, write — put data into a variable the pulpit shows, variable — take the value of a variable as a cold input, show — show data on the node and on the pulpit.
12.4 The pulpit: containers and elements
{
"groups": [
{ "id": "window_1", "name": "Site", "holds": "tabs" },
{ "id": "tab_waiting", "name": "Waiting for a look", "parent": "window_1", "screen": { "sheet": [1240, 900] } },
{ "id": "g_list", "name": "List", "parent": "tab_waiting" }
],
"elements": [
{ "id": "button_refresh", "kind": "button", "group": "g_list", "label": "Refresh", "hint": "Ask the server what is waiting.",
"bindings": { "act": "event:refresh" }, "screen": { "place": [24, 48], "size": [240, 52] } }
]
}
- A container —
id, the optionalname,parent;holds:together— all at once (the default),tabs— one at a time, as tabs;screen.sheet— the sheet of the page[width, height];bindings:shown— a variable of typeboolean,open— for tabs: which is open;look— the look (File formats, section 7). A window is a container withoutparent. - An element —
id,kind,group,label— its label,hint— its hint: what it sets and which way to turn it,bindings,parameters,screen—placeandsizein units of the sheet,look. - A label and a hint are there on every element except a decoration: the window shows the hint when the pointer is held over it and reads it to a screen reader.
The elements with their bindings and parameters — table 6 of File formats, their states for looks — table 7 there; what a kind of element takes is its description functions/canvas/src/elements/<kind>/element.json. A binding names an object of the graph: var:<id> — a variable, event:<node_id> — an event of the pulpit, cancel:, retry: — stop and retry it, status: — how the run goes, progress: — the course of a step of a module, out:<node_id>/<output> — the last thing an output gave, show: — what «Show» showed, ask: — an open question, signal: — a signal a node waits for, program — whether the program is on; what each object means and what the pulpit does with it — The program: graph and runs, table 5.
12.5 A parameter of a module on the pulpit
A parameter of a module on the pulpit is a variable the node takes into the parameter by the record { "variable": "<id>" }, and an input element bound to it. The element is chosen by the type of the parameter in the manifest; the limits, the choices and the first value come from there too, the label and the hint from the dictionary of the module (module.<id>.param.<name>.title and .help): the person sees on the pulpit the same words as on the node.
| Type of the parameter | Variable | Element | Its parameters |
|---|---|---|---|
integer, number |
number |
«Number» | min, max from the manifest, step |
boolean |
boolean |
«Switch» | — |
string with choices |
text |
«Choice» | options — the codes of the manifest, titles — the words .choice.<code> of the dictionary |
string |
text |
«Field» | lines, if the text is long |
string[] |
list |
«Rows» | — |
model |
text |
«Choice» | models — the kind of neural networks of the parameter |
color |
text |
«Field» | — |
A long set of settings is written by the script itself — from the manifests of the modules: so a field does not drift from its parameter when the module changes. A button with the binding resets returns the fields of its container to their first values.
12.6 The requests a program is built and tried with
A script and a check call the platform with the same requests as the workshop; each is described in the contract of its function:
- the graph — Methods of the graph:
process.list,process.create,process.open,process.save,process.validate,process.group.wrap— gather nodes into a group,process.exportandprocess.inspect— hand over and look at what was brought; to switch on and try —process.program.on,process.program.off,process.event.fire,process.variable.set,process.variable.list,process.run.status,process.snapshot,process.runs.list,process.run.stop,process.run.retry,process.run.resume,process.ask.list,process.ask.answer,process.signal.send,process.message.send,process.program.list; - the pulpit — Methods of the workshop and the pulpit:
canvas.get,canvas.save; - modules — Methods of modules:
module.install— install the Python environment of a process; - settings and the journal — Methods of the platform services:
settings.set,settings.reset,observe.logs.query.
13 A network of the assistant
The assistant of the workshop thinks with the network the person chose. A network is a module with "role": "assistant_network": the input request, the output reply. Ready networks come with OGAS SDK (sdk/networks): by the OpenAI protocol — spoken by OpenAI, DeepSeek, OpenRouter, Mistral, Groq, Qwen, xAI and the local servers Ollama, LM Studio, llama.cpp, vLLM — Anthropic, Gemini, GigaChat, YandexGPT and a local network from the folder of networks. The panel of the assistant puts the chosen one into the folder of modules. Another provider is added by a module of your own with the same contract.
The request that comes to the module:
{ "system": "…",
"messages": [ { "role": "user", "text": "…" },
{ "role": "assistant", "text": "", "calls": [ { "id": "c1", "name": "programs_list", "arguments": {} } ] },
{ "role": "tool", "call_id": "c1", "name": "programs_list", "text": "…" } ],
"tools": [ { "name": "draft_create", "description": "…", "schema": { "type": "object" } } ],
"limit_tokens": 4000 }
The answer of the module — reply("reply", { text, calls, usage: { input_tokens, output_tokens }, model }). What all networks share lies in ogas_assistant; the module only has to put the request into the language of its service and the answer back. The module reads the key of the service by secret("NAME") from its .env, and names its secrets in the field secrets of the manifest: by it the panel of the assistant asks the person for the key and writes it into .env.
Table 14 — ogas_assistant
| Name | What it does |
|---|---|
request_of(params) |
the request of the assistant the step carries; without it — refusal bad_answer |
answer(text, calls, input_tokens, output_tokens, model) |
the answer of the network, ready to be given by the step |
arguments_of(raw) |
the arguments of a call as an object — from an object or from its JSON text |
new_call_id() |
the id of a call when the service gave none |
post(url, body, headers, form=None, verify=True) |
a POST to the service and its JSON answer; no answer — refusal unreachable, which the platform repeats; an answer with an error — service_refused with the words of the service |
The refusal codes of a network are no_key, no_model, unreachable, service_refused, bad_answer; their texts lie in the dictionaries of the module, as with any refusal (1.7).
14 Examples
sdk/examples/ holds modules and programs that run. word_count is a module with a parameter and a refusal, and a program for it built by script through the door:
sdk/ogas-sdk try sdk/examples/word_count/modules/word_count --input '{"text": "one two two"}'
cp -r sdk/examples/word_count/modules/word_count modules/
sdk/ogas-sdk run sdk/examples/word_count/build.py
Programs of several modules lie in sdk/tests/live/: a backup copy of a folder (backup), reading invoices (invoices) and others; networks of the assistant — in sdk/networks/.