This section answers the questions of how to write and how to run. What you can call (Zio.node, Zio.text, Zio.utils …) is documented separately by topic; here we cover where to place that function and how to execute it. No matter which one you use, the placement and execution path are the same.
Write a Python script inside the platform’s .skills/ directory and attach the @zio_skill decorator. The decorated function becomes both a skillFunction node and an AI tool. You do not need to restart the entire platform—just restart that single skill (see “Restart” below).
You do not need to write main.py!
By writing only the @zio_skill function, the platform will automatically generate a main.py during cold start and create a virtual environment (.venv). Do not create either of those yourself.
You can create three versions with the same folder structure. What you build is determined by who calls it.
Form
Caller
Placed in Folder
Function only
Agent node · other skills · curl
Put @zio_skill only in *.py. The platform creates main.py.
Server renders the screen
Browser (HTML is generated by the server)
Write your own main.py — a FastAPI route returns an HTMLResponse.
Browser renders the screen
Browser (index.html fetches data)
Write your own main.py + index.html. See the Hosting documentation for details.
If you want to use a hand‑crafted main.py, delete the line at the top of that file that says @zio_skill decorator function auto‑registration. While that line is present, the platform rewrites main.py every time it launches the skill — any changes will be overwritten. After removing that line, do not touch it again.
Create one folder under .skills/ and write the @zio_skill function in a .py file inside it; that’s all. One folder is one deployment unit (one virtual environment).
.skills/
└── <폴더명>/ ← 배포 단위 하나
├── <아무이름>.py ← @zio_skill 함수를 여기 적는다 (여러 개 가능)
├── requirements.txt ← (선택) 추가 라이브러리
├── main.py ← ⚡ 플랫폼 자동 생성. 직접 만들지 말 것
└── .venv/ ← ⚡ 플랫폼 자동 생성. 직접 만들지 말 것
The requirements.txt is read only once when you first create the virtual environment. (pandas, fastapi, uvicorn, and requests are installed automatically even if they’re not listed.) Adding a line later will not install it automatically—what to do then is described in the section below titled 「If you added a library later」.
After dragging the skillFunction node onto the canvas in Agent Builder, enter the skill path in the Skill Path field of the right‑hand properties panel using the format folderName/functionName:
The skillFunction automatically injects the necessary data into function parameters at runtime based on their names.
Reserved keyword (parameter name)
Description and Usage
input (dict)
The result returned by the previous node in the workflow canvas is automatically inserted. Example: user_name = input.get("username")
state (dict)
The entire workflow state dictionary managed by the LangGraph engine is provided as a whole. Useful for inspecting all context from previous flows.
Shared state variable
Variables that were merged into the output space by the previous skill node as part of its execution result. If a function parameter name matches a state key, the value is automatically injected at runtime.
additional_parameter (str)
The setting value (text or JSON string) entered in the additional_parameter field of the Agent Builder’s properties panel is injected unchanged.
A single .py placed directly under .skills/ is not a skill. The platform must have a folder so it can create main.py, a virtual environment (.venv), and plug in the SDK path to launch the process.
Running without the folder results in ModuleNotFoundError: No module named 'zio_ontology'. The code is not wrong; you just skipped the setup steps. Moving it into a folder fixes everything.
Python is not installed on the SSH container. Neither python --version nor import zio_ontology will work here — the absence is normal, so there’s no need to investigate. This is the place to write code; the code runs in the skill runner container. Python, the virtual environment, and the zio_ontology module all exist there.
When you put it in a folder, three different paths appear. All three call the same function — no matter which path you use, the result is identical.
There is only one address. You cannot call it by port
A skill runs inside another container called the skill runner. From an SSH session, that container is invisible — localhost or api:<port> will not open. Do not include a port number in the address. Wherever you see it, that number only has meaning inside the runner.
Where you call it
Address
My laptop browser
The platform URL unchanged — https://<platform-address>/skills/<folder-name>/docs
Not in SSH (terminal/IDE AI)
http://api:8000/api/artifacts/skills/call/<folder-name>/execute/<function-name>— this is always the address. You cannot reach it from an external URL (https://…).
Calling path
When to use
/skills/<folder-name>/docs
Open in a browser to see the function list and click to run immediately (Swagger). Single‑shot tasks like creating a table are fastest via this path. It can also be opened from the left Skills tab.
POST /execute/<function-name>
When calling with curl or another program. Arguments are placed as top‑level keys in the JSON body — the argument name and key name should match. In the browser, /skills/<folder-name> is prefixed; inside SSH, it is prefixed with http://api:8000/api/artifacts/skills/call/<folder-name> (see below).
skillFunction node
When adding folder-name/function-name in Agent Builder to insert into a workflow. This is the path used by batch and debugger.
The environment you are connected to via SSH does not have python, python3, or pip. The place where you write code is separate from the place where it runs — you only write files, and execution happens in a different container. Therefore, what follows is not python main.py but a single curl command.
There are two confusing points. The .venv/ folder is visible, but the python inside it is a broken link pointing to another container, and the if __name__ == "__main__": uvicorn.run(...) at the bottom of main.py does not mean you should run it here — the runner starts that file for you. Do not try things like apt install python3. It is unnecessary and will not work.
After fixing, running it is just one line. You start and call it in a single step.
zio run <폴더>/<함수> [JSON] 다시 띄우고 부른다 ← 고친 뒤엔 이것 하나
zio call <폴더>/<함수> [JSON] 부르기만 한다
zio restart <폴더> 다시 띄우기만 한다
zio logs <폴더> [줄수] 안 뜬 까닭 보기
zio deps <폴더> requirements.txt 설치
zio ls 스킬 폴더 목록
zio ping 엔진이 살아 있나
# 예
zio run example_skillFunction/get_recent_customers
zio run my_project/search '{"keyword":"전소영"}'
zio simply wraps the following curl; no new window opens. Data is still read only through the SDK inside the skill. If you want to see what’s happening or if you’re in an environment without zio, use the raw curl below.
The address is always the same. All you need to remember is this one line, and you don’t have to worry about starting it — if it isn’t running, it will start automatically when called. Creating the virtual environment for the first time takes 1–2 minutes, after which responses are immediate.
Do not store the address in a shell variable. IDEs run each command separately. Even if you capture it with URL=$(…) in one command, it won’t persist to the next. Write the full address as shown below.
Replace <folder_name> and <function_name> with your own. These are two different names — the folder is the deployment unit, and the function is the name you wrote after @zio_skill. If in doubt, look it up in the folder (see the third block below).
> > **After fixing, you must restart.** A skill stays running once started; even if the file changes, it will not automatically reload.
>
> > **This applies only to Python code (`.py`).** Static files like `index.html`, CSS, and JS are read from disk on each request, so a browser refresh shows updates without restarting. If you only changed front‑end assets, do not call `restart` — restart is needed only when the Python function itself changes.
>
> > **Conversely, if nothing changed, do not restart.** `restart` kills the running instance and starts a new one — doing it every time you call the function will slow things down because each call spawns a new process. The address stays the same; just call it normally.
>
> - **Inside SSH** — this is the `restart` line in the “Run Inside” section above. If running, it kills and restarts.
> - **From the web UI** — click the skill tab on the left to restart that skill.
> - **Opening `/skills/<folder_name>/docs` in a browser does not restart.** It only starts if nothing is running; if it’s already running, the old code will keep responding.
>
> If startup fails, the reason is logged in `<folder_name>.skill_stderr.log`. In SSH you can view it with:
> **Starting does not mean your function is registered.** A syntax error will cause the restart itself to fail, but if you omitted `@zio_skill` or made a typo in the function name, the process will start fine and you’ll get a `404` when calling it. That’s why the restart response includes the **currently registered functions** (`loaded_functions`, showing name, signature, first‑line description). Check there first to see if your intended name is present. If none are listed, a `warning` explains why (files starting with `_` or `main` are ignored.)
## If you added libraries later
`requirements.txt` is read once when creating a virtual environment for the first time.
To install newly added packages into an existing virtual environment, run this single line and then restart the skill.
If you receive `{"status":"success", …}` then the installation succeeded. **If it takes more than 90 seconds, it will time out** — for heavy libraries, it's safer to delete the entire `.venv` folder and start over (install from scratch).