ACAgentic Craft

Intermediate~18 minHazelJS

Tool-Using Agent

A bounded AgentRuntime observe→decide→act loop where HazelJS validates tool calls via ToolRegistry and feeds observations back to the model.

Authors
editorial-team
Published
Last reviewed

Problem

The correct sequence of actions depends on intermediate tool results that cannot be fully enumerated in a static workflow.

Context

Investigation, multi-system ops, research-with-actions, and other branchy tasks hosted on HazelJS Agent OS—where MCP servers and Skillgate skills feed the same ToolRegistry.

Forces and constraints

  • Uncertainty requires adaptive tool choice
  • Tools have real side effects and failure modes
  • Cost, latency, and safety must be bounded (RunBudget, maxSteps, PolicyEngine)
  • Operators need traces, stop reasons, and Inspector timelines

Recommended design

Define tools with @Tool on an @Agent (or import MCP/Skillgate capabilities into ToolRegistry). Run AgentRuntime.execute with maxSteps, optional RunBudget, PolicyEngine capabilities, and host-mediated execution. Each step: assemble context → model proposes tool or final → runtime validates args → optional approval gate → execute → append observation → checkpoint when durable. Stop on success, budget, policy deny, or cancel.

Minimal pseudocode

import {
  Agent, Tool, AgentRuntime, ToolRegistry,
  PolicyEngine, type RunBudget,
} from '@hazeljs/agent';

@Agent({ name: 'OpsInvestigator' })
class OpsInvestigator {
  @Tool({ name: 'get_incident', sideEffect: 'read' })
  getIncident(args: { id: string }) { /* ... */ }

  @Tool({ name: 'page_oncall', sideEffect: 'write' })
  pageOncall(args: { team: string }) { /* ... */ }
}

const budget: RunBudget = { maxTokens: 50_000, maxSteps: 8 };
const runtime = new AgentRuntime({
  toolRegistry: ToolRegistry.fromAgents([OpsInvestigator]),
  policyEngine: new PolicyEngine(/* require_approval on writes */),
  defaultBudget: budget,
});

const result = await runtime.execute('OpsInvestigator', { goal }, {
  maxSteps: 8,
  userId,
  sessionId,
});

Failure modes

  • Infinite or oscillating tool loops (missing maxSteps / RunBudget)
  • Duplicate side effects on retry without idempotent tool adapters
  • Prompt injection via tool or MCP output
  • Wrong tool selection from vague @Tool descriptions
  • Context overflow from verbose observations

Security considerations

  • Host-mediated tool execution only (never let the model call credentials directly)
  • Least-privilege credentials per @Tool / MCP server
  • PolicyEngine allow/deny + capability checks; Skillgate for governed OpenAPI skills
  • Sanitize/truncate observations before re-prompting; apply @hazeljs/guardrails

Observability signals

  • Per-run OpenTelemetry spans via @hazeljs/observability
  • Inspector getTimeline({ executionId }) for model/tool steps
  • Metrics: steps, cost, tool error rate, stop reason

Evaluation approach

Golden tasks with acceptable tool traces; forbidden-tool tests against PolicyEngine; cost-per-success vs RunBudget; human review sampling.

Trade-offs

  • Higher task coverage vs higher variance and ops cost
  • More ToolRegistry entries increase capability and mis-selection risk

Sources

Related patterns