Skip to content

Tool Execution

Typed Go interfaces. In-process registry. No Python bounce.

type Tool interface {
Name() string
Description() string
Schema() json.RawMessage
Timeout() time.Duration
Execute(ctx context.Context, args json.RawMessage) (json.RawMessage, error)
}

Every model-visible native tool lives behind this interface. The registry owns the descriptor sent to the provider, the timeout used by the kernel, and the JSON payload returned as a role=tool result.

  • Deterministic execution — no subprocess spawning for in-process tools
  • Bounded side effects — ctx cancels; deadlines respected
  • Hermes-visible progress — started tool calls produce channel/TUI progress through the shared tool-trace renderer, separate from assistant final text
  • Registry proof — default tool exposure is asserted in cmd/gormes registry tests before a model can rely on it
  • Wire Doctorgormes doctor --offline validates the registry before a live turn burns tokens

✅ Shipped in layers:

  • Phase 2.A established the typed in-process registry and kernel tool-call handshake.
  • Phase 5.K added guarded, shell-only execute_code snippets with timeout/output caps, pre-exec filesystem/network blocking, and Python runtime hardline blocking at the terminal boundary.
  • Phase 5.R added config-driven execute_code mode selection. Gormes defaults to strict, which runs shell snippets from a guarded temp directory. project is an explicit opt-in via [code_execution].mode = "project" and runs from the session working directory while preserving the same filesystem/network guard and blocked-result envelope.
  • Phase 5.L now exposes the first Hermes-style local task tools by default: read_file, search_files, write_file, patch, and a foreground-only guarded terminal.
  • Phase 5.N has native todo and session_search implementations, but their default live-turn registration still needs session-aware binding before they should be treated like stateless registry entries.

The important parity distinction is that tool-progress rendering and tool execution inventory are different contracts. Gormes can render 📖 read_file: "..." only if a tool-start event exists, but the model can actually choose read_file only when the descriptor is registered in the runtime registry. Both sides need tests.

Remaining gaps are intentionally narrower: terminal background process registry, PTY execution, sandbox backends, fuzzy patch/lint/checkpoint restore, session-scoped todo registration, and session-bound session_search registration. See Phase 2 and Phase 5.

FamilyCurrent stateRemaining parity
execute_codeGuarded local shell snippets with caps and blocking policy; strict is the default and project is config opt-in; Python runtimes are not exposedhelper parity without a Python child runtime, broader sandbox backends
read_fileWorkspace-rooted text read, line-numbered pagination, duplicate-read status suppression, symlink escape denialfull Hermes file-state integration and checkpoint restore linkage
search_filesWorkspace-rooted content and file-name searchripgrep-level output modes, backend cwd tracking, large-result artifact storage
write_file / patchWorkspace-rooted full writes and replace-mode patch with read-status guardrailsfuzzy matching strategies, V4A multi-file patch mode, syntax/lint hooks, rollback
terminalForeground shell command with dangerous-command guardrails, timeouts, and output capsbackground process registry, PTY, persistent cwd/env snapshots, Docker/Modal/SSH/Daytona/Singularity backends
todoNative stateful tool and prompt-injection formatterlive per-session store binding in default registry
session_searchNative descriptor and execution wrapper over memory/session storescurrent-session binding and safe default registration

When implementing a new tool, tool-output policy, or runtime seam, route through the gormes-references skill (docs/development-skills/gormes-references/SKILL.md) before re-deriving a shape. The most useful Go donors for tools/runtime work:

Tool/runtime problemDonor file
Tool registry with before/after hooks and filter gatesnanobot/pkg/tools/service.go, nanobot/pkg/tools/flows.go
Truncate large tool outputs while persisting full bytes (artifact pointer + short text)nanobot/pkg/agents/truncate.go
Estimate image tokens by decoded dimensions (per-provider conservative fallback)nanobot/pkg/agents/tokencount.go
Wire a runtime with explicit dependency layering (Options.Merge + Complete defaults)nanobot/pkg/runtime/runtime.go
Tool filtering by channel/trust/toolset (declarative pre-call gate)nanobot/pkg/tools/flows.go, helpers under axe/internal/tool/
Per-turn token-budget tracker with reset and overflow signalaxe/internal/budget/budget.go
Artifact tracker for tool outputs (sanitized paths, append-only registry)axe/internal/artifact/tracker.go
Loop / sequential / parallel workflow agents (no kernel rewrite)adk-go/agent/workflowagents/..., examples under adk-go/examples/workflowagents/...

Nanobot is Apache 2.0 and adk-go is Apache 2.0 — both permitted as patterns + adapted code with attribution. Axe is MIT, same. Always add a // Adapted from <donor>/...::Symbol comment on the receiving Gormes file when porting code.