@amd-gaia/gaia 0.1.1 → 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/CHANGELOG.md +711 -6
- package/README.md +64 -17
- package/SCORECARD.md +180 -0
- package/SKILL.md +335 -65
- package/SPEC.md +360 -20
- package/binaries.lock.json +25 -25
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +160 -23
- package/dist/cli.js.map +1 -1
- package/dist/errors.d.ts +9 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +9 -0
- package/dist/errors.js.map +1 -1
- package/dist/fetch.d.ts +5 -4
- package/dist/fetch.d.ts.map +1 -1
- package/dist/fetch.js +174 -22
- package/dist/fetch.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/lifecycle.d.ts +24 -7
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +266 -44
- package/dist/lifecycle.js.map +1 -1
- package/package.json +2 -1
package/SPEC.md
CHANGED
|
@@ -190,6 +190,44 @@ supervises the sidecar and does its own SHA-256 check on the binary it finds
|
|
|
190
190
|
there; installing an already-verified binary at that path turns the daemon's fetch
|
|
191
191
|
into a cache hit instead of a second multi-hundred-megabyte download.
|
|
192
192
|
|
|
193
|
+
### 4.1 The `.installed` sentinel
|
|
194
|
+
|
|
195
|
+
Installing the sidecar also writes `~/.gaia/agents/gaia/.installed`, the record
|
|
196
|
+
the daemon and the TUI both read to answer "is this agent installed?". It is
|
|
197
|
+
written on a **cache hit as well as a fresh download**, so an install staged by
|
|
198
|
+
an earlier release repairs itself on the next run, and only when the fetch
|
|
199
|
+
produced a runnable local install — that is, for the `sidecar` component (the
|
|
200
|
+
TUI is not a hub agent) **and** for this host's own platform. A `--platform`
|
|
201
|
+
fetch stages a binary for a different host; recording it would hand the daemon a
|
|
202
|
+
wrong-architecture executable that re-hashes correctly and then fails to exec.
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
{
|
|
206
|
+
"id": "gaia",
|
|
207
|
+
"version": "<sidecar componentVersion>",
|
|
208
|
+
"language": "python",
|
|
209
|
+
"installed_at": "<ISO-8601 UTC>",
|
|
210
|
+
"artifact_sha256": "<the verified SHA-256>",
|
|
211
|
+
"path": "<install directory>",
|
|
212
|
+
"artifact_kind": "binary",
|
|
213
|
+
"executable": "gaia-agent[.exe]"
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
The shape is a cross-repo contract with `InstalledAgent.to_dict()` in
|
|
218
|
+
`gaia.hub.installer`. Three fields are load-bearing:
|
|
219
|
+
`gaia.daemon.sidecars.fetch._hub_installed_binary` ignores the install unless
|
|
220
|
+
`artifact_kind` is `binary` and `executable` and `artifact_sha256` are non-empty,
|
|
221
|
+
and it **re-hashes the binary and raises `IntegrityError` if the file no longer
|
|
222
|
+
matches `artifact_sha256`** — so the recorded hash is the one actually verified,
|
|
223
|
+
never the lock's nominal value. `executable` must be a bare filename;
|
|
224
|
+
`installer.read_sentinel` discards a sentinel whose executable contains a path
|
|
225
|
+
separator, which reads as "never installed".
|
|
226
|
+
|
|
227
|
+
Without the sentinel the daemon sees no install and the TUI falls back to running
|
|
228
|
+
`gaia-agent` as its own stdio child — spawning the REST sidecar over stdio and
|
|
229
|
+
filling the chat with uvicorn's startup log.
|
|
230
|
+
|
|
193
231
|
The TUI directory is keyed by `agentVersion` — this package's version, not the
|
|
194
232
|
component's — so a bump of either never reuses the previous release's executable.
|
|
195
233
|
|
|
@@ -207,11 +245,16 @@ Both defaults are overridable (`--sidecar-dir`, `--cache-dir`).
|
|
|
207
245
|
|
|
208
246
|
Served by `gaia_agent.server`. Bound to `127.0.0.1` only.
|
|
209
247
|
|
|
248
|
+
Every request must carry a loopback `Host` header. A `Host` that is non-loopback,
|
|
249
|
+
absent or empty is refused with `400` before routing — that check is what defeats
|
|
250
|
+
DNS rebinding, so it fails closed rather than serving a request that simply omits
|
|
251
|
+
the header.
|
|
252
|
+
|
|
210
253
|
| Property | Value |
|
|
211
254
|
| ------------------ | --------------------------------------- |
|
|
212
255
|
| Default port | `8141` (`DEFAULT_PORT` in `server.py`) |
|
|
213
256
|
| Reserved port | `4001` — refused with a `RangeError` |
|
|
214
|
-
| Contract version | `API_VERSION = "2.
|
|
257
|
+
| Contract version | `API_VERSION = "2.17"` |
|
|
215
258
|
| Agent id / prefix | `gaia` → `/v1/gaia/...` |
|
|
216
259
|
|
|
217
260
|
### 5.1 Endpoints
|
|
@@ -222,13 +265,67 @@ Served by `gaia_agent.server`. Bound to `127.0.0.1` only.
|
|
|
222
265
|
| `GET` | `/version` | Contract probe. `{ "apiVersion", "agentVersion" }` |
|
|
223
266
|
| `GET` | `/v1/gaia/version` | The TUI's negotiation probe |
|
|
224
267
|
| `GET` | `/v1/gaia/init` | Readiness detail (Lemonade, model, connectors) |
|
|
268
|
+
| `GET` | `/v1/gaia/memory` | The `/memory` snapshot (contract ≥ 2.13) |
|
|
269
|
+
| `GET` | `/v1/gaia/skills` | The skills the agent can load (contract ≥ 2.17) |
|
|
225
270
|
| `POST` | `/v1/gaia/query` | The streaming surface (`text/event-stream`) |
|
|
226
271
|
| `POST` | `/v1/gaia/query/{run_id}/cancel` | Cancel a run by its host-minted `run_id` |
|
|
227
272
|
| `POST` | `/v1/gaia/query/{run_id}/respond`| Answer a mid-run question |
|
|
273
|
+
| `POST` | `/v1/gaia/query/{run_id}/tool_decision` | Answer a confirmation-gated tool (≥ 2.14) |
|
|
274
|
+
| `POST` | `/v1/gaia/sessions/{session_id}/bypass` | Run gated tools without asking, for one session (≥ 2.14) |
|
|
275
|
+
| `POST` | `/v1/gaia/query/{run_id}/followup`| Add to a run already in flight (contract ≥ 2.15) |
|
|
276
|
+
|
|
277
|
+
#### `/query` request body
|
|
278
|
+
|
|
279
|
+
The full, machine-readable contract — every field, type, and which are
|
|
280
|
+
required — is the committed
|
|
281
|
+
[`openapi.gaia.json`](https://github.com/amd/gaia/blob/main/hub/agents/gaia/python/openapi.gaia.json),
|
|
282
|
+
regenerated from the live routes by `python -m gaia_agent.export_openapi` and
|
|
283
|
+
checked for drift in CI — the same pattern as the email agent's own
|
|
284
|
+
`openapi.email.json`. Required: **`query`**, **`run_id`**,
|
|
285
|
+
**`context`** — `additionalProperties: false`, so an unrecognized field or a
|
|
286
|
+
missing required one is `422` before the agent runs. `run_id` must parse as a
|
|
287
|
+
UUID (`uuid.UUID(v)`); a non-UUID string is also `422`. Everything else
|
|
288
|
+
(`model`, `provider`, `max_steps`, `session_id`, `can_answer_questions`,
|
|
289
|
+
`documents`) is optional.
|
|
290
|
+
|
|
291
|
+
`documents` (contract ≥ 2.16) carries the absolute paths of the files the user
|
|
292
|
+
attached to the conversation. Send the whole set on every turn. Before the turn
|
|
293
|
+
runs, the agent indexes the ones it has not indexed yet, so a file attached
|
|
294
|
+
mid-conversation is read on the next turn. Each path is granted as that exact
|
|
295
|
+
file, never its folder. A relative path is a `422`. A missing file, a folder, or
|
|
296
|
+
a secret the path policy refuses is reported as not indexed in the turn's
|
|
297
|
+
`index_documents` step, and the turn still runs. Pre-2.16 binaries `422` the
|
|
298
|
+
field, so probe `/version` before sending it.
|
|
299
|
+
|
|
300
|
+
```json
|
|
301
|
+
{
|
|
302
|
+
"query": "What's in this directory?",
|
|
303
|
+
"run_id": "5b1f6e2a-7c34-4b4d-9b3e-2b6d9a7a1c3e",
|
|
304
|
+
"context": [],
|
|
305
|
+
"session_id": "my-session-1",
|
|
306
|
+
"can_answer_questions": true
|
|
307
|
+
}
|
|
308
|
+
```
|
|
228
309
|
|
|
229
310
|
`/health` is liveness only. It says nothing about whether Lemonade is up or a
|
|
230
311
|
model is loaded — `/v1/gaia/init` answers that.
|
|
231
312
|
|
|
313
|
+
`GET /v1/gaia/memory` returns the read-only snapshot behind the TUI's
|
|
314
|
+
`/memory` view: `{ "available", "reason", "stats", "contexts", "shown",
|
|
315
|
+
"total", "items" }`. `available: false` means the session has no live memory
|
|
316
|
+
store (Lemonade down, embedding model not pulled, disabled via env) — `reason`
|
|
317
|
+
names why, so an outage never renders as "you have no memories". This is the
|
|
318
|
+
daemon-transport counterpart of the stdio `MEMORY_DUMP_QUERY` sentinel; both
|
|
319
|
+
paths call the same `build_memory_dump()` and return the identical shape.
|
|
320
|
+
|
|
321
|
+
`GET /v1/gaia/skills` (contract ≥ 2.17) lists the skills the agent can load:
|
|
322
|
+
`{ "skills": [{ "name", "description", "version", "tier", "tools", "path" }],
|
|
323
|
+
"invalid": { "<folder>": "<why it failed to parse>" } }`. It reads the same
|
|
324
|
+
roots the agent does — its bundled skills, `~/.gaia/skills`, Claude Code skill
|
|
325
|
+
folders — so a host without the Python package (the bundled skills live inside
|
|
326
|
+
the binary) can still show them. A skill root that cannot be read is a `500`
|
|
327
|
+
naming it.
|
|
328
|
+
|
|
232
329
|
### 5.2 `session_id` and agent retention
|
|
233
330
|
|
|
234
331
|
`POST /v1/gaia/query` accepts an optional `session_id` in the request body.
|
|
@@ -236,29 +333,83 @@ model is loaded — `/v1/gaia/init` answers that.
|
|
|
236
333
|
whole conversation.** Contract ≥ 2.12 resolves `session_id` to a *retained*
|
|
237
334
|
agent instead of a throwaway built fresh per call — indexed documents and
|
|
238
335
|
`load_skill` state only survive between turns when the same `session_id`
|
|
239
|
-
threads them together.
|
|
336
|
+
threads them together. A `session_id` must be 1–128 characters from
|
|
337
|
+
`A-Z a-z 0-9 . _ -`, not only dots (a UUID works); any other value is a **400**
|
|
338
|
+
naming the allowed characters, on `/query` and on `/sessions/{session_id}/bypass`.
|
|
339
|
+
Omitting it is a valid, explicit one-shot: nothing persists past that single
|
|
340
|
+
turn, and the agent is not told otherwise.
|
|
341
|
+
|
|
342
|
+
Internal explicit deletion follows the same idle-only rule as eviction: it returns
|
|
343
|
+
`False` for an absent or busy session and preserves a running agent. Successful
|
|
344
|
+
deletion claims the turn lock before removal and closes outside the registry lock.
|
|
345
|
+
|
|
346
|
+
A skill **captured** in-conversation (the `capture_skill` tool — itself
|
|
347
|
+
confirmation-gated, so over `/query` it needs a session that can answer) loads
|
|
348
|
+
**instruction-only** until a human runs `gaia skill promote <name>` in a
|
|
349
|
+
terminal: `load_skill` injects its body but defers registering any tools it
|
|
350
|
+
declares, and reports the deferral in its result. Integrators must not expect
|
|
351
|
+
a captured skill's `<skill>/<tool>` names to exist before that promote.
|
|
240
352
|
|
|
241
353
|
A retained skill stays *loaded* but its body is not necessarily in the prompt
|
|
242
354
|
every turn: the agent selects per turn which loaded bodies match the query and
|
|
243
355
|
collapses the rest to a one-line menu entry (re-activated by calling
|
|
244
356
|
`load_skill` again). `GAIA_DYNAMIC_SKILLS=0` disables the selection;
|
|
245
357
|
`GAIA_DYNAMIC_SKILLS_TAU=<float>` overrides its threshold; an embedder outage
|
|
246
|
-
disables it for the session and every body renders.
|
|
247
|
-
|
|
358
|
+
disables it for the session and every body renders.
|
|
359
|
+
|
|
360
|
+
A retained session also carries a **project map** — up to 600 prompt tokens of
|
|
361
|
+
directory shape, entry points, installed commands and platform quirks, present
|
|
362
|
+
whenever the agent's working directory resolves to a code repository (a VCS
|
|
363
|
+
directory or a recognised manifest at its root). `GAIA_PROJECT_ROOT=<path>`
|
|
364
|
+
picks the project when the working directory is not it. If that repository has
|
|
365
|
+
no code index, the first `search_code_index` builds one;
|
|
366
|
+
`GAIA_PROJECT_MAP_AUTO_INDEX=1` builds it in a background thread at task start
|
|
367
|
+
instead. The same repository check also
|
|
368
|
+
makes `run_shell_command` always-on for the session instead of semantically
|
|
369
|
+
selected. Neither affects the wire contract — they change what the agent
|
|
370
|
+
knows, what tools it is offered, and what the first turn costs.
|
|
248
371
|
|
|
249
372
|
A second `/query` for a `session_id` that already has a turn in flight gets
|
|
250
|
-
`409 Conflict` — cancel the running turn or wait for it, then retry.
|
|
373
|
+
`409 Conflict` — cancel the running turn or wait for it, then retry. Once that
|
|
374
|
+
turn has been cancelled, the new `/query` is held until it finishes its
|
|
375
|
+
in-flight model call (up to **240 s**) and then runs, instead of getting `409`;
|
|
376
|
+
only a cancelled turn that outlasts the wait still gets `409`. A
|
|
251
377
|
`/query` that needs a **new** session while every retained slot is busy and
|
|
252
378
|
none is idle enough to evict gets `503` with the reason in `detail` — a
|
|
253
379
|
temporary, retryable condition, not a bug: wait for a turn to finish (or
|
|
254
380
|
close an idle session) and retry. A
|
|
255
381
|
`session_id` can also be evicted from the retention table under an idle
|
|
256
|
-
timeout or an LRU cap on concurrent sessions
|
|
382
|
+
timeout (**4 hours** without a turn) or an LRU cap on concurrent sessions
|
|
383
|
+
(**100**) — the two knobs that decide when the `503` above can happen at all
|
|
384
|
+
(`session_registry.py`). Eviction is idle-only: a conversation still in use is
|
|
385
|
+
never timed out mid-flight. A `/query` that lands on an
|
|
257
386
|
evicted id gets a fresh agent (the conversation is not blocked) but the
|
|
258
387
|
response stream's first event is a `{"type":"status","status":"warning",...}`
|
|
259
388
|
telling the caller that per-turn state — most visibly any loaded skill — did
|
|
260
389
|
not survive and should be reloaded.
|
|
261
390
|
|
|
391
|
+
A second `/query` reusing a `run_id` that is still in flight gets `409` —
|
|
392
|
+
`run_id` is caller-minted, so mint a fresh UUID per request; reusing one would
|
|
393
|
+
leave the earlier run with no way to be cancelled. A `/query` supplying a `model`
|
|
394
|
+
that differs from the one its `session_id` was built with **switches the retained
|
|
395
|
+
agent in place** (≥ 2.14), so the conversation and any loaded skills survive the
|
|
396
|
+
change; the same machinery the stdio transport's `/model` uses. A switch that
|
|
397
|
+
fails — a missing Claude credential, an unknown local model — is a `409` naming
|
|
398
|
+
the reason, and leaves the session on its previous model. The session records its
|
|
399
|
+
`provider` too: a turn naming a different `provider` switches it the same way,
|
|
400
|
+
onto that turn's `model` or, if it names none, the provider's default model. A
|
|
401
|
+
`model` from the other provider than the one named (a `claude-*` id with
|
|
402
|
+
`"lemonade"`, or a non-Claude id with `"claude"`) is a `400` on a new session and
|
|
403
|
+
an existing one alike, before anything is built or switched.
|
|
404
|
+
|
|
405
|
+
`provider` is optional, and an omitted one is **not** read as `"lemonade"` — it
|
|
406
|
+
means "whatever the `model` implies", so a `claude-*` id alone reaches Anthropic
|
|
407
|
+
and any other id runs locally. The `400` above therefore fires only when a
|
|
408
|
+
request names a `provider` that disagrees with its `model`; there is no pair to
|
|
409
|
+
disagree when only one is given. On a retained session, omitting both leaves the
|
|
410
|
+
session on the provider it is already using, so an ordinary follow-up turn never
|
|
411
|
+
moves a Claude conversation back to Lemonade.
|
|
412
|
+
|
|
262
413
|
### 5.3 Version gate
|
|
263
414
|
|
|
264
415
|
`checkVersion()` reads `/version` and compares the **major** of `apiVersion`
|
|
@@ -266,16 +417,163 @@ against this package's `API_VERSION`. A differing major is a breaking contract
|
|
|
266
417
|
change and raises `VersionMismatchError`. A higher minor with the same major is a
|
|
267
418
|
backward-compatible addition and is accepted.
|
|
268
419
|
|
|
269
|
-
### 5.4
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
420
|
+
### 5.4 Caller authentication
|
|
421
|
+
|
|
422
|
+
Loopback binding is not access control: this sidecar exposes shell and file
|
|
423
|
+
tools, so an unauthenticated port lets any page the user visits drive it. Three
|
|
424
|
+
controls guard it, all in `gaia_agent.caller_auth`, which binds this sidecar's
|
|
425
|
+
env-var names and exempt paths onto the shared mechanism in
|
|
426
|
+
`gaia.sidecar.caller_auth`:
|
|
427
|
+
|
|
428
|
+
1. **Per-session bearer token.** Every `/v1/gaia/*` request must carry
|
|
429
|
+
`Authorization: Bearer <token>`. The spawning parent supplies it one of two
|
|
430
|
+
ways: `GAIA_GAIA_SIDECAR_TOKEN_FILE` — the path to a `0600`, owner-only file
|
|
431
|
+
holding the token, which is the preferred channel because the secret never
|
|
432
|
+
sits in the environment — or `GAIA_GAIA_SIDECAR_TOKEN`, the token itself, the
|
|
433
|
+
legacy delivery. A request without it is **401**, with a `detail` naming both
|
|
434
|
+
env vars.
|
|
435
|
+
2. **Host allowlist.** The `Host` header must be present *and* loopback
|
|
436
|
+
(`127.0.0.1`, `localhost`, `::1`); anything else — including an absent or
|
|
437
|
+
empty header — is **400**. This is what defeats DNS rebinding, where the
|
|
438
|
+
rebound request arrives with `Host: evil.com`, so it fails closed rather
|
|
439
|
+
than letting a caller opt out by omitting the header.
|
|
440
|
+
3. **Origin rejection.** A request carrying a non-loopback browser `Origin` is
|
|
441
|
+
**403**. Non-browser clients send no `Origin` and are unaffected.
|
|
442
|
+
|
|
443
|
+
`/health`, `/version`, and `/v1/gaia/version` are **token-exempt** — they are
|
|
444
|
+
the probes a host polls during the attach handshake, before any token is in
|
|
445
|
+
play, and none of them exposes user data or accepts work. Host and Origin still
|
|
446
|
+
apply to them, so `waitForHealth` and `checkVersion` work against a
|
|
447
|
+
token-protected sidecar without knowing the token.
|
|
448
|
+
|
|
449
|
+
**Dev mode.** If neither env var is set the token check is skipped and the
|
|
450
|
+
sidecar logs a loud warning saying so; Host and Origin are still enforced. That
|
|
451
|
+
is the state a sidecar this package spawns comes up in, because `spawnSidecar`
|
|
452
|
+
mints nothing — pass your own token through `spawnSidecar`'s `env` option to
|
|
453
|
+
turn the check on. The shipped product does not rely on dev mode: the daemon
|
|
454
|
+
mints a per-session token and passes it as `GAIA_GAIA_SIDECAR_TOKEN_FILE`
|
|
455
|
+
(`gaia.daemon.sidecars.spec` mirrors both names as plain strings so core never
|
|
456
|
+
imports this wheel).
|
|
457
|
+
|
|
458
|
+
**Child processes.** The programs the agent starts — shell commands, MCP
|
|
459
|
+
servers, CLI installs and sign-ins, native hub agents, media tools, the Lemonade
|
|
460
|
+
server — never inherit the sidecar token or GAIA's other internal credentials
|
|
461
|
+
(the model-broker token, custody secret and engineering pairing token): their
|
|
462
|
+
environment comes from `gaia.env.child_env`, which also drops any name listed
|
|
463
|
+
in `GAIA_CHILD_ENV_DENY` (comma or space separated). User credentials such as `GH_TOKEN` pass through unchanged.
|
|
464
|
+
`GAIA_NO_DOTENV=1` in the sidecar's launch environment stops it loading `.env`
|
|
465
|
+
files; a `.env` file cannot set that switch itself.
|
|
466
|
+
|
|
467
|
+
### 5.5 Other transports
|
|
468
|
+
|
|
469
|
+
The HTTP server above is the surface this package drives, and everything in
|
|
470
|
+
this document describes it. It is not the agent's only transport:
|
|
471
|
+
`gaia_agent.stdio` runs the same agent over newline-delimited JSON on
|
|
472
|
+
stdin/stdout — no port, no token, no discovery file. The terminal UI reaches it
|
|
473
|
+
that way when it spawns the agent as a subprocess; an agent **installed** from
|
|
474
|
+
the hub, which is what this package delivers, is supervised by the daemon over
|
|
475
|
+
the HTTP surface above instead (§6.1).
|
|
476
|
+
|
|
477
|
+
It emits the identical canonical event vocabulary, but its input channel accepts
|
|
478
|
+
a JSON line carrying a `gaia_control` key: a back-channel that answers a
|
|
479
|
+
confirmation prompt *while* a turn is in flight, toggles full access or
|
|
480
|
+
"accept edits" (file edits inside the starting folder run unasked; commands still
|
|
481
|
+
ask), and stops a turn (`cancel`) without ending the process — so loaded skills,
|
|
482
|
+
"always" grants, history and the permission mode survive a cancel.
|
|
483
|
+
|
|
484
|
+
Contract 2.14 gave the HTTP surface the same three capabilities per run and per
|
|
485
|
+
session — `/tool_decision`, `/sessions/{id}/bypass` (the route keeps its
|
|
486
|
+
original spelling), and `provider: "claude"`. What remains stdio-only is the
|
|
487
|
+
*launch* form of those switches: `--full-access` starts a process with gating
|
|
488
|
+
already off, `--accept-edits` starts it in accept-edits mode, and `--use-claude` / `--claude-model` pin the backend for the life
|
|
489
|
+
of the process (embeddings stay on Lemonade either way).
|
|
490
|
+
|
|
491
|
+
The stdio TUI also supports Local, Fireworks AI, and AMD LLM Gateway through
|
|
492
|
+
Lemonade. `/model` lists downloaded local and discovered cloud chat models,
|
|
493
|
+
grouped by provider, with `(default)` beside the configured default model;
|
|
494
|
+
`/model <id>` switches the live client while preserving conversation and skills.
|
|
495
|
+
The switch reply is one line naming the model, where it runs, and, for a cloud
|
|
496
|
+
model, that messages and anything read from files or memory are sent there.
|
|
497
|
+
Cloud IDs (`fireworks.*`, `amd.*`) route through Lemonade without local model
|
|
498
|
+
loading. Model-state status events identify the provider and remote inference.
|
|
499
|
+
The TUI's `/provider` panel configures credentials directly with local Lemonade;
|
|
500
|
+
keys never travel through stdio queries. These controls are not exposed over
|
|
501
|
+
`/v1/gaia/query`. Lemonade may independently be configured to route a model
|
|
502
|
+
remotely, so a local server URL alone does not establish local inference.
|
|
503
|
+
|
|
504
|
+
`--bypass-permissions` turns off more than the prompt. It also lifts the shell
|
|
505
|
+
tool's own guardrails for the session: the shell-only operators — redirection
|
|
506
|
+
(`>`, `>>`, `<`), backgrounding (`&`), substitution (`` ` ``, `$()`) and the
|
|
507
|
+
newline — parse and run, chaining (`&&`, `||`, `;`, `|`) already ran by
|
|
508
|
+
default, the read-only binary allowlist is replaced by a developer set
|
|
509
|
+
(`node`, `npm`, `make`, `cmake`, `go`, `cargo`, `sed`, `awk`, `curl`, `sleep`,
|
|
510
|
+
`timeout`, `export`, `cp`, `mv`, plus `python`/`python3`/`pytest`/`gh`/`git`,
|
|
511
|
+
which already had their own paths), and the shell rate limit is dropped. `git`
|
|
512
|
+
being in that set means its policy's outright refusals — push, reset, rebase —
|
|
513
|
+
also stop applying under bypass. That is
|
|
514
|
+
arbitrary code execution in the working directory, which is why it exists only
|
|
515
|
+
on this transport: one local parent process on a private pipe. It is **not**
|
|
516
|
+
reachable over HTTP, and the request body cannot ask for it —
|
|
517
|
+
`POST /v1/gaia/sessions/{id}/bypass` stops an HTTP session's approval prompts
|
|
518
|
+
but never lifts these shell gates. `rm` is excluded
|
|
519
|
+
from the developer set — not a boundary, since anything in the set can delete a
|
|
520
|
+
file, but a tripwire against an accidental recursive delete. Redirection has one
|
|
521
|
+
exception: a command that is nothing but a skill-granted CLI runs argv-only
|
|
522
|
+
rather than through a shell, so a `>` there is refused with an explanation
|
|
523
|
+
instead of reaching the binary as a literal argument.
|
|
524
|
+
|
|
525
|
+
Every shell command run under bypass is recorded with its full arguments and its
|
|
526
|
+
per-segment breakdown in `~/.gaia/cache/file_audit.log` — skipping the prompt
|
|
527
|
+
does not skip the record. Treat that file as sensitive; arguments are verbatim.
|
|
528
|
+
The host can toggle bypass mid-session over `gaia_control`, and the shell gates
|
|
529
|
+
follow on the very next command.
|
|
530
|
+
|
|
531
|
+
`--use-claude` is the one with a reach beyond the machine, and it cannot be
|
|
532
|
+
turned on for what this package delivers: the terminal UI **refuses** the launch
|
|
533
|
+
flag for a daemon-transport agent, with an error saying so, because it pins the
|
|
534
|
+
backend for the life of the process and the daemon relay has no such lever.
|
|
535
|
+
|
|
536
|
+
That is a limit on the *launch flag*, not on the agent. Since contract 2.14 a
|
|
537
|
+
caller can ask for Anthropic per request with `/query`'s `provider` field (§5.2),
|
|
538
|
+
so local inference is this package's **default**, not a property of the transport
|
|
539
|
+
that nobody can change. Anything reaching Anthropic is named by the request that
|
|
540
|
+
asked for it.
|
|
276
541
|
|
|
277
542
|
---
|
|
278
543
|
|
|
544
|
+
### 5.6 Adding to a turn already running
|
|
545
|
+
|
|
546
|
+
`POST /v1/gaia/query/{run_id}/followup` with `{ "text": "…" }` hands a live run
|
|
547
|
+
something the user typed after it started. Contract ≥ 2.15.
|
|
548
|
+
|
|
549
|
+
It is not a second turn and not an interrupt. The run keeps going on its
|
|
550
|
+
existing SSE stream; the agent folds the text into that turn's context at its
|
|
551
|
+
next agent-loop step boundary, labelled as arriving mid-task, and answers it
|
|
552
|
+
alongside the work already in progress. A five-minute turn can therefore be
|
|
553
|
+
corrected ("actually, only the unread ones") while it is still running, instead
|
|
554
|
+
of the correction waiting out the turn it was meant to change.
|
|
555
|
+
|
|
556
|
+
Two refusals, both loud, because the caller has already taken the message from
|
|
557
|
+
the user and owes them a truthful answer about where it went:
|
|
558
|
+
|
|
559
|
+
| Status | Meaning |
|
|
560
|
+
| ------ | ------------------------------------------------------------ |
|
|
561
|
+
| `404` | No such run in flight — it finished or was cancelled. Send it as a new `/query`. |
|
|
562
|
+
| `409` | The run's agent is not accepting mid-turn input. |
|
|
563
|
+
|
|
564
|
+
A turn that has already formed its answer takes one more step to address a
|
|
565
|
+
follow-up, so a turn answering in a single step is covered too. The one
|
|
566
|
+
exception is a turn already at its step limit: the message stays queued rather
|
|
567
|
+
than being consumed into a context no model call will read.
|
|
568
|
+
|
|
569
|
+
Because `/query` is stateless (§2.4) the host still owns the transcript: record
|
|
570
|
+
a delivered follow-up in the `context` you push on the **next** turn, between
|
|
571
|
+
that turn's question and its answer, or the conversation loses words the agent
|
|
572
|
+
demonstrably saw.
|
|
573
|
+
|
|
574
|
+
Clients that predate 2.15 get a `404` on the path itself. Probe `/version`
|
|
575
|
+
before sending rather than reading a 404 as "the run ended".
|
|
576
|
+
|
|
279
577
|
## 6. Process ownership
|
|
280
578
|
|
|
281
579
|
Two distinct paths, deliberately:
|
|
@@ -293,13 +591,23 @@ from `~/.gaia/agents/gaia/`. A sidecar spawned here would be a second process th
|
|
|
293
591
|
TUI never talks to, competing for port `8141`.
|
|
294
592
|
|
|
295
593
|
So `run`'s contribution to the sidecar is putting a **verified** binary where the
|
|
296
|
-
daemon looks
|
|
594
|
+
daemon looks, and recording the install in `.installed` (§4.1) so the daemon and
|
|
595
|
+
the TUI both see it. Daemon and sidecar lifecycle belong to the daemon.
|
|
297
596
|
|
|
298
597
|
### 6.2 `gaia serve` — the direct path
|
|
299
598
|
|
|
300
599
|
`serve` fetches the sidecar and spawns it itself, for integrators who want the
|
|
301
|
-
REST surface without a daemon or a UI. This path owns the process fully:
|
|
302
|
-
`waitForHealth` → `checkVersion` → run until
|
|
600
|
+
REST surface without a daemon or a UI. This path owns the process fully:
|
|
601
|
+
port pre-flight → spawn → `waitForHealth` → `checkVersion` → run until
|
|
602
|
+
interrupted → tree-kill.
|
|
603
|
+
|
|
604
|
+
The port is checked **before** the spawn, and a taken port raises
|
|
605
|
+
`PortInUseError` without starting anything. That ordering is the guarantee: the
|
|
606
|
+
frozen sidecar spends seconds unpacking before it attempts its bind, while an
|
|
607
|
+
incumbent answers `/health` in milliseconds — so a post-spawn check alone would
|
|
608
|
+
see a healthy port and a still-alive child, and hand back a handle for a server
|
|
609
|
+
it does not own. `assertOurs` and the re-probe after a failed health wait remain
|
|
610
|
+
as backstops for the narrower window where something binds after the pre-flight.
|
|
303
611
|
|
|
304
612
|
### 6.3 Tree-kill
|
|
305
613
|
|
|
@@ -325,8 +633,8 @@ failed start never leaks a process.
|
|
|
325
633
|
| Code | Meaning |
|
|
326
634
|
| ------- | ----------------------------------------------------------------------- |
|
|
327
635
|
| `0` | Success |
|
|
328
|
-
| `1` | A typed failure: `IntegrityError`, `PlatformError`, `HealthTimeoutError`, `VersionMismatchError`, `BinaryNotFoundError`, or an unexpected error |
|
|
329
|
-
| `2` | Usage error: unknown command, unknown `--component`,
|
|
636
|
+
| `1` | A typed failure: `IntegrityError`, `PlatformError`, `HealthTimeoutError`, `VersionMismatchError`, `BinaryNotFoundError`, `PortInUseError`, `SidecarExitedError`, `MalformedResponseError`, or an unexpected error; also `serve` on Ctrl+C when the sidecar survives the forced kill (the message names its pid and the kill command) |
|
|
637
|
+
| `2` | Usage error: unknown command, invalid `--port`, or a flag the command does not read (`run --port`, `serve --component`, `serve --cache-dir`, `run`/`serve` `--platform`), an unknown `--component`, or a non-https `--base-url` without `--allow-insecure-base-url` |
|
|
330
638
|
| *other* | From `run`: the TUI's own exit code, propagated verbatim |
|
|
331
639
|
|
|
332
640
|
A TUI killed by a signal propagates as `128 + signum` (the shell convention), so a
|
|
@@ -340,12 +648,15 @@ All extend `GaiaError`, so `instanceof GaiaError` catches any of ours.
|
|
|
340
648
|
|
|
341
649
|
| Class | Raised when |
|
|
342
650
|
| ---------------------- | -------------------------------------------------------------- |
|
|
343
|
-
| `IntegrityError` | A download's SHA-256 does not match the lock
|
|
651
|
+
| `IntegrityError` | A download's SHA-256 does not match the lock, or a resolved binary's does not |
|
|
344
652
|
| `PlatformError` | Unsupported platform, missing/incomplete entry, placeholder hash, malformed lock |
|
|
345
653
|
| `HealthTimeoutError` | The sidecar did not report healthy within the timeout |
|
|
346
654
|
| `VersionMismatchError` | `apiVersion` major differs from this package's |
|
|
347
655
|
| `BinaryNotFoundError` | A binary is absent from disk when spawning |
|
|
656
|
+
| `PortInUseError` | The bind port was already taken; nothing was spawned |
|
|
657
|
+
| `SidecarExitedError` | The spawned sidecar died while the port answered — something else owns it |
|
|
348
658
|
| `HttpError` | A non-2xx from a sidecar probe |
|
|
659
|
+
| `MalformedResponseError` | A 2xx from a sidecar probe whose body is not JSON |
|
|
349
660
|
|
|
350
661
|
Per the repo's no-silent-fallbacks rule, every message names what failed, what to
|
|
351
662
|
do, and where to look next.
|
|
@@ -370,7 +681,8 @@ do, and where to look next.
|
|
|
370
681
|
Exported from the package root — see `src/index.ts`.
|
|
371
682
|
|
|
372
683
|
**Fetch:** `fetchAll`, `fetchBinary`, `verifySha256`, `fileSha256`,
|
|
373
|
-
`binaryExists`, `defaultCacheDir`, `daemonSidecarCacheDir
|
|
684
|
+
`binaryExists`, `defaultCacheDir`, `daemonSidecarCacheDir`,
|
|
685
|
+
`INSTALLED_SENTINEL_NAME`, `SIDECAR_AGENT_ID`.
|
|
374
686
|
|
|
375
687
|
**Lifecycle:** `spawnSidecar`, `startSidecar`, `waitForHealth`, `checkVersion`,
|
|
376
688
|
`health`, `version`, `shutdown`, `runTui`, `resolveSidecarPath`, `resolveTuiPath`,
|
|
@@ -384,6 +696,13 @@ Exported from the package root — see `src/index.ts`.
|
|
|
384
696
|
**Constants:** `AGENT_ID`, `API_VERSION`, `DEFAULT_HOST`, `DEFAULT_PORT`,
|
|
385
697
|
`RESERVED_PORT`.
|
|
386
698
|
|
|
699
|
+
`resolveSidecarPath` / `resolveTuiPath` re-verify the file's SHA-256 against
|
|
700
|
+
`binaries.lock.json` before returning a path that is about to be spawned — the
|
|
701
|
+
cache path is predictable, so anything able to write it would otherwise get code
|
|
702
|
+
run. That makes them **O(size of the binary)**, not a cheap path join: resolve
|
|
703
|
+
once at startup, not per request. Pass `{ verify: false }` for a binary you built
|
|
704
|
+
yourself, which no lock describes, and `{ lock }` to reuse an already-loaded lock.
|
|
705
|
+
|
|
387
706
|
---
|
|
388
707
|
|
|
389
708
|
## 11. Logging
|
|
@@ -391,3 +710,24 @@ Exported from the package root — see `src/index.ts`.
|
|
|
391
710
|
`DEBUG=gaia` (or `DEBUG=*`) enables debug output. **Everything goes to stderr** —
|
|
392
711
|
stdout belongs to the TUI once it is exec'd, and to machine-readable JSON for
|
|
393
712
|
`fetch` / `version`.
|
|
713
|
+
|
|
714
|
+
## Developer engineering mode
|
|
715
|
+
|
|
716
|
+
- **Opt-in.** The agent enables it with `--developer-mode` or `GAIA_DEVELOPER_MODE=1`
|
|
717
|
+
in the host process. `gaia engineering` and the engineering MCP server accept only
|
|
718
|
+
the explicit `--developer-mode` flag. It is separate from diagnostic `--dev`.
|
|
719
|
+
- **Surface.** Only in that mode does the agent load the `gaia-harness-engineering`
|
|
720
|
+
skill and the `share_engineering_context`, `append_engineering_context`,
|
|
721
|
+
`approve_engineering_code`, `engineering_status`, `open_engineering_app` and
|
|
722
|
+
`revoke_engineering_context` tools. Sessions without it have none of them.
|
|
723
|
+
- **Consent.** Sharing, appending and code approval each ask for a fresh approval of
|
|
724
|
+
the exact content or scope shown. None of them can be remembered or auto-approved.
|
|
725
|
+
Snapshots are recipient-bound and expire; revoking stops future reads.
|
|
726
|
+
- **Pairing.** Configure the coding app's MCP connection from a Python GAIA install
|
|
727
|
+
with the `[mcp]` extra. The frozen binary this package ships can reuse that
|
|
728
|
+
configuration but cannot launch the Python MCP server itself.
|
|
729
|
+
- **Code.** Fixes happen in the coding app, in worktrees from GAIA's managed source
|
|
730
|
+
cache. Preview results are reported by that app, not verified by GAIA. Nothing
|
|
731
|
+
self-assesses or updates the installed GAIA.
|
|
732
|
+
|
|
733
|
+
See the [usage guide](https://amd-gaia.ai/docs/guides/harness-engineering).
|
package/binaries.lock.json
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "3.0",
|
|
3
|
-
"agentVersion": "0.
|
|
3
|
+
"agentVersion": "0.3.0",
|
|
4
4
|
"components": {
|
|
5
5
|
"sidecar": {
|
|
6
|
-
"componentVersion": "0.
|
|
7
|
-
"baseUrl": "https://hub.amd-gaia.ai/agents/gaia/0.
|
|
6
|
+
"componentVersion": "0.3.0",
|
|
7
|
+
"baseUrl": "https://hub.amd-gaia.ai/agents/gaia/0.3.0",
|
|
8
8
|
"platforms": {
|
|
9
9
|
"win32-x64": {
|
|
10
10
|
"filename": "gaia-agent-win32-x64.exe",
|
|
11
11
|
"executable": "gaia-agent.exe",
|
|
12
|
-
"sha256": "
|
|
13
|
-
"size":
|
|
12
|
+
"sha256": "12e79e63e1169eafadbdd453e2d5e9114bc8c9c04e7ab95e9bd1c96249f50067",
|
|
13
|
+
"size": 169416076
|
|
14
14
|
},
|
|
15
15
|
"darwin-arm64": {
|
|
16
16
|
"filename": "gaia-agent-darwin-arm64",
|
|
17
17
|
"executable": "gaia-agent",
|
|
18
|
-
"sha256": "
|
|
19
|
-
"size":
|
|
18
|
+
"sha256": "f431642fc5cd9c05110cb8f0ffd05fe36853fe1a579bbd0b5421a3c79adc5ddd",
|
|
19
|
+
"size": 159366272
|
|
20
20
|
},
|
|
21
21
|
"darwin-x64": {
|
|
22
22
|
"filename": "gaia-agent-darwin-x64",
|
|
23
23
|
"executable": "gaia-agent",
|
|
24
|
-
"sha256": "
|
|
25
|
-
"size":
|
|
24
|
+
"sha256": "c793fa760b42c1c1acea7aa411f7a6830ee3bd9f68f56c5b6fac686801883f3b",
|
|
25
|
+
"size": 168077296
|
|
26
26
|
},
|
|
27
27
|
"linux-x64": {
|
|
28
28
|
"filename": "gaia-agent-linux-x64",
|
|
29
29
|
"executable": "gaia-agent",
|
|
30
|
-
"sha256": "
|
|
31
|
-
"size":
|
|
30
|
+
"sha256": "b2b115d0ae84e5fbe9aebcf055b264b5b68ae2f1a4b436bdd80de6602ca51437",
|
|
31
|
+
"size": 221414096
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"tui": {
|
|
36
|
-
"componentVersion": "0.
|
|
37
|
-
"baseUrl": "https://hub.amd-gaia.ai/agents/terminal-hub/0.
|
|
36
|
+
"componentVersion": "0.25.0",
|
|
37
|
+
"baseUrl": "https://hub.amd-gaia.ai/agents/terminal-hub/0.25.0",
|
|
38
38
|
"platforms": {
|
|
39
39
|
"win32-x64": {
|
|
40
40
|
"filename": "gaia-win-x64.exe",
|
|
41
41
|
"executable": "gaia-tui.exe",
|
|
42
|
-
"sha256": "
|
|
43
|
-
"size":
|
|
42
|
+
"sha256": "9b1c467b04b267f172a6024589f1ba0ddc882729337913d8afa7dcd43a7a7967",
|
|
43
|
+
"size": 20604416
|
|
44
44
|
},
|
|
45
45
|
"win32-arm64": {
|
|
46
46
|
"filename": "gaia-win-arm64.exe",
|
|
47
47
|
"executable": "gaia-tui.exe",
|
|
48
|
-
"sha256": "
|
|
49
|
-
"size":
|
|
48
|
+
"sha256": "332068c5588e5639213225d63e4a1827d9258928e4053b7d6602668dc7eda60c",
|
|
49
|
+
"size": 19030016
|
|
50
50
|
},
|
|
51
51
|
"darwin-arm64": {
|
|
52
52
|
"filename": "gaia-darwin-arm64",
|
|
53
53
|
"executable": "gaia-tui",
|
|
54
|
-
"sha256": "
|
|
55
|
-
"size":
|
|
54
|
+
"sha256": "bb5ae28ae99ad25c095b3f57c4abf4e2ff67fa560122dc0a99036aedf9184802",
|
|
55
|
+
"size": 18801330
|
|
56
56
|
},
|
|
57
57
|
"darwin-x64": {
|
|
58
58
|
"filename": "gaia-darwin-x64",
|
|
59
59
|
"executable": "gaia-tui",
|
|
60
|
-
"sha256": "
|
|
61
|
-
"size":
|
|
60
|
+
"sha256": "5259c82dc195297130e16377586ed6e0e668c5ec7f8b27a76ee029d67d776b52",
|
|
61
|
+
"size": 20015840
|
|
62
62
|
},
|
|
63
63
|
"linux-x64": {
|
|
64
64
|
"filename": "gaia-linux-x64",
|
|
65
65
|
"executable": "gaia-tui",
|
|
66
|
-
"sha256": "
|
|
67
|
-
"size":
|
|
66
|
+
"sha256": "07f1cc448f8056b5915eff2869f9f3eec3a5d04485d93e448ccad00d186d23c5",
|
|
67
|
+
"size": 19669154
|
|
68
68
|
},
|
|
69
69
|
"linux-arm64": {
|
|
70
70
|
"filename": "gaia-linux-arm64",
|
|
71
71
|
"executable": "gaia-tui",
|
|
72
|
-
"sha256": "
|
|
73
|
-
"size":
|
|
72
|
+
"sha256": "c324100d2de989823e816a6aae7eb4e1224019096b9a46ef1a6983ca6b98598f",
|
|
73
|
+
"size": 18350242
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
}
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAGA;;;;;;;;;;;GAWG;AA8BH,UAAU,UAAU;IAClB,CAAC,EAAE,MAAM,EAAE,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;IACxC,mEAAmE;IACnE,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAaD,4EAA4E;AAC5E,qBAAa,UAAW,SAAQ,KAAK;CAAG;AAExC,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAuCpD;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAGA;;;;;;;;;;;GAWG;AA8BH,UAAU,UAAU;IAClB,CAAC,EAAE,MAAM,EAAE,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;IACxC,mEAAmE;IACnE,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAaD,4EAA4E;AAC5E,qBAAa,UAAW,SAAQ,KAAK;CAAG;AAExC,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAuCpD;AAsID;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,MAAM,GAAG,SAA+B,EACjD,KAAK,GAAE,MAAM,GAAG,SAA2B,GAC1C,MAAM,GAAG,SAAS,CAsBpB;AA+CD,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAUnG;AAsLD,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAkC1D"}
|