core.gateway_pinned module

Tool-routing pin sets shared across services without importing tool modules.

These frozen name sets decide where a tool executes in the microservice fleet. They live here (not in tools/__init__.py) so the inference service can import the routing decision as plain data — a handful of strings — without pulling in any tools/*.py handler module or its dependencies. tools re-exports both names for backward compatibility.

Three execution tiers exist (see the tools-service split plan):

  • GATEWAY_PINNED_TOOLS -> run on the gateway (need the live discord.py client / node-local audio state). Delegated via the existing execute_tool RPC in tools.ToolRegistry.call().

  • INFERENCE_PINNED_TOOLS -> run in-process on inference (they spawn their own nested OpenRouterClient.chat() loops, so they need the LLM client + swarm stack that inference already hosts; the dedicated tools service is kept lightweight and has no LLM client).

  • everything else -> delegated to the dedicated tools service.

Routing precedence is gateway-pinned first, then inference-pinned, then remote; the two sets must stay disjoint.

core.gateway_pinned.GATEWAY_PINNED_TOOLS: frozenset[str] = frozenset({'create_poll', 'cross_channel_query', 'discord_delete_message', 'discord_edit_message', 'discord_embed', 'discord_invite', 'discord_leave_server', 'discord_manage_channels', 'discord_manage_roles', 'discord_message_reactions', 'discord_moderation', 'discord_react', 'discord_send_dm', 'discord_server_info', 'discord_upload_file', 'discord_webhooks', 'force_guild_index', 'get_current_guild_count', 'get_message_reactions', 'get_server_diagnostics', 'get_voice_states', 'list_server_emojis', 'music_steering', 'pause_music', 'play_music', 'reset_music_context', 'resume_music', 'stop_music'})

Tools that must run on the Gateway node (where the live platform client lives) rather than on a worker node behind a ProxyPlatformAdapter.

  • Voice/music tools hold node-local audio state on the Gateway.

  • The discord_* / guild tools call require_discord_client and need the native discord.py client; the microservice split left them stranded on worker nodes (“Discord client not available”), so they are delegated to the Gateway’s execute_tool RPC. Keep this in sync when adding tools that depend on tools._discord_helpers.require_discord_client.

core.gateway_pinned.INFERENCE_PINNED_TOOLS: frozenset[str] = frozenset({'call_subagent', 'conjure_egregore', 'extend_tool_loop', 'gravimetric_telescope', 'refine_prompt', 'start_deep_think_task', 'start_research_task'})

Compound tools that spawn their own nested OpenRouterClient.chat() loop during execution (verified: each module calls .chat(...)). Wherever they run needs the full LLM client + swarm/subagent stack, so they are kept on the inference tier rather than the lightweight tools service. The background variants (start_*_task) enqueue work onto the host service’s TaskManager, whose worker then runs the nested chat — so the enqueue entry point is pinned to inference too, keeping its background worker co-located with the LLM client. Pure result-fetchers (get_*_result / list_*_tasks) only read Redis and may delegate, so they are intentionally NOT pinned.

AUDIT (finalize when wiring inference_main, plan Phase 4): the swarm subsystem tools (dispatch_async_swarm, create_swarm_agent …) also drive chat loops but run through the swarm/agents path — decide their placement then. cross_channel_query also spawns chat but is already GATEWAY_PINNED (it runs on the gateway against the real client), so it is excluded here to keep the two sets disjoint.