The publish β run loop (architecture overview)
This is the high-level map of how a published setup gets from authoring in ComfyUI to running on a Comfy server through an external frontend. It is the entry point that ties the detailed setup docs together β read this first for the shape of the loop, then follow the links for storage, schema, and contract detail.
Related docs:
published-setups.mdβ storage, validation, routes, implementation detail.published-setup-external-ui-contract.mdβ the product/design contract behind the external frontend surface.published-setup-full-circle-plan.mdβ what is implemented vs. remaining work.external-frontend-kit.md/setup-runner-quickstart.mdβ building and exercising a frontend.
The loop, in plain terms
A "setup" is a published, externally-runnable workflow. The loop has two halves that meet at the Koolook server.
flowchart LR
A["ComfyUI sidebar<br/>author builds & publishes"]
S["Koolook server<br/>API + setups registry"]
F["External frontend (WIP)<br/>browse Β· configure Β· run"]
E["ComfyUI engine<br/>runs the graph, writes output"]
A -->|"publish: POST /koolook/api/setups"| S
S -->|"browse + load: GET /koolook/api/setups + /{id}"| F
F -->|"run: POST /koolook/api/setups/{id}/run"| S
S -->|"queue: POST /prompt"| E
E -->|"rendered output"| S
S -->|"status + result: GET /koolook/api/runs/{id}"| F
The publish edge is one-way into the registry; the run / queue / output / status edges form the loop, with the Koolook server mediating both the frontend and the engine.
1. Publish (one-way, into the registry). An author builds a workflow in the
Kforge Labs sidebar, drops in Koolook publish-contract nodes, and right-clicks
β Publish setup. Koolook captures three things β the visual graph, ComfyUI's
executable apiPrompt, and an inferred UI surface (setupSurface.app) β and
POSTs the record to the server, which writes it into the setups registry
(setups.json). The setup is now a stable, addressable thing with an id.
2. Run (the loop). Any frontend on the same machine talks to the Koolook HTTP API:
| Step | Endpoint | What happens |
|---|---|---|
| Browse | GET /koolook/api/setups |
catalog of published setups (valid only) |
| Load | GET /koolook/api/setups/{id} |
full record incl. setupSurface.app β renders the form |
| Run | POST /koolook/api/setups/{id}/run |
user inputs injected into apiPrompt, queued on engine |
| Poll | GET /koolook/api/runs/{runId} |
status until terminal β returns result path |
The Koolook server is the hub: it injects submitted values into the stored
prompt, prunes unselected router branches, queues it on the same local
ComfyUI server's /prompt, lets the engine run with its installed nodes, and
Koolook_PublishOutput writes the files where the user asked. The frontend just
renders a form and polls β it never needs to understand the graph.
Why this makes a future frontend possible: the frontend depends only on the
published record shape (surface + contract + endpoints), not on ComfyUI
internals. Any external UI that speaks those four endpoints can drive renders.
The governing rule: don't invent a separate external-app format β the file an
external app reads is the same record GET /koolook/api/setups/{id} returns.
Technical map
Three layers β frontend JS β Koolook aiohttp server β ComfyUI engine. Request flow across the loop:
flowchart LR
P["POST /koolook/api/setups<br/>publish"] --> L["GET /koolook/api/setups<br/>browse"]
L --> D["GET /koolook/api/setups/{id}<br/>load"]
D --> R["POST /koolook/api/setups/{id}/run<br/>run"]
R --> Q["GET /koolook/api/runs/{id}<br/>poll"]
Frontend β browser JS (web/)
| File | Symbol | Role |
|---|---|---|
sidebar/modals.js |
showPublishSetupModal() |
publish dialog; collects metadata + contract |
sidebar/published_surface.js |
inferSetupSurface() |
infers setupSurface.app from publish-contract nodes |
sidebar/canvas_io.js |
captureWorkflowApiPrompt(), serializeFullCanvas() |
exports executable apiPrompt + visualGraph |
sidebar/published_setups.js |
publishSavedWorkflowSetup() |
POST β /koolook/api/setups |
setup_runner_simulator.js |
renders setupSurface.app, run + poll |
WIP stand-in / contract test for the future frontend |
Koolook server β Python Β· aiohttp (koolook_routes.py βΊ register_routes())
| Method Β· path | Handler | Role |
|---|---|---|
POST /koolook/api/setups |
publish_setup() |
validate + persist record |
GET /koolook/api/setups |
list_published_setups() |
catalog (valid only) |
GET /koolook/api/setups/{id} |
get_published_setup() |
full record incl. apiPrompt |
POST /koolook/api/setups/{id}/run |
run_published_setup() |
inject inputs, prune routers, queue |
GET /koolook/api/runs/{id} |
get_published_setup_run() |
poll status + result summaries |
Behind the routes:
| File | Symbol | Role |
|---|---|---|
koolook_setups.py |
PublishedSetupRegistry, validate_setup() |
schema v1; status valid / draft / invalid |
koolook_setup_runner.py |
PublishedSetupRunner, AiohttpComfyClient, InMemorySetupRunStore |
prune branches β POST /prompt; run ids run-000001 |
koolook_setups.py |
FileSetupStorage |
on-disk registry at β¦/koolook-published-setups/setups.json |
ComfyUI engine (same local server)
| Method Β· path | Role |
|---|---|
POST /prompt |
queue prompt; executes graph with installed custom nodes |
GET /history, /queue |
status source β runner derives running / done / lost |
Graph contract nodes: Koolook_PublishInput Β· Koolook_PublishOutput Β·
Koolook_PublishRouter Β· Koolook_PublishResult β input injection, output
write, branch select, custom result path.
Requirements & contracts
- Contract nodes in the setup β
Koolook_PublishInput(required),Koolook_PublishOutput(required),Koolook_PublishRouter(optional switch),Koolook_PublishResult(optional result path). Defined ink_publish_contract.py. - Record shape (
schemaVersion: 1) βidΒ·metadataΒ·visualGraphΒ·apiPromptΒ·inputContractΒ·outputContractΒ·setupSurface.appΒ·sourceΒ·validation. - Validation β
validation.status β {valid, draft, invalid}; avalidrecord must carry anapiPrompt. - Run contract β body
{ "inputs": { β¦ } }β{ runId, promptId, status }; poll returns terminal status + result paths. - One format only β the file an external app reads === the record
GET /koolook/api/setups/{id}returns. Do not create an external-app-only format.
Implementation notes worth knowing
- Routes register independently of nodes.
koolook_routes.install()runs even when a node module fails to import, so the setup/preset API stays up when a node is broken. - Run ids are in-memory (
InMemorySetupRunStore,run-000001β¦). They do not survive a ComfyUI restart; a poll for a missing run returns a terminalloststatus. Persisted run history is listed as remaining work in the full-circle plan. - The simulator is the contract test.
setup_runner_simulator.html/.jsrenders fromsetupSurface.appexactly as the real frontend must, so it doubles as the build-against reference. It is a maintainer harness, not the production frontend.
Status
The core loop (publish β registry β load β run β poll β result) is implemented
(PRs 231 / 233 / 235). Remaining "future" work: the production external frontend
itself (auth, job list, run history, multi-user) and the publish-modal
simplification β see
published-setup-full-circle-plan.md.