Main Application

FastAPI application bootstrap and compatibility routing surface.

app.main now owns application setup, simple endpoints, import handlers, and router mounting for the split route modules. Keep this page because it remains the public entrypoint for uvicorn app.main:app.

app/main.py

FastAPI application entrypoint for the Axis Descriptor Lab.

This module is a thin routing layer — each route handler orchestrates calls to domain modules and returns the result. All business logic lives in dedicated modules:

Domain modules

  • pipeworks_ipc – IPC normalisation and SHA-256 hash functions (shared library).

  • app.schema – Pydantic v2 request / response models.

  • app.chat_renderer – Synchronous HTTP wrapper around Ollama.

  • app.signal_isolation – NLP pipeline for content-word delta.

  • app.transformation_map – Clause-level sentence alignment and diffing.

  • app.micro_indicators – Structural pattern indicators for transformation map rows.

  • app.save_package – Manifest builder, zip archive, import/export.

  • app.relabel_policy – Policy table and score-to-label mapping.

  • app.save_formatting – Markdown builders and folder-name generator.

  • app.file_loaders – Example and prompt file loading/listing.

  • app.mud_server_client – Synchronous HTTP client for the mud server lab API.

Run with:

uvicorn app.main:app –reload –host 127.0.0.1 –port 8242

Endpoints

GET / → serves index.html HEAD / → returns the SPA shell headers without a body GET /pipeline-build → serves index.html with Pipeline Build preselected GET /api/examples → list of available example names GET /api/examples/{name} → returns a single example JSON payload GET /api/prompts → list of available prompt names (optionally by purpose) GET /api/prompts/{name} → returns a single prompt’s text content GET /api/models → returns locally available Ollama models POST /api/generate → send axis payload to Ollama, return description POST /api/log → persist a run log entry under the configured log root POST /api/relabel → (optional) recompute labels from policy rules POST /api/analyze-delta → content-word delta between two texts POST /api/transformation-map → clause-level replacement pairs GET /api/system-prompt → return the default system prompt as plain text POST /api/save → save session state under the configured writable save root GET /api/save/{name}/export → download a save package as a zip POST /api/import → import a save package from a zip upload POST /api/import_chat → import a chat save package from a zip upload POST /api/mud/login → proxy login to mud server POST /api/mud/logout → clear mud server session GET /api/mud/mode → return runtime chat mode + available options POST /api/mud/mode → switch runtime chat mode GET /api/mud/session → return auth status + translation mode GET /api/mud/worlds → proxy list worlds from mud server GET /api/mud/world-config/{id}→ proxy world config from mud server GET /api/mud/world-prompts/{id} → proxy world prompt templates from mud server GET /api/mud/world-image-policy-bundle/{id} → proxy canonical image policy bundle metadata POST /api/mud/compile-image-prompt → proxy canonical image prompt compilation POST /api/mud/select-world → store selected world_id

Architecture notes

  • All blocking I/O (file reads, Ollama HTTP calls) lives in regular def route handlers. FastAPI automatically runs those in a threadpool so the async event loop is never blocked.

  • Static files are served by Starlette’s StaticFiles middleware.

  • Jinja2Templates renders index.html (single page — the JS takes over).

  • A simple JSONL log file under the configured writable log root provides the Pipe-Works audit trail without any database dependency.

app.main.close_runtime_clients()[source]

Close all shared HTTP clients created by the application runtime.

app.main.index(request)[source]

Serve the single-page application shell.

Passes the default model name and the list of locally available Ollama models into the Jinja2 template so the frontend can pre-populate its model selector without an extra API round-trip.

app.main.pipeline_build_page(request)[source]

Serve the shared SPA shell with the Pipeline Build page preselected.

app.main.list_examples()[source]

Return a sorted list of example names (without the .json extension) that are stored in app/examples/.

The frontend uses this to populate its example dropdown.

app.main.get_example(name)[source]

Return the parsed JSON for a named example.

Parameters:

name (Example stem, e.g. "proud_operator".)

Returns:

  • The raw example JSON object (validated loosely by Pydantic when the

  • frontend loads it into the textarea).

Return type:

dict

app.main.list_prompts(purpose=None)[source]

Return a sorted list of prompt names (without the .txt extension) that are stored in the grouped app/prompts/ tree.

The frontend uses this to populate its prompt library dropdown, allowing users to browse only the prompt family relevant to the current page.

Parameters:

purpose ({"character_description", "chat_translation"} | None) – Optional prompt-group filter. When omitted, prompt names from every local prompt group are returned.

app.main.get_prompt(name, purpose=None)[source]

Return the text content of a named prompt file as plain text.

Uses PlainTextResponse to match the existing /api/system-prompt endpoint pattern. The frontend loads this into the system prompt override textarea.

Parameters:
  • name (Prompt stem, e.g. "system_prompt_v01".)

  • purpose ({"character_description", "chat_translation"} | None) – Optional prompt-group filter. When provided, lookup is limited to that prompt family.

Return type:

The raw prompt text (plain text, not JSON).

app.main.get_models(host=None)[source]

Query an Ollama instance and return all model names it has pulled.

Parameters:
  • host (Optional Ollama server URL (query param). When omitted the) – server-side default (OLLAMA_HOST env var) is used.

  • unreachable (Returns an empty list if Ollama is)

  • to (allowing the frontend)

  • input. (fall back to a manual text)

app.main.generate(req)[source]

Core endpoint: serialise the axis payload to JSON, attach the system prompt, call Ollama, and return the generated paragraph.

The system prompt is taken from the request body if provided; otherwise the default Character Description prompt is loaded from app/prompts/character_description/. This lets the frontend experiment with custom prompts without restarting the server.

Parameters:

req (GenerateRequest – full request body (payload + model settings).)

Return type:

GenerateResponse containing the generated text and metadata.

Raises:
  • HTTPException(502) if Ollama returns an HTTP error.

  • HTTPException(504) if the Ollama request times out.

  • HTTPException(500) for any other unexpected error.

app.main.log_run(payload, output, model, temperature, max_tokens, system_prompt=None)[source]

Append a structured log entry to the configured run log file.

Each line in the JSONL file is one complete LogEntry serialised as compact JSON. The file can be opened in any JSONL-aware tool (jq, pandas, etc.) for drift analysis.

When system_prompt is provided, the entry includes IPC hashes (system_prompt_hash, output_hash, ipc_id). When omitted, output_hash is still computed but the prompt-dependent fields are set to None for backward compatibility with older frontend versions.

Parameters:
  • payload (The AxisPayload used in the run.)

  • output (The LLM-generated text.)

  • model (Ollama model identifier.)

  • temperature (Sampling temperature used.)

  • max_tokens (Token budget used.)

  • system_prompt (The system prompt used (optional). When provided,) – enables full IPC chain in the log entry.

Return type:

The complete LogEntry that was persisted.

app.main.relabel(payload)[source]

Optional “auto-label” endpoint (Strategy 2 from spec §9.1).

Applies a simple piecewise score → label mapping for each known axis so the lab can demonstrate policy drift as well as LLM drift. Unknown axes are left unchanged.

Delegates to app.relabel_policy.apply_relabel_policy() which owns the policy table and mapping logic.

Parameters:

payload (Current AxisPayload (axes with scores, possibly stale labels).)

Return type:

Updated AxisPayload with labels recomputed from scores.

app.main.analyze_delta(req)[source]

Signal Isolation Layer endpoint.

Takes two text strings (baseline A and current B), runs both through the NLP pipeline (tokenise, lemmatise, filter stopwords), and returns the set difference as alphabetically sorted word lists.

This endpoint surfaces meaningful lexical pivots by filtering structural noise. It is deterministic: same inputs always produce the same outputs.

The LLM is not involved — this is pure programmatic text analysis.

Parameters:

req (DeltaRequest) – Contains baseline_text and current_text.

Returns:

Alphabetically sorted removed and added content-lemma lists.

Return type:

DeltaResponse

app.main.transformation_map(req)[source]

Transformation Map endpoint with micro-indicators.

Takes two text strings (baseline A and current B), runs sentence-aware alignment followed by token-level diffing, and returns clause-level replacement pairs. Each row is then classified with structural micro-indicators (compression, embodiment shift, intensity change, etc.) using deterministic heuristics.

This fills the gap between word-level diff (too granular) and content-word delta (structure-blind) by showing what chunk of text was replaced by what chunk at the clause scale, annotated with structural pattern labels.

The LLM is not involved — this is pure programmatic text analysis.

Parameters:

req (TransformationMapRequest) – Contains baseline_text, current_text, and optional indicator_config for tuning heuristic thresholds.

Returns:

Ordered list of TransformationMapRow replacement pairs, each annotated with zero or more micro-indicator labels.

Return type:

TransformationMapResponse

async app.main.import_save(file)[source]

Accept an uploaded save-package zip, validate it, and return structured state for the frontend to restore a complete session.

The endpoint reads the uploaded file, enforces a maximum upload size, validates the zip structure and manifest checksums (if present), then extracts plain text from the Markdown files so the frontend can populate the UI directly without parsing.

Parameters:

file (The uploaded zip file (multipart form data).)

Returns:

ImportResponse

Return type:

Everything the frontend needs to restore session state.

:raises HTTPException(400) : If the file is not a valid zip, exceeds size limits,: or fails checksum validation. :raises HTTPException(422) : If required files (metadata.json, payload.json,: system_prompt.md) are missing from the zip.

async app.main.import_chat_save(file)[source]

Accept an uploaded chat save-package zip and return structured state for the frontend to restore a complete chat translation session.

Extracts character payloads, model settings, system prompt, and the historical game log so the frontend can rebuild sliders, settings, and the in-game log panel without any further parsing.

Parameters:

file (The uploaded zip file (multipart form data).)

Returns:

ChatImportResponse

Return type:

Everything the frontend needs to restore chat state.

:raises HTTPException(400) : If the file is not a valid zip, exceeds size limits,: or fails checksum validation. :raises HTTPException(422) : If metadata.json is missing from the zip.: