Skip to content

hermes: add disable-tools so operators can extend the driver's disabled tool set #341

Description

@mostlydev

Problem

The hermes driver maintains a set of disabled native tools and exposes
allow-tools so an operator can subtract from it. There is no way to
add to it, so an operator cannot disable a native Hermes tool from
claw-pod.yml.

Setting CLAWDAPUS_DISABLED_TOOLS in the pod's environment: does not work
either: the driver computes its own value and writes it per-service into
compose.generated.yml, overriding whatever the pod supplied.

$ grep CLAWDAPUS_DISABLED_TOOLS compose.generated.yml
      CLAWDAPUS_DISABLED_TOOLS: text_to_speech     # driver-written, per service
      ...
  CLAWDAPUS_DISABLED_TOOLS: skill_manage           # operator anchor, ignored

The runtime side is fine — toolsets.py filters on whatever it receives:

_CLAW_DISABLED_TOOLS = {t.strip() for t in
    os.getenv("CLAWDAPUS_DISABLED_TOOLS", "").split(",") if t.strip()}

def _claw_filter_tools(tools):
    if not _CLAW_DISABLED_TOOLS:
        return tools
    return [t for t in tools if t not in _CLAW_DISABLED_TOOLS]

It simply never receives the operator's entries.

Current behaviour

internal/driver/hermes/config.go:436:

func resolveDisabledHermesTools(rc *driver.ResolvedClaw) []string {
    if !hasDiscordHandle(rc) && !hasSlackHandle(rc) {
        return nil
    }
    disabled := []string{hermesTextToSpeechTool}
    if rc == nil || rc.Hermes == nil || len(rc.Hermes.AllowTools) == 0 {
        return disabled
    }
    // ... drops anything present in AllowTools from `disabled`
}

Consumed at config.go:172 and driver.go:203.

Proposal

Add a disable-tools counterpart alongside the existing allow-tools, in the
same x-claw.hermes block:

services:
  my-agent:
    x-claw:
      hermes:
        disable-tools: [skill_manage]

One consistent config surface, no env-var semantics change, no runtime change.

Implementation sketch

  1. internal/pod/parser.go:117 — extend rawHermesConfig:

    type rawHermesConfig struct {
        AllowTools   []string `yaml:"allow-tools"`
        DisableTools []string `yaml:"disable-tools"`
        AllowSilent  bool     `yaml:"allow-silent"`
    }
  2. internal/pod/parser.go:995 (parseHermesConfig) — trim/validate
    DisableTools the same way AllowTools is handled (reject empty entries
    with an indexed error), and include it in the early-return emptiness check
    so a block containing only disable-tools still produces a config.

  3. internal/driver/types.go:95 — add DisableTools []string to
    HermesConfig.

  4. internal/driver/hermes/config.go:436 — append rc.Hermes.DisableTools
    to disabled before the AllowTools subtraction, deduping.

  5. Precedence — suggest allow-tools wins on conflict (subtraction applied
    last), matching its current "operator re-enables" meaning; a tool named in
    both is enabled. Worth an explicit line in the docs either way.

  6. Guard on the handle check — note that resolveDisabledHermesTools
    returns nil early when there is no Discord/Slack handle, so today
    disable-tools would silently no-op for handle-less hermes services. Either
    move the deny list above that check or document the limitation.

Tests

  • disable-tools alone produces a config (previously nil when allow-tools
    was empty and allow-silent false).
  • Deny entry reaches CLAWDAPUS_DISABLED_TOOLS in the generated compose.
  • Deny + allow on the same tool resolves per the documented precedence.
  • Empty/whitespace entry is rejected with an indexed error.
  • Existing allow-tools behaviour unchanged.

Docs

x-claw reference for hermes: — document disable-tools next to
allow-tools, the precedence rule, and the handle-check caveat.

Motivation

Unattended, scheduled agents. skill_manage lets an agent write into its own
skills directory, and those files are re-read as standing instructions on every
later turn (see #342), so an agent's own conclusion from one turn silently
becomes durable policy with no human in the loop. That is reasonable for an
interactive assistant and risky for a cron-driven one — hence wanting it
configurable per pod rather than removed.

Current workaround is patching toolsets.py from the agent's Clawfile, which
requires an agent image rebuild (neither claw up nor a pull-and-regen
picks it up) and re-breaks whenever that module is reorganized.

Environment

claw version 0.26.0, hermes driver, hermes-base:v2026.6.19-claw.3.

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