@amd-gaia/gaia 0.2.0 → 0.3.2
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/CHANGELOG.md +1005 -72
- package/README.md +103 -13
- package/SCORECARD.md +180 -0
- package/SKILL.md +239 -71
- package/SPEC.md +369 -23
- package/binaries.lock.json +25 -25
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +45 -12
- package/dist/cli.js.map +1 -1
- package/dist/lifecycle.d.ts +2 -2
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +9 -3
- package/dist/lifecycle.js.map +1 -1
- package/package.json +2 -1
package/SKILL.md
CHANGED
|
@@ -65,7 +65,8 @@ A second sidecar started here would only fight the daemon's own for port 8141. U
|
|
|
65
65
|
|
|
66
66
|
Other commands: `gaia fetch` (download + verify, print JSON, exit),
|
|
67
67
|
`gaia serve` (sidecar alone), `gaia version` (per-component version, source URL,
|
|
68
|
-
and platform matrix).
|
|
68
|
+
and platform matrix). Unknown or misplaced launcher options are rejected;
|
|
69
|
+
anything intended for the TUI goes after a bare `--` and is passed through verbatim.
|
|
69
70
|
|
|
70
71
|
### Where each binary comes from
|
|
71
72
|
|
|
@@ -117,6 +118,10 @@ Windows sidecar build**.
|
|
|
117
118
|
| `linux-arm64` | **no** | yes |
|
|
118
119
|
| `win32-arm64` | **no** | yes |
|
|
119
120
|
|
|
121
|
+
The `linux-x64` sidecar needs **glibc 2.34 or newer** (Ubuntu 22.04, Debian 12,
|
|
122
|
+
RHEL 9 or later); an older distro fails at launch, so check `ldd --version`
|
|
123
|
+
before blaming the package.
|
|
124
|
+
|
|
120
125
|
Resolving the sidecar on those two keys raises `PlatformError` naming the
|
|
121
126
|
platform and the supported set. It is not silently skipped, and the TUI is never
|
|
122
127
|
launched with no agent behind it. `npx @amd-gaia/gaia version` prints the matrix
|
|
@@ -127,9 +132,22 @@ for the version you have.
|
|
|
127
132
|
The agent thinks with a model hosted by **Lemonade Server**, which this package
|
|
128
133
|
does not install. Required before any query succeeds:
|
|
129
134
|
|
|
130
|
-
1. Lemonade **10.2.0 or newer**, running
|
|
131
|
-
|
|
132
|
-
|
|
135
|
+
1. Lemonade **10.2.0 or newer**, running. GAIA's daemon starts and supervises
|
|
136
|
+
one, so `gaia daemon start` is normally all that is needed (`gaia init`
|
|
137
|
+
also installs and starts it on first run). Qwen3.8 Flash Next needs
|
|
138
|
+
**v2026.39.1 or newer**, the version `gaia init` installs; older servers
|
|
139
|
+
cannot load it.
|
|
140
|
+
2. The machine's default chat model downloaded — run `gaia init`. It picks
|
|
141
|
+
`Qwen3.6-35B-A3B-GGUF` where a GPU holds it (~27 GB for models,
|
|
142
|
+
weights plus context cache — a 64 GB+ Strix Halo or a 32 GB GPU) and
|
|
143
|
+
`Gemma-4-E4B-it-GGUF` everywhere else, CPU-only PCs included, and records the pick as
|
|
144
|
+
`default_model` in `~/.gaia/config.json`. On 128 GB-class PCs the user may
|
|
145
|
+
switch to the multimodal `user.Qwen3.8-Flash-Next-GGUF` with `gaia config set
|
|
146
|
+
default_model`. `model.id` below names whichever this machine uses.
|
|
147
|
+
`gaia download` takes **no** model argument — naming one makes it exit 2. To
|
|
148
|
+
pull a single model instead, repeat the command `GET /v1/gaia/init` gives
|
|
149
|
+
you: it names the Lemonade client this machine actually has. Do not invent
|
|
150
|
+
one.
|
|
133
151
|
|
|
134
152
|
Do not guess — ask the sidecar. `GET /v1/gaia/init` is a read-only preflight
|
|
135
153
|
(it never pulls or loads) that probes Lemonade, compares its version to the
|
|
@@ -150,7 +168,9 @@ alone:
|
|
|
150
168
|
"min_version": "10.2.0", "compatible": null },
|
|
151
169
|
"model": { "id": "Gemma-4-E4B-it-GGUF", "present": false,
|
|
152
170
|
"loadable": null, "ctx_size": null },
|
|
153
|
-
|
|
171
|
+
// The manual fallback ("Otherwise: …") is resolved for the host's Lemonade
|
|
172
|
+
// install (tray app, macOS app, systemd service, or CLI) — render it verbatim.
|
|
173
|
+
"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
174
|
}
|
|
155
175
|
```
|
|
156
176
|
|
|
@@ -216,6 +236,12 @@ await shutdown(proc); // tree-kill; auto-cleanup also reaps on exit
|
|
|
216
236
|
file path); read it from there, don't invent one. A 401 whose `detail` names
|
|
217
237
|
both env vars means you sent the wrong token or none.
|
|
218
238
|
|
|
239
|
+
The programs the agent starts (shell commands, MCP servers, CLI installs and
|
|
240
|
+
sign-ins, native hub agents, media tools) never inherit this token
|
|
241
|
+
or GAIA's other internal credentials. To withhold your own variables from
|
|
242
|
+
them too, list the names in `GAIA_CHILD_ENV_DENY` (comma or space separated);
|
|
243
|
+
`GAIA_NO_DOTENV=1` stops the sidecar loading `.env` files.
|
|
244
|
+
|
|
219
245
|
Or skip the code entirely and let the CLI own it:
|
|
220
246
|
|
|
221
247
|
```bash
|
|
@@ -226,7 +252,7 @@ curl http://127.0.0.1:8141/health
|
|
|
226
252
|
## 7. Call `POST /v1/gaia/query`
|
|
227
253
|
|
|
228
254
|
This is the whole agent surface. There is **no typed query client** in this
|
|
229
|
-
package — call it with plain `fetch`. Contract version **2.
|
|
255
|
+
package — call it with plain `fetch`. Contract version **2.24**; the stream is
|
|
230
256
|
`text/event-stream` terminated by **exactly one** `final` or `error`.
|
|
231
257
|
|
|
232
258
|
Request body (`extra: "forbid"` — an unknown field is a **422**, not ignored):
|
|
@@ -235,11 +261,19 @@ Request body (`extra: "forbid"` — an unknown field is a **422**, not ignored):
|
|
|
235
261
|
|---|---|---|
|
|
236
262
|
| `query` | yes | Non-empty. |
|
|
237
263
|
| `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
|
-
| `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. |
|
|
264
|
+
| `context` | yes | Transcript slice, pushed in the body — may be `[]`, never absent. Each item `{ role, content }`; `role` ∈ `user` / `assistant` / `system` / `tool`. If it doesn't fit beside the agent's prompt and tools in the model's window, the agent keeps the newest whole turns that do and says so in a `status` event; one turn bigger than that is kept with its longest messages cut. |
|
|
265
|
+
| `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**. |
|
|
266
|
+
| `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. |
|
|
267
|
+
| `allowed_paths` | no | Contract ≥ 2.18. Absolute folder or file paths the conversation may read — the scope your UI shows. A new session gets exactly this scope; a retained session adds any entry it lacks and never drops one until the session ends. `GAIA_ALLOWED_PATHS` still narrows it. A relative path is a **422**. Omit it for the agent's default scope. |
|
|
268
|
+
| `working_directory` | no | Contract ≥ 2.24. Absolute chat workspace used to resolve relative file paths for reads, writes, edits and approval prompts. It is separate from `allowed_paths`; a relative path is a **422**. Send the same folder the UI shows. |
|
|
269
|
+
| `memory` | no | Contract ≥ 2.18. `"on"`, `"private"` or `"off"`. Under `"private"` or `"off"` the turn saves nothing to memory, including errors and lessons the agent saves on its own. A retained session keeps the last value: send it every turn. |
|
|
270
|
+
| `internet_off` | no | Contract ≥ 2.23. `true` refuses web search and page fetches, the browser, connector, mailbox and Agent Hub tools and any call or shell command that contacts another host, with a message telling the model the internet is off for this chat; `false` allows them. A retained session keeps the last value: send it every turn. Omit it to leave the agent as it is. To change it mid-turn, `POST /v1/gaia/sessions/{session_id}/internet` with `{"internet_off": true}`; the next tool call follows it, and an unknown session is a **404**. Post it only once `/query` has answered, since the turn's own value replaces an earlier one; re-read the switch then and post it if it changed. |
|
|
271
|
+
| `changes_turn_id` | no | Contract ≥ 2.19. Needs `session_id`. The agent copies each file before changing it, under `<GAIA home>/snapshots/<session_id>/<changes_turn_id>/`, so the turn can be reviewed and undone; the copies are complete before the terminal event. Format: 14 digits of local time, `-`, 8 hex digits (`YYYYmmddHHMMSS-xxxxxxxx`); anything else is a **422**. Omit it to copy nothing. |
|
|
272
|
+
| `surface` | no | Contract ≥ 2.20. `"agent_ui"`, `"tui"` or `"cli"` — the front-end the turn comes from. With `"agent_ui"` the agent points recurring requests at the Agent UI's **Scheduled tasks** page. It also picks where a model-provider error tells the user to fix it; omitted (a headless caller), that is the model server the GAIA host uses (`LEMONADE_BASE_URL`). Read when a session is built. |
|
|
273
|
+
| `reasoning` | no | Contract ≥ 2.22. `true` streams the model's reasoning as `reasoning` events (`{ delta }`), apart from `status` lines and the answer's `token`s. Join the deltas as they come: each keeps its own spacing. Omit it and reasoning folds into `status` lines as before. |
|
|
240
274
|
| `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
|
|
242
|
-
| `provider` | no |
|
|
275
|
+
| `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. |
|
|
276
|
+
| `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
277
|
| `max_steps` | no | ≥ 1. |
|
|
244
278
|
|
|
245
279
|
```ts
|
|
@@ -295,13 +329,14 @@ The canonical event shapes, as emitted:
|
|
|
295
329
|
|
|
296
330
|
| Event | Shape |
|
|
297
331
|
|---|---|
|
|
298
|
-
| `status` | `{ type, message }` — progress and reasoning narration |
|
|
332
|
+
| `status` | `{ type, message, phase?, words?, chars?, queue? }` — progress and reasoning narration. `phase` (`loading_model`, `downloading_model`, `reading`, `reasoning`, `tool_call`, `model_queue`) names what the model is doing; show it as the live line, not as narration. `model_queue` means the turn is waiting for the model behind another request: it repeats every ~2 s with a `queue` object (`event`, `position`, `queue_length`, `holder`, `holder_model`, `waited_s`), so its steady arrival means queued, not stuck |
|
|
299
333
|
| `token` | `{ type, delta }` — answer text to append |
|
|
300
|
-
| `
|
|
301
|
-
| `
|
|
302
|
-
| `
|
|
334
|
+
| `reasoning` | `{ type, delta }` — the model's reasoning, only when the request set `reasoning: true` (contract ≥ 2.22); join the deltas as they come and keep them apart from the answer |
|
|
335
|
+
| `tool_call` | `{ type, tool, args, mcp_server?, mcp_server_label?, mcp_tool? }` — an MCP tool names its connector (`mcp_server_label`, e.g. `"Git"`) and the server's own tool name (`mcp_tool`, e.g. `"git_log"`); label the call with those, not the prefixed `tool` |
|
|
336
|
+
| `tool_result` | `{ type, tool, data, render? }` — an MCP tool that returned text carries it in `data.mcp_output` (`{ text, truncated }`); a `search_web` result lists its hits in `data.sources` (`[{ title, url }]`) |
|
|
337
|
+
| `needs_confirmation` | `{ type, run_id, action, summary, confirm_id?, always_scope?, risk?, tool_label?, args?, args_truncated?, prompt_context? }` — answer it, or it ends the run; see §8. `summary` shortens long code; `args` is the call itself, exactly the arguments the model sent, each value capped at 64,000 characters (`args_truncated: true` when one was cut — don't auto-approve a call you can't fully see). What GAIA adds to the prompt is in `prompt_context`, never `args`: `working_directory` (where a shell call runs, when the model's arguments don't say), `risk_note` (why GAIA's checks don't cover the call) and `file_path` (the absolute path a write lands at, when the model gave a relative one) |
|
|
303
338
|
| `needs_input` | `{ type, run_id, request_id, question, options[], allow_free_text, sensitive, respond_url, timeout_seconds? }` |
|
|
304
|
-
| `final` | `{ type, answer, usage? }` — terminal |
|
|
339
|
+
| `final` | `{ type, answer, usage?, failed?, context?, context_error?, performance? }` — terminal. `failed: true`: the model failed mid-turn and `answer` says what was and wasn't done; show it as an error, not a result. `context` / `context_error` and `performance` (the turn's step timings) are optional; see SPEC §5.1 |
|
|
305
340
|
| `error` | `{ type, detail, status }` — terminal, surface `detail` verbatim |
|
|
306
341
|
|
|
307
342
|
Rules a client must respect:
|
|
@@ -313,52 +348,133 @@ Rules a client must respect:
|
|
|
313
348
|
guarantees one; a close without one means something broke on your side.
|
|
314
349
|
- **Answer `needs_input`, don't restart.** The run is parked on the *same*
|
|
315
350
|
stream. `POST /v1/gaia/query/{run_id}/respond` with
|
|
316
|
-
`{ request_id,
|
|
351
|
+
`{ request_id, value }` and keep reading the existing stream — a fresh
|
|
317
352
|
`/query` POST abandons the paused run. Unknown run → **404**; a `request_id` that
|
|
318
353
|
is no longer pending → **409** (both loud, never a silent drop). Render each
|
|
319
354
|
option's `description`, and mask the input when `sensitive` is set.
|
|
320
355
|
- **Cancel with `POST /v1/gaia/query/{run_id}/cancel`.** It returns
|
|
321
356
|
`{ run_id, cancelled }` — an unknown id reports `cancelled: false` with a
|
|
322
357
|
**200**, not a 404, because a cancel racing a normal completion is expected.
|
|
323
|
-
Dropping the HTTP connection also cancels the run.
|
|
324
|
-
|
|
325
|
-
|
|
358
|
+
Dropping the HTTP connection also cancels the run. A run stopped mid-answer
|
|
359
|
+
ends with an empty `final`, and no `token` follows the cancel, so keep the
|
|
360
|
+
text the `token` events already delivered. Text not yet sent when the
|
|
361
|
+
cancel lands is dropped, not delivered late.
|
|
362
|
+
- **Add to a running turn with `POST /v1/gaia/query/{run_id}/followup`**
|
|
363
|
+
(contract ≥ 2.15, body `{ text }`). The run is not interrupted and no second
|
|
364
|
+
turn starts: the agent folds the text in at its next step boundary and
|
|
365
|
+
answers it alongside what it was already doing. Unknown run → **404**, an
|
|
366
|
+
agent that cannot take one → **409**; both are loud, so hold the message
|
|
367
|
+
rather than telling the user it was sent. `/query` is stateless, so put a
|
|
368
|
+
delivered follow-up into the next turn's `context` yourself, between that
|
|
369
|
+
turn's question and its answer.
|
|
370
|
+
|
|
371
|
+
## 8. Over `/v1/gaia/query`, a gated tool asks — when you can answer
|
|
326
372
|
|
|
327
373
|
Read this before you design a workflow around it. This section is about the HTTP
|
|
328
374
|
surface — the agent's other transport can collect an approval; see SPEC §5.5.
|
|
329
375
|
|
|
330
|
-
|
|
331
|
-
before they run.
|
|
376
|
+
Twelve of the agent's 95 tools mutate the machine and need explicit approval
|
|
377
|
+
before they run. Nine sit in the base `TOOLS_REQUIRING_CONFIRMATION` set —
|
|
332
378
|
**`write_file`**, **`edit_file`**, **`run_shell_command`**,
|
|
333
|
-
**`
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
`
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
379
|
+
**`wait_for_condition`**, which re-runs a shell command until it succeeds,
|
|
380
|
+
**`execute_python_file`**, **`run_python`**, **`notify_desktop`**, which spawns
|
|
381
|
+
a PowerShell child on Windows to draw the notification, and **`install_cli`** /
|
|
382
|
+
**`sign_in_cli`**, which install software and sign a CLI in to the user's
|
|
383
|
+
account — and the flagship adds three of its own
|
|
384
|
+
(`CONFIRMATION_REQUIRED_TOOLS`): **`install_skill`**, **`capture_skill`**, and
|
|
385
|
+
**`remove_skill`**, because installing or capturing a skill writes third-party
|
|
386
|
+
content under `~/.gaia/skills` and removing one deletes it. A capture that does
|
|
387
|
+
land is additionally **code-inert**: its instructions load, but any
|
|
388
|
+
`tools.py`/scripts stay unregistered until a human runs
|
|
389
|
+
`gaia skill promote <name>` in a terminal. Everything else — reading,
|
|
390
|
+
indexing, querying, web fetching, memory, and the read-only `check_cli_setup`
|
|
391
|
+
— runs without asking. A write or edit inside the agent's own scratch
|
|
392
|
+
folder (`gaia-scratch-…` under the system temp folder) runs without asking too,
|
|
393
|
+
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.
|
|
394
|
+
|
|
395
|
+
The eight live-browser tools are gated by a **hook**, not a set, so a name
|
|
396
|
+
alone does not tell you whether a call will ask. `browser_login` always asks.
|
|
397
|
+
`browser_click` / `browser_type` ask when the call acts inside a session the
|
|
398
|
+
run signed in to, or when the target element's label reads irreversible, and
|
|
399
|
+
run unasked on the open web. The hook **fails closed**: if the browser cannot
|
|
400
|
+
be asked what state it is in, the call is treated as authenticated and prompts.
|
|
401
|
+
Design a workflow around the approval path, not around a fixed tool list.
|
|
402
|
+
|
|
403
|
+
**Contract ≥ 2.14 can answer one.** Send a `session_id` and leave
|
|
404
|
+
`can_answer_questions` unset (or `true`). The stream emits `needs_confirmation`
|
|
405
|
+
carrying a `confirm_id` and then **stays open** while the agent waits:
|
|
406
|
+
|
|
407
|
+
```
|
|
408
|
+
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"}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Answer it on the same run, then keep reading the stream:
|
|
412
|
+
|
|
413
|
+
```
|
|
414
|
+
POST /v1/gaia/query/{run_id}/tool_decision
|
|
415
|
+
{ "decision": "allow" | "deny" | "always" | "timeout", "confirm_id": "…" }
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
`summary` is the command or call itself, shown once — your UI supplies the
|
|
419
|
+
question. For a shell command it can carry two more lines: `in <folder>` when
|
|
420
|
+
the command runs outside the workspace, and one plain sentence on what a `write`
|
|
421
|
+
or `execute` command can do. Show it as-is. A CLI a skill would grant (`git
|
|
422
|
+
commit`, `python script.py`) with no such skill loaded arrives here as a prompt,
|
|
423
|
+
not a refusal. So does a command GAIA's own checks don't cover (`git push`,
|
|
424
|
+
`python -c`, a path outside the project): its `summary` is the exact line the
|
|
425
|
+
shell will run, unshortened, then `in <folder>`, then a risk sentence, and it
|
|
426
|
+
carries no `always_scope` — offer allow-once and deny only. `allow` runs that
|
|
427
|
+
line once; the next call asks again. `risk` says what the call does (`read`, `write`, `execute`,
|
|
428
|
+
`destructive`); label by it rather than by tool name, since `run_shell_command`
|
|
429
|
+
is `pytest` on one call and `rm -rf` on the next. `always` grants the family
|
|
430
|
+
named in `always_scope` for the rest of the session (`pytest` covers every
|
|
431
|
+
spelling of a test run) — say so in your UI, because it stops asking; no
|
|
432
|
+
`always_scope` means don't offer it. If your prompt expires unanswered, send
|
|
433
|
+
`timeout`, not `deny`: it still refuses, but the agent tells the user the
|
|
434
|
+
request timed out instead of claiming they said no. Unknown run → **404**; a
|
|
435
|
+
prompt that is no longer pending → **409**; any other decision → **422**. All loud,
|
|
436
|
+
never a silent drop. Send `confirm_id`: without it a late answer resolves
|
|
437
|
+
whichever prompt replaced the one it was typed against.
|
|
438
|
+
|
|
439
|
+
To stop being asked for a whole session:
|
|
440
|
+
|
|
441
|
+
```
|
|
442
|
+
POST /v1/gaia/sessions/{session_id}/bypass
|
|
443
|
+
{ "enabled": true }
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
It applies to the very next gated tool, including one in a turn already running,
|
|
447
|
+
and an unknown session is a **404** rather than a new one. A malformed
|
|
448
|
+
`session_id` is a **400**, same as on `/query`.
|
|
449
|
+
|
|
450
|
+
**That bypass stops the prompts; it does not open the shell.** The stdio
|
|
451
|
+
transport's `--bypass-permissions` does both — it runs gated tools unasked *and*
|
|
452
|
+
lifts the shell tool's guardrails (redirection and the other shell-only
|
|
453
|
+
operators, the read-only binary allowlist, the rate limit). That second half is
|
|
454
|
+
arbitrary code execution, appropriate for one local parent on a private pipe and
|
|
455
|
+
not for a bound socket, so it stays on stdio: HTTP sessions never lift the shell
|
|
456
|
+
gates, and the request body rejects unknown fields so a client cannot ask. See
|
|
457
|
+
SPEC §5.5.
|
|
458
|
+
|
|
459
|
+
So a shell command past those gates (a delete, `git add`) is never run unasked
|
|
460
|
+
under bypass. On a run someone can answer (`can_answer_questions` not `false`,
|
|
461
|
+
with a `session_id`) it emits `needs_confirmation` and waits for
|
|
462
|
+
`/tool_decision`, exactly as with bypass off; commands that need no approval
|
|
463
|
+
still run unasked. A run nobody can answer refuses it, as below.
|
|
464
|
+
|
|
465
|
+
**A run nobody can answer is still refused.** With `can_answer_questions: false`,
|
|
466
|
+
or with no `session_id`, the server emits `needs_confirmation`, follows it
|
|
467
|
+
immediately with a terminal `final` saying it stopped before running the action,
|
|
468
|
+
and cancels the run:
|
|
349
469
|
|
|
350
470
|
```
|
|
351
|
-
data: {"type":"needs_confirmation","run_id":"…","action":"write_file","summary":"Run 'write_file'?"}
|
|
352
471
|
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
472
|
```
|
|
354
473
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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.
|
|
474
|
+
That is deny-by-default, not an oversight: parking a batch run on a prompt
|
|
475
|
+
nobody will ever see reads as a hang. So a one-shot integration cannot run those
|
|
476
|
+
nine tools — pass a `session_id` and answer, or perform the mutation from your
|
|
477
|
+
own code and let the agent do the reading and reasoning.
|
|
362
478
|
|
|
363
479
|
## 9. File-access scope
|
|
364
480
|
|
|
@@ -378,10 +494,21 @@ come back as a structured error naming the file and the reason, so do not plan a
|
|
|
378
494
|
integration around reading a credential file or editing a shell rc — perform
|
|
379
495
|
those from your own code.
|
|
380
496
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
497
|
+
The agent also gets its own scratch directory for throwaway scripts and
|
|
498
|
+
intermediate files, so they stay out of the user's project. It is created per
|
|
499
|
+
agent under the system temp dir, deleted when the agent closes, and is the only
|
|
500
|
+
part of the temp dir the agent may use.
|
|
501
|
+
|
|
502
|
+
**To narrow it for a run, set `GAIA_ALLOWED_PATHS`** in the environment the
|
|
503
|
+
sidecar starts in — absolute folders joined with `;` on Windows and `:` elsewhere.
|
|
504
|
+
The scope becomes the overlap with the default; an entry outside it is ignored
|
|
505
|
+
with a warning and never widens it, and out-of-scope paths are refused without a
|
|
506
|
+
prompt, even under full access. The scratch directory stays writable. An empty,
|
|
507
|
+
relative or missing entry stops the sidecar at startup. It confines the agent's
|
|
508
|
+
tools, not code they run: a `run_python` script or shell command can still read
|
|
509
|
+
anything the OS user can, so isolating a run from files it must not see needs a
|
|
510
|
+
separate OS user, container or VM. Embedding `GaiaAgent` lets you pass an explicit
|
|
511
|
+
list instead (the variable still narrows it):
|
|
385
512
|
|
|
386
513
|
```python
|
|
387
514
|
from gaia_agent.agent import GaiaAgent, GaiaAgentConfig
|
|
@@ -401,7 +528,8 @@ matching bodies in full — the rest collapse to a one-line menu entry, and the
|
|
|
401
528
|
model (or the user) re-activates one by calling `load_skill` on it again.
|
|
402
529
|
`GAIA_DYNAMIC_SKILLS=0` disables the per-turn selection (every loaded body
|
|
403
530
|
renders every turn); `GAIA_DYNAMIC_SKILLS_TAU=<float>` overrides the match
|
|
404
|
-
threshold. Manifest `skills:` entries are always-on and never collapse
|
|
531
|
+
threshold. Manifest `skills:` entries are always-on and never collapse, and a
|
|
532
|
+
turn with only always-on skills loaded embeds nothing. If the
|
|
405
533
|
embedder is unavailable, selection disables itself for the session and every
|
|
406
534
|
body renders — capability is never silently lost to a failed match.
|
|
407
535
|
|
|
@@ -411,7 +539,17 @@ turn — budget for it. It is not a task recipe but the agent's honesty floor: d
|
|
|
411
539
|
not claim work you did not do, do not present empty output as a result, do not
|
|
412
540
|
substitute a near-miss and report success. Those failures corrupt an answer
|
|
413
541
|
whatever the task is, which is why it cannot live in an opt-in bundle. It
|
|
414
|
-
declares no tools, and its body measures
|
|
542
|
+
declares no tools, and its body measures 790 tokens (tiktoken `cl100k`).
|
|
543
|
+
|
|
544
|
+
**`document-extract` ships bundled but not enabled.** `gaia-voice` routes a
|
|
545
|
+
request for every item in a document to it, and it drives
|
|
546
|
+
`extract_document_items` and `save_extracted_items` so a long transcript yields
|
|
547
|
+
a complete, source-quoted inventory rather than a summary. A skill turns that
|
|
548
|
+
inventorying on only by listing `extract_document_items` in `tools_required`;
|
|
549
|
+
wording such as "find every call site" in another skill does not. Loading it
|
|
550
|
+
registers the tools at once, so `load_skill` never reports them missing; coverage
|
|
551
|
+
is enforced only when the request itself names a source document or asks for
|
|
552
|
+
every item, and a file the user named as a deliverable is never a source.
|
|
415
553
|
|
|
416
554
|
**No skill *set* loads.** `gaia-agent.yaml` ships its `skill_sets:` and
|
|
417
555
|
`default_skill_set:` blocks **commented out** — following the email agent's
|
|
@@ -419,10 +557,12 @@ precedent, because loading several skill bodies into every prompt costs tokens
|
|
|
419
557
|
and no eval has measured that trade for this agent yet. Re-enabling is
|
|
420
558
|
uncommenting two blocks; no code change.
|
|
421
559
|
|
|
422
|
-
So today there is nothing for `GAIA_SKILL_SET` to select — leave it unset
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
560
|
+
So today there is nothing for `GAIA_SKILL_SET` to select — leave it unset; any
|
|
561
|
+
value stops the agent at startup (the sidecar exits non-zero before binding its
|
|
562
|
+
port). Once a release declares sets, `GAIA_SKILL_SET` is the selection channel
|
|
563
|
+
for the packaged sidecar (its CLI accepts only `--host` and `--port`), and an
|
|
564
|
+
undeclared name stops startup naming the valid sets rather than falling back to
|
|
565
|
+
a default. Beyond
|
|
426
566
|
`gaia-voice`, do not design around a skill being on by default.
|
|
427
567
|
|
|
428
568
|
## 11. The project map — two things it costs you
|
|
@@ -437,12 +577,13 @@ Two consequences an integrator needs to plan for:
|
|
|
437
577
|
|
|
438
578
|
- **Up to 600 prompt tokens, every turn.** That is the enforced ceiling
|
|
439
579
|
(1.8% of the NPU profile's 32K window), not a typical value — budget it
|
|
440
|
-
alongside `gaia-voice`'s
|
|
441
|
-
- **
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
`GAIA_PROJECT_MAP_AUTO_INDEX=
|
|
580
|
+
alongside `gaia-voice`'s 790.
|
|
581
|
+
- **An embedding pass on the first semantic code search.** If the repo has no
|
|
582
|
+
[code index](https://amd-gaia.ai/docs/guides/code-index), the first
|
|
583
|
+
`search_code_index` builds it, then searches. On a large monorepo that one
|
|
584
|
+
search takes minutes of local embedding; a task that never searches pays
|
|
585
|
+
nothing. `GAIA_PROJECT_MAP_AUTO_INDEX=1` builds it in the background at task
|
|
586
|
+
start instead.
|
|
446
587
|
|
|
447
588
|
The sidecar's CLI accepts only `--host` and `--port`, so pointing the map at a
|
|
448
589
|
specific project means `GAIA_PROJECT_ROOT=/path/to/repo` in its environment, or
|
|
@@ -450,6 +591,11 @@ specific project means `GAIA_PROJECT_ROOT=/path/to/repo` in its environment, or
|
|
|
450
591
|
a VCS checkout nor holds a recognised manifest gets **no map** — that is the
|
|
451
592
|
designed answer, not a failure.
|
|
452
593
|
|
|
594
|
+
The same root check also decides whether the shell rides along: when it
|
|
595
|
+
resolves to a repository, `run_shell_command` is offered on every turn instead
|
|
596
|
+
of only when semantic selection guesses the request sounds like a shell
|
|
597
|
+
request. No repository, no change.
|
|
598
|
+
|
|
453
599
|
## 12. Ports
|
|
454
600
|
|
|
455
601
|
| Service | Port |
|
|
@@ -494,7 +640,7 @@ There is no silent null.
|
|
|
494
640
|
reachable"** means Lemonade isn't running or isn't reachable — not a bug in
|
|
495
641
|
this package. Start it, or set `LEMONADE_BASE_URL`.
|
|
496
642
|
- **`needs_confirmation` is followed by a refusal and the run ends.** See §8.
|
|
497
|
-
The
|
|
643
|
+
The nine gated tools are unreachable **over `/query`** — the agent itself can
|
|
498
644
|
run them on a transport that can prompt (SPEC §5.5).
|
|
499
645
|
- **A placeholder hash in `binaries.lock.json` blocks the fetch before any
|
|
500
646
|
network call.** Between releases that is the *expected* state — it is not a
|
|
@@ -516,9 +662,9 @@ There is no silent null.
|
|
|
516
662
|
temp dir) is dropped, and a **shared** bin directory is moved to the end
|
|
517
663
|
instead of removed, so the `python3` / `lemonade-server` / real `gaia` beside
|
|
518
664
|
it stay reachable. If the Python CLI isn't installed anywhere, the daemon never
|
|
519
|
-
comes up. It must also be **0.
|
|
520
|
-
|
|
521
|
-
|
|
665
|
+
comes up. It must also be **0.25.2+**: an older core's daemon starts fine but
|
|
666
|
+
cannot run this agent, which reads as a UI with a dead agent rather than as
|
|
667
|
+
a version problem.
|
|
522
668
|
- **The TUI is installed as `gaia-tui`, never `gaia`** — the terminal-hub artifact
|
|
523
669
|
*is* called `gaia-<platform>`, and a file named `gaia` in a cache directory would
|
|
524
670
|
shadow the npm bin shim. The lock's `filename` and `executable` differ for that
|
|
@@ -547,7 +693,7 @@ Then, in another terminal:
|
|
|
547
693
|
|
|
548
694
|
```bash
|
|
549
695
|
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.
|
|
696
|
+
curl -s http://127.0.0.1:8141/version # {"apiVersion":"2.24","agentVersion":"0.3.1"}
|
|
551
697
|
curl -s http://127.0.0.1:8141/v1/gaia/init # 200 + "ready":true, or 503 + a "hint"
|
|
552
698
|
curl -N -X POST http://127.0.0.1:8141/v1/gaia/query \
|
|
553
699
|
-H 'content-type: application/json' \
|
|
@@ -574,14 +720,17 @@ finish (or close an idle session) and retry the same `/query`.
|
|
|
574
720
|
- **409 — the `run_id` is still in flight.** You mint it, so mint a fresh UUID
|
|
575
721
|
per request; reusing one would leave the earlier run with no way to be
|
|
576
722
|
cancelled.
|
|
577
|
-
- **409 —
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
`session_id` to switch.
|
|
723
|
+
- **409 — the model switch itself failed** (a missing Claude credential, an
|
|
724
|
+
unknown local model). The session stays on its previous model, so this is a
|
|
725
|
+
failed request rather than a broken session; `detail` names the reason.
|
|
581
726
|
- **400 — the `Host` header is absent or empty.** The loopback check fails
|
|
582
727
|
closed, so omitting the header is refused rather than served. Send
|
|
583
728
|
`Host: 127.0.0.1:<port>`; every real HTTP client already does.
|
|
584
729
|
|
|
730
|
+
**After cancelling a turn, send the next `/query` on that session straight
|
|
731
|
+
away.** It is held until the stopped turn's model call returns (up to 240 s),
|
|
732
|
+
then runs. A session whose running turn was NOT cancelled still answers `409`.
|
|
733
|
+
|
|
585
734
|
For the full wire contract, lock schema, exit codes, and timeout table, see
|
|
586
735
|
[`SPEC.md`](./SPEC.md). For the user-facing overview, see [`README.md`](./README.md)
|
|
587
736
|
and <https://amd-gaia.ai/docs/guides/gaia>.
|
|
@@ -591,7 +740,26 @@ and <https://amd-gaia.ai/docs/guides/gaia>.
|
|
|
591
740
|
The stdio TUI supports `/provider` for Local, Fireworks AI, and AMD LLM Gateway.
|
|
592
741
|
Keys are entered in a masked field and sent directly to local Lemonade's runtime
|
|
593
742
|
auth API, never as agent queries. `/model` lists supported discovered cloud and
|
|
594
|
-
downloaded local models
|
|
743
|
+
downloaded local models, marking the configured default with `(default)`;
|
|
744
|
+
`/model fireworks.gemma-4-31b-it` selects Gemma 4 31B IT
|
|
595
745
|
when available. Cloud chat sends conversation history to the selected provider;
|
|
596
746
|
embeddings remain on Lemonade. The status event names the actual provider and
|
|
597
747
|
marks remote inference. This is a TUI/stdio capability, not an HTTP query command.
|
|
748
|
+
|
|
749
|
+
## Developer engineering mode
|
|
750
|
+
|
|
751
|
+
Available only when GAIA was started with `--developer-mode` (or
|
|
752
|
+
`GAIA_DEVELOPER_MODE=1`). If the engineering tools are missing, say that developer
|
|
753
|
+
mode is off; don't route around it with shell or file tools. When describing a
|
|
754
|
+
handoff, claim only what happened:
|
|
755
|
+
|
|
756
|
+
- Context was shared only after the user approved that exact snapshot in the prompt.
|
|
757
|
+
Approval can't be remembered, so each share or append asks again.
|
|
758
|
+
- Opening the coding app never creates or submits a task, and for Codex it doesn't
|
|
759
|
+
prefill a prompt either. Never say the context was "posted" or "sent" to Codex;
|
|
760
|
+
give the user the directory and the prompt to paste.
|
|
761
|
+
- Preview and test results come from the coding app. Report them as app-reported,
|
|
762
|
+
not as checks GAIA ran.
|
|
763
|
+
- Revoking stops future reads; it cannot recall data already delivered.
|
|
764
|
+
|
|
765
|
+
See the [usage guide](https://amd-gaia.ai/docs/guides/harness-engineering).
|