Agents
React hook for running AI agents with real-time step updates, tool execution, and cancellation.
Agent Hook
See it in action
Try the Agent & Structured Data block and Chat block for working demos of these hooks.
useAgent
Run AI agents with a ReAct loop, tool execution, real-time step streaming, and cancellation.
import { useAgent, jsonSchema } from '@localmode/react';
import { webllm } from '@localmode/webllm';
import { z } from 'zod';
const model = webllm.languageModel('Qwen3-1.7B-q4f16_1-MLC');
const tools = [
{
name: 'search',
description: 'Search for information',
parameters: jsonSchema(z.object({ query: z.string() })),
execute: async ({ query }) => `Results for: ${query}`,
},
];
function AgentDemo() {
const { steps, result, isRunning, error, run, cancel, reset } = useAgent({
model,
tools,
maxSteps: 10,
});
return (
<div>
<button onClick={() => run('Find info about LocalMode')} disabled={isRunning}>
{isRunning ? 'Thinking...' : 'Run Agent'}
</button>
{isRunning && <button onClick={cancel}>Cancel</button>}
{steps.map((step, i) => (
<div key={i}>
<b>Step {i + 1}:</b> {step.type}
{step.type === 'tool_call' && (
<p>Tool: {step.toolName}({JSON.stringify(step.toolArgs)})</p>
)}
{step.observation && <p>Observation: {step.observation}</p>}
</div>
))}
{result && <p><b>Answer:</b> {result.result}</p>}
{error && <p className="text-error">{error.message}</p>}
</div>
);
}Options
Prop
Type
Return Value
Prop
Type
Human-in-the-Loop Tool Approval
Flag sensitive tools with requiresApproval: true and the hook does the rest — it installs the core onToolApproval callback internally. When the ReAct loop pauses on a gated call, pendingApproval becomes non-null (exposing the pending call's toolName, model-proposed args, and stepIndex) and the run waits until you call approve() or deny(reason?) — or cancel the run. Denied calls are not executed; a denial observation is fed back to the model so the loop continues. Tools without the flag never surface a pending approval.
function GatedAgent() {
const { pendingApproval, approve, deny, steps, run } = useAgent({
model,
tools: [{ ...deleteFileTool, requiresApproval: true }],
});
return (
<div>
<button onClick={() => run('Clean up temp files')}>Start</button>
{pendingApproval && (
<div>
<p>Allow {pendingApproval.toolName}({JSON.stringify(pendingApproval.args)})?</p>
<button onClick={() => approve()}>Approve</button>
<button onClick={() => deny('User rejected')}>Deny</button>
</div>
)}
{steps.map((step, i) => (
// step.approval?.decision is 'approved' | 'denied' for gated steps
<div key={i}>{step.toolName} {step.approval?.decision}</div>
))}
</div>
);
}For the approval contract — requiresApproval, onToolApproval, ToolApprovalDecision, deny-as-observation, and the fail-fast rule when a gated tool has no callback — see Core Agents: Human-in-the-Loop Tool Approval.
See this whole flow live — the pending-approval pause, approve-to-run, and deny-feeds-back-into-the-loop — in the Agent & Structured Data block, which drives useAgent (with the approval gate) over a real on-device model composed with the ui/conversation/tool-approval and agent-step-timeline primitives.
Step Structure
Each AgentStep contains:
| Field | Type | Description |
|---|---|---|
index | number | Zero-based step number |
type | 'tool_call' | 'finish' | What the model decided to do this step |
toolName | string | undefined | Tool called (when type is 'tool_call') |
toolArgs | Record<string, unknown> | undefined | Arguments passed to the tool |
observation | string | undefined | Stringified tool result or error message |
result | string | undefined | Final answer text (when type is 'finish') |
durationMs | number | Time taken for this step |
usage | GenerationUsage | undefined | Token usage from the model call |
approval | object | undefined | Approval decision, on requiresApproval tool calls only |
For the full AgentStep/AgentResult reference, see the Core Agents guide.
Agent with Memory
Use createAgentMemory() for conversation context that persists across runs:
import { useAgent } from '@localmode/react';
import { createAgentMemory } from '@localmode/core';
const memory = await createAgentMemory({
model: embeddingModel,
maxEntries: 100,
});
function AgentWithMemory() {
const agent = useAgent({ model, tools, memory });
// Memory is automatically updated after each run
}For full agent API reference including createAgent(), runAgent(), tool definitions, and memory, see the Core Agents guide.
Blocks
| App | Description | Links |
|---|---|---|
| Agent & Structured Data (Research Agent) | Multi-step ReAct agent with useAgent | Live block · Source |
| Chat (LLM Chat) | Agent mode with tool calling via useAgent | Live block · Source |