Extension Runtime and Lifecycle¶
This page describes what the Zelos Agent does with an agent extension: how it installs it, the environment it runs in, how it starts and stops it, and where its files and logs live. App extensions run in the desktop app instead; see Develop App Extensions.
Install¶
From the Marketplace¶
- Download. The agent fetches the archive for its platform, with a 60-second timeout and a 2 GiB cap.
- Verify. It checks the SHA-256 checksum the marketplace recorded.
- Extract. It unpacks the archive and rejects links, special files, absolute or
..paths, and hidden entries. See Archive Rules and Limits. - Check. It parses
extension.toml, checks the[zelos]version range, and checks that the files the manifest names exist. - Prepare. It moves the files to
code/and builds the Python environment. See Python Environment. - Lock. It records an integrity hash in
.integrityand makescode/read-only.
The extension is then installed and stopped. A user starts it from the app or with zelos extensions start.
From a Local Directory¶
zelos extensions install-local <dir> installs your working copy for development:
- The agent does not copy your code. It writes a
.dev_sourcefile that points at your project directory, and runs the code from there. - Runtime files (the virtual environment, cache, configuration, data, and logs) live in the install directory, outside your project.
- The ID is
local.<slug>, from the manifestname. See Extension Identity. - The agent builds the Python environment, then writes
actions.jsoninto your project. Seeactions.json.
Installing the same project again replaces the previous local install.
Python Environment¶
Every agent extension runs in its own virtual environment, built with uv:
- uv downloads the Python version from
python_versionwhen needed. Zelos uses only uv-managed Python, never a system interpreter. - With a
uv.lockand norequirementsoverride, the agent runsuv sync --frozenfor the exact locked versions. - With a
requirementsfile, it creates the environment and runsuv pip sync, which installs exactly the listed set. - Otherwise it creates the environment and runs
uv pip installagainstpyproject.toml. - Installed dependencies are compiled to bytecode, so the first start is faster.
- The environment lives in
.venv/in the install directory, with a uv cache in.cache/. - Dependency installation times out after 15 minutes.
Python 3.10 or later
The manifest accepts any major.minor version, but zelos-sdk needs Python 3.10 or later. An extension that asks for an older Python installs and then fails at import.
Install Locations¶
| Platform | Location |
|---|---|
| macOS | ~/Library/Application Support/io.zeloscloud.zelos/extensions/<id>/<version>/ |
| Linux, desktop app | ~/.local/share/zelos/extensions/<id>/<version>/ |
Linux, headless zelos-agent package |
/var/lib/zelos-agent/extensions/<id>/<version>/ |
| Windows | %LOCALAPPDATA%\zeloscloud\zelos\data\extensions\<id>\<version>\ |
When the agent runs with ZELOS_DATA_DIR set, extensions live under $ZELOS_DATA_DIR/extensions/.
acme.sensor-monitor/
└── 1.0.0/
├── code/ # Your shipped files, read-only (marketplace installs)
│ ├── extension.toml
│ ├── actions.json
│ └── main.py
├── .integrity # Integrity hash of code/ (marketplace installs)
├── .dev_source # Path to your project (local installs, in place of code/)
├── config.json # Last saved configuration
├── data/ # Writable storage, exposed as ZELOS_DATA_DIR
├── extension.log # stdout and stderr
├── extension.log.1 # Previous log, after rotation
├── .venv/ # Python virtual environment
└── .cache/ # uv cache
For a marketplace install, the agent checks the integrity hash before it starts the extension. A manual edit under code/ changes the hash, and the agent then refuses to start the extension.
Start¶
When an extension starts, the agent:
- Validates the configuration against the extension's schema. See Extension Configuration. Invalid configuration fails the start, and a running extension keeps running on a restart.
- Writes the configuration to
config.json. - Creates
data/if it does not exist. - Runs the
entryfile with the environment's Python, inworkdirunder the code directory. - Sends the process's stdout and stderr to
extension.log.
Start, restart, stop, and reinstall apply to agent extensions only. For an app extension they fail with Operation not supported for app extensions.
Process Environment¶
The agent starts the process with an empty environment and adds back only these variables.
| Variable | Value |
|---|---|
PATH, TEMP, TMP |
Inherited from the agent |
HOME, USER, LOGNAME, LANG, LC_ALL, LC_CTYPE |
Inherited from the agent (macOS and Linux) |
SystemRoot, USERPROFILE, APPDATA, LOCALAPPDATA |
Inherited from the agent (Windows) |
SSL_CERT_FILE, REQUESTS_CA_BUNDLE |
Inherited from the agent, for custom CA certificates |
HTTP_PROXY, HTTPS_PROXY, NO_PROXY |
Inherited from the agent, for proxies |
TZ |
Inherited from the agent |
Your env entries |
From [host.agent.env] |
PYTHONUNBUFFERED |
1, so log lines arrive without delay |
PYTHONIOENCODING |
UTF-8 |
ZELOS_CONFIG_PATH |
Path to config.json |
ZELOS_DATA_DIR |
Path to the writable data/ directory |
ZELOS_AGENT_URL |
The URL of the agent that started the extension |
ZELOS_STANDALONE |
1, only in a standalone action run. See Standalone Action Runs. |
Only the uppercase proxy names pass through. Variables that start with ZELOS_ are reserved, and the manifest cannot set them.
How the SDK uses these:
zelos_sdk.init()connects toZELOS_AGENT_URL, so an extension reports to the agent that started it even when another agent holds port 2300. Without the variable it useshttp://localhost:2300.load_config()readsZELOS_CONFIG_PATH. See Loading Configuration.
Files and Storage¶
The code directory is read-only for marketplace installs. Write files to ZELOS_DATA_DIR:
import os
from pathlib import Path
data_dir = Path(os.environ["ZELOS_DATA_DIR"])
(data_dir / "cache.json").write_text("{}")
data/ survives restarts, reinstalls, and updates. An update moves it to the new version.
Standalone Action Runs¶
The agent can run a standalone action while the extension is stopped, for example to fill a configuration form's choices. For each run it starts a separate short-lived Python process in the extension's environment:
- The process gets the same environment as a supervised start, without
ZELOS_AGENT_URLand withZELOS_STANDALONE=1. - While
ZELOS_STANDALONEis set,zelos_sdk.init()opens no agent connection, so the stopped extension does not appear live. - The process imports your entry module to find the action. Keep startup code under
if __name__ == "__main__":so the import does not start your extension.
The list of standalone actions comes from actions.json. See actions.json and Choices for a Config Field.
Logs¶
The agent appends the process's stdout and stderr to extension.log. When the file passes 10 MiB at the next start, the agent renames it to extension.log.1, replacing any older rotation.
Read the log:
- In the app, use View Logs on the extension.
- From the CLI, use
zelos extensions logs:
zelos extensions logs local.my-extension
zelos extensions logs local.my-extension --tail 65536
zelos extensions logs local.my-extension -o extension.log
--tail <BYTES> reads only the last bytes. Without it, the command reads the whole retained log, extension.log.1 first, up to a cap set by the agent. When the agent drops older output, the command says so on stderr. -o writes the log to a file.
Use TraceLoggingHandler to also send Python log records into Zelos as trace data. See Integrate Logging.
Stop and Shutdown¶
On macOS and Linux, the extension runs in its own process group. To stop it, the agent:
- Sends
SIGTERMto the process group, so child processes receive it too. - Checks every 50 ms whether the group has exited.
- Waits up to
grace_seconds(default 10, range 1 to 300). - Sends
SIGKILLto the group if it is still running.SIGKILLcannot be caught. - Waits up to 1 second for the kill to finish, then kills any descendants that left the group.
On Windows there is no SIGTERM. The extension runs in a job object. The agent waits grace_seconds × 200 ms, at most 1 second, then terminates the job with all its processes. Shutdown handlers do not run on Windows, so do not rely on them to save state there.
Set the grace period in the manifest:
Example timeline on macOS or Linux with grace_seconds = 30:
T+0 ms SIGTERM sent to the process group
T+50 ms Poll: still running
...
T+30000 ms Grace period over: SIGKILL sent
T+31000 ms Confirmation window over: extension stopped
Handle SIGTERM¶
Without a SIGTERM handler, Python exits at once and skips your cleanup. That is acceptable for stateless extensions. When you need to close connections or flush data, handle the signal and let your main loop end:
import signal
from types import FrameType
def shutdown_handler(signum: int, frame: FrameType | None) -> None:
extension.stop() # make the main loop exit, then clean up
signal.signal(signal.SIGTERM, shutdown_handler)
signal.signal(signal.SIGINT, shutdown_handler)
Finish cleanup inside grace_seconds. After that the agent kills the process.
Crashes and Exit Status¶
The agent records how an extension exited:
- User-initiated stop. No exit information is recorded. The extension shows as stopped.
- Unexpected exit (crash, signal, or error). The exit code or signal is recorded. The extension shows an error state, the app shows a notification, and View Logs opens the log.
| Exit code or signal | Meaning | Typical cause |
|---|---|---|
| Code 0 | Clean exit | The main loop ended without a stop request |
| Code 1 | General error | Unhandled exception |
| Code 137 | Killed | Out of memory on Linux |
Signal 9 (SIGKILL) |
Force-killed | Out of memory, or an unresponsive process |
Signal 11 (SIGSEGV) |
Segmentation fault | A crash in native code |
Signal 15 (SIGTERM) |
Terminated | A stop request the process did not handle |
Log meaningful errors before you exit. The log is what users see first after a crash.
Reinstall¶
zelos extensions reinstall <id> rebuilds the extension's Python environment without removing its configuration or data. Use it after you change dependencies. The app's Reinstall button does the same.
- The agent refuses while the extension runs:
Stop the extension before reinstalling. Stop it first. - For a marketplace install, the agent first checks
code/against its integrity hash. When the code has changed, it refuses and asks you to uninstall and install again from the marketplace.
Updates¶
An update installs the new version next to the old one, moves data/ and the saved configuration across, and restarts the extension if it was running. Any failure restores the old version. See Updates.
Security Model¶
Agent extensions:
- Run as separate processes, as the same OS user as the agent. Zelos does not sandbox their file or network access.
- Start from a minimal environment. See Process Environment.
- Have a read-only code directory for marketplace installs, guarded by an integrity hash.
- Must keep
workdirinside the extension. The agent refuses aworkdirthat resolves outside it.
App extensions:
- Run in an iframe sandboxed with
allow-scripts allow-same-origin allow-downloads. They cannot submit forms or open popups. - Cannot reach the network. The Content Security Policy blocks
fetch,XMLHttpRequest, and form submissions tohttp:andhttps:URLs. - Run under their own
zelos-app://<extensionId>origin, isolated from the host and other extensions. - Load only the packaged files served by
zelos-app://.
Before you publish, check that:
- The code holds no secrets or API keys. Take them from configuration with the
passwordwidget. - The extension validates data from devices, files, and the network.
- Error messages do not expose internals.
- Dependencies come from trusted sources and are pinned in
uv.lock. - The extension does not run code that users or devices supply.
Troubleshooting¶
| Problem | Fix |
|---|---|
| The extension does not start | Run zelos extensions logs <id> and read the last error. |
Configuration validation failed |
Fix the listed fields in Configure, or fix the schema defaults. |
Extension requires Zelos version matching ... |
Update Zelos, or widen [zelos].version. |
| Dependency installation fails or times out | Pin versions in uv.lock, test uv sync in a clean checkout, and trim large dependencies. |
Stop the extension before reinstalling |
Run zelos extensions stop <id>, then reinstall. |
| The extension starts but sends no data | Check that zelos_sdk.init() runs and that the log shows no connection errors. |
| A marketplace extension refuses to start after a manual edit | Uninstall it and install it again from the marketplace. Files under code/ must not change. |