# 23 — self-repairing LLM flow over `llm://`

Talk to an LLM through the `llm://` connector (you pick the **model** and the
**provider**), have it emit a **YAML flow**, run that flow through urirun, and — if
a step fails — feed the step + error back to the model so it returns a **corrected
flow**. Repeat until it runs or the attempt budget is spent.

```
action space (URIs + schemas)
        │
        ▼
llm://host/chat/command/complete   ──▶  YAML flow
        ▲                                   │
        │ failing step + error              ▼
        └────────── urirun runs each step under policy ──▶ ok → done
```

## Model + provider selection (on the `llm://` payload)

`llm://host/chat/command/complete` takes `{prompt, model, base_url}`:

- **`model`** — the model name (`llama3`, `mistral`, `claude-3.5-sonnet`, …).
- **`base_url`** — the **provider** endpoint. Default is a local Ollama
  (`http://localhost:11434`, which serves `/api/generate`). Point it at a
  **litellm / OpenAI-compatible proxy** to use hosted models (Claude, GPT, …):
  the proxy is the provider, `model` selects which model behind it.

```bash
# local Ollama
python3 repair_flow.py "stamp the current time" --model llama3 --base-url http://localhost:11434

# hosted model via a litellm proxy (the proxy is the provider)
python3 repair_flow.py "stamp the current time" \
    --model claude-3.5-sonnet --base-url http://localhost:4000
```

The flow's **action space** here is the `time-tools` connector; swap
`tt.conn.registry()` in `repair_flow.py:main` for your own set (or
`urirun.entry_point_registry()` for every installed connector).

## The loop (`generate_run_repair`)

1. `urirun.action_space(registry)` → the routes + input schemas the model may use.
2. `ask_llm()` calls `llm://` → raw reply → `_extract_yaml()` strips ``` fences.
3. `run_flow()` runs each step with `urirun.run(..., mode="execute", policy=urirun.policy(allow=...))`,
   unwraps the result via `urirun.result_data(env)`, stops on the first failure.
4. On failure the next prompt includes the failing step + error + the previous
   YAML, so the model returns a fix. Loops up to `--max-attempts`.

Safety is urirun's: `query` routes run freely, `command` routes only under the
`--allow` policy; secrets stay deny-by-default.

## Run on a remote mesh node (`repair_flow_mesh.py`)

The brain (LLM) runs locally; the generated flow executes on a **remote urirun
node** over HTTP. The action space comes from the node's `/routes`, each step is
forwarded with `urirun.runtime.v2_service.call` (`POST {node}/run`, via
`URI_SERVICE_MAP`), and the node's own `--allow` is the security boundary.

```bash
export URIRUN_MESH_CONFIG=~/.urirun-host/mesh.json   # set up by the host installer

# dry-run: generate + validate the flow against the node's schemas, do NOT execute
python3 repair_flow_mesh.py --node officepc --dry-run "report runtime health and the OS name"

# execute on the node
python3 repair_flow_mesh.py --node officepc "report runtime health and write an audit log line"
```

## OpenRouter / hosted models via `examples/.env`

The `llm://` connector talks Ollama's `/api/generate`, so for **OpenRouter / OpenAI
/ Anthropic** use the **litellm** path (`--litellm`), which picks the provider from
the model prefix (`openrouter/…`, `openai/…`, `anthropic/…`, `ollama/…`) plus the
matching `*_API_KEY`. The repo's [`examples/.env`](../.env) already carries both:

```ini
OPENROUTER_API_KEY=sk-or-...
LLM_MODEL=openrouter/google/gemini-3.1-flash-image-preview
```

`--env-file` loads it and uses `LLM_MODEL` as the model:

```bash
# dry-run a flow on the remote node, generated by an OpenRouter model:
python3 repair_flow_mesh.py --litellm --env-file ../.env \
    --node officepc --dry-run \
    "check whether git and docker are installed, report the OS, and log the audit"

# then execute it for real on the node:
python3 repair_flow_mesh.py --litellm --env-file ../.env \
    --node officepc \
    "report runtime health and current date, then write a log entry that the audit ran"
```

Verified live against a node (`lenovo`, 7 safe routes): the OpenRouter model emitted
a schema-valid flow on the first attempt and it ran on the node —
`report ok: True, attempts: 1`, returning the node's real `platform` / `date` and
writing the log entry. Override the model per run with `--model openrouter/<vendor>/<model>`
(or `--model ollama/llama3 --base-url http://localhost:11434` for local, without `--litellm`).

## Test (offline, no model needed)

A fake LLM returns a broken flow first (unknown route), then — once the error is
fed back — a valid one, proving the repair loop end-to-end. `test_repair_mesh.py`
does the same against a fake in-process HTTP node (forward + repair), so no model
and no remote machine are needed:

```bash
pytest -q          # test_repair.py (local) + test_repair_mesh.py (mesh) — 4 passed
```
