Bug: custom tools (memory, permissions, etc.) are silently unreachable
Description
All custom tools registered via _createCustomTools() — memory tools (memory_save, memory_search, memory_forget), recall_context, permission tools (check_permissions, request_permissions), skill tools, plan workflow tools, and subagent tools — are unreachable during task execution. The agent sees them listed in the system prompt but cannot invoke them.
This affects all execution modes equally: zora-agent ask, zora-agent start (dashboard), Docker, and native.
Root Cause
There are three issues, two of which compound to make tools completely unreachable:
1. ClaudeProvider.execute() passes custom tools via a mechanism the SDK ignores
In src/providers/claude-provider.ts, custom tools are passed as sdkOptions['customTools']:
sdkOptions['customTools'] = task.customTools.map(t => ({
name: t.name,
description: t.description,
input_schema: t.input_schema,
}));
The SDK silently ignores this property. The correct approach (already implemented in ExecutionLoop) is to wrap them as an in-process MCP server via createSdkMcpServer(). However, ExecutionLoop is only used for ancillary paths (heartbeat, compression, memory extraction) — not for main task execution, which goes through ClaudeProvider.execute() directly.
Task execution path: Orchestrator.submitTask() → _executeWithProvider() → provider.execute() → SDK query() — custom tools are passed but ignored.
2. SDK's createSdkMcpServer expects Zod schemas, not JSON Schema
Even after fixing issue #1, the SdkMcpToolDefinition type requires Zod schemas for inputSchema. The custom tools in Zora define input_schema as raw JSON Schema objects. Passing these to createSdkMcpServer causes Q.safeParseAsync is not a function errors at runtime when the SDK attempts to validate tool inputs.
The SDK exports a tool() helper that accepts Zod schemas and produces properly typed SdkMcpToolDefinition objects.
Note: ExecutionLoop has the same bug — it passes raw JSON Schema to inputSchema — but it's never triggered because the tools registered there (heartbeat, compression) are never actually called by the agent.
3. System prompt references bare tool names
MemoryManager.loadContext() tells the agent to use bare names (memory_save, memory_search, recall_context). Tools registered via MCP server are prefixed as mcp__zora-tools__<name>. The prompt needs to reference the prefixed names. Additionally, memory_forget is missing from the prompt entirely.
Steps to Reproduce
- Start Zora (any mode —
zora-agent ask, dashboard, or Docker)
- Ask: "Remember that my name is Joseph"
- Observe: the agent attempts to find
memory_save, fails, and reports the tool is unavailable
Expected Behavior
The agent should invoke the memory save tool and confirm the memory was stored.
Suggested Fix
Issue #1 — Provider tool registration:
In ClaudeProvider.execute(), wrap custom tools as an MCP server using createSdkMcpServer(), and add the MCP-prefixed tool names to allowedTools.
Issue #2 — Zod schema requirement:
Use the SDK's tool() helper to create MCP tool definitions. Convert each JSON Schema property to z.any().describe(...) so the SDK gets a valid Zod shape while letting the tool's own handler perform real validation.
Issue #3 — System prompt naming:
Update src/memory/memory-manager.ts loadContext() to reference mcp__zora-tools__* prefixed names and add memory_forget.
Files
src/providers/claude-provider.ts — passes custom tools via ignored sdkOptions['customTools'] (primary bug)
src/memory/memory-manager.ts — system prompt with bare tool names
src/orchestrator/execution-loop.ts — has the same JSON Schema bug but untriggered (reference for MCP pattern)
src/orchestrator/orchestrator.ts — _createCustomTools() builds the tool array, _executeWithProvider() is the main execution path
src/tools/memory-tools.ts — memory tool definitions
Related issues (discovered, out of scope)
During testing of the above fix, two additional pre-existing issues were observed:
Memory search index not rebuilt after writes
memory_save and the extraction pipeline successfully write items to disk (~/.zora/memory/items/mem_*.json), but memory_search returns empty results. The MiniSearch index (stored in ~/.zora/memory/index/minisearch.json) appears to not be rebuilt when new items are added, so newly saved memories are invisible to search until the index is manually rebuilt or the process restarts.
Dashboard messages have no conversation continuity
Each message submitted through the dashboard is handled as an independent task (POST /api/task). The agent loses all context between messages — it cannot see its own previous responses or the user's earlier messages in the same session. This makes multi-turn interactions (like "Yes please do" in response to an offer) impossible, as the agent has no record of what it offered.
Bug: custom tools (memory, permissions, etc.) are silently unreachable
Description
All custom tools registered via
_createCustomTools()— memory tools (memory_save,memory_search,memory_forget),recall_context, permission tools (check_permissions,request_permissions), skill tools, plan workflow tools, and subagent tools — are unreachable during task execution. The agent sees them listed in the system prompt but cannot invoke them.This affects all execution modes equally:
zora-agent ask,zora-agent start(dashboard), Docker, and native.Root Cause
There are three issues, two of which compound to make tools completely unreachable:
1.
ClaudeProvider.execute()passes custom tools via a mechanism the SDK ignoresIn
src/providers/claude-provider.ts, custom tools are passed assdkOptions['customTools']:The SDK silently ignores this property. The correct approach (already implemented in
ExecutionLoop) is to wrap them as an in-process MCP server viacreateSdkMcpServer(). However,ExecutionLoopis only used for ancillary paths (heartbeat, compression, memory extraction) — not for main task execution, which goes throughClaudeProvider.execute()directly.Task execution path:
Orchestrator.submitTask()→_executeWithProvider()→provider.execute()→ SDKquery()— custom tools are passed but ignored.2. SDK's
createSdkMcpServerexpects Zod schemas, not JSON SchemaEven after fixing issue #1, the
SdkMcpToolDefinitiontype requires Zod schemas forinputSchema. The custom tools in Zora defineinput_schemaas raw JSON Schema objects. Passing these tocreateSdkMcpServercausesQ.safeParseAsync is not a functionerrors at runtime when the SDK attempts to validate tool inputs.The SDK exports a
tool()helper that accepts Zod schemas and produces properly typedSdkMcpToolDefinitionobjects.Note:
ExecutionLoophas the same bug — it passes raw JSON Schema toinputSchema— but it's never triggered because the tools registered there (heartbeat, compression) are never actually called by the agent.3. System prompt references bare tool names
MemoryManager.loadContext()tells the agent to use bare names (memory_save,memory_search,recall_context). Tools registered via MCP server are prefixed asmcp__zora-tools__<name>. The prompt needs to reference the prefixed names. Additionally,memory_forgetis missing from the prompt entirely.Steps to Reproduce
zora-agent ask, dashboard, or Docker)memory_save, fails, and reports the tool is unavailableExpected Behavior
The agent should invoke the memory save tool and confirm the memory was stored.
Suggested Fix
Issue #1 — Provider tool registration:
In
ClaudeProvider.execute(), wrap custom tools as an MCP server usingcreateSdkMcpServer(), and add the MCP-prefixed tool names toallowedTools.Issue #2 — Zod schema requirement:
Use the SDK's
tool()helper to create MCP tool definitions. Convert each JSON Schema property toz.any().describe(...)so the SDK gets a valid Zod shape while letting the tool's own handler perform real validation.Issue #3 — System prompt naming:
Update
src/memory/memory-manager.tsloadContext()to referencemcp__zora-tools__*prefixed names and addmemory_forget.Files
src/providers/claude-provider.ts— passes custom tools via ignoredsdkOptions['customTools'](primary bug)src/memory/memory-manager.ts— system prompt with bare tool namessrc/orchestrator/execution-loop.ts— has the same JSON Schema bug but untriggered (reference for MCP pattern)src/orchestrator/orchestrator.ts—_createCustomTools()builds the tool array,_executeWithProvider()is the main execution pathsrc/tools/memory-tools.ts— memory tool definitionsRelated issues (discovered, out of scope)
During testing of the above fix, two additional pre-existing issues were observed:
Memory search index not rebuilt after writes
memory_saveand the extraction pipeline successfully write items to disk (~/.zora/memory/items/mem_*.json), butmemory_searchreturns empty results. The MiniSearch index (stored in~/.zora/memory/index/minisearch.json) appears to not be rebuilt when new items are added, so newly saved memories are invisible to search until the index is manually rebuilt or the process restarts.Dashboard messages have no conversation continuity
Each message submitted through the dashboard is handled as an independent task (
POST /api/task). The agent loses all context between messages — it cannot see its own previous responses or the user's earlier messages in the same session. This makes multi-turn interactions (like "Yes please do" in response to an offer) impossible, as the agent has no record of what it offered.