@amd-gaia/gaia 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL.md CHANGED
@@ -127,9 +127,22 @@ for the version you have.
127
127
  The agent thinks with a model hosted by **Lemonade Server**, which this package
128
128
  does not install. Required before any query succeeds:
129
129
 
130
- 1. Lemonade **10.2.0 or newer**, running (`lemonade-server serve`).
131
- 2. The default model downloaded (`gaia download Gemma-4-E4B-it-GGUF`, or
132
- `gaia init`).
130
+ 1. Lemonade **10.2.0 or newer**, running. GAIA's daemon starts and supervises
131
+ one, so `gaia daemon start` is normally all that is needed (`gaia init`
132
+ also installs and starts it on first run). Qwen3.8 Flash Next needs
133
+ **v2026.39.1 or newer**, the version `gaia init` installs; older servers
134
+ cannot load it.
135
+ 2. The machine's default chat model downloaded — run `gaia init`. It picks
136
+ `Qwen3.6-35B-A3B-GGUF` where a GPU holds it (~27 GB for models,
137
+ weights plus context cache — a 64 GB+ Strix Halo or a 32 GB GPU) and
138
+ `Gemma-4-E4B-it-GGUF` everywhere else, CPU-only PCs included, and records the pick as
139
+ `default_model` in `~/.gaia/config.json`. On 128 GB-class PCs the user may
140
+ switch to the multimodal `user.Qwen3.8-Flash-Next-GGUF` with `gaia config set
141
+ default_model`. `model.id` below names whichever this machine uses.
142
+ `gaia download` takes **no** model argument — naming one makes it exit 2. To
143
+ pull a single model instead, repeat the command `GET /v1/gaia/init` gives
144
+ you: it names the Lemonade client this machine actually has. Do not invent
145
+ one.
133
146
 
134
147
  Do not guess — ask the sidecar. `GET /v1/gaia/init` is a read-only preflight
135
148
  (it never pulls or loads) that probes Lemonade, compares its version to the
@@ -150,7 +163,9 @@ alone:
150
163
  "min_version": "10.2.0", "compatible": null },
151
164
  "model": { "id": "Gemma-4-E4B-it-GGUF", "present": false,
152
165
  "loadable": null, "ctx_size": null },
153
- "hint": "Local Lemonade Server is not reachable at … — start it with `lemonade-server serve`, or set LEMONADE_BASE_URL to a running server."
166
+ // The manual fallback ("Otherwise: …") is resolved for the host's Lemonade
167
+ // install (tray app, macOS app, systemd service, or CLI) — render it verbatim.
168
+ "hint": "Local Lemonade Server is not reachable at …. GAIA starts it automatically — run `gaia daemon start` if the background service is not running. Otherwise: Start the Lemonade app from Applications, then retry. Or set LEMONADE_BASE_URL to a running server. See https://amd-gaia.ai/docs/guides/gaia."
154
169
  }
155
170
  ```
156
171
 
@@ -216,6 +231,12 @@ await shutdown(proc); // tree-kill; auto-cleanup also reaps on exit
216
231
  file path); read it from there, don't invent one. A 401 whose `detail` names
217
232
  both env vars means you sent the wrong token or none.
218
233
 
234
+ The programs the agent starts (shell commands, MCP servers, CLI installs and
235
+ sign-ins, native hub agents, media tools) never inherit this token
236
+ or GAIA's other internal credentials. To withhold your own variables from
237
+ them too, list the names in `GAIA_CHILD_ENV_DENY` (comma or space separated);
238
+ `GAIA_NO_DOTENV=1` stops the sidecar loading `.env` files.
239
+
219
240
  Or skip the code entirely and let the CLI own it:
220
241
 
221
242
  ```bash
@@ -226,7 +247,7 @@ curl http://127.0.0.1:8141/health
226
247
  ## 7. Call `POST /v1/gaia/query`
227
248
 
228
249
  This is the whole agent surface. There is **no typed query client** in this
229
- package — call it with plain `fetch`. Contract version **2.12**; the stream is
250
+ package — call it with plain `fetch`. Contract version **2.17**; the stream is
230
251
  `text/event-stream` terminated by **exactly one** `final` or `error`.
231
252
 
232
253
  Request body (`extra: "forbid"` — an unknown field is a **422**, not ignored):
@@ -236,10 +257,11 @@ Request body (`extra: "forbid"` — an unknown field is a **422**, not ignored):
236
257
  | `query` | yes | Non-empty. |
237
258
  | `run_id` | yes | **You mint it**, and it must be a UUID (non-UUID → 422). It is the cancel handle, valid from the instant the request is sent. |
238
259
  | `context` | yes | Transcript slice, pushed in the body — may be `[]`, never absent. Each item `{ role, content }`; `role` ∈ `user` / `assistant` / `system` / `tool`. |
239
- | `session_id` | no | Contract ≥ 2.12. **Pass it.** The agent persists its indexed-document set per session — without it, it forgets a document between the turn that indexed it and the next question. |
260
+ | `session_id` | no | Contract ≥ 2.12. **Pass it.** The agent persists its indexed-document set per session — without it, it forgets a document between the turn that indexed it and the next question. 1–128 characters from `A-Z a-z 0-9 . _ -` (a UUID works); anything else is a **400**. |
261
+ | `documents` | no | Contract ≥ 2.16. Absolute paths of the files the user attached to this conversation — send the whole set **every turn**. The agent indexes any it has not read yet before the turn runs, granting each exact file and not its folder. A relative path is a **422**. A missing file, a folder, or a refused secret is named as not indexed in the turn's `index_documents` step, and the turn still runs. |
240
262
  | `can_answer_questions` | no | Set `false` for one-shot / batch runs so the agent resolves ambiguity itself instead of parking on a question nobody can see. |
241
- | `model` | no | Overrides the model id — only when the run builds a fresh agent. On a retained `session_id` the agent already exists, so a model that differs from the one it was built with is a **409**, not an override. |
242
- | `provider` | no | Local inference only — anything but `"lemonade"` is a **400**. |
263
+ | `model` | no | Overrides the model id. On a retained `session_id` a different model is **switched in place** (contract ≥ 2.14), keeping the conversation and any loaded skills; a switch that fails is a **409** and leaves the session on its previous model. |
264
+ | `provider` | no | `"lemonade"` (the default backend) or `"claude"`, which sends the conversation to Anthropic's API instead of the local server. Anything else is a **400**. When you name it, `model` must agree: under `"claude"` it must be a `claude-*` id, under `"lemonade"` it must not be. A mismatch is a **400**, on a new session or an existing one. Omit it and the `model` decides instead — a `claude-*` id alone reaches Anthropic, any other id runs locally — so there is nothing to mismatch. On a retained `session_id`, naming a different provider switches the session to it in place, onto `model` or, without one, the provider's default model; omitting both leaves the session where it is. |
243
265
  | `max_steps` | no | ≥ 1. |
244
266
 
245
267
  ```ts
@@ -295,11 +317,11 @@ The canonical event shapes, as emitted:
295
317
 
296
318
  | Event | Shape |
297
319
  |---|---|
298
- | `status` | `{ type, message }` — progress and reasoning narration |
320
+ | `status` | `{ type, message, phase?, words?, chars? }` — progress and reasoning narration. `phase` (`loading_model`, `downloading_model`, `reading`, `reasoning`, `tool_call`) names what the model is doing; show it as the live line, not as narration |
299
321
  | `token` | `{ type, delta }` — answer text to append |
300
322
  | `tool_call` | `{ type, tool, args }` |
301
323
  | `tool_result` | `{ type, tool, data, render? }` |
302
- | `needs_confirmation` | `{ type, run_id, action, summary }` — no `confirm_url`; see §8 |
324
+ | `needs_confirmation` | `{ type, run_id, action, summary, confirm_id?, always_scope? }` — answer it, or it ends the run; see §8 |
303
325
  | `needs_input` | `{ type, run_id, request_id, question, options[], allow_free_text, sensitive, respond_url, timeout_seconds? }` |
304
326
  | `final` | `{ type, answer, usage? }` — terminal |
305
327
  | `error` | `{ type, detail, status }` — terminal, surface `detail` verbatim |
@@ -313,7 +335,7 @@ Rules a client must respect:
313
335
  guarantees one; a close without one means something broke on your side.
314
336
  - **Answer `needs_input`, don't restart.** The run is parked on the *same*
315
337
  stream. `POST /v1/gaia/query/{run_id}/respond` with
316
- `{ request_id, response }` and keep reading the existing stream — a fresh
338
+ `{ request_id, value }` and keep reading the existing stream — a fresh
317
339
  `/query` POST abandons the paused run. Unknown run → **404**; a `request_id` that
318
340
  is no longer pending → **409** (both loud, never a silent drop). Render each
319
341
  option's `description`, and mask the input when `sensitive` is set.
@@ -321,44 +343,116 @@ Rules a client must respect:
321
343
  `{ run_id, cancelled }` — an unknown id reports `cancelled: false` with a
322
344
  **200**, not a 404, because a cancel racing a normal completion is expected.
323
345
  Dropping the HTTP connection also cancels the run.
346
+ - **Add to a running turn with `POST /v1/gaia/query/{run_id}/followup`**
347
+ (contract ≥ 2.15, body `{ text }`). The run is not interrupted and no second
348
+ turn starts: the agent folds the text in at its next step boundary and
349
+ answers it alongside what it was already doing. Unknown run → **404**, an
350
+ agent that cannot take one → **409**; both are loud, so hold the message
351
+ rather than telling the user it was sent. `/query` is stateless, so put a
352
+ delivered follow-up into the next turn's `context` yourself, between that
353
+ turn's question and its answer.
324
354
 
325
- ## 8. Over `/v1/gaia/query`, confirmation-gated tools are **refused, not prompted**
355
+ ## 8. Over `/v1/gaia/query`, a gated tool asks — when you can answer
326
356
 
327
357
  Read this before you design a workflow around it. This section is about the HTTP
328
358
  surface — the agent's other transport can collect an approval; see SPEC §5.5.
329
359
 
330
- Seven of the agent's 71 tools mutate the machine and need explicit approval
331
- before they run. Five sit in the base `TOOLS_REQUIRING_CONFIRMATION` set —
360
+ Twelve of the agent's 95 tools mutate the machine and need explicit approval
361
+ before they run. Nine sit in the base `TOOLS_REQUIRING_CONFIRMATION` set —
332
362
  **`write_file`**, **`edit_file`**, **`run_shell_command`**,
333
- **`execute_python_file`**, and **`notify_desktop`**, which spawns a PowerShell
334
- child on Windows to draw the notification — and the flagship adds two of its
335
- own, **`install_skill`** and **`remove_skill`**, because installing a skill
336
- writes third-party code under `~/.gaia/skills` and removing one deletes it.
337
- Everything else — reading, indexing, querying, web fetching, memory — runs
338
- without asking.
339
-
340
- Over `/v1/gaia/query` there is **no way to collect an approval**, so the stream
341
- does not prompt. When the agent reaches one of those tools it emits a
342
- `needs_confirmation` event, and the server **immediately follows it with a
343
- terminal `final`** whose `answer` says it stopped before running that action,
344
- then cancels the run. There is no `confirm_url`, no resume, and no
345
- `/query/{run_id}/confirm` endpoint — it is a deliberate deny-by-default stub, not
346
- an oversight.
347
-
348
- Concretely, your client sees:
363
+ **`wait_for_condition`**, which re-runs a shell command until it succeeds,
364
+ **`execute_python_file`**, **`run_python`**, **`notify_desktop`**, which spawns
365
+ a PowerShell child on Windows to draw the notification, and **`install_cli`** /
366
+ **`sign_in_cli`**, which install software and sign a CLI in to the user's
367
+ account — and the flagship adds three of its own
368
+ (`CONFIRMATION_REQUIRED_TOOLS`): **`install_skill`**, **`capture_skill`**, and
369
+ **`remove_skill`**, because installing or capturing a skill writes third-party
370
+ content under `~/.gaia/skills` and removing one deletes it. A capture that does
371
+ land is additionally **code-inert**: its instructions load, but any
372
+ `tools.py`/scripts stay unregistered until a human runs
373
+ `gaia skill promote <name>` in a terminal. Everything else — reading,
374
+ indexing, querying, web fetching, memory, and the read-only `check_cli_setup`
375
+ — runs without asking. A write or edit inside the agent's own scratch
376
+ folder (`gaia-scratch-…` under the system temp folder) runs without asking too,
377
+ so running a script from it asks every time and carries no `always_scope`. So does a shell command run there or naming a path there, and a script handed a scratch path as an argument.
378
+
379
+ The eight live-browser tools are gated by a **hook**, not a set, so a name
380
+ alone does not tell you whether a call will ask. `browser_login` always asks.
381
+ `browser_click` / `browser_type` ask when the call acts inside a session the
382
+ run signed in to, or when the target element's label reads irreversible, and
383
+ run unasked on the open web. The hook **fails closed**: if the browser cannot
384
+ be asked what state it is in, the call is treated as authenticated and prompts.
385
+ Design a workflow around the approval path, not around a fixed tool list.
386
+
387
+ **Contract ≥ 2.14 can answer one.** Send a `session_id` and leave
388
+ `can_answer_questions` unset (or `true`). The stream emits `needs_confirmation`
389
+ carrying a `confirm_id` and then **stays open** while the agent waits:
390
+
391
+ ```
392
+ data: {"type":"needs_confirmation","run_id":"…","action":"run_shell_command","summary":"python -m pytest -q tests/\nCan run any code, with your permissions.","confirm_id":"…","always_scope":"pytest (runs the test code it finds)","risk":"execute"}
393
+ ```
394
+
395
+ Answer it on the same run, then keep reading the stream:
396
+
397
+ ```
398
+ POST /v1/gaia/query/{run_id}/tool_decision
399
+ { "decision": "allow" | "deny" | "always" | "timeout", "confirm_id": "…" }
400
+ ```
401
+
402
+ `summary` is the command or call itself, shown once — your UI supplies the
403
+ question. For a shell command it can carry two more lines: `in <folder>` when
404
+ the command runs outside the workspace, and one plain sentence on what a `write`
405
+ or `execute` command can do. Show it as-is. A CLI a skill would grant (`git
406
+ commit`, `python script.py`) with no such skill loaded arrives here as a prompt,
407
+ not a refusal. So does a command GAIA's own checks don't cover (`git push`,
408
+ `python -c`, a path outside the project): its `summary` is the exact line the
409
+ shell will run, unshortened, then `in <folder>`, then a risk sentence, and it
410
+ carries no `always_scope` — offer allow-once and deny only. `allow` runs that
411
+ line once; the next call asks again. `risk` says what the call does (`read`, `write`, `execute`,
412
+ `destructive`); label by it rather than by tool name, since `run_shell_command`
413
+ is `pytest` on one call and `rm -rf` on the next. `always` grants the family
414
+ named in `always_scope` for the rest of the session (`pytest` covers every
415
+ spelling of a test run) — say so in your UI, because it stops asking; no
416
+ `always_scope` means don't offer it. If your prompt expires unanswered, send
417
+ `timeout`, not `deny`: it still refuses, but the agent tells the user the
418
+ request timed out instead of claiming they said no. Unknown run → **404**; a
419
+ prompt that is no longer pending → **409**; any other decision → **422**. All loud,
420
+ never a silent drop. Send `confirm_id`: without it a late answer resolves
421
+ whichever prompt replaced the one it was typed against.
422
+
423
+ To stop being asked for a whole session:
424
+
425
+ ```
426
+ POST /v1/gaia/sessions/{session_id}/bypass
427
+ { "enabled": true }
428
+ ```
429
+
430
+ It applies to the very next gated tool, including one in a turn already running,
431
+ and an unknown session is a **404** rather than a new one. A malformed
432
+ `session_id` is a **400**, same as on `/query`.
433
+
434
+ **That bypass stops the prompts; it does not open the shell.** The stdio
435
+ transport's `--bypass-permissions` does both — it runs gated tools unasked *and*
436
+ lifts the shell tool's guardrails (redirection and the other shell-only
437
+ operators, the read-only binary allowlist, the rate limit). That second half is
438
+ arbitrary code execution, appropriate for one local parent on a private pipe and
439
+ not for a bound socket, so it stays on stdio: HTTP sessions never lift the shell
440
+ gates, and the request body rejects unknown fields so a client cannot ask. See
441
+ SPEC §5.5.
442
+
443
+ **A run nobody can answer is still refused.** With `can_answer_questions: false`,
444
+ or with no `session_id`, the server emits `needs_confirmation`, follows it
445
+ immediately with a terminal `final` saying it stopped before running the action,
446
+ and cancels the run:
349
447
 
350
448
  ```
351
- data: {"type":"needs_confirmation","run_id":"…","action":"write_file","summary":"Run 'write_file'?"}
352
449
  data: {"type":"final","answer":"I stopped before running 'write_file' because it needs your explicit approval, and this streaming surface cannot collect that yet. …"}
353
450
  ```
354
451
 
355
- So: **`/query` cannot run any of those seven tools.** If your integration needs
356
- that, drive the agent from a surface that can prompt — its stdio transport is the
357
- one that can, because its control channel carries an approval back to a turn
358
- already in flight (SPEC §5.5) — or perform the mutation yourself from your own
359
- code and let the agent do the reading and reasoning. Treat `needs_confirmation`
360
- as an early warning that the run is about to end, not as a question you can
361
- answer.
452
+ That is deny-by-default, not an oversight: parking a batch run on a prompt
453
+ nobody will ever see reads as a hang. So a one-shot integration cannot run those
454
+ nine tools — pass a `session_id` and answer, or perform the mutation from your
455
+ own code and let the agent do the reading and reasoning.
362
456
 
363
457
  ## 9. File-access scope
364
458
 
@@ -378,10 +472,21 @@ come back as a structured error naming the file and the reason, so do not plan a
378
472
  integration around reading a credential file or editing a shell rc — perform
379
473
  those from your own code.
380
474
 
381
- **In 0.1.1 narrowing it is a construction-time setting only.** The packaged
382
- sidecar exposes no flag or env var for `allowed_paths` (its CLI accepts only
383
- `--host` and `--port`), so restricting the scope means embedding `GaiaAgent` in
384
- your own Python process:
475
+ The agent also gets its own scratch directory for throwaway scripts and
476
+ intermediate files, so they stay out of the user's project. It is created per
477
+ agent under the system temp dir, deleted when the agent closes, and is the only
478
+ part of the temp dir the agent may use.
479
+
480
+ **To narrow it for a run, set `GAIA_ALLOWED_PATHS`** in the environment the
481
+ sidecar starts in — absolute folders joined with `;` on Windows and `:` elsewhere.
482
+ The scope becomes the overlap with the default; an entry outside it is ignored
483
+ with a warning and never widens it, and out-of-scope paths are refused without a
484
+ prompt, even under full access. The scratch directory stays writable. An empty,
485
+ relative or missing entry stops the sidecar at startup. It confines the agent's
486
+ tools, not code they run: a `run_python` script or shell command can still read
487
+ anything the OS user can, so isolating a run from files it must not see needs a
488
+ separate OS user, container or VM. Embedding `GaiaAgent` lets you pass an explicit
489
+ list instead (the variable still narrows it):
385
490
 
386
491
  ```python
387
492
  from gaia_agent.agent import GaiaAgent, GaiaAgentConfig
@@ -401,7 +506,8 @@ matching bodies in full — the rest collapse to a one-line menu entry, and the
401
506
  model (or the user) re-activates one by calling `load_skill` on it again.
402
507
  `GAIA_DYNAMIC_SKILLS=0` disables the per-turn selection (every loaded body
403
508
  renders every turn); `GAIA_DYNAMIC_SKILLS_TAU=<float>` overrides the match
404
- threshold. Manifest `skills:` entries are always-on and never collapse. If the
509
+ threshold. Manifest `skills:` entries are always-on and never collapse, and a
510
+ turn with only always-on skills loaded embeds nothing. If the
405
511
  embedder is unavailable, selection disables itself for the session and every
406
512
  body renders — capability is never silently lost to a failed match.
407
513
 
@@ -411,7 +517,17 @@ turn — budget for it. It is not a task recipe but the agent's honesty floor: d
411
517
  not claim work you did not do, do not present empty output as a result, do not
412
518
  substitute a near-miss and report success. Those failures corrupt an answer
413
519
  whatever the task is, which is why it cannot live in an opt-in bundle. It
414
- declares no tools, and its body measures 676 tokens (tiktoken `cl100k`).
520
+ declares no tools, and its body measures 790 tokens (tiktoken `cl100k`).
521
+
522
+ **`document-extract` ships bundled but not enabled.** `gaia-voice` routes a
523
+ request for every item in a document to it, and it drives
524
+ `extract_document_items` and `save_extracted_items` so a long transcript yields
525
+ a complete, source-quoted inventory rather than a summary. A skill turns that
526
+ inventorying on only by listing `extract_document_items` in `tools_required`;
527
+ wording such as "find every call site" in another skill does not. Loading it
528
+ registers the tools at once, so `load_skill` never reports them missing; coverage
529
+ is enforced only when the request itself names a source document or asks for
530
+ every item, and a file the user named as a deliverable is never a source.
415
531
 
416
532
  **No skill *set* loads.** `gaia-agent.yaml` ships its `skill_sets:` and
417
533
  `default_skill_set:` blocks **commented out** — following the email agent's
@@ -419,10 +535,12 @@ precedent, because loading several skill bodies into every prompt costs tokens
419
535
  and no eval has measured that trade for this agent yet. Re-enabling is
420
536
  uncommenting two blocks; no code change.
421
537
 
422
- So today there is nothing for `GAIA_SKILL_SET` to select — leave it unset. Once
423
- a release declares sets, `GAIA_SKILL_SET` is the selection channel for the
424
- packaged sidecar (its CLI accepts only `--host` and `--port`), and an undeclared
425
- name raises naming the valid sets rather than falling back to a default. Beyond
538
+ So today there is nothing for `GAIA_SKILL_SET` to select — leave it unset; any
539
+ value stops the agent at startup (the sidecar exits non-zero before binding its
540
+ port). Once a release declares sets, `GAIA_SKILL_SET` is the selection channel
541
+ for the packaged sidecar (its CLI accepts only `--host` and `--port`), and an
542
+ undeclared name stops startup naming the valid sets rather than falling back to
543
+ a default. Beyond
426
544
  `gaia-voice`, do not design around a skill being on by default.
427
545
 
428
546
  ## 11. The project map — two things it costs you
@@ -437,12 +555,13 @@ Two consequences an integrator needs to plan for:
437
555
 
438
556
  - **Up to 600 prompt tokens, every turn.** That is the enforced ceiling
439
557
  (1.8% of the NPU profile's 32K window), not a typical value — budget it
440
- alongside `gaia-voice`'s 676.
441
- - **A background embedding pass on first contact with a new repository.** If
442
- the repo has no [code index](https://amd-gaia.ai/docs/guides/code-index), the
443
- map starts one in a background thread so semantic search is ready when it is
444
- needed. On a large monorepo that is minutes of local embedding.
445
- `GAIA_PROJECT_MAP_AUTO_INDEX=0` turns it off.
558
+ alongside `gaia-voice`'s 790.
559
+ - **An embedding pass on the first semantic code search.** If the repo has no
560
+ [code index](https://amd-gaia.ai/docs/guides/code-index), the first
561
+ `search_code_index` builds it, then searches. On a large monorepo that one
562
+ search takes minutes of local embedding; a task that never searches pays
563
+ nothing. `GAIA_PROJECT_MAP_AUTO_INDEX=1` builds it in the background at task
564
+ start instead.
446
565
 
447
566
  The sidecar's CLI accepts only `--host` and `--port`, so pointing the map at a
448
567
  specific project means `GAIA_PROJECT_ROOT=/path/to/repo` in its environment, or
@@ -450,6 +569,11 @@ specific project means `GAIA_PROJECT_ROOT=/path/to/repo` in its environment, or
450
569
  a VCS checkout nor holds a recognised manifest gets **no map** — that is the
451
570
  designed answer, not a failure.
452
571
 
572
+ The same root check also decides whether the shell rides along: when it
573
+ resolves to a repository, `run_shell_command` is offered on every turn instead
574
+ of only when semantic selection guesses the request sounds like a shell
575
+ request. No repository, no change.
576
+
453
577
  ## 12. Ports
454
578
 
455
579
  | Service | Port |
@@ -494,7 +618,7 @@ There is no silent null.
494
618
  reachable"** means Lemonade isn't running or isn't reachable — not a bug in
495
619
  this package. Start it, or set `LEMONADE_BASE_URL`.
496
620
  - **`needs_confirmation` is followed by a refusal and the run ends.** See §8.
497
- The seven gated tools are unreachable **over `/query`** — the agent itself can
621
+ The nine gated tools are unreachable **over `/query`** — the agent itself can
498
622
  run them on a transport that can prompt (SPEC §5.5).
499
623
  - **A placeholder hash in `binaries.lock.json` blocks the fetch before any
500
624
  network call.** Between releases that is the *expected* state — it is not a
@@ -516,9 +640,9 @@ There is no silent null.
516
640
  temp dir) is dropped, and a **shared** bin directory is moved to the end
517
641
  instead of removed, so the `python3` / `lemonade-server` / real `gaia` beside
518
642
  it stay reachable. If the Python CLI isn't installed anywhere, the daemon never
519
- comes up. It must also be **0.23.1+**: an older core's daemon starts fine but
520
- has no sidecar entry for this agent, which reads as a UI with a dead agent
521
- rather than as a version problem.
643
+ comes up. It must also be **0.25.0+**: an older core's daemon starts fine but
644
+ cannot run this agent, which reads as a UI with a dead agent rather than as
645
+ a version problem.
522
646
  - **The TUI is installed as `gaia-tui`, never `gaia`** — the terminal-hub artifact
523
647
  *is* called `gaia-<platform>`, and a file named `gaia` in a cache directory would
524
648
  shadow the npm bin shim. The lock's `filename` and `executable` differ for that
@@ -547,7 +671,7 @@ Then, in another terminal:
547
671
 
548
672
  ```bash
549
673
  curl -s http://127.0.0.1:8141/health # {"status":"ok","service":"gaia-agent-gaia"}
550
- curl -s http://127.0.0.1:8141/version # {"apiVersion":"2.12","agentVersion":"0.1.1"}
674
+ curl -s http://127.0.0.1:8141/version # {"apiVersion":"2.17","agentVersion":"0.1.1"}
551
675
  curl -s http://127.0.0.1:8141/v1/gaia/init # 200 + "ready":true, or 503 + a "hint"
552
676
  curl -N -X POST http://127.0.0.1:8141/v1/gaia/query \
553
677
  -H 'content-type: application/json' \
@@ -574,14 +698,17 @@ finish (or close an idle session) and retry the same `/query`.
574
698
  - **409 — the `run_id` is still in flight.** You mint it, so mint a fresh UUID
575
699
  per request; reusing one would leave the earlier run with no way to be
576
700
  cancelled.
577
- - **409 — `model` differs from what this `session_id` was built with.** Only
578
- construction reads a model, so it cannot be applied to the retained agent.
579
- Omit `model` to stay on the session's current one, or start a new
580
- `session_id` to switch.
701
+ - **409 — the model switch itself failed** (a missing Claude credential, an
702
+ unknown local model). The session stays on its previous model, so this is a
703
+ failed request rather than a broken session; `detail` names the reason.
581
704
  - **400 — the `Host` header is absent or empty.** The loopback check fails
582
705
  closed, so omitting the header is refused rather than served. Send
583
706
  `Host: 127.0.0.1:<port>`; every real HTTP client already does.
584
707
 
708
+ **After cancelling a turn, send the next `/query` on that session straight
709
+ away.** It is held until the stopped turn's model call returns (up to 240 s),
710
+ then runs. A session whose running turn was NOT cancelled still answers `409`.
711
+
585
712
  For the full wire contract, lock schema, exit codes, and timeout table, see
586
713
  [`SPEC.md`](./SPEC.md). For the user-facing overview, see [`README.md`](./README.md)
587
714
  and <https://amd-gaia.ai/docs/guides/gaia>.
@@ -591,7 +718,26 @@ and <https://amd-gaia.ai/docs/guides/gaia>.
591
718
  The stdio TUI supports `/provider` for Local, Fireworks AI, and AMD LLM Gateway.
592
719
  Keys are entered in a masked field and sent directly to local Lemonade's runtime
593
720
  auth API, never as agent queries. `/model` lists supported discovered cloud and
594
- downloaded local models; `/model fireworks.gemma-4-31b-it` selects Gemma 4 31B IT
721
+ downloaded local models, marking the configured default with `(default)`;
722
+ `/model fireworks.gemma-4-31b-it` selects Gemma 4 31B IT
595
723
  when available. Cloud chat sends conversation history to the selected provider;
596
724
  embeddings remain on Lemonade. The status event names the actual provider and
597
725
  marks remote inference. This is a TUI/stdio capability, not an HTTP query command.
726
+
727
+ ## Developer engineering mode
728
+
729
+ Available only when GAIA was started with `--developer-mode` (or
730
+ `GAIA_DEVELOPER_MODE=1`). If the engineering tools are missing, say that developer
731
+ mode is off; don't route around it with shell or file tools. When describing a
732
+ handoff, claim only what happened:
733
+
734
+ - Context was shared only after the user approved that exact snapshot in the prompt.
735
+ Approval can't be remembered, so each share or append asks again.
736
+ - Opening the coding app never creates or submits a task, and for Codex it doesn't
737
+ prefill a prompt either. Never say the context was "posted" or "sent" to Codex;
738
+ give the user the directory and the prompt to paste.
739
+ - Preview and test results come from the coding app. Report them as app-reported,
740
+ not as checks GAIA ran.
741
+ - Revoking stops future reads; it cannot recall data already delivered.
742
+
743
+ See the [usage guide](https://amd-gaia.ai/docs/guides/harness-engineering).