A graph document is the unit of work in Entropia Riko: it fully describes a node graph (its metadata, nodes, edges, and settings). The same logical document is the object the editor loads, edits, saves, sends to the execution API, and exports to Python.
The document has two on-disk representations, which are interchangeable:
| Extension | Kind | Description |
|---|---|---|
.riko |
ASCII JSON | Human-readable, diff-friendly, UTF-8 JSON (indented). |
.ric |
Binary | ERIK magic + version byte + zlib-compressed JSON (compact). |
Both decode to the identical JSON structure described below.
{
"version": "1.0",
"metadata": { "name": "classifier", "app": "entropia-riko", "appVersion": "0.1.0" },
"nodes": [ ... ],
"edges": [ ... ],
"settings": { "theme": "dark", "backgroundImage": "" }
}| Field | Type | Required | Description |
|---|---|---|---|
version |
string | ✓ | Format version. Currently "1.0". |
metadata |
object | Document metadata (name, origin app, app version). | |
nodes |
array | ✓ | Array of node objects (see Nodes). |
edges |
array | ✓ | Array of edge objects (see Edges). |
settings |
object | Editor/UI settings (theme, background image). |
The Python model is src/core/document.py (GraphDocument). In memory nodes are
keyed by id in a dict; when serialized they are emitted as a plain array. Missing
optional fields are tolerated on load (version defaults to "1.0", metadata
and settings to {}).
Descriptive information written by the editor:
{
"name": "classifier",
"app": "entropia-riko",
"appVersion": "0.1.0"
}| Field | Type | Description |
|---|---|---|
name |
string | The document/active-file name (falls back to "untitled"). |
app |
string | The application that produced it: "entropia-riko". |
appVersion |
string | The producing app version (src/ui/version.ts). |
metadata is stored as a free-form dict, so consumers must treat unknown keys as
optional (for example, saveFileToDisk also writes an empty description).
Subgraph I/O is not declared here — it is declared by graph_input /
graph_output nodes in the graph itself (see
Subgraph references).
Editor/UI state persisted with the document:
{
"theme": "dark",
"backgroundImage": "https://example.com/cover.jpg"
}| Field | Type | Description |
|---|---|---|
theme |
string | UI theme: light, dark, system, or glass (Liquid Glass). |
backgroundImage |
string | Optional cover-image URL; empty string means none. |
Both are read from localStorage (entropia_riko_theme,
entropia_riko_background) when the editor serializes the document. The backend
does not interpret them; they round-trip unchanged.
Each element of nodes:
{
"id": "fc1",
"type_name": "linear",
"label": "Linear 8→16",
"category": "Neural",
"position": [240, 240],
"parameters": { "in_features": 8, "out_features": 16 },
"inputs": [],
"outputs": []
}| Field | Type | Required | Description |
|---|---|---|---|
id |
string | ✓ | Unique node id within the document. |
type_name |
string | ✓ | Registered node type (see GET /api/nodes). |
label |
string | Display name; falls back to the node type's label. | |
category |
string | UI grouping (falls back to the type's category). | |
position |
[number, number] |
Canvas position [x, y] (defaults to [0, 0]). |
|
parameters |
object | Parameter values keyed by name; merged over the type's defaults. | |
inputs |
array | Port annotations (see below); usually empty in editor-written files. | |
outputs |
array | Port annotations; usually empty in editor-written files. |
The editor serializes inputs/outputs as empty arrays and re-attaches port
definitions from the node registry on load. The model
(src/core/document.py PortModel) also supports explicit port objects for
hand-authored files:
{ "name": "x", "label": "X", "data_kind": "tensor", "direction": "in" }data_kind is one of scalar, tensor, image_tensor, model, text,
unknown (see src/core/tensor.py DATA_KINDS); direction is "in" or
"out".
Each element of edges:
{ "id": "e1", "source_node": "a", "source_port": "value",
"target_node": "b", "target_port": "left" }| Field | Type | Required | Description |
|---|---|---|---|
id |
string | ✓ | Unique edge id. |
source_node |
string | ✓ | Source node id. |
source_port |
string | ✓ | Source node's output port name. |
target_node |
string | ✓ | Target node id. |
target_port |
string | ✓ | Target node's input port name. |
- Encoding: UTF-8.
- Serialization:
json.dumps(..., ensure_ascii=False, indent=2)— readable and diff-friendly. - The editor downloads
.rikofiles with MIME typeapplication/json. - Loaded by plain
json.loads(...)on both the frontend and the backend.
The binary form wraps the same JSON document in a compact, compressed container:
offset size content
0 4 magic b"ERIK"
4 1 version b"\x01" (container version byte)
5 … payload zlib.compress(utf8 JSON)
- The JSON payload is serialized compactly (
separators=(",", ":"),ensure_ascii=False) before compression, so.ricfiles are smaller than their.rikocounterparts. - Magic
b"ERIK"identifies the format; version byteb"\x01"reserves space for future container changes (it is not the document's"1.0"versionstring). - Decoding: verify the leading
ERIKmagic, thenzlib.decompress(data[5:])andjson.loadsthe result. A payload without the magic header raisesValueError("not a valid .ric binary").
This is implemented as GraphDocument.to_binary() / from_binary() in
src/core/document.py:
_RIC_MAGIC = b"ERIK"
_RIC_VERSION = b"\x01"
def to_binary(self) -> bytes:
payload = json.dumps(self.to_dict(), ensure_ascii=False,
separators=(",", ":")).encode("utf-8")
return _RIC_MAGIC + _RIC_VERSION + zlib.compress(payload){
"version": "1.0",
"metadata": { "name": "classifier", "app": "entropia-riko", "appVersion": "0.1.0" },
"nodes": [
{ "id": "gin", "type_name": "graph_input", "label": "Input", "position": [40, 240], "parameters": { "name": "input" } },
{ "id": "fc1", "type_name": "linear", "label": "Linear 8→16", "position": [240, 240], "parameters": { "in_features": 8, "out_features": 16 } },
{ "id": "a1", "type_name": "relu", "label": "ReLU", "position": [440, 240] },
{ "id": "fc2", "type_name": "linear", "label": "Linear 16→4", "position": [640, 240], "parameters": { "in_features": 16, "out_features": 4 } },
{ "id": "sm", "type_name": "softmax", "label": "Softmax", "position": [840, 240] },
{ "id": "gout", "type_name": "graph_output", "label": "Output", "position": [1040, 240], "parameters": { "name": "output" } }
],
"edges": [
{ "id": "e0", "source_node": "gin", "source_port": "value", "target_node": "fc1", "target_port": "x" },
{ "id": "e1", "source_node": "fc1", "source_port": "output", "target_node": "a1", "target_port": "x" },
{ "id": "e2", "source_node": "a1", "source_port": "output", "target_node": "fc2", "target_port": "x" },
{ "id": "e3", "source_node": "fc2", "source_port": "output", "target_node": "sm", "target_port": "x" },
{ "id": "e4", "source_node": "sm", "source_port": "result", "target_node": "gout", "target_port": "value" }
],
"settings": { "theme": "system", "backgroundImage": "" }
}A .riko graph can be referenced as a subgraph by another graph:
graph_referencenode — references by a relative/absolute path (parameterfile).importnode — references by module name (parametermodule), resolved against the module search pathsworkflows/,examples/,examples/models/.
The referenced document declares its interface with graph_input
(name="input") and graph_output (name="output") nodes, so graphs compose
like function calls / Python imports. Resolution lives in
src/runtime/subgraph.py (resolve_graph_file).
- The current format version is
"1.0"; loaders accept it and tolerate missing optional fields (emptymetadata/settings, missingposition, etc.). - Future minor additions must remain backward-compatible (new optional keys).
- Breaking changes bump the major version and require an explicit migration.
- The
.riccontainer has its own one-byte version (b"\x01") independent of the documentversion.
POST /api/execute— execute a document.POST /api/export_python/POST /api/export_keras— export code.POST /api/export_binary— encode a document to.ric(returned as base64).POST /api/files/decode— decode an uploaded.ricbody to a document.GET /api/files,GET /api/files/content?path=— list / read disk-backed.riko/.ricfiles.POST /api/files/save— save toworkflows/<name>.riko/.ric.POST /api/fs/save— save to an arbitrary path (file-picker style).GET /api/project/tree,GET /api/project/open,POST /api/project/create, … — working-folder mini file manager.