The flavor probe gains a third answer: the wrapper names itself on
/health, so behind OLLAMA_HOST tatlock now distinguishes boilerroom,
a bare llama-server, and Ollama. Through the wrapper each pipeline
phase is a named session with the decided eviction ranking —
tatlock-steward/-orchestrate/-synthesize at 40, librarian at 30,
lower parks sooner (webber will sit at 20; Open WebUI stays
session-less and can never evict anyone). Against a bare llama-server
the raw id_slot pins survive unchanged, Ollama gets neither, and an
unprobed flavor sends nothing rather than guessing — the backend
stays swappable by env alone. tool_choice through the wrapper follows
the llama-server rule, since that is who answers.
The wrapper's balancing and compaction-due signals are read
everywhere: an httpx response hook on the provider covers every
PydanticAI call, streams included, and the steward's raw call reads
the body extras. Acting on compaction_due is a future ticket — the
signal just must not pass silently.
Verified end to end against the live wrapper: the dev server probed
flavor=boilerroom, a full pipeline turn answered in 5.7 s, and
GET /sessions showed all three phase sessions resident at rank 40
with engine-reported occupancies. The demonstration also filled the
production slot map — session-less prod delegations would have 503d
— cleared by a wrapper restart and filed as boilerroom T-11 (sessions
need an exit). A latent test flaw surfaced too: the ollama
tool_choice test relied on the dev backend probing as ollama; it now
pins the flavor it claims to test.
27 selector tests (9 new), 677 total green; three mutations shown to
fail their tests (librarian rank, the wrapper branch, the no-nudge
set); the new wrapper contract class runs 10/10 against the live
boundary.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each pipeline phase owns one llama-server slot (steward 0, orchestrator
1, synthesizer 2), carried as id_slot in extra_body through the same
mechanism tool_choice already uses, so the phase's stable prompt prefix
stays in that slot's KV cache and a turn re-prefills only its new
tokens. Off by default; a no-op on the Claude backend and ignored by
Ollama, so the flag is safe on any backend and the cutover itself stays
a pure env swap.
The merge helper preserves existing extra_body keys — mutation-checked
(dropping the merge fails exactly the test written for it).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three native-API touchpoints converted — steward /api/generate to
/v1/chat/completions, embeddings /api/embeddings to /v1/embeddings,
health /api/tags to /v1/models — so the backend behind OLLAMA_HOST is
swappable by env alone. This makes the serving plan's "tatlock needs
zero changes" claim true for the llama-server cutover and for forge
after it. EMBEDDING_HOST (default: OLLAMA_HOST) lets gen and embed
point at different servers, which the boilerroom stack needs.
The dead OllamaClient goes with it: a native-API client nothing
imported, whose presence would make the post-cutover "no native
endpoints" grep lie.
Contract tests rewritten to mirror the new requests and extended with
the embeddings shape (dim must match the configured Qdrant dimension).
All pass against live Ollama's /v1 — deployable before any cutover.
Both new assertions mutation-checked via env overrides (bogus model,
wrong dim: each fails). The Anthropic contract now skips on 401: the
configured key is deliberately revoked per workspace D-11, which is
"fallback disabled", not a boundary break. Embedding continuity across
backends was measured separately: same nomic bytes, cosine 1.0000.
663 unit tests pass; ruff clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Twelve gain `-> None`, each confirmed by AST to contain no returning
`return` and no `yield` rather than by reading the name and assuming.
The three context-manager exits gain the canonical
type[BaseException]/BaseException/TracebackType argument triple.
Both files taking TracebackType needed the import, and inserting it
before the first import broke ruff's I001 — lint was exit 0 at the
baseline commit, verified by stashing this work and re-running, so that
breakage was mine. Fixed with `ruff check --fix` on the two files, which
placed the import in sorted position.
86 errors -> 75; no-untyped-def 29 -> 14.
Suite: 658 passed. The baseline was 657 passed with one failure in
test_tatlock_tool_call_logging_calculator, which asserts on the content
of a live model's reply. It passing here is nondeterminism, NOT evidence
this commit fixed anything, and it may fail again on the next run.
Co-Authored-By: Claude <noreply@anthropic.com>
Mechanical only, and separated from the judgment calls that follow so the
reviewable changes are not buried in a 98-file whitespace diff.
227 automatic fixes: 60 blank lines carrying whitespace, 60 unsorted import
blocks, 34 Optional[X] to X | None, 28 unused imports, 16 deprecated typing
imports, 12 datetime.timezone.utc to datetime.UTC, and assorted smaller
modernisations. Then `ruff format` over src and tests: 98 files reformatted,
35 already conforming.
No file among the unused-import findings defines __all__ or is an __init__.py,
so nothing here removes a re-export.
`make test`: 658 passed, unchanged from HEAD.
Two things observed while verifying, neither addressed here:
`pytest tests/` cannot collect — tests/e2e/test_orchestration_e2e.py uses an
`e2e` marker that is not registered, and the config is strict about markers.
This fails identically at HEAD, so it predates this change; `make test` passes
because it ignores tests/e2e, tests/integration and tests/contracts.
test_tatlock_tool_call_logging_calculator is flaky. It failed once in a full run
with these changes and passed on the next, passes in isolation with them, and
fails in isolation at HEAD. It is order- or timing-dependent, not a regression
from this commit — established by running the full suite both ways rather than
by reasoning about which change could have caused it.
Co-Authored-By: Claude <noreply@anthropic.com>
Rolls back the claudification backend preference: PREFER_CLOUD_BACKEND now
defaults to false, resolve_backend() picks Ollama first and uses Claude when
explicitly preferred or when the new Ollama startup health check fails. The
Steward retries mid-request failures on the other backend in both directions.
Also hardens the fallback itself: Anthropic SDK imports are lazy so a broken
anthropic package degrades to Ollama-only instead of crashing at import time
(root cause of the production outage since April), anthropic is pinned to a
pydantic-ai-1.27-compatible range, ANTHROPIC_MODEL defaults to claude-sonnet-5
(sonnet-4-20250514 retired 2026-06-15), sampling parameters are stripped from
Claude calls (Sonnet 5 rejects them), and the Steward timeout is configurable
(STEWARD_TIMEOUT, default 60s) since gemma4 needs ~35s warm for analysis.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All agents now prefer Claude API when ANTHROPIC_API_KEY is configured,
with automatic fallback to Ollama when offline or unconfigured. New
src/anthropic/ module provides model selection via get_model() factory.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Paperless document search to HybridRAG pipeline
- Add volatile cache (weather, forecast, news, stocks) to HybridRAG
- Add include_documents and include_volatile params to hybrid_search
- Add 📑 and ⚡ icons for document/volatile sources
- Update Librarian prompt with new data source awareness
- Fix Biographer routing: personal memory queries now route correctly
- Add location keywords to Steward pre-fetch logic
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes several issues with the web search migration to Librarian:
- Update Steward routing guidelines for web search/weather → Librarian
- Register search_web, read_url, read_urls_batch tools with Librarian agent
- Update Librarian system prompt with web search documentation
- Fix query enrichment not being passed to delegations (location context)
- Add URL reading keywords to RESEARCH action type detection
Weather queries now automatically include user's stored location from
the Biographer, enabling location-aware search results.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Updates Steward's output format to structured delegation format:
- DELEGATE: [capability] to [action] [task]
- REASON: [explanation]
- COMPLEXITY: [simple/moderate/complex]
- CONTEXT: [relevant history or "none"]
Also adds guidance for conversation memory queries (handled by
Tatlock directly, not delegated to Librarian).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update Librarian capability description to highlight CREATE/UPDATE/SEARCH
- Add specific Steward guidelines for wiki creation, updates, and research
- Add dynamic time injection to user prompts for temporal awareness
- Expand domains to include 'create', 'write', 'update'
- Update test to match new capability description
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>