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