glove-core

Complete API reference for the core runtime package. Contains the agent loop, tool execution engine, display manager, context management, model adapters, and all foundational types.

Glove

The top-level builder and runtime entry point. Use the builder pattern to register tools and subscribers, then call build() to produce a runnable agent.

typescript
import { Glove, MemoryStore, Displaymanager, createAdapter } from "glove-core";
import { z } from "zod";

const agent = new Glove({
  store: new MemoryStore("session-1"),
  model: createAdapter({ provider: "anthropic" }),
  displayManager: new Displaymanager(),
  systemPrompt: "You are a helpful assistant.",
  compaction_config: {
    compaction_instructions: "Summarize the conversation.",
    max_turns: 30,
  },
})
  .fold({
    name: "get_weather",
    description: "Get weather for a city.",
    inputSchema: z.object({ city: z.string() }),
    async do(input) {
      const res = await fetch(`https://api.weather.example/v1?city=${input.city}`);
      return { status: "success", data: await res.json() };
    },
  })
  .build();

const result = await agent.processRequest("What is the weather in Tokyo?");

store is optional — when omitted, Glove constructs a fresh MemoryStore internally. You can also defer the store decision until build() by passing it there: new Glove({ ... }).build(myStore).

Constructor

new Glove(config: GloveConfig)

GloveConfig

PropertyTypeDescription
store?StoreAdapterConversation persistence. Optional — defaults to a fresh MemoryStore. May also be supplied later via build(store) / rebuild(store).
modelModelAdapterThe model adapter for language model communication. Required.
displayManagerDisplayManagerAdapterThe display manager adapter for UI slot management. Required.
systemPromptstringThe system prompt sent with every model request. Required.
serverMode?booleanDefault false. Hint to integrations (e.g. mountMcp) that no UI is present. Drives default permission gating.
maxRetries?numberMaximum number of retries for failed tool executions. Passed to the Executor.
compaction_configCompactionConfigConfiguration for automatic context window compaction. Required.
enableToolResultSummary?booleanDefault false. When true, the PromptMachine swaps each older tool result's data for its summary before sending messages to the model. Pairs with the per-tool generateToolSummary handler. See Tool result summaries below.

Methods

For the in-message /hook and /skill directive system and the subagent factory pattern, see the dedicated Hooks, Skills & Subagents guide.

MethodReturnsDescription
fold<I>(args: GloveFoldArgs<I>)IGloveBuilderRegister a tool with the agent. Legal at any time, including after build(). Returns the builder for chaining.
defineHook(name, handler)IGloveBuilderRegister a /name hook that runs before the model with full agent controls. See the Hooks, Skills & Subagents guide.
defineSkill(args)IGloveBuilderRegister a /name skill that injects context as a synthetic user message. Object form: { name, handler, description?, exposeToAgent? }. exposeToAgent: true exposes the skill via the glove_invoke_skill tool.
defineSubAgent(args)IGloveBuilderRegister a subagent factory the main agent can route to via the auto-registered glove_invoke_subagent tool. Object form: { name, factory, description? }. The factory builds and returns a fully-configured child IGloveRunnable for each invocation.
addSubscriber(subscriber: SubscriberAdapter)IGloveBuilderAdd a subscriber that receives streaming events. Returns the builder for chaining.
removeSubscriber(subscriber: SubscriberAdapter)voidDetach a previously added subscriber.
setDisplayManager(dm: DisplayManagerAdapter)IGloveBuilderSwap the display manager. Builder-form (chainable) and runtime-form. Subagents typically call this on the child to share the parent's display stack mid-run.
build(store?: StoreAdapter)IGloveRunnableFinalize configuration and return a runnable agent. If no store was supplied to the constructor and one is supplied here, the executor's already-folded tools (including auto-registered skill/subagent dispatch tools) and subscribers are transferred onto the freshly-built executor.
rebuild(store?: StoreAdapter)IGloveRunnableSame body as build(); chainable variant useful for swapping the store late.
processRequest(request, signal?)Promise<ModelPromptResult | Message>Send a request string or ContentPart[] to the agent and receive the result. Available after build().
setModel(model: ModelAdapter)voidReplace the model adapter at runtime. Useful for model switching mid-session.
setSystemPrompt(prompt: string)voidUpdate the system prompt for this session. Only safe to call when no request is in progress.
getSystemPrompt()stringReturn the current system prompt.

Properties

PropertyTypeDescription
displayManagerDisplayManagerAdapterRead-only access to the display manager instance.
modelModelAdapterRead-only access to the active model adapter.
serverModebooleanWhether the agent was constructed with serverMode: true.

GloveFoldArgs

PropertyTypeDescription
namestringUnique name for the tool.
descriptionstringDescription of what the tool does. The model reads this to decide when to invoke it.
inputSchema?z.ZodType<I>Zod schema for the tool's input. Validated locally on each call. Provide either inputSchema or jsonSchema.
jsonSchema?Record<string, unknown>Raw JSON Schema — for tools bridged from MCP, OpenAPI, etc. Skips local validation. Provide either inputSchema or jsonSchema.
requiresPermission?booleanWhen true, checks the store for permission before execution. Defaults to false.
unAbortable?booleanWhen true, the tool runs to completion even if the abort signal fires (e.g. from voice barge-in). Use for mutation-critical tools. Defaults to false.
do(input: I, display: DisplayManagerAdapter, glove: IGloveRunnable, signal?: AbortSignal) => Promise<ToolResultData>The tool's implementation. Receives validated input, the parent's display manager, the running Glove instance (use to fold further tools at runtime), and the active request's AbortSignal. Forward the signal into long-running internal work so abort propagates.
generateToolSummary?(summaryArgs?: unknown) => Promise<string>Optional. When the tool's do() returns a ToolResultData with generateSummaryArgs set, the Executor calls this with those args and stores the returned string on result.summary. The summary replaces data in older context when Glove was constructed with enableToolResultSummary: true.

IGloveRunnable

The interface returned by build(). Represents a fully configured, ready-to-run agent. Most builder methods are also part of this interface — folding tools and registering hooks/skills/subagents is legal at any time, including after build.

MemberTypeDescription
processRequest(request, signal?)(request: string | ContentPart[], signal?: AbortSignal) => Promise<ModelPromptResult | Message>Send a user request to the agent and get the response. Parses /hook and /skill directives; @mentions reach the model verbatim.
fold<I>(args: GloveFoldArgs<I>)IGloveRunnableFold a tool. Legal at any time, including after build.
defineHook(name, handler)IGloveRunnableRegister a /name hook handler.
defineSkill(args)IGloveRunnableRegister a /name skill handler.
defineSubAgent(args)IGloveRunnableRegister a subagent factory.
rebuild(store?)IGloveRunnableRebuild the agent's internals, optionally swapping the store. Tools folded before rebuild are transferred onto the new executor.
setModel(model)(model: ModelAdapter) => voidSwap the model adapter at runtime.
setSystemPrompt(prompt)(prompt: string) => voidUpdate the system prompt.
getSystemPrompt()() => stringRead the current system prompt.
setDisplayManager(dm)(dm: DisplayManagerAdapter) => voidSwap the display manager. Subagents typically call this on the child Glove to share the parent's display stack mid-run.
addSubscriber(s)(s: SubscriberAdapter) => voidAttach a SubscriberAdapter to the prompt machine, executor, and observer.
removeSubscriber(s)(s: SubscriberAdapter) => voidDetach a previously attached SubscriberAdapter.
displayManagerDisplayManagerAdapterRead-only reference to the active display manager.
modelModelAdapterRead-only reference to the active model adapter.
serverModebooleanWhether the agent was constructed with serverMode: true.

DisplayManager

Manages the display stack: a ordered collection of UI slots that tools push and users resolve. Implements DisplayManagerAdapter.

typescript
import { Displaymanager } from "glove-core/display-manager";

const dm = new Displaymanager();

dm.subscribe((stack) => {
  console.log("Display stack changed:", stack);
});

Methods

MethodReturnsDescription
registerRenderer<I,O>(renderer: Renderer<I,O>)voidRegister a named renderer with input/output schemas.
pushAndForget<I>(slot: { renderer?: string; input: I })Promise<string>Push a slot onto the stack without blocking. Returns the slot ID.
pushAndWait<I,O>(slot: { renderer?: string; input: I })Promise<O>Push a slot and block until resolved or rejected. Returns the resolved value.
subscribe(listener: ListenerFn)UnsubscribeFnSubscribe to stack changes. The listener is called with the current stack whenever it changes. Returns an unsubscribe function.
notify()Promise<void>Manually trigger all subscribed listeners with the current stack state.
resolve<O>(slot_id: string, value: O)voidResolve a pushAndWait slot by ID, unblocking the waiting tool.
reject(slot_id: string, error: string)voidReject a pushAndWait slot by ID, causing the pushAndWait promise to throw.
removeSlot(id: string)voidRemove a slot from the stack by ID.
clearStack()Promise<void>Remove all slots from the display stack and notify listeners.

DisplayManagerAdapter Interface

The interface that DisplayManager implements. Any custom display manager must conform to this shape.

MemberTypeDescription
renderersArray<Renderer<unknown, unknown>>Registry of named renderers.
stackSlot<unknown>[]The current display stack, ordered from bottom to top.
listenersSet<ListenerFn>Set of subscribed listener functions.
resolverStoreMap<string, { resolve: ResolverFn<unknown>; reject: RejectFn }>Internal map of pending pushAndWait resolvers keyed by slot ID.
registerRenderer(renderer)voidRegister a renderer.
pushAndForget(slot)Promise<string>Push without blocking.
pushAndWait(slot)Promise<unknown>Push and block until resolved.
notify()Promise<void>Trigger listeners.
subscribe(listener)UnsubscribeFnSubscribe to changes.
resolve(slot_id, value)voidResolve a pending slot.
reject(slot_id, error: any)voidReject a pending slot.
removeSlot(id)voidRemove a slot by ID.
clearStack()Promise<void>Clear all slots.

Slot

Represents a single entry on the display stack. Pushed by tools, rendered by the UI layer.

PropertyTypeDescription
idstringUnique identifier for this slot instance.
rendererstringName of the renderer to use for displaying this slot.
inputIInput data passed to the renderer. Shape depends on the tool that created the slot.

Renderer

A named renderer definition registered with the display manager.

PropertyTypeDescription
namestringUnique name identifying this renderer.
inputSchemaz.ZodType<I>Zod schema for validating the input data.
outputSchema?z.ZodType<O>Optional Zod schema for validating the resolved output.

Context

Wraps a StoreAdapter and provides a simplified interface for reading and writing conversation data, messages, and tasks.

typescript
import { Context } from "glove-core";

const ctx = new Context(store);
const messages = await ctx.getMessages();
await ctx.appendMessages([{ sender: "user", text: "Hello" }]);

Constructor

new Context(store: StoreAdapter)

Methods

MethodReturnsDescription
getMessages()Promise<Message[]>Retrieve messages for the model. Applies splitAtLastCompaction internally: finds the last message with is_compaction set to true and returns only messages from that point onward. This means the model sees the compaction summary plus any subsequent messages, not the full raw history. To access the complete unfiltered history, use the store's getMessages() directly.
appendMessages(msgs: Message[])Promise<void>Append messages to the conversation history.
getTasks()Promise<Task[]>Retrieve all tasks from the store. Requires store to implement getTasks.
addTasks(tasks: Task[])Promise<void>Add tasks to the store. Requires store to implement addTasks.
updateTask(taskId: string, updates: Partial<Task>)Promise<void>Update a task by ID. Requires store to implement updateTask.

PromptMachine

Manages model prompting: sends messages and tool definitions to the model adapter and collects the response. Notifies subscribers of streaming events.

typescript
import { PromptMachine } from "glove-core";

const pm = new PromptMachine(model, ctx, "You are a helpful assistant.");
pm.addSubscriber(subscriber);
const result = await pm.run(messages, tools);

Constructor

new PromptMachine(model: ModelAdapter, ctx: Context, systemPrompt: string, enableToolResultSummary?: boolean)

The optional enableToolResultSummary flag (default false) is wired through from GloveConfig.enableToolResultSummary. When set, every call to run() first passes the message list through summarizeOlderToolResults.

Methods

MethodReturnsDescription
addSubscriber(subscriber: SubscriberAdapter)voidAdd a subscriber to receive model events (text_delta, tool_use, model_response_complete).
summarizeOlderToolResults(messages: Message[])Message[]Pure transform. Finds the index of the latest non-tool user message and, for every message with tool_results at or before that index, replaces result.data with result.summary when summary is present. Untouched messages (including the current turn's tool results) are returned by reference. Called automatically by run() when enableToolResultSummary is true.
run(messages: Message[], tools?: Tool<unknown>[], signal?: AbortSignal)Promise<ModelPromptResult>Prompt the model with messages and optional tools. When enableToolResultSummary is true, the message list is first passed through summarizeOlderToolResults. Returns the model's response including token counts.

Executor

The tool execution engine. Maintains a registry of tools and a call stack. Executes tool calls from the model, validates inputs, handles errors, and returns results.

typescript
import { Executor } from "glove-core";

const executor = new Executor(3, store);
executor.registerTool(myTool);
executor.addSubscriber(subscriber);

executor.addToolCallToStack({ tool_name: "get_weather", input_args: { city: "Tokyo" } });
const results = await executor.executeToolStack();

Constructor

new Executor(MAX_RETRIES?: number, store?: StoreAdapter)

Properties

PropertyTypeDescription
toolsTool[]Array of registered tools.
MAX_RETRIESnumberMaximum retry attempts for failed tool calls.

Methods

MethodReturnsDescription
registerTool(tool: Tool<unknown>)voidAdd a tool to the executor's registry.
addSubscriber(subscriber: SubscriberAdapter)voidAdd a subscriber to receive tool execution events (tool_use, tool_use_result).
addToolCallToStack(call: ToolCall)voidQueue a tool call for execution.
executeToolStack(handOver?: HandOverFunction, signal?: AbortSignal)Promise<ToolResult[]>Execute all queued tool calls and return their results. Clears the stack after execution.

Observer

Monitors the context window size and triggers compaction when limits are exceeded. Tracks turn counts and token consumption. Compaction is history-preserving: messages are never deleted from the store. Instead, a compaction summary is appended with is_compaction: true, and resetCounters() resets the token and turn counts. The model only sees post-compaction messages because Context.getMessages() applies splitAtLastCompaction() internally.

typescript
import { Observer } from "glove-core";

const observer = new Observer(
  store,
  ctx,
  promptMachine,
  "Summarize the conversation so far.",
  30,   // max turns
  100000 // context compaction token limit
);

await observer.turnComplete();
await observer.tryCompaction();

Constructor

new Observer(store: StoreAdapter, ctx: Context, prompt: PromptMachine, compaction_instructions: string, max_turns?: number, context_compaction_limit?: number, escape_compaction_threshold?: number)

Properties

PropertyTypeDescription
MAX_TURNSnumberMaximum turns per request. Defaults to 120. Exceeding this aborts the loop with a polite error message.
CONTEXT_COMPACTION_LIMITnumberToken count above which compaction runs. Defaults to 100,000.
ESCAPE_COMPACTION_THRESHOLDnumberPre-emptive compaction percentage (0-100). Defaults to 90. When the current consumption is at or above this fraction of CONTEXT_COMPACTION_LIMIT, Agent.ask runs compaction immediately after a model turn that contained tool_use calls — keeps tool_use / tool_result pairs from being split across a compaction boundary.

Methods

MethodReturnsDescription
setCompactionInstructions(instruction: string)voidUpdate the compaction instructions at runtime.
setMaxTurns(new_max: number)voidUpdate the maximum turn threshold.
setContextCompactionLimit(new_limit: number)voidUpdate the token consumption threshold.
turnComplete()Promise<void>Notify the observer that a turn has completed. Increments the turn counter in the store.
getCurrentTurns()Promise<number>Get the current turn count from the store.
addTokensConsumed(args: TokenConsumptionCounter)Promise<void>Add { tokens_in, tokens_out } to the store and emit a token_consumption subscriber event.
isCompactionImminent()Promise<boolean>Returns true when current consumption has crossed the ESCAPE_COMPACTION_THRESHOLD fraction of CONTEXT_COMPACTION_LIMIT. Used by Agent.ask to compact pre-emptively after tool-call turns.
getCurrentTokenConsumption()Promise<number>Get the current total token consumption from the store.
tryCompaction()Promise<void>Check if compaction is needed (turns or tokens exceeded) and perform it if so. Summarizes the conversation and appends the summary as a new message with is_compaction set to true. Calls resetCounters() to reset token and turn counts without deleting messages. The full message history is preserved in the store for frontend display, while Context.getMessages() uses splitAtLastCompaction to ensure the model only sees messages from the latest compaction onward.
runCompactionNow()Promise<void>Same body as tryCompaction() but skips the token-threshold guard. Used by hook handlers via AgentControls.forceCompaction() to compact on demand.

Agent

Orchestrates the core agent loop: prompt the model, check for tool calls, execute tools, feed results back, repeat until the model responds with text only.

typescript
import { Agent } from "glove-core";

const agent = new Agent(store, executor, context, observer, promptMachine);
const result = await agent.ask(userMessage);

Constructor

new Agent(store: StoreAdapter, executor: Executor, context: Context, observer: Observer, prompt_machine: PromptMachine)

Methods

MethodReturnsDescription
ask(message: Message, handOver?: HandOverFunction, signal?: AbortSignal)Promise<ModelPromptResult>Run the full agent loop for a user message. Prompts the model, executes any tool calls, loops until the model produces a final text response. Returns the final result with token counts.

AbortError

Custom error class thrown when an agent request is aborted via an AbortSignal. Has name set to "AbortError".

typescript
import { AbortError } from "glove-core";

try {
  await agent.processRequest("Hello", signal);
} catch (err) {
  if (err instanceof AbortError) {
    console.log("Request was aborted.");
  }
}

Constructor

new AbortError(message?: string)

ModelAdapter

Interface for language model providers. Implement this to connect any LLM to Glove.

typescript
interface ModelAdapter {
  name: string;
  prompt(
    request: PromptRequest,
    notify: NotifySubscribersFunction,
    signal?: AbortSignal
  ): Promise<ModelPromptResult>;
  setSystemPrompt(systemPrompt: string): void;
}
MemberTypeDescription
namestringDisplay name of the model or provider.
prompt(request, notify, signal?)Promise<ModelPromptResult>Send messages and tools to the model. Call notify() to emit streaming events. Returns the complete response with token counts.
setSystemPrompt(systemPrompt)voidUpdate the system prompt used for subsequent requests.

PromptRequest

PropertyTypeDescription
messagesMessage[]The conversation messages to send to the model.
tools?Tool<unknown>[]Optional array of tools the model can invoke.

ModelPromptResult

PropertyTypeDescription
messagesMessage[]Response messages from the model (typically one agent message).
tokens_innumberInput tokens consumed by this prompt.
tokens_outnumberOutput tokens generated by this prompt.

Subscriber Events

Every model adapter emits events via the notify callback during prompting. These events are fully typed using a discriminated union so that subscribers (and custom adapter authors) get compile-time safety.

SubscriberEvent

A discriminated union of all event shapes. Each variant has a type field plus event-specific data.

typescript
type SubscriberEvent =
  | { type: "text_delta"; text: string }
  | { type: "tool_use"; id: string; name: string; input: unknown }
  | { type: "model_response"; text: string; tool_calls?: ToolCall[];
      stop_reason?: string; tokens_in?: number; tokens_out?: number }
  | { type: "model_response_complete"; text: string; tool_calls?: ToolCall[];
      stop_reason?: string; tokens_in?: number; tokens_out?: number }
  | { type: "tool_use_result"; tool_name: string; call_id?: string;
      result: ToolResultData }
  | { type: "compaction_start"; current_token_consumption: number }
  | { type: "compaction_end"; current_token_consumption: number;
      summary_message: Message }
  | { type: "token_consumption"; consumption: TokenConsumptionCounter }
  | { type: "hook_invoked"; name: string }
  | { type: "skill_invoked"; name: string; source: "user" | "agent"; args?: string }
  | { type: "subagent_invoked"; name: string; prompt: string }
  | { type: "subagent_completed"; name: string; status: "success" | "error"; message?: string };

Event Reference

EventEmitted byDescription
text_deltaModel adapter (streaming)Incremental text fragment from the model. Use to render streaming text in the UI.
tool_useModel adapter (streaming)The model is invoking a tool. Contains the tool id, name, and parsed input.
model_responseModel adapter (non-streaming)Complete model response in non-streaming mode. Contains text, optional tool_calls, stop_reason, and token counts.
model_response_completeModel adapter (streaming)Final aggregated response after streaming finishes. Same shape as model_response.
tool_use_resultExecutorResult of executing a tool. Includes tool_name, call_id, and the full ToolResultData.
compaction_startObserverConversation compaction is beginning. Contains current token consumption.
compaction_endObserverCompaction finished. Contains the new token count and the summary message.
token_consumptionObserverTokens were just added to the running totals. Carries the per-turn TokenConsumptionCounter.
hook_invokedGloveA user-side /name hook handler is about to run.
skill_invokedGlove / skill dispatch toolA skill handler is about to run. source: "user" for /name directive invocations, "agent" when the model called glove_invoke_skill.
subagent_invokedExecutorA subagent's child Glove run is about to start. Carries the subagent name and the prompt the model supplied.
subagent_completedExecutorCloses the subagent_invoked bracket with a 1:1 guarantee — fired even on abort or factory failure.

SubscriberEventDataMap

A mapped type that extracts the data shape (everything except type) for each event. Use this when implementing a SubscriberAdapter or handling events in a switch statement.

typescript
type SubscriberEventDataMap = {
  [E in SubscriberEvent as E["type"]]: Omit<E, "type">;
};

// Example: SubscriberEventDataMap["text_delta"] = { text: string }

SubscriberAdapter

Interface for receiving events. Both the React hook subscriber and GloveVoice implement this. The record method is generic over the event type.

typescript
interface SubscriberAdapter {
  record: <T extends SubscriberEvent["type"]>(
    event_type: T,
    data: SubscriberEventDataMap[T],
  ) => Promise<void>;
}

Implementing a Custom Model Adapter

When building a custom ModelAdapter, you must emit the correct events via the notify callback. Here is the minimal contract:

typescript
import type { ModelAdapter, NotifySubscribersFunction, PromptRequest, ModelPromptResult } from "glove-core";

class MyAdapter implements ModelAdapter {
  name = "my-provider:model-name";
  private systemPrompt?: string;

  setSystemPrompt(systemPrompt: string) {
    this.systemPrompt = systemPrompt;
  }

  async prompt(
    request: PromptRequest,
    notify: NotifySubscribersFunction,
    signal?: AbortSignal,
  ): Promise<ModelPromptResult> {
    // ... call your LLM API ...

    // Non-streaming: emit a single model_response event
    await notify("model_response", {
      text: responseText,
      tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
      stop_reason: finishReason ?? undefined,
      tokens_in: usage.promptTokens,
      tokens_out: usage.completionTokens,
    });

    return { messages: [message], tokens_in: ..., tokens_out: ... };
  }
}

For streaming adapters, emit events incrementally:

typescript
// During streaming — emit text fragments as they arrive
notify("text_delta", { text: chunk });

// When a tool call is fully assembled
await notify("tool_use", { id: toolCallId, name: toolName, input: parsedArgs });

// After the stream completes — emit the final aggregated response
await notify("model_response_complete", {
  text: fullText,
  tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
  stop_reason: finishReason ?? undefined,
});

Key rules:

StoreAdapter

Interface for conversation persistence. Implement this to store messages, token counts, tasks, and permissions in any backend.

typescript
interface StoreAdapter {
  identifier: string;
  getMessages(): Promise<Message[]>;
  appendMessages(msgs: Message[]): Promise<void>;
  getTokenCount(): Promise<number>;
  addTokens(args: TokenConsumptionCounter): Promise<void>;
  getTurnCount(): Promise<number>;
  incrementTurn(): Promise<void>;
  resetCounters(): Promise<void>;
  // Optional — tasks:
  getTasks?(): Promise<Task[]>;
  addTasks?(tasks: Task[]): Promise<void>;
  updateTask?(taskId: string, updates: Partial<Task>): Promise<void>;
  // Optional — permissions:
  getPermission?(toolName: string): Promise<PermissionStatus>;
  setPermission?(toolName: string, status: PermissionStatus): Promise<void>;
  // Optional — inbox:
  getInboxItems?(): Promise<InboxItem[]>;
  addInboxItem?(item: InboxItem): Promise<void>;
  updateInboxItem?(
    itemId: string,
    updates: Partial<Pick<InboxItem, "status" | "response" | "resolved_at">>,
  ): Promise<void>;
  getResolvedInboxItems?(): Promise<InboxItem[]>;
  // Optional — subagent stores:
  createSubAgentStore?(namespace: string, durable?: boolean): Promise<StoreAdapter>;
}

interface TokenConsumptionCounter {
  tokens_in: number;
  tokens_out: number;
}
MemberTypeDescription
identifierstringUnique identifier for the store instance (typically a session ID).
getMessages()Promise<Message[]>Retrieve all conversation messages.
appendMessages(msgs)Promise<void>Append messages to the history.
getTokenCount()Promise<number>Get the cumulative token count.
addTokens(args)Promise<void>Add { tokens_in, tokens_out } to the cumulative counts. getTokenCount() still returns a single sum.
getTurnCount()Promise<number>Get the current turn count.
incrementTurn()Promise<void>Increment the turn counter.
resetCounters()Promise<void>Reset token and turn counts to zero without deleting messages. Called during compaction to reset thresholds while preserving the full message history in the store.
getTasks?()Promise<Task[]>Retrieve all tasks. Optional. Enables the built-in task tool when present.
addTasks?(tasks)Promise<void>Add tasks. Optional.
updateTask?(taskId, updates)Promise<void>Update a task by ID. Optional.
getPermission?(toolName)Promise<PermissionStatus>Check permission status for a tool. Optional.
setPermission?(toolName, status)Promise<void>Set permission status for a tool. Optional.
getInboxItems?()Promise<InboxItem[]>Retrieve all inbox items. Optional. See the Inbox guide.
addInboxItem?(item)Promise<void>Persist a new inbox item. Optional.
updateInboxItem?(itemId, updates)Promise<void>Mutate an inbox item's status / response / resolved_at. Optional.
getResolvedInboxItems?()Promise<InboxItem[]>Retrieve only items in the resolved status. Optional.
createSubAgentStore?(namespace, durable?)Promise<StoreAdapter>Derive a child store for a subagent. With durable: false (default) every invocation returns a fresh store; with durable: true the same child store is returned for the same namespace so the subagent accumulates history across calls. Optional — when absent, subagent factories must construct their own store.

MemoryStore

In-process implementation of StoreAdapter shipped with glove-core. Used as the default store when Glove is constructed without one. All data lives in memory and is lost when the instance is garbage-collected — perfect for prototypes, scripts, tests, and short-lived sessions.

MemoryStore implements every optional method, including createSubAgentStore, so subagents work out of the box without any extra setup. With durable: true, derived sub-stores are cached per namespace so a subagent can carry message history across invocations within the same parent process.

typescript
import { MemoryStore } from "glove-core";

const store = new MemoryStore("session-1");

// Sub-stores
const ephemeral = await store.createSubAgentStore("reviewer");           // fresh per call
const durable = await store.createSubAgentStore("planner", true);        // cached per namespace

Constructor

new MemoryStore(identifier: string)

SubscriberAdapter

Interface for observing agent events. record is generic over the event type via SubscriberEventDataMap, so the compiler enforces that the data shape matches the event name.

typescript
interface SubscriberAdapter {
  record: <T extends SubscriberEvent["type"]>(
    event_type: T,
    data: SubscriberEventDataMap[T],
  ) => Promise<void>;
}
MemberTypeDescription
record(event_type, data)Promise<void>Called whenever an event occurs. The event_type discriminates the data shape via the SubscriberEventDataMap.

For the full list of events and payload shapes, see the SubscriberEvent reference above.

Message

Represents a single message in the conversation history.

typescript
interface Message {
  sender: "user" | "agent";
  id?: string;
  text: string;
  pre_modified_text?: string;
  content?: ContentPart[];
  tool_results?: ToolResult[];
  tool_calls?: ToolCall[];
  is_compaction?: boolean;
  is_compaction_request?: boolean;
  is_skill_injection?: boolean;
  reasoning_content?: string;
}
PropertyTypeDescription
sender"user" | "agent"Who sent the message.
id?stringOptional unique identifier for the message.
textstringThe text content of the message. After hooks rewrite a user turn, this holds the rewritten text.
pre_modified_text?stringOriginal user text before any hook rewrite. Useful for transcript renderers that want to show the user what they actually typed.
content?ContentPart[]Optional multimodal content parts (images, documents, etc.).
tool_results?ToolResult[]Tool execution results attached to this message (agent messages responding to tool calls).
tool_calls?ToolCall[]Tool calls the model wants to execute (present in agent messages).
is_compaction?booleanWhen true, marks this message as a compaction summary. Context.getMessages() uses this flag to split the history at the last compaction point, so the model only sees messages from the most recent compaction onward.
is_compaction_request?booleanInternal marker on the synthetic user message that prompts the model for a compaction summary.
is_skill_injection?booleanWhen true, marks this message as a synthetic user turn produced by a /skill invocation (see Hooks, Skills & Mentions). Use it in transcript renderers to distinguish injected context from real user turns.
reasoning_content?stringProvider-emitted reasoning trace captured by the OpenAI-compat adapter (when reasoning is enabled) or the MiMo adapter. DeepSeek V4 and MiMo require this to be echoed back on subsequent tool-calling turns — the adapters handle that round-trip automatically.

ContentPart

Represents a multimodal content element within a message.

typescript
interface ContentPart {
  type: "text" | "image" | "video" | "document";
  text?: string;
  source?: {
    type: string;
    media_type: string;
    data?: string;
    url?: string;
  };
}
PropertyTypeDescription
type"text" | "image" | "video" | "document"The type of content.
text?stringText content. Used when type is "text".
source?objectSource information for binary content. Contains type, media_type, and either data (base64) or url.
source.typestringSource type (e.g., "base64", "url").
source.media_typestringMIME type (e.g., "image/png", "application/pdf").
source.data?stringBase64-encoded content data.
source.url?stringURL pointing to the content.

Tool

The core tool interface used by the Executor. This is the runtime representation, distinct from ToolConfig in glove-react which adds the render property.

PropertyTypeDescription
namestringUnique tool name.
descriptionstringDescription for the model.
input_schema?z.ZodType<I>Zod schema for input validation and JSON Schema generation. Provide either input_schema or jsonSchema.
jsonSchema?Record<string, unknown>Raw JSON Schema for tools bridged from MCP / OpenAPI / etc. Skips local validation. Provide either input_schema or jsonSchema.
requiresPermission?booleanWhether the tool requires explicit permission before execution.
unAbortable?booleanWhen true, the tool runs to completion despite abort signals. Essential for tools that perform mutations the user has committed to (e.g. checkout, payment).
run(input, handOver?, signal?)Promise<ToolResultData>Execute the tool with validated input. handOver delegates to the renderer / display stack. signal is the active request's AbortSignal — forward it into long-running internal work (e.g. a child Glove run) so abort propagates. Tools marked unAbortable should ignore signal.
generateSummary?(args)(args: unknown) => Promise<string>Optional. Called by the Executor after run() resolves when the result includes generateSummaryArgs. The returned string lands on result.summary and is swapped in for data in older messages when enableToolResultSummary is on.

ToolCall

PropertyTypeDescription
tool_namestringName of the tool to invoke.
input_argsunknownArguments to pass to the tool (validated against the tool's input schema at runtime).
id?stringOptional call identifier for correlating calls with results.

ToolResult

PropertyTypeDescription
tool_namestringName of the tool that produced this result.
call_id?stringIdentifier correlating this result with its ToolCall.
resultToolResultDataThe execution result. See ToolResultData below.

ToolResultData

The shape of the result field on a ToolResult. Contains the data returned by the tool, a status indicator, an optional error message, and an optional client-only rendering payload.

typescript
interface ToolResultData {
  status: "success" | "error" | "aborted";
  data: unknown;                 // Sent to the AI model
  message?: string;              // Error / abort message
  renderData?: unknown;          // Client-only — NOT sent to model, used by renderResult
  summary?: string;              // Set by the Executor from generateSummary — swapped in for data in older context when enableToolResultSummary is on
  generateSummaryArgs?: unknown; // Opaque args passed to the tool's generateSummary handler
}
PropertyTypeDescription
status"success" | "error" | "aborted"Outcome of the tool. The Executor synthesizes "aborted" results when an abort signal interrupts an abortable tool.
dataunknownThe tool's return value. This is the data sent to the AI model as the tool result.
message?stringError message describing what went wrong. Typically present when status is "error".
renderData?unknownClient-only data for rendering tool results from history. Model adapters explicitly strip this field before sending to the AI — safe for sensitive client-only data like email addresses or UI state. Used by the renderResult function in glove-react tools.
summary?stringCompact string description of the result. Set by the Executor after run()/do() resolves, by calling the tool's generateSummary(generateSummaryArgs). When the Glove was constructed with enableToolResultSummary: true, the PromptMachine substitutes summary for data on every tool result older than the most recent user message before sending to the model. Untouched in the store and in renderers.
generateSummaryArgs?unknownOpaque payload the tool's do() returns to drive its generateSummary handler — e.g. the line range it just read, the URL it just fetched, the query it just executed. Omit it to skip summary generation for a given call.

Model adapters (Anthropic, OpenAI-compat) explicitly destructure and only send data, status, and message to the API. The renderData field is preserved in the message store for client-side rendering via renderResult but is never sent to the AI model.

Tool result summaries

Long-running agents that read files, fetch URLs, or run queries blow through tokens fast — the model rarely needs the full payload of a tool call once a few turns have passed. Tool result summaries are an opt-in optimization: each tool produces a compact description of what it did, and that description is what older context carries instead of the raw payload.

The mechanism has three coordinated pieces:

  1. Tool returns generateSummaryArgs. The tool's do() includes whatever the summary handler needs (line range, URL, query, row count) on the result.
  2. Executor calls generateToolSummary. After do() resolves, if the tool defined a generateToolSummary handler and the result has generateSummaryArgs, the Executor awaits it and assigns the returned string to result.summary.
  3. PromptMachine swaps data for summary in older context. When Glove is constructed with enableToolResultSummary: true, PromptMachine.summarizeOlderToolResults rewrites every tool result that sits at or before the most recent non-tool user message: result.data is replaced with result.summary. Tool results from the current turn are untouched, so the model still has full fidelity for what it just asked about.
file-read.tool.tstypescript
import { z } from "zod";
import type { GloveFoldArgs } from "glove-core";

export const readFile: GloveFoldArgs<{ path: string; from?: number; to?: number }> = {
  name: "read_file",
  description: "Read a slice of a file.",
  inputSchema: z.object({
    path: z.string(),
    from: z.number().optional(),
    to: z.number().optional(),
  }),
  async do(input) {
    const content = await fs.readFile(input.path, "utf8");
    const slice = sliceLines(content, input.from, input.to);
    return {
      status: "success",
      data: slice,
      // What the summary handler needs to describe this call later.
      generateSummaryArgs: { path: input.path, from: input.from, to: input.to, lineCount: slice.split("\n").length },
    };
  },
  async generateToolSummary(args) {
    const { path, from, to, lineCount } = args as { path: string; from?: number; to?: number; lineCount: number };
    const range = from != null || to != null ? ` lines ${from ?? 1}-${to ?? "EOF"}` : "";
    return `Read ${path}${range} (${lineCount} lines).`;
  },
};
agent.tstypescript
const agent = new Glove({
  store: new MemoryStore("session"),
  model: createAdapter({ provider: "anthropic" }),
  displayManager: new Displaymanager(),
  systemPrompt: "You are a helpful assistant.",
  compaction_config: { compaction_instructions: "Summarize so far." },
  enableToolResultSummary: true,
})
  .fold(readFile)
  .build();

What the model sees

Imagine the agent has executed read_file three turns ago and again on the current turn. With enableToolResultSummary: true:

The store keeps both data and summary untouched on every result, so re-renders, transcripts, and post-hoc analytics still have the full record. Only the array of messages handed to the model adapter is rewritten.

Opt-in per tool

Tools that omit generateToolSummary, or omit generateSummaryArgs on a particular call, leave summary unset. The pruner only substitutes when summary is a non-empty string, so partially-instrumented tool catalogues still work — instrumented tools shrink in older context, uninstrumented tools keep their original data.

Compaction vs summaries

Compaction collapses an entire run of messages into a single summary message once token use crosses a threshold (see Observer). Tool result summaries shrink individual tool payloads on every turn before compaction would have fired. The two compose: tool summaries delay the point at which the Observer needs to compact, and compaction still runs when the instrumented context eventually grows large enough.

Task

Represents a tracked task in the agent's task list.

PropertyTypeDescription
idstringUnique identifier for the task.
contentstringDescription of the task in imperative form (e.g., "Fix the login bug").
activeFormstringPresent-continuous form shown during execution (e.g., "Fixing the login bug").
statusTaskStatusCurrent status: "pending", "in_progress", or "completed".

TaskStatus

typescript
type TaskStatus = "pending" | "in_progress" | "completed";

PermissionStatus

typescript
type PermissionStatus = "granted" | "denied" | "unset";

Function Types

TypeSignatureDescription
NotifySubscribersFunction<T extends SubscriberEvent['type']>(event_name: T, data: SubscriberEventDataMap[T]) => Promise<void>Type-safe callback passed to ModelAdapter.prompt for emitting events to subscribers.
HandOverFunction(input: unknown) => Promise<unknown>Delegation callback passed to tool execution for handing control to another tool or system.
ListenerFn(stack: Slot<unknown>[]) => Promise<void>Display stack change listener. Called whenever the stack is modified.
UnsubscribeFn() => voidReturned by subscribe() to remove a listener.
ResolverFn<RI>(value: RI) => voidInternal resolver for pushAndWait promises.
RejectFn(reason?: any) => voidInternal rejector for pushAndWait promises.

Exported Constants

NameValueDescription
SUBAGENT_DISPATCH_TOOL_NAME"glove_invoke_subagent"Tool name of the auto-registered subagent dispatch tool. The Executor recognises calls to this tool name and brackets them with subagent_invoked / subagent_completed events. Match against this constant rather than the literal string when filtering events or tool calls.

CompactionConfig

Compaction is history-preserving. When triggered, the full conversation is summarized and the summary is appended as a new message with is_compaction: true. No messages are deleted from the store, so frontends can still display the complete history. The model only sees messages from the last compaction point onward, courtesy of splitAtLastCompaction() in Context.getMessages().

PropertyTypeDescription
compaction_instructionsstringInstructions given to the model when summarizing the conversation. Required.
max_turns?numberMaximum turns before compaction is triggered.
compaction_context_limit?numberMaximum token count before compaction is triggered.

Built-in Task Tool

The framework provides a built-in tool for task management. It is automatically registered when the store supports tasks (implements getTasks, addTasks, updateTask).

typescript
import { createTaskTool } from "glove-core";

const taskTool = createTaskTool(context);
// taskTool.name === "glove_update_tasks"

Signature

typescript
function createTaskTool(context: Context): Tool<TaskToolInput>

TaskToolInput

PropertyTypeDescription
todosArray<{ content: string; activeForm: string; status: TaskStatus }>The complete task list. Each call replaces the entire list.

The tool name is glove_update_tasks. The model calls it to create, update, or complete tasks. Each invocation sends the full current task list, enabling additions, status changes, and removals in a single call.

Providers

The glove-core/models/providers module exports factory functions for creating model adapters from supported providers.

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

const model = createAdapter({
  provider: "anthropic",
  model: "claude-sonnet-4-20250514",
  maxTokens: 4096,
  stream: true,
});

const available = getAvailableProviders();
// [{ id: "openai", name: "OpenAI", ... }, ...]

createAdapter

typescript
function createAdapter(opts: CreateAdapterOptions): ModelAdapter

CreateAdapterOptions

PropertyTypeDescription
providerstringProvider ID. One of: openai, anthropic, openrouter, gemini, minimax, kimi, glm, mimo, ollama, lmstudio, bedrock.
model?stringModel name to use. Defaults to the provider's default model.
apiKey?stringAPI key. Defaults to the provider's environment variable.
maxTokens?numberMaximum output tokens. Defaults to the provider's default.
stream?booleanWhether to use streaming. Defaults to true.
baseURL?stringOverride the provider's default base URL (e.g., custom port for local LLMs).
timeout?numberRequest timeout in milliseconds. Useful for slow local LLMs. Defaults to 10 minutes (600_000).
reasoning?boolean | OpenAICompatReasoningOptionsReasoning / thinking support for OpenAI-compatible providers. Pass true for sensible defaults (capture provider-emitted reasoning_content / reasoning into Message.reasoning_content, echo on tool turns) or an object for fine-grained control (effort, reasoningObject, thinking, extraBody, includeInText, echo). Ignored by the Anthropic, Bedrock, and MiMo paths.
reasoningEffort?"minimal" | "low" | "medium" | "high"Hint how much the model should think. Sent as the top-level reasoning_effort request field on the OpenAI-compat path (GPT-5/o-series, GLM-4.5/4.6, MiniMax M2.5, Kimi K2, DeepSeek V4) and mapped onto MiMo's existing knob. "minimal" is GPT-5-specific. On adaptive models like mimo-v2.5-pro, "low"/"medium" can suppress thinking — use "high" for consistently deep reasoning.
includeReasoningInText?booleanWhen true, wrap reasoning in <think>…</think> and prepend to the visible message text. Defaults to false — the trace stays on Message.reasoning_content. Honoured by the OpenAI-compat and MiMo adapters.

OpenAICompatReasoningOptions

Fine-grained reasoning configuration for OpenAI-compatible providers. Captures provider-emitted reasoning traces (reasoning_content per the DeepSeek / Qwen / GLM / Kimi / MiniMax / MiMo convention, or reasoning per OpenRouter normalization) and routes thinking-related request knobs.

typescript
interface OpenAICompatReasoningOptions {
  /** Wrap reasoning in <think>...</think> and prepend to visible text. Default false. */
  includeInText?: boolean;
  /** Echo Message.reasoning_content back on tool-calling turns. Default true. */
  echo?: boolean;
  /** Top-level reasoning_effort request field. */
  effort?: "minimal" | "low" | "medium" | "high";
  /** OpenRouter-style reasoning object — sent verbatim. */
  reasoningObject?: {
    effort?: "low" | "medium" | "high";
    max_tokens?: number;
    exclude?: boolean;
    enabled?: boolean;
  };
  /** Anthropic-style thinking object — for OpenAI shims that forward it. */
  thinking?: { type: "enabled" | "disabled"; budget_tokens?: number };
  /** Escape hatch — merged into request body. For Qwen3 dashscope's enable_thinking etc. */
  extraBody?: Record<string, unknown>;
}

Common patterns:

typescript
// Sensible defaults: capture + echo on tool-calling turns.
createAdapter({ provider: "openai", reasoning: true });

// Hint thinking depth — DeepSeek V4, GLM, MiniMax, Kimi, GPT-5/o-series.
createAdapter({ provider: "openai", reasoning: { effort: "high" } });

// OpenRouter's unified reasoning object.
createAdapter({
  provider: "openrouter",
  reasoning: { reasoningObject: { effort: "high", max_tokens: 2000 } },
});

// Qwen3 dashscope's enable_thinking.
createAdapter({
  provider: "openai",
  baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
  reasoning: { extraBody: { enable_thinking: true, thinking_budget: 1024 } },
});

// Surface reasoning in the visible message text.
createAdapter({ provider: "openai", reasoning: { includeInText: true } });

// Disable echo (DeepSeek-R1 specifically — newer V4 needs echo on).
createAdapter({ provider: "openai", reasoning: { echo: false } });

getAvailableProviders

Returns an array of provider configurations that have API keys available in the current environment.

typescript
function getAvailableProviders(): Array<{ id: string; name: string; available: boolean; models: string[]; defaultModel: string }>

Supported Providers

IDEnv VariableDefault Model
openaiOPENAI_API_KEYgpt-4.1
anthropicANTHROPIC_API_KEYclaude-sonnet-4-20250514
openrouterOPENROUTER_API_KEYanthropic/claude-sonnet-4
geminiGEMINI_API_KEYgemini-2.5-flash
minimaxMINIMAX_API_KEYMiniMax-M2.5
kimiMOONSHOT_API_KEYkimi-k2.5
glmZHIPUAI_API_KEYglm-4-plus
mimoMIMO_API_KEYmimo-v2.5
ollama(none)(user-specified)
lmstudio(none)(user-specified)
bedrockAWS_ACCESS_KEY_IDanthropic.claude-3-5-sonnet-20241022-v2:0

Each provider has properties: id, name, baseURL, envVar, defaultModel, models[], format (either "openai", "anthropic", or "bedrock"), defaultMaxTokens, and requiresApiKey.

Local providers (ollama and lmstudio) don't require an API key and have no default model — you must pass a model name. Use baseURL to override the default port if needed:

typescript
const model = createAdapter({
  provider: "ollama",
  model: "llama3",
  baseURL: "http://localhost:9999/v1", // optional, defaults to :11434
});