fix(chat): gate qwen3 thinking on message content instead of always-on — root-cause fix for the 5–20s TTFT variance on qwen3:14b. Frontend no longer hardcodes think=true; _should_think is authoritative and now a real content classifier (length + keyword). Timing logs split first_token_ms / thinking_ms / ttft_ms so thinking duration is no longer hidden in ttft.
test(llm): lock in _should_think classifier; drop briefing think overrides — 38 parametrized tests; briefing /discuss-* actions defer to the classifier via new "discuss" keyword.
fix(llm): correct context sizing, honor think requests, broaden delete — pick_num_ctx now includes tool schemas in estimate (fixes silent prompt truncation at 14K+ tokens with num_ctx=8192); delete_note description covers all five note types.
fix(llm): normalize Ollama model tags to lowercase — avoids 400s when tags were pulled with mixed case.
fix(llm): switch default background model to gemma3:4b — replaces qwen2.5:3b which produced broken summaries.
Tools refactor
refactor(tools): decorator-based tool registry replaces monolithic tools.py — 2566-line tools.py split into tools/ package with @tool decorator. Single source of truth per tool (schema + metadata + impl). Briefing eligibility via briefing=True flag; CalDAV/SearXNG via requires= metadata.
refactor(tools): consolidate LLM tools from 42 to 38 — merges create_task into create_note, delete_task into delete_note, entity create/update → save_person / save_place, get_note → read_note, shared check_duplicate() helper.
Research
feat(research): linked section notes + executive summary in index — index note with 2–3 paragraph summary + clickable links to section notes; section notes get parent_id pointing to the index; lowered minimum sections to 2 with one retry before monolithic fallback.
fix(research): show exception type when error message is empty.
Chat / UI
fix(chat): let backend auto-generate conversation titles — Knowledge and Workspace views were passing explicit titles, blocking auto-title. Now pass none.
fix(chat): prevent New Chat button from stretching full height.
fix(knowledge): task cards showed 'LIST' badge instead of 'Task'.
CI
ci: new ci-runner base image + uv/ruff/node_modules cache — typecheck 9m41s → ~30s, lint 13s → 2s, test via uv.
fix(ci): install setuptools before http-ece for uv.
fix(ci): make registry cache export non-fatal.
fix(test): update mock path for tool registry refactor.
Test plan
ci.yml green on dev HEAD
build.yml green on dev HEAD
38 new _should_think tests pass
Smoke test chat TTFT on qwen3:14b after merge/deploy to confirm variance gone
## Highlights
**LLM / responsiveness**
- `fix(chat): gate qwen3 thinking on message content instead of always-on` — root-cause fix for the 5–20s TTFT variance on qwen3:14b. Frontend no longer hardcodes `think=true`; `_should_think` is authoritative and now a real content classifier (length + keyword). Timing logs split `first_token_ms` / `thinking_ms` / `ttft_ms` so thinking duration is no longer hidden in ttft.
- `test(llm): lock in _should_think classifier; drop briefing think overrides` — 38 parametrized tests; briefing `/discuss-*` actions defer to the classifier via new `"discuss"` keyword.
- `fix(llm): correct context sizing, honor think requests, broaden delete` — `pick_num_ctx` now includes tool schemas in estimate (fixes silent prompt truncation at 14K+ tokens with num_ctx=8192); `delete_note` description covers all five note types.
- `fix(llm): normalize Ollama model tags to lowercase` — avoids 400s when tags were pulled with mixed case.
- `fix(llm): surface Ollama error body; refresh pre-gemma3 summaries`.
- `fix(llm): switch default background model to gemma3:4b` — replaces qwen2.5:3b which produced broken summaries.
**Tools refactor**
- `refactor(tools): decorator-based tool registry replaces monolithic tools.py` — 2566-line tools.py split into `tools/` package with `@tool` decorator. Single source of truth per tool (schema + metadata + impl). Briefing eligibility via `briefing=True` flag; CalDAV/SearXNG via `requires=` metadata.
- `refactor(tools): consolidate LLM tools from 42 to 38` — merges `create_task` into `create_note`, `delete_task` into `delete_note`, entity create/update → `save_person` / `save_place`, `get_note` → `read_note`, shared `check_duplicate()` helper.
**Research**
- `feat(research): linked section notes + executive summary in index` — index note with 2–3 paragraph summary + clickable links to section notes; section notes get `parent_id` pointing to the index; lowered minimum sections to 2 with one retry before monolithic fallback.
- `fix(research): show exception type when error message is empty`.
**Chat / UI**
- `fix(chat): let backend auto-generate conversation titles` — Knowledge and Workspace views were passing explicit titles, blocking auto-title. Now pass none.
- `fix(chat): prevent New Chat button from stretching full height`.
- `fix(knowledge): task cards showed 'LIST' badge instead of 'Task'`.
**CI**
- `ci: new ci-runner base image + uv/ruff/node_modules cache` — typecheck 9m41s → ~30s, lint 13s → 2s, test via uv.
- `fix(ci): install setuptools before http-ece for uv`.
- `fix(ci): make registry cache export non-fatal`.
- `fix(test): update mock path for tool registry refactor`.
## Test plan
- [x] `ci.yml` green on dev HEAD
- [x] `build.yml` green on dev HEAD
- [x] 38 new `_should_think` tests pass
- [ ] Smoke test chat TTFT on qwen3:14b after merge/deploy to confirm variance gone
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Highlights
LLM / responsiveness
fix(chat): gate qwen3 thinking on message content instead of always-on— root-cause fix for the 5–20s TTFT variance on qwen3:14b. Frontend no longer hardcodesthink=true;_should_thinkis authoritative and now a real content classifier (length + keyword). Timing logs splitfirst_token_ms/thinking_ms/ttft_msso thinking duration is no longer hidden in ttft.test(llm): lock in _should_think classifier; drop briefing think overrides— 38 parametrized tests; briefing/discuss-*actions defer to the classifier via new"discuss"keyword.fix(llm): correct context sizing, honor think requests, broaden delete—pick_num_ctxnow includes tool schemas in estimate (fixes silent prompt truncation at 14K+ tokens with num_ctx=8192);delete_notedescription covers all five note types.fix(llm): normalize Ollama model tags to lowercase— avoids 400s when tags were pulled with mixed case.fix(llm): surface Ollama error body; refresh pre-gemma3 summaries.fix(llm): switch default background model to gemma3:4b— replaces qwen2.5:3b which produced broken summaries.Tools refactor
refactor(tools): decorator-based tool registry replaces monolithic tools.py— 2566-line tools.py split intotools/package with@tooldecorator. Single source of truth per tool (schema + metadata + impl). Briefing eligibility viabriefing=Trueflag; CalDAV/SearXNG viarequires=metadata.refactor(tools): consolidate LLM tools from 42 to 38— mergescreate_taskintocreate_note,delete_taskintodelete_note, entity create/update →save_person/save_place,get_note→read_note, sharedcheck_duplicate()helper.Research
feat(research): linked section notes + executive summary in index— index note with 2–3 paragraph summary + clickable links to section notes; section notes getparent_idpointing to the index; lowered minimum sections to 2 with one retry before monolithic fallback.fix(research): show exception type when error message is empty.Chat / UI
fix(chat): let backend auto-generate conversation titles— Knowledge and Workspace views were passing explicit titles, blocking auto-title. Now pass none.fix(chat): prevent New Chat button from stretching full height.fix(knowledge): task cards showed 'LIST' badge instead of 'Task'.CI
ci: new ci-runner base image + uv/ruff/node_modules cache— typecheck 9m41s → ~30s, lint 13s → 2s, test via uv.fix(ci): install setuptools before http-ece for uv.fix(ci): make registry cache export non-fatal.fix(test): update mock path for tool registry refactor.Test plan
ci.ymlgreen on dev HEADbuild.ymlgreen on dev HEAD_should_thinktests pass