@fugood/buttress-server 2.26.0-beta.3 → 2.26.0-beta.30
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/README.md +439 -10
- package/config/function-samples/README.md +1 -0
- package/config/function-samples/classify-ticket.ts +73 -0
- package/config/function-samples/run-agent.ts +39 -0
- package/config/sample.toml +67 -0
- package/lib/agent/cli.d.ts +19 -0
- package/lib/agent/client.d.ts +66 -0
- package/lib/agent/codemode-worker.d.ts +1 -0
- package/lib/agent/codemode.d.ts +7 -0
- package/lib/agent/config.d.ts +15 -0
- package/lib/agent/context.d.ts +11 -0
- package/lib/agent/decisions.check.d.ts +1 -0
- package/lib/agent/decisions.d.ts +113 -0
- package/lib/agent/device-auth.d.ts +3 -0
- package/lib/agent/device-tools.d.ts +5 -0
- package/lib/agent/loopback.d.ts +21 -0
- package/lib/agent/mcp.d.ts +22 -0
- package/lib/agent/models.d.ts +22 -0
- package/lib/agent/service.d.ts +16 -0
- package/lib/agent/sessions.d.ts +30 -0
- package/lib/agent/tools.d.ts +35 -0
- package/lib/agent/tui.d.ts +17 -0
- package/lib/agent/types.d.ts +155 -0
- package/lib/cli-KbjOekMI.mjs +22 -0
- package/lib/client-qqI-HCOj.mjs +8 -0
- package/lib/codemode-C7j8OJ4D.mjs +112 -0
- package/lib/codemode-worker.mjs +3 -0
- package/lib/command-QjZhpiuj.mjs +38 -0
- package/lib/config-DiYweJ2m.mjs +2 -0
- package/lib/functions/executor.d.ts +7 -1
- package/lib/functions/index.d.ts +3 -1
- package/lib/functions/status.d.ts +1 -1
- package/lib/functions/templates.d.ts +1 -1
- package/lib/functions/types.d.ts +15 -2
- package/lib/index.d.ts +12 -5
- package/lib/index.mjs +160 -93
- package/lib/mcp-DzknURyz.mjs +5 -0
- package/lib/mlx-bridge.py +717 -0
- package/lib/net-ChIaAlIH.mjs +46 -0
- package/lib/protocol-WkPByalT.mjs +468 -0
- package/lib/quickjs.wasm +0 -0
- package/lib/rolldown-runtime-dTnj95Mm.mjs +2 -0
- package/lib/routes/agents.d.ts +66 -0
- package/lib/routes/decision-shared.d.ts +28 -0
- package/lib/routes/generator-cache.d.ts +3 -1
- package/lib/routes/index.d.ts +2 -0
- package/lib/routes/llm-shared.d.ts +8 -0
- package/lib/routes/openai-compat.d.ts +13 -2
- package/lib/routes/openai-decisions.check.d.ts +1 -0
- package/lib/routes/openai-decisions.d.ts +97 -0
- package/lib/routes/status.d.ts +2 -1
- package/lib/routes/tts-shared.d.ts +5 -0
- package/lib/routes/typesafe-compat.check.d.ts +1 -0
- package/lib/routes/typesafe-compat.d.ts +30 -0
- package/lib/services/ggml-decision.d.ts +91 -0
- package/lib/services/ggml-llm.d.ts +34 -1
- package/lib/services/index.d.ts +3 -0
- package/lib/services/register-tts-result.d.ts +4 -0
- package/lib/tui-ClNupsCH.mjs +2 -0
- package/lib/types.d.ts +56 -2
- package/lib/ui/command.d.ts +2 -0
- package/lib/ui/host.d.ts +1 -0
- package/lib/ui/window-module.d.ts +6 -0
- package/lib/utils/SessionFileManager.d.ts +12 -4
- package/lib/utils/config.d.ts +3 -3
- package/lib/utils/cors.d.ts +1 -0
- package/lib/utils/decision-sharing.d.ts +37 -0
- package/lib/utils/downloadStatus.d.ts +57 -0
- package/lib/utils/hostFacts.d.ts +13 -0
- package/lib/utils/workspaceState.d.ts +8 -0
- package/lib/wrapper-3PX6qE3t.mjs +7 -0
- package/package.json +17 -9
- package/public/status.html +219 -1489
package/README.md
CHANGED
|
@@ -27,7 +27,8 @@ irm https://cdn.bricks.tools/bricks-buttress/release/install.ps1 | iex
|
|
|
27
27
|
The installer detects the host's supported GGML accelerator (CUDA > Vulkan >
|
|
28
28
|
Snapdragon, falling back to the default CPU/Metal build) and downloads only
|
|
29
29
|
the native modules the binary needs — the ggml llama.node / whisper.node
|
|
30
|
-
variant packages plus onnxruntime, sharp,
|
|
30
|
+
variant packages plus onnxruntime, sharp, oxc-transform and (where gpuix
|
|
31
|
+
publishes one) the settings window's gpuix prebuilds — into a
|
|
31
32
|
`node_modules` sidecar next to the executable. It installs to
|
|
32
33
|
`~/.bricks-cli/bin` (shared with the BRICKS CLI, so one PATH entry covers
|
|
33
34
|
both). Override detection with
|
|
@@ -60,6 +61,67 @@ npx bricks-buttress --config ./config.toml
|
|
|
60
61
|
npx bricks-buttress
|
|
61
62
|
```
|
|
62
63
|
|
|
64
|
+
## Settings, status and chat window (`bricks-buttress ui`)
|
|
65
|
+
|
|
66
|
+
`bricks-buttress ui` opens a native desktop window for one config file:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
bricks-buttress ui -c ./config.toml # or: bricks-buttress ui ./config.toml
|
|
70
|
+
bricks-buttress ui -c ./config.toml --tab chat # open on a given tab: chat, status or settings
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- **Settings** edits the TOML through a form: server, runtime defaults, discovery and endpoints, and generators (a HuggingFace search adds models, and each model shows whether it fits the memory budget). `[functions]`, `[[agents]]`, `[agents_options]` and `[env]` are edited as raw TOML. HuggingFace requests use `[runtime] huggingface_token`, else `$HUGGINGFACE_TOKEN`, else `$HF_TOKEN`.
|
|
74
|
+
- **Status** is a live dashboard of the server started with this config: identity, hardware, pre-downloads, the generators and requests of each backend, and local functions.
|
|
75
|
+
- **Chat** talks to the `[[agents]]` of that server (see [Chat tab](#chat-tab) below).
|
|
76
|
+
|
|
77
|
+
Saving shows a diff of the file first, and an invalid value blocks it. The write is atomic (temporary file + rename), and the previous content is kept as `<config>.bak` next to the file (one backup, replaced on each save). The form rewrites the sections it owns, so comments inside them are lost; the raw sections are written back verbatim, comments included. When the file changes on disk while the window is open, the window offers to reload it or to keep your edits; it never merges them. A missing file opens as a new config from defaults, and nothing is written until you confirm **Create file**.
|
|
78
|
+
|
|
79
|
+
The window never starts, stops or restarts the server, and the server reads its config only at startup: restart it to apply saved changes (the window shows the start command). The settings stay editable while the server runs.
|
|
80
|
+
|
|
81
|
+
**Server detection.** The window reads `[server] port` (default `2080`) and `id` (default `buttress-<machineId>`) from the config, then asks `http://127.0.0.1:<port>` for the server id every 3 seconds:
|
|
82
|
+
|
|
83
|
+
- the same `id` answers: the server runs, and the window opens on Chat when the server reports agents, else on Status;
|
|
84
|
+
- another `id` answers: the port is used by another buttress server;
|
|
85
|
+
- something answers HTTP without a buttress id: the port is used by another program;
|
|
86
|
+
- no answer: the server is not running. The window opens on Settings, and its Status tab turns live by itself when the server appears.
|
|
87
|
+
|
|
88
|
+
The id comes from `/buttress/info` when the server serves it, else from `server.id` in `/buttress/status`. The server serves `/buttress/info` only while autodiscover HTTP is on (`autodiscover = true`, or an `[autodiscover]` table without `http.enabled = false`), and the window asks for it at the default path only: with a custom `http.path` it identifies the server from `/buttress/status`. `/buttress/status` is always served, so detection works with autodiscover off too. Without `/buttress/info` the window shows no addresses or workspace binding for the server.
|
|
89
|
+
|
|
90
|
+
When you save a new `[server] port` or `id` while the server runs, the window keeps showing the running server (with a "restart required" banner) until it stops, then looks for the new port and id.
|
|
91
|
+
|
|
92
|
+
`--tab chat|status|settings` opens the window on that tab instead of the one detection picks.
|
|
93
|
+
|
|
94
|
+
**Platform support.** The window uses gpuix (a React renderer for GPUI) and runs under Bun on `darwin-arm64`, `linux-x64`, `win32-x64` and `win32-arm64`, the platforms with gpuix prebuilt modules. Under Node.js or on another platform the command stops with a message; open `http://<host>:<port>/status` in a browser instead. The standalone binary includes the window (its installer adds gpuix's native module to the sidecar on those platforms); the npm package does not include `@fugood/buttress-ui`, and the command says so. Light or dark follows the macOS appearance; other platforms use light.
|
|
95
|
+
|
|
96
|
+
### Chat tab
|
|
97
|
+
|
|
98
|
+
The Chat tab is a client of the [agent endpoints](#agents-experimental), like `bricks-buttress agent`: it calls `http://127.0.0.1:<port>` of the config with the runtime token the server writes at startup, so it works on an unbound server without `allow_unauthenticated`. The sidebar groups the sessions by agent, newest first, with the first prompt as the title and a badge on forked sessions; it refreshes every 10 seconds and after each run. A run streams into the chat: the thinking (collapsed), the answer as markdown, each tool call with its arguments and result, and a footer with the stop reason, turns and tokens. Enter sends and Shift+Enter starts a new line. Stop aborts the run and the session stays continuable; Fork sends the next prompt to a copy of the session; New session (or `/new` in the composer) starts a fresh one. The window runs one prompt at a time. The server keeps no turn of a stopped or failed run in the session, so a session opened later shows such a prompt with a "No saved answer" note. The tab needs the running server: without it the tab shows the start command, and a config without `[[agents]]` shows where to add them (Settings › Advanced). When the server starts again, the tab reads the new runtime token and reloads the agents by itself. A saved file with `[[agents]]` that the running server does not have (it was started before they were saved, or from another file) shows the restart command instead of an error; agents the server would refuse to start with show that refusal, and the Advanced section warns about it before you save. An agent on a `buttress/…` model needs `[openai_compat] enabled = true` (see [Agents](#agents-experimental), Models).
|
|
99
|
+
|
|
100
|
+
### Status page and `/buttress/status`
|
|
101
|
+
|
|
102
|
+
The server still serves the dashboard at `/status` for any browser (phone width included). It is built from the same view model as the Status tab (see DEVELOPMENT.md) and polls `/buttress/status` every 3 seconds, and `/buttress/info` while the server serves it (the page footer names the path, or says there is none).
|
|
103
|
+
|
|
104
|
+
Next to the per-backend status (`ggmlLlm`, `ggmlStt`, `ggmlTts`, `ggmlDecision`, `mlxLlm`, `onnxStt`, `onnxTts`) and `functions`, `/buttress/status` returns:
|
|
105
|
+
|
|
106
|
+
| Field | Content |
|
|
107
|
+
| ---------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
108
|
+
| `server.id`, `server.name`, `server.version`, `server.port` | The identity `/buttress/info` reports, here also when autodiscover HTTP is off |
|
|
109
|
+
| `server.startedAt` | ISO time the server started |
|
|
110
|
+
| `server.uptimeMs` | Milliseconds since `startedAt` |
|
|
111
|
+
| `server.sessions.open` | RPC sessions in memory: connected clients, plus disconnected ones inside their `session_timeout` window |
|
|
112
|
+
| `server.generatorTypes` | The generator types this server loaded from the config (entries with `enabled = false` excluded), each once |
|
|
113
|
+
| `server.generators` | The generators this server loaded from the config, in file order: `type`, `repoId` and `quantization` (`null` when the entry sets none). The status page lists the ones no client has loaded yet |
|
|
114
|
+
| `server.features.openaiCompat`, `.anthropicMessages`, `.typesafeCompat` | `true` when that compatibility endpoint is served (env var or config flag) |
|
|
115
|
+
| `server.features.autodiscover` | `{ udpPort, udpAnnouncing, httpPath }` as configured (`null` for a transport that is off), or `null` when autodiscover is off. `udpAnnouncing` is `false` when UDP is configured but does not run: it needs a server bound to a workspace with an announce key |
|
|
116
|
+
| `server.features.functions` | `{ enabled, count, daemons, hotReload }`: the local functions loaded now, and the daemon functions configured (`meta.daemon`), including ones that failed to start |
|
|
117
|
+
| `server.features.agents` | `{ enabled, count, names }` of the configured agents, or `null` when none run |
|
|
118
|
+
| `server.host` | This machine, read at startup: `cpuModel`, `cpuCores`, `totalMemoryBytes`, `appleSilicon`, and `gpuName` on Apple Silicon (the chip name) |
|
|
119
|
+
| `server.caps` | The hardware probe of each configured generator type, run at startup: `type`, `score`, `hasGpu`, `usableBytes` (the `generators` list of `/buttress/info`, here also when that route is off) |
|
|
120
|
+
| `downloads.active` | Pre-downloads in progress: `type`, `repoId`, `filename`, `startedAt`, `progress` (0–1), `receivedBytes`, `totalBytes`, `bytesPerSecond` |
|
|
121
|
+
| `downloads.recent` | The last 20 finished pre-downloads, newest first: `type`, `repoId`, `filename`, `totalBytes`, `durationMs`, `finishedAt`, `success`, `error` |
|
|
122
|
+
|
|
123
|
+
`downloads` covers the startup pre-downloads (`download = true`) only; a download that starts when a client loads a model that is not cached yet is not listed. The backends report download progress as a fraction, so `receivedBytes` is `0` and `totalBytes` / `bytesPerSecond` are `null`: use `progress`.
|
|
124
|
+
|
|
63
125
|
## Workspace Binding (`bricks buttress`)
|
|
64
126
|
|
|
65
127
|
By default, a buttress-server runs in **public mode**: any client on the LAN can connect, no auth required. To restrict access to a single BRICKS workspace and enable workspace-scoped JWT auth, **bind** the server with the `bricks buttress` CLI commands. Once bound, the server only accepts WebSocket / file-transfer requests carrying a valid access token signed by that workspace's issuer.
|
|
@@ -69,14 +131,14 @@ The `bricks` CLI is the tool that performs the binding and writes the local stat
|
|
|
69
131
|
### Bind a server to a workspace
|
|
70
132
|
|
|
71
133
|
```bash
|
|
72
|
-
# Pair the local
|
|
134
|
+
# Pair the local server and provision a workspace DevTools token using the current CLI profile
|
|
73
135
|
bricks buttress bind
|
|
74
136
|
|
|
75
137
|
# Override the auto-detected server id, give it a friendly name, or write to a custom state dir
|
|
76
138
|
bricks buttress bind --server-id buttress-mac-studio --name "Studio LLM" --state-dir /etc/buttress
|
|
77
139
|
|
|
78
140
|
# For headless/remote setups: emit state.json to stdout instead of writing to disk
|
|
79
|
-
bricks buttress bind --print > /etc/buttress/state.json
|
|
141
|
+
(umask 077; bricks buttress bind --print > /etc/buttress/state.json)
|
|
80
142
|
```
|
|
81
143
|
|
|
82
144
|
The state file (`~/.bricks-cli/buttress/state.json` by default, or `$BRICKS_BUTTRESS_STATE_DIR`) stores:
|
|
@@ -84,6 +146,15 @@ The state file (`~/.bricks-cli/buttress/state.json` by default, or `$BRICKS_BUTT
|
|
|
84
146
|
- `workspace.id` / `workspace.name` — which workspace this server belongs to
|
|
85
147
|
- `workspace.serverId` — the server's stable id (defaults to `buttress-<machineId>`)
|
|
86
148
|
- `workspace.issuerPublicKey` + `workspace.kid` — Ed25519 SPKI used to verify access tokens
|
|
149
|
+
- `devtools` — workspace-scoped DevTools JWT (`k: "da"`), workspace id and expiry,
|
|
150
|
+
provisioned automatically by `bind` for agent device tools
|
|
151
|
+
|
|
152
|
+
Normal bind/status output reports only token expiry, never the token. The CLI writes
|
|
153
|
+
state with mode `0600`; `--print` includes credentials and must be treated as a secret.
|
|
154
|
+
Rebinding provisions a fresh token (default lifetime: 30 days) and rotates the announce
|
|
155
|
+
key. Issuance failure stops binding before changing the remote announce key. Rebinding
|
|
156
|
+
or unbinding does not revoke previously issued DevTools JWTs; they remain valid until
|
|
157
|
+
expiry or issuer-key rotation.
|
|
87
158
|
|
|
88
159
|
**Restart `bricks-buttress` after binding** for the change to take effect — the state file is read once at startup.
|
|
89
160
|
|
|
@@ -153,7 +224,9 @@ Configuration is loaded from a TOML file passed via `--config` / `-c`. Every top
|
|
|
153
224
|
| `[autodiscover]` | LAN UDP / HTTP / mDNS discovery toggles |
|
|
154
225
|
| `[openai_compat]` | Enable `/oai-compat/v1/*` — see [Compatibility Endpoints](#compatibility-endpoints-experimental) |
|
|
155
226
|
| `[anthropic_messages]` | Enable `/anthropic-messages` — see [Compatibility Endpoints](#compatibility-endpoints-experimental)|
|
|
227
|
+
| `[typesafe_compat]` | Enable `/typesafe-compat/v1/*` — see [Compatibility Endpoints](#compatibility-endpoints-experimental) |
|
|
156
228
|
| `[functions]` | Enable local functions — see [Local Functions](#local-functions-experimental) |
|
|
229
|
+
| `[[agents]]` | Config-defined agents — see [Agents](#agents-experimental) |
|
|
157
230
|
| `[[generators]]` | Array of generator instances — one entry per loaded model |
|
|
158
231
|
|
|
159
232
|
### `[env]`
|
|
@@ -206,6 +279,8 @@ Most ggml-llm `[generators.model]` keys can also live in `[runtime]` as defaults
|
|
|
206
279
|
| `no_extra_bufts` | boolean | Disable extra compute buffer types |
|
|
207
280
|
| `cpu_mask`, `cpu_strict` | string / boolean | CPU affinity (advanced) |
|
|
208
281
|
| `devices` | string[] | Restrict to specific GGML devices |
|
|
282
|
+
| `split_mode` | `"none"` / `"layer"` / `"row"` / `"tensor"` | ggml-llm GPU splitting: single GPU, layers + KV (default), weight rows, or weights + KV (experimental tensor parallelism) |
|
|
283
|
+
| `tensor_split` | number[]\|string | ggml-llm per-GPU proportions, e.g. `[3, 1]` or `"3,1"`; unset uses the native automatic split |
|
|
209
284
|
| Speculative keys | various | `speculative`, `spec_type`, `spec_draft_n_max/n_min/p_min/p_split`, plus draft-model GPU/cache settings |
|
|
210
285
|
|
|
211
286
|
### `[autodiscover]`
|
|
@@ -229,7 +304,8 @@ Every generator entry has a `type`, an optional `[generators.backend]` table, an
|
|
|
229
304
|
|
|
230
305
|
```toml
|
|
231
306
|
[[generators]]
|
|
232
|
-
type = "ggml-llm" # or "ggml-stt" / "ggml-tts" / "mlx-llm"
|
|
307
|
+
type = "ggml-llm" # or "ggml-stt" / "ggml-tts" / "ggml-decision" / "mlx-llm"
|
|
308
|
+
# enabled = false # keep the entry but skip it at startup
|
|
233
309
|
|
|
234
310
|
[generators.backend]
|
|
235
311
|
# (see per-type sections below)
|
|
@@ -239,6 +315,11 @@ repo_id = "..."
|
|
|
239
315
|
# (see per-type sections below)
|
|
240
316
|
```
|
|
241
317
|
|
|
318
|
+
| Key | Type | Notes |
|
|
319
|
+
| -------------------- | ------- | ----------------------------------------------------------------------------------------- |
|
|
320
|
+
| `type` *(required)* | string | `ggml-llm`, `ggml-stt`, `ggml-tts`, `ggml-decision`, `mlx-llm`, `onnx-stt` or `onnx-tts` |
|
|
321
|
+
| `enabled` | boolean | Default `true`. With `false` the server skips the entry at startup: it is not served, announced or pre-downloaded, but stays in the file (the settings window uses this to switch a model off) |
|
|
322
|
+
|
|
242
323
|
#### Common `[generators.model]` keys
|
|
243
324
|
|
|
244
325
|
Shared by **all** generator types:
|
|
@@ -249,7 +330,7 @@ Shared by **all** generator types:
|
|
|
249
330
|
| `revision` | string | Default `"main"` |
|
|
250
331
|
| `download` | boolean | Pre-download at server startup (default `false`) |
|
|
251
332
|
|
|
252
|
-
Additional keys honored by **ggml-llm**, **ggml-stt** and **ggml-
|
|
333
|
+
Additional keys honored by **ggml-llm**, **ggml-stt**, **ggml-tts** and **ggml-decision** (mlx-llm gets quantization from the repo itself and does not use these):
|
|
253
334
|
|
|
254
335
|
| Key | Type | Notes |
|
|
255
336
|
| ------------------------- | --------- | -------------------------------------------------------------------------------- |
|
|
@@ -267,6 +348,10 @@ Additional keys honored by **ggml-llm**, **ggml-stt** and **ggml-tts** (mlx-llm
|
|
|
267
348
|
|
|
268
349
|
Loads a GGUF LLM. Runtime keys above can be overridden per-generator under `[generators.model]`; `[generators.backend]` only controls backend selection and resource planning.
|
|
269
350
|
|
|
351
|
+
GPU splitting (`split_mode`, `tensor_split`) is currently configurable only in Buttress.
|
|
352
|
+
TODO: Expose these options in the LLM (GGML) generator. The experimental `tensor` mode
|
|
353
|
+
requires a compatible GPU backend and model architecture.
|
|
354
|
+
|
|
270
355
|
**`[generators.backend]`**
|
|
271
356
|
|
|
272
357
|
| Key | Type | Default | Notes |
|
|
@@ -282,6 +367,7 @@ Loads a GGUF LLM. Runtime keys above can be overridden per-generator under `[gen
|
|
|
282
367
|
| ----------------------------------------------------------------------------- | ---------------- | -------------------------------------------------------------------- |
|
|
283
368
|
| `n_ctx` | number | Context window. Auto-capped at the model's training context. |
|
|
284
369
|
| `n_gpu_layers` | number\|`"auto"` | Layers offloaded to GPU (default `"auto"`) |
|
|
370
|
+
| `split_mode`, `tensor_split` | string / number[]\|string | Per-model overrides for the GPU split defaults in `[runtime]` |
|
|
285
371
|
| `n_batch` | number | Prompt batch size (default `512`) |
|
|
286
372
|
| `embedding` | boolean | Enable llama.cpp embedding mode for a dedicated embedding model (default `false`) |
|
|
287
373
|
| `pooling_type` | string | Optional embedding pooling override such as `mean` |
|
|
@@ -354,12 +440,21 @@ spec_draft_cache_type_v = "f16"
|
|
|
354
440
|
|
|
355
441
|
Loads a Whisper GGML model for speech-to-text.
|
|
356
442
|
|
|
443
|
+
With `@fugood/whisper.node` 1.2.0-rc.0, `snapdragon` enables experimental
|
|
444
|
+
Whisper Hexagon NPU acceleration on Linux arm64 with a working Qualcomm
|
|
445
|
+
HTP/FastRPC runtime. This variant does not include OpenCL. Install it with
|
|
446
|
+
`--ggml-variant=snapdragon` (the default CPU package remains available for fallback).
|
|
447
|
+
The platform package includes the HTP libraries; the loader defaults
|
|
448
|
+
`ADSP_LIBRARY_PATH` to that package directory and `GGML_HEXAGON_NDEV` to `16`,
|
|
449
|
+
without overriding explicit environment values. Hexagon always enables flash
|
|
450
|
+
attention, even when `use_flash_attn` is off.
|
|
451
|
+
|
|
357
452
|
**`[generators.backend]`**
|
|
358
453
|
|
|
359
454
|
| Key | Type | Default | Notes |
|
|
360
455
|
| --------------------- | -------- | ----------------------------- | ---------------------------------- |
|
|
361
|
-
| `variant` | string | auto | `cuda` / `vulkan` / `default`
|
|
362
|
-
| `variant_preference` | string[] | `["cuda","vulkan","default"]` | Probe order
|
|
456
|
+
| `variant` | string | auto | `cuda` / `vulkan` / `snapdragon` / `default` |
|
|
457
|
+
| `variant_preference` | string[] | `["cuda","vulkan","snapdragon","default"]` | Probe order |
|
|
363
458
|
| `gpu_memory_fraction` | number | `0.85` | |
|
|
364
459
|
| `cpu_memory_fraction` | number | `0.5` | |
|
|
365
460
|
|
|
@@ -384,7 +479,7 @@ Loads a Whisper GGML model for speech-to-text.
|
|
|
384
479
|
[[generators]]
|
|
385
480
|
type = "ggml-stt"
|
|
386
481
|
[generators.backend]
|
|
387
|
-
variant_preference = ["cuda", "vulkan", "default"]
|
|
482
|
+
variant_preference = ["cuda", "vulkan", "snapdragon", "default"]
|
|
388
483
|
[generators.model]
|
|
389
484
|
repo_id = "BricksDisplay/whisper-ggml"
|
|
390
485
|
filename = "ggml-large-v3-turbo-q8_0.bin"
|
|
@@ -467,6 +562,127 @@ vocoder_batch_size = 4096
|
|
|
467
562
|
|
|
468
563
|
---
|
|
469
564
|
|
|
565
|
+
### `ggml-decision` (typed decisions via `@fugood/llama.node`)
|
|
566
|
+
|
|
567
|
+
Loads a typed decision model (System One — Julia-1, Laya, Kev, Jevling, lev,
|
|
568
|
+
Clef, LiquidAI d1, pplx-decider, ...) and answers `choice` / `score` / `noul`
|
|
569
|
+
questions about a state with calibrated probabilities: one forward pass per
|
|
570
|
+
answer, no generated tokens. Clients reach it through the Typed Decision (GGML)
|
|
571
|
+
generator; local functions through `context.buttress.decide`.
|
|
572
|
+
|
|
573
|
+
The model is checked when its context loads: a GGUF with no decision metadata
|
|
574
|
+
(`<arch>.decision.type`) and no `system_one` template, or one whose decision
|
|
575
|
+
type this build cannot serve, fails the load with the reason. Requests on one
|
|
576
|
+
generator run one at a time.
|
|
577
|
+
|
|
578
|
+
**Requests** follow the TypeSafe `/v1/systemone` shape:
|
|
579
|
+
`{ state, questions, images?, files? }`.
|
|
580
|
+
|
|
581
|
+
- `state` is text, or a JSON object/array (given to the model as JSON text).
|
|
582
|
+
- `questions` maps an id to a `choice`, `score` or `noul` question. A `noul`
|
|
583
|
+
question takes `criteria: { true?, false? }`; `lfm2-d1-omni` also takes
|
|
584
|
+
`yes` / `no`.
|
|
585
|
+
- `images` carries the media: images, or audio clips for a model whose prompt
|
|
586
|
+
takes audio (`lfm2-d1-omni`). `files` is an alias; both lists are used. A
|
|
587
|
+
client session sends uploaded file names (resolved like STT audio) or data
|
|
588
|
+
URLs (`data:image/...`, `data:audio/...`); local functions send file paths
|
|
589
|
+
or data URLs; the HTTP endpoints take data URLs only. `lfm2-d1-omni` takes
|
|
590
|
+
images or one audio clip, not both.
|
|
591
|
+
- With media, `state` may be `null` (or empty) to ask about the media only.
|
|
592
|
+
Only `lfm2-d1` and `lfm2-d1-omni` accept that; other models reject it. Without
|
|
593
|
+
media a `state` is required.
|
|
594
|
+
|
|
595
|
+
The OpenAI-compatible `POST /oai-compat/v1/decisions` also takes `input_audio`
|
|
596
|
+
content parts (`{ type: 'input_audio', input_audio: { data, format } }`, base64
|
|
597
|
+
`data`, `format` `wav` or `mp3`). An input with only images or audio gets a
|
|
598
|
+
placeholder state (`The attached image.`), since every model type takes one.
|
|
599
|
+
|
|
600
|
+
**Sharing a model with `ggml-llm`** — a model that both generates and decides
|
|
601
|
+
(Jevling, for one) can be configured as both a `ggml-llm` and a
|
|
602
|
+
`ggml-decision`. By default each loads its own context. Set
|
|
603
|
+
`[generators.backend] share_llm_context = true` on the decision entry to run
|
|
604
|
+
its decisions on the LLM's context instead, when the two entries name the same
|
|
605
|
+
weights (same `repo_id`, `revision`, `filename`, `quantization`, `url` and
|
|
606
|
+
`local_path`): the model is then loaded once, with the LLM's config. This
|
|
607
|
+
applies to local functions, the TypeSafe-compatible endpoint and client
|
|
608
|
+
sessions (whose decision runtime options then do not apply).
|
|
609
|
+
|
|
610
|
+
Sharing trades throughput for memory. A decision takes one of the LLM's
|
|
611
|
+
`n_parallel` slots, and its forward passes run between the LLM's batches, so
|
|
612
|
+
concurrent completions stop generating while it runs (in a CPU test, a
|
|
613
|
+
128-token completion took twice as long beside six decisions; with a separate
|
|
614
|
+
decision context it did not slow down). Results are not affected: the slot's
|
|
615
|
+
sequence and prompt cache are cleared after each decision. Without
|
|
616
|
+
`kv_unified = true` on the LLM a slot holds `n_ctx / n_parallel` tokens, which
|
|
617
|
+
must fit the state plus the questions. The decision loads its own context
|
|
618
|
+
anyway when it sets `rank_pooling` (it needs an embedding context), when its
|
|
619
|
+
projector differs from the LLM's, or when the LLM is embedding or vocab-only.
|
|
620
|
+
The server logs at startup which decision models share.
|
|
621
|
+
|
|
622
|
+
```toml
|
|
623
|
+
[[generators]]
|
|
624
|
+
type = "ggml-llm"
|
|
625
|
+
[generators.model]
|
|
626
|
+
repo_id = "BricksDisplay/jevling-0.8b-v1-GGUF"
|
|
627
|
+
filename = "jevling-0.8b-v1-q8_0-embf16.gguf"
|
|
628
|
+
[generators.runtime]
|
|
629
|
+
n_ctx = 16384
|
|
630
|
+
|
|
631
|
+
[[generators]]
|
|
632
|
+
type = "ggml-decision"
|
|
633
|
+
[generators.backend]
|
|
634
|
+
share_llm_context = true
|
|
635
|
+
[generators.model]
|
|
636
|
+
repo_id = "BricksDisplay/jevling-0.8b-v1-GGUF"
|
|
637
|
+
filename = "jevling-0.8b-v1-q8_0-embf16.gguf"
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
**`[generators.backend]`** — same keys as `ggml-tts` (`variant`,
|
|
641
|
+
`variant_preference`, `gpu_memory_fraction`, `cpu_memory_fraction`).
|
|
642
|
+
|
|
643
|
+
**`[generators.model]`** — common keys plus:
|
|
644
|
+
|
|
645
|
+
| Key | Type | Default | Notes |
|
|
646
|
+
| ------------------------- | -------- | -------------------------------- | ------------------------------------------------------------------ |
|
|
647
|
+
| `preferred_quantizations` | string[] | `["q8_0", "q4_k_m", <no-quant>]` | Default fallback chain |
|
|
648
|
+
| `mmproj_repo_id` | string | falls back to `repo_id` | Projector repo, for models whose prompt takes images or audio |
|
|
649
|
+
| `mmproj_filename` | string | auto (`*mmproj*`) | Pin a specific projector artifact |
|
|
650
|
+
| `mmproj_url` | string | — | Direct projector URL (skips manifest lookup) |
|
|
651
|
+
| `mmproj_revision` | string | falls back to `revision` | |
|
|
652
|
+
| `mmproj_local_path` | string | — | Requires `allow_local_file` |
|
|
653
|
+
| `rank_pooling` | boolean | `false` | `system_one` models with a classification head (`rank_head` readout) |
|
|
654
|
+
| `use_gpu` | boolean | `true` | Force-disable GPU even when available |
|
|
655
|
+
| `n_gpu_layers` | number | `99` | Forced to `0` when the GPU is unused |
|
|
656
|
+
|
|
657
|
+
Setting any `mmproj_*` key loads a projector; requests can then pass `images`
|
|
658
|
+
(images or audio clips, as file paths or data URLs). The projector is ignored,
|
|
659
|
+
with a warning, when the model's prompt has no place for images or audio.
|
|
660
|
+
|
|
661
|
+
**Runtime extras** — under `[runtime]` for ggml-decision:
|
|
662
|
+
|
|
663
|
+
| Key | Type | Default | Notes |
|
|
664
|
+
| -------------------------- | ------ | --------------- | ---------------------------------------- |
|
|
665
|
+
| `n_ctx` | number | `4096` | Must hold the state plus every question |
|
|
666
|
+
| `n_batch` / `n_ubatch` | number | `2048` / `512` | |
|
|
667
|
+
| `max_threads` | number | auto | Caps the llama.cpp thread count |
|
|
668
|
+
| `flash_attn_type` | string | `auto` | `auto` / `on` / `off` |
|
|
669
|
+
| `context_release_delay_ms` | number | `10000` | `0` releases the context right away |
|
|
670
|
+
|
|
671
|
+
**Example**
|
|
672
|
+
|
|
673
|
+
```toml
|
|
674
|
+
[[generators]]
|
|
675
|
+
type = "ggml-decision"
|
|
676
|
+
[generators.model]
|
|
677
|
+
repo_id = "ggml-org/Julia-1-GGUF"
|
|
678
|
+
filename = "Julia-1-Q8_0.gguf"
|
|
679
|
+
download = true
|
|
680
|
+
[generators.runtime]
|
|
681
|
+
n_ctx = 4096
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
---
|
|
685
|
+
|
|
470
686
|
### `mlx-llm` (Apple Silicon, Python `mlx-lm` / `mlx-vlm` bridge)
|
|
471
687
|
|
|
472
688
|
Loads an MLX-format model on Apple Silicon. On first use, the backend creates a virtualenv at `{cache_dir}/mlx-env` and installs `mlx_lm_package`, `mlx_vlm_package`, plus `torch` and `torchvision` (required by some VLM processors). If an existing venv already has `mlx_vlm` and `torch` importable, the install step is skipped. There is no `[generators.backend]` section.
|
|
@@ -575,9 +791,12 @@ Buttress server for remote inference with GGML backends.
|
|
|
575
791
|
|
|
576
792
|
Usage:
|
|
577
793
|
bricks-buttress [options]
|
|
794
|
+
bricks-buttress ui [-c] <config.toml>
|
|
578
795
|
bricks-buttress update [--check] [-y] [--channel <release|beta>]
|
|
579
796
|
|
|
580
797
|
Commands:
|
|
798
|
+
ui Open the settings, status and chat window for a
|
|
799
|
+
config file (see `bricks-buttress ui --help`)
|
|
581
800
|
update Update bricks-buttress to the latest version.
|
|
582
801
|
Standalone binary installs re-run the CDN
|
|
583
802
|
installer (refreshing the native-module
|
|
@@ -612,7 +831,7 @@ Examples:
|
|
|
612
831
|
|
|
613
832
|
## Compatibility Endpoints (Experimental)
|
|
614
833
|
|
|
615
|
-
The server can expose OpenAI- and
|
|
834
|
+
The server can expose OpenAI-, Anthropic- and TypeSafe-compatible HTTP endpoints in addition to the native RPC. Each endpoint is opt-in via the TOML config:
|
|
616
835
|
|
|
617
836
|
```toml
|
|
618
837
|
[openai_compat]
|
|
@@ -622,14 +841,19 @@ enabled = true
|
|
|
622
841
|
[anthropic_messages]
|
|
623
842
|
enabled = true
|
|
624
843
|
# cors_allowed_origins = ["http://localhost:3000"]
|
|
844
|
+
|
|
845
|
+
[typesafe_compat]
|
|
846
|
+
enabled = true
|
|
625
847
|
```
|
|
626
848
|
|
|
627
849
|
| Endpoint | Config flag |
|
|
628
850
|
| --------------------- | ------------------------------------ |
|
|
629
851
|
| `/oai-compat/v1/*` | `[openai_compat] enabled = true` |
|
|
630
852
|
| `/anthropic-messages` | `[anthropic_messages] enabled = true` |
|
|
853
|
+
| `/typesafe-compat/v1/*` | `[typesafe_compat] enabled = true` |
|
|
631
854
|
|
|
632
|
-
Completion requests may select only LLM models declared in `[[generators]]
|
|
855
|
+
Completion requests may select only LLM models declared in `[[generators]]`, and decision
|
|
856
|
+
requests only `ggml-decision` generators. Use each
|
|
633
857
|
endpoint's `GET /v1/models` response for valid model IDs; unknown IDs are rejected before the
|
|
634
858
|
server starts or downloads a generator.
|
|
635
859
|
|
|
@@ -693,6 +917,7 @@ export default async function ({ path }: { path: string }, context: ButtressFunc
|
|
|
693
917
|
| `buttress.detokenize({ model?, tokens })` | Convert token ids back into text with the same model. |
|
|
694
918
|
| `buttress.transcribe({ model?, filePath \| audioData, options? })` | Transcribe audio with this server's STT generator. |
|
|
695
919
|
| `buttress.synthesize({ model?, text, options? })` | Synthesize speech with this server's TTS generator (`onnx-tts` or `ggml-tts`); the WAV lands in `tempDir` → `{ path, sampling_rate, channels }`. |
|
|
920
|
+
| `buttress.decide({ model?, state, questions, images?, files? })` | Answer typed `choice` / `score` / `noul` questions about `state` with this server's `ggml-decision` generator → `{ model, answers, usage }`. `answers[id]` is typed from its question. `images` (alias `files`) are images or audio clips, as local paths or data URLs; with media, `state` may be `null` for `lfm2-d1` / `lfm2-d1-omni`. |
|
|
696
921
|
| `bank.list({ keyword?, meta?, ids? })` / `bank.get(id)` / `bank.update(properties, { dontNotify? })` / `bank.remove(id)` | Read/write the bound workspace's remote **Data Bank** (see below). To listen for changes, use `bank.subscribe` inside a daemon function. |
|
|
697
922
|
| `daemons.emit(name, event, data?)` / `daemons.list()` | Signal a running **daemon function**'s `onEvent` handlers (see below) / list running daemon names. |
|
|
698
923
|
| `emit(event, data)` | Progress event; delivered to SSE callers, ignored otherwise. |
|
|
@@ -850,6 +1075,210 @@ The handler receives `{ method, path, name?, headers, query, token, workspaceAut
|
|
|
850
1075
|
|
|
851
1076
|
Function files themselves are trusted input, exactly like this config file. They run in a `node:vm` context with a clean global (no ambient `process` or `require`), but that is for clarity, not isolation — a function that is handed `spawn` can do anything the server process can. Only put code you wrote (or reviewed) in the functions directory.
|
|
852
1077
|
|
|
1078
|
+
## Agents (Experimental)
|
|
1079
|
+
|
|
1080
|
+
Buttress can host **agents**: pi-based LLM loops that run inside the server
|
|
1081
|
+
process, use **local functions** (and MCP servers) as their tools, and keep
|
|
1082
|
+
**config-scoped sessions** on disk. The primary consumer is automation — a
|
|
1083
|
+
local function or daemon calls `context.agents.run(...)` to add multi-step
|
|
1084
|
+
reasoning to a server-side workflow; an interactive CLI exists for driving and
|
|
1085
|
+
inspecting the same agents.
|
|
1086
|
+
|
|
1087
|
+
```toml
|
|
1088
|
+
[[agents]]
|
|
1089
|
+
name = "ops-assistant" # unique; the session scope key
|
|
1090
|
+
model = "buttress/ggml-org/gpt-oss-20b-GGUF" # split on the FIRST slash: provider/model-id
|
|
1091
|
+
# model = "anthropic/claude-sonnet-5" # any pi-supported provider; API key from env
|
|
1092
|
+
system_prompt_file = "./prompts/ops.md" # or inline: system_prompt = "..."
|
|
1093
|
+
tools = ["get_server_status", "restart_service"] # local function names (explicit; no wildcard)
|
|
1094
|
+
local_devices = false # opt in to LAN scan + device DevTools tools
|
|
1095
|
+
max_turns = 30 # assistant↔tool round-trips per run (default 30)
|
|
1096
|
+
# codemode = true # opt in: a sandboxed JS tool that calls the other tools
|
|
1097
|
+
# max_tokens_per_run = 200000 # per-run token budget; unset = unlimited
|
|
1098
|
+
# temperature = 0.2 # unrecognized keys pass through to generation
|
|
1099
|
+
|
|
1100
|
+
[agents.mcp_servers.github] # optional MCP servers (StreamableHTTP or stdio)
|
|
1101
|
+
url = "https://api.githubcopilot.com/mcp/"
|
|
1102
|
+
# headers = { Authorization = "Bearer ..." }
|
|
1103
|
+
# optional = true # continue without this server if it won't connect
|
|
1104
|
+
# timeout = 60 # per-request seconds (progress renews it)
|
|
1105
|
+
# exposure = "direct" # "hidden": unreachable; "codemode": scripts only (needs codemode = true)
|
|
1106
|
+
# tool_exposure = { "delete_*" = "hidden" } # per tool, by exact name or * pattern
|
|
1107
|
+
|
|
1108
|
+
[agents_options] # optional; defaults shown
|
|
1109
|
+
# sessions_dir = "./.buttress-agent/sessions" # relative to this config file
|
|
1110
|
+
# session_max_age = "30d" # retention sweep; 0 disables
|
|
1111
|
+
# session_max_count = 500 # per agent; 0 disables
|
|
1112
|
+
# max_depth = 2 # function → agent → function → agent chain cap
|
|
1113
|
+
# allow_unauthenticated = false # serve /agents on an UNBOUND server (see below)
|
|
1114
|
+
```
|
|
1115
|
+
|
|
1116
|
+
**Models.** `buttress/<repo_id>` targets a configured `[[generators]]` LLM
|
|
1117
|
+
(ggml/mlx) and is validated at startup; traffic flows through an in-process
|
|
1118
|
+
OpenAI-compat loopback (no socket). The loopback calls the server's own
|
|
1119
|
+
`/oai-compat/v1` routes, which exist only with `[openai_compat] enabled = true`
|
|
1120
|
+
(or `ENABLE_OPENAI_COMPAT_ENDPOINT=1`): an agent with a `buttress/…` model
|
|
1121
|
+
needs it, or every run fails with `404 NOT_FOUND`. Any other provider prefix is a pi
|
|
1122
|
+
built-in provider (`anthropic/…`, `openai/…`, `google/…`, …) authenticated by
|
|
1123
|
+
its usual environment variable (`ANTHROPIC_API_KEY`, …) — set it via `[env]`
|
|
1124
|
+
or the process environment. OAuth-based logins are not supported headless.
|
|
1125
|
+
|
|
1126
|
+
**Tools.** `tools` lists local function names explicitly. Each tool call runs
|
|
1127
|
+
through the normal functions executor (same lazy reload, scratch dir, spawn
|
|
1128
|
+
tracking, and the function's own `meta.timeout` deadline), and aborting the
|
|
1129
|
+
run aborts in-flight tool calls and their spawned processes. A listed function
|
|
1130
|
+
that is missing fails the run loudly rather than letting a headless automation
|
|
1131
|
+
improvise around it. MCP tools get server-qualified names
|
|
1132
|
+
(`mcp__github__create_issue`; characters outside `[A-Za-z0-9_]` become `_`);
|
|
1133
|
+
MCP servers connect lazily on the first run and fail closed unless marked
|
|
1134
|
+
`optional = true`.
|
|
1135
|
+
|
|
1136
|
+
**Codemode.** `codemode = true` on an `[[agents]]` entry (default `false`) adds a
|
|
1137
|
+
`codemode` tool: the model writes JavaScript that runs in a QuickJS sandbox and
|
|
1138
|
+
calls the agent's other tools (`await tools.<name>(args)`), so it can batch calls
|
|
1139
|
+
and filter large results before they reach the context. MCP servers of such an
|
|
1140
|
+
agent may use `exposure = "codemode"` (or `"deferred"`) to keep their tools out
|
|
1141
|
+
of the model's tool list; scripts find them with `searchTools()`.
|
|
1142
|
+
`models.classify()` runs pi-ai classifier models with the agent's credentials,
|
|
1143
|
+
including this server's decision generators (`buttress/<ggml-decision id>`).
|
|
1144
|
+
Small local models may write poor scripts — prefer it with capable models.
|
|
1145
|
+
|
|
1146
|
+
**Decisions (System One).** `decision_model` gives an agent a typed-decision
|
|
1147
|
+
model: `buttress/<ggml-decision id>` (as `GET /typesafe-compat/v1/models` lists
|
|
1148
|
+
it), or a hosted classifier such as `typesafe/jev-latest` (`TYPESAFE_API_KEY`;
|
|
1149
|
+
the questions and the state are then sent to TypeSafe). Unset means no
|
|
1150
|
+
decisions; it never falls back to the chat model.
|
|
1151
|
+
|
|
1152
|
+
```toml
|
|
1153
|
+
[[agents]]
|
|
1154
|
+
name = "ops-assistant"
|
|
1155
|
+
model = "buttress/unsloth/Qwen3.5-4B-GGUF"
|
|
1156
|
+
tools = ["host-info"]
|
|
1157
|
+
decision_model = "buttress/BricksDisplay/jevling-e2b-v1-GGUF:jevling-e2b-v1-q8_0-embf16.gguf"
|
|
1158
|
+
decision_assistance = true # opt-in tool prefilter (default false)
|
|
1159
|
+
# decision_timeout_ms = 500 # prefilter deadline; past it the run keeps every tool
|
|
1160
|
+
```
|
|
1161
|
+
|
|
1162
|
+
- With a `decision_model`, the agent gets a `decision_evaluate` tool. The model
|
|
1163
|
+
calls it itself with a `state` and `choice` / `score` / `bool` questions, and
|
|
1164
|
+
gets calibrated probabilities back. The tool sees only that state, not the
|
|
1165
|
+
conversation. Its answers are evidence for the model, not permission.
|
|
1166
|
+
- With `decision_assistance = true` too, a tool prefilter runs before each run.
|
|
1167
|
+
It asks three yes/no questions about the request: does it need live facts
|
|
1168
|
+
about the system, does it ask for an action (or approve/retry one), and does
|
|
1169
|
+
it point at something not in the conversation. Only when all three are
|
|
1170
|
+
clearly no (each probability < 0.35) does the run go out **without tool
|
|
1171
|
+
definitions**: a shorter prompt for greetings, thanks, explanations or
|
|
1172
|
+
translations. Anything else, including a timeout or an error, keeps every
|
|
1173
|
+
tool. It sends the request, the nearest user/assistant text (≤ 6,000
|
|
1174
|
+
characters) and the tool names; never tool arguments, results or reasoning.
|
|
1175
|
+
The server logs each outcome (`tool prefilter pruned|kept …`).
|
|
1176
|
+
- The prefilter never adds or allows a tool; it only withholds them for one
|
|
1177
|
+
run. Small decision models judge it less reliably: jevling-0.8b pruned some
|
|
1178
|
+
live-fact questions that need a tool, jevling-e2b kept them (about 170 ms per
|
|
1179
|
+
run on a GPU). The first run after start may time out while the model loads.
|
|
1180
|
+
|
|
1181
|
+
**Local devices.** Set `local_devices = true` on an individual `[[agents]]` entry
|
|
1182
|
+
(default `false`) to add just two built-in tools, independently of local functions/MCP:
|
|
1183
|
+
|
|
1184
|
+
- `devices_scan`: the same UDP + HTTP LAN discovery as `bricks devtools scan`,
|
|
1185
|
+
including project previews found relative to the Buttress config directory.
|
|
1186
|
+
- `devtools`: one action-based tool for screenshots (inline images), brick tree/query,
|
|
1187
|
+
tap/key/text input, a press by brick id, the open app modals and one operation on the topmost
|
|
1188
|
+
one (app version >= 2.26; a generator confirm dialog is only dismissed), console capture,
|
|
1189
|
+
JavaScript evaluation, storage overview, and raw CDP requests for other supported operations.
|
|
1190
|
+
Reuses the CLI CDP transport and shared DevTools operations; no CLI subprocess or separate tool
|
|
1191
|
+
per device/action.
|
|
1192
|
+
|
|
1193
|
+
For example, scan with `{}` then call `devtools` with
|
|
1194
|
+
`{ "action": "tree", "address": "192.168.1.42", "port": 19851 }`.
|
|
1195
|
+
After `bricks buttress bind` and a restart, the workspace credential is injected
|
|
1196
|
+
server-side automatically when the device advertises the same workspace and issuer.
|
|
1197
|
+
The token is validated for signature, kind and expiry before use; inbound Buttress
|
|
1198
|
+
JWTs (`k: "ba"`) are never forwarded. Older bindings without the credential, or expired
|
|
1199
|
+
credentials, require rebinding and a restart. The server does not read the CLI login
|
|
1200
|
+
or renew tokens itself. Local previews can still use advertised inspect credentials;
|
|
1201
|
+
explicit `passcode` / `accessToken` arguments remain optional overrides. Calls open/close their own connection;
|
|
1202
|
+
avoid parallel calls or other inspectors on the same device (one CDP client at a time).
|
|
1203
|
+
|
|
1204
|
+
**Security:** enabling this option grants network discovery and device control,
|
|
1205
|
+
including arbitrary JavaScript evaluation on reachable authenticated devices. It is
|
|
1206
|
+
not read-only or a network sandbox. Automatic authentication requires a **trusted LAN**:
|
|
1207
|
+
workspace/issuer hints from device info are not cryptographic peer authentication.
|
|
1208
|
+
The stored token is never inserted into model arguments and is redacted from text
|
|
1209
|
+
results/errors before truncation. Explicit credentials supplied in arguments still
|
|
1210
|
+
enter session transcripts, as do console output and device data; prefer automatic
|
|
1211
|
+
authentication and only grant this to trusted agents/callers. Disable with `local_devices = false` and
|
|
1212
|
+
restart Buttress. Aborting closes active CDP connections; discovery completes its
|
|
1213
|
+
bounded scan before returning the abort result.
|
|
1214
|
+
|
|
1215
|
+
**Sessions.** Each run returns a `sessionId`; pass it back to continue the
|
|
1216
|
+
conversation, or add `fork: true` to branch it into a fresh session. Sessions
|
|
1217
|
+
are JSONL files under `sessions_dir`, scoped by agent name (renaming an agent
|
|
1218
|
+
orphans its sessions), written as the run streams — an aborted or timed-out
|
|
1219
|
+
run still leaves a continuable transcript. Same-session runs queue; different
|
|
1220
|
+
sessions run in parallel. A retention sweep prunes by age and count.
|
|
1221
|
+
|
|
1222
|
+
**From a local function** (the primary surface):
|
|
1223
|
+
|
|
1224
|
+
```ts
|
|
1225
|
+
export default async ({ service }, context) => {
|
|
1226
|
+
const result = await context.agents.run('ops-assistant', {
|
|
1227
|
+
prompt: `Investigate the '${service}' service and fix it if needed.`,
|
|
1228
|
+
// sessionId, fork, onEvent are also accepted
|
|
1229
|
+
})
|
|
1230
|
+
return { conclusion: result.content, sessionId: result.sessionId }
|
|
1231
|
+
}
|
|
1232
|
+
```
|
|
1233
|
+
|
|
1234
|
+
`context.agents.run` resolves within the caller's lifetime and deadline
|
|
1235
|
+
(`context.signal` aborts it); long-running agent work belongs in a **daemon**
|
|
1236
|
+
(no deadline) or a function with a raised `meta.timeout`. `context.agents.list()`
|
|
1237
|
+
and `context.agents.sessions(name)` round out the surface. Chained invocations
|
|
1238
|
+
(a tool function calling another agent) are capped by `agents_options.max_depth`.
|
|
1239
|
+
|
|
1240
|
+
**HTTP endpoints** (also what the CLI uses):
|
|
1241
|
+
|
|
1242
|
+
```
|
|
1243
|
+
GET /agents { agents: names, details: [{ name, model, tools,
|
|
1244
|
+
mcpServers, maxTurns, localDevices, codemode,
|
|
1245
|
+
decisionModel }] }
|
|
1246
|
+
POST /agents/:name/run { prompt, sessionId?, fork? }; ?stream=1 for SSE
|
|
1247
|
+
(a `session` event with the id arrives first,
|
|
1248
|
+
then `agent` events, then `result`/`error`)
|
|
1249
|
+
GET /agents/:name/sessions newest-first summaries (id, timestamps, preview)
|
|
1250
|
+
GET /agents/:name/sessions/:id full transcript
|
|
1251
|
+
POST /agents/:name/sessions/:id/abort abort the active run
|
|
1252
|
+
```
|
|
1253
|
+
|
|
1254
|
+
`details` has, per agent, its `model` reference (`provider/model-id`), its local function `tools`, the names of its `mcpServers` (never their urls, commands, headers or env), `maxTurns`, `localDevices`, `codemode` and `decisionModel` (`null` when unset).
|
|
1255
|
+
|
|
1256
|
+
The `agent` events are pi agent events made small for the wire. `message_update` carries only the delta (`event: { type, contentIndex, delta }`), `message_start` / `message_end` only the `role`, and `tool_execution_end` carries `toolCallId`, `toolName`, `isError` and `result: { text, images, truncated }`: the text blocks of the tool result joined by newlines (`null` when there are none), cut after 4,000 characters with a trailing `…` (then `truncated` is `true`), and the number of images. Image data is never streamed; the transcript has the full result. `tool_emit` (a local function's `context.emit` progress) carries `toolCallId`, `toolName`, `event` and `data`: the calls of one turn run in parallel, so the call id tells two calls of the same tool apart.
|
|
1257
|
+
|
|
1258
|
+
Auth mirrors the functions surface, not the open inference endpoints: a bound
|
|
1259
|
+
server requires a workspace JWT; an **unbound server rejects remote calls**
|
|
1260
|
+
unless `allow_unauthenticated = true`. The server also writes an ephemeral
|
|
1261
|
+
internal token to `<sessions_dir>/../runtime-token` (mode 0600) at startup —
|
|
1262
|
+
same-host CLIs authenticate with it automatically, bound or not.
|
|
1263
|
+
|
|
1264
|
+
**Interactive CLI**:
|
|
1265
|
+
|
|
1266
|
+
```sh
|
|
1267
|
+
bricks-buttress agent -c config.toml # list agents
|
|
1268
|
+
bricks-buttress agent ops-assistant -c config.toml # chat (streams text, thinking, tool calls)
|
|
1269
|
+
bricks-buttress agent ops-assistant --sessions -c config.toml
|
|
1270
|
+
bricks-buttress agent ops-assistant --session <id> -c config.toml # continue
|
|
1271
|
+
bricks-buttress agent ops-assistant --fork <id> -c config.toml # fork, then continue the fork
|
|
1272
|
+
```
|
|
1273
|
+
|
|
1274
|
+
On a real terminal the chat runs a pi-tui interface (markdown answers, dim
|
|
1275
|
+
thinking, tool-call lines, Ctrl+C aborts the running turn); pipes/scripts —
|
|
1276
|
+
or `--plain` — get a line-based renderer with identical semantics.
|
|
1277
|
+
|
|
1278
|
+
Like function files, agent definitions are trusted input: an agent is only as
|
|
1279
|
+
safe as the functions and MCP servers you hand it.
|
|
1280
|
+
|
|
1281
|
+
|
|
853
1282
|
## Session State Cache
|
|
854
1283
|
|
|
855
1284
|
The server supports session state caching for ggml-llm generators, which saves KV cache state to disk after completions. This enables:
|
|
@@ -14,6 +14,7 @@ needed. On first start the server scaffolds `buttress-functions.d.ts` and a
|
|
|
14
14
|
| `simple-rag.ts` | Token chunking, embeddings, `sqlite3` + `sqlite-vec` retrieval, then completion | a chat LLM + a GGML embedding `[[generators]]` entry (below) |
|
|
15
15
|
| `transcribe-media.ts` | `context.spawn` (ffmpeg), the scratch dir, SSE progress, STT | `ffmpeg` on PATH + an STT `[[generators]]` entry |
|
|
16
16
|
| `text-to-speech.ts` | TTS (`context.buttress.synthesize`) + downloadable output (`context.fileUrl`) | an `onnx-tts` `[[generators]]` entry |
|
|
17
|
+
| `classify-ticket.ts` | Typed decisions (`context.buttress.decide`): a choice, a score and yes/no flags with calibrated probabilities | a `ggml-decision` `[[generators]]` entry |
|
|
17
18
|
| `bank-note.ts` | Reading/writing the workspace's remote Data Bank (`context.bank`) | `bricks buttress bank-key` run on this host |
|
|
18
19
|
| `bank-watch-daemon.ts` | A daemon (`meta.daemon = true`): `context.bank.subscribe`, `context.setInterval`, `context.onEvent` | `bricks buttress bank-key` run on this host |
|
|
19
20
|
| `_auth.ts` | Custom auth: keep workspace tokens working, add static API keys | see the file header |
|