This guide is for AI agents and contributors that need to inspect, create, validate, run, export, or share Blacknode workflows.
Blacknode workflows are portable JSON graph files. They are meant to run outside the browser editor through the Python runtime and CLI.
The current stable pieces are:
- workflow schema:
docs/workflow.schema.json - schema docs:
docs/workflow-schema.md - reusable templates:
templates/*.json - local user saves:
workflows/*.json - CLI:
python -m blacknode.cli - converted examples:
examples/converted_text_pipeline.py,examples/converted_nvidia_nim.py - validation/runtime module:
python/blacknode/workflow.py - MCP smoke prompts:
docs/mcp-test-prompts.md
Use these paths consistently:
| Path | Purpose | Git status |
|---|---|---|
templates/*.json |
Shared reusable workflow templates | tracked |
workflows/*.json |
User-saved local workflows from the editor | ignored |
nodes/learned/*.py |
User-created learned nodes generated through MCP | ignored |
examples/learned/* |
Reference learned-node examples | tracked |
examples/*.py |
Human-readable Python examples | tracked |
docs/*.md |
Project documentation | tracked |
editor-server/blacknode_graph.json |
Last local editor session | ignored |
editor-server/api_keys.json |
Local saved provider keys | ignored |
workflow.py |
Common temporary export output | local unless intentionally added |
Do not commit local planning notes, generated scratch exports, API keys, run logs, cached results, or editor session state.
- Workflow: a JSON document with
kind: "blacknode.workflow"andschema_version: 1. - Node: one graph operation stored in
node_metawith ID, type, params, position, ports, and port types. - Port: a named input or output on a node, with a type such as
Text,Float,Dict,Fn,Model, orAny. - Edge: a connection from one output port to one input port.
- Entrypoint: the node and port cooked by the CLI. Prefer an explicit
entrypoint. - Output node: terminal node used by the editor and inferred by the CLI when there is exactly one.
- Subnet: nested workflow graph inside a node.
- Template: a tracked workflow JSON file in
templates/withmetadata.template: true. - Learned node: a reusable MCP-created node type stored on disk and executed in Docker through the learned-node wrapper.
Default rule: inspect the catalog first, use built-in nodes when they solve the task exactly, and create a learned node when a reusable missing capability is needed. Do not treat brittle approximations as matches; for example, generic regex extraction is not a structural RSS parser if the requested output is only article titles.
Use PythonFn for one-shot code that belongs only to the current workflow.
Examples: a tiny adapter between two ports, a temporary formatting expression,
or exploratory code that should travel with one graph.
Use create_node_type when the capability is general and reusable across
workflows. Examples: parsing RSS, extracting a domain-specific CSV format,
normalizing a recurring API response, or wrapping a stable local calculation.
Before creating a learned node:
- call
list_nodesandget_node_schemato confirm no built-in node fits - do not create or modify files under
nodes/learneddirectly; usecreate_node_type,list_learned_nodes,get_learned_node_source,promote_learned_node, anddelete_learned_node - keep the interface small and typed with
Text,Int,Float,Bool,List,Dict, orAny - set
requires_network=Falseunless the code must reach the network - set a useful
categoryso the node lands in the right palette group instead of the defaultLearned - generate only a
def run(...)function and make sure its parameters match the declared input ports - after creation, call
list_learned_nodes, use the learned node in the visual workflow, validate the graph, open it in the editor, and cook the finalOutputnode - remember that learned-node code never runs in the host process; it runs in the Docker sandbox wrapper
Do not ask the editor to edit learned nodes. The UI is read-only by design; the
plain files in nodes/learned/<Name>/ are the editing contract.
Promote stable learned nodes with promote_learned_node instead of moving files
by hand. Promotion writes a normal @node module into custom-nodes/ or
community-nodes/ and removes the learned-node source unless keep_learned=True
is requested.
A workflow file must include full node metadata, not just node type names.
Required top-level fields:
{
"kind": "blacknode.workflow",
"schema_version": 1,
"name": "Text Pipeline",
"saved_at": "2026-05-20T00:00:00",
"entrypoint": { "node_id": "out", "port": "value" },
"node_meta": {},
"edges": []
}Each node should include:
idtypeparamsposinputsoutputsinput_typesoutput_typesinput_defaults- optional
subgraph - optional
metadata - optional
multi_input_ports
When creating a workflow programmatically, start from an existing template or from editor output so ports stay complete and valid.
From a repo checkout:
$env:PYTHONPATH="python"
python -m blacknode.cli doctor
python -m blacknode.cli demo
python -m blacknode.cli validate templates\text-pipeline.jsonExpected success:
Blacknode demo OK
Result: Hello World
{
"ok": true,
"errors": [],
"warnings": []
}Validate every tracked template through tests:
python -m unittest tests.test_templates -vRun a workflow and write structured output:
$env:PYTHONPATH="python"
python -m blacknode.cli run templates\text-pipeline.json --output result.jsonExport a workflow to Python:
$env:PYTHONPATH="python"
python -m blacknode.cli export-python templates\text-pipeline.json --output workflow.py
python workflow.pytemplates\text-pipeline.json prints:
Hello World
Tool/subgraph export example:
$env:PYTHONPATH="python"
python -m blacknode.cli export-python templates\subnet-tool-call.json --output subnet_tool_call.py
python subnet_tool_call.pyExpected output:
59.0
LLM templates export the same way, but running them requires the relevant provider key.
blacknode run returns a structured result with:
run_idnode_idportvalueevents
Event types include:
run_startrun_finishrun_errornode_startnode_finishnode_errormodel_calltool_call
Run logs are output artifacts. They must not be saved back into workflow JSON.
Workflow JSON must not contain API keys.
Use these instead:
- environment variables such as
NVIDIA_API_KEY,OPENAI_API_KEY, orANTHROPIC_API_KEY - editor-saved local keys in
editor-server/api_keys.json
editor-server/api_keys.json is ignored by git.
To make a new shared template:
- Build and save the workflow in the editor.
- Find the saved JSON under
workflows/. - Copy it into
templates/with a clear slug, for exampletemplates/my-agent.json. - Add or verify:
kind: "blacknode.workflow"schema_version: 1entrypointmetadata.template: truemetadata.descriptionmetadata.color
- Run validation:
$env:PYTHONPATH="python"
python -m blacknode.cli validate templates\my-agent.json
python -m blacknode.cli export-python templates\my-agent.json --output workflow.py- Add a focused test if the template should be guaranteed.
- Commit the template only after validation passes.
Do not commit the local workflows/ copy unless the user explicitly wants local saves tracked.
LLM Chat:
Model->LLMAgent.modelTextsystem ->LLMAgent.systemTextprompt ->LLMAgent.promptLLMAgent.text->Output.value
NVIDIA NIM:
- use model string
nim:meta/llama-3.1-8b-instruct - requires
NVIDIA_API_KEYor editor-saved NVIDIA NIM key
Python Tool Agent:
PythonFn.fn->ToolBox.tool_1ToolBox.tools->AgentLoop.tools- prompt/system/model ->
AgentLoop AgentLoop.result->Output.value
Subnet Tool:
- create
SubnetAsTool - inside it, use
SubnetInputfor tool arguments - use
SubnetOutputfor returned value - connect outer
SubnetAsTool.fnintoToolBoxorToolCall
Visual Agent Loop:
- use
VisualAgentLoopwhen a graph should show the agent-loop internals - runtime behavior matches
AgentLoop
Use docs/mcp-test-prompts.md when verifying the MCP server from an agent
client. MCP exposes read-only resources for quick context:
blacknode://nodesblacknode://templatesblacknode://workflowsblacknode://editor/graph
The NVIDIA NIM prompts exercise the full live-editor path:
- build workflow JSON through MCP tools
- validate the graph
- open it as a new organized editor tab
- cook
out.valuein the running editor - inspect and save the currently loaded editor graph
- list and reopen saved workflows by slug
- organize, rename, and close live editor tabs
For tracked templates, prefer run_template_in_editor when the task is simply
"open this template, organize it, and optionally cook it." Use the lower-level
load_workflow, validate_workflow, open_workflow_in_editor_tab, and
cook_editor_node calls when the agent needs to inspect or modify the graph
before opening it.
The live editor tools require editor-server/server.py to be running at
http://127.0.0.1:7777 or BLACKNODE_EDITOR_URL to point at the backend.
Before changing workflows or templates:
- Check
git status -sb. - Leave unrelated user files alone, especially untracked generated exports.
- Use existing templates as examples.
- Preserve full node metadata and edge fields.
- Keep secrets out of JSON.
After changing workflows or templates:
python -m unittest tests.test_templates -v
python -m unittest discover -s tests -vAfter changing frontend template loading:
cd editor
npm run buildBefore committing:
git diff --check
git status -sbpython/blacknode/workflow.py: validation, runtime execution, Python export, structured run logs.python/blacknode/graph.py: lazy graph execution engine.python/blacknode/node.py: node decorator and port parsing.python/blacknode/nodes/*.py: built-in node implementations and port definitions.editor-server/server.py: editor persistence, workflow routes, template routes.editor/src/components/TemplateGallery.tsx: UI that lists and loads tracked templates.editor/src/api.ts: frontend API client.tests/test_templates.py: template validation/export coverage.tests/test_examples.py: runnable example coverage with stubbed external calls.
Prefer this order:
- Copy or adapt an existing valid template.
- Validate the workflow.
- Export it to Python.
- Run the exported script when it does not require live external services.
- Add or update tests.
Do not invent a new file format until the existing workflow JSON schema cannot represent the use case.