Quickstart

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.

PathForPackages
Full-stackNext.js App Router, tools running in the browserglove-react, glove-next
Server-onlyCLI, worker, backend service — no Reactglove-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.

The three ideas

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.

Full-stack (Next.js + React)

1. Install

terminalbash
pnpm add glove-react glove-next zod
  • glove-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 runtime

2. Create the server route

One line gives you a streaming POST endpoint. It holds your API key and proxies the model; it never sees your tool implementations.

app/api/chat/route.tstypescript
import { createChatHandler } from "glove-next";

export const POST = createChatHandler({
  provider: "anthropic",              // "openai", "gemini", "ollama", …
  model: "claude-sonnet-4-20250514",
});
.env.localbash
ANTHROPIC_API_KEY=sk-ant-...

3. Define your tools

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.

lib/glove.tstypescript
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.

4. Add the provider

app/providers.tsxtsx
"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>;
}
app/layout.tsxtsx
import { Providers } from "./providers";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

5. Build the chat UI

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.

app/page.tsxtsx
"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.

6. Make a tool show UI

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:

lib/glove.tstypescript
{
  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.

7. Run it

terminalbash
pnpm dev

Open http://localhost:3000 and ask “What's the weather in Tokyo?”. The model calls get_weather and answers from the result.

Server-only (Node, CLI, worker)

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.

terminalbash
pnpm add glove-core zod
scripts/run-agent.tstypescript
import { 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);
terminalbash
npx tsx scripts/run-agent.ts

Watching it work

Subscribers are the server-side equivalent of the React timeline — stream deltas to a terminal, a log, a socket, or a metrics sink:

scripts/run-agent.tstypescript
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.

Making it persist

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.

  • MemoryStore (glove-core) — in-process, for prototypes
  • createRemoteStore (glove-react) — delegates to your own API endpoints
  • Custom StoreAdapter — Postgres, Redis, DynamoDB, anything (contract)

Where to go next