From 17d37d76df71c779ed15700d3f471a275d47dc8d Mon Sep 17 00:00:00 2001 From: Sam Edwards <264948+handstandsam@users.noreply.github.com> Date: Mon, 10 Aug 2026 11:32:25 -0400 Subject: [PATCH 1/3] Add every hosted Gemma 4 variant to the built-in model list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #225 added one Gemma 4 model to the google provider. Google serves three on the Gemini API free tier (12B dense, 26B-A4B MoE, 31B dense) and Ollama publishes the same three locally, so all six are now built in — pick one by id, no specs to look up. Two details worth calling out: - `gemma-4-31b-it` and `gemma-4-26b-a4b-it` are both real, distinct models. The mismatch between #225's yaml and its LLM_MODELS.md row read like a typo; it wasn't, and regenerating the doc silently dropped one of them. Both are here now. - max_output_tokens is 32768, not the 65536 the Gemini models use. Google publishes no output cap for Gemma 4; 32768 is the largest figure any host documents. The e2b/e4b edge builds are deliberately omitted from Ollama — 128K context and much weaker at the screenshot-driven tool calling Trailblaze's agent loop does. Note `gemma4:latest` resolves to e4b, so name a size explicitly. LLM_MODELS.md is generated from the provider yamls (`:docs:generator:run`). --- docs/generated/LLM_MODELS.md | 7 +++++- .../trails/config/providers/google.yaml | 22 ++++++++++++++++++- .../trails/config/providers/ollama.yaml | 13 +++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/docs/generated/LLM_MODELS.md b/docs/generated/LLM_MODELS.md index c2d199f9d..18046a3df 100644 --- a/docs/generated/LLM_MODELS.md +++ b/docs/generated/LLM_MODELS.md @@ -21,12 +21,17 @@ Trailblaze ships with the following built-in models. When you reference a model | `gemini-3.1-flash-lite-preview` | 1M | 65K | $0.25 | $1.50 | $0.03 | basic-json-schema, completion, document, image, multipleChoices, openai-endpoint-chat-completions, openai-endpoint-responses, speculation, standard-json-schema, temperature, toolChoice, tools | | `gemini-3.1-pro-preview` | 1M | 65K | $2.00 | $12.00 | $0.20 | basic-json-schema, completion, document, image, multipleChoices, openai-endpoint-chat-completions, openai-endpoint-responses, speculation, standard-json-schema, temperature, toolChoice, tools | | `gemini-3.1-pro-preview-customtools` | 1M | 65K | $2.00 | $12.00 | $0.20 | basic-json-schema, completion, document, image, multipleChoices, openai-endpoint-chat-completions, openai-endpoint-responses, speculation, standard-json-schema, temperature, toolChoice, tools | -| `gemma-4-31b-it` | 262K | 65K | free | free | free | basic-json-schema, completion, document, image, multipleChoices, openai-endpoint-chat-completions, openai-endpoint-responses, speculation, standard-json-schema, temperature, toolChoice, tools | +| `gemma-4-12b-it` | 262K | 32K | free | free | free | basic-json-schema, completion, document, image, multipleChoices, openai-endpoint-chat-completions, openai-endpoint-responses, speculation, standard-json-schema, temperature, toolChoice, tools | +| `gemma-4-26b-a4b-it` | 262K | 32K | free | free | free | basic-json-schema, completion, document, image, multipleChoices, openai-endpoint-chat-completions, openai-endpoint-responses, speculation, standard-json-schema, temperature, toolChoice, tools | +| `gemma-4-31b-it` | 262K | 32K | free | free | free | basic-json-schema, completion, document, image, multipleChoices, openai-endpoint-chat-completions, openai-endpoint-responses, speculation, standard-json-schema, temperature, toolChoice, tools | ## Ollama | Model ID | Context | Max Output | Input $/1M | Output $/1M | Cached Input $/1M | Capabilities | |----------|---------|------------|-----------|------------|-------------------|--------------| +| `gemma4:12b` | 262K | 8K | free | free | free | basic-json-schema, completion, document, image, multipleChoices, openai-endpoint-chat-completions, openai-endpoint-responses, speculation, standard-json-schema, temperature, toolChoice, tools | +| `gemma4:26b` | 262K | 8K | free | free | free | basic-json-schema, completion, document, image, multipleChoices, openai-endpoint-chat-completions, openai-endpoint-responses, speculation, standard-json-schema, temperature, toolChoice, tools | +| `gemma4:31b` | 262K | 8K | free | free | free | basic-json-schema, completion, document, image, multipleChoices, openai-endpoint-chat-completions, openai-endpoint-responses, speculation, standard-json-schema, temperature, toolChoice, tools | | `gpt-oss:120b` | 131K | 65K | free | free | free | basic-json-schema, completion, document, multipleChoices, openai-endpoint-chat-completions, openai-endpoint-responses, speculation, standard-json-schema, temperature, toolChoice, tools | | `gpt-oss:20b` | 131K | 65K | free | free | free | basic-json-schema, completion, document, multipleChoices, openai-endpoint-chat-completions, openai-endpoint-responses, speculation, standard-json-schema, temperature, toolChoice, tools | | `qwen3-vl:2b` | 131K | 8K | free | free | free | basic-json-schema, completion, document, image, multipleChoices, openai-endpoint-chat-completions, openai-endpoint-responses, speculation, standard-json-schema, temperature, toolChoice, tools | diff --git a/trailblaze-models/src/commonMain/resources/trails/config/providers/google.yaml b/trailblaze-models/src/commonMain/resources/trails/config/providers/google.yaml index 5f122dde3..de2b42d6f 100644 --- a/trailblaze-models/src/commonMain/resources/trails/config/providers/google.yaml +++ b/trailblaze-models/src/commonMain/resources/trails/config/providers/google.yaml @@ -46,9 +46,29 @@ models: output_per_million: 1.50 cached_input_per_million: 0.025 + # Gemma 4 — open weights, served on the Gemini API at $0 on a rate-limited free tier. + # Multimodal (text + image) with native function calling; 256K context on all three. + # max_output_tokens is 32768 rather than the 65536 the Gemini models above use: Google + # publishes no output cap for Gemma 4, and 32768 is the largest figure any host documents. + - id: gemma-4-12b-it + context_length: 262144 + max_output_tokens: 32768 + cost: + input_per_million: 0.0 + output_per_million: 0.0 + cached_input_per_million: 0.0 + + - id: gemma-4-26b-a4b-it + context_length: 262144 + max_output_tokens: 32768 + cost: + input_per_million: 0.0 + output_per_million: 0.0 + cached_input_per_million: 0.0 + - id: gemma-4-31b-it context_length: 262144 - max_output_tokens: 65536 + max_output_tokens: 32768 cost: input_per_million: 0.0 output_per_million: 0.0 diff --git a/trailblaze-models/src/commonMain/resources/trails/config/providers/ollama.yaml b/trailblaze-models/src/commonMain/resources/trails/config/providers/ollama.yaml index 495883314..a25bb9f63 100644 --- a/trailblaze-models/src/commonMain/resources/trails/config/providers/ollama.yaml +++ b/trailblaze-models/src/commonMain/resources/trails/config/providers/ollama.yaml @@ -53,3 +53,16 @@ models: - id: "qwen3.5:latest" context_length: 131072 max_output_tokens: 8192 + + # Gemma 4 models (vision-capable, 256K context) + # The edge builds (e2b/e4b — and `gemma4:latest`, which resolves to e4b) are omitted + # deliberately: 128K context and much weaker at screenshot-driven tool calling. + - id: "gemma4:12b" + context_length: 262144 + max_output_tokens: 8192 + - id: "gemma4:26b" + context_length: 262144 + max_output_tokens: 8192 + - id: "gemma4:31b" + context_length: 262144 + max_output_tokens: 8192 From 1bbcffe9c45f899341e03de01e02fb1b6825e18e Mon Sep 17 00:00:00 2001 From: Sam Edwards <264948+handstandsam@users.noreply.github.com> Date: Mon, 10 Aug 2026 11:32:35 -0400 Subject: [PATCH 2/3] Document how to use a model Trailblaze doesn't ship with MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Chasing model releases through Trailblaze releases is the wrong loop, and nothing required it — a workspace trailblaze.yaml can declare any model its provider serves, and it takes effect on the next run. That was documented only as a passing sentence inside the LLM reference. New 'Adding a Model' page covers it directly: how a workspace entry merges with the built-in list (by id — new ids add, known ids override field-by-field), the 131K/8K fallbacks you get by omitting the limits, recipes for hosted, local Ollama, and gateway models, and how to contribute a model back to the built-in registry (including regenerating LLM_MODELS.md, which is generated). Also fills a real gap: the page titled 'Configuration' documented Android instrumentation args and scripting timeouts and never mentioned trailblaze.yaml itself. It now opens with the file — where it lives, that every section is optional, what each top-level key does, the inline-or-ref shape, and one precedence table — with the existing sections kept below. --- docs/adding_a_model.md | 194 ++++++++++++++++++++++++++++++++++++++ docs/configuration.md | 123 +++++++++++++++++++++++- docs/index.md | 3 +- docs/llm_configuration.md | 6 ++ 4 files changed, 324 insertions(+), 2 deletions(-) create mode 100644 docs/adding_a_model.md diff --git a/docs/adding_a_model.md b/docs/adding_a_model.md new file mode 100644 index 000000000..6f1571cae --- /dev/null +++ b/docs/adding_a_model.md @@ -0,0 +1,194 @@ +--- +title: Adding a Model +--- + +# Adding a Model + +**You do not need a new Trailblaze release to use a new model.** Model definitions are +config, not code. Add the model to your workspace's `trails/config/trailblaze.yaml` and +Trailblaze picks it up on the next run. + +The built-in registry ([Built-in Models](generated/LLM_MODELS.md)) exists so common models +work with zero configuration and correct specs. It is a convenience, not a gate — a model +missing from it is not a model Trailblaze refuses to run. Model families ship faster than +release cycles, so this page is the escape hatch that keeps you off the upgrade treadmill. + +## The 30-second version + +```yaml +# trails/config/trailblaze.yaml +llm: + providers: + google: + models: + - id: gemma-4-31b-it + context_length: 262144 + max_output_tokens: 32768 + defaults: + model: gemma-4-31b-it +``` + +```bash +trailblaze config models # confirm it's listed +trailblaze config llm google/gemma-4-31b-it +``` + +Set the provider's API key (`GOOGLE_API_KEY` here — see the +[env var table](llm_configuration.md#environment-variables)) and you're running. + +## How your entry combines with the built-ins + +Workspace config **adds to** the built-in registry — it does not replace it. Entries are +matched by `id`: + +| Your `id` | Result | +|---|---| +| Not in the built-in registry | A new model, defined entirely by your entry | +| Already in the built-in registry | Field-by-field merge — the fields you set win, the rest are inherited | + +So a workspace entry is equally the way to **add** a model and the way to **pin or correct** +one that ships in the box (stale pricing, a context window your gateway caps lower). + +Anything you omit falls back to a safe default rather than failing: `context_length` +defaults to **131072** and `max_output_tokens` to **8192**. Both are conservative — set them +explicitly for any model whose real limits you know, or you'll silently leave context on the +table. + +## Filling in the values + +| Field | Set it when | Where to find it | +|---|---|---| +| `id` | Always | The exact model string the provider's API expects — copy it from the provider's model list, not from marketing copy | +| `context_length` | Always, in practice | The provider's model card / docs | +| `max_output_tokens` | Always, in practice | Same. If the provider publishes no figure, use the largest any host documents | +| `vision` | Text-only models | Set `false`. Defaults to `true` | +| `cost.*` | You want spend reported accurately | Provider pricing page. Local / free-tier models: `0.0` | +| `temperature` | The model needs a non-default | Provider guidance | +| `screenshot.max_dimensions` | The model has a tighter image limit | Provider image-input docs | + +Full field reference: [LLM Configuration → Model fields](llm_configuration.md#model-fields). + +**What Trailblaze actually needs from a model.** The agent loop drives a device by looking at +an annotated screenshot and emitting tool calls, so a model wants **image input** and +**function/tool calling** to be useful. A text-only model (`vision: false`) still works for +text-only flows, but it will struggle on anything that needs to read the screen. Smaller +local models frequently accept the tools and then ignore them — try before you commit a +team-wide default. + +## Recipes + +### A hosted model on a built-in provider + +`openai`, `anthropic`, `google`, `ollama`, and `openrouter` already know their endpoint and +auth env var. You only supply the model: + +```yaml +llm: + providers: + openrouter: + models: + - id: google/gemma-4-31b-it:free + context_length: 262144 + max_output_tokens: 32768 + cost: + input_per_million: 0.0 + output_per_million: 0.0 +``` + +### A local Ollama model + +```yaml +llm: + providers: + ollama: + models: + - id: "gemma4:31b" + context_length: 262144 + max_output_tokens: 8192 + defaults: + model: "gemma4:31b" +``` + +Quote Ollama ids — the `:` makes them look like YAML mappings otherwise. Trailblaze also +discovers whatever `ollama list` reports at runtime, so a model already pulled locally shows +up without any config; listing it explicitly is how you tell teammates which model the +project expects. Nothing is auto-downloaded — they run `ollama pull gemma4:31b`. + +### A model behind your own gateway + +A provider Trailblaze has never heard of is the same amount of work, plus the endpoint: + +```yaml +llm: + providers: + acme_gateway: + type: openai_compatible + base_url: "https://ai.acme.example.com/v1" + auth: + env_var: ACME_AI_TOKEN + models: + - id: acme-vision-large + context_length: 200000 + max_output_tokens: 32768 + defaults: + model: acme-vision-large +``` + +See [Enterprise gateway](llm_configuration.md#enterprise-gateway) for headers, custom +completion paths, and the on-device story. + +### Override a built-in model's specs + +Specify only what you're changing: + +```yaml +llm: + providers: + openai: + models: + - id: gpt-4.1 + max_output_tokens: 16384 # our gateway caps output lower than the default +``` + +## Where to put the file + +| Scope | Path | +|---|---| +| Whole team (commit it) | `/trails/config/trailblaze.yaml` | +| Just you | `~/.trailblaze/trailblaze.yaml` | + +The workspace file wins over the user file; environment variables win over both. Full +precedence table: [Configuration → Precedence](configuration.md#precedence). + +Committing the workspace file is the recommended shape for teams — everyone who clones the +repo gets a working model with no per-machine setup, and the project is pinned to models you +have actually validated rather than to whatever the current release happens to ship. + +## Verifying + +```bash +trailblaze config models # every model Trailblaze can see, per provider +trailblaze config llm # the provider/model currently selected +trailblaze config show # all persisted settings +``` + +If your model isn't listed, the usual causes are: the file isn't at +`trails/config/trailblaze.yaml` (Trailblaze walks up from the current directory looking for +exactly that path — see [Project Layout](project_layout.md)), the entry is nested under the +wrong provider key, or an unquoted `id` containing `:` parsed as a map. + +## Contributing a model to the built-in registry + +Once a model is worth having work out of the box for everyone, send it upstream — but note +you never have to wait for that to use it. + +1. Add the entry to the matching provider file in + [`trailblaze-models/src/commonMain/resources/trails/config/providers/`](https://github.com/block/trailblaze/tree/main/trailblaze-models/src/commonMain/resources/trails/config/providers). +2. Regenerate the docs — [`docs/generated/LLM_MODELS.md`](generated/LLM_MODELS.md) is + generated from those provider files, so hand-editing it drifts and fails CI: + + ```bash + ./gradlew :docs:generator:run + ``` + +3. Commit both the provider YAML and the regenerated `LLM_MODELS.md`. diff --git a/docs/configuration.md b/docs/configuration.md index b22b23a9f..da1395f8d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -2,6 +2,124 @@ title: Configuration --- +# Configuration + +Most of Trailblaze needs no configuration — a single `.trail.yaml` file is a complete +project. When you do want project-level settings (which app to target, which LLM to use, +which toolsets your agent sees), they live in one file: **`trailblaze.yaml`**. + +## `trailblaze.yaml` — the workspace config file + +``` +my-project/ +└── trails/ + ├── config/ + │ └── trailblaze.yaml ← this file + └── login/ + └── trail.yaml +``` + +Trailblaze walks up from the current directory (or from the directory containing the trail +you invoked) until it finds `trails/config/trailblaze.yaml`. The owning `trails/` directory +becomes the **workspace root**, and every relative path inside the file resolves against +`trails/config/`. See [Project Layout](project_layout.md) for the discovery rules and +[External Config](generated/external-config.md) for the full `trails/config/` directory shape. + +`trailblaze.yaml` is config, never a trail — Trailblaze will not try to run it. + +**Every section is optional**, and an empty file is valid. Committing it is the point: it's +how a team pins one target, one model, and one toolset surface for everybody who clones the +repo, with no per-machine setup. + +```yaml +# trails/config/trailblaze.yaml — everything below is optional +defaults: + target: my-app + max-llm-calls: 25 + +targets: + - my-app + +llm: + providers: + openai: + models: + - id: gpt-4.1 + defaults: + model: gpt-4.1 +``` + +### Top-level keys + +| Key | Type | What it does | +|---|---|---| +| `defaults` | map | Workspace-wide defaults — see below | +| `targets` | list of ids | Target-trailmap ids this workspace opts into. **Omit to auto-discover** every target trailmap under `/trailmaps/`. Listing ids is how a workspace with many trailmaps loads only a subset. Each id must be a *target* trailmap (one with a `target:` block); library trailmaps reach scope through a target's `dependencies:`. | +| `toolsets` | list | Extra toolsets, either written inline or pulled in with `ref: path/to/toolset.yaml` | +| `tools` | list | Extra tools, same inline-or-`ref:` shape | +| `providers` | list | Reserved for standalone LLM provider files. Provider and model definitions are read from the `llm:` block today — put them there. | +| `llm` | map | LLM providers, models, and defaults — see [LLM Configuration](llm_configuration.md) | + +### `defaults` + +| Key | What it does | +|---|---| +| `target` | Target-trailmap id used when nothing more specific is set. Must match a loaded target (case-sensitive); an unknown id is logged and skipped rather than failing the run. | +| `max-llm-calls` | Team-wide cap on LLM calls per objective, so every developer and CI runner inherits the same budget without passing `--max-llm-calls`. Positive integer. | + +### `ref:` entries + +`toolsets`, `tools`, and `providers` each accept either the entry written inline or a +pointer to a separate file: + +```yaml +toolsets: + - ref: toolsets/my-toolset.yaml # relative to trails/config/ + - name: inline-toolset # or write the whole thing here + tools: [tapOn, assertVisible] +``` + +Ref paths are always resolved relative to the directory holding `trailblaze.yaml` — a +leading `/` is stripped and treated the same way, so `/foo.yaml` is not an escape to the +filesystem root. A `ref:` entry may not carry any sibling keys. + +## Precedence + +Later rows win. + +| Priority | Source | Scope | +|---|---|---| +| 1 (lowest) | Built-in defaults shipped in the binary | Everyone | +| 2 | `~/.trailblaze/trailblaze.yaml` | Just you, every workspace | +| 3 | `/trails/config/trailblaze.yaml` | Everyone in this project | +| 4 | Persisted per-machine settings (`trailblaze config …`) | Just you, this machine | +| 5 (highest) | Environment variables and per-run CLI flags | This invocation | + +Two clarifications worth knowing: + +- **Workspace beats user file, but per-run beats everything.** A committed workspace file is + the team's baseline; `--target`, `-d`, `TRAILBLAZE_DEFAULT_MODEL` and friends still win for + a single run, which is what makes CI overrides work. +- **`defaults.target` is deliberately ranked below a real user selection but above the + neutral built-in target** — and a persisted selection of the neutral `default` target does + *not* count as a real selection, so it can't mask the committed workspace default. Full + ordering in [Project Layout → Workspace defaults](project_layout.md#workspace-defaults). + +## Common tasks + +| I want to… | Go to | +|---|---| +| Use a model that isn't built in | [Adding a Model](adding_a_model.md) | +| Point at a private LLM gateway | [LLM Configuration](llm_configuration.md#enterprise-gateway) | +| See what's currently in effect | `trailblaze config show` ([CLI](CLI.md#trailblaze-config)) | +| Set a default target for the team | `defaults.target`, above | +| Understand the `trails/config/` directory | [External Config](generated/external-config.md) | +| Add custom tools to a project | [Your First Trailmap](your-first-trailmap.md) | + +Per-machine settings (`trailblaze config llm`, `trailblaze config target`, …) live in +`~/.trailblaze/` and are documented with the [`trailblaze config`](CLI.md#trailblaze-config) +command. + ## On-Device Android Instrumentation Arguments * `trailblaze.aiEnabled` (defaults to `true`) - This will have the Trailblaze SDK send all requests to the LLM. When `false`, only recordings can be used. * `trailblaze.reverseProxy` (defaults to `false`) - This will enable the reverse proxy for all Trailblaze traffic. @@ -13,6 +131,9 @@ title: Configuration * `trailblaze.httpsPort` (defaults to `52526`, i.e. `trailblaze.port` + 1) - The HTTPS port for the Trailblaze server. Override this when running multiple Trailblaze instances. * `trailblaze.logsEndpoint` - Defaults to the same values as the `reverseProxy` uses. You can use this value if you want to use a remote logs server. NOTE: Logging timeouts are set to 5 seconds as they are expected to be fast. +LLM selection for on-device runs has its own resolution order — see +[LLM Configuration → On-Device Android Agent](llm_configuration.md#on-device-android-agent). + ## Scripting Callback Channel Tuning knobs for the `/scripting/callback` endpoint that backs the TypeScript scripting SDK's `client.tools.(args)` round-trip (the wire-protocol callback name inside the framework is `callTool`). Defaults are production-ready; override only when a slow emulator or unusual composition graph needs more headroom. @@ -20,4 +141,4 @@ Tuning knobs for the `/scripting/callback` endpoint that backs the TypeScript sc * `-Dtrailblaze.callback.timeoutMs` (JVM system property, defaults to `120000`) — Per-callback dispatch timeout on the daemon side. Bounds how long a single `client.tools.(args)` dispatch can run before the daemon returns a structured timeout error. Raise when a target tool is legitimately slow (e.g. waiting for a screen to settle on a slow emulator). * `TRAILBLAZE_CLIENT_FETCH_TIMEOUT_MS` (env var, defaults to `32000` standalone) — Client-side fetch timeout in the subprocess. At runtime the daemon forwards its own timeout value + 2 s as this variable, so the daemon is normally the one that surfaces a structured timeout. **If you raise `trailblaze.callback.timeoutMs`, raise this in lockstep** — otherwise the client aborts the HTTP request before the daemon can return and the daemon-side override is defeated. Sampled once at SDK module load; must be set before `import { trailblaze } from "@trailblaze/scripting"`. * `-Dtrailblaze.callback.maxDepth` (JVM system property, defaults to `16`) — Reentrance cap for recursive callback chains. A subprocess tool that calls back into the daemon to dispatch another subprocess tool counts as one level; the cap prevents runaway recursion from wedging a session until the outer agent timeout fires. Raise only if you have a legitimate deep-composition use case (e.g. recursive tree-walker). -* `-Dtrailblaze.callback.maxBodyBytes` (JVM system property, defaults to `1048576` / 1 MB) — Maximum accepted `JsScriptingCallbackRequest` body size. Requests whose declared `Content-Length` exceeds this are rejected with HTTP 413 before buffering. Real callback payloads are tiny (invocation id, session id, a single action with a JSON-string args field) so the cap is pure belt-and-suspenders against a buggy subprocess emitting a runaway args string. Raise only if a legitimate tool needs to pass a very large args payload through the callback channel. \ No newline at end of file +* `-Dtrailblaze.callback.maxBodyBytes` (JVM system property, defaults to `1048576` / 1 MB) — Maximum accepted `JsScriptingCallbackRequest` body size. Requests whose declared `Content-Length` exceeds this are rejected with HTTP 413 before buffering. Real callback payloads are tiny (invocation id, session id, a single action with a JSON-string args field) so the cap is pure belt-and-suspenders against a buggy subprocess emitting a runaway args string. Raise only if a legitimate tool needs to pass a very large args payload through the callback channel. diff --git a/docs/index.md b/docs/index.md index a499220b0..e9864b64b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -273,7 +273,8 @@ and project context to the loop, which the built-in agent can't. the [Trailmaps](trailmaps.md) manifest schema, and the [Trailblaze Tools](tools.md) catalog of scripted / pure-YAML / Kotlin flavors. - **Customizing the LLM?** See [LLM Configuration](llm_configuration.md) and - [Built-in Models](generated/LLM_MODELS.md). + [Built-in Models](generated/LLM_MODELS.md). Using a model that isn't built in — no + Trailblaze upgrade required — is [Adding a Model](adding_a_model.md). - **Going deep?** See [Architecture](architecture.md) and the [devlog](devlog/index.md). ## License diff --git a/docs/llm_configuration.md b/docs/llm_configuration.md index 840a8df05..89ca42d5e 100644 --- a/docs/llm_configuration.md +++ b/docs/llm_configuration.md @@ -4,6 +4,10 @@ title: LLM Configuration Trailblaze supports configurable LLM providers and models via YAML files. This allows teams to use enterprise endpoints, custom gateways, self-hosted models, and project-specific defaults without modifying source code. +> **Just want to use a model Trailblaze doesn't ship with?** You don't need a new release — +> add it to your workspace config. [Adding a Model](adding_a_model.md) is the short, +> task-focused version of this page. + ## Configuration Loading Order Configuration is loaded from multiple locations. Later sources override earlier ones: @@ -370,3 +374,5 @@ Trailblaze ships with a registry of models from major providers. See [Built-in L When referencing a built-in model by `id` in your YAML config, all specs (pricing, context length, capabilities) are inherited automatically. You only need to specify fields you want to override. Built-in model specs are updated with each Trailblaze release. If you need stable, predictable pricing or specs, override them in your workspace config. + +**A model missing from the registry is not blocked** — the registry only saves you from typing the specs. Declare any model your provider serves in `trails/config/trailblaze.yaml` and it works on the next run, no upgrade needed. See [Adding a Model](adding_a_model.md), which also covers contributing the model back to the built-in registry. From ff68dbd134dc5002aad636194840aa1652f67665 Mon Sep 17 00:00:00 2001 From: Sam Edwards <264948+handstandsam@users.noreply.github.com> Date: Mon, 10 Aug 2026 12:53:00 -0400 Subject: [PATCH 3/3] Point the External Config page at the trailblaze.yaml documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Landing on the generated External Config page gave no way to find out what trailblaze.yaml itself is — the page covers the trails/config/ directory (targets, toolsets, tools) and only mentions the file in passing. It now opens with a pointer to the Configuration page, which documents the file's keys, workspace defaults, and precedence. Generator change (ExternalConfigDocsGenerator) + regenerated output. --- docs/generated/external-config.md | 2 ++ .../block/trailblaze/docs/ExternalConfigDocsGenerator.kt | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/docs/generated/external-config.md b/docs/generated/external-config.md index 06ebf288c..3d46afb86 100644 --- a/docs/generated/external-config.md +++ b/docs/generated/external-config.md @@ -8,6 +8,8 @@ title: External Config Trailblaze's desktop/CLI binary currently builds the effective app-target config from three layers, in order: framework-bundled `trails/config/**` resources, an optional workspace `trails/config/` directory, and the current workspace's `trails/config/trailblaze.yaml` entries. Later layers override earlier ones by id / filename. +> This page covers the `trails/config/` **directory** — authoring targets, toolsets, and tools. The `trailblaze.yaml` **file** itself (its keys, workspace defaults, and precedence) is documented in [Configuration](../configuration.md). + The intended split is now the live split: `trails/` is the workspace anchor, `trails/config/trailblaze.yaml` is the workspace manifest, and `trails/config/` is the artifact directory that holds concrete trailmap, target, toolset, and tool files, plus the reserved location for provider YAMLs. ## Lookup Order for Filesystem Config diff --git a/docs/generator/src/main/java/xyz/block/trailblaze/docs/ExternalConfigDocsGenerator.kt b/docs/generator/src/main/java/xyz/block/trailblaze/docs/ExternalConfigDocsGenerator.kt index 1743bfba3..aa620a42e 100644 --- a/docs/generator/src/main/java/xyz/block/trailblaze/docs/ExternalConfigDocsGenerator.kt +++ b/docs/generator/src/main/java/xyz/block/trailblaze/docs/ExternalConfigDocsGenerator.kt @@ -55,6 +55,14 @@ class ExternalConfigDocsGenerator( "entries. Later layers override earlier ones by id / filename.", ) appendLine() + appendLine( + "> This page covers the `${TrailblazeConfigPaths.WORKSPACE_CONFIG_DIR}/` **directory** — " + + "authoring targets, toolsets, and tools. The " + + "`${TrailblazeProjectConfigLoader.CONFIG_FILENAME}` **file** itself (its keys, " + + "workspace defaults, and precedence) is documented in " + + "[Configuration](../configuration.md).", + ) + appendLine() appendLine( "The intended split is now the live split: ${workspaceAnchorDir()} is the workspace anchor, " + "`${TrailblazeConfigPaths.WORKSPACE_CONFIG_FILE}` is the workspace manifest, and " +