Live observation during the v6.4.1 audit caught all 5 Facebook crons firing
on schedule and producing zero posts. The model was being asked "Call fb_post
ONCE" but the agent loop sent tools=[] to Ollama, so the model hallucinated
the tool-call syntax inside its text body. HallucinationGuard correctly
rejected it (good), but no real post went out.
Three stacked bugs:
1. executePrompt() silently dropped allowedTools. The `as Parameters<...>`
cast let it pass through, but processMessage's overrides type had no
allowedTools member. The comment said it all: "allowedTools is plumbed
but not yet honored end-to-end — logged here so a future hardening pass
can enforce it." This is that pass.
2. allowedTools is stored as a comma-separated STRING ("fb_post,fb_read_feed")
for legacy cron entries. The truthy check passed (strings have .length)
but downstream filters expected an array. Normalize both shapes.
3. fb-afternoon misclassified as 'content' instead of 'social' because the
content rule's `\bwrite.*comparison\b` matched "Write an afternoon
COMPARISON" first. Swap social/content rule order — social wins for any
prompt mentioning Facebook explicitly.
Fix: cron.ts normalizes string→array. agent.ts adds allowedTools to
processMessage overrides; when present, short-circuits the persona /
brain / toolSearch / dangerous filter chain entirely and binds EXACTLY the
requested tools (with [AllowedTools] override bound N/M log + warn on missing
tool names). pipeline.ts swaps rule order.
Hoisted allToolsBackup + toolSearchEnabled above the new short-circuit so
both branches can reach them (used downstream by runAgentLoop).
7848/7850 tests pass. Typecheck clean. UI builds clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>