May 7, 2026

Memory recall stops faking its own tool calls

Memory recall now delegates to the real schedule and document tools instead of quietly duplicating their queries, and a redundant LLM call comes out of the thinking gate.

Today was about closing a gap between what memory recall said it did and what actually happened underneath. Recall could surface a scheduled item or a document artifact, but it did so by running its own private query and faking the bookkeeping around it — no real tool call, no proper record in the act trail. That’s the kind of shortcut that looks fine until you need to trust the history of what the assistant actually did.

The fix was to stop duplicating logic and start reusing it. When recall needs scheduled items, it now calls the schedule ability’s own search action through the same handleTool() path any other tool call goes through — a genuine vec0 embedding match over pending, non-hidden scheduled items, not a bespoke filter bolted onto the recall engine. Document artifacts got the same treatment: recall delegates straight to the document ability instead of re-running dgs.recall(kinds=[KIND_DOCUMENT]) a second time under a different name. One code path, one source of truth, and now a correct entry lands in the act trail every time recall pulls in a schedule item or a document — dispatch, recording, and history all go through the front door.

Then I pulled the thread further and decided recall shouldn’t be reaching into schedules at all. If the LLM wants schedule data, it should ask for it directly with the schedule tool — recall fanning out into schedules on every call was scope creep dressed up as convenience, and it made recall’s behavior harder to reason about. Pulled it back out the same day it went in, once the pattern was clear.

The other half of today was prompt-level, not code-level: recall was tested against multi-topic queries and it wasn’t expanding across all of them cleanly. Rather than build clause-splitting into the recall engine itself — more surface area, more edge cases — I told the LLM to make one recall call per topic instead. Simpler contract, and it closes the expansion gap without adding a single line to the retrieval path. Sometimes the right fix is teaching the model to ask better, not teaching the engine to guess harder.

Separately, the thinking gate got lighter. Every time the deliberation classifier flagged a turn as “high,” it triggered a full blocking exploration call — system prompt, tools, the works — adding 60 to 105 seconds before the real response even started. That call was redundant: the reasoning loop already does native extended thinking through thinking_mode once thinking mode is active for the turn. Cutting the exploration call removed 178 lines and a chunk of latency on exactly the turns where speed matters most, without losing any of the reasoning quality — the ONNX classifier, the EMA smoothing, and the bucket assignment all stayed intact.

Small housekeeping rounded out the day: document creation from raw text was silently leaving clean_text null, which meant any query or assertion against that column came back empty — a test caught it, and the create path now fills it in immediately.

None of this changes what Chalie can do today, but it changes how much you can trust what it says it did. Recall now leaves a real trail instead of a shadow one, which matters more with every new memory feature stacked on top of it.

  • Schedule and document recall now delegate to the real ability tools via handleTool() instead of duplicating queries

  • Recall no longer fans out into schedules on its own — the LLM calls the schedule tool directly when it needs one

  • LLM guidance now asks for one recall call per topic, closing a multi-topic expansion gap at the prompt level

  • Removed a blocking 60-105s exploration call from the thinking gate; the loop’s native thinking_mode covers the same ground

  • Document creation now populates clean_text, fixing NULL reads on newly created text documents