Skip to content

Correct offline-agent guidance from measurements on a 24 GB M2 - #57

Merged
d-morrison merged 3 commits into
mainfrom
docs/offline-agent-corrections
Aug 10, 2026
Merged

Correct offline-agent guidance from measurements on a 24 GB M2#57
d-morrison merged 3 commits into
mainfrom
docs/offline-agent-corrections

Conversation

@d-morrison

Copy link
Copy Markdown
Collaborator

Closes #56.

Everything here was measured on the machine while setting it up for offline agentic work, not recalled. Each claim states the command that produced it so a reader can re-measure rather than inherit.

Corrections

The 2048-token context claim was stale. Ollama now derives the default from detected memory, per its own ollama serve --help:

OLLAMA_CONTEXT_LENGTH   (default: 4k/32k/256k based on VRAM)

A 24 GB Apple-silicon machine lands in the 4096 tier, not the 32k its headline number suggests, because only ~75% of unified memory is GPU-addressable. The chapter now says how to check (ollama ps prints the live context) instead of asserting a number, and gives three ways to raise it, distinguished by which clients each one reaches. The Modelfile route is the only one that reaches clients that cannot send num_ctx themselves.

qwen2.5-coder cannot drive an agentic loop. It was recommended throughout and listed first in the agentic-model table. Against a single-function tool schema it returned finish_reason: stop with no tool call, 0/4. granite4:7b-a1b-h returned a well-formed call 3/3 plus a full multi-turn round trip, at a third of the size. It remains a good completion model, and the text now says so rather than dropping it.

Relatedly, ollama show capability metadata is unreliable in both directions: qwen2.5-coder:14b advertises tools and does not call them; gemma4:12b advertises none and calls them correctly. Only a real request settles it, so the chapter gives a one-line curl to run.

The hardware-tier table overstated 24 GB of unified memory. The 30-32B tier is not reachable there: qwen3-coders smallest tag is 19 GB against a ~18 GB addressable ceiling, with no smaller variant, and context is charged on top -- raising a 14B from 4k to 32k moved it from 9.5 GB to 15 GB resident.

Additions

Claude Code offline. Ollama serves an Anthropic-compatible /v1/messages endpoint, so any Anthropic-API client can be pointed at a local model with no proxy. Verified returning a real Anthropic-shaped response.

Harness overhead, added as a sixth guardrail. A model that tool-calls correctly against the raw API can still fail inside a heavyweight harness, because the system prompt and tool schemas crowd out the task. Asked to read one file, granite4 under Claude Code produced a paragraph about missing credentials with a GitHub MCP server loaded; invented a task list copied from a tool description with MCP disabled; and still ignored the prompt with the surface cut to Read/Grep/Glob. The same model through aider fixed a real bug and committed in 17 seconds.

edit_format: whole for small models, with the most important finding in the PR:

File diff whole
One function 3/3 3/3
Two functions, one to leave alone 0/3 3/3

The diff failure is not a refused edit. In all three runs the model fixed the target function and silently deleted the adjacent one, then committed a message naming only the intended fix. Nothing in the commit message or exit status mentioned the deletion. That is the compounding-error hazard in its most concrete form, and it is also a case of the chapter own guardrail working: committing every step meant git still had the original.

Verification

  • quarto render chapters/coding-agents.qmd -- clean, zero warnings. Two link warnings surfaced mid-work and were fixed: mcp-server-setup.qmd resolves relative to the including file, and that chapter appears later anyway, so a cross-reference would have been a forward reference. The acronym is expanded inline instead.
  • Banned punctuation: 0 hits in 363 added lines, with a negative control confirming the detector fires on a real em-dash.
  • Spellcheck: differenced against main so only genuinely new words counted -- Modelfile, Ollama's, schemas, added to inst/WORDLIST. Re-run is clean.
  • No multi-sentence added lines.

Note on scope

references.bib is untouched; no new citations were added. The companion issues (ai-config#1292, gha#436) still carry the runnable halves -- this PR only corrects the explanatory side, per the repo division of labor.

Three corrections and two additions, all measured on the machine rather
than recalled, while setting it up for offline agentic work.

Corrections:

- The "2048-token context window" claim is stale. Ollama now picks the
  default from detected memory (4k/32k/256k), per `ollama serve --help`.
  A 24 GB Apple-silicon machine gets 4096, not 32k, because only ~75% of
  unified memory is GPU-addressable. Adds `ollama ps` as the way to check
  what you actually got, and a Modelfile route for clients that cannot
  send num_ctx themselves.

- qwen2.5-coder does not emit tool calls (0/4 attempts, finish_reason
  "stop"), so it cannot drive an agentic loop, though it remains a strong
  completion model. granite4:7b-a1b-h succeeded 3/3 plus a multi-turn
  round trip at a third of the size. `ollama show` capability metadata is
  wrong in both directions, so only a real request settles it.

- The hardware-tier table overstated 24 GB of unified memory. The
  30-32B tier is unreachable there: qwen3-coder's smallest tag is 19 GB
  against a ~18 GB ceiling, and raising a 14B from 4k to 32k context cost
  ~5.5 GB of KV cache.

Additions:

- Ollama serves an Anthropic-compatible /v1/messages endpoint, so Claude
  Code runs against a local model with no proxy.

- Harness overhead bounds what a small model can do, with the tool
  surface added as a sixth guardrail. Also documents edit_format: whole
  for small models: with `diff`, granite4 fixed the target function and
  silently deleted an adjacent one in 3/3 runs, committing a message that
  mentioned only the fix.

Closes #56
Copilot AI lite review requested due to automatic review settings August 9, 2026 23:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@d-morrison
d-morrison requested a lite review from Copilot August 9, 2026 23:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

api.anthropic.com returns 404 to a plain GET, so lychee rejected it. The
probe only needs any external host to demonstrate the block, and a
vendor-neutral one states the point better: what matters for an offline
claim is that no outbound traffic succeeds. Verified all six external
URLs this branch adds now return 200.
Copilot AI review requested due to automatic review settings August 9, 2026 23:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment thread chapters/ai-tools/running-agents-offline.qmd
Comment thread chapters/ai-tools/running-agents-offline.qmd Outdated
Comment thread chapters/ai-tools/small-local-models-agentic.qmd Outdated
Comment thread chapters/ai-tools/running-agents-offline.qmd Outdated
Comment thread chapters/ai-tools/running-agents-offline.qmd Outdated
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

All four confirmed, none rebutted.

1. Restore the "Falling Back Between Cloud and Local Automatically"
   heading, which my edit dropped, orphaning the LiteLLM content under
   the new Claude Code section. Found by reading the diff's deleted
   lines, which I should have done before the first push.

2. Drop "a third of the size". It was true only of resident size at 32k
   (4.7 vs 15 GB) and false by parameter count, with no measure stated.
   Now says "half the parameter count", which is unambiguous.

3. Correct the tool-calling claim, which was wrong. Re-checking with the
   full `ollama show` output shows gemma4:12b DOES advertise `tools`; my
   original reading came from `grep -A3`, which truncated the capability
   list one line before `tools`. The "wrong in both directions" claim is
   withdrawn.

   Re-measuring also gave a sharper result than the one it replaces.
   qwen2.5-coder does not ignore the tools: it emits a correct tool call
   as prose in `content` while leaving `tool_calls` empty, so a harness
   sees a plain reply and never runs the tool. The rule is that an
   advertised capability is necessary but not sufficient.

4. Add HTTP-status checking to the tool-calling snippet. As written it
   could not distinguish a model declining to call a tool from a request
   that errored, which is the ambiguity the same file warns about
   elsewhere. The no-call branch now prints `content`, which is what
   makes the qwen failure mode visible. Verified against all three
   branches: granite4 (agent), qwen (no call), bad tag (HTTP 404).
Copilot AI review requested due to automatic review settings August 9, 2026 23:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@d-morrison

Copy link
Copy Markdown
Collaborator Author

ARD round 1

All four findings Addressed, none rebutted. The review was right on every point, including the one I would have defended.

# Finding Disposition
1 Deleted #### Falling Back... heading Addressed
2 "a third of the size" arithmetic Addressed
3 gemma4 tool-capability claim Addressed --- I was wrong
4 Test snippet cannot distinguish error from no-call Addressed

Finding 3 deserves the detail, because the reviewer inferred the right conclusion from the wrong reason. The suggestion was that I had mixed this up with gemma3 or misread the result. Neither: I ran the right command against the right model and truncated its output. The original check was ollama show "$m" | grep -iA3 "capabilities", and gemma4's capability list is five entries long, so -A3 cut it off exactly one line before tools:

Capabilities
  completion
  vision
  audio        <- -A3 stopped here
  tools
  thinking

So the "wrong in both directions" claim is withdrawn. All three models I tested advertise tools, including the one that does not work.

Re-measuring with full output produced a sharper finding than the one it replaces, so the section is stronger for the correction. qwen2.5-coder does not ignore the tools at all --- it emits a correct tool call as prose in content while leaving tool_calls empty:

{"name": "read_file", "arguments": {"path": "src/main.py"}}

Right JSON, wrong field. A harness reads tool_calls, finds nothing, and treats the turn as a plain reply. That is a more useful thing for a reader to recognise than "it does not tool-call", and it makes the corrected rule precise: an advertised capability is necessary but not sufficient.

Finding 4 was the same class of mistake one level down, and the file criticises it elsewhere in its own words: a check whose failure path and pass path look identical. The snippet now separates the status code, and prints content on the no-call branch, which is what makes the qwen behaviour above visible rather than silent. Verified against all three branches:

granite4:7b-a1b-h        usable as an agent
qwen2.5-coder:14b        no tool call; completion model only
does-not-exist:1b        request failed (HTTP 404)

Finding 1 was mine and was avoidable: I dropped the heading while inserting a section, and did not read the diff's deleted lines before pushing. I ran that check this round and it is clean apart from intentional replacements.

Finding 2: "a third" was true only of resident size at 32k (4.7 vs 15 GB) and false by parameter count, with no measure stated. Now "half the parameter count".

Re-verified after the changes: render clean with zero warnings, 0 banned-punctuation hits in 402 added lines with a positive control confirming the detector fires, spellcheck differenced against main clean (errored and mistyped added to inst/WORDLIST).

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

Claude finished reviewView run


All four issues from the previous review round have been fixed in commit 422e319, pushed after the prior review (### Verdict: Needs more work). I re-fetched the PR's current head (422e319) and diffed it against the previously-reviewed commit (7aee252) to verify each fix directly rather than trusting the commit message:

  1. Orphaned heading (running-agents-offline.qmd)#### Falling Back Between Cloud and Local Automatically is restored immediately before the "Air-gapped work aside..." paragraph. Confirmed via a full heading listing of the current file: the heading is back in its original position, and the file's :::/:::{.callout fenced-div counts are balanced (10/10), so nothing else broke in the process.
  2. "A third of the size" math error — both instances (running-agents-offline.qmd and small-local-models-agentic.qmd) now read "at half the parameter count" / "at half the parameter count" — correct for 7B vs. 14B.
  3. Gemma4 tool-capability claim — the unverifiable/implausible gemma4:12b claim ("advertises no tools, calls them anyway") is removed entirely from both files, not just reworded. The replacement claim (qwen2.5-coder emits a syntactically correct tool call as prose in content rather than in the tool_calls field, so the harness never sees it) is internally consistent with the worked JSON example added just above it, and is a well-documented LLM failure mode (template mismatch between the model's function-calling format and the harness's parser), not a fabricated one.
  4. Missing HTTP-status check in the curl test — the snippet now separates the HTTP status via curl -w '\n%{http_code}', splits it from the body with tail -1/sed '$d', and branches on a non-200 status before interpreting tool_calls presence, matching the discipline already used elsewhere in the file. I checked this script for correctness (quoting, tail/sed line-splitting logic, curl's own \n escape handling in -w format strings) and it holds up; the author's commit message states it was verified against all three real branches (agent, no-call, bad-tag/404), which is consistent with what the script does.

I also checked the fix commit itself for new problems and found none: no re-introduction of unicode em/en-dashes (the repo's banned-punctuation check), no new 3+-item lists rendered as prose instead of bullets, no new raw HTML, and the @sec-ai-offline / LiteLLM cross-reference in small-local-models-agentic.qmd now again resolves to a heading with actual content in the target chapter.

No new issues found in this round.

Verdict

Ready for merge

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

💰 Cost: $1.1745 (review) — run

@d-morrison
d-morrison merged commit 6aab262 into main Aug 10, 2026
11 checks passed
@d-morrison
d-morrison deleted the docs/offline-agent-corrections branch August 10, 2026 01:04
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-08-10 01:04 UTC

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.

Offline-agent chapters: stale Ollama context default, missing Anthropic-compatible endpoint, and a recommended model that cannot tool-call

3 participants