Skip to content

feat(323): P0 gates measured + P1 — a model on this computer runs an agent - #118

Merged
munchist merged 3 commits into
mainfrom
aflow/323-p0-gates
Oct 16, 2026
Merged

munchist merged 3 commits into
mainfrom
aflow/323-p0-gates

Conversation

@munchist

Copy link
Copy Markdown
Contributor

Plan 323: P0 (feasibility gates, measured) and P1 (one model on this computer runs an agent). The plan doc is updated to say what was built and what is still open.

P0 — measured, not researched

yarn models:local-gates <gate> runs each gate against the installed Ollama and prints JSON you can reproduce. Results for an Apple M3 with 24 GB on Ollama 0.40.5 are in docs/plans/323-p0/apple-m3-24gb.json; plan §9 summarises them. What they changed:

  • The runners fail in opposite ways. The llama.cpp runner silently cuts an over-long prompt from the front, dropping the system message, and the answer comes back wrong. The MLX runner does not enforce num_ctx at all.
  • prompt_eval_count is exact. The count includes any cached prefix; caching only changes the timing. That makes a one-token pre-count nearly free.
  • Characters ÷ 4 undercounts. JSON is undercounted by about 40% and Chinese by about 2.5×.
  • Replayed thinking reaches no model. The chat templates drop it from history.
  • Locality is visible. GET /api/status reports the cloud-disable setting, and cloud models show remote_host in /api/tags.
  • Pulls behave well. Two pulls of one tag join, a cancelled pull installs nothing, and the next pull resumes.
  • Embeddings. Qwen3-Embedding 4B can produce 1536 dimensions, so the existing column works; EmbeddingGemma refuses. Both truncate silently unless truncate: false is sent.
  • On MLX, model memory must be read from the process. /api/ps does not reflect the context size there.
  • Model call inventory. Compaction was hard-coded to a cloud model, and memory embedding bypasses the space's credentials entirely.

P1 — what it adds

  • An ollama provider on Ollama's native /api/chat and /api/embed. It is a credential-registry connection with no required secret, offered only by the local edition: the providers list, credential writes and /catalog/models all filter by edition.
  • Context. Each request gets the connection's window, narrowed to the catalog's; the same window drives compaction (client.contextWindowFor). A prompt that may not fit is counted exactly by a one-token evaluation, the answer gets what the window leaves, and a prompt that doesn't fit is refused before it is sent. A response the runtime truncated fails instead of answering.
  • Structured output. JSON is carried by a tool call when the runner refuses format (MLX returns 501). The adapter learns this per model from the runtime's answer.
  • Native tool calling follows capabilities.functionCalling instead of a five-provider list. ⚠️ This also moves the three OpenRouter models, all declaring functionCalling: true, onto native tool calls. Their route already accepts tool definitions in the streaming path (models:verify-reasoning exercises it).
  • Runtime admission.
    • A Redis lease keyed by the runtime, so every name for this machine's loopback counts as one runtime, sized by the connection's max_parallel.
    • A new optional StepHandler.admit: the step waits before it is started, without its executor slot and outside its timeout. A cancel ends the wait, and a draining executor hands the step back.
    • Callers outside a step take the same lease through createByokAiClientFactory(db, admission): titles decline when the runtime is busy, post-run judging waits.
    • The renewal timer is registered as ai.local_runtime_lease_renewal.
  • Timeouts. Every AI operation on a local model gets a 15-minute idle window (AI_LOCAL_MODEL_IDLE_TIMEOUT_MS) with the agent-turn ceiling.
  • Clerk and compaction. The clerk's auto mode takes a free space default, and compaction takes the clerk's model, so a space whose only provider is this computer can compact and title.
  • Readiness. It no longer reports a local provider as ready with no credential, and the connect dialog can connect a provider that needs no key.
  • yarn models:verify-local measures a build (digest, runner, quantisation, runtime version) and exits non-zero when its catalog entry drifts.
  • First catalog entry: ollama-gemma4-26b (alias gemma-local).

Verified

  • Unit tests: adapter, sizing, lease, admit hook, credential config, clerk, check probe and the edition filters.
  • Full suites: yarn test with no database (Postgres suites skipped), tsc -b, format:check and repo-wide lint (0 errors).
  • Against the live runtime (Ollama 0.40.5, gemma4:26b): plain text, two parallel native tool calls, streaming with thinking, JSON through the tool fallback after the 501, and a refusal before sending of a prompt the llama.cpp runner would have cut.

Not yet run

P1's exit: a Helmsman conversation through the web UI on the dev stack started from this branch, three concurrent sessions, and a long-prompt prefill. To try it:

  1. Start the stack from this branch.
  2. Add the Ollama connection under Settings → Credentials.
  3. Enable Gemma 4 26B (this computer) for the tenant.
  4. Assign it to Helmsman.

🤖 Generated with Claude Code

munchist and others added 3 commits October 16, 2026 14:47
`yarn models:local-gates` answers each P0 gate against the installed
Ollama and prints reproducible JSON: overflow (G1), locality (G2), pull
lifecycle (G3), embeddings (G4) and the machine profile (G5). Results for
an Apple M3 with 24 GB on Ollama 0.40.5 are committed beside the plan.

The plan's decisions are revised from what was measured: the llama.cpp
runner truncates silently while the MLX runner ignores num_ctx;
prompt_eval_count is exact, so D5 sizes from an exact baseline plus a
conservative delta and a one-token exact count; replayed thinking reaches
no model; /api/status and /api/tags carry the locality signals; MLX
footprint needs process memory, not /api/ps. The call-site inventory moves
compaction and the clerk into P1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An `ollama` provider, connected through the credential registry with no
required secret and offered only by the local edition, behind an adapter
on Ollama's native API:

- the context window is the connection's, narrowed to the catalog's and
  sent on every request; a prompt that might not fit is counted exactly
  by a one-token evaluation and refused before it is sent, the answer gets
  what the window leaves, and a response the runtime truncated fails;
- JSON rides a tool call where the runner refuses `format` (MLX);
- native tool calling follows the measured capability, not a provider list;
- a runtime admits `max_parallel` requests across every caller, through a
  Redis lease keyed by the runtime and a new `StepHandler.admit` that waits
  before the step starts, without its slot and outside its timeout;
- local models get a prefill-sized idle window;
- compaction and the clerk resolve a free local default, so a space with
  only this computer's model can compact and title;
- `yarn models:verify-local` measures a build and checks its catalog entry.

Gemma 4 26B is the first entry, measured on Ollama 0.40.5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every script that runs tsx on a file of the repository goes through
scripts/with-stack-env.mjs; the guard test names the two new ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@munchist
munchist merged commit b755a33 into main Oct 16, 2026
3 checks passed
@munchist
munchist deleted the aflow/323-p0-gates branch October 16, 2026 15:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant