Fifteen minutes to a working agent that calls a tool you wrote. Two paths — pick one; they share the same runtime and the same tool definitions.
| Path | For | Packages |
|---|---|---|
| Full-stack | Next.js App Router, tools running in the browser | glove-react, glove-next |
| Server-only | CLI, worker, backend service — no React | glove-core |
Not installed yet? Installation covers packages, providers and environment variables. This guide assumes React components, hooks and basic TypeScript; we explain Zod and every Glove concept as it comes up.
Refer back to these if anything below feels unfamiliar — each has a full page in Core Concepts.
Tools are the capabilities your app exposes. A tool is a name, a description (this is what the model reads to decide), a Zod inputSchema, and an async do().
The agent loop is the engine. A user message goes in; the model calls whichever tools it needs, reads the results, and either answers or calls more. You never sequence it yourself.
The display stack is how a tool shows UI mid-run. pushAndForget renders and keeps going; pushAndWait renders and pauses the tool until the user responds. Section 6 uses it.
pnpm add glove-react glove-next zodglove-next — the server handler that talks to your model provider (reference)glove-react — hooks and components for the UI, with glove-core bundled as a dependency (reference)zod — validates tool inputs at runtimeOne line gives you a streaming POST endpoint. It holds your API key and proxies the model; it never sees your tool implementations.
import { createChatHandler } from "glove-next";
export const POST = createChatHandler({
provider: "anthropic", // "openai", "gemini", "ollama", …
model: "claude-sonnet-4-20250514",
});ANTHROPIC_API_KEY=sk-ant-...The GloveClient holds the system prompt and the tool list. Tools defined here run in the browser, so they can touch component state and the display stack directly.
import { GloveClient } from "glove-react";
import { z } from "zod";
export const gloveClient = new GloveClient({
endpoint: "/api/chat",
systemPrompt: "You are a helpful weather assistant.",
tools: [
{
name: "get_weather",
// The description IS the interface — the model picks tools by reading it.
description: "Get the current weather for a city.",
inputSchema: z.object({
city: z.string().describe("The city to get weather for"),
}),
async do(input) {
const res = await fetch(
`https://wttr.in/${encodeURIComponent(input.city)}?format=j1`,
);
const data = await res.json();
const now = data.current_condition[0];
return {
city: input.city,
temperature: `${now.temp_C}°C`,
condition: now.weatherDesc[0].value,
};
},
},
],
});Whatever do() returns is fed back to the model as the tool result. Keep it small and structured — it costs context on every subsequent turn.
"use client";
import { GloveProvider } from "glove-react";
import { gloveClient } from "@/lib/glove";
export function Providers({ children }: { children: React.ReactNode }) {
return <GloveProvider client={gloveClient}>{children}</GloveProvider>;
}import { Providers } from "./providers";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}useGlove() gives you the timeline, the streaming text, a busy flag and sendMessage. <Render> wires them together — including display-stack slots and tool result rendering — so you only supply the pieces you care about.
"use client";
import { useGlove, Render } from "glove-react";
export default function Chat() {
const glove = useGlove();
return (
<main style={{ maxWidth: 640, margin: "2rem auto" }}>
<h1>Weather Chat</h1>
<Render
glove={glove}
renderMessage={({ entry }) => (
<p>
<strong>{entry.kind === "user" ? "You" : "Assistant"}:</strong>{" "}
{entry.text}
</p>
)}
renderStreaming={({ text }) => <p style={{ opacity: 0.7 }}>{text}</p>}
renderInput={({ send, busy }) => (
<form
onSubmit={(e) => {
e.preventDefault();
const input = e.currentTarget.elements.namedItem("msg") as HTMLInputElement;
if (!input.value.trim() || busy) return;
send(input.value.trim());
input.value = "";
}}
>
<input name="msg" placeholder="Ask about the weather…" disabled={busy} />
<button type="submit" disabled={busy}>Send</button>
</form>
)}
/>
</main>
);
}Prefer to drive the timeline yourself? useGlove() returns it as a plain array — map over entry.kind (user, agent_text, tool) and render whatever you like. <Render> is a convenience, not a requirement.
This is the part that makes Glove an application runtime rather than a chat wrapper. A tool can push a component and block until the user answers it:
{
name: "book_trip",
description: "Book a trip once the user has confirmed the details.",
inputSchema: z.object({ city: z.string(), nights: z.number() }),
async do(input, display) {
// Renders <ConfirmTrip {...input} /> and suspends here.
const confirmed = await display.pushAndWait({
renderer: "confirm_trip",
input,
});
if (!confirmed.ok) return { status: "cancelled" };
return await bookings.create(input);
},
}Register confirm_trip as a renderer on the React side and <Render> mounts it in the conversation. The full story — renderers, display strategies, typed props via defineTool — is in The Display Stack.
pnpm devOpen http://localhost:3000 and ask “What's the weather in Tokyo?”. The model calls get_weather and answers from the result.
No React, no Next.js — construct Glove, fold tools onto it, and call processRequest. This is the shape for cron jobs, queue workers, terminal agents and WebSocket servers.
pnpm add glove-core zodimport { Glove, MemoryStore, Displaymanager, createAdapter } from "glove-core";
import { z } from "zod";
const agent = new Glove({
store: new MemoryStore("local-session"),
model: createAdapter({ provider: "anthropic", model: "claude-sonnet-4-20250514" }),
displayManager: new Displaymanager(),
systemPrompt: "You are a helpful weather assistant.",
compaction_config: {
// Runs automatically when the context gets long.
compaction_instructions: "Summarize the conversation so far.",
},
})
.fold({
name: "get_weather",
description: "Get the current weather for a city.",
inputSchema: z.object({ city: z.string() }),
async do(input) {
return { status: "success", data: { city: input.city, temperature: "22°C" } };
},
})
.build();
const result = await agent.processRequest("What's the weather in Tokyo?");
console.log(result);npx tsx scripts/run-agent.tsSubscribers are the server-side equivalent of the React timeline — stream deltas to a terminal, a log, a socket, or a metrics sink:
app.addSubscriber({
async record(event, data) {
if (event === "text_delta") process.stdout.write(data.text);
if (event === "tool_use") console.log(`\n→ ${data.name}`);
if (event === "tool_use_result") console.log(`← ${data.result.status}`);
},
});More on long-running processes, WebSocket servers and terminal UIs in Server-Side Agents.
MemoryStore lives in process memory — perfect for scripts and tests, gone on restart. For real sessions implement StoreAdapter against your own backend; it is a small interface (messages, turns, tokens, inbox) and the one seam that decides where conversation state lives.
glove-core) — in-process, for prototypesglove-react) — delegates to your own API endpointsMemory, sandboxes, mesh, MCP, voice — what each one solves and the snippet that turns it on.
BuildThe Display StackConfirmation dialogs, forms and data cards pushed by the tools that need them.
UnderstandCore ConceptsThe agent loop, adapters, subscribers and context compaction.
ExtendHooks, Skills & SubagentsShape a turn before it runs, inject context on demand, delegate to isolated children.
RememberMemoryEntities, episodes, resources and standing context — across sessions.
ReferenceReact APIGloveClient, useGlove, <Render>, defineTool and typed display props.