@octocodeai/freellama-mcp-server 0.6.2 → 0.9.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.
Files changed (45) hide show
  1. package/CONFIGURATION.md +11 -3
  2. package/DEVELOPING.md +6 -6
  3. package/README.md +224 -402
  4. package/dist/index.js +11714 -4251
  5. package/dist/research/bash-agent.js +1495 -71
  6. package/dist/research/coder-agent.js +4668 -0
  7. package/dist/research/github-agent.js +2923 -0
  8. package/dist/research/octocode-agent.js +1407 -116
  9. package/docs/ARCHITECTURE.md +101 -45
  10. package/docs/BUILD_AND_NOTES.md +31 -0
  11. package/docs/CLI.md +76 -27
  12. package/docs/COMPARISON.md +40 -0
  13. package/docs/CPU_GPU_ROUTING.md +127 -25
  14. package/docs/DEFERRED_JOBS.md +79 -0
  15. package/docs/FEATURES.md +303 -0
  16. package/docs/LOCAL_MODELS.md +190 -0
  17. package/docs/MODEL_FORMS_AND_RESULTS.md +55 -0
  18. package/docs/MODEL_INTROSPECTION.md +128 -0
  19. package/docs/MODEL_SELECTION.md +74 -22
  20. package/docs/MONITORING.md +115 -33
  21. package/docs/PRODUCTION.md +76 -13
  22. package/docs/README.md +11 -6
  23. package/docs/SCOPES_AND_WARMING.md +55 -7
  24. package/docs/TESTING.md +24 -2
  25. package/docs/TUNING.md +86 -0
  26. package/docs/cookbooks/README.md +22 -0
  27. package/docs/cookbooks/batch-text.md +19 -0
  28. package/docs/cookbooks/classify.md +25 -0
  29. package/docs/cookbooks/coding.md +17 -0
  30. package/docs/cookbooks/duplicates.md +17 -0
  31. package/docs/cookbooks/find-code.md +21 -0
  32. package/docs/cookbooks/image-recognition.md +15 -0
  33. package/docs/cookbooks/image-search.md +13 -0
  34. package/docs/cookbooks/jobs.md +60 -0
  35. package/docs/cookbooks/memory-and-caching.md +31 -0
  36. package/docs/cookbooks/ocr.md +17 -0
  37. package/docs/cookbooks/research-agent.md +27 -0
  38. package/docs/cookbooks/speech-to-text.md +19 -0
  39. package/docs/cookbooks/sweep.md +66 -0
  40. package/docs/cookbooks/text-to-speech.md +15 -0
  41. package/docs/octocode/GITHUB.md +30 -0
  42. package/docs/octocode/LOCAL.md +29 -0
  43. package/native/index.d.ts +1 -11
  44. package/native/index.js +18 -34
  45. package/package.json +13 -15
@@ -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.unified_memory_bytes` only when unified memory is known; that compatibility field is not a
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
- Start a second process on another loopback port:
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`, requested compatibility `placement`, and
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 resident/shared and
225
- transition/exclusive lock. CPU and GPU model transitions therefore do not block each other, and a
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["GPU shared permit"]
242
- GL -->|"No"| GX["GPU exclusive transition"]
243
- CL -->|"Yes"| CR["CPU shared permit"]
244
- CL -->|"No"| CX["CPU exclusive transition"]
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 also require exclusive access to the primary backend's managed transition
264
- lock and receive 503 if it stays busy past the raw queue wait. Metadata GET/HEAD, `/api/show`,
265
- model-store calls (pull, push, copy, delete), and empty `keep_alive:0` unloads remain available.
266
- This prevents raw inference from overlapping a managed generation on that backend.
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 task duration. Learned hints require two eligible backends, explicit
272
- context, and a known execution payload-control projection. A decision-only preview cannot supply
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
- Generation compares decode nanoseconds per output token; embeddings compare total nanoseconds per
287
- input token. Each task/backend has one current profile window, reset when identity changes.
288
- Identity includes installed/resident digest, rate metric kind, explicit observed context, effective controls,
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. The CLI persists the bounded, versioned,
293
- prompt-free snapshot atomically by default; `--ephemeral-feedback` opts into reset-on-restart
294
- behavior. Legacy aggregate fields survive loading but do not qualify learned hints. Health's
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. Physically verified
298
- warm completions still contribute legacy duration diagnostics even when their interval cannot
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.
@@ -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
+ ![A brown cartoon llama stands on a pastel cloud background.](../assets/logo.jpg)
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).