Skip to content

TODO-pass: continuous-conversation memory (v67), cross-source recall tool, cbm/MCP startup hardening#22

Merged
KerseyFabrications merged 16 commits into
mainfrom
TODO-pass
Jun 18, 2026
Merged

TODO-pass: continuous-conversation memory (v67), cross-source recall tool, cbm/MCP startup hardening#22
KerseyFabrications merged 16 commits into
mainfrom
TODO-pass

Conversation

@KerseyFabrications

Copy link
Copy Markdown
Contributor

Summary

A pass over the TODO/bug-fix backlog: one memory-architecture change, a new
cross-source recall tool, a cluster of MCP/code-project (cbm) startup-robustness
fixes, OpenRouter context sizing, two WebUI rendering fixes, config/startup-log
cleanup, and two file-split refactors to stay under the size limits. 15 commits,
all build/format clean.

Highlights

Memory — single continuous conversations (v67)

  • 4a2f5fe Replace fork-on-compaction (archive parent + create a continuation
    row — the source of the both-locked bug) with an in-conversation compaction
    watermark
    . On compaction, the summary + last-compacted msg id are recorded on
    the same row (single atomic UPDATE, monotonic guard); on reload, context is
    bounded to messages after the watermark + the summary, while the UI still shows
    the full transcript. The summary is re-injected as an assistant message
    carrying a reconstructed [COMPACTED …] marker so the LLM keeps a
    context_expand handle to the originals across reloads. All bounding gated on
    watermark > 0, so un-compacted conversations are byte-identical to before.
    Schema v67; messaging forever-conversation loader goes through the same path;
    old split code left dormant. Live-verified end-to-end, 88/88 CI, +2 unit tests,
    five-agent review applied.
  • 2ecf254 LCM design doc: add Phase 5 (compaction watermark), mark the
    continued_from continuation/chain machinery legacy (pre-v67 only).

Recall / retrieval

  • 087cef3 New high-level recall tool that gathers across memory, notes,
    documents, and calendar in a single call (via a new focus_compose_ex() trim
    override), instead of relying on the LLM to fan out across per-source search
    tools — which it reliably failed to do. Groups results by source with
    read-pointers + a RULES nudge; demotes the per-source tools. Eval (untracked):
    cross-source gather 8% → ~54%; recall fires unprompted.
  • f046fd8 UTF-8-safe focus-block truncation. A fixed-byte cut could land
    mid-multibyte-character, producing invalid UTF-8 in the context_injection
    WebSocket frame → browser drops the socket (1006) → reconnect/reload loop, so
    the conversation never rendered. Added focus_utf8_safe_cap() at both cut sites.

MCP / code-project (cbm) startup robustness

  • c18335c Self-heal the cbm connection instead of dropping it on a boot race
    (mcp-proxy answers initialize before its stdio child is ready for
    tools/list). Keep the server slot on failed startup connect; add lazy
    reconnect on first use; retry tools/list across the cold-start window; fix a
    double-free reachable on any registration failure; weak service ordering.
  • cbfdb19 Capture the cbm name-translation map after auth_db_init (it ran
    before the DB was open, so the map was empty after a plain restart and clean
    names didn't resolve). Re-capture on lazy reconnect.
  • 16684a9 Raise the MCP tool param cap 12 → 20 so cbm's 14-param
    search_graph (DAWN's primary code-search tool) actually registers. The cap is
    validation-only; sizes no array.
  • 68f3456 Force global=true for operator/CLI (dawn-admin) imports
    (requester_user_id <= 0), so an ownerless import is visible to the code_project
    tool and non-admin users instead of being owned by no one.

LLM

  • b54ceba Resolve OpenRouter context sizes from /api/v1/models (cached
    slug → context_length, 1h TTL, single-flight) instead of falling back to a wrong
    128K default for OpenRouter-only vendors. Falls back to the old table probe when
    offline/no key.

WebUI

  • ac230a4 Size Chart.js visuals via a dedicated .dawn-chart-box container +
    maintainAspectRatio:false + post-animation resize() — charts were stuck at
    the default 300×150 in the sandboxed iframe.
  • b5d5611 Render diagram-only turns live: the role:visual handler had no
    fallback when there was no active streaming entry, so the diagram only appeared
    on reload.

Config / startup

  • 15dd30e Write embedding keys to [memory.embeddings] (the section the
    parser reads) instead of [memory.graph_retrieval], so saved config round-trips
    instead of logging "unknown key" and falling back to defaults.
  • 7db9ea8 Quiet two startup-log false alarms: add note_extraction_guard to
    the [memory] allowlist; downgrade optional-MCP-server handshake failures from
    ERROR to WARNING.

Refactors (size discipline)

  • 55df2ac Extract the Import/Export cluster from memory.js (2,094 →
    1,634 lines) into memory_import.js, mirroring the memory_aliases.js pattern.
  • 4241abb Split the 4 music-DB handlers out of admin_socket.c (2,554 →
    2,423, back under the 2,500 hard limit) into admin_socket_music.c.

Testing

  • Build + format clean across all commits; 88/88 ctest -L ci.
  • New unit tests: test_recall_format, test_focus_source (UTF-8 cap), +2 for v67.
  • v67 watermark, recall cross-source gather, cbm namemap/reconnect, and OpenRouter
    context sizing all live-verified per the individual commit messages.

🤖 Generated with Claude Code

admin_socket.c had regrown to 2,554 lines, back over the 2,500-line hard
limit. Extract the 4 music-DB handlers (stats/search/list/rescan) into a
new admin_socket_music.c — the lowest-coupling seam, since they depend
only on send_text_response. Core is now 2,423 lines.

Handlers renamed admin_handle_music_* to avoid a link-time collision with
webui_music_handlers.c's extern handle_music_search (the admin copies were
static before the move, so the clash only surfaced once they went extern).

Pure code movement: dispatch switch and wire protocol unchanged.
Build clean (0 warnings), 88/88 ctest -L ci.
From a startup-log review:
- config_parser: add note_extraction_guard to the [memory] known-keys
  allowlist. It was parsed and applied but missing from the allowlist,
  so it logged a spurious "unknown key (typo?)" warning every boot.
- mcp_client: downgrade the inner handshake initialize/tools-list
  failures from ERROR to WARNING. The bridge already treats a failed
  optional server as skip-and-continue and reports the outcome at WARN;
  an optional MCP server that isn't up yet at boot shouldn't throw red.

Build clean, format clean. (A dawn.toml mis-placement that silently
dropped the [memory.embeddings] recompute keys was fixed locally —
gitignored, not in this commit.)
Under the gateway, any slug that didn't prefix-match the direct-provider
tables fell back to a 128K default — wrong for OpenRouter-only vendors
(deepseek/mistral/qwen/meta) and divergent names. llm_context now fetches
openrouter.ai/api/v1/models on first use, caches slug -> context_length
(256-entry table, 1h TTL, single-flight), and looks up the active model's
true window. Falls back to the old vendor-strip + table probe when
offline / no key / cache miss — no regression. 2MB cap for the catalog
(exceeds the 64K /props cap).

Build clean, format clean; live-verified context now reports correctly
under the gateway.
…ort.js

memory.js was 2,094 lines, over the 1,500 JS soft limit. Move the
Import/Export cluster (16 functions + import state) into a new
memory_import.js, mirroring the memory_aliases.js sibling pattern:
window.DawnMemoryImport with init(ctx) for shared escapeHtml +
onMemoriesChanged, and thin handle*Response forwarders kept on
DawnMemory so dawn.js dispatch is unchanged. memory.js -> 1,634;
index.html loads memory_import.js before memory.js.

Pure code movement, no behavior change. node --check + Prettier clean.
Export live-verified in the Memory panel; Import path moved verbatim
but not re-exercised live.
At boot, cbm-mcp (mcp-proxy fronting a stdio child) answers `initialize`
before its child is ready for `tools/list`, returning -32602. DAWN treated
the failed handshake as fatal, destroyed the server slot, and never
reconnected — so a later code-project import reported "no code server
connected" until a daemon restart, with no logs (the availability check was
a passive cached-state read).

- mcp_client: retry tools/list across the proxy cold-start window (RPC
  error / timeout only); log the JSON-RPC error body so failures aren't a
  bare "(7)".
- mcp_bridge: keep the server slot when the startup connect fails; add
  mcp_bridge_ensure_connected() for lazy reconnect on first use. cbm's
  availability gate now reconnects instead of reporting it permanently
  absent. Tool registration stays at startup only (registry locks post-init).
- mcp_bridge_register_tool: null the moved-in param set immediately so a
  failed registry insert frees it exactly once (fixes a double-free crash
  reachable on any registration failure).
- dawn-server.service: weak After=/Wants=cbm-mcp.service ordering.

Tested live: cbm down at startup keeps the slot (0 connected, not dropped);
reconnect on `mcp reset`/first use succeeds without crashing; warm path
registers all 12 tools. 44 MCP/code-project/auth unit tests pass.
A `dawn-admin` import passes requester user_id 0 ("operator import"), and
with global defaulting to false the row was written user_id=NULL/is_global=0
— owned by no one and shared with no one. Such a row is invisible to every
code_project_db_list_visible() caller: the LLM `code_project` tool and all
non-admin users. Only admins saw it, via the WebUI's list_all fallback (which
is why it showed in the UI but the assistant reported it "not indexed").

Force global=true when requester_user_id <= 0 in code_project_import, so an
ownerless import is shared and visible. WebUI imports (real auth_user_id) are
unaffected. Existing ownerless rows need a one-time `UPDATE ... SET is_global=1`.
The cbm name-translation map (clean project name <-> cbm path slug) was
captured from mcp_bridge_init(), which runs inside tools_register_all() —
before auth_db_init(). code_project_db wasn't open yet, so the capture failed
instantly and silently, leaving the map empty. It only ever populated as a
side effect of an index, so after a plain restart with no new index every cbm
graph tool received an untranslated clean name, cbm returned "project not
found", and the LLM had to fall back to raw "var-lib-dawn-source-*" slugs.

- dawn.c: capture the map after auth_db_init + the MCP admin-grant bootstrap,
  where the DB is open and cbm is connected. This is the real startup capture.
- mcp_bridge_tool.c: drop the premature no-op capture in mcp_bridge_init;
  re-capture on cbm lazy reconnect (mcp_bridge_ensure_connected) so a server
  that was down at boot rebuilds its map when it comes up.

Tested live: startup now logs "captured 6 project mapping(s)" with no index;
clean names resolve on the first cbm call. 20 MCP/code-project tests pass.
cbm's search_graph declares 14 parameters, but the MCP bridge rejected any
tool with more than TOOL_PARAM_MAX (12) properties — so DAWN's primary
code-search tool was silently dropped at registration and never reached the
LLM. TOOL_PARAM_MAX sizes no array anywhere (tool_metadata_t.params is a
pointer + count; the bridge mallocs its set); it's purely a validation cap.
Raise it to 20 — admits real MCP tools like search_graph while still bounding
an untrusted upstream schema. Generic: any MCP server's 13–20-param tools now
register too.

Verified live: cbm now registers 13 tools incl. cbm_search_graph.
The live `role:visual` handler only rendered a <dawn-visual> into the active
streaming entry; when that entry didn't exist — a diagram-only turn, where the
render_visual result arrives during the tool phase before the answer streams —
the `if (sEntry)` block was skipped with no fallback, so the diagram was
silently dropped from the live view and merely stashed for save. It then
appeared only on reload (via extractVisuals in addNormalEntry). finalizeStream
drains pending visuals into the save payload, not the live transcript, so there
was no other live path.

Add the else branch: render the visual as its own assistant entry via
renderVisuals (the same path reload uses). No duplicate — finalize never
renders visuals live.
…k (v67)

Replace fork-on-compaction (archive parent + create a continuation row,
the source of the both-locked bug) with an in-conversation watermark. On
compaction, conv_db_set_compaction_watermark records the summary + last-
compacted msg id on the same row (single atomic UPDATE, monotonic guard) —
no archive, no new conversation. On reload, context is bounded to messages
after the watermark (conv_db_get_messages_after) + the summary, while the UI
still displays the full transcript. The summary is re-injected as an
ASSISTANT message carrying a reconstructed [COMPACTED conv=N msgs=X-Y node=Z
depth=D] marker (shared formatter), so the LLM keeps a context_expand handle
to the originals across reloads. All bounding gated on watermark>0, so
un-compacted conversations are byte-identical to before.

The messaging forever-conversation loader (memory_history_load_from_db) goes
through the same watermark path. Old split code left dormant (warns if hit).
Schema v67 adds context_watermark_msg_id + one-time unlocks legacy archived
conversations. Also fixes a latent bug: reloaded summaries were injected as
system messages and silently dropped by the per-turn two-system-message
rebuild — now assistant role, matching the live marker.

Live-verified end-to-end (watermark set, bounded reload, marker visible,
context_expand fired and retrieved verbatim originals). Build clean, 88/88
CI, +2 unit tests. Five-agent review applied.
Document the v67 fork→watermark change: single continuous conversations,
in-row watermark + bounded reload, assistant-role [COMPACTED] marker
re-injection, dormant split path. Flag the Phase 3/4 continued_from
continuation/chain machinery as legacy (fires only for pre-v67 convs).
…imation resize

Chart.js charts rendered at the default 300x150 in the sandboxed srcdoc iframe.
Root causes: (1) maintainAspectRatio:true caches aspectRatio 2.0 from the default
canvas, so height is always width/2, never the container; (2) resize() during the
~1s entrance animation is a no-op (Chart.animator.running guard), so rAF-timed
nudges did nothing. Fix: wrap the canvas in a definite-size .dawn-chart-box
(canonical Chart.js responsive container), force the canvas to fill it, and flip
maintainAspectRatio:false + resize() after the animation settles. Canvas content
only; SVG/non-chart HTML unaffected. chart.md guideline updated to match.
Focus-block candidate text was truncated at a fixed byte count
(FOCUS_TEXT_MAX_BYTES=4608) with a raw memcpy in two places
(focus_candidate_init and webui_broadcasts capped_text_view). When byte 4608
landed inside a multi-byte UTF-8 character, the truncated text ended in a
partial sequence — invalid UTF-8 — which made the context_injection WebSocket
text frame undecodable. The browser rejected the frame and dropped the socket
(1006), looping reconnect→reload→drop so the conversation never rendered.
DB content was valid; corruption was introduced at focus-compose/broadcast time.

Add focus_utf8_safe_cap() (back the cut up to a character boundary) and use it
at both sites. Surfaced on a conversation full of article punctuation
(em-dashes, curly quotes) whose excerpts crossed the cap mid-character.

Tested: new test_utf8_safe_cap (em-dash straddle, ASCII, whole, NULL);
test_focus_source 21/21.
One high-level 'recall' tool that gathers across memory, notes, documents, and
calendar in a single call, instead of forcing the LLM to fan out across the
per-source search tools (which it reliably failed to do, answering shallowly
from per-turn injection). Wraps the existing focus engine via focus_compose_ex()
(a per-call trim-limit override; focus_compose() is now a NULL-limits wrapper),
groups results by source with read-pointers, adds a RULES nudge, and demotes the
per-source tools so the model calls recall first for what-do-we-know/status
questions.

New: src/tools/recall_tool.c, recall_format.c (+headers), [memory.recall] config,
tests/test_recall_format.c. Removes the 'recall' alias from the memory tool.

Eval (benchmarks/recall_eval, untracked): cross-source gather 8% -> ~54%; recall
fires unprompted; completeness neutral (eval noise-limited at 13 prompts).
Tests: test_recall_format 5/5, test_focus_source 20/20, test_config_validate 18/18.
Build + format clean. Big-3 review applied (summary dead-pointer, redundant
strlen, include_private, config/availability comments).
…etrieval

config_write_toml emitted backfill_on_startup / model_id / recompute_* under
[memory.graph_retrieval], but the parser reads them from [memory.embeddings].
Every settings save misplaced them, so the next load logged 'unknown key'
warnings and silently fell back to defaults. Move the writes into the
embeddings section to match the parser so saved config round-trips.

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.

Pull request overview

This PR is a broad “TODO/bug-fix backlog” pass that upgrades DAWN’s memory compaction model (v67) to keep conversations continuous, introduces a unified cross-source recall tool, and hardens MCP/cbm startup/connect behavior. It also includes WebUI rendering fixes (Chart.js sizing + diagram-only live visuals), OpenRouter context sizing via /api/v1/models, config round-trip fixes, and a couple of file-split refactors to stay within size limits.

Changes:

  • Memory v67: replace fork-on-compaction with an in-conversation compaction watermark + bounded reload, including summary reinjection and new DB migration/tests.
  • Add unified recall tool (formatter + routing nudges + config), plus UTF-8-safe truncation for focus/context-injection payloads.
  • MCP/cbm: startup race hardening (lazy reconnect, handshake retries, logging), OpenRouter context catalog cache, WebUI visual rendering fixes, and code organization refactors.

Reviewed changes

Copilot reviewed 59 out of 59 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
www/js/ui/visual-render.js Adds Chart.js sizing wrapper/CSS + runtime resize logic for iframe-rendered visuals.
www/js/ui/memory.js Splits import/export UI logic out to memory_import.js and adds thin response forwarders.
www/js/ui/memory_import.js New module owning Memory Import/Export modals, state, and WS response handling.
www/js/ui/history.js Updates compaction handling for v67 (no longer continues into a new conversation).
www/js/dawn.js Renders diagram-only visual turns live when no active streaming entry exists.
www/index.html Loads memory_import.js before memory.js.
tool_instructions/render_visual/chart.md Updates Chart.js instructions (no canvas max-height; require maintainAspectRatio:false).
tests/test_recall_format.c Adds unit tests for recall result formatting and pointer/grouping behavior.
tests/test_focus_source.c Adds UTF-8 safe cap unit test and links new helper.
tests/test_config_validate.c Sets valid defaults for new memory.recall.* config fields in tests.
tests/test_auth_db.c Adds v67 compaction watermark and bounded-message-load unit tests.
tests/CMakeLists.txt Adds v67 migration source, links focus helper, and adds new recall formatter test.
src/webui/webui_server.c Restores LLM context bounded by watermark; reinjects compaction summary as assistant message.
src/webui/webui_history.c Leaves legacy continue_conversation handler dormant (warns if invoked); bounds restore path.
src/webui/webui_broadcasts.c UTF-8-safe truncation for broadcast payload text.
src/webui/webui_auth_helpers.c Adds stable system-prompt routing nudge to call recall first for broad context questions.
src/tools/tools_init.c Registers the recall tool unconditionally (availability gated at runtime).
src/tools/recall_tool.c Implements unified recall tool using focus engine + formatter.
src/tools/recall_format.c Formats recall results into grouped source sections with read pointers.
src/tools/memory_tool.c Removes recall alias from memory tool; updates guidance to prefer recall for broad context.
src/tools/mcp_transport_http_sse.c Improves endpoint-ready logging to include resolved endpoint URL.
src/tools/mcp_client.c Adds tools/list handshake retry and improves RPC error logging; downgrades handshake failures to WARNING.
src/tools/mcp_bridge_tool.c Keeps server slots on failed startup connect; adds lazy reconnect; fixes params ownership/double-free risk.
src/tools/mcp_bridge_schema.c Ties MCP schema property cap explicitly to TOOL_PARAM_MAX and clarifies intent.
src/tools/document_search.c Updates tool description to position it as a targeted follow-up vs recall.
src/tools/document_grep_tool.c Updates tool description to position it as a targeted follow-up vs recall.
src/tools/code_project_service.c Forces global=true for ownerless (CLI/operator) imports so projects are visible.
src/tools/code_graph_provider_cbm.c Availability check now attempts lazy reconnect (boot race self-heal).
src/memory/memory_history_loader.c Mirrors v67 bounded restore behavior for messaging history loads.
src/llm/llm_context.c Adds OpenRouter model catalog cache + TTL + single-flight lookup for context sizing; persists v67 watermark.
src/llm/llm_command_parser.c Adds system rules nudge to call recall first for broad “what do we know” questions.
src/dawn.c Captures cbm name-translation map after DB init (and after MCP init).
src/core/focus/focus_source.c Introduces focus_compose_ex() with per-call trim overrides; keeps focus_compose() as wrapper.
src/core/focus/focus_candidate_helpers.c Adds focus_utf8_safe_cap() and uses it for safe truncation of candidate text.
src/config/config_validate.c Validates new [memory.recall] fields and bounds per_source_max.
src/config/config_parser.c Parses [memory.recall] table and allowlists note_extraction_guard.
src/config/config_env.c Fixes TOML writer to emit embedding keys under [memory.embeddings] (parser-consistent).
src/config/config_defaults.c Adds defaults for unified recall tool limits.
src/auth/auth_db_statements.c Adds v67 statements: conv watermark update and bounded message-select.
src/auth/auth_db_schema.c Adds context_watermark_msg_id column to conversations schema.
src/auth/auth_db_migrations.c Applies v67 migration and bumps schema readiness gating.
src/auth/auth_db_migrations_v67.c New v67 migration: add watermark column + unlock legacy archived split conversations.
src/auth/auth_db_conv.c Persists watermark + formats compaction context marker; adds bounded conv_db_get_messages_after().
src/auth/admin_socket.c Removes music handlers from main file; dispatches to extracted admin_socket_music.c.
src/auth/admin_socket_music.c New extracted music admin handler implementations.
services/dawn-server/dawn-server.service Adds weak ordering/wants for cbm-mcp service to reduce boot race window.
include/tools/tool_registry.h Raises TOOL_PARAM_MAX to 20 to admit real MCP tools (e.g., cbm search_graph).
include/tools/recall_tool.h New public header for registering the recall tool.
include/tools/recall_format.h New public header for recall formatter.
include/tools/mcp_bridge.h Adds mcp_bridge_ensure_connected() API.
include/core/focus/focus_source.h Adds focus_limits_t and focus_compose_ex() declaration.
include/core/focus/focus_candidate_helpers.h Declares focus_utf8_safe_cap().
include/config/dawn_config.h Adds recall_config_t under memory config.
include/auth/auth_db.h Adds watermark field and new DB APIs to public auth DB header.
include/auth/auth_db_internal.h Bumps schema version to 67; adds new stmt pointers and migration prototype.
include/auth/admin_socket_internal.h Declares extracted music admin handler entrypoints.
docs/LCM_DESIGN.md Documents Phase 5 compaction watermark model and legacy continuation behavior.
CMakeLists.txt Adds v67 migration and extracted admin music file to build.
cmake/DawnTools.cmake Always compiles recall tool sources and reports it enabled.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread www/js/ui/visual-render.js Outdated
Comment thread www/js/ui/memory_import.js Outdated
Comment thread www/js/ui/memory_import.js
Comment thread src/tools/mcp_bridge_tool.c Outdated
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Memory v67 watermark, cross-source recall tool, MCP/cbm startup hardening
✨ Enhancement 🐞 Bug fix 🕐 40+ Minutes

Grey Divider

Description

• **Memory schema v67**: Replace fork-on-compaction with an in-conversation
 context_watermark_msg_id for bounded reloads.
• **New recall tool**: One-call cross-source gather (memory/notes/docs/calendar) with grouped
 results + pointers.
• **Robustness fixes**: UTF-8-safe truncation, MCP/cbm lazy reconnect + retry, OpenRouter context
 sizing, WebUI renders.
Diagram

graph TD
    subgraph Memory["Memory — Schema v67"]
        WM["conversations\nwatermark_msg_id"]
        CONV["conv_db_set_compaction_watermark"]
        MSGS["conv_db_get_messages_after"]
        LOADER["memory_history_loader"]
        WEBUI_H["webui_server\nrestore context"]
        CONV --> WM
        MSGS --> LOADER
        MSGS --> WEBUI_H
    end

    subgraph Recall["Recall Tool"]
        RT["recall_tool.c"]
        RF["recall_format.c"]
        FCX["focus_compose_ex"]
        UTF["focus_utf8_safe_cap"]
        RT --> FCX
        RT --> RF
        FCX --> UTF
    end

    subgraph MCP["MCP / cbm Startup"]
        MBT["mcp_bridge_tool\nensure_connected"]
        MC["mcp_client\ntools/list retry"]
        CBM["code_graph_provider_cbm"]
        MBT --> MC
        CBM --> MBT
    end

    subgraph LLM["LLM Context"]
        LLC["llm_context.c\ncompact + OpenRouter"]
        OR[("OpenRouter\n/api/v1/models")]
        LLC --> OR
        LLC --> CONV
    end

    subgraph WebUI["WebUI"]
        VR["visual-render.js\nChart.js fix"]
        DJ["dawn.js\ndiagram-only turns"]
        HJS["history.js\nv67 compaction"]
    end

    subgraph Legend
        direction LR
        _db[("Database")] ~~~ _svc(["Service"]) ~~~ _ext{{"External"}}
    end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. LLM fan-out via per-source tool descriptions only
  • ➕ No new tool surface
  • ➕ No code changes to focus engine
  • ➖ Empirically failed (8% cross-source gather rate before this PR)
  • ➖ LLM reliably picks one source and stops
  • ➖ No way to enforce cross-source coverage without a dedicated tool
2. Single focus_compose call in the per-turn injection (larger budget)
  • ➕ No new tool needed
  • ➕ Automatic — fires every turn
  • ➖ Inflates every turn's context even when not needed
  • ➖ No LLM-visible grouping or read-pointers
  • ➖ Competes with the tuned per-turn focus_injection budget
3. Separate recall endpoint / WebSocket message type
  • ➕ Keeps tool registry clean
  • ➕ Could stream results progressively
  • ➖ Requires new client-side handling
  • ➖ Breaks the tool-call paradigm the LLM already understands
  • ➖ More surface area than a single registered tool

Recommendation: The PR's approach is well-chosen for each concern. The compaction watermark (v67) is strictly better than fork-on-compaction: it eliminates the both-locked bug, keeps conversations single and writable, and is zero-risk for un-compacted conversations (watermark=0 gate). The main alternative considered is avoiding a new recall tool, but per-source tool demotion alone was insufficient; a dedicated cross-source tool is justified.

Files changed (59) +2856 / -754

Enhancement (14) +506 / -52
auth_db.hAdd context_watermark_msg_id field and new v67 DB API declarations +63/-4

Add context_watermark_msg_id field and new v67 DB API declarations

• Adds 'context_watermark_msg_id' to 'conversation_t'. Declares 'conv_db_set_compaction_watermark()' (atomic monotonic UPDATE) and 'conv_db_get_messages_after()' (bounded message fetch) and 'conv_db_format_compaction_context()' (reconstructed COMPACTED marker).

include/auth/auth_db.h

auth_db_internal.hBump schema version to 67, add new prepared statement slots +11/-1

Bump schema version to 67, add new prepared statement slots

• Increments 'AUTH_DB_SCHEMA_VERSION' from 66 to 67. Adds 'stmt_msg_get_after' and 'stmt_conv_set_watermark' to the internal DB state struct.

include/auth/auth_db_internal.h

focus_source.hAdd focus_limits_t struct and focus_compose_ex() declaration +42/-0

Add focus_limits_t struct and focus_compose_ex() declaration

• Defines 'focus_limits_t' (per-call top_k/min_score/budget_bytes overrides) and declares 'focus_compose_ex()' which accepts a 'focus_limits_t*' for the recall tool's deep-gather path.

include/core/focus/focus_source.h

auth_db_migrations.cWire v67 migration into the migration ladder +13/-1

Wire v67 migration into the migration ladder

• Calls 'auth_db_migrations_v67()' in the migration sequence and gates the schema version bump on 'v67_ok', consistent with all prior migration entries.

src/auth/auth_db_migrations.c

auth_db_schema.cAdd context_watermark_msg_id column to CREATE TABLE schema +4/-0

Add context_watermark_msg_id column to CREATE TABLE schema

• Adds 'context_watermark_msg_id INTEGER NOT NULL DEFAULT 0' to the conversations table DDL so fresh databases include the column without a migration.

src/auth/auth_db_schema.c

auth_db_statements.cPrepare stmt_msg_get_after and stmt_conv_set_watermark statements +33/-1

Prepare stmt_msg_get_after and stmt_conv_set_watermark statements

• Adds two new prepared statements: 'stmt_msg_get_after' (SELECT messages WHERE id > ?) for bounded watermark reload, and 'stmt_conv_set_watermark' (UPDATE with monotonic guard) for persisting compaction watermarks. Both are finalized in 'auth_db_finalize_statements'.

src/auth/auth_db_statements.c

config_validate.cAdd validation ranges for memory.recall config block +8/-0

Add validation ranges for memory.recall config block

• Adds 'VALIDATE_RANGE_INT/FLOAT' checks for 'memory.recall.top_k', 'budget_bytes', 'per_source_max', and 'min_score' with appropriate bounds.

src/config/config_validate.c

focus_source.cImplement focus_compose_ex with per-call trim overrides; make focus_compose a thin wrapper +34/-11

Implement focus_compose_ex with per-call trim overrides; make focus_compose a thin wrapper

• Renames the core pipeline to 'focus_compose_ex()' and adds per-call override logic for top_k, min_score, and budget_bytes (NULL or zero/negative falls back to config). 'focus_compose()' becomes a thin wrapper passing 'limits=NULL'.

src/core/focus/focus_source.c

llm_command_parser.cAdd recall routing rule to NATIVE_TOOLS_RULES prompt +8/-1

Add recall routing rule to NATIVE_TOOLS_RULES prompt

• Adds rule 7 to the native-tools rules string: instructs the LLM to call 'recall' first for 'what do we know / status / tell me about' questions before jumping to a single per-source tool.

src/llm/llm_command_parser.c

llm_context.cPersist compaction watermark on compact; add OpenRouter /api/v1/models catalog cache +243/-14

Persist compaction watermark on compact; add OpenRouter /api/v1/models catalog cache

• In 'llm_context_compact()', calls 'conv_db_set_compaction_watermark()' after summary node creation, replacing fork-on-compaction. Adds a 256-entry slug→context_length cache for OpenRouter models fetched from '/api/v1/models' (1h TTL, single-flight), with fallback to the offline vendor-strip probe.

src/llm/llm_context.c

document_grep_tool.cDemote document_grep to targeted follow-up in tool description +6/-4

Demote document_grep to targeted follow-up in tool description

• Updates the tool description to position 'document_grep' as a targeted exact-match follow-up, directing broad context questions to 'recall' first.

src/tools/document_grep_tool.c

document_search.cDemote document_search to targeted follow-up in tool description +9/-7

Demote document_search to targeted follow-up in tool description

• Updates the tool description to position 'document_search' as a targeted follow-up for known-document queries, directing broad 'what do we know' questions to 'recall' first.

src/tools/document_search.c

memory_tool.cRemove 'recall' alias from memory tool; demote memory search in description +10/-4

Remove 'recall' alias from memory tool; demote memory search in description

• Removes the 'recall' alias (now a dedicated tool). Updates the memory tool description to direct broad context questions to the 'recall' tool first, reserving 'memory search' for targeted fact lookups.

src/tools/memory_tool.c

webui_auth_helpers.cAdd recall routing nudge to system prompt stable prefix +22/-4

Add recall routing nudge to system prompt stable prefix

• Appends 'k_recall_routing_footer' (CONTEXT GATHERING section) to the tool-discipline footer in the stable system-prompt prefix, steering the LLM to call 'recall' first for broad context questions.

src/webui/webui_auth_helpers.c

Bug fix (18) +426 / -67
focus_candidate_helpers.hDeclare focus_utf8_safe_cap() for UTF-8-safe text truncation +14/-0

Declare focus_utf8_safe_cap() for UTF-8-safe text truncation

• Adds 'focus_utf8_safe_cap(text, max_bytes)' which returns the largest byte length ≤ max_bytes that does not split a UTF-8 character, preventing invalid WebSocket text frames.

include/core/focus/focus_candidate_helpers.h

tool_registry.hRaise TOOL_PARAM_MAX from 12 to 20 for cbm search_graph +8/-5

Raise TOOL_PARAM_MAX from 12 to 20 for cbm search_graph

• Increases 'TOOL_PARAM_MAX' from 12 to 20 to admit cbm's 14-parameter 'search_graph' tool. Adds a comment clarifying this is a validation cap only — no array is sized by it.

include/tools/tool_registry.h

config_env.cFix embedding keys written to wrong TOML section +14/-9

Fix embedding keys written to wrong TOML section

• Moves 'backfill_on_startup', 'model_id', and 'recompute_*' keys from '[memory.graph_retrieval]' to '[memory.embeddings]' (the section the parser reads), so saved config round-trips without 'unknown key' warnings and silent fallback to defaults.

src/config/config_env.c

config_parser.cAdd note_extraction_guard to [memory] allowlist; parse [memory.recall] sub-table +17/-0

Add note_extraction_guard to [memory] allowlist; parse [memory.recall] sub-table

• Adds 'note_extraction_guard' to the known-keys allowlist (eliminating spurious 'unknown key' startup warning). Adds parsing for the new '[memory.recall]' sub-table (top_k, budget_bytes, min_score, per_source_max).

src/config/config_parser.c

focus_candidate_helpers.cImplement focus_utf8_safe_cap; apply it in focus_candidate_init +22/-1

Implement focus_utf8_safe_cap; apply it in focus_candidate_init

• Implements 'focus_utf8_safe_cap()' by backing up past UTF-8 continuation bytes (0b10xxxxxx). Replaces the raw byte cut in 'focus_candidate_init()' with the safe variant to prevent invalid UTF-8 in context_injection frames.

src/core/focus/focus_candidate_helpers.c

dawn.cCapture cbm name-translation map after auth_db_init +14/-0

Capture cbm name-translation map after auth_db_init

• Calls 'code_project_namemap_capture()' after 'auth_db_init()' completes, fixing the ordering bug where the map was captured before the DB was open (leaving it empty after a plain restart).

src/dawn.c

memory_history_loader.cMake forever-conversation loader watermark-aware (v67) +31/-1

Make forever-conversation loader watermark-aware (v67)

• Reads 'context_watermark_msg_id' from the conversation before loading messages. When watermark > 0, prepends the compaction summary as an assistant message and calls 'conv_db_get_messages_after()' instead of the unbounded 'conv_db_get_messages()'.

src/memory/memory_history_loader.c

code_graph_provider_cbm.cUse mcp_bridge_ensure_connected in cbm_is_available for transparent reconnect +6/-1

Use mcp_bridge_ensure_connected in cbm_is_available for transparent reconnect

• Replaces 'mcp_bridge_server_connected()' with 'mcp_bridge_ensure_connected()' in 'cbm_is_available()', turning a boot-race 'no code server connected' error into a transparent lazy reconnect.

src/tools/code_graph_provider_cbm.c

code_project_service.cForce global=true for ownerless (CLI/operator) imports +8/-0

Force global=true for ownerless (CLI/operator) imports

• When 'requester_user_id <= 0', forces 'global=true' so CLI-imported projects are visible to all users via 'code_project_db_list_visible()' instead of being owned by no one.

src/tools/code_project_service.c

mcp_bridge_tool.cKeep server slot on failed startup; add lazy reconnect; fix double-free on registration failure +99/-26

Keep server slot on failed startup; add lazy reconnect; fix double-free on registration failure

• Registers the server slot regardless of startup connect outcome so it remains reconnectable. Implements 'mcp_bridge_ensure_connected()' with per-alias reconnect and cbm namemap refresh. Fixes a double-free by nulling the params source immediately on ownership transfer (before the failure path). Calls 'mcp_bridge_ensure_connected()' at the top of 'mcp_bridge_call_tool()' for self-healing.

src/tools/mcp_bridge_tool.c

mcp_client.cRetry tools/list across proxy cold-start window; downgrade handshake errors to WARNING +37/-2

Retry tools/list across proxy cold-start window; downgrade handshake errors to WARNING

• Adds 'MCP_TOOLS_LIST_RETRIES=3' with 300ms delay between attempts for RPC/timeout errors on 'tools/list'. Downgrades 'initialize' and 'tools/list' failure logs from ERROR to WARNING. Surfaces the JSON-RPC error body in the log for diagnosability.

src/tools/mcp_client.c

mcp_transport_http_sse.cLog resolved SSE endpoint URL for diagnosability +4/-1

Log resolved SSE endpoint URL for diagnosability

• Changes the 'endpoint ready' log to include the resolved endpoint URL (including session_id), making mid-handshake session repoints visible in the log.

src/tools/mcp_transport_http_sse.c

webui_broadcasts.cApply UTF-8-safe truncation in capped_text_view broadcast helper +7/-3

Apply UTF-8-safe truncation in capped_text_view broadcast helper

• Replaces the raw 'FOCUS_TEXT_MAX_BYTES' cut in 'capped_text_view()' with 'focus_utf8_safe_cap()' to prevent mid-multibyte truncation in WebSocket broadcast payloads.

src/webui/webui_broadcasts.c

webui_history.cPass NULL restore_msgs for watermarked conversations; mark continue_conversation dormant +16/-1

Pass NULL restore_msgs for watermarked conversations; mark continue_conversation dormant

• Passes 'NULL' to 'webui_restore_conversation_context()' when a watermark is set so the restore does its own bounded fetch (display stays full). Adds a WARNING log to the now-dormant 'handle_continue_conversation' handler noting it should no longer fire under v67.

src/webui/webui_history.c

webui_server.cBound WebUI context restore to post-watermark messages; use assistant role for summary +26/-6

Bound WebUI context restore to post-watermark messages; use assistant role for summary

• In 'webui_restore_conversation_context()', uses 'conv_db_get_messages_after()' when 'context_watermark_msg_id > 0'. Changes the compaction summary injection from 'system' to 'assistant' role so it survives the per-turn two-system-message rebuild. Uses 'conv_db_format_compaction_context()' to include the '[COMPACTED]' marker.

src/webui/webui_server.c

dawn.jsRender diagram-only turns live without waiting for reload +13/-0

Render diagram-only turns live without waiting for reload

• Adds an else branch to the 'role:visual' handler for when there is no active streaming entry (diagram-only turn). Creates a new assistant transcript entry and calls 'DawnVisualRender.renderVisuals()' immediately so the diagram appears live.

www/js/dawn.js

history.jsRemove fork-on-compaction client logic; keep only trust-tier reset on compaction +7/-11

Remove fork-on-compaction client logic; keep only trust-tier reset on compaction

• Removes the 'requestContinueConversation()' call from 'handleContextCompacted()'. Under v67, compaction no longer splits the conversation, so only the per-turn trust-tier surfaces (DawnContextInjection, DawnSilentObserve) are reset.

www/js/ui/history.js

visual-render.jsFix Chart.js charts stuck at 300×150 in sandboxed iframe +83/-0

Fix Chart.js charts stuck at 300×150 in sandboxed iframe

• Wraps each '<canvas>' in a '.dawn-chart-box' container with a definite pixel height, injects CSS to fill the wrapper, and runs a post-animation script that sets 'maintainAspectRatio:false' and calls 'resize()' on all Chart.js instances after the entrance animation settles.

www/js/ui/visual-render.js

Refactor (5) +759 / -627
admin_socket_internal.hDeclare admin_handle_music_* functions for cross-file dispatch +7/-0

Declare admin_handle_music_* functions for cross-file dispatch

• Adds extern declarations for the four 'admin_handle_music_*' functions now living in 'admin_socket_music.c'.

include/auth/admin_socket_internal.h

admin_socket.cRemove music-DB handlers (moved to admin_socket_music.c); update dispatch calls +4/-135

Remove music-DB handlers (moved to admin_socket_music.c); update dispatch calls

• Removes ~135 lines of music handler code. Updates the dispatch switch to call the new 'admin_handle_music_*' extern functions. Reduces file from 2,554 to 2,423 lines (back under the 2,500-line hard limit).

src/auth/admin_socket.c

admin_socket_music.cNew file: music-DB admin handlers extracted from admin_socket.c +160/-0

New file: music-DB admin handlers extracted from admin_socket.c

• Contains the four music-DB admin handlers (stats/search/list/rescan), renamed 'admin_handle_music_*' to avoid link-time collision with 'webui_music_handlers.c'. Pure code movement; dispatch switch unchanged.

src/auth/admin_socket_music.c

memory.jsRemove Import/Export cluster (moved to memory_import.js); add thin forwarders +32/-492

Remove Import/Export cluster (moved to memory_import.js); add thin forwarders

• Removes ~460 lines of Import/Export code (modals, state, handlers). Adds thin 'handleExportResponse'/'handleImportResponse' forwarders that delegate to 'DawnMemoryImport', preserving the 'DawnMemory.*' dispatch surface in 'dawn.js'.

www/js/ui/memory.js

memory_import.jsNew file: Import/Export surface extracted from memory.js +556/-0

New file: Import/Export surface extracted from memory.js

• Contains the full Import modal (paste/file/preview/commit), Export modal, their private state, and the two WebSocket response handlers. Split from 'memory.js' to keep that file under the 1500-line JS soft limit. Loaded before 'memory.js'; 'DawnMemory' preserves thin forwarders.

www/js/ui/memory_import.js

Tests (5) +276 / -0
CMakeLists.txtAdd v67 migration and recall_format to test build +11/-0

Add v67 migration and recall_format to test build

• Adds 'auth_db_migrations_v67.c' to the auth_db test sources and registers a new 'test_recall_format' unit test target. Also adds 'focus_candidate_helpers.c' to the 'test_focus_source' build.

tests/CMakeLists.txt

test_auth_db.cAdd v67 unit tests: watermark monotonic guard and bounded message fetch +82/-0

Add v67 unit tests: watermark monotonic guard and bounded message fetch

• Adds 'test_compaction_watermark_monotonic' (advances forward, rejects stale rewind, rejects invalid watermark) and 'test_get_messages_after_bounds' (after_id=0 returns all; bounding excludes ≤ after_id).

tests/test_auth_db.c

test_config_validate.cAdd memory.recall defaults to config validation test baseline +6/-0

Add memory.recall defaults to config validation test baseline

• Sets valid 'memory.recall.*' defaults in 'set_valid_defaults()' so the baseline config validates clean with the new recall config block.

tests/test_config_validate.c

test_focus_source.cAdd test_utf8_safe_cap unit test +17/-0

Add test_utf8_safe_cap unit test

• Tests 'focus_utf8_safe_cap()' with an em-dash (3-byte UTF-8 sequence) at various cut points, verifying it never splits a multi-byte character and handles NULL input.

tests/test_focus_source.c

test_recall_format.cNew unit tests for recall result formatter +160/-0

New unit tests for recall result formatter

• Five tests covering: zero-result explicit message, grouping by source family with correct read-pointers, empty-family listing, NULL injected-set overlap note, and injected-id marking. No daemon, DB, or embedding engine required.

tests/test_recall_format.c

Documentation (2) +46 / -5
LCM_DESIGN.mdAdd Phase 5 (compaction watermark) design doc; mark continuation machinery legacy +36/-4

Add Phase 5 (compaction watermark) design doc; mark continuation machinery legacy

• Documents the Phase 5 watermark model: schema, persist/restore/marker-re-injection details, and live test results. Marks 'continued_from' chain-walk and the old split path as legacy (pre-v67 only).

docs/LCM_DESIGN.md

chart.mdUpdate Chart.js template: remove max-height, add maintainAspectRatio:false +10/-1

Update Chart.js template: remove max-height, add maintainAspectRatio:false

• Removes 'max-height:400px' from the canvas template and adds 'maintainAspectRatio: false' to the Chart.js options, matching the new iframe sizing fix and preventing the 0×0 collapse bug.

tool_instructions/render_visual/chart.md

Other (15) +843 / -3
CMakeLists.txtAdd new source files to main build +2/-0

Add new source files to main build

• Adds 'auth_db_migrations_v67.c' and 'admin_socket_music.c' to 'DAWN_SOURCES'. Recall tool sources are added via 'cmake/DawnTools.cmake'.

CMakeLists.txt

DawnTools.cmakeRegister recall_tool.c and recall_format.c in TOOL_SOURCES +8/-0

Register recall_tool.c and recall_format.c in TOOL_SOURCES

• Appends 'src/tools/recall_tool.c' and 'src/tools/recall_format.c' to 'TOOL_SOURCES' unconditionally; availability is gated at runtime.

cmake/DawnTools.cmake

dawn_config.hAdd recall_config_t struct and recall field to memory_config_t +20/-0

Add recall_config_t struct and recall field to memory_config_t

• Defines 'recall_config_t' (top_k, budget_bytes, min_score, per_source_max) and adds a 'recall' field to 'memory_config_t' for the unified recall tool's deep-gather limits.

include/config/dawn_config.h

mcp_bridge.hDeclare mcp_bridge_ensure_connected() for lazy reconnect +20/-0

Declare mcp_bridge_ensure_connected() for lazy reconnect

• Adds 'mcp_bridge_ensure_connected(alias)' which reconnects a server that wasn't ready at startup, enabling self-healing on first use without a daemon restart.

include/tools/mcp_bridge.h

recall_format.hNew header: recall_format_result() declaration +65/-0

New header: recall_format_result() declaration

• Declares 'recall_format_result()' which renders a 'focus_compose_result_t' into a grouped, source-tagged block with per-item read-pointers for the LLM.

include/tools/recall_format.h

recall_tool.hNew header: recall_tool_register() declaration +43/-0

New header: recall_tool_register() declaration

• Declares 'recall_tool_register()' for the new unified cross-source recall tool.

include/tools/recall_tool.h

dawn-server.serviceAdd weak ordering after cbm-mcp.service to shrink boot race window +8/-1

Add weak ordering after cbm-mcp.service to shrink boot race window

• Adds 'After=cbm-mcp.service' and 'Wants=cbm-mcp.service' (weak — DAWN still starts if cbm-mcp is absent). Notes that 'After=' only guarantees cbm-mcp has started, not that its stdio child is ready; the MCP bridge's lazy reconnect is the actual fix.

services/dawn-server/dawn-server.service

auth_db_conv.cImplement conv_db_set_compaction_watermark, conv_db_get_messages_after, conv_db_format_compaction_context +116/-0

Implement conv_db_set_compaction_watermark, conv_db_get_messages_after, conv_db_format_compaction_context

• Implements the three new v67 DB functions. 'conv_db_set_compaction_watermark' does a single atomic UPDATE with a monotonic guard. 'conv_db_get_messages_after' fetches messages with id > watermark using the same ownership JOIN as the unbounded variant. 'conv_db_format_compaction_context' reconstructs the '[COMPACTED conv=N msgs=X-Y node=Z depth=D]' marker from the latest summary node.

src/auth/auth_db_conv.c

auth_db_migrations_v67.cNew schema v67 migration: add context_watermark_msg_id, unlock legacy archived conversations +102/-0

New schema v67 migration: add context_watermark_msg_id, unlock legacy archived conversations

• Adds 'context_watermark_msg_id INTEGER NOT NULL DEFAULT 0' to the conversations table via an idempotent ALTER (probes PRAGMA table_info first). One-time UPDATE clears 'is_archived=1' on all legacy split-archived conversations, making them writable again.

src/auth/auth_db_migrations_v67.c

config_defaults.cSet defaults for memory.recall config block +13/-0

Set defaults for memory.recall config block

• Sets defaults: 'top_k=40', 'budget_bytes=24576' (24KB), 'min_score=0.25', 'per_source_max=16' for the new unified recall tool deep-gather path.

src/config/config_defaults.c

mcp_bridge_schema.cUpdate MCP_SCHEMA_MAX_PROPERTIES comment to reflect TOOL_PARAM_MAX raise +5/-2

Update MCP_SCHEMA_MAX_PROPERTIES comment to reflect TOOL_PARAM_MAX raise

• Updates the comment explaining that 'MCP_SCHEMA_MAX_PROPERTIES' is tied to 'TOOL_PARAM_MAX' (now 20) as a validation/hardening bound, not a storage size.

src/tools/mcp_bridge_schema.c

recall_format.cNew recall result formatter: groups by source family with read-pointers +271/-0

New recall result formatter: groups by source family with read-pointers

• Implements 'recall_format_result()'. Groups candidates into MEMORY (facts/entities/relations), MEMORY (summaries), NOTES & DOCUMENTS, and CALENDAR families. Emits per-item read-pointers ('[memory id N]', 'document_read "label"', calendar hint). Marks already-injected items and lists empty families explicitly.

src/tools/recall_format.c

recall_tool.cNew unified cross-source recall tool implementation +161/-0

New unified cross-source recall tool implementation

• Implements the 'recall' tool: embeds the query, calls 'focus_compose_ex()' with recall-specific limits from 'g_config.memory.recall', and formats results via 'recall_format_result()'. Gracefully degrades to keyword-only when embedding fails. Gated on 'embedding_engine_available()'.

src/tools/recall_tool.c

tools_init.cRegister recall tool in tools_register_all +8/-0

Register recall tool in tools_register_all

• Calls 'recall_tool_register()' unconditionally; 'recall_is_available()' gates at runtime on the embedding engine.

src/tools/tools_init.c

index.htmlLoad memory_import.js before memory.js +1/-0

Load memory_import.js before memory.js

• Adds '<script defer src="/js/ui/memory_import.js">' before 'memory.js' so 'DawnMemoryImport' is available when 'memory.js' initializes.

www/index.html

@qodo-code-review

qodo-code-review Bot commented Jun 18, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (2) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 27 rules

Grey Divider


Action required

1. focus_compose_ex Doxygen params missing 📘 Rule violation ✧ Quality
Description
The new public API focus_compose_ex() has a Doxygen block that documents only limits and omits
@param entries for the other parameters and an @return for its non-void return type. This breaks
the required public API documentation standard.
Code

include/core/focus/focus_source.h[R368-391]

+/**
+ * @brief Like focus_compose(), but with per-call trim-limit overrides.
+ *
+ * Identical pipeline to focus_compose(); the only difference is steps 3-5 (the
+ * min_score / top_k / byte-budget trim) consult `limits` first, falling back to
+ * `g_config.memory.focus_injection` per field when `limits` is NULL or a field
+ * is zero/negative.  Ranking (step 2) is unchanged — weights are always config.
+ *
+ * The unified `recall` tool uses this for a deep cross-source gather at a budget
+ * larger than the per-turn injection.  focus_compose() is a thin wrapper passing
+ * `limits = NULL`.
+ *
+ * @param limits Per-call trim overrides, or NULL to use config for all three.
+ * @see focus_compose for all other parameter semantics.
+ */
+int focus_compose_ex(int user_id,
+                     bool include_private,
+                     const char *query_text,
+                     const float *query_embedding,
+                     size_t embed_dim,
+                     time_t now,
+                     int per_source_max_candidates,
+                     const focus_limits_t *limits,
+                     focus_compose_result_t *out_result);
Evidence
PR Compliance ID 278940 requires Doxygen comments for public API declarations to include @param
for every parameter and @return for non-void. The added focus_compose_ex() comment includes only
@param limits and lacks documentation for the other parameters and the return value.

Rule 278940: Require Doxygen-style comments for all public API functions
include/core/focus/focus_source.h[368-391]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Doxygen comment for the public API declaration `focus_compose_ex(...)` is incomplete: it is missing `@param` tags for most parameters and is missing an `@return` tag.

## Issue Context
The rule requires Doxygen-style comments for public API functions with `@param` entries for each parameter (in order) and `@return` for non-void.

## Fix Focus Areas
- include/core/focus/focus_source.h[368-391]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. recall_tool_register() compared to 0 📘 Rule violation ≡ Correctness
Description
The new recall tool registration checks against the literal 0 instead of the standardized
SUCCESS/FAILURE constants. This violates the project's required convention for status returns
and comparisons.
Code

src/tools/tools_init.c[185]

+   if (recall_tool_register() != 0) {
Evidence
PR Compliance ID 278936 requires functions using integer status codes to return/compare using
SUCCESS/FAILURE rather than raw literals. The added code compares recall_tool_register()
against 0 (!= 0).

Rule 278936: Use standardized SUCCESS/FAILURE constants for function status returns
src/tools/tools_init.c[182-187]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`tools_register_all()` compares `recall_tool_register()` against the literal `0`, but the project requires standardized `SUCCESS`/`FAILURE` constants for status returns and comparisons.

## Issue Context
This was introduced with the new `recall` tool registration block.

## Fix Focus Areas
- src/tools/tools_init.c[182-187]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. mcp_bridge_ensure_connected missing @param ✓ Resolved 📘 Rule violation ✧ Quality
Description
The new public API mcp_bridge_ensure_connected() Doxygen comment omits the required @param entry
for server_alias. This does not meet the project's Doxygen documentation requirement for public
API functions.
Code

include/tools/mcp_bridge.h[R128-146]

+/**
+ * @brief Ensure an upstream server is connected, reconnecting it if it wasn't
+ *        ready at startup.
+ *
+ * Active counterpart to @ref mcp_bridge_server_connected: callers that gate a
+ * code path on a server being usable (e.g. the code-graph provider's
+ * availability check) should use this so a server that came up after DAWN
+ * connects self-heals on first use instead of staying unavailable until a
+ * daemon restart. Blocks for the connect handshake; call off the main loop.
+ *
+ * Restores the connection only — it does NOT register the server's LLM-facing
+ * tools (those are registered at startup against the still-unlocked registry; a
+ * server first reached after init exposes its tools on the next restart). This
+ * is sufficient for direct programmatic callers such as @ref mcp_bridge_call_tool.
+ *
+ * @return SUCCESS if the alias is connected (already or after reconnect);
+ *         FAILURE if it is not configured or the reconnect failed.
+ */
+int mcp_bridge_ensure_connected(const char *server_alias);
Evidence
PR Compliance ID 278940 requires a Doxygen-style comment block with @param entries for each
parameter. The added documentation for mcp_bridge_ensure_connected() includes @return but does
not document its server_alias parameter.

Rule 278940: Require Doxygen-style comments for all public API functions
include/tools/mcp_bridge.h[128-146]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Doxygen comment for `mcp_bridge_ensure_connected(const char *server_alias)` is missing an `@param server_alias` entry.

## Issue Context
Public API declarations must have Doxygen comments with `@param` entries for each parameter.

## Fix Focus Areas
- include/tools/mcp_bridge.h[128-146]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. Watermark no-op hidden 🐞 Bug ≡ Correctness
Description
conv_db_set_compaction_watermark() returns AUTH_DB_SUCCESS even when the UPDATE affects 0 rows,
conflating a benign stale-guard rejection with unexpected cases like wrong owner or missing
conversation. This prevents llm_context_compact() from warning when persistence didn’t actually
occur, so reload may fall back to full-history behavior without any diagnostic signal.
Code

src/auth/auth_db_conv.c[R1199-1202]

+   /* 0 changes = not found / wrong owner / stale (guard rejected the rewind).
+    * Stale is benign, so treat 0 changes as success for the caller's purposes. */
+   AUTH_DB_UNLOCK();
+   return AUTH_DB_SUCCESS;
Evidence
The UPDATE enforces ownership and includes a monotonic guard, so 0-row updates are possible for
multiple reasons; yet the function never checks sqlite3_changes() and the caller only warns on
non-success.

src/auth/auth_db_statements.c[395-402]
src/auth/auth_db_conv.c[1167-1203]
src/llm/llm_context.c[1623-1636]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`conv_db_set_compaction_watermark()` unconditionally returns `AUTH_DB_SUCCESS` after a successful `sqlite3_step()`, even when the UPDATE touches 0 rows.

Because the SQL has `WHERE id=? AND user_id=? AND ? >= context_watermark_msg_id`, 0 rows can mean:
- benign stale watermark (guard rejected rewind), OR
- conversation not found / wrong owner (unexpected).

The function should distinguish these so callers (e.g. `llm_context_compact`) can warn on unexpected failure while still treating stale no-ops as success.

## Issue Context
The caller only logs on non-success, so returning success on all 0-row updates hides real persistence failures.

## Fix Focus Areas
- src/auth/auth_db_conv.c[1167-1203]
- src/auth/auth_db_statements.c[395-402]
- src/llm/llm_context.c[1618-1636]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Unescaped document labels 🐞 Bug ≡ Correctness
Description
recall_format_result() emits document_read "<label>" pointers using a label parsed from candidate
text, but it neither escapes quotes nor handles labels containing ], producing malformed follow-up
pointers. Since document labels are user-provided and stored/rendered verbatim, this can break
recall-driven drilldowns for certain valid labels.
Code

src/tools/recall_format.c[R160-163]

+      case FAM_DOC: {
+         char fname[RECALL_FNAME_MAX];
+         if (filename_from_text(c->text, fname, sizeof(fname)))
+            (void)strbuf_appendf(sb, "   -> document_read \"%s\"", fname);
Evidence
recall_format parses until the first ']' and then wraps the label in double quotes without escaping;
document labels are user-controlled and rendered into candidates verbatim.

src/tools/recall_format.c[81-95]
src/tools/recall_format.c[160-164]
src/tools/document_focus_adapter.c[221-223]
src/tools/document_manage_tool.c[290-321]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`recall_format_result()` extracts a document/note label from candidate text and prints a `document_read "..."` pointer without escaping. Labels containing `"` will break the pointer, and labels containing `]` will be truncated by the current `filename_from_text()` parser.

## Issue Context
- Document candidates are rendered as `[%s] %s` using the stored filename/label.
- Labels are user-provided (via document_manage) and not restricted from containing these characters.

## Fix Focus Areas
- src/tools/recall_format.c[81-95]
- src/tools/recall_format.c[160-164]
- src/tools/document_focus_adapter.c[221-223]
- src/tools/document_manage_tool.c[290-321]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Unchecked reconnect failure 🐞 Bug ☼ Reliability
Description
mcp_bridge_call_tool() invokes mcp_bridge_ensure_connected() but ignores its return value, so a
failed reconnect attempt is neither surfaced nor used to short-circuit before issuing tools/call.
This leads to inconsistent failure handling/logging and can waste time attempting calls when the
bridge already knows the server is unavailable.
Code

src/tools/mcp_bridge_tool.c[R717-722]

+   /* Self-heal: reconnect a server that wasn't ready at startup before the call.
+    * (mcp_client_call also lazily reconnects, but doing it here also registers
+    * the server's tools on first success — and gives a clean failure if the
+    * server is genuinely down.) */
+   mcp_bridge_ensure_connected(server_alias);
+
Evidence
mcp_bridge_ensure_connected() returns FAILURE when the server is not configured or connection fails,
but mcp_bridge_call_tool() does not branch on that result and proceeds anyway.

src/tools/mcp_bridge_tool.c[642-670]
src/tools/mcp_bridge_tool.c[705-754]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`mcp_bridge_call_tool()` calls `mcp_bridge_ensure_connected(server_alias)` but does not check the return code. If the server alias is not configured or the connect fails, the function still proceeds to build and send the `tools/call` request, producing inconsistent error behavior and muddy logs.

## Issue Context
`mcp_bridge_ensure_connected()` explicitly returns `FAILURE` when it cannot connect. The caller should honor this and return a clear failure early.

## Fix Focus Areas
- src/tools/mcp_bridge_tool.c[642-754]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

7. Missing strcasecmp header 🐞 Bug ⚙ Maintainability
Description
src/llm/llm_context.c uses strcasecmp() in openrouter_lookup_context() but does not include
<strings.h>, which can cause implicit-declaration warnings or build failures on stricter toolchains.
This is a portability regression compared to other files that explicitly include <strings.h> for
strcasecmp.
Code

src/llm/llm_context.c[R530-536]

+static int openrouter_lookup_context(const char *slug) {
+   if (!slug || slug[0] == '\0') {
+      return 0;
+   }
+   for (int i = 0; i < s_state.or_model_count; i++) {
+      if (strcasecmp(slug, s_state.or_models[i].slug) == 0) {
+         return s_state.or_models[i].context_length;
Evidence
The include list lacks <strings.h> while openrouter_lookup_context calls strcasecmp(); other repo
files show the intended include pattern.

src/llm/llm_context.c[24-35]
src/llm/llm_context.c[530-539]
src/audio/audio_backend.c[37-43]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`llm_context.c` uses `strcasecmp()` but does not include `<strings.h>` where it is declared on POSIX.

## Issue Context
Other code in the repo includes `<strings.h>` explicitly when using `strcasecmp()`, so this file should follow the same convention.

## Fix Focus Areas
- src/llm/llm_context.c[24-35]
- src/llm/llm_context.c[530-539]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread src/tools/tools_init.c
Comment thread include/core/focus/focus_source.h
Comment thread include/tools/mcp_bridge.h
- visual-render: gate the Chart.js sizing wrapper on `new Chart(` rather
  than any <canvas>, so a non-chart canvas visual isn't wrapped/clipped.
- memory_import: give escapeHtml a real entity-escaping fallback (the old
  one returned input unchanged — an innerHTML XSS sink if ctx is missing);
  fix the stale "matches memory_aliases.js" comment.
- memory_import: release an existing focus trap before installing a new
  one so a double-open can't leak competing traps.
- mcp_bridge_tool: correct the self-heal comment (ensure_connected does
  NOT register tools, per the header) and note the return is intentionally
  unchecked (the NULL-client lookup below reports a failed reconnect).
- focus_source.h / mcp_bridge.h: complete Doxygen (@return, @param).

Comment/doc-only on the C side. test_focus_source 21/21,
test_recall_format 5/5; format + node --check clean.
@KerseyFabrications KerseyFabrications merged commit 971b585 into main Jun 18, 2026
5 checks passed
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.

2 participants