@amd-gaia/gaia 0.1.1 → 0.2.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 +154 -3
- package/README.md +36 -11
- package/SKILL.md +164 -40
- package/SPEC.md +159 -15
- package/binaries.lock.json +25 -25
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +146 -21
- 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 +22 -5
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +257 -41
- package/dist/lifecycle.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -12,18 +12,69 @@ into the terminal UI. Before this there was no packaged path at all — the flag
|
|
|
12
12
|
agent had to be run from a repo checkout with a Python environment, and reaching
|
|
13
13
|
the terminal UI meant building it from source.
|
|
14
14
|
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Windows npm launchers now find the Python daemon CLI even when npm passes the
|
|
18
|
+
package script as argv[1], preserving unrelated tools in shared PATH directories.
|
|
19
|
+
|
|
15
20
|
### Added
|
|
16
21
|
|
|
22
|
+
- **Image generation, reachable out of the box.** "Draw me a red bicycle" now
|
|
23
|
+
generates a PNG with local Stable Diffusion and reports the path; previously
|
|
24
|
+
the tools existed behind a flag nothing turned on, so the agent just said it
|
|
25
|
+
couldn't. Adds `generate_image`, `list_sd_models`, and `get_generation_history`
|
|
26
|
+
(70 tools → 73) plus an `image_gen` bundle so per-turn selection can find them.
|
|
27
|
+
Generating swaps the resident model, so the next reply waits for the chat model
|
|
28
|
+
to reload. The `image-gen` starter skill covers prompt expansion and iterating
|
|
29
|
+
on the previous image.
|
|
30
|
+
- TUI provider setup for Local, Fireworks AI, and AMD LLM Gateway, with masked
|
|
31
|
+
runtime API keys, discovered models, and remote-inference status.
|
|
32
|
+
|
|
33
|
+
- **A project map at task start.** In a code repository the agent now opens
|
|
34
|
+
every task knowing the directory shape, the likely entry points, which
|
|
35
|
+
commands are installed, and the three platform differences that change
|
|
36
|
+
command syntax — instead of discovering each one through a failed tool call.
|
|
37
|
+
Capped at 600 prompt tokens. If the repository has no code index the map
|
|
38
|
+
starts one in the background; `GAIA_PROJECT_MAP_AUTO_INDEX=0` turns that off,
|
|
39
|
+
and `GAIA_PROJECT_ROOT` picks the project when the working directory is not
|
|
40
|
+
it. See SKILL §11.
|
|
17
41
|
- **`503` from `/query` at session capacity.** When every retained session
|
|
18
42
|
slot is busy and none is idle enough to evict, starting a new session
|
|
19
43
|
returns `503` with the reason in `detail` — retryable, distinct from a
|
|
20
44
|
bug-shaped `500`. See SPEC §5.2.
|
|
45
|
+
- **Three further client-visible refusals from `/query`.** Reusing a `run_id`
|
|
46
|
+
that is still in flight gets `409` — it used to replace the live run, leaving
|
|
47
|
+
it with no way to be cancelled. Supplying a `model` that differs from the one
|
|
48
|
+
the `session_id` was built with gets `409` rather than silently answering on
|
|
49
|
+
the old model. A request with an absent or empty `Host` header gets `400`
|
|
50
|
+
rather than being served, closing a DNS-rebinding check that failed open.
|
|
51
|
+
See SPEC §5 and §5.2.
|
|
21
52
|
- **Per-turn skill-body selection.** A loaded skill stays loaded, but its body
|
|
22
53
|
only renders in the prompt on turns whose query matches its description; the
|
|
23
54
|
rest collapse to a one-line menu the model re-activates with `load_skill`.
|
|
24
55
|
`GAIA_DYNAMIC_SKILLS=0` turns the selection off, `GAIA_DYNAMIC_SKILLS_TAU`
|
|
25
56
|
overrides the match threshold, and an embedder outage disables it for the
|
|
26
57
|
session (every body renders — capability is never lost to a failed match).
|
|
58
|
+
- **Per-turn tool selection, now on by default for the flagship `full`
|
|
59
|
+
profile.** The model is sent at most 26 of its 71 tools on any one call — a
|
|
60
|
+
fixed core plus whichever cohesion bundles the query matched — instead of the
|
|
61
|
+
whole registry every time. No capability is lost: `load_tools` is an escape
|
|
62
|
+
hatch the model calls mid-turn to pull in a bundle the selector missed.
|
|
63
|
+
`GAIA_DYNAMIC_TOOLS=0` turns the selection off, `GAIA_DYNAMIC_TOOLS_MAX`
|
|
64
|
+
moves the cap and `GAIA_DYNAMIC_TOOLS_TAU` the match threshold.
|
|
65
|
+
- **One bundled skill ships enabled: `gaia-voice`.** It is a manifest `skills:`
|
|
66
|
+
entry, so it is always on and rendered in full on every LLM call — 676 tokens
|
|
67
|
+
of every prompt, and it declares no tools. It is the agent's honesty floor
|
|
68
|
+
(don't claim work you didn't do, don't present empty output as a result,
|
|
69
|
+
don't substitute a near-miss and report success), which is why it is not in an
|
|
70
|
+
opt-in bundle the way a task skill is. No *skill set* ships enabled: the
|
|
71
|
+
manifest's `skill_sets:` / `default_skill_set:` blocks stay commented out until
|
|
72
|
+
an eval measures what loading several bodies costs. See SKILL.md §10.
|
|
73
|
+
- **A materially smaller fixed prompt on every call.** That always-on
|
|
74
|
+
`gaia-voice` guidance was rewritten from 2,129 tokens to 676 with all of its
|
|
75
|
+
behavioural rules intact. It had been a rationale document — every rule
|
|
76
|
+
followed by the incident that motivated it — and the model needs the rule,
|
|
77
|
+
not the incident report.
|
|
27
78
|
- **`gaia run` (the default command)** — resolves the host platform, fetches and
|
|
28
79
|
SHA-256 verifies both binaries, then launches the terminal UI and propagates its
|
|
29
80
|
exit code. Arguments after a bare `--` are forwarded to the TUI verbatim.
|
|
@@ -64,6 +115,101 @@ the terminal UI meant building it from source.
|
|
|
64
115
|
- **Programmatic exports** — `fetchAll`, `startSidecar`, `shutdown`, `runTui`, the
|
|
65
116
|
platform helpers, and the typed error classes, for embedding GAIA in another
|
|
66
117
|
app.
|
|
118
|
+
- **The `.installed` record is written after a verified sidecar install.**
|
|
119
|
+
Staging the binary was only half an install: the daemon and the terminal UI
|
|
120
|
+
both key "this agent is installed" on `~/.gaia/agents/gaia/.installed`, and
|
|
121
|
+
without it the UI ran the REST sidecar as its own stdio child and the chat
|
|
122
|
+
filled with uvicorn's startup log. It is written on a cache hit as well as a
|
|
123
|
+
fresh download, so an install left by an earlier version repairs itself, and
|
|
124
|
+
only for this host's own platform — a `--platform` fetch stages a binary for a
|
|
125
|
+
different machine and records nothing. See SPEC §4.1.
|
|
126
|
+
- **`--allow-insecure-base-url`** — opt-in for a non-`https` `--base-url`, for a
|
|
127
|
+
trusted local mirror.
|
|
128
|
+
|
|
129
|
+
### Changed
|
|
130
|
+
|
|
131
|
+
- **A `LEMONADE_BASE_URL` that already carries a path is now used exactly as
|
|
132
|
+
written.** Previously any URL not ending in `/api/v1` had that suffix appended,
|
|
133
|
+
so a reverse proxy configured as `https://proxy.example/lemonade` was silently
|
|
134
|
+
rewritten to `https://proxy.example/lemonade/api/v1`. It now resolves to
|
|
135
|
+
`https://proxy.example/lemonade` unchanged, and only a bare origin with no path
|
|
136
|
+
at all gains `/api/v1`. If a proxied install starts returning `404` after
|
|
137
|
+
upgrading, append the API path to the variable yourself.
|
|
138
|
+
|
|
139
|
+
### Fixed
|
|
140
|
+
|
|
141
|
+
- **A second `gaia serve` no longer reports success against a server it does not
|
|
142
|
+
own.** With the port already taken, the incumbent answered `/health` while our
|
|
143
|
+
own sidecar was still unpacking, so the start "succeeded", printed a ready URL
|
|
144
|
+
for someone else's server, and the child then died of `EADDRINUSE`. The port is
|
|
145
|
+
now checked before anything is spawned, and a taken one fails naming the port
|
|
146
|
+
and how to find the process holding it.
|
|
147
|
+
- **Importing the package no longer changes a host app's error handling.** The
|
|
148
|
+
crash and signal handlers reaped every sidecar *before* checking whether the
|
|
149
|
+
host had its own handler, so an exception the host handled killed the sidecar
|
|
150
|
+
and the host's next request got an unexplained `ECONNREFUSED`.
|
|
151
|
+
- **`gaia run` no longer breaks the terminal UI's `PATH`.** Hiding our own `gaia`
|
|
152
|
+
shim removed the whole bin directory, which on a Homebrew or pipx layout also
|
|
153
|
+
took `python3`, `lemonade-server`, and the real `gaia` with it — so the UI
|
|
154
|
+
reported a missing CLI that we had hidden. A shared directory now moves to the
|
|
155
|
+
end of `PATH` instead of being dropped.
|
|
156
|
+
- **Flags a command does not read are refused, not ignored.** `run --port 9000`
|
|
157
|
+
parsed fine and came up on the default port; likewise `serve --component` and
|
|
158
|
+
`serve --cache-dir`.
|
|
159
|
+
- **A failed install says what to do.** A download that could not be moved into
|
|
160
|
+
place — usually a running sidecar holding the file on Windows — printed a raw
|
|
161
|
+
stack trace.
|
|
162
|
+
- **`taskkill` failures name their reason.** Its exit code and stderr were
|
|
163
|
+
discarded, so "Access is denied" surfaced ten seconds later as a generic
|
|
164
|
+
timeout.
|
|
165
|
+
- **A sidecar that survives shutdown is still reaped at exit.** `shutdown`
|
|
166
|
+
de-registered it before killing it, so one that survived both kill windows
|
|
167
|
+
became a permanent orphan holding the port.
|
|
168
|
+
- **`serve` removes its signal handlers once it stops**, so Ctrl+C keeps working
|
|
169
|
+
afterwards, and a repeat Ctrl+C during a slow teardown now says what it is
|
|
170
|
+
waiting for instead of looking frozen.
|
|
171
|
+
- **A non-JSON reply from a sidecar probe raises a typed error** rather than a
|
|
172
|
+
bare `SyntaxError` — the case where a proxy answers `200` with an HTML page.
|
|
173
|
+
- **`--port` no longer accepts what `Number()` would coerce**, so `--port 0x2710`
|
|
174
|
+
is rejected instead of quietly binding 10000.
|
|
175
|
+
|
|
176
|
+
### Security
|
|
177
|
+
|
|
178
|
+
- **A desktop notification can no longer run code.** On Windows, `notify_desktop`
|
|
179
|
+
rendered its message box by pasting the title and body into a PowerShell command
|
|
180
|
+
string, so a `'` in either — text the model picks, and prompt-injected content
|
|
181
|
+
can steer it — closed the string literal and the remainder ran as PowerShell.
|
|
182
|
+
The command is now a fixed script that reads both values from the child's
|
|
183
|
+
environment, and the tool now needs your approval before it runs, like the
|
|
184
|
+
other tools that spawn a process (SKILL.md §8).
|
|
185
|
+
- **`resolveSidecarPath` / `resolveTuiPath` verify the binary before returning a
|
|
186
|
+
path that gets spawned.** Both fed `spawn()` from a predictable cache path with
|
|
187
|
+
no integrity check, so anything able to write `~/.gaia/agents/gaia/` got code
|
|
188
|
+
run — despite the package documenting the SHA verify as its security boundary.
|
|
189
|
+
They now re-hash against `binaries.lock.json`; pass `{ verify: false }` for a
|
|
190
|
+
binary you built yourself. Note this makes them proportional to the binary's
|
|
191
|
+
size — resolve once at startup, not per request.
|
|
192
|
+
- **`--base-url` requires `https`** unless `--allow-insecure-base-url` is passed.
|
|
193
|
+
The pinned SHA already made a plaintext mirror non-exploitable, but tampering
|
|
194
|
+
surfaced as a confusing `IntegrityError` instead of the transport failure it is.
|
|
195
|
+
- **A ~200MB artifact is never held in memory whole.** Downloads stream to disk,
|
|
196
|
+
and the cache-hit check that re-hashes an already-installed binary — which runs
|
|
197
|
+
on every `gaia run` — reads it in bounded chunks. Both hashes are computed
|
|
198
|
+
incrementally, and a download is still verified *before* the file is moved into
|
|
199
|
+
place.
|
|
200
|
+
|
|
201
|
+
### Fixed
|
|
202
|
+
|
|
203
|
+
- **Esc stops a running turn in the terminal UI without killing the agent.**
|
|
204
|
+
The TUI used to kill the agent process, and on the released one-file binary
|
|
205
|
+
that killed only the launcher: the cancelled tool call ran to completion and
|
|
206
|
+
the surviving process consumed the next message. The first Esc now sends the
|
|
207
|
+
agent a `cancel` control message, so the turn ends and the session keeps its
|
|
208
|
+
loaded skills, "always" grants, history and bypass mode. A second Esc stops
|
|
209
|
+
the whole process tree.
|
|
210
|
+
- **A restart after a hard stop no longer turns bypass permissions back on.**
|
|
211
|
+
The replacement agent is launched in the session's current permission mode
|
|
212
|
+
instead of from the original flags, and the TUI says what the restart lost.
|
|
67
213
|
|
|
68
214
|
### Notes
|
|
69
215
|
|
|
@@ -83,11 +229,16 @@ the terminal UI meant building it from source.
|
|
|
83
229
|
building its own TUI. Each terminal-hub artifact is additionally cross-checked
|
|
84
230
|
against the hub's own server-side SHA-256 before its hash enters the lock.
|
|
85
231
|
- Requires Node.js 18+ (built-in `fetch`), a running Lemonade Server for
|
|
86
|
-
inference, and the `gaia` Python CLI on `PATH` for the daemon the TUI
|
|
232
|
+
inference, and the `gaia` Python CLI 0.23.1+ on `PATH` for the daemon the TUI
|
|
233
|
+
starts. 0.23.1 is the first core whose daemon knows how to supervise this
|
|
234
|
+
agent; on 0.23.0 the UI starts with nothing behind it.
|
|
87
235
|
- The sidecar has no arm64 Linux or arm64 Windows build. On those platforms the
|
|
88
236
|
run stops with an error naming the platform and the supported set rather than
|
|
89
237
|
launching a UI with no agent behind it.
|
|
90
|
-
- `gaia_agent`
|
|
91
|
-
|
|
238
|
+
- `gaia_agent` enforces a per-session caller-auth bearer on every `/v1/gaia/*`
|
|
239
|
+
request, plus a loopback `Host` allowlist and non-loopback `Origin` rejection.
|
|
240
|
+
This package mints no token, so a sidecar it spawns comes up in dev mode (token
|
|
241
|
+
check skipped, loudly warned, Host/Origin still enforced) — pass your own
|
|
242
|
+
through `spawnSidecar`'s `env` to turn it on. See SPEC §5.4.
|
|
92
243
|
- Tracks sidecar contract `apiVersion` **2.12**; a differing major raises
|
|
93
244
|
`VersionMismatchError`.
|
package/README.md
CHANGED
|
@@ -9,7 +9,11 @@ npx @amd-gaia/gaia
|
|
|
9
9
|
That fetches the two binaries GAIA needs — the agent sidecar and the terminal UI —
|
|
10
10
|
verifies both against a checksum manifest that ships inside this package, and drops
|
|
11
11
|
you into the terminal UI. No Python to install, no repo to clone, no build step.
|
|
12
|
-
|
|
12
|
+
Local inference is the default. The TUI can explicitly select Fireworks AI or AMD
|
|
13
|
+
LLM Gateway through Lemonade; remote chat sends conversation history to that provider. The
|
|
14
|
+
terminal UI's `--use-claude` flag, which would send a conversation to the
|
|
15
|
+
Anthropic API, is refused for the agent this package installs — see
|
|
16
|
+
[`SPEC.md` §5.5](./SPEC.md#55-other-transports).
|
|
13
17
|
|
|
14
18
|
The terminal UI you get here is the published **`terminal-hub`** component — the
|
|
15
19
|
exact same binary a full GAIA install runs as `gaia tui`, not a separate build. So
|
|
@@ -33,8 +37,10 @@ however you arrive at the terminal UI, it behaves identically.
|
|
|
33
37
|
- **Node.js 18+** (for the built-in `fetch`).
|
|
34
38
|
- **[Lemonade Server](https://amd-gaia.ai/docs/reference/dev)** running locally —
|
|
35
39
|
it hosts the model the agent thinks with. GAIA tells you if it isn't up.
|
|
36
|
-
- The `gaia` Python CLI on `PATH` for the daemon the terminal
|
|
37
|
-
|
|
40
|
+
- The `gaia` Python CLI **0.23.1 or newer** on `PATH` for the daemon the terminal
|
|
41
|
+
UI starts. Earlier cores start a daemon that has no entry for this agent, so the
|
|
42
|
+
UI comes up with nothing behind it. Install it with
|
|
43
|
+
`curl -fsSL https://amd-gaia.ai/install.sh | sh` (Windows:
|
|
38
44
|
`irm https://amd-gaia.ai/install.ps1 | iex`).
|
|
39
45
|
|
|
40
46
|
## Supported platforms
|
|
@@ -75,14 +81,19 @@ npx @amd-gaia/gaia -- --debug
|
|
|
75
81
|
|
|
76
82
|
Common options:
|
|
77
83
|
|
|
78
|
-
| Flag | Meaning |
|
|
79
|
-
| --------------------- | ------------------------------------------------------------- |
|
|
80
|
-
| `--base-url <url>` | Override the download base URL from `binaries.lock.json`
|
|
81
|
-
| `--
|
|
82
|
-
| `--sidecar-dir <dir>` | Where to install the agent sidecar (default `~/.gaia/agents/gaia`) |
|
|
83
|
-
| `--
|
|
84
|
-
| `--force` | Re-download even when a verified binary is already cached |
|
|
85
|
-
| `--
|
|
84
|
+
| Flag | Meaning | Accepted by |
|
|
85
|
+
| --------------------- | ------------------------------------------------------------- | ----------- |
|
|
86
|
+
| `--base-url <url>` | Override the download base URL from `binaries.lock.json`. Must be `https:` | `run`, `fetch`, `serve` |
|
|
87
|
+
| `--allow-insecure-base-url` | Permit a non-`https` `--base-url` (a trusted local mirror) | `run`, `fetch`, `serve` |
|
|
88
|
+
| `--sidecar-dir <dir>` | Where to install the agent sidecar (default `~/.gaia/agents/gaia`) | `run`, `fetch`, `serve` |
|
|
89
|
+
| `--cache-dir <dir>` | Where to cache the terminal UI binary | `run`, `fetch` |
|
|
90
|
+
| `--force` | Re-download even when a verified binary is already cached | `run`, `fetch`, `serve` |
|
|
91
|
+
| `--platform <key>` | Fetch for another platform | `fetch` |
|
|
92
|
+
| `--component <name>` | Fetch only `sidecar` or `tui` | `fetch` |
|
|
93
|
+
| `--port <n>` | Sidecar bind port (default `8141`) | `serve` |
|
|
94
|
+
|
|
95
|
+
A flag a command does not read is **refused**, not ignored — `gaia run --port
|
|
96
|
+
9000` exits 2 rather than silently coming up on the default port.
|
|
86
97
|
|
|
87
98
|
Set `DEBUG=gaia` for download, spawn, and sidecar output on stderr. Diagnostics
|
|
88
99
|
never touch stdout, which the terminal UI owns.
|
|
@@ -92,6 +103,7 @@ never touch stdout, which the terminal UI owns.
|
|
|
92
103
|
| What | Path |
|
|
93
104
|
| --------------- | ---------------------------------------- |
|
|
94
105
|
| Agent sidecar | `~/.gaia/agents/gaia/gaia-agent[.exe]` |
|
|
106
|
+
| Install record | `~/.gaia/agents/gaia/.installed` |
|
|
95
107
|
| Terminal UI | `~/.gaia/npm-cache/gaia-<version>/gaia-tui[.exe]` |
|
|
96
108
|
|
|
97
109
|
The sidecar goes into the GAIA daemon's own cache directory on purpose: the daemon
|
|
@@ -99,6 +111,13 @@ is what spawns and supervises it, and it does its own SHA-256 check on the way.
|
|
|
99
111
|
putting an already-verified binary there we save a second download rather than
|
|
100
112
|
racing one.
|
|
101
113
|
|
|
114
|
+
The `.installed` record next to it is what the daemon and the terminal UI read to
|
|
115
|
+
know the agent is installed — without it the UI would run the sidecar as its own
|
|
116
|
+
stdio child instead of letting the daemon supervise it. It is rewritten even when
|
|
117
|
+
the binary was already cached, so an install left by an earlier version repairs
|
|
118
|
+
itself the next time you run. A `--platform` fetch stages a binary for a
|
|
119
|
+
*different* machine, so it deliberately leaves no record.
|
|
120
|
+
|
|
102
121
|
The terminal UI is installed as `gaia-tui`, **never** as `gaia` — a file named
|
|
103
122
|
`gaia` in a cache directory would shadow the `gaia` shim npm puts on your `PATH`.
|
|
104
123
|
|
|
@@ -126,6 +145,12 @@ curl http://127.0.0.1:8141/health
|
|
|
126
145
|
It waits for `GET /health`, checks the contract version, and tears the whole
|
|
127
146
|
process tree down on Ctrl+C. See [`SPEC.md`](./SPEC.md) for the endpoints.
|
|
128
147
|
|
|
148
|
+
The sidecar normally requires a per-session bearer token on `/v1/gaia/*`, but
|
|
149
|
+
neither `serve` nor `startSidecar` mints one, so both leave it in dev mode — the
|
|
150
|
+
token check off, `Host`/`Origin` still enforced. This agent has shell and file
|
|
151
|
+
tools, so before you expose it to anything, supply a token of your own: see
|
|
152
|
+
[`SPEC.md` §5.4](./SPEC.md#54-caller-authentication).
|
|
153
|
+
|
|
129
154
|
## Programmatic use
|
|
130
155
|
|
|
131
156
|
```ts
|
package/SKILL.md
CHANGED
|
@@ -23,7 +23,8 @@ Two ways in:
|
|
|
23
23
|
> playbook: how *you* wire this package into an app. The agent separately loads
|
|
24
24
|
> **Agent Skills** into its own prompt at runtime from
|
|
25
25
|
> `gaia_agent/skills/<name>/SKILL.md`. Same filename, different artifact —
|
|
26
|
-
> don't ship this one as an agent skill. See
|
|
26
|
+
> don't ship this one as an agent skill. See
|
|
27
|
+
> [Skills](#10-skills--one-always-on-the-rest-opt-in).
|
|
27
28
|
|
|
28
29
|
## 1. Install
|
|
29
30
|
|
|
@@ -51,7 +52,10 @@ built-in `fetch`. Use `import`, not `require`; from CommonJS use
|
|
|
51
52
|
each against the lock**.
|
|
52
53
|
4. Installs the sidecar into `~/.gaia/agents/gaia/` and the TUI into
|
|
53
54
|
`~/.gaia/npm-cache/gaia-<version>/`.
|
|
54
|
-
5.
|
|
55
|
+
5. Writes `~/.gaia/agents/gaia/.installed` — the record the daemon and the TUI
|
|
56
|
+
both read to decide the sidecar is installed. Written on a cache hit too, so
|
|
57
|
+
an install staged by an earlier release repairs itself.
|
|
58
|
+
6. Execs the TUI, whose exit code becomes ours.
|
|
55
59
|
|
|
56
60
|
`run` deliberately does **not** spawn a sidecar. The TUI reaches agents through
|
|
57
61
|
the GAIA daemon's relay and never holds a sidecar token, and the daemon is what
|
|
@@ -187,6 +191,30 @@ await shutdown(proc); // tree-kill; auto-cleanup also reaps on exit
|
|
|
187
191
|
on `exit`, `SIGINT`/`SIGTERM`/`SIGHUP`, `uncaughtException`, and
|
|
188
192
|
`unhandledRejection`. A `SIGKILL` of *your* process is the one case nothing
|
|
189
193
|
in-process can catch.
|
|
194
|
+
- **Mint a caller token, or you are running the sidecar unauthenticated.** The
|
|
195
|
+
sidecar requires `Authorization: Bearer <token>` on every `/v1/gaia/*`
|
|
196
|
+
request, and skips the check only when neither token env var is set — dev
|
|
197
|
+
mode, which is what `startSidecar` gives you, because this package mints
|
|
198
|
+
nothing. Loopback binding is not the boundary: this agent has shell and file
|
|
199
|
+
tools. Mint your own and pass it through the `env` option (`StartOptions`
|
|
200
|
+
extends `SpawnOptions`, so it merges over `process.env`):
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
import { randomBytes } from "node:crypto";
|
|
204
|
+
const token = randomBytes(32).toString("hex");
|
|
205
|
+
const proc = await startSidecar({
|
|
206
|
+
binaryPath: sidecar.binaryPath,
|
|
207
|
+
env: { GAIA_GAIA_SIDECAR_TOKEN: token }, // or ..._TOKEN_FILE, a 0600 path
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Then send `authorization: "Bearer " + token` on every `/v1/gaia/*` call.
|
|
212
|
+
`/health`, `/version`, and `/v1/gaia/version` are exempt, so `startSidecar`'s
|
|
213
|
+
own health-and-version handshake works either way. **If you did not spawn the
|
|
214
|
+
sidecar** — you are talking to one the GAIA daemon started — the token is the
|
|
215
|
+
daemon's, delivered to the sidecar as `GAIA_GAIA_SIDECAR_TOKEN_FILE` (a `0600`
|
|
216
|
+
file path); read it from there, don't invent one. A 401 whose `detail` names
|
|
217
|
+
both env vars means you sent the wrong token or none.
|
|
190
218
|
|
|
191
219
|
Or skip the code entirely and let the CLI own it:
|
|
192
220
|
|
|
@@ -210,7 +238,7 @@ Request body (`extra: "forbid"` — an unknown field is a **422**, not ignored):
|
|
|
210
238
|
| `context` | yes | Transcript slice, pushed in the body — may be `[]`, never absent. Each item `{ role, content }`; `role` ∈ `user` / `assistant` / `system` / `tool`. |
|
|
211
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. |
|
|
212
240
|
| `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. |
|
|
213
|
-
| `model` | no | Overrides the model id
|
|
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. |
|
|
214
242
|
| `provider` | no | Local inference only — anything but `"lemonade"` is a **400**. |
|
|
215
243
|
| `max_steps` | no | ≥ 1. |
|
|
216
244
|
|
|
@@ -220,7 +248,11 @@ import { randomUUID } from "node:crypto";
|
|
|
220
248
|
const runId = randomUUID();
|
|
221
249
|
const res = await fetch(`${proc.baseUrl}/v1/gaia/query`, {
|
|
222
250
|
method: "POST",
|
|
223
|
-
headers: {
|
|
251
|
+
headers: {
|
|
252
|
+
"content-type": "application/json",
|
|
253
|
+
accept: "text/event-stream",
|
|
254
|
+
authorization: `Bearer ${token}`, // required unless the sidecar is in dev mode — §6
|
|
255
|
+
},
|
|
224
256
|
body: JSON.stringify({
|
|
225
257
|
query: "Summarize the PDFs in ~/Documents/reports",
|
|
226
258
|
run_id: runId,
|
|
@@ -290,14 +322,20 @@ Rules a client must respect:
|
|
|
290
322
|
**200**, not a 404, because a cancel racing a normal completion is expected.
|
|
291
323
|
Dropping the HTTP connection also cancels the run.
|
|
292
324
|
|
|
293
|
-
## 8.
|
|
325
|
+
## 8. Over `/v1/gaia/query`, confirmation-gated tools are **refused, not prompted**
|
|
294
326
|
|
|
295
|
-
Read this before you design a workflow around it.
|
|
327
|
+
Read this before you design a workflow around it. This section is about the HTTP
|
|
328
|
+
surface — the agent's other transport can collect an approval; see SPEC §5.5.
|
|
296
329
|
|
|
297
|
-
|
|
298
|
-
base `TOOLS_REQUIRING_CONFIRMATION` set
|
|
299
|
-
**`
|
|
300
|
-
|
|
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 —
|
|
332
|
+
**`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.
|
|
301
339
|
|
|
302
340
|
Over `/v1/gaia/query` there is **no way to collect an approval**, so the stream
|
|
303
341
|
does not prompt. When the agent reaches one of those tools it emits a
|
|
@@ -314,11 +352,13 @@ data: {"type":"needs_confirmation","run_id":"…","action":"write_file","summary
|
|
|
314
352
|
data: {"type":"final","answer":"I stopped before running 'write_file' because it needs your explicit approval, and this streaming surface cannot collect that yet. …"}
|
|
315
353
|
```
|
|
316
354
|
|
|
317
|
-
So: **`/query` cannot
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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.
|
|
322
362
|
|
|
323
363
|
## 9. File-access scope
|
|
324
364
|
|
|
@@ -328,6 +368,16 @@ for a personal document agent, and it is still a real boundary — system
|
|
|
328
368
|
directories, program files, and other users' homes are refused, with the check
|
|
329
369
|
run against the *resolved* path so a symlink out of scope doesn't slip through.
|
|
330
370
|
|
|
371
|
+
**Being in scope is not the same as being safe, and two denylists apply inside
|
|
372
|
+
it.** Reads refuse secrets — `.env`, `id_rsa`, `credentials.json`, `.netrc`,
|
|
373
|
+
`.pem`/`.key`, and everything under `~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.kube` —
|
|
374
|
+
even in an allowed directory. Writes additionally refuse anything that executes
|
|
375
|
+
on its own: shell startup files, PowerShell profiles, `~/.config/autostart/*`,
|
|
376
|
+
systemd user units, `LaunchAgents`, and a repo's `.git/` (hooks and config). Both
|
|
377
|
+
come back as a structured error naming the file and the reason, so do not plan an
|
|
378
|
+
integration around reading a credential file or editing a shell rc — perform
|
|
379
|
+
those from your own code.
|
|
380
|
+
|
|
331
381
|
**In 0.1.1 narrowing it is a construction-time setting only.** The packaged
|
|
332
382
|
sidecar exposes no flag or env var for `allowed_paths` (its CLI accepts only
|
|
333
383
|
`--host` and `--port`), so restricting the scope means embedding `GaiaAgent` in
|
|
@@ -339,7 +389,7 @@ from gaia_agent.agent import GaiaAgent, GaiaAgentConfig
|
|
|
339
389
|
agent = GaiaAgent(config=GaiaAgentConfig(allowed_paths=["/home/me/Documents"]))
|
|
340
390
|
```
|
|
341
391
|
|
|
342
|
-
## 10. Skills —
|
|
392
|
+
## 10. Skills — one always-on, the rest opt-in
|
|
343
393
|
|
|
344
394
|
The agent is built to host **Agent Skills** (short playbooks loaded into its own
|
|
345
395
|
prompt, grouped into named sets, one set active per launch), and its bundled
|
|
@@ -355,19 +405,52 @@ threshold. Manifest `skills:` entries are always-on and never collapse. If the
|
|
|
355
405
|
embedder is unavailable, selection disables itself for the session and every
|
|
356
406
|
body renders — capability is never silently lost to a failed match.
|
|
357
407
|
|
|
358
|
-
**
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
408
|
+
**One skill ships enabled: `gaia-voice`.** It is a manifest `skills:` entry, so
|
|
409
|
+
it is always on, always rendered in full, and paid on every LLM call of every
|
|
410
|
+
turn — budget for it. It is not a task recipe but the agent's honesty floor: do
|
|
411
|
+
not claim work you did not do, do not present empty output as a result, do not
|
|
412
|
+
substitute a near-miss and report success. Those failures corrupt an answer
|
|
413
|
+
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`).
|
|
415
|
+
|
|
416
|
+
**No skill *set* loads.** `gaia-agent.yaml` ships its `skill_sets:` and
|
|
417
|
+
`default_skill_set:` blocks **commented out** — following the email agent's
|
|
418
|
+
precedent, because loading several skill bodies into every prompt costs tokens
|
|
419
|
+
and no eval has measured that trade for this agent yet. Re-enabling is
|
|
420
|
+
uncommenting two blocks; no code change.
|
|
421
|
+
|
|
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
|
|
426
|
+
`gaia-voice`, do not design around a skill being on by default.
|
|
427
|
+
|
|
428
|
+
## 11. The project map — two things it costs you
|
|
429
|
+
|
|
430
|
+
When the agent's working directory is a code repository, every task starts with
|
|
431
|
+
a **project map** in the system prompt: the root, the directory shape, the
|
|
432
|
+
likely entry points, which commands are installed, and the three platform
|
|
433
|
+
differences that change command syntax. It exists so the agent stops burning
|
|
434
|
+
round trips on "no such file" and "command not found".
|
|
435
|
+
|
|
436
|
+
Two consequences an integrator needs to plan for:
|
|
437
|
+
|
|
438
|
+
- **Up to 600 prompt tokens, every turn.** That is the enforced ceiling
|
|
439
|
+
(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.
|
|
446
|
+
|
|
447
|
+
The sidecar's CLI accepts only `--host` and `--port`, so pointing the map at a
|
|
448
|
+
specific project means `GAIA_PROJECT_ROOT=/path/to/repo` in its environment, or
|
|
449
|
+
`GaiaAgentConfig(project_root=...)` when embedding. A directory that is neither
|
|
450
|
+
a VCS checkout nor holds a recognised manifest gets **no map** — that is the
|
|
451
|
+
designed answer, not a failure.
|
|
452
|
+
|
|
453
|
+
## 12. Ports
|
|
371
454
|
|
|
372
455
|
| Service | Port |
|
|
373
456
|
|---|---|
|
|
@@ -379,11 +462,14 @@ Port **4001 is reserved repo-wide**: `spawnSidecar` throws a `RangeError` and
|
|
|
379
462
|
speaks for the user's documents and memory and has no business on a LAN
|
|
380
463
|
interface.
|
|
381
464
|
|
|
382
|
-
##
|
|
465
|
+
## 13. Running in a server or long-lived app
|
|
383
466
|
|
|
384
467
|
- **`fetchAll` / `fetchBinary` are a build step**, not per request — network plus
|
|
385
|
-
a full SHA-256 hash of a large artifact. Run once
|
|
386
|
-
|
|
468
|
+
a full SHA-256 hash of a large artifact. Run once at install time.
|
|
469
|
+
- **`resolveSidecarPath` / `resolveTuiPath` are startup, not per request.** They
|
|
470
|
+
re-hash the binary against `binaries.lock.json` before handing back a path that
|
|
471
|
+
gets spawned, so they cost a full read of a large file. Resolve once and keep
|
|
472
|
+
the path. `{ verify: false }` skips the check for a binary you built yourself.
|
|
387
473
|
- **Spawn once at boot** and hold the `Sidecar` handle for the process lifetime.
|
|
388
474
|
Never per request.
|
|
389
475
|
- **Low concurrency.** One local Lemonade model slot, so parallel queries
|
|
@@ -408,22 +494,31 @@ There is no silent null.
|
|
|
408
494
|
reachable"** means Lemonade isn't running or isn't reachable — not a bug in
|
|
409
495
|
this package. Start it, or set `LEMONADE_BASE_URL`.
|
|
410
496
|
- **`needs_confirmation` is followed by a refusal and the run ends.** See §8.
|
|
411
|
-
|
|
497
|
+
The seven gated tools are unreachable **over `/query`** — the agent itself can
|
|
498
|
+
run them on a transport that can prompt (SPEC §5.5).
|
|
412
499
|
- **A placeholder hash in `binaries.lock.json` blocks the fetch before any
|
|
413
500
|
network call.** Between releases that is the *expected* state — it is not a
|
|
414
501
|
broken install, and there is no override.
|
|
415
502
|
- **No `linux-arm64` / `win32-arm64` sidecar.** The TUI has both. A `PlatformError`
|
|
416
503
|
on those hosts is the design, not a missing artifact.
|
|
417
|
-
- **
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
504
|
+
- **A `401` from `/v1/gaia/*` is the caller-auth token, not a bug.** The sidecar
|
|
505
|
+
requires `Authorization: Bearer <token>`; `/health`, `/version`, and
|
|
506
|
+
`/v1/gaia/version` are exempt, which is why a green health check sits happily
|
|
507
|
+
in front of a 401 on `/query`. It skips the check only in dev mode — neither
|
|
508
|
+
token env var set — which is what `spawnSidecar` and `gaia serve` produce,
|
|
509
|
+
because this package mints nothing. Do not treat loopback binding as the
|
|
510
|
+
boundary; mint a token and pass it (§6).
|
|
421
511
|
- **`run_id` must be a UUID**, and unknown fields in the request body are a
|
|
422
512
|
**422** — the model forbids extras. Typos don't get ignored.
|
|
423
513
|
- **`gaia run` needs the *Python* `gaia` CLI on `PATH`** — the TUI shells out to
|
|
424
|
-
it to start the daemon.
|
|
425
|
-
|
|
426
|
-
|
|
514
|
+
it to start the daemon. So the TUI doesn't re-invoke our own npm shim, the
|
|
515
|
+
child's `PATH` is rewritten: a directory holding nothing but our shim (an npx
|
|
516
|
+
temp dir) is dropped, and a **shared** bin directory is moved to the end
|
|
517
|
+
instead of removed, so the `python3` / `lemonade-server` / real `gaia` beside
|
|
518
|
+
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.
|
|
427
522
|
- **The TUI is installed as `gaia-tui`, never `gaia`** — the terminal-hub artifact
|
|
428
523
|
*is* called `gaia-<platform>`, and a file named `gaia` in a cache directory would
|
|
429
524
|
shadow the npm bin shim. The lock's `filename` and `executable` differ for that
|
|
@@ -463,11 +558,40 @@ A healthy run streams `status` / `token` events and ends with one `final`. If
|
|
|
463
558
|
`/v1/gaia/init` is 503, fix what its `hint` names and retry — the rest of your
|
|
464
559
|
integration is fine.
|
|
465
560
|
|
|
561
|
+
That `/query` call carries no `Authorization` header because `gaia serve` starts
|
|
562
|
+
the sidecar in dev mode. Against one started with a token, add
|
|
563
|
+
`-H "authorization: Bearer $TOKEN"` — a **401** here and a green `/health` is
|
|
564
|
+
that and nothing else (§6).
|
|
565
|
+
|
|
466
566
|
**A 503 from `/query` itself is a different condition**: every retained
|
|
467
567
|
session slot is busy and none is idle enough to evict (SPEC §5.2). Do NOT
|
|
468
568
|
loop on `/v1/gaia/init` — it will report ready. Wait for a running turn to
|
|
469
569
|
finish (or close an idle session) and retry the same `/query`.
|
|
470
570
|
|
|
571
|
+
**Three more refusals are yours to avoid**, each naming its fix in `detail`
|
|
572
|
+
(SPEC §5.2 has the reasoning):
|
|
573
|
+
|
|
574
|
+
- **409 — the `run_id` is still in flight.** You mint it, so mint a fresh UUID
|
|
575
|
+
per request; reusing one would leave the earlier run with no way to be
|
|
576
|
+
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.
|
|
581
|
+
- **400 — the `Host` header is absent or empty.** The loopback check fails
|
|
582
|
+
closed, so omitting the header is refused rather than served. Send
|
|
583
|
+
`Host: 127.0.0.1:<port>`; every real HTTP client already does.
|
|
584
|
+
|
|
471
585
|
For the full wire contract, lock schema, exit codes, and timeout table, see
|
|
472
586
|
[`SPEC.md`](./SPEC.md). For the user-facing overview, see [`README.md`](./README.md)
|
|
473
587
|
and <https://amd-gaia.ai/docs/guides/gaia>.
|
|
588
|
+
|
|
589
|
+
## TUI inference providers
|
|
590
|
+
|
|
591
|
+
The stdio TUI supports `/provider` for Local, Fireworks AI, and AMD LLM Gateway.
|
|
592
|
+
Keys are entered in a masked field and sent directly to local Lemonade's runtime
|
|
593
|
+
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
|
|
595
|
+
when available. Cloud chat sends conversation history to the selected provider;
|
|
596
|
+
embeddings remain on Lemonade. The status event names the actual provider and
|
|
597
|
+
marks remote inference. This is a TUI/stdio capability, not an HTTP query command.
|