ACAgentic Craft

Foundation~30 minHazelJS

What Is an Agent?

Define a production agent through HazelJS AgentRuntime: goal loop, tools, observation, and budgets—explained slowly for beginners.

Authors
editorial-team
Published
Last reviewed
Progress is stored locally in this browser.

Direct answer

In HazelJS, an agent is a TypeScript class decorated with @Agent, with actions declared as @Tool methods, registered on AgentRuntime, and started with:

typescript
await runtime.execute('ops-lookup', 'Status of ORD-1001?', { /* options */ });

The runtime—not the model—owns the loop:

  1. Assemble context (goal, prompt, prior tool results)
  2. Ask the model what to do next (call a tool or give a final answer)
  3. Validate tool arguments against the contract
  4. Execute the tool in your process
  5. Feed the observation back
  6. Stop on success, budget, policy, cancel, or human-approval wait

If there is no observe → decide → act cycle under AgentRuntime, you do not have a HazelJS agent. You have a single @hazeljs/ai completion, a @hazeljs/flow workflow, or a chatbot controller.

Glossary: /glossary/agent, /glossary/agent-runtime. Docs: Agent package.

Section 1 — Why a strict definition helps beginners

Vague labels produce vague ops. Teams that ship “Agent” chat wrappers without a runtime rediscover the same failures:

  • Unbounded tool calls
  • Side effects without idempotency
  • No durable mid-run record
  • No human gate for refunds
  • No eval of whether the goal was met

AgentRuntime forces those decisions into reviewable APIs: which tools exist, which need requiresApproval, which maxSteps / budgets apply, and how PolicyEngine bounds behavior.

You will build this loop in Agentic Development. This lesson teaches you to recognize it.

Section 2 — The four control loops (slow walkthrough)

Think of four nested concerns. HazelJS names them concretely:

1. Goal loop

You pass a goal string into execute. Optionally you also pass an outer confidence loop:

typescript
loop: {
  maxIterations: 4,
  stages: ['plan', 'execute', 'critique', 'validate'],
}

Done must be checkable: a clear answer, a structured field, or a success score — not “it felt right.”

2. Action loop

Tools are @Tool methods (later: Skillgate / MCP into ToolRegistry). The model never holds your API keys. It only proposes name + args. The runtime validates and runs your code.

3. Observation loop

Tool results return into the next model turn. Large or secret-laden dumps hurt quality and safety — you will truncate them in the builder track.

4. Governance loop

Production controls:

  • maxSteps on @Agent
  • Budgets / contracts
  • PolicyEngine (allow / deny / mask / require approval)
  • durableSuspend + approveAndResume for human waits
  • Persisted stop reasons for operators

Missing any loop shows up in production. Missing goals → endless chatter. Missing observation → hallucinated progress. Missing governance → expensive incidents.

Section 3 — Minimal anatomy (cheat sheet)

PieceHazelJS surfaceBeginner note
Agent definition@Agent({ name, systemPrompt, maxSteps })name must match execute
Tools@Tool({ name, description, parameters })Prefer few, sharp tools
LLM@hazeljs/ai provider or createMockLlmProviderMock first while learning
RuntimeAgentRuntime / AgentModule.forRootKernel of Agent OS
Startruntime.execute(name, goal, options)One entry point
Safety laterrequiresApproval, policies, durable storeDo not skip forever
See what happenedInspector / timelines / @hazeljs/observabilityNeeded before real traffic
TestsdescribeAgent in @hazeljs/testingLock behavior before prompt tweaks

Section 4 — Skeleton you should be able to explain

Read this slowly. Every line has a job.

typescript
import { Agent, Tool, AgentRuntime } from '@hazeljs/agent';
import { OpenAIProvider } from '@hazeljs/ai';
// Learning tip: swap OpenAIProvider for createMockLlmProvider() at first

@Agent({
  name: 'ops-lookup', // must match execute('ops-lookup', …)
  description: 'Read-only ops lookup agent',
  systemPrompt:
    'Answer using tools only. Never invent order or ticket ids.',
  maxSteps: 6, // hard stop — not a suggestion
})
export class OpsLookupAgent {
  @Tool({
    name: 'lookupOrder',
    description: 'Fetch order metadata by id',
    parameters: [
      { name: 'orderId', type: 'string', required: true },
    ],
  })
  async lookupOrder({ orderId }: { orderId: string }) {
    // Return structured data the model can read
    return { ok: true, orderId, status: 'shipped' };
  }
}

const runtime = new AgentRuntime({
  llmProvider: new OpenAIProvider(process.env.OPENAI_API_KEY!),
});

runtime.registerAgent(OpsLookupAgent);
runtime.registerAgentInstance('ops-lookup', new OpsLookupAgent());

const result = await runtime.execute('ops-lookup', 'Status of ORD-1001?', {
  loop: {
    maxIterations: 4,
    stages: ['plan', 'execute', 'critique', 'validate'],
  },
});

console.log(result.response);

In a full HazelJS app, you often use AgentModule.forRoot({ runtime: { llmProvider } }) and register agents as DI providers so Inspector can list them. Same loop; different wiring. See the Agent OS guide.

Section 5 — Agent vs “LLM app” in this stack

ShapeHazelJS choiceWhen beginners should pick it
Single completion@hazeljs/aiClassify, draft, extract — one shot
Deterministic multi-step@hazeljs/flowKnown stages, audit timeline, saga
Open tool loopAgentRuntimeNext step depends on unpredictable tool results

Use an agent when the path is branchy and you accept ops cost for autonomy. Prefer a workflow or single call when the sequence is stable or wrong actions are expensive.

Related: /patterns/augmented-llm, next lesson Agent vs workflow vs chatbot.

Section 6 — Practical one-pager (before more packages)

Before Skillgate, MCP, or multi-agent graphs:

  1. Goal in one sentence + measurable done condition
  2. Tools as contracts (name, parameters, read/write/irreversible)
  3. Stop conditions (maxSteps, budgets, policy, HITL)
  4. Authority — whose credentials do tools use? Least privilege
  5. Durability — can the process die mid-write?
  6. Evaluation — what golden tasks prove it works?

Most failed agent projects skipped this page.

Section 7 — Common misconceptions

  • “If it uses tools, it’s an agent.” One tool call inside a request handler without AgentRuntime’s loop is still a function call with an LLM.
  • “Agents replace workflows.” They compose. Flows can call agents; agents can call playbooks as tools.
  • “More autonomy is always better.” Bounded autonomy usually wins for customer-facing writes.
  • “Stronger models fix design.” Better models help; they do not replace contracts, budgets, or tests.

Checkpoint

  • You can point at @Agent, @Tool, and execute and say what each does
  • You know the model proposes tools; the runtime executes them
  • Your sticky note still has a done check

What to do next

Compare the three shapes: /learn/agent-foundations/agent-vs-workflow-vs-chatbot

Artifact: Annotated skeleton @Agent class you can explain line by line

Sources

Related