Skip to content

request_tool_access doesn't actually grant the requested tool mid-action #280

Description

@basedfriday

What I expected

When an FSM action calls request_tool_access(toolName, reason) and the user clicks Allow, the LLM should be able to call toolName for the rest of that action.

What actually happens

The block is real, but the granted boolean has no path to actually unlock the tool. The LLM gets { ok: true, granted: true }, then tries to call the underlying tool — the Anthropic API rejects it because the tool isn't in the action's registered tools array. The grant record exists in storage, but nothing reads it except request_tool_access itself.

Trace

  1. buildTools runs once at action start (packages/fsm-engine/fsm-engine.ts:3200-3356). It assembles the LLM's tool array from action.tools, workspace MCP servers, and the atlas-platform ambient set, then hands that fixed array to streamText. The toolset doesn't change for the rest of the action.

  2. request_tool_access blocks the run via waitForTerminalElicitation (packages/mcp-server/src/tools/permissions/request-tool-access.ts). That pause is real. The pause is the only thing about this tool that genuinely affects the action.

  3. On allow_always, ToolAccessGrants.grantAlways persists (workspaceId, toolName) to storage. But ToolAccessGrants.hasGrant is only consulted from inside request_tool_access itself (to short-circuit duplicate elicitations). I grepped fsm-engine, agent-server, agent-sdk, core/delegate, core/agent-conversion — no other consumer. So the grant is a cache for future elicitations, not a runtime permission gate.

  4. The only mechanism that actually expands the LLM's callable surface is permissions.dangerouslySkipAllowlist at the job/workspace/daemon-env level (fsm-engine.ts:3322-3329). When that's on, buildTools skips per-agent narrowing and the LLM gets every platform-allowlisted tool — and request_tool_access returns bypass as a no-op formality.

So what does request_tool_access actually do?

  • Blocks the action until the user answers.
  • Returns a granted boolean the LLM can use as a signal — to decide whether to route around (delegate to a sub-agent that does have the tool, fail-step gracefully, etc.). The LLM cannot use the granted response to invoke the originally-requested tool in the same action.
  • For allow_always, primes the grant cache so the next request_tool_access for the same tool short-circuits. The LLM still needs the tool in the action's tools array to actually use it.

Why this matters

The tool reads like a permission gate ("request access"), and the workspace-jobs SKILL.md describes the allow path as "the same action continues" — easy to read as "the LLM can now call the tool." It cannot. Authors who declare tools: ['fs_read_file', 'request_tool_access'] expecting the LLM to gain fs_write_file after an allow will be confused when the next tool call fails.

Possible directions

Pick one or some combination:

  1. Union declared tools with prior allow_always grants when buildTools runs. Cross-action only — once the user has allow-always-ed a tool, every future action in that workspace sees it. Cheapest to ship. Doesn't help allow_once.

  2. Mid-action toolset rebuild on grant. Interrupt streamText, rebuild the toolset including the newly-granted tool, resume. Closest match to the tool's name, but mechanically fiddly with current ai-sdk semantics.

  3. Dynamic dispatch tool. Add a call_tool(name, args) meta-tool the LLM can use to invoke any granted-this-session tool. Sidesteps the static-toolset constraint, but introduces an indirection layer.

  4. Tighten the framing. Rename or re-document the tool as an "approval signal" rather than an access grant. Update the skill text to say the LLM has to route around, not retry. Cheapest but doesn't fix the underlying gap.

Files touched in this trace

  • packages/fsm-engine/fsm-engine.ts (buildTools)
  • packages/mcp-server/src/tools/permissions/request-tool-access.ts
  • packages/core/src/elicitations/tool-access-grants.ts
  • packages/core/src/agent-conversion/agent-tool-filters.ts (wrapPlatformToolsWithScope — no permission gate)
  • packages/system/skills/writing-workspace-jobs/SKILL.md (the misleading "action continues" wording)

Metadata

Metadata

Assignees

Labels

area/devDaemon, CLI, workspace.yml, user agents — running from sourcearea/elicitationsbugSomething isn't working

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions