Construct
Using an AI model, optionally
When a model is involved, how to choose one, and how output is checked.
Construct never needs an AI model. Two places can use one, and only when you ask.
Construct is a library of small, deterministic,
non-LLM building blocks. The only place an LLM is ever involved is where
a human explicitly opts in with --llm <provider> (CLI) or a Settings
capability (UI) — and even then, in one of exactly two shapes:
- Execution-class calls — small, scoped, mechanical: "write this one file's real implementation." One call per file, never one call for a whole batch. These may route to a local model (Ollama/Qwen Coder) as well as a hosted one (Claude).
- Plan-analysis-class calls — whole-feature, high-context reasoning:
"look at this entire existing feature and propose how to decompose it."
Exactly one call, always. Never allowed to route to a local model —
enforced in code (
ui/server/src/settings.mjs'supdateSettingsthrows ifplanAnalysisis set toollama), not just left as a UI convention.
Locating files, deciding which layers/files to scaffold, and deciding a feature's shape are never delegated to an LLM in either case — that stays deterministic code, or a human/Claude-in-conversation decision made before any of this runs.
Choosing a provider#
--llm claude runs the local claude command-line tool (it must be installed and signed in). --llm ollama calls a local Ollama server, by default at http://localhost:11434 with the model qwen2.5-coder:7b.
construct create layer Invoice --feature billing --layers domain,hook,page,controller --llm ollama
Which layers get created is still decided by Construct; the model only fills in the body of files that were going to be created anyway.
Model output is checked before it is written#
Every per-file fill (import, create, generate) goes through
packages/core/llm-fill.mjs: the prompt states the reply is captured from stdout and
that the model has no file access; a reply that wraps exactly one fenced
block in prose is unwrapped; the result must parse as TypeScript/JavaScript
(parseToAst) and contain a declaration, import or export. A rejected
reply gets one corrected retry; a provider call that throws is not
retried. Whatever happens, an unfilled file keeps its scaffolded stub (plus
the TODO(import) breadcrumb on the import path), the command reports it
per file, other files continue, and the exit code is 3.