docs(13-10): complete Ollama backend + node-side history plan

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-05 18:06:50 -04:00
co-authored by Claude Fable 5
parent 3da9928cc9
commit dba3aecf26
3 changed files with 205 additions and 11 deletions
+2 -2
View File
@@ -345,7 +345,7 @@ Plans:
**Depends on:** Independent of Phases 112 for its UI and content work. Its security model must not contradict Phase 10 (Key-Material Hardening) — coordinate rather than widen. AIUI's own source lives in a **separate repository** (`git.tx1138.com/lfg2025/AIUI`, branch `development`, cloned at `~/Projects/AIUI`), so this phase spans two repos and needs push access to both.
**Plans:** 9/15 plans executed
**Plans:** 10/15 plans executed
Plans:
@@ -369,7 +369,7 @@ Plans:
**Wave 4**
- [x] 13-09-PLAN.md — AIUI delivery: enforced build, pinned commit, live-asset verify, iframe sandbox mechanism (AIUI-04/05)
- [ ] 13-10-PLAN.md — D-04 chain: Ollama tool-calling + D-08 node-side history (AIUI-01)
- [x] 13-10-PLAN.md — D-04 chain: Ollama tool-calling + D-08 node-side history (AIUI-01)
- [ ] 13-11-PLAN.md — SongGrid lit from the real library + the m4a/aac/opus/wma share-mime fix (AIUI-03)
**Wave 5**
+13 -9
View File
@@ -5,15 +5,15 @@ milestone_name: milestone
current_phase: 13
current_phase_name: aiui-functional-conversational-node-control-and-content-surf
status: executing
stopped_at: 13-08 COMPLETE 2026-08-05, next wave 4 (13-10, then 13-11 -- check 13-11 for stale /home/archipelago/Projects/AIUI absolute paths per D-19 before executing it)
last_updated: "2026-08-05T19:17:57.167Z"
stopped_at: 13-10 COMPLETE 2026-08-05, next wave 4 remainder is 13-11 -- check 13-11 for stale /home/archipelago/Projects/AIUI absolute paths per D-19 before executing it
last_updated: "2026-08-05T22:06:22.580Z"
last_activity: 2026-08-05
last_activity_desc: 13-08 complete (D-11 confirm gate + trusted-chrome dialog, checkpoint approved on-device)
last_activity_desc: 13-10 complete (D-04 Ollama tool-calling backend + D-08 node-side chat history)
progress:
total_phases: 13
completed_phases: 2
total_plans: 60
completed_plans: 47
completed_plans: 48
percent: 15
---
@@ -29,11 +29,11 @@ See: .planning/PROJECT.md (updated 2026-07-29)
## Current Position
Phase: 13 (aiui-functional-conversational-node-control-and-content-surf) — EXECUTING
Plan: 9 of 15 complete (13-01, 13-02, 13-03, 13-04, 13-05, 13-06, 13-07, 13-08, 13-09) — next: wave 4 (13-10, 13-11)
Plan: 10 of 15 complete (13-01, 13-02, 13-03, 13-04, 13-05, 13-06, 13-07, 13-08, 13-09, 13-10) — next: wave 4 remainder (13-11)
Status: Ready to execute
Last activity: 2026-08-05 — 13-08 complete (D-11 confirm gate + trusted-chrome dialog, checkpoint approved on-device)
Last activity: 2026-08-05 — 13-10 complete (D-04 Ollama tool-calling backend + D-08 node-side chat history)
Progress: [████████░░] 78%
Progress: [████████░░] 80%
## Performance Metrics
@@ -66,6 +66,7 @@ Progress: [████████░░] 78%
| Phase 01 P01 | n/a-continuation | 2 tasks | 1 files |
| Phase 13 P07 | ~3h45m | 2 tasks | 5 files |
| Phase 13 P08 | ~7h45m (elapsed, w/ session restart) | 3 tasks | 10 files |
| Phase 13 P10 | ~2h45m | 2 tasks | 10 files |
## Accumulated Context
@@ -135,6 +136,9 @@ Decisions are logged in PROJECT.md (10 locked ADRs in the `<decisions>` block +
- [Phase ?]: [Phase 13, 13-08]: System prompt rewritten to make the model call destructive tools directly rather than text-asking for confirmation — the original wording ('every write requires a human confirmation') read as 'collect consent in text first', which skipped the tool call and dropped the user's typed 'confirmed' into a void on the next stateless turn
- [Phase ?]: [Phase 13, 13-08]: CONFIRM_TIMEOUT raised 120s->300s and the full HTTP timeout chain (rpcClient, assistant.chat 420s, AIUI bridge 180s->430s) raised past it, so transport can no longer time out a human reading the confirm dialog before the confirm gate itself does
- [Phase ?]: [Phase 13, 13-08]: Declined actions remembered per-turn in ToolExecCtx, keyed by the same action_key the approval nonce binds; execute_tool refuses a re-ask for that exact action before the gate reopens, closing a retry loop where a declined action kept re-prompting (T-13-50 mechanized)
- [Phase ?]: [Phase 13, 13-10]: OllamaBackend leads the D-04 chain via POST /api/chat (never assist.rs's /api/generate); model_supports_tools() probes /api/show and process-caches the answer, turning AI-SPEC's qwen2.5-coder [ASSUMED] tool-capability note into a runtime fact; FallbackChain falls through to Claude on a transport error mid-turn, not just at initial selection
- [Phase ?]: [Phase 13, 13-10]: history.rs persists the full ChatMessage transcript per CallerScope-derived HistoryKey under data_dir, atomic (temp+rename) and 0600, with wallet/files-category tool-call arguments redacted before disk (verified against raw bytes, not just the struct); chat() persists but does not yet feed prior turns back into live model context (deliberately scoped out, needs Claude tool_use/tool_result id-pairing test budget as a follow-up)
- [Phase ?]: [Phase 13, 13-10]: detect_ollama() and its two containing modules bumped to pub(crate) (api/rpc/mod.rs, api/rpc/mesh/mod.rs, api/rpc/mesh/assistant.rs) so assistant::backends could reuse the existing Ollama probe rather than writing a second one; run_loop (loop_.rs) now returns (answer, full_history) instead of just the answer, both Rule-3 deviations structurally required by the plan's own stated intent
### Pending Todos
@@ -212,8 +216,8 @@ The 5x lifecycle gate was NOT run.
## Session Continuity
Last session: 2026-08-05T19:17:34.921Z
Stopped at: 13-08 COMPLETE 2026-08-05, next wave 4 (13-10, then 13-11 -- check 13-11 for stale /home/archipelago/Projects/AIUI absolute paths per D-19 before executing it)
Last session: 2026-08-05T22:06:22.548Z
Stopped at: 13-10 COMPLETE 2026-08-05, next wave 4 remainder is 13-11 -- check 13-11 for stale /home/archipelago/Projects/AIUI absolute paths per D-19 before executing it
`assistant::` tests incl. `approval_nonce_binds_to_exact_action` individually; dispatcher.rs
untouched; fc09d7a2's tools.rs/grants.rs/backends/mod.rs diffs confirmed rustfmt-only, no
behavior change). Task 2 (ToolConfirmModal.vue, contextBroker.ts, Chat.vue mount,
@@ -0,0 +1,190 @@
---
phase: 13-aiui-functional-conversational-node-control-and-content-surf
plan: 10
subsystem: ai
tags: [rust, ollama, tool-calling, chat-history, persistence, d-04, d-08]
requires:
- phase: 13-aiui-functional-conversational-node-control-and-content-surf
provides: "13-01's Backend trait/BackendTurn seam, 13-05's curated tool registry + grants, 13-08's confirm gate (ConfirmGate, ToolExecCtx) that every destructive tool call passes through regardless of backend"
provides:
- "assistant/backends/ollama.rs: OllamaBackend — POST /api/chat tool-calling adapter with synthesized call ids, pass-through (not re-parsed) arguments, an explicit generation cap, and model_supports_tools() turning AI-SPEC's qwen2.5-coder [ASSUMED] note into a runtime, process-cached fact"
- "assistant/backends/mod.rs: select_backend is now async and D-04-ordered (Ollama first via detect_ollama() reuse, Claude fallback); a new FallbackChain falls through to Claude on an Ollama transport error mid-turn"
- "assistant/history.rs: History/HistoryKey — D-08 node-side chat persistence under data_dir, keyed by CallerScope, atomic (temp+rename) and 0600, with truncation + incremental compaction and wallet/files-category argument redaction"
- "assistant.history / assistant.clear-history RPCs, routed through 13-01's existing assistant. dispatcher arm"
- "run_loop (loop_.rs) now returns (answer, full_history) so chat() can persist the whole completed turn, not just the final answer"
affects: [13-11, 13-12, 13-13, 13-14, 13-15]
tech-stack:
added: []
patterns:
- "Async backend selection with per-call transport fallback: FallbackChain wraps a primary Backend and falls through to a secondary on a send() error, so a leg that answers most of a loop and then drops mid-turn still completes the turn"
- "Process-lifetime capability cache keyed by (base_url, model): model_supports_tools probes once per (server, model) pair and never re-probes, failing CLOSED (not-tool-capable) on any ambiguity"
- "data_dir-scoped JSON persistence, atomic via temp-sibling + rename (music/index.rs::save_atomic's precedent, not streaming/session.rs's in-place write) plus 0600 (grants.rs's precedent)"
- "Caller-resolved category maps instead of a shared registry reference: history.rs takes a HashMap<String, PermissionCategory> the caller builds from tools::registry(), keeping history.rs decoupled from ToolRegistry's private internals and independently unit-testable"
key-files:
created:
- core/archipelago/src/assistant/backends/ollama.rs
- core/archipelago/src/assistant/history.rs
modified:
- core/archipelago/src/assistant/backends/mod.rs
- core/archipelago/src/assistant/mod.rs
- core/archipelago/src/assistant/loop_.rs
- core/archipelago/src/assistant/tools.rs
- core/archipelago/src/api/rpc/assistant_chat.rs
- core/archipelago/src/api/rpc/mod.rs
- core/archipelago/src/api/rpc/mesh/mod.rs
- core/archipelago/src/api/rpc/mesh/assistant.rs
key-decisions:
- "detect_ollama() (api/rpc/mesh/assistant.rs) bumped to pub(crate), with its containing mesh and assistant submodules bumped to pub(crate) mod (api/rpc/mod.rs, api/rpc/mesh/mod.rs) — the minimal visibility change needed to reuse the existing probe from assistant::backends rather than writing a second one, matching this file's own pre-existing pub(crate) mod bitcoin_relay;/pub(crate) mod lnd; convention"
- "run_loop's return type changed from Result<String> to Result<(String, Vec<ChatMessage>)> — structurally necessary so chat() can persist the tool-call/tool-result messages the loop built internally, not only the final answer text. Touches loop_.rs and one pre-existing test call site in tools.rs, both outside this plan's declared files_modified — documented as Rule 3 (structurally necessary), mirroring 13-05's own precedent for the same class of change"
- "history.rs takes a HashMap<String, PermissionCategory> built by the caller (mod.rs::chat()) rather than a &ToolRegistry reference — ToolRegistry's internal tools field is private to the tools module, so a sibling module (history) cannot construct a synthetic registry for its own redaction tests. The HashMap seam keeps history.rs fully unit-testable (including the wallet/files redaction test) without adding a test-only constructor to tools.rs"
- "chat() persists the completed turn to history.rs but does NOT (yet) feed a caller's prior persisted turns back into a NEW turn's live model context — scoped out deliberately: none of Task 2's <behavior> bullets require it, and reconstructing prior ToolCall/ToolResult pairs from the persisted, id-less record in a way that stays correct against Claude's strict tool_use/tool_result id-pairing needs its own test budget. Documented inline in mod.rs::chat()'s doc comment as a named follow-up"
- "OllamaBackend's model selection uses a fixed OLLAMA_DEFAULT_MODEL constant (mirroring assist.rs::DEFAULT_MODEL) rather than reading mesh.assistant-configure's live-configured model — that config lives inside MeshState, reachable only when the mesh service is running, and AIUI's chat path must work with mesh entirely absent. Matches the existing ClaudeBackend's own hardcoded-constant-model pattern from 13-01/13-08"
- "The generation-length cap is set explicitly on every OllamaBackend request but is not verified against a live Ollama server this session (no Ollama running on this dev box — noted in the executor's own gotchas) — the wire-format contract is exercised end-to-end against a local hyper-based HTTP stub instead, and model_supports_tools/select_backend's fall-through path is proven the same way. Live on-device verification (a real question answered by a real local model, and the fall-through when Ollama is stopped) is deferred; see Next Phase Readiness"
requirements-completed: [AIUI-01]
coverage:
- id: D1
description: "OllamaBackend implements the Backend trait against POST /api/chat (never /api/generate), with a messages+tools request, non-streaming requests, an explicit generation cap on every request, synthesized non-empty unique-within-turn tool-call ids, and pass-through (not re-parsed) tool-call arguments"
requirement: AIUI-01
verification:
- kind: unit
ref: "cargo test --package archipelago assistant::backends::ollama::tests:: (10 tests: ollama_uses_chat_endpoint_not_generate, tool_calls_get_synthesized_ids, arguments_object_is_not_string_parsed, text_only_response_maps_to_backend_turn_text, request_is_non_streaming_with_explicit_generation_cap, request_carries_messages_and_tools_arrays, model_supports_tools_reads_capabilities_from_api_show, non_tool_capable_model_falls_through_to_claude, model_supports_tools_caches_for_process_lifetime, unreachable_ollama_returns_transport_error_not_panic)"
status: pass
human_judgment: false
- id: D2
description: "select_backend is async and D-04-ordered — Ollama first (via the reused detect_ollama() probe) when reachable and tool-capable, Claude otherwise; a transport error mid-turn on the Ollama leg falls through to Claude for that same call rather than failing the turn"
requirement: AIUI-01
verification:
- kind: unit
ref: "cargo test --package archipelago assistant::backends::tests:: (3 tests: ollama_is_selectable_truth_table, ollama_transport_error_falls_through_to_next_backend, healthy_primary_never_reaches_secondary)"
status: pass
human_judgment: false
- id: D3
description: "History persists the completed ChatMessage transcript under data_dir per caller (HistoryKey from CallerScope), survives a simulated daemon restart, truncates oversized tool results with a visible marker, compacts older turns into an incrementally-extended running summary once the verbatim window is exceeded, and never persists a wallet/files-category tool's argument value (verified against both the deserialized struct and the raw file bytes)"
requirement: AIUI-01
verification:
- kind: unit
ref: "cargo test --package archipelago assistant::history::tests:: (8 tests: append_persists_and_survives_reload, operator_and_mesh_transcripts_are_separate, long_tool_result_is_truncated_with_marker, compaction_folds_older_turns_into_incremental_summary, clear_removes_only_this_callers_transcript, wallet_tool_arguments_never_reach_the_transcript, project_has_no_path_to_pending_confirmation_state, history_file_is_created_0600)"
status: pass
human_judgment: false
- id: D4
description: "assistant.history / assistant.clear-history are reachable through the existing assistant.* dispatcher arm (dispatcher.rs untouched), each scoped to the calling session's own HistoryKey"
requirement: AIUI-01
verification:
- kind: unit
ref: "git diff --exit-code -- archipelago/src/api/rpc/dispatcher.rs (exit 0); cargo test --package archipelago assistant:: full suite compiles and passes with the new match arms wired in handle_assistant"
status: pass
human_judgment: false
- id: D5
description: "A real question, answered locally by a real Ollama server when reachable and tool-capable, and the observable fall-through-to-Claude behavior when Ollama is stopped, on a real node"
verification: []
human_judgment: true
rationale: "No Ollama server is running on this dev box (confirmed refused at 127.0.0.1:11434, per the executor's own pre-briefed gotcha) — the wire-format contract, capability probe, and fall-through decision are all proven against a local HTTP stub instead, which is a real but not live-Ollama verification. The plan's own top-level <verification> block names this exact live check; it could not be run this session and needs a human/on-device pass on a box with Ollama installed before this can be marked fully proven end-to-end."
duration: ~2h45m (dominated by concurrent-load cargo compiles on a shared 4-core box also running another agent's release build — each full test compile took 16-22 minutes)
completed: 2026-08-05
status: complete
---
# Phase 13 Plan 10: D-04's Local-First Ollama Leg + D-08 Node-Side History Summary
**Ollama tool-calling backend (`POST /api/chat`, synthesized call ids, pass-through arguments, capability-probed fall-through) leading the D-04 chain, plus a per-caller, atomic, compacted chat-history transcript under `data_dir` (D-08) that `assistant.history`/`assistant.clear-history` serve back to the calling session.**
## Performance
- **Duration:** ~2h45m wall-clock (`a245e5d2` 15:18 → `3da9928c` 18:03, 2026-08-05), most of it spent inside three full `cargo test --package archipelago assistant::` compiles (16-22 min each) on a 4-core box that was concurrently running another agent's `cargo build --release` for an unrelated in-flight release cut
- **Tasks:** 2/2 (both `type="auto" tdd="true"`, no checkpoints — plan is fully autonomous)
- **Files modified:** 10 (2 created: `backends/ollama.rs`, `history.rs`; 8 modified)
## Accomplishments
- `OllamaBackend` (`assistant/backends/ollama.rs`) implements the `Backend` trait against Ollama's tool-calling chat endpoint — a different endpoint, request shape, and response shape from `mesh/listener/assist.rs::call_ollama`'s single-shot prompt endpoint, which is left untouched. Ollama's per-call tool-call ids (absent on the wire) are synthesized (`synthesize_call_id`); its already-parsed `function.arguments` object is passed straight through with zero `from_str` calls anywhere in the file (grep-verified).
- `model_supports_tools` queries Ollama's own `/api/show` and caches the answer for the process lifetime, keyed by `(base_url, model)` — this is what turns AI-SPEC's `[ASSUMED]` note about `qwen2.5-coder`'s tool capability into a runtime, checked fact rather than an inherited guess.
- `select_backend` (`backends/mod.rs`) is now `async` and D-04-ordered: it reuses the existing `detect_ollama()` probe (bumped to `pub(crate)`, along with its two containing modules, matching this file's own pre-existing `pub(crate) mod bitcoin_relay;`/`pub(crate) mod lnd;` convention) rather than writing a second probe. A new `FallbackChain` wraps the Ollama leg so a transport error reached mid-turn (not just at initial selection) falls through to Claude for that same call instead of failing the whole turn.
- `History`/`HistoryKey` (`assistant/history.rs`) persist the `ChatMessage` transcript under `data_dir/assistant/history/<key>.json`, atomically (temp-sibling + rename, matching `music/index.rs::save_atomic`'s precedent — a crash mid-write leaves the previous transcript intact) and 0600 (matching `grants.rs`'s convention). `HistoryKey` is derived from `CallerScope`, so an operator's AIUI transcript and a mesh peer's transcript are structurally distinct files.
- Oversized tool results are truncated with a visible marker (`MAX_TOOL_RESULT_CHARS`, a NEW assistant-scoped constant — never `assist.rs`'s LoRa-airtime-tuned `MAX_REPLY_CHARS`); once the verbatim window exceeds `KEEP_VERBATIM_TURNS`, older turns fold into a running summary extended incrementally (proven by asserting the earlier summary text survives verbatim as a substring after further growth, not merely that a summary exists).
- Wallet/files-category tool-call arguments are never persisted, verified against both the deserialized struct field (`None`) and the raw on-disk JSON bytes (the sensitive values themselves never appear in the file) — categories are resolved by the caller (`mod.rs::chat()`) from the same `tools::registry()` `execute_tool` uses, so `history.rs` never carries a second, driftable category list.
- `assistant.history`/`assistant.clear-history` route through 13-01's existing single `assistant.*` dispatcher arm; `dispatcher.rs` is untouched (`git diff --exit-code` confirmed).
- `run_loop` (`loop_.rs`) now returns `(answer, full_history)` instead of just the answer string, so `chat()` can persist the tool-call/tool-result messages the loop built internally, not only the user's question and the final answer.
- Full `assistant::` suite: **50/50 passing** (42 after Task 1 alone — 29 pre-existing + 13 new — then 50 after Task 2's 8 new history tests land), including `confirm::tests::restart_drops_pending_not_executes` (S-09 not weakened).
## Task Commits
Each task was committed atomically, with a Task-1-only intermediate state deliberately reconstructed (temporarily reverting `loop_.rs`'s signature change, `mod.rs`'s history wiring, and `assistant_chat.rs`'s new RPC arms) so Task 1's commit is genuinely self-contained and independently compilable/testable, matching the plan's own 2-task boundary rather than 13-05's precedent of a combined commit:
1. **Task 1: Ollama tool-calling, first in the D-04 chain**`821d8700` (feat). 42/42 `assistant::` tests pass in this commit's own tree state (verified directly, not inferred).
2. **Task 2: Node-side history, scoped by caller, compacted rather than truncated**`3da9928c` (feat). 50/50 `assistant::` tests pass.
**Plan metadata:** this commit (`docs(13-10): complete Ollama backend + node-side history plan`)
## Files Created/Modified
- `core/archipelago/src/assistant/backends/ollama.rs` (new) — `OllamaBackend`, `synthesize_call_id`, `model_supports_tools`, `OLLAMA_BASE_URL`, `OLLAMA_CHAT_URL`, `OLLAMA_DEFAULT_MODEL`, `OLLAMA_NUM_PREDICT`, `OLLAMA_HTTP_TIMEOUT`, `message_to_wire`
- `core/archipelago/src/assistant/backends/mod.rs``select_backend` now async, D-04-ordered; new `BackendId`, `FallbackChain`, `ollama_is_selectable`
- `core/archipelago/src/assistant/history.rs` (new) — `History`, `HistoryKey`, `PersistedMessage`/`PersistedRole`/`PersistedToolCall`/`PersistedToolResult`, `MAX_TOOL_RESULT_CHARS`, `KEEP_VERBATIM_TURNS`, `History::{load,save,append,compact,clear,recent}`
- `core/archipelago/src/assistant/mod.rs``chat()` rewired to await the async `select_backend`, persist the completed turn via `history.rs`; `pub mod history;` added
- `core/archipelago/src/assistant/loop_.rs``run_loop` returns `(String, Vec<ChatMessage>)`; the final answer is now also pushed onto history as a trailing `Assistant` message before returning
- `core/archipelago/src/assistant/tools.rs` — one pre-existing test call site updated to destructure `run_loop`'s new tuple return (mechanical compile fix, Rule 3)
- `core/archipelago/src/api/rpc/assistant_chat.rs``handle_assistant_history`, `handle_assistant_clear_history`, two new match arms in `handle_assistant`
- `core/archipelago/src/api/rpc/mod.rs``mod mesh;``pub(crate) mod mesh;`
- `core/archipelago/src/api/rpc/mesh/mod.rs``mod assistant;``pub(crate) mod assistant;`
- `core/archipelago/src/api/rpc/mesh/assistant.rs``detect_ollama()``pub(crate) async fn detect_ollama()`
## Decisions Made
See `key-decisions` in frontmatter — the `detect_ollama()` visibility bump, `run_loop`'s return-type change (Rule 3, mirroring 13-05's precedent), `history.rs`'s `HashMap<String, PermissionCategory>` seam instead of a `&ToolRegistry` reference, the deliberate scoping decision to persist-but-not-yet-feed-back history into live model context, `OllamaBackend`'s fixed default-model constant (matching `ClaudeBackend`'s own pattern), and the deferred live-Ollama on-device verification.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 - Blocking] `run_loop` needed to return its built history, not just the final answer**
- **Found during:** Task 2, while wiring `chat()` to persist the completed turn
- **Issue:** `history.rs`'s persistence behavior explicitly requires the tool-call/tool-result messages a loop iteration produces (per AI-SPEC's "every turn of the loop... gets serialized to the per-node chat history file"), but `run_loop` (`loop_.rs`, not in this plan's `files_modified`) only ever returned the final answer string — `chat()` had no way to see what the loop built internally.
- **Fix:** `run_loop`'s return type became `Result<(String, Vec<ChatMessage>)>`; the loop now also pushes a final `Assistant` message carrying the answer before returning, so the returned vector IS the complete persisted-worthy transcript for the turn.
- **Files modified:** `core/archipelago/src/assistant/loop_.rs`, plus one pre-existing test call site in `core/archipelago/src/assistant/tools.rs` that needed a mechanical destructure-tuple fix to keep compiling.
- **Verification:** Full `assistant::` suite green (50/50) both immediately after this change and in the final state.
- **Committed in:** `3da9928c` (Task 2 commit)
**2. [Rule 3 - Blocking] `detect_ollama()` was unreachable from `assistant::backends` without a visibility change**
- **Found during:** Task 1, implementing `select_backend`'s D-04 chain
- **Issue:** The plan's own read_first explicitly names `detect_ollama()` (`api/rpc/mesh/assistant.rs`) as the probe to reuse, but it — and its two containing modules (`mesh` inside `api::rpc`, `assistant` inside `mesh`) — were all module-private, invisible outside their own subtree. `crate::assistant::backends` is a completely separate module tree and could not reach it at all without a visibility change.
- **Fix:** Bumped `detect_ollama()` to `pub(crate)`, and its two containing `mod` declarations to `pub(crate) mod` — matching this same file's own pre-existing convention (`pub(crate) mod bitcoin_relay;`, `pub(crate) mod lnd;`) for exactly this kind of cross-module reuse. No behavior change to any existing caller.
- **Files modified:** `core/archipelago/src/api/rpc/mod.rs`, `core/archipelago/src/api/rpc/mesh/mod.rs`, `core/archipelago/src/api/rpc/mesh/assistant.rs`.
- **Verification:** `cargo check --package archipelago` clean; full `assistant::` suite green.
- **Committed in:** `821d8700` (Task 1 commit)
---
**Total deviations:** 2 auto-fixed (both Rule 3 — blocking issues discovered while implementing the plan's own explicitly-stated intent, not scope creep). No architectural changes; both fixes were the minimal change needed to make the plan's own read_first guidance and Task 2's own behavior bullets achievable at all.
**Impact on plan:** Neither deviation touches a file the plan's threat model assigns a mitigation to beyond what was already planned; both are additive visibility/signature changes with no security-relevant behavior change to any existing caller.
## Issues Encountered
- **Shared-box compute contention.** This 4-core dev box was concurrently running another agent's `cargo build --release -p archipelago` (an in-flight release cut in the sibling `archy` worktree) for the entire session. Each full `cargo test --package archipelago assistant::` compile took 16-22 minutes as a result (vs. the executor gotchas' expectation of a much faster targeted-filter run) — no test failures resulted, only elongated wall-clock time. Verified via `ps aux` mid-session that this was genuine resource contention, not a bug in this plan's own code.
- **No live Ollama server available.** Per the executor's own pre-briefed gotcha (127.0.0.1:11434 refuses connections on this box), the plan's top-level `<verification>` item "with Ollama running and tool-capable, a read question is answered locally; with Ollama stopped, the same question falls through to Claude and the fall-through is logged" could not be exercised against a real Ollama process this session. All of `OllamaBackend`'s wire-format behavior, `model_supports_tools`' capability probing, and the D-04 fall-through decision logic ARE proven — against a local `hyper`-based HTTP stub (no mock-HTTP crate exists in this workspace) that faithfully reproduces Ollama's documented `/api/chat` and `/api/show` shapes — but a live-server, on-device pass is still needed before this can be called fully end-to-end proven. See coverage item D5.
## User Setup Required
None — no external service configuration required by this plan. (A user who wants to exercise the Ollama leg on their own node needs Ollama installed and running locally with a tool-capable model pulled; that is a pre-existing product requirement, not new setup this plan introduces.)
## Next Phase Readiness
- D-04's chain is now Ollama-first, Claude-fallback, with a real (not tracer-stub) local-model leg — 13-13's Routstr leg can insert as a third `Backend` without changing the trait or `select_backend`'s shape, exactly as the architecture promised.
- D-08's persistence is fully wired at the storage layer (append/load/compact/redact/truncate) and exposed via `assistant.history`/`assistant.clear-history`, but `chat()` deliberately does not yet feed a caller's prior turns back into a NEW turn's live model context — a scoped, documented follow-up (see `mod.rs::chat()`'s doc comment). Whoever picks this up next should budget real test coverage for Claude's strict `tool_use`/`tool_result` id-pairing requirement before reconstructing persisted turns into live `ChatMessage`s, since `history.rs`'s persisted `PersistedToolCall`/`PersistedToolResult` records deliberately don't carry the original call ids.
- **Live-Ollama on-device verification is the one open item from this plan's own `<verification>` block** — needs a box with Ollama installed and a tool-capable model pulled (13-08-SUMMARY.md's "Known Model-Quality Observations" section, and the flagged `[ASSUMED]` status of `qwen2.5-coder`'s tool-capability tag, are both still worth re-checking against a real model at that time).
- 13-11 (SongGrid + share-mime fix) still carries the documented D-19 stale-`/home/archipelago/Projects/AIUI` absolute-path risk noted in 13-08-SUMMARY.md — check it before executing, unrelated to this plan's own scope.
---
*Phase: 13-aiui-functional-conversational-node-control-and-content-surf*
*Completed: 2026-08-05*
## Self-Check: PASSED
All referenced files found (`backends/ollama.rs`, `history.rs`, this SUMMARY). Both task commit hashes
(`821d8700`, `3da9928c`) verified present in `git log --oneline --all`.