OGAS SDK
Руководство для тех, кто пишет модули и собирает программы сценарием: что входит в OGAS SDK, как написать модуль на Python и на Rust, как проверить его и попробовать без платформы, как собрать программу и выложить вещь на витрину. Здесь же — как расширяют саму платформу: функция, вид узла, элемент пульта, окно.
Введение
Здесь собрано всё, что нужно написать часть платформы, не заглядывая в готовые. Если для работы пришлось смотреть в чужой модуль — это недостаток этого документа: правят его. Договор сообщений — Методы модулей, формат манифеста — Форматы файлов, раздел 4, понятия — Модули. Новое согласуют с Правила разработки, 4.7.
Что входит в OGAS SDK. Папка sdk/ установки:
sdk/
ogas-sdk команда без установки
python/ код модуля на Python и пакет ogas
rust/ пакет ogas-module для модуля на Rust
templates/ шаблоны модулей
networks/ сети помощника — готовые модули
examples/ примеры
tests/ проверки SDK и тестовые модули
Модуль на Python пишет from ogas_runtime import …: платформа кладёт sdk/python на PYTHONPATH каждого модуля. Пакет ogas — для программ, которые говорят с платформой снаружи: сценариев сборки, редакторов, проверок. Поставить его как пакет Python — pip install -e sdk/python, и команда зовётся просто ogas-sdk.
Таблица 1 — Команда ogas-sdk
| Команда | Что делает |
|---|---|
new <id> --template <шаблон> --name <название> |
модуль из шаблона: python-basic, python-network, python-service, rust-basic |
check <папки> |
держит ли модуль свой договор — те же проверки, что в make ci (таблица 4) |
try <папка> --input <JSON> --params <JSON> |
один шаг модуля без платформы, так, как его даёт платформа; родной модуль тоже |
pack <папка> --out <папка> --author <имя> --license <SPDX> |
вещь для витрины: <id>/<версия>/item.json и files/ с суммами sha256; .env, .venv и то, что оставил Python, не берутся; модуль, не прошедший проверку, не пакуется |
status |
достаётся ли работающая платформа через вход для программ |
catalog |
из чего собирают программы: каждый вид узла и элемент пульта с устройством и словами |
run <сценарий> |
выполнить сценарий сборки программы (раздел 9) |
С ключом --json команда отвечает одним документом JSON: так её зовёт другая программа.
Таблица 2 — Что на чём пишут
| Что | Для чего | На чём | Где лежит | Раздел |
|---|---|---|---|---|
| модуль | работа, которую нельзя собрать из узлов: нейросеть, разбор файла, внешний сервис | Python или Rust | modules/<id>/ |
1–4 |
| функция платформы | новая часть самой платформы со своими методами | Rust | functions/<id>/ |
5 |
| вид узла | новый примитив графа | Rust | functions/graph/src/kinds/<id>/ |
6 |
| элемент пульта | новый способ показать или задать значение | Rust и JavaScript | functions/canvas/src/elements/<id>/ и functions/ui/window/ui/pulpit/elements/ |
7 |
| окно мастерской | новый инструмент в окне | JavaScript | functions/ui/window/ui/host/<id>/ |
8 |
| программа | собрать программу из узлов и пульта — рукой в мастерской или сценарием | граф, пульт; сценарий — Python, пакет ogas |
data/processes/<id>/; сценарий — где угодно |
9, 12 |
| сеть помощника | дать помощнику мастерской ещё одного поставщика сетей | Python | modules/<id>/, с "role": "assistant_network" |
13 |
1 Модуль на Python: первый модуль
1.1 Папка
Шаблон делает module.create; вручную кладут папку в modules/<id>/ (Модули, раздел 2):
modules/shout/
manifest.json порты и параметры: единственный источник договора
main.py код
requirements.txt зависимости Python, каждая с версией: name==1.2.3
locales/ru.json слова для человека
locales/en.json
1.2 Манифест
{
"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"
}
Типы параметра: string, integer, number, string[], color, boolean — да или нет, model — нейросеть одного вида ({ "type": "model", "kind": "llm", "default": "имя" }). choices — закрытый набор кодов. В process: step — OS-процесс на шаг и его окончание после шага, long — живёт и держит сеть в памяти, service — живёт, пока включена программа, и сам даёт пакеты. resources.gpu_mb — сколько видеопамяти модуль займёт вместе со своей нейросетью: по нему функция модулей выгружает тех, кто простаивает, а библиотека говорит человеку, запустится ли модуль и программа с ним на его видеокарте; модуль, который берёт видеокарту, называет его честно (1.9). suspend: true — модуль хранит память узла и сохранит её перед выгрузкой (1.8).
1.3 Код
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 принадлежит протоколу: своё модуль пишет в stderr или send_log. print в модуле ломает обмен.
1.4 Параметры: одно умолчание
Умолчание живёт только в манифесте: платформа подставляет его до шага, и параметр читают по имени.
| Так | Не так | Почему |
|---|---|---|
options["steps"] |
options.get("steps") or 30 |
or превращает ноль человека в умолчание: «Сходство 0» становится 60 |
options["negative"] |
options.get("negative", NEGATIVE) |
поле, очищенное человеком, должно остаться пустым |
options.get("seed") |
options.get("seed") or 0 |
параметр без default необязателен: его нет, пока узел его не задал |
options["speckle"] or по_подробности |
— | допустимо, когда 0 в манифесте значит «решает модуль», и это сказано в подсказке |
Закрытый набор приходит кодом, а не словом. Слово лежит в словаре модуля; на пульте элемент «Выбор» держит код в options, а слово в titles (Форматы файлов, таблица 6). Таблиц слов пульта в коде модуля нет. Выключатель пульта приходит как true или false в параметр типа boolean.
Значение из переменной ({ "variable": "id" }) платформа подставляет в момент шага и по choices не проверяет. Модуль получает код, который пульт туда положил.
1.5 Что приходит на вход
params["input"] зависит от входов узла (Программа: граф и запуски, раздел 6):
- один горячий вход — пакет как есть;
- есть холодный вход или правило
allсобрало несколько — запись по именам входов:{ "request": …, "image": … }; поляinput_portтогда нет; - несколько рёбер на один вход ждут только с правилом узла
input_policy: "any"или через «Слияние»: иначе второй пакет не будет взят.
Пакет приходит от чужого узла и бывает глубже, чем ждёт модуль: ответ вопроса — { "question": <данные>, "answer": <ответ> }, выделение — запись с mask, cutout и picture, кнопка пульта — запись по именам полей carries. Поэтому файл и значение ищут, а не адресуют:
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 сначала ищет названные поля на любой глубине, затем любой файл. Путь к файлу даёт только file_path; файл из чужого шага только читают (Программа: граф и запуски, 24.4).
Папку или файл, которые человек вписал на пульте, читают через person_path(said): D:\Фото становится /mnt/d/Фото.
1.6 Что отдаёт шаг
name = "кадр.png"
picture.save(new_file(params, name))
made = file_ref(name)
return reply("image", {"picture": made, "width": picture.width}, [made])
Файл результата пишут только в рабочую папку (new_file) и называют в третьем аргументе reply: так он становится результатом прогона, а ссылка в выходе — настоящей. Запись в папку человека результатом не считается.
1.7 Отказ и слова
Refusal("текст для журнала по-английски", code="no_face") — отказ с причиной; текст для человека лежит в module.<id>.error.no_face обоих словарей. retryable=True — повторить по retry.max_attempts. Любое другое исключение — неудавшийся шаг.
Слова, которые модуль показывает в данных, берут из словаря: say("said.remove", target=target) читает module.<id>.said.remove на языке интерфейса и заполняет места {target}.
Таблица 3 — Ключи словаря модуля
| Ключ | Что | Обязателен |
|---|---|---|
module.<id>.name, .description — ключи, которые называют name_key и description_key манифеста |
название и описание | название — да, описание — если манифест его называет |
module.<id>.param.<имя>.title, .help |
название параметра и пояснение: что он задаёт и куда его крутить | да |
module.<id>.param.<имя>.choice.<код> |
слово для кода закрытого набора | да, если есть choices |
module.<id>.input.<порт>.title, .output.<порт>.title |
названия портов | да |
module.<id>.input.<порт>.help, .output.<порт>.help |
подсказки портов: что даст связь с портом | да |
module.<id>.error.<код> |
текст отказа | да для каждого кода, который модуль называет |
module.<id>.<ключ> |
текст, который говорит say |
да для каждого ключа, который модуль называет |
В русском словаре слова русские: название сэмплера пишут с пояснением — «Euler a — живой, с разбросом».
1.8 Долгий модуль и память узла
run_long(handle, on_suspend=save) держит модуль поднятым; обработчик получает ещё cancelled — threading.Event. Его смотрят между частями работы, поднимая Cancelled. send_progress(доля) двигает полосу на пульте.
Модуль один на все свои узлы, поэтому память держат по узлам:
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)
Правила памяти:
- память лежит в
state_dir(params)и читается только приresumed(params): иначе она может быть чужой или недописанной; - то, что зависит от весов, сверяют по
model_mark(path): другие веса дают другую отметку, иread_memoryвернётNone; - запись — только целиком:
write_memoryилиwith whole_file(path) as aside:; suspend: trueв манифесте — обещание: модуль без памяти этого поля не ставит;- при
on_suspendмодуль уже не посреди шага: платформа не выгружает работающий модуль.
1.9 Нейросети
Модель выбирают параметром типа model; chosen_model(params, "network") даёт путь или None. Нейросети лежат в models/<вид>/<имя> — файл, папка или ссылка (Модули, раздел 11).
Модуль пишут под род моделей, а не под версию: семейство узнают по файлам сети (model_index.json, ключи весов), берут общие загрузчики, незнакомое отклоняют словами: Refusal(…, code="unknown_network"). Импорт тяжёлых библиотек — внутри функций, чтобы модуль поднимался и отвечал ping быстро. В начале файла ставят os.environ.setdefault("HF_HUB_OFFLINE", "1"): шаг ничего не скачивает.
Откуда взять нейросеть по умолчанию, модуль говорит полем sources её параметра — тогда библиотека у программы покажет, что её нет, и скачает по кнопке (Модули, раздел 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 — вид источника: huggingface, civitai или ogas; id — как источник называет вещь; необязательное files — какие файлы взять, без него — те, что источник предлагает сам: .safetensors и то, что при них. Имя по умолчанию — то, под которым она встанет в папку нейросетей. Форму поля проверяет платформа, принимая модуль (module.validate).
ogas_models даёт общее: TensorMemory — память с тензорами, drawing_progress — полоса и отмена у конвейеров diffusers, thumbnails — миниатюры снимков с диска для пульта, hub_snapshot — веса из кэша Hugging Face, text_network, chat_prompt, layers_of и Light — для текстовых сетей. Его зависимости (torch, safetensors, pillow, transformers) модуль называет в своём requirements.txt.
1.10 Что модуль приносит с собой
Программа — в bin/, путь к ней даёт module_dir(); лицензии — в licenses/. Собранное на своей машине не должно хранить домашнюю папку: Rust собирают с RUSTFLAGS="--remap-path-prefix=$HOME=/build".
1.11 Проверка
sdk/ogas-sdk check modules/shout
sdk/ogas-sdk try modules/shout --input '{"text": "привет"}'
Проверка читает файлы модуля, не запуская его, и называет место и что сделать (таблица 4). То же для всех модулей репозитория спрашивает make ci.
Свои тесты лежат в test_<id>.py рядом с main.py. ogas_check.execute(папка, вход, параметры) проводит один шаг от запуска до остановки, подставляя умолчания манифеста, как платформа; для функции модуля, вызванной напрямую, параметры дают через with_defaults(манифест, параметры).
Таблица 4 — Что называет ogas_check.py
| Нарушение | Как исправить |
|---|---|
| второе умолчание параметра | читать по имени (1.4) |
| параметр, которого нет в манифесте; параметр манифеста, который никто не читает | объявить или убрать |
chosen_model по параметру не типа model |
объявить тип |
цикл не того режима: run_step при process: long |
привести к манифесту |
suspend: true без памяти |
on_suspend или убрать обещание |
| таблица слов пульта → коды параметра | код в значении, слово в titles |
| отказ русским текстом или с кодом без текста в словарях | текст в module.<id>.error.<код> |
say с ключом без текста |
добавить в оба словаря |
| зависимость без версии | name==1.2.3 |
bin/ без licenses/; домашняя папка внутри программы |
1.10 |
одна и та же функция в нескольких модулях (make ci, живые задачи) |
вынести в SDK |
код ходит в сеть, а permissions_info.network об этом молчит |
сказать в манифесте: человек читает это до установки |
модуль сам ставит пакеты: pip, uv pip, ensurepip |
пакет — в requirements.txt, ставит платформа по команде человека |
параметр с именем секрета: password, api_key, bot_token и подобные |
секрет — в .env папки модуля, читать secret(name) |
except, в котором один pass или continue и не сказано почему |
причина в строке except или отказ |
ссылка на документ платформы в коде или словаре: ОГАС с номером, docs/, знак раздела |
причина своими словами |
модуль берёт видеокарту — cuda в коде или сеть через text_network SDK, — а resources.gpu_mb манифеста нет или он 0 |
назвать, сколько видеопамяти модуль займёт со своей нейросетью: иначе делёж видеокарты и библиотека о нём не знают |
модуль с SDK пишет в stdout: print без file=, sys.stdout.write |
send_log или print(…, file=sys.stderr): stdout принадлежит обмену; модуль, который ведёт обмен сам, без SDK, владеет своим stdout |
обращение внутрь платформы: OGAS_SOCKET, OGAS_LAUNCH_TOKEN, core/data, config/user.json, layout.json, functions.json |
брать нужное из шага, своей папки, папки прогона и памяти узла (1.5, 1.8) |
голое число в вызове сети, которое влияет на результат: guidance_scale=6.0, temperature=0.7, num_inference_steps=30 и подобные |
параметр манифеста — человек его крутит; внутренняя величина — константа с именем и причиной |
| у модуля, параметра, варианта выбора или порта нет названия или подсказки в одном из словарей; текст в манифесте | текст, который называет name_key (и description_key, если есть), module.<id>.param.<имя>.title и .help, .choice.<значение>, module.<id>.input.<имя>.title и .help — в обоих словарях (таблица 3) |
| словари держат разные тексты | один и тот же набор в ru.json и en.json |
| русский текст латиницей без единой русской буквы; слово разработки — «пакет», «прогон», «итерация», «слот» и подобные | написать по-русски словами человека: «данные», «запуск», «круг» |
2 Справочник SDK для Python
Лежит в sdk/python, на PYTHONPATH модуля.
Таблица 5 — ogas_runtime: цикл и обмен
| Имя | Что делает |
|---|---|
run_step(handle) |
цикл шагового модуля: handle(params) -> reply |
run_long(handle, on_init, on_suspend, on_configure) |
цикл долгого модуля и службы: handle(params, cancelled) -> reply |
send_progress(ratio, execution_id=None) |
полоса: доля от 0 до 1 |
send_log(level, message) |
строка журнала: info, warn, error |
send_partial(execution_id, port, data) |
часть результата идущего шага |
send_emit(port, data, node_id=None) |
пакет службы вне шага |
send_signal(name, data=None, key=None) |
сигнал своей программе |
Refusal(message, retryable=False, code=None) |
отказ шага |
Cancelled |
поднимают, когда cancelled установлен |
write_message(message), write_result(request_id, result), write_error(request_id, message, error_code, retryable) |
обмен вручную: только модулю со своим циклом вместо run_step и run_long |
Таблица 6 — ogas_runtime: чтение шага
| Имя | Что даёт |
|---|---|
parameters(params) |
параметры шага с умолчаниями манифеста |
find_file(given, *names, media="") |
ссылку на файл на любой глубине |
find_value(given, *names) |
первое непустое значение под этими именами |
extract_text(payload) |
текст входа; входа без текста — отказ |
file_path(params, reference) |
путь к файлу ссылки или None |
chosen_model(params, name) |
путь к нейросети параметра или None |
person_path(said) |
папку человека как её видит модуль; отказ no_folder |
module_dir(), module_path(path) |
папку модуля; путь относительно неё |
secret(name) |
секрет модуля — ключ сервиса, токен — из файла .env его папки, строкой ИМЯ=значение; None, если его нет |
run_dir() |
папку прогона |
Таблица 7 — ogas_runtime: ответ, память, слова
| Имя | Что делает |
|---|---|
reply(port, output=None, files=()) |
ответ шага |
file_ref(name, media_type=None) |
ссылку на файл рабочей папки |
new_file(params, name), work_dir(params) |
путь нового файла с созданными папками; рабочую папку |
state_dir(params), resumed(params) |
папку памяти узла; цела ли она |
NodeMemories(make) |
память по узлам: .of(params), обход, .clear() |
read_memory(folder, mark=""), write_memory(folder, values, mark="") |
малая память узла в JSON |
whole_file(path) |
запись целиком или никак, блок with |
model_mark(*paths) |
отметку весов для памяти |
say(key, **values) |
текст словаря модуля на языке интерфейса |
parameter_value(name, value) |
значение параметра, как его примет узел: тип, пределы и набор манифеста; иначе ValueError — модуль, который ставит параметр сам, не берёт того, чего не дал бы человек |
titled_setting(said, settings, key) |
настройку, названную своим именем или подписью на пульте на любом языке модуля; None, если такой нет |
axis_values(said, current), sweep_grid(axes) |
значения оси перебора, как их пишет человек: через «;», промежутком «от..до/шаг», словами через «|», «да; нет»; все сочетания осей |
Таблица 8 — ogas_models
| Имя | Что делает |
|---|---|
TensorMemory(folder, was_saved) |
память узла с values и tensors: .made_with(mark), .write(); создают через NodeMemories(TensorMemory) |
drawing_progress(cancelled, steps) |
значение callback_on_step_end конвейера diffusers |
thumbnails(params, paths, side=512) |
миниатюры файлов с диска: ссылки для просмотра на пульте |
hub_snapshot(repo) |
папку весов в кэше Hugging Face; отказ no_weights |
text_network(path, precision) |
текстовую сеть и её токенайзер из папки библиотеки, точность чисел bfloat16, float16, float32 или auto; отказ unknown_network |
chosen_layers(said, count) |
слои сети из слова: auto — середина сети, 14, 10-18, 12, 15, 18; отказ bad_layers |
chat_prompt(tokenizer, messages) |
разговор как его читает сеть: последняя реплика сети закрывает её ход, иначе ход открыт; шаблон без роли system получает её в первой реплике человека; режим размышлений выключен |
layers_of(model) |
слои декодера текстовой сети любого семейства; отказ unknown_network |
Light(vectors, about) |
добавку к выходам слоёв на каждом токене: .on(model) — блок with, .strength, .answer_only, .mask, .with_other(light, share), .only(layers), .write(path), Light.read(path, mark); добавка от других весов — отказ other_network |
Таблица 9 — ogas_check
| Имя | Что делает |
|---|---|
module_problems(folder) |
что нарушает договор, по файлам модуля (таблица 4) |
rules_problems(folder) |
что нарушает правила платформы, которые держит любой модуль, по его файлам (таблицы 4 и 12) |
words_problems(folder) |
чего не хватает в текстах, которые человек читает о модуле, и что в них не так (таблица 4) |
development_words(text), untranslated(key, text) |
слова разработки в тексте для человека; русский текст, оставленный без перевода |
copies(folders) |
функции, повторённые в нескольких модулях |
execute(module, payload, params, iteration, work, models) |
один шаг от запуска до остановки |
with_defaults(manifest, given) |
параметры, как их даёт платформа |
start(module, work), init(process), send(process, message), read_json(process), stop(process) |
разговор по протоколу вручную |
module_environment(folder, work) |
окружение, которое даёт платформа |
folder_of(module) |
папку модуля по имени или пути |
main(folders) |
команда ogas_check.py |
3 Модуль на Rust
Модуль на Rust — исполняемый файл с "runtime": "native" и "entrypoint" — путём к нему в папке модуля. SDK — пакет ogas-module в sdk/rust; он зависит только от serde_json и собирается отдельно от платформы:
[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);
}
Манифест, словари, папка, память и правила — те же, что в разделе 1; правила 1.4–1.10 от языка не зависят. Паника в шаге — неудавшийся шаг, а не упавший модуль. Полный пример — шаговый и долгий модуль с памятью узла — sdk/rust/examples/double.rs. Пример выше — файл sdk/rust/examples/shout.rs, пример из раздела 5 — core/protocol/examples/function.rs: их собирает cargo test, а make ci сверяет текст здесь с этими файлами слово в слово.
Таблица 10 — SDK для Rust рядом с SDK для Python
| Python | Rust |
|---|---|
run_step(handle) |
run_step(handle) |
run_long(handle, on_suspend=…) |
Long::new(handle).on_suspend(…).run(); ещё on_init, on_configure |
cancelled.is_set() и Cancelled |
cancel.check()? или 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]); ответ читают через 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 |
те же имена |
send_partial(execution_id, port, data) |
send_partial(step, port, data) |
module_dir() |
module_dir() |
secret(name), run_dir() |
secret(name), run_dir() — Option |
| — | константы MODULE_FAILED, MODULE_CANCELLED |
Проверка: cargo test в пакете модуля; договор по файлам — тот же ogas_check.py (манифест, зависимости, принесённое). Как проверяет сам SDK — sdk/rust/tests/protocol.rs.
4 Модуль на другом языке
Протокол читают в Методы модулей, раздел 1: строки JSON через stdin и stdout, init с { "status": "ready", "protocol": 1, "capabilities": […] }, execute, cancel, ping, suspend, configure. Образец поведения — исходники SDK; разговор с модулем проверяют ogas_check.start, send и read_json.
5 Функция платформы
Функция — отдельный OS-процесс и пакет Cargo; папка новой функции сама входит в рабочую область. Правила — Правила разработки, 2.3–2.6. Устройство папки — Устройство платформы, приложение А, А.3.
- Папка
functions/<id>/сCargo.toml,src/main.rs,src/state.rs,src/codes.rs,src/constants.rs,locales/{ru,en}.json. function.json— что функция такое (Форматы файлов, раздел 3):requires,optional,contract,restart.- Запись в реестр
core/data/functions.json. main.rsтолько запускает и раздаёт:
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 читает окружение, подключается к ядру и знакомится с ним; не вышло — функция завершается с причиной. Запись spec.commands = vec![…] читает и проверка договора, поэтому имена перечисляют здесь, а не собирают кодом. Описание получает spec и папку установки: там же добавляют spec.locales, spec.surfaces, spec.capabilities.
Обработчик каждого запроса идёт в своём потоке. Другие функции зовут через client.request(метод, параметры, QUERY_TIMEOUT): прямых связей между функциями нет. Отказ — RpcError::domain(код, текст, можно_повторить); коды объявляют в src/codes.rs, а в Коды ошибок и словарях дают им текст. Настройки — settings.schema.json и регистрация в начале main; без функции настроек работают умолчания схемы.
Каждый метод и событие записывают в договор, docs/contract/: make ci сверяет список функции с документами. Общий код двух функций кладут в core/protocol, а не копируют.
6 Вид узла
Вид оправдан, только если он примитив: это правило ЗС-10 в Виды узлов. Папка functions/graph/src/kinds/<id>/ держит:
- описание
kind.json— что вид говорит о себе:family,inputs,outputs,template.parameters— с чего начинается новый узел,fields— что задаёт человек, значокglyph; движок, проверки и окно спрашивают его, а не держат таблиц; - код
mod.rs—pub(crate) const KIND: Kind = Kind { id: ID, description: include_str!("kind.json"), step: Some(step), ..NOTHING };и функция шага;check— своя проверка узла,hand_out— значение холодного входа; - слова вида в
locales/{ru,en}.jsonи проверки вtests.rs.
Вид вносят строкой в KINDS файла kinds/mod.rs, единственный список, и карточкой в Виды узлов.
7 Элемент пульта
Набор закрыт и пополняется платформой (Мастерская, 6.2).
- Папка
functions/canvas/src/elements/<id>/:element.json—kind,family(input,show,action,decoration),bindings,parameters,size,min_size,states;locales/{ru,en}.json. Параметр —how:number,text,choice,list,image,model_kind. - Запись в
ELEMENTSфайлаelements/mod.rs. - Рисунок —
functions/ui/window/ui/pulpit/elements/<id>.jsсdraw(state, element, { name })и строка вindex.js; что показывает для вида —shows. - Строка в таблице 6 Форматы файлов и описание элемента в заглушке
functions/ui/window/ui/tests/fixtures.js: иначе тесты страницы нарисуют его негодным.
Привязка к вместилищу объявляется "objects": ["group"]: проверка, метка и перенос в заготовку работают по описанию вида. Подпись и подсказка (label, hint) есть у каждого элемента, кроме украшения.
8 Окно мастерской
Окно — папка functions/ui/window/ui/host/<id>/ с index.js (WINDOW: kind, drawing, вид, панель, fields, tests), словами locales/{ru,en}.json, стилем и проверками; строка в host/index.js, единственном списке окон. Цвета и размеры — токены style.css, слова — через t(ключ), запросы — через rpc из lib/rpc.js, запись состояния — commit(). Страница вшита в программу окна: после правки нужен make window. Тесты страницы идут в самом окне: в заглушке stubbedPlatform страница зовёт платформу и сама, поэтому вызов ищут по методу, а не берут последний.
9 Программа сценарием
Сценарий собирает программу в работающей платформе через вход для программ. Вход закрыт, пока человек не откроет его в настройках платформы («Вход для программ» → «Открыт»); порт и ключ платформа пишет в gate.json папки данных, и Platform.connect() находит его сам — в папке данных установки, где лежит SDK, или в той, что названа переменной OGAS_DATA_DIR. Программа с ключом входа может делать в платформе всё, что человек делает в мастерской, кроме остановки платформы и смены самого входа (Методы служб платформы, раздел 5).
from ogas import Platform, Program
program = Program("Привет", "Записывает имя в ответ.")
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", "Привет", sheet=(560, 300))
box = program.group("g_main", "Привет", window)
program.element("field_name", "field", box, {"value": "var:name"}, "Имя", "Кого приветствовать.", (24, 48, 480, 52), writes="typing")
program.element("button_go", "button", box, {"act": "event:go", "carries": {"text": "var:name"}}, "Сказать", "Записать имя в ответ.", (24, 112, 200, 52))
with Platform.connect() as platform:
print(program.save(platform))
sdk/ogas-sdk run build.py
Таблица 11 — Пакет ogas
| Имя | Что делает |
|---|---|
Platform.connect(data_dir=None, port=None, key=None) |
войти в платформу; без порта и ключа они читаются из gate.json |
Platform(port, key, host, client) |
то же, когда порт и ключ известны |
platform.call(method, params, timeout, command=True) |
один запрос; команда получает свой command_id; отказ поднимается исключением Refused |
platform.attempt(method, params) |
тот же запрос, отказ — ответом { error, message, data } |
platform.programs(), platform.program_id(name) |
программы человека; id программы по названию |
platform.catalog(language) |
виды узлов с входами, выходами, полями и словами, элементы пульта, типы переменных |
platform.events |
последние события платформы, пришедшие соединению |
Refused |
отказ платформы: method, code, message, data |
data_folders(), read_gate(data_dir) |
где искать папку данных; что говорит gate.json |
Program(name, description) |
программа: node, event, module, edge, variable — граф; window, tab, group, element — пульт |
program.lay_out() |
расставить узлы, которым сценарий не дал места, слева направо по удалённости от событий |
program.save(platform) |
выключить программу, если включена, записать граф, проверить рамки групп, записать пульт, проверить программу; ответ — { process_id, nodes, edges, elements, validate } |
FramesMeet |
исключение save: рамки групп налезают друг на друга или выходят за лист; crossing называет их |
frames_meeting(groups, shown) |
та же проверка рамок по ответу canvas.preview |
Что нужно помнить в графе:
- у узла модуля один горячий вход, остальное — холодные от узлов «Переменная» или параметры
{ "variable": "id" }; - запись, в которую сходятся несколько путей, — с
input_policy: "any"; - то, что читает следующий шаг, пишут цепочкой: выход узла «Запись» несёт тот же пакет, и выбор идёт после записи;
- кнопка несёт данные полем
carries, поля ввода пишут переменную сами; - шаг, после которого нужно перечитать то, о чём он был, отдаёт в выходе и это — номер, ключ: следующий шаг получает пакет его выхода, а не кнопки.
Что нужно помнить, собирая и пробуя:
- включённую программу не сохраняют:
process.saveотвечаетprocess_running— сначалаprocess.program.off(Program.saveделает это сам); - поправили модуль после того, как программу сохранили, — сохраните её снова: модуль, который отличается от записанного в
modules.lock.json, платформа не запускает, а окно говорит, что файлы модуля изменились; - открытое лицо программы держит пульт, прочитанный при открытии: записанное сценарием видно, когда лицо открыто заново;
- «Надпись» — одна строка: абзац ставят несколькими надписями одна под другой;
- рамку группы на странице считает функция пульта — голова и поля вокруг того, что в группе лежит (Форматы файлов, раздел 7), — поэтому группы рядом ставят с промежутком больше двух полей, а то рамки налезут одна на другую;
Program.saveсмотрит на них до записи поboxвместилищ в ответеcanvas.preview(Методы мастерской и пульта).
10 Правила платформы в модуле
Модуль — часть платформы и держит её правила (Правила разработки, 1.2.1); как каждое правило применяется к модулю, показывает приложение А Правила разработки, угрозы, от которых бережётся модуль, — Защита, раздел 3. Таблица 12 говорит, что из них следует автору модуля и кто это держит: то, что видно по файлам, называет ogas_check.py (таблица 4), остальное держит сам автор. Проверка только называет нарушение и ничего в модуле не меняет.
Таблица 12 — Правила платформы и модуль
| Правило | Что следует модулю | Кто держит |
|---|---|---|
| Правила разработки, 2.1 | модуль — одна задача; main.py запускает модуль и отдаёт шаг тому, чья это задача; подзадача — свой файл |
автор |
| Правила разработки, 2.2 | умолчание параметра — только в манифесте, слова — в словарях модуля; значение, которое влияет на результат, — параметр, внутренняя величина — константа с именем и причиной | ogas_check.py — второе умолчание, текст в манифесте, голое число в вызове сети; остальное автор |
| Правила разработки, 2.3, 2.6; Защита, T-02, T-03 | модуль знает только протокол: файлы берёт из шага, своей папки, папки прогона и памяти узла, а не из чужих папок и данных платформы; к ядру не подключается | ogas_check.py — имена ядра и файлов платформы в коде; путь за пределы разрешённой папки отклоняет платформа; остальное автор |
| Правила разработки, 2.4 | модуль пишет только своё: файлы результатов, память своего узла, папку, которую назвал человек; файлы, пришедшие в данных, только читает | платформа — файлы данных приходят только для чтения; остальное автор |
| Правила разработки, 2.5, 2.10 | модуль — папка: манифест, код, словари, свои проверки; добавить модуль — положить папку, удалить — убрать её | платформа читает список модулей из папки; состав папки — ogas_check.py и автор |
| Правила разработки, 2.7 | всё, что влияет на результат, названо параметром; ход долгой работы — send_progress, сказанное для журнала — send_log, сбой службы между шагами — send_log с уровнем error |
ogas_check.py — параметр, который никто не читает, и голое число; остальное автор |
| Правила разработки, 2.8, 2.9 | параметр, который никто не читает, убирают; общее у нескольких модулей уносят в SDK | ogas_check.py |
| Правила разработки, 2.11 | в коде, комментариях и словарях нет ссылок на документы платформы: причину говорят своими словами | ogas_check.py |
| Правила разработки, 3.1; Защита, T-07 | пакеты — только из requirements.txt с версиями, ставит их платформа по команде человека; данные человека модуль удаляет только по его просьбе или параметру, который человек видит |
ogas_check.py — установка и версии; удаление — автор |
| Правила разработки, 3.2 | не сделал — отказ с кодом и текстом; сбой, пропущенный намеренно, объяснён в строке except; незнакомое не читается как знакомое; отказ не оставляет недописанного без пометки |
ogas_check.py — молчаливый пропуск и текст отказа; остальное автор |
| Правила разработки, 2.7, 3.3 | что модулю нужно от компьютера, сказано в манифесте: видеопамять — resources.gpu_mb |
ogas_check.py — модуль с видеокартой без gpu_mb |
| Правила разработки, 3.3; Защита, T-01, T-06 | сеть заявлена в permissions_info.network; execution_semantics честный: что шлёт письма, платит или пишет наружу, — non_idempotent |
ogas_check.py — сеть; повтор — автор |
| Правила разработки, 3.4 | название, описание, подписи и подсказки параметров, вариантов и портов, тексты отказов и say — в обоих словарях, одним набором; русский написан по-русски словами человека |
ogas_check.py |
| Правила разработки, 3.5 | файл, который переживает шаг, пишут целиком: во временный рядом и переименованием (whole_file, write_memory); выгрузка сохраняет то, с чего работа продолжится |
ogas_check.py — suspend без памяти; остальное автор |
| Правила разработки, 4.5 | готов — значит проверен: ogas_check.py чист, свои проверки test_<id>.py проходят, шаг проведён через execute и результат посмотрен глазами |
автор |
| Защита, T-04 | stdout принадлежит обмену: модуль с SDK пишет своё в stderr или send_log |
ogas_check.py; строку, которая не сообщение, отклоняет платформа |
| Защита, T-05 | секрет — в .env папки модуля, читается secret(name); не параметр, не запись журнала и не данные результата |
ogas_check.py — имя параметра; остальное автор |
| Защита, T-08 | большие данные идут файлами результатов, а не в пакете; долгая работа говорит ход и слышит отмену | пределы держит платформа; остальное автор |
Разделы 4 и 5 Правила разработки — порядок разработки и документация — держит тот, кто ведёт платформу; модулю из них следует только подтверждение готовности проверкой (4.5).
11 Что проверяется само
Таблица 13 — Проверки
| Команда | Что проверяет |
|---|---|
python3 sdk/python/ogas_check.py <папки> |
договор модуля и правила платформы по файлам (таблицы 4 и 12) |
make ci |
формат, clippy, правила scripts/check_rules.py, тесты; справочник SDK в этом документе сверяется с кодом: имя SDK, вид узла, источник события или элемент пульта, которых здесь нет, не проходят |
make build |
обязателен перед живой проверкой: функции берутся из target/debug |
make window, затем проверки страницы в окне песочницы (functions/ui/window/ui/tests/run.js) |
страница в самом окне: стили, раскладка, события |
sdk/tests/live/run.sh <задача> |
живая задача целиком |
12 Программа: документы, узлы и пульт
Справочник того, из чего собирают программу, — рукой в мастерской или сценарием (раздел 9). Здесь названо всё, что есть, с формой в документе; точные правила каждого — там, куда ведёт ссылка.
12.1 Документы программы
Программа — папка data/processes/<process_id>/ (Форматы файлов, раздел 5):
- граф —
process.json, форматogas.process/4:id,name,description,nodes,edges,variables,groups; хозяин — функция графа, пишут егоprocess.saveи мастерская (Форматы файлов, 5.1); - пульт —
workspace.json, форматogas.workspace/3:groups— вместилища,elements— элементы, необязательныеthemeиbindings; хозяин — функция пульта, пишутcanvas.saveи мастерская (Форматы файлов, раздел 7); - настройки этого процесса поверх общих —
settings.json(Форматы файлов, раздел 8); версии и отпечатки модулей —modules.lock.json(Форматы файлов, раздел 9); - запомненные значения переменных (
remember: true) —state.json, его пишет платформа (Форматы файлов, 5.2); файлы процесса, рисунки и виды пульта — рядом (Форматы файлов, 16.3, 7.2).
Передают программу папкой экспорта (Форматы файлов, 10.1): с модулями, которые она берёт, и с процессами, которые она вызывает. Витрина раздаёт программы, модули, нейросети и заготовки интерфейса вещами ogas.catalog/1 (Форматы файлов, 17.1); заготовка — вместилище пульта со всем, что в нём, документ ogas.piece/1 (Форматы файлов, 7.4).
12.2 Узел, ребро, переменная
{
"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 }
]
}
- Узел —
id,kind(12.3), местоx,y,parameters; у модуля ещёmodule_id; необязательныеname,description,input_policy(all— ждать все входы,any— работать с каждым),region— рамка, в которой стоит узел (Программа: граф и запуски, разделы 4, 6, 11). - Параметр берут из переменной записью
{ "variable": "<id>" }: значение подставляется в момент шага (Программа: граф и запуски, раздел 15). - Ребро — из выхода в вход:
source_node,source_port,target_node,target_port; порты называет вид узла (12.3) или манифест модуля, у модуля есть ещё выходfailed(Программа: граф и запуски, 12.4). - Переменная —
id,type— один из видов данных:text,number,boolean,time,list,image,audio,video,file,json;initial,remember, необязательноеscope: "run"— своя у каждого запуска (Программа: граф и запуски, раздел 7). - Группа — надстройка над узлами со своим лицом (Программа: граф и запуски, раздел 17); вызов другой программы узлом
processразворачивается до запуска (Программа: граф и запуски, раздел 18).
12.3 Виды узлов
Каждый вид узла описан карточкой в каталоге — Виды узлов, приложение А: что делает, входы и выходы с тем, что приходит на каждый, что задаёт человек, сколько портов и где правила. Обзор каталога — там же, таблицы 4–13. Что вид получает при постановке и какие у полей варианты, — его описание functions/graph/src/kinds/<kind>/kind.json: inputs, outputs, template.parameters, fields с choices; мастерская показывает то же на узле.
Для первой программы хватает пяти видов: event — с него начинается работа (у кнопки пульта source: pult), module — работа модуля, write — положить данные в переменную, которую показывает пульт, variable — взять значение переменной холодным входом, show — показать данные на узле и на пульте.
12.4 Пульт: вместилища и элементы
{
"groups": [
{ "id": "window_1", "name": "Сайт", "holds": "tabs" },
{ "id": "tab_waiting", "name": "Ждёт взгляда", "parent": "window_1", "screen": { "sheet": [1240, 900] } },
{ "id": "g_list", "name": "Список", "parent": "tab_waiting" }
],
"elements": [
{ "id": "button_refresh", "kind": "button", "group": "g_list", "label": "Обновить", "hint": "Спросить сервер, что ждёт.",
"bindings": { "act": "event:refresh" }, "screen": { "place": [24, 48], "size": [240, 52] } }
]
}
- Вместилище —
id, необязательныеname,parent;holds:together— всё сразу (по умолчанию),tabs— по одному, вкладками;screen.sheet— лист страницы[ширина, высота];bindings:shown— переменнаяboolean,open— у вкладок: какая открыта;look— вид (Форматы файлов, раздел 7). Окно — вместилище безparent. - Элемент —
id,kind,group,label— подпись,hint— подсказка: что он задаёт и куда его крутить,bindings,parameters,screen—placeиsizeв единицах листа,look. - Подпись и подсказка есть у каждого элемента, кроме украшения: окно показывает подсказку при наведении и читает её экранному чтецу.
Элементы с их привязками и параметрами — таблица 6 Форматы файлов, их состояния для видов — таблица 7 там же; что вид элемента принимает, — его описание functions/canvas/src/elements/<kind>/element.json. Привязка называет объект графа: var:<id> — переменная, event:<node_id> — событие пульта, cancel:, retry: — остановить и повторить его, status: — как идёт запуск, progress: — ход шага модуля, out:<node_id>/<выход> — последнее, что дал выход, show: — что показал «Показать», ask: — открытый вопрос, signal: — сигнал, которого ждёт узел, program — включена ли программа; что каждый объект значит и что с ним делает пульт, — Программа: граф и запуски, таблица 5.
12.5 Параметр модуля на пульте
Параметр модуля на пульте — это переменная, которую узел берёт в параметр записью { "variable": "<id>" }, и элемент ввода, привязанный к ней. Элемент выбирают по типу параметра в манифесте; пределы, варианты и первое значение берут оттуда же, подпись и подсказку — из словаря модуля (module.<id>.param.<имя>.title и .help): человек видит на пульте те же слова, что на узле.
| Тип параметра | Переменная | Элемент | Его параметры |
|---|---|---|---|
integer, number |
number |
«Число» | min, max из манифеста, step |
boolean |
boolean |
«Выключатель» | — |
string с choices |
text |
«Выбор» | options — коды манифеста, titles — слова .choice.<код> словаря |
string |
text |
«Поле» | lines, если текст длинный |
string[] |
list |
«Строки» | — |
model |
text |
«Выбор» | models — вид нейросетей параметра |
color |
text |
«Поле» | — |
Длинный набор настроек сценарий пишет сам — по манифестам модулей: так поле не разойдётся с параметром, когда модуль поменяется. Кнопка с привязкой resets возвращает поля вместилища к первым значениям.
12.6 Запросы, которыми собирают и пробуют программу
Сценарий и проверка зовут платформу теми же запросами, что и мастерская; каждый описан в договоре своей функции:
- граф — Методы графа:
process.list,process.create,process.open,process.save,process.validate,process.group.wrap— собрать узлы в группу,process.exportиprocess.inspect— передать и посмотреть принесённое; включить и пробовать —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; - пульт — Методы мастерской и пульта:
canvas.get,canvas.save; - модули — Методы модулей:
module.install— поставить окружение Python процесса; - настройки и журнал — Методы служб платформы:
settings.set,settings.reset,observe.logs.query.
13 Сеть помощника
Помощник мастерской думает сетью, которую выбрал человек. Сеть — модуль с "role": "assistant_network": вход request, выход reply. С OGAS SDK приходят готовые сети (sdk/networks): по протоколу OpenAI — им говорят OpenAI, DeepSeek, OpenRouter, Mistral, Groq, Qwen, xAI и локальные серверы Ollama, LM Studio, llama.cpp, vLLM, — Anthropic, Gemini, GigaChat, YandexGPT и локальная сеть из папки нейросетей. Панель помощника ставит выбранную в папку модулей. Другого поставщика добавляют своим модулем того же договора.
Запрос, который приходит модулю:
{ "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 }
Ответ модуля — reply("reply", { text, calls, usage: { input_tokens, output_tokens }, model }). Что общее у всех сетей, лежит в ogas_assistant; модулю остаётся перевести запрос на язык своего сервиса и ответ обратно. Ключ сервиса модуль читает secret("NAME") из своего .env, а имена своих секретов называет полем secrets манифеста: по нему панель помощника спрашивает ключ у человека и пишет его в .env.
Таблица 14 — ogas_assistant
| Имя | Что делает |
|---|---|
request_of(params) |
запрос помощника, который несёт шаг; без него — отказ bad_answer |
answer(text, calls, input_tokens, output_tokens, model) |
ответ сети, готовый к отдаче шагом |
arguments_of(raw) |
аргументы вызова объектом — из объекта или из его текста JSON |
new_call_id() |
id вызова, когда сервис его не дал |
post(url, body, headers, form=None, verify=True) |
POST к сервису и его ответ JSON; нет ответа — отказ unreachable, который платформа повторит; ответ с ошибкой — service_refused со словами сервиса |
Коды отказов сети — no_key, no_model, unreachable, service_refused, bad_answer; их тексты лежат в словарях модуля, как у любого отказа (1.7).
14 Примеры
sdk/examples/ держит модули и программы, которые запускаются. word_count — модуль с параметром и отказом и программа к нему, собранная сценарием через вход:
sdk/ogas-sdk try sdk/examples/word_count/modules/word_count --input '{"text": "раз два два"}'
cp -r sdk/examples/word_count/modules/word_count modules/
sdk/ogas-sdk run sdk/examples/word_count/build.py
Программы из нескольких модулей лежат в sdk/tests/live/: резервная копия папки (backup), разбор счетов (invoices) и другие; сети помощника — в sdk/networks/.