hermes-tool-as-cap

star 26

Replace Hermes' singleton ToolRegistry (name → handler dispatch with ambient authority) with Goblins vats-with-methods. Each tool becomes an actor; the LLM sees an unguessable URL handle (Mandy nonce-registry pattern); invocation = eventual send. No raw cap leaks into model context.

plurigrid By plurigrid schedule Updated 6/10/2026

name: hermes-tool-as-cap description: Replace Hermes' singleton ToolRegistry (name → handler dispatch with ambient authority) with Goblins vats-with-methods. Each tool becomes an actor; the LLM sees an unguessable URL handle (Mandy nonce-registry pattern); invocation = eventual send. No raw cap leaks into model context. type: bridge parent: hermes-goblins-bridge row: 1 proto: R polarity: +1 status: stub

hermes-tool-as-cap

Phase 3 (action). The most-used row — every tool call goes through this primitive.

Hermes signature

/Users/bob/i/hermes-agent/tools/registry.py

class ToolEntry:                       # :75
    name, toolset, schema, handler, check_fn,
    requires_env, is_async, description, emoji, max_result_size_chars

class ToolRegistry:                    # :99
    _tools: Dict[str, ToolEntry]
    _toolset_checks: Dict[str, Callable]
    _lock: threading.RLock              # MCP dynamic refresh
    def dispatch(self, name, args): ...   # :291

run_agent.py:7660 _execute_tool_calls_concurrent, :7525 _execute_tool_calls, :327 _paths_overlap (parallelism safety).

Authority pattern: singleton registry, ambient authority for handlers. A tool's handler is a Python callable that closes over whatever the importing module had access to (FS, network, env vars). Tool selection = string name → callable. Schema → LLM, name → dispatch.

Goblins signature

Each tool becomes a ^tool-cap actor; the registry is replaced by Mandy's nonce-registry (Pattern B from parent SKILL) that mounts caps behind /object/<base32-id> URLs. The LLM sees the URL handle, not the cap; the registry vat resolves URL → cap on invocation.

;; Nonce registry — Mandy Pattern B
(define-values (registry locator) (spawn-nonce-registry-and-locator))

;; Each tool: a cap with explicit-authority closure
(define (^bash-tool bcom #:fs-cap fs #:net-cap net #:approval-cap appr)
  (methods
    ((invoke args)
     (define cmd (parse args))
     (when (dangerous? cmd)
       (<- appr 'request cmd))
     (<- fs 'exec cmd))))      ; only the caps it was constructed with

(define handle (mount registry (spawn ^bash-tool #:fs-cap fs ...)))
;; → handle is a base32 URL like "/object/abc...xyz"

LLM tool list = [(name, schema, url-handle), …]. Invocation translator (Mandy Pattern C, activity->message) takes {"tool": name, "args": …} and rewrites to (<- (locator handle) 'invoke args).

Translation table

Hermes call Goblins message Notes
registry.register(ToolEntry(…)) mount cap in nonce registry URL handle minted
registry.dispatch(name, args) (<- (locator handle) 'invoke args) LLM sees URL, not cap
handler closes over module imports tool actor takes explicit #:fs-cap, #:net-cap, etc. no ambient authority
_execute_tool_calls_concurrent parallel <- sends; promises join vat handles concurrency
_paths_overlap parallelism gate per-cap exclusion via revocable forwarders not a global lock
requires_env declarative dep constructor parameter (cap or value) typechecked at spawn
check_fn (e.g. opt-in toolset) gate cap construction; absent cap = absent tool binary, not runtime check
MCP dynamic refresh mount/unmount in registry atomic; no _lock needed

Failure modes (closed by this bridge)

  • Tool handler with ambient FS/net authority — handlers receive caps explicitly; no module-level import os.
  • String-name confusion attacks (e.g. bash vs bash vs BASH) — there is no name dispatch; URL handle is unforgeable.
  • MCP refresh race with concurrent dispatch — registry vat serializes mount/unmount; readers see consistent state.
  • Two tools sharing a path through global state — caps are isolated; sharing requires explicit cap-passing.
  • LLM crafting tool-call to a tool not advertised — no URL handle in scope = (locator unknown) returns 'no-such-cap.

Failure modes (introduced; must mitigate)

  • URL handle leaks into memory backend / context — handles are per-session; rotate on session-end (Mandy registry has built-in TTL).
  • Cap constructor explosion — large tools with many cap deps. → use a ^cap-bundle builder per skill (see hermes-skill-as-cap-module, row 3).

Test vector

fs = fs_cap(root='/tmp/sandbox')
net = net_cap(allowlist=['api.openai.com'])
bash = mount(spawn(BashTool, fs_cap=fs, net_cap=net))   # → "/object/abc..."

# LLM gets handle, not cap:
llm_view = {'name': 'bash', 'schema': {...}, 'handle': '/object/abc...'}

# Invocation:
invoke('/object/abc...', {'cmd': 'ls /tmp/sandbox'})    # OK
invoke('/object/abc...', {'cmd': 'ls /etc'})            # CapError (fs scope)
invoke('/object/zzz...', {'cmd': 'ls'})                  # CapError (no-such-cap)

# Concurrent dispatch:
parallel([invoke(h1, args1), invoke(h2, args2)])        # vat handles
# vs Hermes _paths_overlap which needs _lock + manual gating

# MCP refresh during dispatch:
refresh_mcp_tools()      # may mount new caps mid-dispatch
# in-flight calls unaffected; new calls see updated registry

Capability diff

Property Hermes (status quo) Goblins (this bridge)
Tool selection string name → callable URL handle → cap
Authority ambient (handler imports) explicit (constructor caps)
Refresh atomicity RLock + mutation mount/unmount in vat
Parallelism safety _paths_overlap ad-hoc per-cap revocable forwarder
LLM-cap visibility handler is a Python callable only opaque URL handle
Failure mode name-confusion / ambient-leak no cap = no call

Test-harness location

~/i/goblins-adapter/tests/tool-cap-bisim.scm (todo). Adversarial probe: prompt-injection asking the LLM to "call the tool whose schema is X but path traversal Y" — Hermes may comply via a related tool; Goblins version cannot construct a path that resolves to a cap not in scope.

Status: stub

Phase 3 priority. Foundational for hermes-delegate-as-spawn (row 2), hermes-skill-as-cap-module (row 3), and hermes-bg-as-vat (row 4) — all build on the tool-as-cap primitive plus the nonce registry.

Install via CLI
npx skills add https://github.com/plurigrid/asi --skill hermes-tool-as-cap
Repository Details
star Stars 26
call_split Forks 8
navigation Branch main
article Path SKILL.md
More from Creator