File size: 9,282 Bytes
e4ab0d4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# 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`](published-setups.md) β€” storage, validation, routes, implementation detail.
- [`published-setup-external-ui-contract.md`](published-setup-external-ui-contract.md) β€” the product/design contract behind the external frontend surface.
- [`published-setup-full-circle-plan.md`](published-setup-full-circle-plan.md) β€” what is implemented vs. remaining work.
- [`external-frontend-kit.md`](external-frontend-kit.md) / [`setup-runner-quickstart.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.

```mermaid
flowchart LR
  A["ComfyUI sidebar<br/>author builds &amp; 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
`POST`s 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:

```mermaid
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 in
  [`k_publish_contract.py`](../../k_publish_contract.py).
- **Record shape** (`schemaVersion: 1`) β€” `id` Β· `metadata` Β· `visualGraph` Β·
  `apiPrompt` Β· `inputContract` Β· `outputContract` Β· `setupSurface.app` Β·
  `source` Β· `validation`.
- **Validation** β€” `validation.status ∈ {valid, draft, invalid}`; a `valid`
  record must carry an `apiPrompt`.
- **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 terminal
  `lost` status. Persisted run history is listed as remaining work in the
  full-circle plan.
- **The simulator is the contract test.** `setup_runner_simulator.html/.js`
  renders from `setupSurface.app` exactly 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`](published-setup-full-circle-plan.md).