An AI coding agent that turns a plain-text chat prompt into a working HTML/CSS/JS web app. You describe what you want. The agent plans, writes all three files, validates them, and shows you the result.
Runs in Docker. Uses Ollama Cloud for model inference — no local GPU required.
Open the UI at localhost:8000, create a project, and send a message like:
"Build me a pomodoro timer with a start/pause button and a session counter."
The agent then runs a four-stage pipeline:
- Plan — reasons about features, UI flow, and the cross-file class-name contract.
- HTML — writes
index.htmlwith semantic structure, all element IDs, and initial state classes. - JS — writes
script.jsreferencing the exact IDs from the HTML, with localStorage and modal logic. - CSS — writes
styles.cssusing every class from the HTML and JS, with a design from the approved palette.
After the four stages: validation, a test run, and a written summary.
When it is done, click Open HTML to view the result in your browser.
Three columns run in parallel as the agent works:
- Actions (left) — every file write and tool call, with expandable previews.
- Reasoning (middle) — live stream of the agent's planning and stage progress.
- Chat (right) — your messages and the agent's final summary.
| Button | What it does |
|---|---|
| Open HTML | Opens the project's index.html in a new browser tab via the local server. |
| New Project | Creates a new folder inside lch_workspaces/. Just enter a name — lch_ is prepended automatically. |
| Open Project | Lists all existing lch_ projects. Click one to load it. |
| Clear Chat | Resets the in-memory conversation context (does not delete files). |
| ? | Opens the tutorial. |
- Docker Desktop
- An Ollama Cloud account and API key
No local Ollama installation needed. The model runs on Ollama's cloud infrastructure.
git clone <repo-url>
cd low-cortisol-htmlcp .env.example .envEdit .env and fill in your values:
OLLAMA_API_KEY=your_ollama_cloud_api_key_here
OLLAMA_BASE_URL=https://ollama.com
ORCHESTRATOR_MODEL=qwen3.5
Your Ollama API key is available in your Ollama Cloud account dashboard.
mkdir -p lch_workspaces
touch lch_workspaces/.gitkeepAll generated projects are stored here. The folder is gitignored — your work stays local.
docker compose -f docker/docker-compose.yml up --buildOn subsequent runs (no code changes):
docker compose -f docker/docker-compose.yml upGo to http://localhost:8000.
Click New Project, enter a name, and start chatting.
cd ui
python server.pyThe server starts at http://localhost:8000. Your .env must be present in the project root.
| Variable | Default | Description |
|---|---|---|
OLLAMA_API_KEY |
— | Ollama Cloud API key (required for cloud mode). |
OLLAMA_BASE_URL |
http://localhost:11434 |
Ollama endpoint. Use https://ollama.com for cloud. |
ORCHESTRATOR_MODEL |
qwen3.5:9b |
Primary chat model name. |
ORCHESTRATOR_FALLBACK_MODEL |
qwen3:7b |
Fallback if the primary model does not support tool calls. |
EMBEDDING_MODEL |
nomic-embed-text |
Model used for tool-selection embeddings (local only). |
ORCHESTRATOR_AGENT_NUM_CTX |
32768 |
Context window size passed to the model. |
ORCHESTRATOR_CODE_NUM_PREDICT |
16384 |
Max tokens per coding stage response. |
ORCHESTRATOR_MEMORY_CHAR_BUDGET |
120000 |
Character budget before memory compaction triggers. |
ORCHESTRATOR_MEMORY_TAIL_COUNT |
16 |
Number of recent messages preserved verbatim after compaction. |
ORCHESTRATOR_REACT_MAX_ITERS_<STAGE> |
varies | Per-stage ReAct turn limit override (e.g. ORCHESTRATOR_REACT_MAX_ITERS_JS_CODE=8). |
UI_HOST |
127.0.0.1 |
UI server bind address (0.0.0.0 in Docker). |
UI_PORT |
8000 |
UI server port. |
Each stage runs multiple turns, not a single call. On each turn the model can:
- Call
read_file,search_files, orlist_directoryto explore the workspace. - Call
create_fileto write the complete output file. - Return plain text (no tool calls) to signal the stage is done.
Turn limits per stage: feature_plan 6, html_code 12, js_code 16, css_code 16. Override with environment variables.
If a coding stage returns a text plan with no tool calls and hasn't written its primary file yet, the controller sends a correction message and continues the loop rather than advancing. Same pattern applies to the test stage.
The agent communicates with the workspace through a local MCP server — a JSON-over-subprocess protocol. All file operations are sandboxed: the agent cannot read or write paths outside the current project folder.
| Tool | What it does |
|---|---|
create_file |
Write a complete file to the workspace. |
replace_range |
Replace a contiguous line range in an existing file. Used for surgical fixes without rewriting. |
append_to_file |
Append content to an existing file without rewriting it. |
insert_after_marker |
Insert content immediately after a unique marker string in a file. |
read_file |
Read a file (size-limited). |
list_directory |
List workspace contents. |
search_files |
Glob pattern + optional content substring search. |
plan_web_build |
Record a structured development plan as a workspace artifact. |
validate_web_app |
Check file links and key file presence. |
run_unit_tests |
Run a plain Node.js test file and return output. |
Before each stage, the controller uses text embeddings to rank all available tools by similarity to the stage's task, then a reranker model fine-tunes the order. Only the top-ranked tools are included in the model's context window. This reduces noise and keeps the prompt tight.
Each coding stage injects a dedicated skill guide from skills/:
| File | Controls |
|---|---|
skills/html.md |
Semantic structure, element ID rules, state class contract, modal pattern, required initial hidden classes, persistent action button rule (primary add/create must be in the header — never only inside a conditional empty-state section). |
skills/js.md |
Modal toggle pattern (hidden only), edit-state tracking, event delegation, escapeHtml usage rules. |
skills/css.md |
Color palette selection (Uncodixify), button classes, dynamic element styles, .hidden rule, banned anti-patterns. |
skills/test.md |
Node.js assert-only tests, what to test and what not to test. |
skills/context.md |
Context efficiency rules: avoid re-reading files already in context, write complete files once, keep reasoning concise. |
HTML is the source of truth. JS must reference only IDs and classes defined in the HTML. CSS must style only classes that exist in the HTML or are created by JS in dynamic elements. The only allowed visibility toggle class is hidden — alternatives like is-open, show, or visible are explicitly banned in all skill files and stage prompts.
Context is managed at multiple levels to prevent HTTP 500s on the shared Ollama Cloud server:
Dynamic num_ctx — each API call requests only the context window it actually needs. The controller measures the payload (message chars + tool schema chars), estimates token count, adds 20% overhead and the num_predict budget, then rounds up to the nearest 8192. This prevents allocating a fixed 200k KV cache for a 20k-token payload.
Per-iteration pruning — a _run_context_management step runs at the top of every loop iteration before the LLM call. It truncates all tool results to a head+tail summary and triggers compaction if over the soft budget (600k chars / ~200k tokens).
Per-turn tool result trimming — every read_file result is trimmed to 800 chars immediately after being stored in memory, not only when the budget overflows.
Content scrubbing — once create_file succeeds, the full file content stored in that tool call's arguments (8–15k chars) is replaced with a one-line stub. The file is on disk and cross-file refs are in PLAN.md — it does not need to stay in the KV cache.
Compact HTML/JS refs — coding stage prompts inject 10–30 line summaries of element IDs, buttons, modals, and dynamic classes rather than the full source files (100–300 lines each).
Compaction algorithm — when over budget, the compactor preserves the first 2 messages and the last 16 verbatim, then replaces everything in between with a structured summary of which files were created, which tools were called, and one reasoning snippet per turn.
Critical rules are appended at the end of each coding stage prompt in addition to appearing at the start. This is position-aware: attention recall is highest at the beginning and end of a context (U-shaped curve) and lowest in the middle where large skill guides sit.
low-cortisol-html/
├── docker/
│ ├── Dockerfile Python 3.11-slim + git + nodejs
│ └── docker-compose.yml Service definition, env_file, volume mounts
├── docs/phase_docs/ Per-phase implementation notes
├── embeddings/ Cached tool embedding vectors (gitignored at runtime)
├── lch_workspaces/ Generated project folders (gitignored contents)
├── logs/ Runtime logs (gitignored)
├── mcp_server/
│ ├── server.py JSON-over-subprocess MCP server
│ ├── tool_registry.py Tool registration and dispatch
│ └── tools/
│ ├── file_tools.py create_file, read_file, list_directory, search_files
│ ├── web_tools.py validate_web_app, run_unit_tests, plan_web_build
│ ├── sandbox.py Path sandboxing and schema validation
│ └── action_logger.py Audit log writer
├── orchestrator/
│ ├── main_orchestrator.py Entry point: startup, model selection, fallback
│ ├── loop_controller.py Four-stage pipeline, ReAct loop, memory compaction
│ ├── ollama_client.py HTTP client for Ollama API (stdlib only, no SDK)
│ ├── planner.py Pre-pipeline planning call
│ ├── reranker.py Tool reranker
│ ├── tool_pruner.py Embedding-based tool selection
│ ├── project_memory.py Semantic file index for existing projects
│ └── session_memory.py Message list with structured compaction
├── skills/
│ ├── html.md HTML rules and cross-file contract
│ ├── js.md JavaScript rules
│ ├── css.md CSS rules + Uncodixify design philosophy
│ ├── test.md Unit test rules
│ └── context.md Context efficiency rules
├── ui/
│ ├── index.html Browser UI
│ ├── style.css UI styles
│ ├── script.js UI logic (SSE, streaming, modals)
│ └── server.py Python HTTP server + SSE endpoint
├── .env.example Template for required environment variables
└── .gitignore
- Be specific about layout, colors, and interactions in your prompt.
- For follow-up changes, just type what to update — the agent reads existing files first and rewrites them completely.
- The agent works best for single-page apps. Complex multi-page sites or framework-based projects are outside its scope.
- If a stage runs out of turns without writing its file, increase the relevant
ORCHESTRATOR_REACT_MAX_ITERS_*variable.