@octocodeai/freellama-mcp-server 0.1.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/docs/CLI.md ADDED
@@ -0,0 +1,219 @@
1
+ # CLI reference
2
+
3
+ The `freellama` command exposes local diagnostics, deterministic model routing, managed task
4
+ execution, benchmarking, and the Ollama-compatible proxy. The front page covers the shortest path;
5
+ this page is the complete command map.
6
+
7
+ ## Choose a command
8
+
9
+ ```mermaid
10
+ flowchart TD
11
+ Q{"What do you need?"}
12
+ Q -->|"Prepare first use"| I["init"]
13
+ Q -->|"Inspect this machine"| D["doctor or machine"]
14
+ Q -->|"Inspect or choose a model"| M["models, route, or recommend"]
15
+ Q -->|"Execute a managed request"| T["session, natural-route, or task"]
16
+ Q -->|"Run the control plane"| S["serve"]
17
+ Q -->|"Preserve only Ollama APIs"| P["proxy"]
18
+ Q -->|"Measure or compare"| B["bench-all, run, or eval"]
19
+ Q -->|"Create a routing policy"| E["policy-from-eval"]
20
+ Q -->|"Compare CLI and MCP"| L["tools"]
21
+ ```
22
+
23
+ | Command | Purpose | Needs `freellama serve` |
24
+ |---|---|---|
25
+ | `init` | Inspect prerequisites and print a side-effect-free first-run plan | No |
26
+ | `serve` | Run the control plane and Ollama-compatible proxy | Starts it |
27
+ | `auth-token` | Create a new mode-0600 bearer-token file without printing the secret | No |
28
+ | `models` | List installed models, capabilities, residency, and evidence | Yes |
29
+ | `machine` | Print portable host RAM, CPU, OS, architecture, disk, and the local Ollama endpoint | Yes |
30
+ | `session` | Create an affinity scope for related tasks | Yes |
31
+ | `route` | Choose a model and request profile without executing it | Yes |
32
+ | `recommend` | Return an installed route or a reviewed installation plan | Yes |
33
+ | `natural-route` | Convert natural language to a route intent locally, then route it | Yes |
34
+ | `task` | Route and execute one nonstreaming task | Yes |
35
+ | `proxy` | Run only the Ollama-compatible retry and telemetry sidecar | No |
36
+ | `bench-all` | Measure installed models by capability group | No |
37
+ | `policy-from-eval` | Generate policy from quality-evaluation pass rates | No |
38
+ | `tools` | Print MCP tools and their CLI equivalents | No |
39
+ | `doctor` | Inspect Ollama, hardware, versions, and effective settings | No |
40
+ | `run` | Run a frozen suite against one Ollama build | No |
41
+ | `eval` | Compare the same frozen suite against stock and candidate builds | No |
42
+
43
+ Run `npx @octocodeai/freellama <command> --help` for every accepted flag and enum value. The executable's help
44
+ is authoritative; this page explains how the commands fit together.
45
+
46
+ ## Start the control plane
47
+
48
+ ```bash
49
+ npx @octocodeai/freellama serve --recommendation-catalog recommendations.example.toml
50
+ ```
51
+
52
+ The default listener is `http://127.0.0.1:11435`, and the default Ollama upstream is
53
+ `http://127.0.0.1:11434`. Use `--listen` and `--upstream` to change them. Nonloopback listeners
54
+ require `--allow-remote` and a token from `--auth-token-file` or
55
+ `FREELLAMA_AUTH_TOKEN_FILE`. Authentication applies to managed and passthrough routes.
56
+
57
+ `serve` persists bounded adaptive feedback under the platform data directory by default. Override
58
+ the path with `--feedback-file`; use `--ephemeral-feedback` only for disposable runs. See the
59
+ [production runbook](PRODUCTION.md) for token generation, state files, and ingress requirements.
60
+
61
+ `serve` exposes both managed routes under `/_freellama/v1/*` and byte-preserving Ollama routes under
62
+ `/api/*` and `/v1/*`. Use `proxy` when you need only the latter. See
63
+ [Architecture](ARCHITECTURE.md) for the request flow.
64
+
65
+ ### Assign exact models to CPU
66
+
67
+ Use `--cpu-upstream` with one or more `--cpu-model` values to send named models to a second Ollama
68
+ process. Other managed models and all raw passthrough traffic remain on the primary process.
69
+
70
+ ```bash
71
+ npx @octocodeai/freellama serve \
72
+ --upstream http://127.0.0.1:11434 \
73
+ --cpu-upstream http://127.0.0.1:11436 \
74
+ --cpu-model nomic-embed-text:latest
75
+ ```
76
+
77
+ FreeLlama rejects a CPU upstream with no assignments, nonloopback upstreams, and endpoints that are
78
+ aliases for the same socket. It also sets `options.num_gpu=0` on CPU-assigned managed requests.
79
+ Read [CPU and GPU model routing](CPU_GPU_ROUTING.md) before deploying this layout.
80
+
81
+ After startup, require `contracts.placement_observation` and `placement_evidence_gate`, then inspect
82
+ the declared upstreams:
83
+
84
+ ```bash
85
+ curl --silent http://127.0.0.1:11435/_freellama/v1/health |
86
+ jq '{contracts, backends}'
87
+ ```
88
+
89
+ A missing contract identifies a stale `serve` binary; rebuild and restart it before evaluating
90
+ placement.
91
+
92
+ ### Bound admission
93
+
94
+ `--max-concurrent-tasks` is the primary/GPU cost budget, not a request count; it defaults to 2.
95
+ `--cpu-max-concurrent-tasks` controls the independent CPU pool and defaults to 1. Embedding costs 1,
96
+ chat costs 2, and vision costs 4 (capped to the selected backend's pool). A saturated GPU pool does
97
+ not consume CPU permits. `--max-queue-wait-seconds` defaults to 120; when no permit becomes
98
+ available, FreeLlama refuses the task with HTTP 503 instead of waiting forever.
99
+
100
+ These are conservative workload-unit defaults, not detected core or RAM counts. Tune them from
101
+ queue-wait receipts and resident-memory observations on the target host. Ollama still controls
102
+ decoding concurrency with `OLLAMA_NUM_PARALLEL`. It also owns a separate internal queue through
103
+ `OLLAMA_MAX_QUEUE`: managed tasks enter that queue only after FreeLlama admission, while raw proxy
104
+ traffic enters it directly. Start an unmeasured deployment with one loaded model and one parallel
105
+ stream per Ollama process.
106
+
107
+ `route`, `recommend`, and `task` accept
108
+ `--execution-preference auto|prefer-cpu|prefer-gpu`. This is a fallback-capable hint over models
109
+ already assigned by the operator; it never rewrites raw passthrough and never makes an ineligible
110
+ model eligible. Preview the route and inspect the `execution` receipt to confirm whether the hint
111
+ was satisfied. Add `--min-placement-evidence observed` to refuse cold or physically mismatched
112
+ placement; the default `configured` accepts the operator assignment and observes after execution.
113
+
114
+ ## Inspect and route
115
+
116
+ Start with the read-only guided receipt, then inspect available models. `init` never pulls a model;
117
+ it stops at exact-tag approval and prints the next prerequisite, serve, managed-task, and MCP steps.
118
+
119
+ ```bash
120
+ npx @octocodeai/freellama init
121
+ npx @octocodeai/freellama doctor
122
+ npx @octocodeai/freellama models
123
+ npx @octocodeai/freellama machine
124
+ ```
125
+
126
+ Preview a deterministic decision without spending generation tokens:
127
+
128
+ ```bash
129
+ npx @octocodeai/freellama route --task coding --objective fastest
130
+ npx @octocodeai/freellama route \
131
+ --task vision \
132
+ --objective fastest \
133
+ --required-capability vision
134
+ ```
135
+
136
+ Task kinds are `completion`, `coding`, `code-repair`, `tools`, `browser`, `vision`, `embedding`, and
137
+ `long-context`. Objectives are `fastest`, `balanced`, and `quality`. `fastest` can use capability
138
+ and local benchmark evidence alone; `balanced` and `quality` require a policy for that task.
139
+
140
+ `recommend` is side-effect-free. If no installed model qualifies, it can return an installation
141
+ plan from the reviewed recommendation catalog, but it never pulls a model.
142
+
143
+ `natural-route` asks the configured local intent model to produce structured intent, validates that
144
+ intent, and then invokes the same deterministic router as `route`. It does not let the intent model
145
+ choose the final model directly.
146
+
147
+ ## Execute tasks
148
+
149
+ The prompt for `task` is positional:
150
+
151
+ ```bash
152
+ npx @octocodeai/freellama task --task completion --objective fastest "Reply with exactly OK."
153
+ npx @octocodeai/freellama task --task coding --min-confidence medium "Explain this patch."
154
+ ```
155
+
156
+ Useful task options include:
157
+
158
+ - `--model` for an exact installed model.
159
+ - `--session` for affinity across related requests.
160
+ - `--context-tokens` for a minimum context requirement.
161
+ - repeatable `--required-capability` constraints.
162
+ - repeatable `--image` paths for vision tasks.
163
+ - `--input-file` for batched embedding input, one item per line.
164
+ - `--min-confidence low|medium` to refuse insufficiently evidenced routes before generation.
165
+
166
+ Create a session with `session`, then pass its identifier to related `route` or `task` calls. A
167
+ route preview honors an existing affinity but does not create or change one. Only a successfully
168
+ admitted task execution binds the session to its selected model. Reuse never bypasses capability,
169
+ policy, or memory checks.
170
+
171
+ The CLI has no keep-alive flag. MCP `run_task` accepts `keepAlive`: `"0"` unloads after the request,
172
+ `"-1"` pins the runner, and omission leaves Ollama's default in place.
173
+
174
+ ## Earn medium routing confidence
175
+
176
+ Every route starts at low confidence. Medium confidence requires both a task policy and a local
177
+ benchmark record for the selected model.
178
+
179
+ ```mermaid
180
+ flowchart LR
181
+ Q["Quality evaluation aggregate"] --> P["policy-from-eval"]
182
+ P --> PF["platform.toml"]
183
+ T["Local throughput benchmark"] --> BR["benchmark-report.json"]
184
+ PF --> S["freellama serve"]
185
+ BR --> S
186
+ S --> C["Medium confidence for configured, measured routes"]
187
+ ```
188
+
189
+ Generate the policy from correctness data and the benchmark report from local runtime data:
190
+
191
+ ```bash
192
+ npx @octocodeai/freellama policy-from-eval \
193
+ --aggregate benchmark/local/results/<model>/aggregate.json \
194
+ --task coding \
195
+ --min-pass 0.8 \
196
+ --out platform.toml
197
+
198
+ npx @octocodeai/freellama bench-all --output benchmark-report.json
199
+ npx @octocodeai/freellama serve --recommendation-catalog recommendations.example.toml
200
+ ```
201
+
202
+ `serve` discovers `platform.toml` and `benchmark-report.json` in its working directory. Explicit
203
+ `--policy-file` and `--benchmark-report` values take precedence.
204
+
205
+ `policy-from-eval` reads `pass_at_1`, not the throughput produced by `bench-all`. It refuses expired
206
+ aggregates and fewer than three trials unless `--allow-smoke` is explicit, and it skips models that
207
+ are not installed. This prevents speed data from being mislabeled as quality evidence.
208
+
209
+ ## Compare the CLI and MCP surfaces
210
+
211
+ ```bash
212
+ npx @octocodeai/freellama tools
213
+ ```
214
+
215
+ The command prints the maintained parity map. MCP-only operations are `delegate_research` and the
216
+ online `models { view: "library" }` view. CLI-only operations include `serve`, `proxy`, `session`,
217
+ `recommend`, `natural-route`, `bench-all`, `policy-from-eval`, `run`, and `eval`.
218
+
219
+ For the six MCP tool contracts, read the [MCP package reference](../packages/mcp/README.md).
@@ -0,0 +1,352 @@
1
+ # Run models on CPU and GPU
2
+
3
+ Use two Ollama processes when a large model should remain on the GPU while smaller helper models run
4
+ on the CPU. FreeLlama assigns exact model tags to the secondary process, pins those managed requests
5
+ with `num_gpu: 0`, and reports the selected backend in catalog and task responses.
6
+
7
+ This configuration is useful for embeddings, intent interpretation, and other small helper models.
8
+ Benchmark large CPU workloads before adopting them. Apple silicon shares memory bandwidth; systems
9
+ with discrete GPUs have different contention and transfer costs, so neither result transfers
10
+ without measurement.
11
+
12
+ ## Adapt the design to your hardware
13
+
14
+ The two-process contract is portable; the device-control mechanism and useful workload are
15
+ machine-specific:
16
+
17
+ | Host | Primary process | CPU process defense in depth | Capacity evidence |
18
+ |---|---|---|---|
19
+ | Apple silicon | Metal auto-detection | Managed `num_gpu: 0`; `OLLAMA_LLM_LIBRARY=cpu` is only a hint | Total unified memory plus `/api/ps` |
20
+ | Linux or Windows with NVIDIA | CUDA auto-detection | `CUDA_VISIBLE_DEVICES=-1` plus managed `num_gpu: 0` | Host RAM, `nvidia-smi`, and `/api/ps` |
21
+ | Linux or Windows with AMD | ROCm or Vulkan auto-detection | `ROCR_VISIBLE_DEVICES=-1` or `GGML_VK_VISIBLE_DEVICES=-1`, plus managed `num_gpu: 0` | Host RAM, vendor tools, and `/api/ps` |
22
+ | CPU-only | CPU | A second process can isolate helpers, but it does not create a second accelerator | Host RAM and measured overlap |
23
+
24
+ `doctor` reports `machine.memory_bytes` on macOS, Linux, and Windows. It reports
25
+ `machine.unified_memory_bytes` only when unified memory is known; that compatibility field is not a
26
+ VRAM estimate on discrete-GPU hosts. Model-library fit is therefore a host-memory preflight, and
27
+ actual accelerator placement must come from Ollama's resident-runner report.
28
+
29
+ An agent can choose `auto`, `prefer_cpu`, or `prefer_gpu`, but it cannot invent topology. The
30
+ operator owns the backend endpoints and exact CPU-eligible tags. This split lets the same schema
31
+ work on a laptop, a CPU-only server, or a multi-GPU workstation without silently moving arbitrary
32
+ client requests.
33
+
34
+ ## Understand the boundary
35
+
36
+ The primary upstream owns the compatibility API and GPU-default managed tasks. The secondary
37
+ upstream owns only exact tags configured with `--cpu-model`:
38
+
39
+ ```mermaid
40
+ flowchart TB
41
+ CLIENT["Client"] --> F["FreeLlama :11435"]
42
+ F --> K{"Request path"}
43
+ K -->|"Raw /api/* or /v1/*"| GP["Primary Ollama :11434"]
44
+ K -->|"Managed /_freellama/v1/tasks"| R["Route exact model"]
45
+ R -->|"Default assignment"| GP
46
+ R -->|"--cpu-model match"| O["Set options.num_gpu = 0"]
47
+ O --> CP["Secondary Ollama :11436"]
48
+ GP --> GPU["GPU runner"]
49
+ CP --> CPU["CPU runner"]
50
+ ```
51
+
52
+ FreeLlama does not inspect or rewrite raw passthrough request bodies. A direct Ollama client that
53
+ needs model-aware CPU placement must use the managed task endpoint instead.
54
+
55
+ ## Start the backends
56
+
57
+ Start the primary Ollama process with a conservative local-only baseline:
58
+
59
+ ```bash
60
+ OLLAMA_HOST=127.0.0.1:11434 OLLAMA_NO_CLOUD=1 \
61
+ OLLAMA_MAX_LOADED_MODELS=1 OLLAMA_NUM_PARALLEL=1 ollama serve
62
+ ```
63
+
64
+ Start a second process on another loopback port:
65
+
66
+ ```bash
67
+ OLLAMA_HOST=127.0.0.1:11436 OLLAMA_NO_CLOUD=1 \
68
+ OLLAMA_MAX_LOADED_MODELS=1 OLLAMA_NUM_PARALLEL=1 \
69
+ OLLAMA_LLM_LIBRARY=cpu ollama serve
70
+ ```
71
+
72
+ The library override is experimental and varies by Ollama build. On the measured Apple Metal 0.33.2
73
+ server, Ollama accepted `OLLAMA_LLM_LIBRARY=cpu` but still selected Metal until the request included
74
+ `num_gpu: 0`. FreeLlama adds that option to CPU-assigned managed requests.
75
+
76
+ On other GPU backends, Ollama also documents process-level device controls:
77
+
78
+ - NVIDIA: `CUDA_VISIBLE_DEVICES=-1`
79
+ - ROCm: `ROCR_VISIBLE_DEVICES=-1`
80
+ - Vulkan: `GGML_VK_VISIBLE_DEVICES=-1`
81
+
82
+ Use a device control appropriate for the Ollama build in addition to FreeLlama's request option when
83
+ you need defense in depth.
84
+
85
+ ## Assign models
86
+
87
+ Start FreeLlama with the two upstreams and at least one exact CPU model tag:
88
+
89
+ ```bash
90
+ ./target/release/freellama serve \
91
+ --upstream http://127.0.0.1:11434 \
92
+ --cpu-upstream http://127.0.0.1:11436 \
93
+ --cpu-model nomic-embed-text:latest
94
+ ```
95
+
96
+ Repeat `--cpu-model` to assign more tags:
97
+
98
+ ```bash
99
+ ./target/release/freellama serve \
100
+ --upstream http://127.0.0.1:11434 \
101
+ --cpu-upstream http://127.0.0.1:11436 \
102
+ --cpu-model nomic-embed-text:latest \
103
+ --cpu-model qwen2.5:0.5b
104
+ ```
105
+
106
+ Use the exact name returned by Ollama, including `:latest` when present. FreeLlama rejects these
107
+ unsafe or ambiguous configurations:
108
+
109
+ - A CPU upstream without a CPU model assignment
110
+ - A CPU model assignment without `--cpu-upstream`
111
+ - Empty model names
112
+ - The same socket expressed through aliases such as `localhost:11434` and `127.0.0.1:11434`
113
+ - A non-loopback listener or upstream
114
+
115
+ Set independent admission budgets when the defaults do not fit the workload:
116
+
117
+ ```bash
118
+ ./target/release/freellama serve \
119
+ --upstream http://127.0.0.1:11434 \
120
+ --cpu-upstream http://127.0.0.1:11436 \
121
+ --cpu-model nomic-embed-text:latest \
122
+ --max-concurrent-tasks 2 \
123
+ --cpu-max-concurrent-tasks 1
124
+ ```
125
+
126
+ The defaults above admit one ordinary primary-backend chat (cost 2) and one CPU embedding (cost 1)
127
+ at the same time. They are conservative workload units for the common `OLLAMA_NUM_PARALLEL=1`
128
+ layout, not a profile of the development Mac. Increase them only after measuring queue wait,
129
+ resident memory, and Ollama's own parallel setting on the target host.
130
+
131
+ ## Let an agent express placement intent
132
+
133
+ MCP `run_task` and the managed HTTP route accept `executionPreference`: `auto`, `prefer_cpu`, or
134
+ `prefer_gpu` (HTTP: `execution_preference`). `minPlacementEvidence` accepts `configured` or
135
+ `observed` (HTTP: `min_placement_evidence`). Always preview consequential work:
136
+
137
+ ```json
138
+ {
139
+ "task": "embedding",
140
+ "objective": "fastest",
141
+ "executionPreference": "prefer_cpu",
142
+ "minPlacementEvidence": "observed",
143
+ "preview": true
144
+ }
145
+ ```
146
+
147
+ A preview accepts routing constraints only. Add the embedding input when submitting the execution
148
+ request after accepting the route.
149
+
150
+ The agent does not gain authority to assign models. FreeLlama first applies model installation,
151
+ capability, policy, context, explicit-model, and session-affinity constraints. It only honors the
152
+ preference if an eligible model is already assigned to that backend. Inspect
153
+ `execution.preference_satisfied`, `execution.backend`, `execution.observation`, and
154
+ `execution.reason`. `observed` fails closed for cold models: warm once with `configured`, inspect
155
+ the receipt, then retry with observed evidence.
156
+
157
+ ## Verify placement
158
+
159
+ Inspect FreeLlama's declared backend contract:
160
+
161
+ ```bash
162
+ curl --silent http://127.0.0.1:11435/_freellama/v1/health | jq '.backends'
163
+ ```
164
+
165
+ The output is similar to the following:
166
+
167
+ ```json
168
+ {
169
+ "gpu": {
170
+ "upstream": "http://127.0.0.1:11434"
171
+ },
172
+ "cpu": {
173
+ "upstream": "http://127.0.0.1:11436",
174
+ "models": ["nomic-embed-text:latest"]
175
+ }
176
+ }
177
+ ```
178
+
179
+ Run a managed embedding task:
180
+
181
+ ```bash
182
+ curl --silent http://127.0.0.1:11435/_freellama/v1/tasks \
183
+ --header 'content-type: application/json' \
184
+ --data '{
185
+ "task": "embedding",
186
+ "objective": "fastest",
187
+ "model": "nomic-embed-text:latest",
188
+ "input": "placement check",
189
+ "keep_alive": "-1"
190
+ }' | jq '{execution, admission, route}'
191
+ ```
192
+
193
+ The `execution` object separates the configured `backend`, requested compatibility `placement`, and
194
+ post-run `observation`. The source is Ollama `/api/ps`; direct inspection remains useful:
195
+
196
+ ```bash
197
+ curl --silent http://127.0.0.1:11434/api/ps | jq '.models[] | {name, size_vram}'
198
+ curl --silent http://127.0.0.1:11436/api/ps | jq '.models[] | {name, size_vram}'
199
+ ```
200
+
201
+ A fully CPU-loaded model reports `size_vram:0`; a fully GPU-loaded model reports positive resident
202
+ VRAM. `observation.status` is `verified`, `mismatch`, or unavailable. Unknown, mixed, and mismatched
203
+ samples are reported but excluded from adaptive feedback.
204
+
205
+ ## Understand concurrency
206
+
207
+ Each backend has its own weighted admission pool and its own resident/shared and
208
+ transition/exclusive lock. CPU and GPU model transitions therefore do not block each other, and a
209
+ GPU burst cannot spend the CPU helper's permit:
210
+
211
+ ```mermaid
212
+ flowchart TD
213
+ T["Managed task"] --> B{"Selected backend"}
214
+ B -->|"GPU"| GA["GPU admission pool<br/>default 2 units"]
215
+ B -->|"CPU"| CA["CPU admission pool<br/>default 1 unit"]
216
+ GA --> GL{"Resident?"}
217
+ CA --> CL{"Resident?"}
218
+ GL -->|"Yes"| GR["GPU shared permit"]
219
+ GL -->|"No"| GX["GPU exclusive transition"]
220
+ CL -->|"Yes"| CR["CPU shared permit"]
221
+ CL -->|"No"| CX["CPU exclusive transition"]
222
+ GR --> GO["Primary Ollama"]
223
+ GX --> GO
224
+ CR --> CO["Secondary Ollama"]
225
+ CX --> CO
226
+ ```
227
+
228
+ FreeLlama can overlap requests across the two servers even when each Ollama process uses
229
+ `OLLAMA_NUM_PARALLEL=1`. Raising `OLLAMA_NUM_PARALLEL` affects parallel requests within one server
230
+ and multiplies K/V-cache memory; it is a separate tuning decision. `OLLAMA_MAX_QUEUE` likewise
231
+ bounds each Ollama process after FreeLlama admission. It does not replace either backend's weighted
232
+ FreeLlama budget or queue-wait deadline.
233
+
234
+ ## Understand automatic feedback
235
+
236
+ With `executionPreference: "auto"`, unpinned `fastest` and `balanced` work can select a backend from
237
+ observed warm task duration. The loop is deliberately bounded:
238
+
239
+ ```mermaid
240
+ flowchart LR
241
+ X["Successful resident task"] --> O["Record duration by<br/>task + backend"]
242
+ O --> N{"At least 3 samples<br/>on both backends?"}
243
+ N -->|"No"| D["Keep deterministic default"]
244
+ N -->|"Yes"| C{"More than 10% faster<br/>per work unit?"}
245
+ C -->|"Yes"| P["Prefer measured backend"]
246
+ C -->|"No"| D
247
+ P --> R["Return reason + placement receipt"]
248
+ ```
249
+
250
+ Generation compares decode nanoseconds per output token; embeddings compare total nanoseconds per
251
+ input token. Buckets are model-specific and reset when the selected model changes. Only physically
252
+ verified placement contributes; cold transitions and assignment mismatches do not. A difference
253
+ of 10% or less is treated as noise. Feedback never overrides an explicit model, session affinity,
254
+ capability or policy guards, or the `quality` objective. The CLI persists the bounded, versioned,
255
+ prompt-free aggregate snapshot atomically by default; `--ephemeral-feedback` opts into reset-on-
256
+ restart behavior. `GET /_freellama/v1/health` exposes persistence status, sample counts, readiness,
257
+ and per-backend admission capacity so an agent can inspect the loop instead of guessing.
258
+
259
+ ## Interpret the measured Mac result
260
+
261
+ The local benchmark warmed both models, then ran three matched sequential and parallel trials using
262
+ a 100-line Qwen completion and a 256-item Nomic embedding batch:
263
+
264
+ | Trial | Sequential | Parallel |
265
+ |---|---:|---:|
266
+ | 1 | 34.586 s | 27.499 s |
267
+ | 2 | 38.988 s | 28.233 s |
268
+ | 3 | 37.997 s | 38.167 s |
269
+ | Median | 37.997 s | 28.233 s |
270
+
271
+ The median parallel result was 1.346 times faster, a 25.70% wall-time reduction. The third trial
272
+ shows why this feature is workload-dependent: CPU work can slow Metal work through host and unified-
273
+ memory contention.
274
+
275
+ The placement and compatibility guardrails also passed:
276
+
277
+ | Guardrail | Observed result |
278
+ |---|---|
279
+ | GPU placement | `qwen3.8:27b-mlx`, `size_vram: 19,175,677,668` |
280
+ | CPU placement | `nomic-embed-text:latest`, `size_vram: 0` |
281
+ | Managed execution | Every recorded request returned HTTP 200 with the correct upstream receipt |
282
+ | Admission | `queue_wait_ms: 0` for every request |
283
+ | GPU output | Identical output length in every sequential and parallel trial |
284
+ | Raw compatibility | `/api/version` through FreeLlama returned primary Ollama 0.33.2 |
285
+
286
+ An earlier, lighter overlap check paired a small CPU embedding with a cold GPU completion. The CPU
287
+ request completed in 60 ms while the GPU request continued and completed in 7.391 seconds. This is
288
+ the intended sweet spot: a small helper can finish without waiting for unrelated GPU generation.
289
+
290
+ A fresh post-build smoke check repeated the concurrent path through the persistent services. Both
291
+ requests returned HTTP 200 in 8.303 seconds wall time, both reported zero queue wait, Qwen returned
292
+ `OK` from the primary backend, and Nomic returned one embedding from the CPU backend. That runner
293
+ snapshot reported 18,486,900,456 GPU bytes for Qwen and zero for Nomic. The positive GPU byte count
294
+ is the placement invariant; its absolute value can change with the request profile and runner state.
295
+
296
+ A production-gate adversarial trial assigned `qwen3.8:27b-mlx` to the CPU endpoint with
297
+ `num_gpu:0`. Ollama still reported all 18,490,183,380 resident bytes in VRAM. FreeLlama returned
298
+ `observation.status:"mismatch"`, `feedback.accepted:false`, kept CPU samples at zero, and refused a
299
+ subsequent `min_placement_evidence:"observed"` preview with HTTP 422. This is why assignment and
300
+ physical processor are distinct fields.
301
+
302
+ Immediate unload is a managed transaction. For `keep_alive:0`, FreeLlama temporarily retains the
303
+ runner, observes `/api/ps`, records feedback only when placement is verified, requests an explicit
304
+ unload, and verifies that the runner is no longer resident. Inspect both `execution.observation`
305
+ and `execution.lifecycle`; a failed unload is reported without discarding the completed task output.
306
+
307
+ Prefer the following assignments:
308
+
309
+ - Small embedding models
310
+ - Lightweight intent models
311
+ - Short extraction or classification helpers with measured CPU behavior
312
+
313
+ Benchmark these assignments before adopting them:
314
+
315
+ - Large generative models on CPU
316
+ - Large embedding batches that saturate memory bandwidth
317
+ - Two workloads whose combined resident memory approaches system capacity
318
+
319
+ The local measurement receipt is written to
320
+ `.octocode/evals/cpu-gpu-concurrency-benchmark.md`. Git intentionally ignores that path, so the
321
+ trial table in "Interpret the measured result" carries the durable result while the local artifact
322
+ retains the full run detail.
323
+
324
+ ## Troubleshoot the secondary backend
325
+
326
+ If an assigned model does not appear, check the following conditions:
327
+
328
+ 1. Confirm that the exact tag appears in `GET http://127.0.0.1:11436/api/tags`.
329
+ 2. Confirm that the secondary server is listening on the configured port.
330
+ 3. Run a managed task, then confirm `size_vram: 0` on the secondary `/api/ps` response.
331
+ 4. Inspect the secondary Ollama log for a runner command containing `-ngl 0` or equivalent CPU
332
+ placement evidence.
333
+ 5. Confirm that the request used `/_freellama/v1/tasks`, not raw `/api/chat` passthrough.
334
+
335
+ A configured but unreachable CPU backend causes model discovery to fail closed. GPU-managed tasks
336
+ can also be unavailable until the secondary server returns. Raw passthrough to the primary server
337
+ continues to work because it does not depend on catalog discovery.
338
+
339
+ For the surrounding runtime settings, see
340
+ [Ollama and FreeLlama optimization](OLLAMA_SYSTEM_OPTIMIZATION.md). For all `serve` flags, see the
341
+ [CLI reference](CLI.md).
342
+
343
+ ## Upstream sources
344
+
345
+ - [Ollama hardware support](https://docs.ollama.com/gpu) documents GPU visibility controls and
346
+ Apple Metal support.
347
+ - [Ollama FAQ](https://docs.ollama.com/faq) documents concurrency, residency, `/api/ps` processor
348
+ reporting, and K/V-cache behavior.
349
+ - [Ollama troubleshooting](https://github.com/ollama/ollama/blob/main/docs/troubleshooting.mdx)
350
+ documents the experimental `OLLAMA_LLM_LIBRARY` override.
351
+ - [Ollama API types](https://github.com/ollama/ollama/blob/main/api/types.go) defines `num_gpu` as a
352
+ runner-load option.
@@ -0,0 +1,105 @@
1
+ # Token economics
2
+
3
+ This reference keeps benchmark detail out of the README's "what, why, and run it" path. The
4
+ per-call rows come from one machine (M4 Pro, 48 GB unified memory); values marked with `~` and the
5
+ session projection are estimates derived from those calls.
6
+
7
+ ## Per-call, measured
8
+
9
+ | Work | Source tokens | Returned tokens | Preserved |
10
+ |---|---:|---:|---:|
11
+ | 6 grounded code questions | 59,208 | 1,742 | **97.1%** |
12
+ | 4 text embeddings (307 ms) | 17,600 | ~200 | **98.9%** |
13
+ | 1 image, OCR, byte-exact | 1,970 | 37 | **98.1%** |
14
+ | Historical tool-schema surface, per turn | <3.2k | 0 | **100%** |
15
+
16
+ ## Current MCP-envelope audit (2026-09-03)
17
+
18
+ The earlier table describes a historical workload on an M4 Pro. This audit measures the current
19
+ eight-tool MCP build on the current host. JSON tokenization varies by client and model, so it records
20
+ bytes first and gives `characters / 4` only as a deliberately rough token estimate.
21
+
22
+ | Current result | Canonical structured chars | Compact text chars | Duplicate chars avoided | Rough duplicate-token estimate |
23
+ |---|---:|---:|---:|---:|
24
+ | `doctor {view:"summary"}` | 3,099 | 35 | 3,064 | ~766 |
25
+ | `doctor {view:"scheduler"}` | 3,933 | 35 | 3,898 | ~975 |
26
+ | `doctor {view:"config"}` | 7,373 | 86 | 7,287 | ~1,822 |
27
+ | `doctor {view:"full"}` | 10,836 | 185 | 10,651 | ~2,663 |
28
+ | `models {view:"raw", limit:1}` | 447 | 43 | 404 | ~101 |
29
+
30
+ These are **not automatically billed-token savings**. They are the second serialized text copy
31
+ that FreeLlama does not emit. An MCP host that feeds both `structuredContent` and text into a model
32
+ can avoid roughly the listed amount; a host that already keeps only one representation does not.
33
+ The canonical structured payload still exists for deterministic clients that need it.
34
+
35
+ The current eight-tool schema plus initialization instructions measured 17,935 characters, or
36
+ ~4,484 tokens using the same rough conversion. The integration suite enforces a ceiling below 4,500
37
+ estimated tokens. This is tool-schema **rent**, not a saving: clients pay it whenever they expose
38
+ the entire tool list to a model. Read the bundled documentation resource on demand and do not expose
39
+ unneeded tools in a client that supports tool filtering.
40
+
41
+ Delegated research has a different economics boundary. It keeps source files and full tool
42
+ observations out of the calling agent's active prompt, but it spends local Ollama input/output
43
+ tokens in the delegated agent. The historical research rows below remain useful workload evidence;
44
+ they are not a claim that the current installed model or host can reproduce those savings. Measure
45
+ the same question, source size, model, context policy, and client transcript before using a number
46
+ as a production budget.
47
+
48
+ ## Scaled to a session
49
+
50
+ 20 grounded questions, 200 turns, one embedding index over 322 chunks:
51
+
52
+ | Component | Tokens preserved |
53
+ |---|---:|
54
+ | Research offload | 191,553 |
55
+ | Embedding vectors withheld | 354,200 |
56
+ | Schema rent avoided, after ~90% prompt-cache discount | 59,740 |
57
+ | **Total** | **≈ 605,000 — about 3 × a 200K window** |
58
+
59
+ ## Measurement and projection limits
60
+
61
+ Schema rent is nominally **597,400** tokens (200 turns × the former eight-tool schemas). Including
62
+ the nominal figure puts the headline at **1.14M — nearly double**. This report discounts it by ~90%
63
+ because prompt caching makes cached input far cheaper,
64
+ and because the rent only accrues at all if a *separate* small model holds the tool schemas. The
65
+ first two rows are the uncontestable part: that data physically never reaches the orchestrator.
66
+
67
+ **Do not lead with this number.** Providers, caching strategies, context windows, and pricing all
68
+ move, and a token count moves with them. The durable benefit is **context isolation** — the
69
+ orchestrator never receives whole files, raw vectors, OCR output, intermediate research, or
70
+ repetitive schemas. That improves context *quality* as well as cost, and it does not depend on
71
+ anyone's price list.
72
+
73
+ ## The cost side
74
+
75
+ | | |
76
+ |---|---|
77
+ | Grounded question latency | **7–62 s** vs ~1 s for a frontier model that already holds the file |
78
+ | Where it goes | **51% prompt re-evaluation**, 42% generation, 7% model load (measured on a 4-turn run) |
79
+ | Scaling | turn-dominated: `seconds ≈ 9.8 × tool_calls`, correlation **0.811** over 60 runs |
80
+ | Break-even | past ~1k tokens of source the token maths wins; below that, read the file directly |
81
+ | Judgment ceiling | ~67% accurate, in the same confident tone as the ~99% case |
82
+
83
+ **Correction (measured same day): prefix-cache reuse WORKS on this path.** An earlier revision of
84
+ this file claimed the opposite by misreading `prompt_eval_count`, which counts *all* prompt tokens
85
+ whether or not they were served from cache. The durations tell the truth:
86
+
87
+ | Probe (raw `/api/chat`, 2,462-token prefix) | prompt_eval |
88
+ |---|---:|
89
+ | turn 1, cold | 18,631 ms |
90
+ | turn 2, warm — tiny suffix appended | **281 ms** |
91
+ | a *different* conversation interjected | 22,467 ms (its own cold prefill) |
92
+ | turn 3 of the original, after the interjection | **285 ms — cache survived** |
93
+
94
+ So the per-turn cost is **prefill of NEW tokens plus generation**, not re-reading old context, and
95
+ the cache even survives interleaved conversations. The `seconds ≈ 9.8 × tool_calls` relationship
96
+ still holds empirically — each turn adds a fresh observation to prefill (~130 tok/s measured) and a
97
+ generation (~40 tok/s) — but the mechanism is new-token cost, not context re-evaluation. Fewer,
98
+ better-aimed commands remain the lever; "the cache is broken" is not the reason.
99
+
100
+ **One consequence worth acting on: context compaction is cache-hostile.** `fit_to_context` rewrites
101
+ old observations when the window is nearly full, which changes the byte prefix and invalidates the
102
+ cache from that point — the next turn re-prefills everything after the edit. The cheap mitigation is
103
+ headroom: `OLLAMA_KV_CACHE_TYPE=q8_0` roughly halves KV memory (flash attention is already auto-on,
104
+ measured and documented in `doctor`), which buys double the context for the same memory and makes
105
+ compaction — and therefore invalidation — rare.