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
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:
await runtime.execute('ops-lookup', 'Status of ORD-1001?', { /* options */ });
The runtime—not the model—owns the loop:
- Assemble context (goal, prompt, prior tool results)
- Ask the model what to do next (call a tool or give a final answer)
- Validate tool arguments against the contract
- Execute the tool in your process
- Feed the observation back
- 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:
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:
maxStepson@Agent- Budgets / contracts
PolicyEngine(allow / deny / mask / require approval)durableSuspend+approveAndResumefor 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)
| Piece | HazelJS surface | Beginner 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 createMockLlmProvider | Mock first while learning |
| Runtime | AgentRuntime / AgentModule.forRoot | Kernel of Agent OS |
| Start | runtime.execute(name, goal, options) | One entry point |
| Safety later | requiresApproval, policies, durable store | Do not skip forever |
| See what happened | Inspector / timelines / @hazeljs/observability | Needed before real traffic |
| Tests | describeAgent in @hazeljs/testing | Lock behavior before prompt tweaks |
Section 4 — Skeleton you should be able to explain
Read this slowly. Every line has a job.
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
| Shape | HazelJS choice | When beginners should pick it |
|---|---|---|
| Single completion | @hazeljs/ai | Classify, draft, extract — one shot |
| Deterministic multi-step | @hazeljs/flow | Known stages, audit timeline, saga |
| Open tool loop | AgentRuntime | Next 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:
- Goal in one sentence + measurable done condition
- Tools as contracts (name, parameters, read/write/irreversible)
- Stop conditions (
maxSteps, budgets, policy, HITL) - Authority — whose credentials do tools use? Least privilege
- Durability — can the process die mid-write?
- 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, andexecuteand 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