← Blog

Before you build it

Fourteen things teams and coding agents keep building on top of Glove, and the primitive that already does each one. Every one of these works when you build it by hand. Every one is code you then own, and most lose something the primitive gives you for free.

The pattern is always the same. Someone needs a feature, writes the obvious version, and ships it. Months later the agent forgets a reminder after a deploy, or two runs approve the same refund, or the prompt cache stops hitting. The primitive had already solved that edge. Here is the list we wish every team, and every coding agent, read first.

1. A job queue for "remind me tomorrow"

By hand: a reminders table, a worker that polls it, and a tool that inserts rows.

typescript
await db.insert(reminders).values({ userId, at: tomorrow, text });
setInterval(async () => {
  for (const due of await dueReminders()) await runAgent(due.userId, due.text);
}, 60_000);

Already there: every Foundry agent has scheduling tools. The model calls them itself, so you write nothing.

typescript
glove_foundry_schedule({ message: "Remind Ada about the dentist.", timing: { kind: "after", duration: "1d" } })
glove_foundry_sleep({ kind: "for", duration: "20m", message: "Check whether the deploy finished." })
glove_foundry_schedules({ action: "list" })

What you get: activations stored through the data adapter before they are armed, so they survive a restart. Sleep wakes the same instance and conversation with its history intact. The agent can list, pause and cancel its own reminders. Recurring work you know about in advance goes in code with defineSchedule and the agent's schedules field.

2. An approvals table with pause and resume

By hand: a pending_actions table, a tool that writes a row and returns "waiting for approval", an endpoint that flips the row, and logic to resume the agent with the answer.

Already there: one flag on the tool.

typescript
agent.fold({
  name: "refund_order",
  description: "Refund an order in full.",
  inputSchema: z.object({ orderId: z.string(), amount: z.number() }),
  requiresPermission: ({ amount }) => amount > 50,
  async do({ orderId, amount }) { /* … */ },
});

// In Foundry, a trusted host resolves it:
const [pending] = await foundry.approvals({ runId: handle.id, status: "pending" });
await foundry.resolveApproval(pending.id, "approve");

What you get: in an app, the display stack shows the prompt and the tool resumes with the answer. In Foundry, the run pauses on an approval record that carries the exact tool input, expires, and fails closed when the channel is gone or the run is cancelled. The inspector shows it with approve and deny buttons.

3. An "ask the user and wait" protocol

By hand: a need_input message over the socket, a map of pending promises keyed by request id, a resume endpoint, and cleanup for abandoned asks.

Already there: the display stack.

typescript
async do({ slots }, display) {
  const choice = await display.pushAndWait({ renderer: "pick_slot", input: { slots } });
  return { status: "success", data: choice };
}

What you get: the tool suspends until the person answers, then resumes in the same loop. pushAndForget shows a result without waiting. With glove-react's defineTool the renderer lives next to the tool, typed, with a separate view for history.

4. A conversations-per-customer table

By hand: an agents table keyed by customer, a threads table, a lookup in every request handler, and a factory that rebuilds the agent with the right tools for that customer.

Already there: Foundry instances and conversations.

typescript
const support = await foundry.agent("support").create({ context: { customerId: "c_42" } });
const thread = await foundry.createConversation(support.id, { title: "Order 1042" });
const run = await foundry.send(support.id, thread.id, "Where is order 1042?");
const done = await run.wait();

What you get: one definition in code, any number of persisted instances, each with its own context, workspace, installed capabilities and threads. Every run assembles a fresh agent from the definition, the instance and the message, so per-customer behaviour is a resolver, not a switch statement.

5. Rebuilding the system prompt every turn

By hand: append the current state to the instructions before each request.

typescript
agent.setSystemPrompt(BASE_PROMPT + "\nCurrent cart: " + JSON.stringify(await loadCart()));

Already there: a context provider.

typescript
agent.addContextProvider(async () => `Current cart: ${JSON.stringify(await loadCart())}`);

What you get: the snapshot is appended after the history before every model call, including the call after a tool changed the cart. The hand-built version only refreshes per request, and it changes the start of every request, which defeats prefix caching. The provider leaves the prompt and history stable and is never saved as a message. See Fresh context belongs inside the agent loop.

6. A summariser for long conversations

By hand: count tokens, call the model to summarise older turns, splice the summary into history, and hope no tool call is separated from its result.

Already there: compaction is part of the agent's config.

typescript
new Glove({
  // …
  compaction_config: {
    compaction_instructions: "Keep decisions, open questions and order numbers.",
    compaction_context_limit: 120_000,
  },
});

What you get: compaction never separates a tool call from its result. The store keeps the full history for your UI while the model sees the summary forward. Subscribers get compaction_start and compaction_end with the exact usage of the summary call.

7. A todo or planning tool

By hand: an update_plan tool, a place to keep the plan, and a UI that reads it.

Already there: a store that implements getTasks and addTasks (the built-in MemoryStore does) registers glove_update_tasks on the agent automatically.

What you get: the agent keeps a pending, in-progress and completed plan with an imperative and an "-ing" form for each item, saved with the conversation and readable by your UI from the store.

8. Fifty tool definitions

By hand: a fold per endpoint. The prompt grows with every one, and a task that needs ten lookups costs ten model round trips.

Already there: give the model an interpreter over a catalogue of functions.

typescript
import { JsSession, defineFn, mountJs } from "glove-js";

const session = JsSession.create();
session.register(defineFn({
  name: "orders__list",
  description: "List a customer's orders, newest first.",
  input: z.object({ customerId: z.string() }),
  readOnlyHint: true,
  handler: (args) => orders.list(args.customerId as string),
}));
mountJs(agent, { session });

What you get: one execute_js tool and discovery inside the interpreter. The model writes a program that loops, filters and branches, and only the final value returns to context. glove-python, glove-lisp and glove-scratchpad (the same catalogue as SQL tables) cover other shapes.

9. A webhook dispatcher

By hand: a route per provider, signature checks, a switch that decides which agent handles which event, a dedupe table for retries, and onboarding code that creates an agent before its first message.

Already there: transmissions, playbooks and subscriptions.

typescript
const tickets = defineTransmission({
  events: [ticketCreated],
  inbound: { event: TicketSchema, classify: () => Effect.succeed(ticketCreated), predicates: [isUrgent] },
  outbound: { input: ReplySchema, output: SentSchema, adapter: { deliver: sendReply } },
  // …
});

composePlaybook({
  name: "urgent-ticket",
  transmission: tickets,
  match: { event: ticketCreated, routes: [ticketInbound], predicate: { definition: isUrgent } },
  directives: [{ action: resolve, instruction: "Investigate and respond." }],
});

What you get: delivery is claimed by route and event id before anything runs, so a retried webhook does not start a second run. Events land in a conversation per external thread. A subscription can create zero, one or many instances when the first event arrives. Outbound delivery becomes a validated tool only on instances that install the app, and credentials stay in your adapter. See Apps & transmissions.

10. Playwright wrapped in click and type tools

By hand: open_page, click, type, screenshot, each a model round trip, with screenshots pasted into history as base64.

Already there:

typescript
import { mountBrowser } from "glove-execution";

const browser = mountBrowser(agent, { adapter });
try {
  await agent.processRequest("Fill in the contact form on example.com");
} finally {
  await browser.close();
}

What you get: execute_browser, where the model scripts open, observe, act and verify in one call. Screenshots arrive as native images on the next model call and never pile up in history. Failed scripts are not replayed. The adapter is Station today, or any BrowserAdapter you implement.

11. A docker exec wrapper

By hand: create a container per task, shell out to docker exec, track which container belongs to whom, and remember to clean up.

Already there: mountSandbox(agent, { adapter }) from glove-execution.

What you get: execute_sandbox over create, exec, files, terminals and long-running services. The scope only sees sandboxes it was granted or created, caps how many it may open, counts creations whose outcome is unknown, and on close either keeps or destroys what it made. On Kuiper, a unit can run Station's container sandboxes through the Docker socket Kuiper serves it, or call Kuiper's own sandboxes through @kuiper-sh/sdk.

12. A vector store as memory

By hand: embed every message, add remember and recall tools, and discover later that nothing records where a memory came from or who may change it.

Already there: glove-memory, with SQLite adapters included.

typescript
import { createSqliteMemoryAdapters } from "glove-memory/sqlite";
import { useContext, useEpisodicReader, useMemoryReader } from "glove-memory/tools";

const memory = createSqliteMemoryAdapters({ file: "/data/memory.db", namespace: userId, schema });
useMemoryReader(agent, memory.entity);
useEpisodicReader(agent, memory.episodic);
useContext(agent, memory.context);

What you get: an entity graph, an episodic timeline, a resource filesystem and pinned context, every write with provenance. Readers and curators are separate, so the conversational agent can look things up while a narrower agent decides what to file. Search is yours to plug in, including vectors.

13. Asking people for things they already said

By hand: nothing, usually. The user mentions their order number in the first message, and the form asks for it again five minutes later.

Already there:

typescript
import { useFacts } from "glove-facts";

useFacts(agent, facts, source); // mounts record_fact

What you get: details recorded once, scoped, revisioned and reusable by forms and goals in the same scope, which can prepare their answers from them through a dedicated preparation agent. Recorded facts are unverified on purpose: they never complete a goal or fill a field on their own.

14. Glue for each model provider

By hand: one code path per SDK, a translation between tool-call formats, a streaming parser each, and a different way to read cache usage.

typescript
import { createAdapter } from "glove-core/models/providers";

const model = createAdapter({ provider: "gemini", model: "gemini-2.5-flash", cache: true });
agent.setModel(model); // between requests, history kept

What you get: Anthropic, OpenAI, Gemini, OpenRouter, Bedrock, the Vercel AI Gateway, Kimi, GLM, MiniMax, MiMo, Ollama and LM Studio behind one interface, with streaming, reasoning, prompt caching and cache usage reported the same way.

A few more

How to check before you build

The rule of thumb: if the thing you are about to write is not specific to your product, it probably exists. Three ways to find out: