@octocodeai/freellama-mcp-server 0.7.0 → 0.10.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/CONFIGURATION.md +11 -3
- package/DEVELOPING.md +6 -6
- package/README.md +267 -403
- package/dist/index.js +7186 -1601
- package/dist/research/bash-agent.js +1504 -71
- package/dist/research/coder-agent.js +4677 -0
- package/dist/research/github-agent.js +2932 -0
- package/dist/research/octocode-agent.js +1416 -116
- package/docs/ARCHITECTURE.md +101 -45
- package/docs/BUILD_AND_NOTES.md +31 -0
- package/docs/CLI.md +76 -27
- package/docs/COMPARISON.md +40 -0
- package/docs/CPU_GPU_ROUTING.md +127 -25
- package/docs/DEFERRED_JOBS.md +79 -0
- package/docs/FEATURES.md +303 -0
- package/docs/LOCAL_MODELS.md +190 -0
- package/docs/MODEL_FORMS_AND_RESULTS.md +55 -0
- package/docs/MODEL_INTROSPECTION.md +128 -0
- package/docs/MODEL_SELECTION.md +75 -23
- package/docs/MONITORING.md +115 -33
- package/docs/PRODUCTION.md +74 -11
- package/docs/README.md +11 -6
- package/docs/SCOPES_AND_WARMING.md +55 -7
- package/docs/TESTING.md +24 -2
- package/docs/TUNING.md +86 -0
- package/docs/cookbooks/README.md +22 -0
- package/docs/cookbooks/batch-text.md +19 -0
- package/docs/cookbooks/classify.md +25 -0
- package/docs/cookbooks/coding.md +17 -0
- package/docs/cookbooks/duplicates.md +17 -0
- package/docs/cookbooks/find-code.md +21 -0
- package/docs/cookbooks/image-recognition.md +15 -0
- package/docs/cookbooks/image-search.md +13 -0
- package/docs/cookbooks/jobs.md +60 -0
- package/docs/cookbooks/memory-and-caching.md +31 -0
- package/docs/cookbooks/ocr.md +17 -0
- package/docs/cookbooks/research-agent.md +27 -0
- package/docs/cookbooks/speech-to-text.md +19 -0
- package/docs/cookbooks/sweep.md +66 -0
- package/docs/cookbooks/text-to-speech.md +15 -0
- package/docs/octocode/GITHUB.md +30 -0
- package/docs/octocode/LOCAL.md +29 -0
- package/native/index.d.ts +0 -11
- package/native/index.js +18 -34
- package/package.json +6 -9
package/docs/CPU_GPU_ROUTING.md
CHANGED
|
@@ -27,8 +27,8 @@ machine-specific:
|
|
|
27
27
|
| CPU-only | CPU | A second process can isolate helpers, but it does not create a second accelerator | Host RAM and measured overlap |
|
|
28
28
|
|
|
29
29
|
`doctor` reports `machine.memory_bytes` on macOS, Linux, and Windows. It reports
|
|
30
|
-
`machine.
|
|
31
|
-
VRAM estimate on discrete-GPU hosts. `machine.gpu` names the accelerator when the OS reports it
|
|
30
|
+
`machine.memory_kind` as `unified` only when host and accelerator share one memory pool; host RAM
|
|
31
|
+
is not a VRAM estimate on discrete-GPU hosts. `machine.gpu` names the accelerator when the OS reports it
|
|
32
32
|
(Apple model and core count, or NVIDIA name and memory on Linux), and `performance_cpus` /
|
|
33
33
|
`efficiency_cpus` split hybrid CPUs on macOS. These are identity facts, not placement proof. Model-library fit is therefore a host-memory preflight, and
|
|
34
34
|
actual accelerator placement must come from Ollama's resident-runner report.
|
|
@@ -68,7 +68,18 @@ OLLAMA_HOST=127.0.0.1:11434 OLLAMA_NO_CLOUD=1 \
|
|
|
68
68
|
OLLAMA_MAX_LOADED_MODELS=1 OLLAMA_NUM_PARALLEL=1 ollama serve
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
Or let `serve` start and own the CPU process: `--spawn-cpu-backend` runs `ollama serve` on
|
|
72
|
+
`127.0.0.1:11436` (`--cpu-backend-port`, `--ollama-binary`) with `OLLAMA_NO_CLOUD=1`,
|
|
73
|
+
`OLLAMA_LLM_LIBRARY=cpu`, `OLLAMA_NUM_PARALLEL=1` and every GPU runtime hidden
|
|
74
|
+
(`CUDA_VISIBLE_DEVICES`, `ROCR_VISIBLE_DEVICES`, `HIP_VISIBLE_DEVICES`, `GGML_VK_VISIBLE_DEVICES` set
|
|
75
|
+
to `-1`), waits until it answers, logs it to `cpu-ollama.log` in the FreeLlama home, and stops it
|
|
76
|
+
when `serve` exits. A port that already answers is refused rather than adopted:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npx @octocodeai/freellama serve --spawn-cpu-backend --cpu-model nomic-embed-text:v1.5
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
To run the second process yourself, start it on another loopback port:
|
|
72
83
|
|
|
73
84
|
```bash
|
|
74
85
|
OLLAMA_HOST=127.0.0.1:11436 OLLAMA_NO_CLOUD=1 \
|
|
@@ -205,7 +216,7 @@ curl --silent http://127.0.0.1:11435/_freellama/v1/tasks \
|
|
|
205
216
|
}' | jq '{execution, admission, route}'
|
|
206
217
|
```
|
|
207
218
|
|
|
208
|
-
The `execution` object separates the configured `backend`,
|
|
219
|
+
The `execution` object separates the configured `backend`, the `requested_processor`, and
|
|
209
220
|
post-run `observation`. The source is Ollama `/api/ps`; direct inspection remains useful:
|
|
210
221
|
|
|
211
222
|
```bash
|
|
@@ -221,9 +232,12 @@ samples are reported but excluded from adaptive feedback.
|
|
|
221
232
|
|
|
222
233
|
## Understand concurrency
|
|
223
234
|
|
|
224
|
-
Each backend has its own weighted admission pool and its own
|
|
225
|
-
|
|
226
|
-
GPU burst cannot spend the CPU helper's permit:
|
|
235
|
+
Each backend has its own weighted admission pool and its own load lock (see
|
|
236
|
+
[Load lock and use lease](#load-lock-and-use-lease)). CPU and GPU model loads therefore do not block
|
|
237
|
+
each other, and a GPU burst cannot spend the CPU helper's permit:
|
|
238
|
+
|
|
239
|
+
Within a priority class, each pool prefers a waiter whose model is already running on its backend
|
|
240
|
+
(or was admitted there last), within bounded skips; see [the admission design](ARCHITECTURE.md#managed-task-execution).
|
|
227
241
|
|
|
228
242
|
Both pools also share local host-pressure sampling and forecast-memory reservations. Independent
|
|
229
243
|
queues do not imply independent RAM on a unified-memory machine. A request can wait for available
|
|
@@ -238,10 +252,10 @@ flowchart TD
|
|
|
238
252
|
B -->|"CPU"| CA["CPU admission pool<br/>default 1 unit"]
|
|
239
253
|
GA --> GL{"Resident?"}
|
|
240
254
|
CA --> CL{"Resident?"}
|
|
241
|
-
GL -->|"Yes"| GR["
|
|
242
|
-
GL -->|"No"| GX["GPU
|
|
243
|
-
CL -->|"Yes"| CR["
|
|
244
|
-
CL -->|"No"| CX["CPU
|
|
255
|
+
GL -->|"Yes"| GR["Use lease only"]
|
|
256
|
+
GL -->|"No"| GX["GPU load lock until the runner is observed"]
|
|
257
|
+
CL -->|"Yes"| CR["Use lease only"]
|
|
258
|
+
CL -->|"No"| CX["CPU load lock until the runner is observed"]
|
|
245
259
|
GR --> GO["Primary Ollama"]
|
|
246
260
|
GX --> GO
|
|
247
261
|
CR --> CO["Secondary Ollama"]
|
|
@@ -260,16 +274,92 @@ the entire pool's ceiling. Default vision weight four can serialize two vision r
|
|
|
260
274
|
four-unit budget even when Ollama has two slots. A lower weight requires a qualified workload;
|
|
261
275
|
it does not add engine slots or relax memory checks. See
|
|
262
276
|
[monitoring controls](MONITORING.md#adaptive-concurrency).
|
|
263
|
-
Raw generation requests
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
277
|
+
Raw generation requests follow the same rules on the primary backend. A raw request for a runner
|
|
278
|
+
Ollama already holds at a matching digest and context takes no lock. A raw load takes the load lock
|
|
279
|
+
and receives 503 (`proxy busy: another model load is active`) if it stays busy past the raw queue
|
|
280
|
+
wait. Metadata GET/HEAD, `/api/show`, model-store calls (pull, push, copy, delete), and empty
|
|
281
|
+
`keep_alive:0` unloads remain available. Raw and managed work share the lock, the use leases and the
|
|
282
|
+
memory forecast, so a raw load and a managed load on one backend never overlap.
|
|
283
|
+
|
|
284
|
+
### Load lock and use lease
|
|
285
|
+
|
|
286
|
+
Two different things protect a model. Its use lease lasts through generation; the backend load
|
|
287
|
+
lock ends as soon as a compatible runner is observed, including before a nonstream response returns
|
|
288
|
+
its headers.
|
|
289
|
+
|
|
290
|
+
| | Held by | Held for | Blocks |
|
|
291
|
+
|---|---|---|---|
|
|
292
|
+
| **Use lease** (per model, per backend) | every managed task and raw generation for that model | the whole request, until the response ends or the caller disconnects | eviction and unload of that model. Tasks arriving for a model being unloaded wait for the unload |
|
|
293
|
+
| **Load lock** (per backend) | a request that loads or reloads a runner, and a managed explicit unload | only until `/api/ps` shows the runner (or the request ends or is cancelled); managed unloads retain it through bounded confirmation | other loads and managed explicit unloads on the same backend |
|
|
294
|
+
|
|
295
|
+
The rules that follow:
|
|
296
|
+
|
|
297
|
+
- A request whose runner is already resident at the expected digest and context (`resident_shared`)
|
|
298
|
+
takes no load lock. It waits for no other model's load, and its lease keeps its runner from being
|
|
299
|
+
evicted while it decodes.
|
|
300
|
+
- A request for a model that is not resident (`nonresident_transition_exclusive`) takes the load
|
|
301
|
+
lock, reads `/api/ps` again, and shares the runner if a same-model load just finished. Otherwise
|
|
302
|
+
it holds the lock until the runner appears, then releases it and keeps decoding under its lease.
|
|
303
|
+
Same-model arrivals queue behind that load and then share the loaded runner instead of loading
|
|
304
|
+
it again. A cancelled loader releases the lock at once.
|
|
305
|
+
- A load that needs memory held by a busy model waits. Eviction only ever takes models with no
|
|
306
|
+
lease, and each time any model's use ends the waiting load plans its eviction again, so it
|
|
307
|
+
proceeds as soon as that model is idle and never earlier.
|
|
308
|
+
- Managed explicit unloads take the model's lease first and then the load lock. Raw explicit
|
|
309
|
+
unloads and automatic idle eviction use the per-model lifecycle claim without the backend load
|
|
310
|
+
lock, so they can free an idle runner while another model is preparing a load. They still wait
|
|
311
|
+
for that model's users and retain ownership through bounded unload observation.
|
|
312
|
+
- Observing runner readiness wakes resource waiters to re-size their demand. A same-model follower
|
|
313
|
+
waiting for a cold-load forecast can then share the runner while its first request still decodes.
|
|
314
|
+
|
|
315
|
+
A cold task reads `/api/ps` six times in all: discovery, the receipt's capacity preview, the
|
|
316
|
+
dispatch-time forecast, the observation before the lock, the observation after winning it, and the
|
|
317
|
+
placement observation after the run. A resident task skips the post-lock read.
|
|
318
|
+
|
|
319
|
+
### Degraded runner identity
|
|
320
|
+
|
|
321
|
+
Reusing a runner without the load lock requires an observed identity. `/api/ps` must report the
|
|
322
|
+
runner's digest and it must equal the catalog digest. The reported `context_length` must equal the
|
|
323
|
+
requested `num_ctx`; when `/api/ps` omits it, the runner is shared only if the request sends no
|
|
324
|
+
`num_ctx` (Ollama's default applies, so nothing explicit can contradict it). A missing digest is never
|
|
325
|
+
compatible: the runner could be any build of the tag, so the task takes the load lock instead.
|
|
326
|
+
|
|
327
|
+
The reason is in the task receipt at `admission.reason` (copied from `execution.runner_reuse.reason`):
|
|
328
|
+
`resident_identity_unobserved`, `resident_digest_changed`, `resident_context_mismatch`,
|
|
329
|
+
`resident_context_unobserved`, `unverified_load_options`, `resident_placement_mismatch`,
|
|
330
|
+
`resident_context_unobserved_default_context_assumed` (shared, with the context assumed), or `null`
|
|
331
|
+
for a plain match or when no runner is resident. `execution.runner_reuse.status` is `compatible`, `incompatible_or_unverified`,
|
|
332
|
+
`absent` or `unavailable`.
|
|
333
|
+
|
|
334
|
+
### GPU placement rule
|
|
335
|
+
|
|
336
|
+
One rule decides whether a resident runner satisfies a request's placement, for managed and raw work
|
|
337
|
+
alike (`PlacementRule`). Placement is judged from the same `/api/ps` entry's `size_vram`:
|
|
338
|
+
|
|
339
|
+
| Rule | When | A runner satisfies it when |
|
|
340
|
+
|---|---|---|
|
|
341
|
+
| `any` | no placement evidence required (the default, and all raw passthrough) | always |
|
|
342
|
+
| `cpu_only` | the request sets `num_gpu:0` | `size_vram` is `0` |
|
|
343
|
+
| `gpu_resident` | `minPlacementEvidence: "observed"` on a GPU-assigned task | `size_vram` is above `0` |
|
|
344
|
+
|
|
345
|
+
`execution.runner_reuse.placement_rule` reports the rule applied. A runner that fails it is
|
|
346
|
+
`resident_placement_mismatch` and the task takes the load lock and reloads.
|
|
347
|
+
|
|
348
|
+
### Raw generation whose allocation cannot be forecast
|
|
349
|
+
|
|
350
|
+
A raw generation is admitted only with a memory forecast. When the model's metadata cannot be read
|
|
351
|
+
in time, or the request body names a model the proxy cannot resolve (a multipart body, say) and
|
|
352
|
+
could still load one, nothing could size or serialize that load. It is refused with HTTP 503,
|
|
353
|
+
`Retry-After: 1` and `code: "model_forecast_unavailable"` instead of being admitted with a zero-byte
|
|
354
|
+
reservation. A well-formed JSON body that names no usable model, or a model that is not installed,
|
|
355
|
+
reaches Ollama's own validation and gets its 4xx.
|
|
267
356
|
|
|
268
357
|
## Understand automatic feedback
|
|
269
358
|
|
|
270
359
|
With `executionPreference: "auto"`, unpinned `fastest` and `balanced` work can select a backend from
|
|
271
|
-
comparable observed warm
|
|
272
|
-
context
|
|
360
|
+
comparable observed warm execution profiles. Learned hints require two eligible backends, a verified
|
|
361
|
+
effective context and known execution controls. Automatic context preparation is shared with execution,
|
|
362
|
+
including Ollama's known default where suitable. A decision-only preview cannot supply
|
|
273
363
|
that payload projection and uses capacity and policy signals. The loop is bounded:
|
|
274
364
|
|
|
275
365
|
```mermaid
|
|
@@ -283,19 +373,31 @@ flowchart LR
|
|
|
283
373
|
P --> R["Return reason + placement receipt"]
|
|
284
374
|
```
|
|
285
375
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
376
|
+
When both candidates have qualified phase history and idle compatible runners, routing compares
|
|
377
|
+
estimated setup + prefill + decode time. It requires more than a 10% predicted advantage and takes
|
|
378
|
+
no speculative prefix-cache credit. Cached-token counters are required to learn generation prefill
|
|
379
|
+
rates; older Ollama responses without those counters retain the existing normalized-rate fallback.
|
|
380
|
+
Busy queues and cold-load costs remain unknown rather than becoming zero.
|
|
381
|
+
|
|
382
|
+
The fallback compares generation decode nanoseconds per output token or embedding nanoseconds per
|
|
383
|
+
input token. Each task/backend retains up to 32 exact profiles with 32 observations each; switching
|
|
384
|
+
models does not erase another model's history. Identity includes installed/resident digest, rate
|
|
385
|
+
metric kind, observed effective context, effective controls,
|
|
289
386
|
endpoint-attributed process settings, and serial admission class. Unknown or changed evidence,
|
|
290
387
|
cold transitions, and overlapping managed work supply no speed sample. A difference
|
|
291
388
|
of 10% or less is treated as noise. Feedback never overrides an explicit model, session affinity,
|
|
292
|
-
capability or policy guards, or the `quality` objective.
|
|
293
|
-
|
|
294
|
-
|
|
389
|
+
capability or policy guards, or the `quality` objective. A measured advantage also never forces a
|
|
390
|
+
cold load: when the faster backend's model is not loaded but the other backend's eligible model is,
|
|
391
|
+
the loaded one runs (`resident_backend_avoids_cold_load`); warm the faster model to switch. The CLI
|
|
392
|
+
keeps the bounded, versioned, prompt-free snapshot in the shared database by default; `--ephemeral`
|
|
393
|
+
opts into reset-on-restart behavior. Aggregate duration counters are diagnostic only and do not
|
|
394
|
+
qualify learned hints; a snapshot of another schema version is refused. Health's
|
|
295
395
|
`decision_ready` describes the stored profile window; the current request must independently
|
|
296
396
|
match it before routing can use its speed. `GET /_freellama/v1/health` exposes profile identity,
|
|
297
|
-
profile sample counts, persistence status, and per-backend admission capacity.
|
|
298
|
-
|
|
397
|
+
profile sample counts, persistence status, and per-backend admission capacity. Task receipts include
|
|
398
|
+
`execution.completion_planning` with candidate estimates and fallback explanations. These are snapshots,
|
|
399
|
+
not capacity reservations or measurements of saved time. Physically verified
|
|
400
|
+
warm completions still contribute aggregate duration diagnostics even when their interval cannot
|
|
299
401
|
qualify a speed-learning sample.
|
|
300
402
|
|
|
301
403
|
Configured processor intent, verified placement, and speed-learning eligibility remain separate
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Deferred jobs and recovery
|
|
2
|
+
|
|
3
|
+
FreeLlama's deferred jobs keep one accepted task identity through polling, cancellation and result retrieval. A missing result or an uncertain outcome never authorizes automatic inference replay. Ollama still owns execution and physical cancellation; these records coordinate the FreeLlama worker.
|
|
4
|
+
|
|
5
|
+
## Enable durable receipts
|
|
6
|
+
|
|
7
|
+
Persistence is disabled by default. Set `FREELLAMA_DURABLE_JOBS=true` (or `1`) before starting the server with its file-backed SQLite store. Rust embedders can use `PlatformConfig::with_store(store).with_durable_jobs(true)`. Enabling it without a file-backed store fails server construction. `false` and `0` disable it; invalid environment values fail configuration.
|
|
8
|
+
|
|
9
|
+
The journal opens an independent connection to that database, using SQLite WAL with `synchronous=FULL`. The ordinary usage/learning connection keeps its existing synchronization setting. Acceptance is committed before a new worker is spawned. This uses SQLite's durability guarantees subject to the filesystem and hardware honoring synchronization; it is not a distributed execution lease.
|
|
10
|
+
|
|
11
|
+
The journal persists receipts, bounded results/errors, and keyed hashes of request identity and input (see [request hashes](#request-hashes)). It does not save executable requests for replay. Results and upstream errors may themselves contain sensitive user content. Database access and retention therefore need the same care as task output storage.
|
|
12
|
+
|
|
13
|
+
## Submit, inspect and cancel
|
|
14
|
+
|
|
15
|
+
`POST /_freellama/v1/tasks` with `defer:true` returns `deferred:true`, a `job` containing its `id`, and `retention` limits. Use `GET /_freellama/v1/jobs/{id}` for its result and `GET /_freellama/v1/jobs` for a metadata-only list. The list strips result/error payloads in SQLite before loading recovered records into the control plane.
|
|
16
|
+
|
|
17
|
+
An embedding task may carry `nearest` (`{query?, top?, min_score?, labels?}`, with `return_embeddings` to keep the vectors). The core embeds the items (the query first, in slices of 32 pinned to one verified runner identity), ranks them by cosine similarity and stores `nearest: {mode, matches|pairs}` in place of vectors, so a deferred job's result is ranked and small. The whole input is one job and must fit the 16 MiB job input limit; without a query at most 4,000 items are compared pairwise. A malformed `nearest` is refused at submission. Through MCP, `run_task {defer:true, tasks:[...]}` submits every item as its own job and returns `{deferred:true, jobs:[{id, job}]}`; the HTTP `/tasks/batch` endpoint still refuses deferred items.
|
|
18
|
+
|
|
19
|
+
`POST /_freellama/v1/jobs/{id}/cancel` requests cancellation and waits for the attached worker to settle and release local permits. It also waits for pending terminal persistence. A result already committed by the execution path retains its completed status. Acknowledged local cancellation is not proof that Ollama physically stopped accepted work.
|
|
20
|
+
|
|
21
|
+
`DELETE /_freellama/v1/jobs/{id}` first settles locally owned work, then hides the record and removes its retained payload. With persistence enabled, its idempotency tombstone remains until expiry. An unattached worker with an unknown outcome cannot be removed early. A failed terminal database commit also prevents removal of its still-unfinished durable record.
|
|
22
|
+
|
|
23
|
+
MCP task cancellation and managed research use addressable jobs regardless of whether the caller requested progress notifications. The MCP wrapper retains accepted job IDs after polling, cancellation or result errors; it does not replay them synchronously. Managed research and MCP task calls reject definitive pre-execution job refusals with `cancellable_task_unavailable`, `execution_outcome:"not_started"` and `retry_safe:true`. They do not switch to unaddressable synchronous execution.
|
|
24
|
+
|
|
25
|
+
`cancellation_state:"unknown"` means the acknowledgement is unknown; a request may have reached the server even if its response was lost. `cancellation_requested:true` records the local attempt, not confirmed delivery or physical stop. Native MCP polling currently has no abort signal: cancellation dispatch starts immediately, but an in-flight job GET can delay the caller's error until that GET returns or its control timeout expires (30 seconds by default, configurable with `FREELLAMA_CONTROL_TIMEOUT_SECONDS`). The five-second cancellation-report cap does not interrupt that pending native poll.
|
|
26
|
+
|
|
27
|
+
## Idempotency
|
|
28
|
+
|
|
29
|
+
With persistence enabled, the HTTP task request may include `idempotency_key`, containing 1–128 UTF-8 bytes. It requires `defer:true`; synchronous requests and servers without durable jobs reject the field. The current MCP tool schema does not add an idempotency-key argument.
|
|
30
|
+
|
|
31
|
+
Keys are hashed (HMAC-SHA256, see [request hashes](#request-hashes)) with authenticated caller identity and scoped to a server topology: listen address, primary endpoint, CPU endpoint and configured CPU model list. Keep that configuration and caller identity stable when recovering jobs. Task input is serialized and hashed after removing the key, together with the set of explicitly supplied fields. Retries must preserve both values and field presence: omission can inherit scope defaults while an explicit default overrides them. Reusing a key with changed input returns `task_job_idempotency_conflict`.
|
|
32
|
+
|
|
33
|
+
A repeated key returns the original job, including a retained terminal result, before checking local registry capacity. Claiming the key and reserving its durable record is one transaction. Concurrent requests cannot both claim that key. A new durable claim rejected by local capacity is recorded as failed with `execution_outcome:"not_started"` and `retry_safe:true`; repeating the key returns that original refusal instead of starting work.
|
|
34
|
+
|
|
35
|
+
After restart, only recorded terminal outcomes are recoverable as settled results. An unfinished record becomes a read-time `failed` receipt with reason `execution_outcome_unknown`, error code `task_job_outcome_unknown`, its original `job_id`, and `retry_safe:false`. It may represent an interrupted worker, a worker still attached to another server, or a submission interrupted around acceptance. No worker is resumed or recreated. If the original worker later commits a terminal record, subsequent retrieval can observe it.
|
|
36
|
+
|
|
37
|
+
Idempotency applies only while the record is retained. After expiry, a key can start new work. Changing topology, caller identity or database also changes the deduplication boundary. Use one coordinating FreeLlama authority per topology; durable receipts do not coordinate model resources across servers or provide exactly-once execution.
|
|
38
|
+
|
|
39
|
+
## Request hashes
|
|
40
|
+
|
|
41
|
+
The request identity (caller identity plus idempotency key) and the serialized task input are stored only as HMAC-SHA256 digests, domain-separated for the two purposes. The key is a 32-byte per-installation secret in a file beside the database, `<database file name>.job-key`, never in the `task_jobs` table. Someone who can read only the database cannot confirm a guessed key or prompt by hashing it. Someone who can read the secret file can, so give it the permissions of the database itself and back both up together.
|
|
42
|
+
|
|
43
|
+
The file is created on first durable use with mode `0600` on Unix. The complete key is written under a private temporary name and hard-linked into place, so concurrent starts never replace one another's key. A file with looser permissions is tightened to `0600` when read. A file that is not exactly 32 bytes fails server construction instead of being replaced, because replacing it would silently end deduplication for every retained key.
|
|
44
|
+
|
|
45
|
+
Idempotency survives restart as long as the same secret file sits beside the same database. Deleting or replacing the file, or moving the database without it, changes every digest: retained keys stop matching and a retry starts new work. There is no recognition of rows written under earlier unkeyed hashes; those rows do not match and expire normally. The server-topology namespace remains a plain SHA-256 of non-secret configuration. It scopes keys and is not a secret.
|
|
46
|
+
|
|
47
|
+
## Persistence states and retention
|
|
48
|
+
|
|
49
|
+
Execution status and storage status are separate. Each local receipt exposes `persistence.state` and `persistence.stage`:
|
|
50
|
+
|
|
51
|
+
| State | Meaning |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `disabled` | Only process-local retention is enabled |
|
|
54
|
+
| `confirmed`, stage `acceptance` | The original queued record committed; this does not confirm an eventual result |
|
|
55
|
+
| `pending`, stage `terminal` | The local worker settled, but its terminal database transaction has not finished |
|
|
56
|
+
| `confirmed`, stage `terminal` | The terminal transaction committed; normal payload eviction can still apply |
|
|
57
|
+
| `failed`, stage `terminal` | The local result/error is available, but its commit failed; `persistence_error` gives the failure |
|
|
58
|
+
|
|
59
|
+
Consumers requiring restart recovery must check terminal persistence confirmation, not only `status:"completed"`. A local result can be returned while persistence is pending. The database retains the original acceptance if terminal persistence fails, so restart recovery reports an unknown outcome rather than inventing completion.
|
|
60
|
+
|
|
61
|
+
| Limit | Contract |
|
|
62
|
+
|---|---|
|
|
63
|
+
| Local handles | 64 total; settled handles may be evicted to admit new work |
|
|
64
|
+
| Durable records | 1,024 across the entire database, including other topology namespaces and removed tombstones |
|
|
65
|
+
| Durable receipt budget | 32 MiB of serialized receipts across that database |
|
|
66
|
+
| Durable metadata | 16 KiB per accepted/terminal receipt excluding terminal result/error payloads |
|
|
67
|
+
| Individual result or error | 2 MiB of encoded JSON |
|
|
68
|
+
| Job input | 16 MiB of encoded JSON per job and 64 MiB across queued and running jobs (`task_jobs_input_budget`, retry after 5 s); executable input is not retained in the journal |
|
|
69
|
+
| Terminal retention | 600 seconds after terminal commit; local retention starts when the worker settles |
|
|
70
|
+
| Unfinished retention | Accepted timeout plus 600 seconds from claim |
|
|
71
|
+
| Database workers | At most four blocking journal operations per process; canceled awaiters do not release a running operation's permit early |
|
|
72
|
+
|
|
73
|
+
When the receipt budget is exceeded, the journal removes older terminal payloads while preserving job identity and its unexpired key. Such a receipt has `status:"failed"`, reason `retained_payload_unavailable`, `original_status`, and error `task_job_result_evicted` with `retry_safe:false` and `result_recoverable:false`. This describes unavailable retained output; `original_status` preserves whether execution completed, failed or was cancelled. Explicit removal uses `task_job_result_removed` on a repeated key. Neither operation starts a replacement inference. A still-retained local handle may continue to provide its in-memory result even if the database payload has been evicted.
|
|
74
|
+
|
|
75
|
+
Once all durable slots are occupied by unexpired keys, new claims fail; keys are not discarded to admit unrelated work. Expired rows are pruned on a later claim. These are logical serialized-data limits, not exact process-RSS or SQLite-file-size bounds. WAL, indexes, allocator overhead and reusable database pages add storage overhead; expiry and removal are not secure erasure.
|
|
76
|
+
|
|
77
|
+
## Validation
|
|
78
|
+
|
|
79
|
+
Focused tests exercise keyed hash stability across restart, per-installation secrets, secret-file permissions and refusal of a malformed secret, concurrent same-key claims, file-backed restart recovery, unknown outcomes, FULL-sync isolation, deduplication at local capacity, terminal result delivery, refused-work accounting, bounded payload eviction, hidden tombstones, slim listing, persistence failures and cancellation during database work. They use no live models. See [testing](TESTING.md) for the full project checks.
|
package/docs/FEATURES.md
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# FreeLlama features and how it works
|
|
2
|
+
|
|
3
|
+
Moved from the root README. For install and a first task, start with the [README](../README.md).
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
**Run your agent's bulk work on local models without overloading your machine.**
|
|
8
|
+
|
|
9
|
+
FreeLlama sits between an AI agent and [Ollama](https://ollama.com). For each task it picks an
|
|
10
|
+
installed model that fits, admits the task against live memory, CPU and heat signals, runs it on the
|
|
11
|
+
GPU or a CPU-only Ollama, and returns the result with a receipt of where and how it ran. Any Ollama
|
|
12
|
+
model works.
|
|
13
|
+
|
|
14
|
+
```mermaid
|
|
15
|
+
flowchart LR
|
|
16
|
+
Agent["Agent or CLI"] --> FL["FreeLlama: pick model, check memory, queue"]
|
|
17
|
+
FL -->|admitted| Ollama["Ollama: GPU or CPU"]
|
|
18
|
+
FL -->|held| Wait["Bounded wait or refusal with reason"]
|
|
19
|
+
Ollama --> Receipt["Result + routing, timing, placement receipt"]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Why FreeLlama
|
|
23
|
+
|
|
24
|
+
Coding agents spend paid tokens and context on work a local model can do: embedding files, reading
|
|
25
|
+
screenshots and scans, rewriting or summarizing many files. Calling Ollama directly, an agent cannot
|
|
26
|
+
tell:
|
|
27
|
+
|
|
28
|
+
- **Which model:** tag names do not show capabilities, context window or memory footprint.
|
|
29
|
+
- **Whether there is room:** a new load can push the machine into swap, several agents can pile onto
|
|
30
|
+
one GPU, and a busy or hot machine slows everything.
|
|
31
|
+
- **Whether it worked:** a model can repeat itself until the token cap, and nothing says whether the
|
|
32
|
+
task ran on the GPU or fell back to the CPU.
|
|
33
|
+
|
|
34
|
+
FreeLlama routes from installed metadata and observed behavior, admits against host signals, stops
|
|
35
|
+
runaway repetition, and returns a checkable receipt. The agent still decides what to offload and
|
|
36
|
+
verifies the answer. The name refers to less metered inference, not only Meta's Llama models.
|
|
37
|
+
|
|
38
|
+
## How it works
|
|
39
|
+
|
|
40
|
+
### What to offload
|
|
41
|
+
|
|
42
|
+
Offload bounded work whose result your agent can check; keep judgment in the agent.
|
|
43
|
+
|
|
44
|
+
```mermaid
|
|
45
|
+
flowchart TD
|
|
46
|
+
T{"Task for a local model?"}
|
|
47
|
+
T -->|"Embed, OCR/vision, bulk rewrite,<br/>summarize many files"| Fit["Offload"]
|
|
48
|
+
T -->|"Question about workspace files"| R["delegate_research"]
|
|
49
|
+
T -->|"Small lookup, high-stakes decision,<br/>final verification"| Keep["Keep in your agent"]
|
|
50
|
+
Fit --> N{"How many items?"}
|
|
51
|
+
N -->|"one"| RT["run_task"]
|
|
52
|
+
N -->|"many, independent"| B["run_task {tasks}"]
|
|
53
|
+
N -->|"long-running"| D["run_task {defer:true}<br/>then task_jobs"]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### The smart flow
|
|
57
|
+
|
|
58
|
+
Every managed task passes the same gates, each deciding from installed metadata, live host signals
|
|
59
|
+
and observed behavior, never from a model's name.
|
|
60
|
+
|
|
61
|
+
```mermaid
|
|
62
|
+
flowchart LR
|
|
63
|
+
T(["Task"]) --> R{"Route"}
|
|
64
|
+
R -->|"capabilities · context · policy<br/>· observed loops"| A{"Admit"}
|
|
65
|
+
A -->|"fair queue + host signals"| B{"Backend"}
|
|
66
|
+
B -->|"default"| GPU["Ollama on GPU"]
|
|
67
|
+
B -->|"operator-assigned helper"| CPU["Ollama on CPU"]
|
|
68
|
+
GPU --> X["Execute<br/>optional stop on repetition"]
|
|
69
|
+
CPU --> X
|
|
70
|
+
X --> Rc(["Result + receipt"])
|
|
71
|
+
A -->|"queue full or wait budget spent"| No(["Refused with reason<br/>and Retry-After"])
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
An explicit `model` always wins over routing.
|
|
75
|
+
|
|
76
|
+
## Features
|
|
77
|
+
|
|
78
|
+
Each feature is reachable from MCP, the CLI and the HTTP control API under `/_freellama/v1` unless a
|
|
79
|
+
cell says otherwise.
|
|
80
|
+
|
|
81
|
+
| Feature | What you get | MCP tool | CLI | HTTP |
|
|
82
|
+
|---|---|---|---|---|
|
|
83
|
+
| [Model selection](#model-selection) | The right installed model for the task, with a confidence level | `run_task {preview:true}`, `models` | `route`, `models` | `POST /routes`, `GET /models` |
|
|
84
|
+
| [Resource admission](#resource-admission) | Tasks run, wait or are refused with a reason | every `run_task` receipt, `doctor {view:"status"}` | `status`, `ready` | `GET /health` (liveness), `GET /ready` (can serve now), `GET /status` |
|
|
85
|
+
| [CPU and GPU backends](#cpu-and-gpu-backends) | Helper models on CPU next to GPU work, with proof of where each ran | `run_task {executionPreference}` | `serve --cpu-upstream --cpu-model` | `POST /tasks` |
|
|
86
|
+
| Decisions | Label, triage or yes/no-check items with a decision model: one forward pass, one line per answer | `run_task {task:"decision", state, questions}` | `task --task decision --questions` | `POST /tasks` |
|
|
87
|
+
| Ranked search and duplicates | Embed files or texts and get the closest items to a query, or the most similar pairs, as labels with scores instead of vectors; files are chunked by lines and labelled `path:Lstart-Lend` | `run_task {task:"embedding", files, nearest}` | `task --task embedding --input-file F --nearest QUERY` | — |
|
|
88
|
+
| Speech | Transcribe or translate a WAV, or speak text to a WAV file, on a local audio model | `audio` | — | Ollama `/v1/audio/*` through serve |
|
|
89
|
+
| Inputs by path | The server reads the files an agent names, so logs, screenshots and source files are never pasted into its output | `run_task {files}` | `task --input-file`, `--image` | — |
|
|
90
|
+
| [Loop control](#loop-control) | A repeating answer stops early with every token kept | `run_task {stopOnRepetition}` | `task --stop-on-repetition` | `stop_on_repetition` |
|
|
91
|
+
| [Machine awareness](#machine-awareness) | GPU, cores, memory, GPU load and heat read from the OS | `doctor` | `machine`, `doctor` | `GET /machine` |
|
|
92
|
+
| [Batches, jobs and history](#batches-jobs-and-history) | Fan-out, submit now and collect later, multi-turn chat without resending history | `run_task {tasks}` / `{defer}`, `task_jobs`, `scope`, `session` | `task --defer`, `jobs`, `scope`, `session` | `/task-batches`, `/jobs`, `/scopes`, `/sessions` |
|
|
93
|
+
| Warm-up and unload | Load a model ahead of use through the same memory checks; unload a resident one once its tasks finish (never loads first) | `ollama_manage {action:"warm"}`, `ollama_manage {action:"unload"}` | `warm`, `unload` | `POST /warm`, `POST /unload` |
|
|
94
|
+
| Model catalog and cookbooks | Tested picks per job with their evidence, what is installed and fits now, and a recipe for each | `models {view:"catalog"}`, `freellama://docs/cookbooks/<job>` | `catalog [--job JOB]`, [docs/cookbooks](cookbooks/README.md) | — |
|
|
95
|
+
| Find and install models | ollama.com and Hugging Face GGUF search, every tag with its size and memory fit, pulls only on approval | `models {view:"library"}`, `ollama_pull`, `ollama_delete` | MCP only | MCP only |
|
|
96
|
+
| Job mode and prompts | Name a job and FreeLlama runs its first catalog pick that is installed and fits, with its documented prompt, options and embedding prefixes; your fields win. MCP prompts give each job's call, and `saveAs` hands an answer to the next task as a file | `run_task {job, saveAs}`, MCP prompts, [cookbook](cookbooks/jobs.md) | MCP only | MCP only |
|
|
97
|
+
| Chunked sweeps | A local model checks a code tree, log or docs site chunk by chunk in the background, saves every result, re-checks flags with a stronger model and, against a baseline, reports only what changed | `sweep`, [cookbook](cookbooks/sweep.md) | MCP only | MCP only |
|
|
98
|
+
| [Grounded research](#grounded-research) | Read-only repository answers with file and line citations | `delegate_research` | MCP only | MCP only |
|
|
99
|
+
| [Small agent context](FEATURES.md#small-agent-context) | Compact results and paged details instead of raw dumps | every tool, `read_response` | JSON on stdout | JSON |
|
|
100
|
+
| [Observe and evaluate](#observe-and-evaluate) | Live status, usage, metrics, benchmarks, routing policy from evaluations | `doctor {view:"usage"}` | `status`, `usage`, `config`, `bench-all`, `policy from-eval` | `/status`, `/usage`, `/metrics`, `/config`, `/_freellama/ui` |
|
|
101
|
+
| Ollama passthrough | Any Ollama `/api/*` or `/v1/*` call, byte-preserving, with retries and a stream cap | — | `serve`, `proxy` | Ollama's own paths |
|
|
102
|
+
|
|
103
|
+
### Model selection
|
|
104
|
+
|
|
105
|
+
Routing filters installed models by task, capabilities (`tools`, `vision`, `embedding`, ...), context
|
|
106
|
+
window, routing policy and memory fit, and reports a confidence level. `minConfidence:"medium"`
|
|
107
|
+
refuses weakly evidenced routes, `objective:"quality"` requires a policy-backed model, and `model`
|
|
108
|
+
pins one. A bare name resolves as in Ollama: `name:latest`, or the only installed tag of that family.
|
|
109
|
+
|
|
110
|
+
Routing never ranks by a model's name or architecture and adds no model-specific request options. A
|
|
111
|
+
model that loops on a task kind (at least two loops in at most twice as many runs) ranks last for that
|
|
112
|
+
kind; `models` lists the evidence as `observed_loops`. See [model selection](MODEL_SELECTION.md).
|
|
113
|
+
|
|
114
|
+
### Resource admission
|
|
115
|
+
|
|
116
|
+
A task queues fairly, waits for budget units on its backend and for the host signals that apply to
|
|
117
|
+
it, then runs or is refused with `Retry-After`.
|
|
118
|
+
|
|
119
|
+
- **Budget.** Each Ollama decoding slot (`OLLAMA_NUM_PARALLEL`) is worth two units: a chat costs 2, a
|
|
120
|
+
vision task 4 (capped to the budget), an embedding 1 per four inputs. The GPU queue holds 16 tasks,
|
|
121
|
+
the CPU queue 8. A task waits at most 120 s, or `maxWaitSeconds`.
|
|
122
|
+
- **Fairness.** Priority classes get 3, 2 and 1 turns per round, FIFO within a class. After six
|
|
123
|
+
bypasses the oldest blocked task reserves capacity, so nothing starves. In a live run, 4
|
|
124
|
+
interactive tasks submitted last finished before the normal and background tasks queued ahead.
|
|
125
|
+
- **Fast refusals.** A full queue answers 429 at once, an expired wait 503. Both carry `Retry-After`
|
|
126
|
+
from the tasks ahead, how many of that cost run at once, and recent service time.
|
|
127
|
+
|
|
128
|
+
Each host signal holds only the work it affects:
|
|
129
|
+
|
|
130
|
+
| Signal | Holds |
|
|
131
|
+
|---|---|
|
|
132
|
+
| OS memory pressure (macOS `kern.memorystatus_vm_pressure_level`, Linux PSI) at warning | new model loads; at critical, everything |
|
|
133
|
+
| Low available RAM | judged per load: a new load runs if it leaves its own reserve, else waits; a loaded model keeps serving |
|
|
134
|
+
| Active swapping | new loads of 1 GiB or more, or of unknown size; a loaded model keeps serving, raw passthrough included |
|
|
135
|
+
|
|
136
|
+
A load that waits for memory is told who holds it: the 503, the progress message and the route
|
|
137
|
+
preview name the largest programs (`node: tsserver.js ×3 3.8 GB, rust-analyzer ×2 2.3 GB`).
|
|
138
|
+
| CPU load | CPU-bound work only, never a GPU load or GPU inference |
|
|
139
|
+
| Thermal throttling (macOS) | CPU-bound work and new loads |
|
|
140
|
+
|
|
141
|
+
A new load must leave a reserve free: 15% of RAM, at least 1 GiB, capped at the load's own size. If
|
|
142
|
+
it does not fit, idle models are unloaded first; if it still does not fit, it waits and is refused at
|
|
143
|
+
its deadline, on every OS. On macOS the available figure already includes reclaimable pages
|
|
144
|
+
(inactive, speculative, purgeable), so loading past it means compression and swap.
|
|
145
|
+
`serve --low-memory advisory` (or `FREELLAMA_LOW_MEMORY=advisory`) restores admit-with-warning.
|
|
146
|
+
While any model is loaded, `serve` also samples the host itself: under low memory, OS pressure or
|
|
147
|
+
swapping it unloads the cheapest idle models (audit trigger `pressure_sweep`), never pinned,
|
|
148
|
+
`keep_alive: -1` or busy ones. Because of the reserve, a load can wait while the OS still shows free
|
|
149
|
+
memory ([admission controls](CLI.md#bound-admission)). Total deadlines (`timeoutSeconds`) and
|
|
150
|
+
per-backend circuit breakers complete admission; see [monitoring and tuning](MONITORING.md).
|
|
151
|
+
|
|
152
|
+
### Model memory and switching
|
|
153
|
+
|
|
154
|
+
A loaded model answers at once, a cold one first pays its load time, and loading past what the
|
|
155
|
+
machine holds swaps and stalls everything. FreeLlama decides what stays loaded:
|
|
156
|
+
|
|
157
|
+
```mermaid
|
|
158
|
+
flowchart TD
|
|
159
|
+
Req(["Request names a model<br/>managed task or raw Ollama call"]) --> Res{"Loaded at a<br/>compatible context?"}
|
|
160
|
+
Res -->|yes| Serve(["Serve now<br/>memory holds never apply"])
|
|
161
|
+
Res -->|no| Size["Size the load<br/>Ollama's measured footprint, else file + KV + graph"]
|
|
162
|
+
Size --> Room{"Room needed?"}
|
|
163
|
+
Room -->|"GPU VRAM short"| Plan
|
|
164
|
+
Room -->|"over the model memory budget"| Plan
|
|
165
|
+
Room -->|"host memory low or swapping"| Plan
|
|
166
|
+
Room -->|"fits"| Load
|
|
167
|
+
Plan["Pick the cheapest idle set to unload<br/>reload time x (1 + recent uses) x weight<br/>never pinned, busy or the target"] --> Unload["Unload (keep_alive 0)<br/>wait until Ollama frees it"]
|
|
168
|
+
Unload --> Load["Load and serve"]
|
|
169
|
+
Load --> Audit["Audit: load, unload, cause, switch time<br/>flag a model loaded 3+ times in 10 min"]
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
- **Model memory budget.** Loaded models on one backend may hold at most `model_memory_budget`
|
|
173
|
+
together: 75% of RAM by default, `"24GiB"`, or `"off"`. Ollama checks only what is free at that
|
|
174
|
+
moment, so without a budget loads keep landing until macOS swaps. It applies where models share
|
|
175
|
+
host memory (Apple silicon, or a CPU backend).
|
|
176
|
+
- **What to unload.** The cheapest idle set that frees enough: measured reload time, scaled by recent
|
|
177
|
+
use and an optional per-model weight. Pinned models, models with queued or running work (raw
|
|
178
|
+
passthrough included) and the model being loaded are never chosen. Warms and preloads teach load
|
|
179
|
+
time but do not count as uses, so they cannot keep themselves resident.
|
|
180
|
+
- **No ping-pong between backends.** A measured speed advantage never forces a cold load while the
|
|
181
|
+
other backend has an eligible model loaded.
|
|
182
|
+
- **Audit.** `status` shows the budget in use, loads, unloads and evictions by cause, switch time per
|
|
183
|
+
model, and models swapped back and forth. Every MCP result carries `timing` (wall time, plus queue,
|
|
184
|
+
load, prompt and output time for a task).
|
|
185
|
+
- **Learned per model.** Each model revision's output needs (reasoning included), load time, memory
|
|
186
|
+
footprint and loops are measured from its runs and saved. FreeLlama sends no output cap of its own;
|
|
187
|
+
a model that ran out of room gets twice as much next time.
|
|
188
|
+
- **Preload.** `preload.startup` loads listed models at startup; with `messages` (such as a long
|
|
189
|
+
system prompt) it also evaluates them into the runner's prompt cache. Predictive preload counts
|
|
190
|
+
model-to-model switches and, once a successor is clear, loads it in the background, only when it
|
|
191
|
+
fits without unloading anything and the host is calm. Agents warm a prefix with
|
|
192
|
+
`ollama_manage {action:"warm", model, systemPrompt}`.
|
|
193
|
+
- **Shared state.** Measured sizes, load times, placement feedback, usage and the audit live in one
|
|
194
|
+
`SQLite` file, `~/.octocode/freellama/freellama.sqlite`, shared by every FreeLlama process on the
|
|
195
|
+
machine.
|
|
196
|
+
|
|
197
|
+
Measured switch times on an Apple M2 Pro (32 GB) through `serve`, model files in the OS file cache:
|
|
198
|
+
|
|
199
|
+
| Model | Loaded size | Load (switch) | Request when loaded |
|
|
200
|
+
|---|---:|---:|---:|
|
|
201
|
+
| `gpt-oss:20b` | 11.9 GiB | 5.1–9.7 s | 0.9 s (short answer) |
|
|
202
|
+
| `glm-ocr:latest` | 2.7 GiB | 1.5–2.0 s | 0.45 s (one image strip) |
|
|
203
|
+
| `strands-decider:2b-nvfp4` | 1.4 GiB | first call 1.2–1.4 s, load included | 3–60 ms (one question) |
|
|
204
|
+
| `nomic-embed-text:v1.5` | 0.3 GiB | 0.27–0.5 s | 70 ms (one input) |
|
|
205
|
+
|
|
206
|
+
Unloading takes milliseconds on Apple silicon. With a 14 GiB budget and all four in rotation, loading
|
|
207
|
+
glm-ocr unloaded gpt-oss for the budget, loading gpt-oss unloaded the helpers (for the budget, then
|
|
208
|
+
for low host memory), and the audit flagged all four as thrashing: raise the budget, pin the big
|
|
209
|
+
model, or give helpers their own backend.
|
|
210
|
+
|
|
211
|
+
### Keys, budgets and other engines
|
|
212
|
+
|
|
213
|
+
- **Client keys.** `clients.toml` in the home (or `--client-keys`) names one bearer token per agent
|
|
214
|
+
or tool, each with an optional `daily_tokens` budget and `admin` right. Usage counts output plus
|
|
215
|
+
prompt tokens not reused from a cache, per UTC day, for managed tasks and raw Ollama or OpenAI calls
|
|
216
|
+
(`task: "raw"`, from the counts each response reports). A spent budget refuses generation with 429
|
|
217
|
+
and `Retry-After` until the next UTC day; reads still work. Only admin keys may pull, push, copy,
|
|
218
|
+
create or delete models or reload the runtime file. The operator token is unmetered.
|
|
219
|
+
- **Other engines.** `upstreams.toml` (or `--upstreams`) adds OpenAI-compatible engines. A `/v1/...`
|
|
220
|
+
request for `lmstudio/qwen3-8b` goes to upstream `lmstudio` as `qwen3-8b`; a bare name goes to
|
|
221
|
+
Ollama when Ollama has it, else to the engine that lists it. `GET /v1/models` lists all. A local
|
|
222
|
+
engine waits out host holds as a loaded model would. The caller's FreeLlama token never leaves;
|
|
223
|
+
each engine gets its own key, sent only over HTTPS or to this machine.
|
|
224
|
+
|
|
225
|
+
```toml
|
|
226
|
+
# ~/.octocode/freellama/clients.toml
|
|
227
|
+
[[client]]
|
|
228
|
+
name = "octocode"
|
|
229
|
+
token_file = "keys/octocode.token" # freellama auth-token --out keys/octocode.token
|
|
230
|
+
daily_tokens = 2000000
|
|
231
|
+
|
|
232
|
+
# ~/.octocode/freellama/upstreams.toml
|
|
233
|
+
[[upstream]]
|
|
234
|
+
name = "lmstudio"
|
|
235
|
+
url = "http://127.0.0.1:1234/v1"
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### CPU and GPU backends
|
|
239
|
+
|
|
240
|
+
An optional second, CPU-only Ollama runs exact helper models, such as an embedder, so they never
|
|
241
|
+
compete with GPU work: `serve --spawn-cpu-backend --cpu-model TAG` starts it on loopback, waits for it
|
|
242
|
+
and stops it with `serve`, or point `--cpu-upstream` at one you run. Each backend has its own budget, queue and model-transition lock. The operator
|
|
243
|
+
assigns models; agents can only state an `executionPreference`. Ask for
|
|
244
|
+
`minPlacementEvidence:"observed"` when placement matters, and trust the receipt's observed placement
|
|
245
|
+
over the configuration. See [CPU/GPU routing](CPU_GPU_ROUTING.md).
|
|
246
|
+
|
|
247
|
+
### Loop control
|
|
248
|
+
|
|
249
|
+
Some models answer correctly, then repeat the answer until their output limit (a `num_predict` cap or
|
|
250
|
+
the end of their context). With the repetition stop, FreeLlama streams the answer, checks it every 16
|
|
251
|
+
tokens, and stops once the text has repeated twice over at least 96 characters; closing the
|
|
252
|
+
connection cancels generation in Ollama. It is on by default for free text with no output cap (JSON
|
|
253
|
+
and tool calls are left whole); `stopOnRepetition:false` (CLI `--stop-on-repetition false`) turns it
|
|
254
|
+
off, `true` turns it on for a capped task. Nothing is trimmed: `response` holds the full text and
|
|
255
|
+
`output_check` says where the first copy ends. A loop that ran to the limit without the stop is
|
|
256
|
+
flagged the same way, and routing records it either way.
|
|
257
|
+
|
|
258
|
+
### Machine awareness
|
|
259
|
+
|
|
260
|
+
`freellama machine`, `freellama status` and MCP `doctor` report what the OS says, without elevated
|
|
261
|
+
privileges:
|
|
262
|
+
|
|
263
|
+
- **Machine:** chip, logical CPUs, Apple silicon's performance/efficiency split, total memory and
|
|
264
|
+
whether it is unified, and the GPU (Apple model and cores; NVIDIA name and memory via `nvidia-smi`).
|
|
265
|
+
- **Live:** available memory, OS memory pressure, CPU load, GPU busy percent (Apple, NVIDIA) and Apple
|
|
266
|
+
GPU-driver memory. macOS heat is the thermal pressure level (`nominal` to `sleeping`, throttled from
|
|
267
|
+
`heavy`) plus the `pmset` CPU speed limit on Intel Macs; Linux and Windows report it as unknown.
|
|
268
|
+
- **Placement:** fully GPU, fully CPU or split, as Ollama reports each loaded model.
|
|
269
|
+
|
|
270
|
+
### Batches, jobs and history
|
|
271
|
+
|
|
272
|
+
| Need | Use |
|
|
273
|
+
|---|---|
|
|
274
|
+
| Many independent items | `run_task {tasks, maxParallelism}` with up to 64 `{id, independent:true, task}` items |
|
|
275
|
+
| Submit now, collect later | `run_task {defer:true}` returns `job.id`, then `task_jobs {action:"get", jobId}` |
|
|
276
|
+
| Multi-turn chat without resending history | `scope {action:"create"}`, then `run_task {scopeId, scopeRevision, prompt}` |
|
|
277
|
+
| Keep related calls on one model | `session {action:"create"}`, then `run_task {sessionId}` |
|
|
278
|
+
| Hot model before latency-sensitive work | `ollama_manage {action:"warm", model, contextTokens}` |
|
|
279
|
+
|
|
280
|
+
Scopes, sessions and deferred jobs live in memory and are lost when the service restarts. See
|
|
281
|
+
[scopes and warming](SCOPES_AND_WARMING.md).
|
|
282
|
+
|
|
283
|
+
### Grounded research
|
|
284
|
+
|
|
285
|
+
`delegate_research {question, workspacePath}` gives a local model bounded, read-only search and read
|
|
286
|
+
tools over an allowed workspace and returns an answer, file and line citations, and an independently
|
|
287
|
+
computed verdict. Discard `escalate` results and verify the citations on the rest.
|
|
288
|
+
|
|
289
|
+
### Small agent context
|
|
290
|
+
|
|
291
|
+
`run_task` and `delegate_research` return a compact view unless `receipt:"full"`; every tool saves its
|
|
292
|
+
whole result to a `response_file`, which `read_response` pages by section. Embedding vectors are
|
|
293
|
+
omitted unless `returnEmbeddings:true`. The optional `telemetry.externalEquivalent` estimates avoided
|
|
294
|
+
API cost from a rate card you configure. See [token economics](../packages/mcp/README.md#token-economics).
|
|
295
|
+
|
|
296
|
+
### Observe and evaluate
|
|
297
|
+
|
|
298
|
+
- **Live status:** queues, memory holds, loaded models, circuit breakers and usage per day and model,
|
|
299
|
+
from `doctor {view:"status"}`, `freellama status` or `/_freellama/ui`.
|
|
300
|
+
- **Evaluation:** `bench-all` benchmarks installed models; `policy from-eval` writes a routing policy
|
|
301
|
+
from quality-evaluation results. See [model selection](MODEL_SELECTION.md).
|
|
302
|
+
- **Ollama compatible:** `/api/*` and `/v1/*` reach Ollama byte-for-byte, streaming included;
|
|
303
|
+
FreeLlama never rewrites raw bodies. See [Ollama sidecar](ARCHITECTURE.md#ollama-sidecar).
|