fix(10-02b): wire availableTools from runtime catalog at AppDelegate

Plan 10-02b / B-01 root-cause fix.

App/AppDelegate.swift:1274 previously constructed AgentOrchestrator
with `availableTools: []` — a hardcoded empty array. The 2026-05-07
live verification of Plan 10-02 surfaced this as the substrate bug
silencing every tool call across the agent: get_time, get_clipboard,
run_applescript, search_memory, forget_fact, vision tools, and the
four Phase 10-01 self-knowledge tools (list_audio_devices,
get_active_audio_route, get_self_state, list_camera_devices).

The model saw the system-prompt preamble naming tools but had no
schemas in the API tools[] field, so it hallucinated JSON tool
definitions into chat ("I'll check that for you. {\"name\": \"get_active_audio_route\", \"arguments\": {}}")
instead of dispatching real tool_use blocks.

Fix: source the catalog lazily via the new
`availableToolsResolver:` parameter from BOTH:
  - mcpRuntime.client.toolCatalog() — stdio helper tools
  - inProcessToolRegistry.toolSchemas() — memory + self-knowledge tools

The resolver fires once per outer-loop iteration in
AgentOrchestrator.runTurnLoop, NOT per stream event. By the time the
user submits their first turn, the install-order chain
  installMemory → installVision → installAgent → installVoice
                                              → installSelfKnowledgeTools
has populated all 7 in-process tools and all 3 stdio tools (10 total
catalog entries) — no install-order coupling needed at construction
time.

Verification:
- bash scripts/check-app-builds.sh PASS
- All 16 standalone boundary gates PASS
- Live re-verification of AC-05 ("What microphone are you using?")
  is the human-verify gate; see Live Verification section in
  10-02b-SUMMARY.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
james.maesandClaude Opus 4.7 committed 2026-05-07 07:42:39 -05:00
1 parent 3eeb3c178e
commit e0c0310b64
1 file changed
+29
+29
View File
@@ -1256,6 +1256,34 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
)
return
}
// Plan 10-02b / B-01: source the tool catalog lazily from BOTH
// the in-process registry (memory + self-knowledge tools) AND
// the stdio MCPClient (mcp-time / mcp-clipboard /
// mcp-applescript). The in-process registry is populated in
// stages — memory tools register inside installMemory (before
// installAgent runs), but the four Phase 10-01 self-knowledge
// tools register inside installSelfKnowledgeTools (which runs
// AFTER installAgent because it depends on the live
// audioGraphOwner). A static snapshot at orchestrator
// construction time would freeze out the self-knowledge tools.
// The resolver fires per outer-loop iteration in
// AgentOrchestrator.runTurnLoop, so by the time the user submits
// their first turn the catalog is fully populated. No per-event
// cost — one resolver invocation per LLM round-trip.
let mcpClientLocal = mcpRuntime.client
let inProcessRegistryRef: InProcessToolRegistry? = self.inProcessToolRegistry
let toolCatalogResolver: @Sendable () async -> [ToolSchema] = {
var catalog: [ToolSchema] = []
// Stdio helpers (get_time / get_clipboard / run_applescript).
let stdio = await mcpClientLocal.toolCatalog()
catalog.append(contentsOf: stdio)
// In-process tools (memory + self-knowledge).
if let inProc = inProcessRegistryRef {
let inProcessSchemas = await inProc.toolSchemas()
catalog.append(contentsOf: inProcessSchemas)
}
return catalog
}
let orchestrator = AgentOrchestrator(
configStore: configStore,
providerFactory: providerFactory,
@@ -1272,6 +1300,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
// PresenceStateSnapshot.shared (passed below).
systemPrompt: ContextBuilder.systemPrompt(for: .empty),
availableTools: [],
availableToolsResolver: toolCatalogResolver,
visionRouter: self.visionRouter,
presenceSnapshot: PresenceStateSnapshot.shared
)