Primitives map
Before you build something for an agent, look for it here. Every primitive Glove, Glove Foundry and Kuiper give you is on this page, grouped by the part of a harness it serves. The last column is what people write by hand when they don't know the primitive exists.
Reading the tables. Each row gives the smallest call that uses the primitive and its package. The name links to the docs. The same list is in /llms.txt and in the agent skills, so your coding agent sees it too. The parts and layers are explained in First principles.
Harness: Glove
A model becomes an agent when something runs its loop, hands it tools, decides what it sees, remembers for it, gives it somewhere to work and lets people reach it. That something is the harness.
Loop
How does it act?Gloveglove-core runs the loop: prompt the model, run the tools it asks for, feed the results back, repeat until it answers. Retries, turn limits, aborts, streaming and token accounting come with it.
| Primitive | What you get | Instead of building |
|---|---|---|
| Agent loopglove-core | Model → tool calls → results → model, until it answers. Matched tool call/result pairs, retries, a per-request turn limit.new Glove({ model, store, displayManager, systemPrompt, compaction_config }).build().processRequest(text) | a while-loop that calls the model, parses tool calls, runs them and appends results |
| Model adaptersglove-core | Anthropic, OpenAI, Gemini, OpenRouter, Bedrock, Vercel AI Gateway, Kimi, GLM, MiniMax, MiMo, Ollama and LM Studio behind one interface, with streaming, reasoning and prompt caching.createAdapter({ provider: "anthropic", model, cache: true }) | provider SDK glue, a streaming parser and tool-call format translation per provider |
| Hot-swap model and promptglove-core | Change the model or prompt between requests without rebuilding the agent or losing history.glove.setModel(adapter) · glove.setSystemPrompt(text) | tearing the agent down to switch models |
| Abort and un-abortable toolsglove-core | Cancel a request mid-loop; tools that must finish (a payment, a write) run to completion first.processRequest(text, signal) · fold({ ..., unAbortable: true }) | custom cancellation flags threaded through every tool |
| Subscribersglove-core | Typed events for text deltas, tool calls and results, compaction, tokens and subagents: stream them, log them, meter them.glove.addSubscriber({ record(type, data) { … } }) | wrapping the loop to stream text, log tool calls or count tokens |
Tools
What can it do?GloveA tool is a name, a description, a Zod schema and a function. Everything an agent can do arrives as tools, including MCP servers, code interpreters, classifiers and media pipelines.
| Primitive | What you get | Instead of building |
|---|---|---|
| Toolsglove-core | Zod-validated input, results the model reads, optional renderData the model never sees.glove.fold({ name, description, inputSchema, do }) | hand-written JSON schemas, input validation and a dispatch switch |
| Permission gatesglove-core | Ask before a tool runs, per call or always; decisions are stored per tool and input. In Foundry it becomes an approval record.fold({ ..., requiresPermission: (input) => input.destructive }) | confirm-before-delete logic inside each tool |
| Tools that change at runtimeglove-core | Add tools mid-session or swap a whole set atomically.glove.fold(tool) after build · glove.replaceTools(previousNames, nextTools) | rebuilding the agent when the available tools change |
| Tool result summariesglove-core | Large results replaced by a short summary in later requests.enableToolResultSummary: true · fold({ ..., generateToolSummary }) · do() returns generateSummaryArgs | truncating big tool results by hand |
| MCP serversglove-mcp | Any MCP server's tools as Glove tools, a discovery subagent that activates servers mid-conversation, OAuth helpers.mountMcp(glove, { adapter, entries }) | an MCP client and a schema bridge |
| Code as the tool surfaceglove-js · glove-python · glove-lisp | One execute tool over a catalogue of functions: the model writes a program that loops, filters and branches, and only the answer returns to context.mountJs(glove, { session: JsSession.create() }) | fifty tool definitions and a round-trip per step |
| Capabilities as tablesglove-scratchpad | Resources become SQL tables behind one execute_sql tool, with discovery, joins and staged writes.mountDatabase(glove, { db }) | a list/get/search tool for every resource |
| Classifiersglove-classifier | Fast, calibrated yes/no, choice and score answers from classifier models, with an LLM fallback and confidence gates.mountClassifier(glove, { classifier: jev() }) | a chat model call for every routing or triage decision |
| Image and video workflowsglove-image · glove-video | Prompt pipelines, durable characters and scenes, references, review, lineage and cost tracking.mountImage(glove, { adapter, assets, library }) · mountVideo(…) | a one-shot generate_image tool with no consistency between images |
Context
What does it see?GloveA stable system prompt, the saved history, and live state appended at the tail of every model call. Skills load guidance on demand and compaction keeps long sessions inside the window.
| Primitive | What you get | Instead of building |
|---|---|---|
| Runtime context providersglove-core | Live state appended after history before every model call, including between tool steps. Never saved to history, never rewrites the system prompt.glove.addContextProvider(() => renderCurrentState()) | rebuilding the system prompt every turn (and losing the prompt cache) |
| Compactionglove-core | Long sessions summarised automatically; the store keeps the full history for your UI.compaction_config: { compaction_instructions, compaction_context_limit } | a summariser and history-trimming logic |
| Content skillsglove-core | A listing of guides the agent can read in full, by section, when it needs them.useReadSkill(glove, skills) · loadContentSkillsFromFs(dir) | pasting every playbook into the system prompt |
| Hooks and slash skillsglove-core | /commands that change agent state or inject context before the user's message.glove.defineHook("compact", handler) · glove.defineSkill({ name, handler }) | parsing slash commands in your request handler |
| Task listglove-core | A built-in plan the agent keeps up to date and your UI can show.store with getTasks/addTasks → glove_update_tasks | a todo or planning tool |
| Pinned contextglove-memory | Standing facts every turn should see, editable by the agent, delivered as runtime context.useContext(glove, contextAdapter) | a user-preferences block in the system prompt |
State
What does it remember?GloveA store holds the conversation, tasks, permissions and inbox. glove-memory, glove-facts, forms and goals hold what the agent learns and what it is trying to get done.
| Primitive | What you get | Instead of building |
|---|---|---|
| Storeglove-core | Messages, token and turn counts, and optional tasks, permissions, inbox and subagent stores behind one interface.StoreAdapter (MemoryStore built in; bring your own) | a conversations table and token accounting |
| Inboxglove-core | Work that can't finish now is posted, resolved later by anything, and injected on the next turn. Blocking items keep the agent from acting on missing results.glove_post_to_inbox (registered when the store supports it) | polling, callbacks and 'check back later' state |
| Memoryglove-memory | An entity graph, an episodic timeline and a resource filesystem with provenance, behind adapters you own (SQLite included).useMemoryReader · useEpisodicReader · useResourcesReader (+ curators) | a vector store and an ad hoc remember tool |
| Formsglove-memory | Validated answers collected over a conversation: steps, branches, skips, undo and executors that act on completion.defineForm(…) · useFormRunner(glove, adapter, { registry, subject }) | a slot-filling state machine |
| Goalsglove-memory | Objectives that survive interruptions, with versioned progress and deferrals.defineGoalProgram(…) · useGoalRunner(glove, adapter, config) | tracking what the agent is supposed to finish inside the prompt |
| Factsglove-facts | Things people mention early, recorded once and reused by forms and goals.useFacts(glove, factStore, source) | asking the user for something they already said |
Workspace
Where does it work?GloveA virtual filesystem with scripts, checkpoints and format adapters, sandboxed interpreters, and mounted browsers and sandboxes. Work happens in files and code, not in the context window.
| Primitive | What you get | Instead of building |
|---|---|---|
| Virtual filesystemglove-vfs | One tree shared by the working environment, memory resources and REPLs, with path-scoped access rules.mountFs([...]) · withAccess(fs, policy) · inMemoryFs() · hostDirectory(dir) · cachedRemote(store) | letting tools read and write the host filesystem |
| Working environmentglove-working-environment | Files, named scripts in worker threads, checkpoints, undo, run history and artifact export, with no ambient network or host access.mountWorkingEnvironment(glove, { env: await createWorkingEnvironment({ stdlib }) }) | a read/write/run tool set and a code runner |
| Format adaptersglove-env-* | PDF, DOCX, XLSX, PPTX, images, audio, video, archives, HTTP and secrets as script imports, plus rendering pages so the agent can look at its output.stdlib: [documents(), spreadsheets(), slides(), images(), media(), render(), fetchFiles()] | wiring pdf, docx and spreadsheet libraries into tools |
| Browsersglove-execution | execute_browser: the model scripts observe, act and verify in one call, and screenshots arrive as images.mountBrowser(glove, { adapter }) | Playwright wrapped in click/type/screenshot tools |
| Sandboxesglove-execution | execute_sandbox over persistent containers: files, commands, terminals and long-running services, with ownership and cleanup.mountSandbox(glove, { adapter }) | docker exec wrappers and container bookkeeping |
Surface
How do people reach it?GloveTools push UI onto a display stack and can wait for the user's answer. React, Next.js, voice pipelines, realtime speech and avatars all read the same stack and the same agent.
| Primitive | What you get | Instead of building |
|---|---|---|
| Display stackglove-core | Tools render UI mid-conversation; pushAndWait suspends the tool until the person answers.display.pushAndForget({ renderer, input }) · await display.pushAndWait({ renderer, input }) | a side channel from tools to the UI and a pause/resume protocol |
| React bindingsglove-react | Tools with colocated, typed renderers, a hook with the whole timeline, and headless rendering.defineTool({ …, render, renderResult }) · useGlove() · <Render /> | a chat UI state machine |
| Streaming routeglove-next | An SSE endpoint for Next.js (or any fetch handler) that a GloveClient talks to.export const POST = createChatHandler({ … }) | an SSE protocol between your server and client |
| Voice pipelineglove-voice | VAD → STT → agent → TTS with barge-in, push-to-talk and React Native backends.new GloveVoice(glove, { stt, createTTS, vad }) · useGloveVoice(…) · useGlovePTT(…) | stitching speech-to-text, the agent and text-to-speech together |
| Realtime speech and avatarsglove-voice-s2s · glove-voice-avatar · glove-voice-livekit | The same tools on OpenAI Realtime, Gemini Live or GPT-Live, with live avatars and LiveKit rooms.new RealtimeAgent({ agent }) with model: s2sDrivenModel({ provider }) | a second tool implementation for the realtime provider |
| Voice callsglove-foundry/voice | A Foundry agent as a live call: same prompt, tools and memory, transcript saved, delegation results spoken at the next pause.run: voiceCall({ room, greeting }) · webSocketRoom({ port, token }) | a hand-written call server around RealtimeAgent |
Team
Who does it work with?GloveA subagent is a harness used as a tool. Mesh lets agents message each other through their inboxes, and Continuum supervises them as processes.
| Primitive | What you get | Instead of building |
|---|---|---|
| Subagentsglove-core | glove_invoke_subagent routes work to an isolated child with its own tools and store; events bracket the child's run.glove.defineSubAgent({ name, description, factory }) | a router that calls other agents by hand |
| Delegatesglove-foundry | A fast agent hands work to sibling agents and keeps talking; results and the workers' questions come back to its conversation.defineAgent({ delegates: [{ agent: "coder", description }] }) | a job queue and callback plumbing between a chat agent and its workers |
| Meshglove-mesh | Direct, broadcast and acknowledged messages between agents, delivered through their inboxes.mountMesh(glove, { adapter, identity }) | a message bus between agents |
| Continuumglove-continuum-signal | Agents supervised as subprocesses, cold per wake-up or warm and notified inline.agent("name").triggered() · new ContinuumRunner({ agentsDir }) | process supervision for long-lived agents |
System: Glove Foundry
A harness becomes a system when there are many of it, it acts on its own time, the outside world can wake it, people can oversee it and other software can call it.
Identity
Whose agent is this?Glove FoundryOne definition in code, many persisted instances in data. Each instance has its own conversations, context, installed capabilities and workspace.
| Primitive | What you get | Instead of building |
|---|---|---|
| Definitionsglove-foundry | The file path is the id. Every field can be a function of the instance, conversation and incoming message.agents/<route>/agent.ts → export default defineAgent({ … }) | a factory that rebuilds the agent per request from a switch over tenants |
| Instancesglove-foundry | Persisted agents with their own id, context, workspace, installations and playbooks.client.agent(route).create({ context }) · defineAgentInstance(definition, { … }) | an agents-per-customer table and lookup |
| Conversationsglove-foundry | Any number of durable threads per instance with exact transcripts.client.createConversation(agentId) · client.conversationTranscript(…) | thread tables and routing messages to the right history |
| Installable capabilitiesglove-foundry | Capabilities mount only on instances that install them, with typed config.defineSharedTool · defineApp · defineMcp · install(definition, config) | per-customer feature flags around tool lists |
| Shared workglove-foundry | Workspace entries, tasks, a shared inbox and scoped values that agents coordinate through.glove_foundry_workspace · _tasks · _shared_inbox · _environment | coordination tables between agents |
| Data adaptersglove-foundry | Instances, conversations, activations and workspace data persisted atomically; swap in a database for several hosts.new FileFoundryDataAdapter({ file }) · FoundryDataAdapter | serialising runtime state yourself |
Time
When does it act?Glove FoundrySchedules, sleep, spawn and background work are built-in agent tools backed by durable activations, so an agent can act later without a job queue of your own.
| Primitive | What you get | Instead of building |
|---|---|---|
| Schedulesglove-foundry | Cron, interval and one-shot work, declared in code or created by the agent, rebuilt on restart.defineSchedule({ timing: { kind: "cron", expression }, message }) · glove_foundry_schedule | cron jobs and a job queue that call your agent |
| Sleepglove-foundry | Suspend the run and wake the same instance and conversation later.glove_foundry_sleep({ kind: "for", duration: "20m", message }) | storing 'check back later' state and a timer |
| Spawn and backgroundglove-foundry | Start another instance's run, or continue work without blocking the reply.glove_foundry_spawn · glove_foundry_background | fire-and-forget promises that die with the request |
| Schedule managementglove-foundry | The agent lists and edits its own reminders and recurring work.glove_foundry_schedules({ action: "list" | "update" | "pause" | "resume" | "cancel" }) | a reminders CRUD API for the agent |
Arrival
What wakes it up?Glove FoundryTransmissions turn Slack, email, tickets or webhooks into typed events. Playbooks route them to instances, and subscriptions create the instance if none exists yet.
| Primitive | What you get | Instead of building |
|---|---|---|
| Transmissionsglove-foundry | Typed inbound events and outbound delivery for one channel; outbound becomes a validated tool when installed. Credentials stay in your adapter.defineTransmission({ inbound, outbound, account }) | a webhook handler and an API client per channel |
| Applications and connectionsglove-foundry | Channels bundled into something an instance installs; long-lived listeners started only while something needs them.defineApp({ inbound, outbound, connections }) · defineConnection(…) | polling loops and socket clients you keep alive yourself |
| Playbooksglove-foundry | Rules that match events to instances, with idempotent delivery into the right conversation.composePlaybook({ transmission, match, directives }) | a dispatcher that decides which agent handles which event |
| Subscriptionsglove-foundry | An event can create the agent that handles it: zero, one or many instances provisioned atomically.definePlaybookSubscription(…) · runtime.putPlaybookSubscription(…) | onboarding code that creates an agent before its first message |
| Inbound triageglove-classifier/foundry | Classify events before any agent starts, and match playbooks on the answers.classifyInbound({ classifier, question }) · classifierPredicate(…) | waking an LLM agent to read every event |
Oversight
Who keeps it in check?Glove FoundryPermission-gated tools become expiring approval records, running work can be steered, and the inspector shows every run, model call and tool call.
| Primitive | What you get | Instead of building |
|---|---|---|
| Approvalsglove-foundry | Gated tool calls pause the run on an expiring, fail-closed approval record with the exact input.requiresPermission → client.resolveApproval(id, "approve") | an approvals table and a pause/resume flow |
| Steeringglove-foundry | Interrupt running work cleanly and restart it in the same conversation with the new instruction.handle.steer(message) · POST /v1/runs/:id/steer | cancel-and-resend logic that races the old run |
| Inspector and WebChatglove-foundry | Every instance, conversation, run, model call, tool call and approval, plus a chat to talk to any instance.glove foundry dev → http://localhost:4141 | an admin dashboard and a debug chat |
| Event streamglove-foundry | Correlated events by definition, instance, conversation and run, live or after the fact.GET /api/events (SSE) · defineSubscriber(…) | log scraping to reconstruct what an agent did |
Access
How does other software call it?Glove FoundryA typed client, a REST API, an async run API and OpenAI-compatible endpoints, all writing into the same durable conversations.
| Primitive | What you get | Instead of building |
|---|---|---|
| Typed clientglove-foundry/client | Create instances, send messages, and wait on, stream, cancel or steer run handles, with route types generated from your files.createFoundryClient<FoundryRoutes>({ baseUrl }).agent(route) | a hand-written HTTP client for your agent service |
| OpenAI-compatible APIglove-foundry | Any OpenAI SDK or chat UI talks to a durable instance and conversation.POST /v1/chat/completions · POST /v1/responses | an adapter so existing chat clients can reach your agent |
| Run API and RESTglove-foundry | Asynchronous runs with live events, and REST over every runtime record.POST /v1/runs · GET /v1/runs/:id/events · /api/* | a job-status API around your agent |
| Request authorizationglove-foundry | One check in front of the API, the inspector and the event streams. Foundry refuses a public bind without it.defineApplication({ requestAuthorization: { authorize } }) | auth middleware in front of each route |
Production: Kuiper
A system is in production when it has somewhere to run, somewhere to keep state, a way to reach models, browsers, sandboxes and mail, and a way to watch it.
Run
Where does it run?KuiperKuiper detects a Foundry app, builds it, runs it and keeps it alive. No Dockerfile. Pushes deploy, deployments are numbered and any of them can come back.
| Primitive | What you get | Instead of building |
|---|---|---|
| Zero-config deploysKuiper | Foundry and Station apps detected and built with no Dockerfile: Node 22, the right start command, port and health check.kuiper deploy --source . -u UNIT --wait · kuiper link -u UNIT --repo … (push to deploy) | a Dockerfile, a process manager and a deploy script |
| Deployments and rollbacksKuiper | Numbered deployments, one live at a time; any earlier one redeploys with its manifest.kuiper deployments · kuiper rollback DEPLOYMENT | image tags and a rollback runbook |
| Domains and environmentsKuiper | HTTPS on every unit, custom domains, and staging environments from their own branches.kuiper domains add HOST · kuiper environments create NAME | certificates, DNS and a second copy of the stack |
Keep
Where does state live?KuiperA persistent disk at /data for Foundry's data, transcripts and memory, plus managed Postgres, Redis and buckets, and sealed secrets bound into variables.
| Primitive | What you get | Instead of building |
|---|---|---|
| Persistent diskKuiper | Where FileFoundryDataAdapter, transcripts, SQLite memory and VFS snapshots live across restarts and redeploys./data on every Foundry unit | mounting and backing up a volume |
| Managed databases and bucketsKuiper | Postgres, Redis and S3-compatible buckets inside the project, wired in by variable templates.kuiper db create postgres main · ${{main.DATABASE_URL}} | provisioning a database and copying its URL into config |
| Secrets and variablesKuiper | Sealed values bound into units, never in the image, redacted from logs.kuiper secrets set NAME=- · kuiper env set 'NAME=${{secrets.NAME}}' | a secrets manager integration |
Reach
What does it reach?KuiperOne model gateway in front of your own provider keys, real Chrome browsers, sandboxes from ready-made images, and email addresses for agents.
| Primitive | What you get | Instead of building |
|---|---|---|
| Model gatewayKuiper | Each provider's own API at one address with your keys, limits and every call recorded with tokens and cost.createAdapter({ provider, model, ...kuiper.models.glove(provider) }) | provider keys in every unit and your own usage tracking |
| Kuiper browsersKuiper | Real Chrome with profiles, hibernation and recordings, billed by the second.Steel-compatible API at ${{browsers.BASE_URL}} | running headless Chrome next to your agent |
| Kuiper sandboxesKuiper | Remote computers from ready-made images with a terminal, files and ports. An app that depends on station-sandbox gets a Docker socket Kuiper polices, for Station's own containers.kuiper.sandboxes.create({ image: "kuiper/coding" }) · capabilities.sandbox in kuiper.json | a container fleet for untrusted code |
| Kuiper inboxKuiper | An email address per agent that receives, sends, threads and calls your unit's webhook.inboxTools(inbox, { mailbox }) from @kuiper-sh/inbox/glove | an email provider, inbound parsing and webhook verification |
Watch
How do you see it?KuiperLogs that survive restarts, the agent's runs read straight into the dashboard, every model call recorded, network logs, usage and budgets.
| Primitive | What you get | Instead of building |
|---|---|---|
| Agent inspectionKuiper | Foundry runs, conversations, events and approvals read into the dashboard through the node, once requestAuthorization accepts Bearer $FOUNDRY_TOKEN. The app's token never reaches a browser.the unit's Inspect card · the project's Agents page | exposing your inspector publicly to see production |
| Logs and network logsKuiper | Logs kept across crashes and redeploys with secrets redacted, plus requests, DNS and connections per unit.kuiper logs -u UNIT -f · kuiper network summary | a log shipper and a proxy to see outbound calls |
| Usage and budgetsKuiper | Spend by resource and project, with limits that notify or block.kuiper usage · kuiper budgets create --scope project --limit 50 | spreadsheets reconciling provider bills |
| Kuiper over MCPKuiper | Your coding agent reads projects, logs and usage and, if you allow it, deploys.claude mcp add --transport http kuiper https://app.kuiper.sh/mcp | copy-pasting logs into your coding agent |
Something missing?
If you are about to build something that isn't here, first check whether it is a tool (most things are), an adapter behind an interface Glove already has, or a mount on an existing agent. Those are the rules every primitive follows, and a new piece usually fits one of them. If it really is new, open an issue: it may belong in Glove.