Skip to content

Quickstart - Flask

Terminal window
pip install z4j-flask z4j-rq z4j-rqscheduler
# or: pip install z4j-flask z4j-celery z4j-celerybeat

From the z4j dashboard, Agents, Mint. The response shows the bearer token and the per-project HMAC secret exactly once. Save both.

Z4J(app) reads configuration from Flask's app.config (flat Z4J_* keys), an optional Z4J dict, or the environment. It does not take a config dict as a positional argument.

Either set the values on app.config before calling Z4J(app):

app.py
from flask import Flask
from z4j_flask import Z4J
def create_app():
app = Flask(__name__)
# Supply the brain's HTTP(S) base URL. z4j derives /ws/agent itself.
app.config["Z4J_BRAIN_URL"] = "https://z4j.example.com"
app.config["Z4J_TOKEN"] = "<token from agent mint>"
app.config["Z4J_HMAC_SECRET"] = "..."
app.config["Z4J_PROJECT_ID"] = "billing-prod"
Z4J(app)
return app

Or, more commonly, set them as environment variables (Z4J_BRAIN_URL, Z4J_TOKEN, Z4J_HMAC_SECRET, Z4J_PROJECT_ID, Z4J_AGENT_NAME) and just call Z4J(app). The third form is the nested dict:

app.config["Z4J"] = {
"brain_url": "...",
"token": "...",
"hmac_secret": "...",
"project_id": "billing-prod",
}
Z4J(app)

The forms are not equal-precedence aliases. Environment variables override flat app.config["Z4J_*"] keys, and flat keys override the nested Z4J dictionary. Z4J_HMAC_SECRET is required by the runtime, but Z4J(app) catches startup exceptions so Flask continues serving without an agent; inspect z4j.host.flask.extension errors and do not treat a healthy Flask response as proof that z4j started.

Z4J(app) registers request teardown hooks and starts the agent's background WebSocket task during app init.

Run the doctor first. It reads CLI flags and Z4J_* environment variables; it does not import your Flask app or read app.config:

Terminal window
python -m z4j_flask doctor

If your app uses only app.config, repeat the effective values as CLI flags or environment variables for this command. Run as the same user your service runs under (sudo -u www-data ... for gunicorn). The WebSocket probe does not wait for agent authentication, so all [OK] rows can still accompany a rejected token; confirm online state and inspect auth warnings too.

Then boot Flask plus your RQ or Celery worker. Processes that use one bearer token register as worker slots under one agent, not as separate agents. A Celery worker can auto-bootstrap after importing z4j_celery with the required credentials in its environment. An RQ worker has no automatic signal: import and call z4j_rq.register_worker_bootstrap() from a module the worker loads.

Without preload, each gunicorn or uWSGI worker imports the app after forking and registers its own (agent_id, worker_id) slot. With gunicorn --preload or uWSGI's default preload, the master imports the app once and the children inherit a runtime whose threads are dead. Wire the exported post-fork hook:

gunicorn.conf.py
def post_fork(server, worker):
from z4j_bare import post_fork as z4j_post_fork
z4j_post_fork()

For uWSGI, call the same function from an @postfork hook. Do not call it from Celery prefork pool children.

Z4J_AGENT_NAME is sent as host metadata and appears in the dashboard's Host column. It does not rename the agent (that name is chosen when the agent is minted), and concurrent slots sharing a token overwrite the same agent-level host field on each handshake:

import os
import socket
app.config["Z4J_AGENT_NAME"] = f"web-{socket.gethostname()}-{os.getpid()}"

See framework: Flask for the deeper reference.