Skip to content

emrg: a message cannot choose which tools the session gets — the table is fixed at daemon start #2465

Closed

Description

@argszero

What is true today

emrg/server/daemon.py registers eight tools once, in build_tool_registry (daemon.py:669-685): the platform's shell dialect, read, write, edit, glob, grep, submit_rant, subagent. After start-up nothing narrows that table for any session or for any single message:

  • _run_tool_loop serialises the whole registry into every round's request (daemon.py:4419, tools_base = self.tools.to_openai_tools()), and the round loop re-sends it unchanged.
  • The execution side resolves a requested name out of the same global table (daemon.py:5011, tool = self.tools.get(tc_name)), so a model that names a tool is answered from the full registry regardless of what it was offered.
  • The memory-reflection loop builds a second, independent copy of that table (daemon.py:7822) and resolves names against the registry itself (daemon.py:7883).
  • The task message carries no tool field at all: TaskRequest (emrg/protocol.py:33-49) has sandbox and no tools; the receiver (daemon.py:1644) reads the fields it knows; the producer (emrg/server/scheduler.py:2681) builds a dict with prompt, sandbox and input_history only.

Consequence. A conversation the host typed and a scheduled task's round are served exactly the same eight tools, and there is no way to make them differ. A tool that is only meaningful for a scheduled task cannot be kept out of a host conversation except by a second mechanism inside the tool itself.

What the change must satisfy

  1. TaskRequest gains tools: Optional[list[str]] = None, carried by to_dict the way sandbox is (protocol.py:63-64); the task branch reads it as data.get("tools") (daemon.py:1644) and it reaches the tool loop as req.tools.
  2. Two named whitelists live in the daemon beside the registration: a default set and a scheduled-task set. Both are whitelists — a newly registered tool that neither names is invisible to that class of session. The shell entry is taken per platform (shell_tool_name()), never the literal "bash": on Windows the tool is named pwsh.
  3. Effective set = (the message's list if not None else the default set) ∩ registered names. A name that is not registered is dropped silently — no error, and no start-up validation.
  4. The effective set is enforced in all three places, because any one of them alone is a false gate: the table serialised for the model (daemon.py:4419), the execution-side lookup (daemon.py:5011), and the memory-reflection loop's own table and lookup (daemon.py:7822 / :7883).
  5. The scheduler's task_msg names the scheduled-task set (scheduler.py:2681).
  6. A child session spawned by the subagent tool inherits its parent's effective set.

Acceptance

  • Four cases, each with its own test: a list is honoured by intersection; no list falls back to the default set; a list carrying an unregistered name drops that name and keeps the rest; a list of nothing but unknown names yields zero tools.
  • The execution side is exercised for real, not only through the serialised table: a session whose set names only A must not get B's result even though B is registered.
  • The memory-reflection table carries a test of its own, so a change to the main loop cannot fix it by accident.
  • Mutation arms: removing the filter at the serialisation site, and removing it at the execution site, each turns its own test red.

Out of scope

The scheduled-task-only tool itself (a review / owner role). It is not part of this change; once it exists it is added to the scheduled-task set alone, which is what makes requirement 2 worth doing now.

Activity

  1. argszero commented on Oct 22, 2026

    @argszero
    OwnerAuthor

    Handled by #2466

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions