Skip to main content
Monad’s built-in tool set — the agent-reachable capabilities (fs, shell, process, net, web-search/extract, email, code-exec, todo, memory, schedule, the delegation/model-derived tools, and the MCP adapter). Every tool is reachable by the model, so all tool inputs are treated as hostile (prompt injection): validation and resource guards run at call time, not just in the schema. Tools are first-party, not an atom kind: they ship with the daemon and are never contributed by atom packs. The Tool type therefore lives in apps/monad/src/capabilities/tools/types.ts, not in the SDK — tools do not go through @monad/sdk-atom. (The one SDK type a tool touches is ProviderToolHint, for provider-native tool bindings such as Anthropic computer-use.)

Layout

The call-time guards themselves (assertPathWithinRoots, assertUrlAllowed, isBlockedIp, ToolSecurityError) and the per-OS launchers live in @monad/sandbox (packages/sandbox/src/security.ts, packages/sandbox/src/launchers/), so the standalone msr CLI enforces the same policy the daemon does. Infra shared by tools stays at the tools/ root; only real tool implementations live under registry/. A tool that needs cohesive internal modules gets a same-named folder (email/, mcp/) instead of a flat file.

The uniform module contract (registry/contract.ts)

EVERY tool module exposes the SAME entry — export const register: ToolModule<Deps> — a factory that takes its dependency bag and returns the ready Tool[]. The shape is uniform; the deps type is parameterized so each module declares exactly what it needs rather than sharing one god-bag.
Three tiers, one idiom: A module may keep an internal createXxxTool builder (several are imported directly by tests); register is the one canonical entry that assembly goes through.

Assembly

  • Static + service → manifests in registry/index.ts (staticModules, serviceModules). builtinTools is buildTools(staticModules, {}). The barrel uses namespace imports (not export *) precisely because every module exports a symbol named register.
  • Agent-runtime → composed in agent/execution.ts with live agent deps (model router, inbound-approval gate, context engine, the live getTools registry view, hook runner, …). The order is preserved so the prompt-cache prefix stays stable across turns.
Conditional tools opt out by returning []: email when no backend is configured, agent_delegate when there are no delegatable Studio agents at boot. Reflexive tools (delegate, tool_search, tool_call) read a getTools thunk so they see the live registry — including hot-installed atom-pack/MCP tools — without rebinding.

Authoring rules

  • Declare scopes and highRisk honestly. Gate dangerous, irreversible, or trust-boundary-crossing ops behind needsApproval (async; runs in the oversight gate). Absent gate + high-risk ⇒ denied (fail-closed).
  • Resource guards live in run() bodies, not the schema: assertPathWithinRoots, assertUrlAllowed, isBlockedIp, path-traversal and egress checks. Never skip them on a path you believe is “trusted” — the argument is attacker-controllable.
  • Sandbox / credential wrapping is the daemon’s job, not the tool’s. Don’t reach for process/fs primitives that bypass the injected ToolContext constraints (sandboxRoots, backends).
  • Tools are snapshotted by the agent at startup; a hot-installed tool registers into the live registry but only reaches the model through the live getTools view — don’t assume a tool object captured at boot picks up mid-session changes.
  • Imports: the Tool type comes from @/capabilities/tools/types.ts; the only SDK import a tool needs is ProviderToolHint. Otherwise @monad/logger is allowed. Never import @monad/{monad,environment,client}.
Repository contributors should also follow the security rules and performance rules. Related public documentation covers skills and hooks.