# How Modly's CLI Communicates with a Running Instance and Polls for Workflow Execution

> Learn how Modly's CLI connects to a running instance using HTTP POST for jobs and GET for status updates. Understand Modly CLI workflow execution.

- Repository: [lightningpixel/modly](https://github.com/lightningpixel/modly)
- Tags: how-to-guide
- Published: 2026-08-20

---

**Modly's CLI communicates with a running desktop instance via HTTP to a local FastAPI server at `http://127.0.0.1:8765`, submitting workflow jobs via POST and polling status via GET until completion.**

The `lightningpixel/modly` project ships with a lightweight, std-lib-only command-line client that lets external agents and scripts trigger 3D mesh generation without installing heavy dependencies. The entire communication layer is implemented in pure Python using `urllib.request`.

## The Local HTTP Architecture

When the Modly desktop application launches, it starts an embedded FastAPI server. The CLI discovers this server through the `MODLY_API_URL` environment variable, defaulting to **`http://127.0.0.1:8765`**【1†L40-L42】.

The CLI module at [`tools/modly-cli/agent.py`](https://github.com/lightningpixel/modly/blob/main/tools/modly-cli/agent.py) contains three core components:

- **Request helper** — `_request_json()` for all HTTP traffic
- **Submission logic** — `_start_workflow_run()` for job creation
- **Polling loop** — `_poll_workflow_run()` for status monitoring

## Building and Sending Requests

All API calls flow through `_request_json()` in [`agent.py`](https://github.com/lightningpixel/modly/blob/main/agent.py). This function:

```python

# From tools/modly-cli/agent.py

def _request_json(method: str, url: str, data=None, headers=None, timeout=DEFAULT_TIMEOUT_SECONDS):
    req = urllib.request.Request(url, method=method, data=data, headers=headers or {})
    with urllib.request.urlopen(req, timeout=timeout) as resp:
        return json.loads(resp.read().decode("utf-8"))

```

Errors are wrapped in `ModlyCliError` with a structured `code` field for programmatic handling【1†L65-L85】.

## Starting a Workflow Run

The `_start_workflow_run()` function constructs a **multipart form POST** to `/workflow-runs/from-image`. The payload includes:

- Input image bytes
- Model ID (or `auto` for automatic selection)
- Collection name
- Remeshing options
- Export format preferences

```bash
modly-cli workflow-run start \
  --image ./my_photo.png \
  --model auto \
  --collection Agent \
  --remesh quad \
  --output ./result.glb

```

This maps to line 14-22 in [`agent.py`](https://github.com/lightningpixel/modly/blob/main/agent.py), where the function builds the form body and extracts the returned `run_id`【1†L14-L22】.

## Polling Loop Implementation

The `_poll_workflow_run()` function implements a **blocking poll loop** with configurable interval:

1. GET `/workflow-runs/{run_id}`
2. Check `status` field in response JSON
3. If `status == "done"`, break and return
4. Otherwise, sleep for `args.poll` seconds (derived from `DEFAULT_POLL_SECONDS`) and repeat【1†L35-L50】

```python

# Simplified polling logic from agent.py

def _poll_workflow_run(base_url, run_id, poll_seconds, verbose=False):
    while True:
        result = _request_json("GET", f"{base_url}/workflow-runs/{run_id}")
        if result.get("status") == "done":
            return result
        if verbose:
            print(json.dumps(result), file=sys.stderr)
        time.sleep(poll_seconds)

```

The CLI exposes this directly via:

```bash
modly-cli workflow-run status --run-id 12345

```

Which hits the same endpoint without the automatic export step【1†L95-L99】.

## Health Verification

Before operations, the CLI calls `_require_health()` or `_try_health()` to verify server availability. These functions hit `/health` and validate the response【1†L66-L71】:

```bash
modly-cli health

```

This prevents wasted work when the Modly desktop app isn't running.

## Complete Execution Flow

```

CLI → GET  /health                       (optional: verify server)
CLI → POST /workflow-runs/from-image     (submit job, receive run_id)
CLI → GET  /workflow-runs/{run_id}       (poll until status="done")
CLI → GET  /export/{fmt}?path=...        (download final mesh)

```

The std-lib-only design means the CLI runs on any Python 3 installation without `requests`, `httpx`, or other HTTP dependencies.

## Timeout and Configuration

Two constants control CLI behavior:

| Constant | Default | Purpose |
|----------|---------|---------|
| `DEFAULT_TIMEOUT_SECONDS` | 300 (5 min) | Maximum wait for any single HTTP request |
| `DEFAULT_POLL_SECONDS` | 2 | Sleep interval between status checks |

Override via `--timeout` and `--poll` flags or environment variables.

## Summary

- **Modly CLI uses pure `urllib.request`** for zero-dependency HTTP operations against a local FastAPI server
- **Workflow submission** sends multipart form data to `POST /workflow-runs/from-image` and receives a `run_id`
- **Status polling** loops on `GET /workflow-runs/{run_id}` until `status` equals `"done"`
- **Health checks** verify server availability via `GET /health` before costly operations
- **All logic lives in [`tools/modly-cli/agent.py`](https://github.com/lightningpixel/modly/blob/main/tools/modly-cli/agent.py)** with clear separation between request building, submission, and polling concerns

## Frequently Asked Questions

### Why does Modly use `urllib.request` instead of `requests`?

Modly's CLI avoids external dependencies entirely. Using `urllib.request` from the Python standard library lets agents invoke the tool on minimal systems without pip-installing packages. The tradeoff is slightly more verbose request construction, wrapped in helper functions for maintainability.

### What happens if the desktop app isn't running when I run a CLI command?

The CLI health check (`_try_health()` or `_require_health()`) will fail with a `ModlyCliError` indicating connection refused. Commands that require server communication exit early with a clear diagnostic message rather than hanging or crashing obscurely.

### Can I change the poll interval or timeout for long-running workflows?

Yes. Both `--poll` (seconds between checks) and `--timeout` (seconds per HTTP request) are exposed as CLI flags. The defaults of 2 seconds polling and 300 seconds timeout can be adjusted per command or via `MODLY_API_URL` and related environment variables.

### Does the polling loop consume significant CPU or network resources?

No. The loop explicitly `time.sleep(poll_seconds)` between requests, defaulting to 2 seconds. With JSON payloads under 1KB and idle CPU usage near zero, the CLI remains lightweight even when monitoring hour-long generation jobs.