O.G.A.S. English

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

Пакет приходит от чужого узла и бывает глубже, чем ждёт модуль: ответ вопроса — { "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)

Правила памяти:

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.

  1. Папка functions/<id>/ с Cargo.toml, src/main.rs, src/state.rs, src/codes.rs, src/constants.rs, locales/{ru,en}.json.
  2. function.json — что функция такое (Форматы файлов, раздел 3): requires, optional, contract, restart.
  3. Запись в реестр core/data/functions.json.
  4. 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>/ держит:

Вид вносят строкой в KINDS файла kinds/mod.rs, единственный список, и карточкой в Виды узлов.

7 Элемент пульта

Набор закрыт и пополняется платформой (Мастерская, 6.2).

  1. Папка 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.
  2. Запись в ELEMENTS файла elements/mod.rs.
  3. Рисунок — functions/ui/window/ui/pulpit/elements/<id>.js с draw(state, element, { name }) и строка в index.js; что показывает для вида — shows.
  4. Строка в таблице 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

Что нужно помнить в графе:

Что нужно помнить, собирая и пробуя:

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

Передают программу папкой экспорта (Форматы файлов, 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 }
  ]
}

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

Элементы с их привязками и параметрами — таблица 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 Запросы, которыми собирают и пробуют программу

Сценарий и проверка зовут платформу теми же запросами, что и мастерская; каждый описан в договоре своей функции:

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