Store Primitives
A Store in Fragola is a reactive state container used to persist information outside of the LLM context. You use stores to hold things like application state, user sessions, connection pools, or retrieved database rows.
Creating Stores
You create a store using the createStore helper. A store requires an initial value, and optionally, a string namespace.
import { createStore } from "@fragola-ai/agent";
const sessionStore = createStore(
{ userId: "123", role: "admin" }, // initial value
"session" // optional namespace (required if passing to addStore later)
);Reading and Writing
Stores expose a simple API to read, replace, or partially update data.
store.value
Access the current value synchronously.
console.log(sessionStore.value.userId); // "123"store.set(data)
Completely replaces the store's value.
sessionStore.set({ userId: "456", role: "user" });store.update(callback)
Updates the value based on the previous state.
sessionStore.update(prev => ({
...prev,
role: "superadmin"
}));Reactive Updates
Stores are reactive. You can attach listeners that fire whenever the value is modified using set or update.
sessionStore.onChange((newValue) => {
console.log("Session updated!", newValue);
});Local Stores
A Local Store is a state container that belongs exclusively to a single Agent. It is isolated from the rest of your application and from other agents.
Attaching a Local Store
You can provide an initial, default local store to an agent during creation via the store option.
import { Fragola, createStore } from "@fragola-ai/agent";
const fragola = new Fragola({ model: "__DEFAULT_MODEL__" });
const myLocalStore = createStore({ queryCount: 0 });
const agent = fragola.agent({
name: "QueryAgent",
store: myLocalStore // Attach the store locally to this agent
});Accessing the Local Store
Once attached, the local store can be accessed from the AgentContext via context.store. This makes it instantly available inside any tool handler or event listener without needing to lookup a namespace.
agent.onAfterModelInvocation(({ context }) => {
// Increment the query counter after every LLM call
context.store.update(prev => ({
queryCount: prev.queryCount + 1
}));
});Global Stores
A Global Store is a state container that is attached to the parent Fragola instance. Any agent created by that instance automatically shares the same global store.
This is highly effective for sharing resources like database connection pools, global application configurations, or shared metrics counters across multiple agents.
Attaching a Global Store
You pass a store to the Fragola constructor as the second argument.
import { Fragola, createStore } from "@fragola-ai/agent";
const appConfigStore = createStore({ apiLimit: 100, maintenanceMode: false });
// Pass the global store during initialization
const fragola = new Fragola(
{ model: "__DEFAULT_MODEL__" },
appConfigStore
);
// Both agents share `appConfigStore`
const agentA = fragola.agent({ name: "AgentA" });
const agentB = fragola.agent({ name: "AgentB" });Accessing the Global Store
Inside tools or events, you can access the global store through context.instance.store.
agentA.onBeforeModelInvocation(({ context }) => {
const config = context.instance.store?.value;
if (config?.maintenanceMode) {
context.stopSync();
}
});Scoped Stores
Agents and Fragola instances aren't limited to a single store. Using Scoped Stores, you can attach and retrieve multiple disjoint stores based on a namespace string.
Scoped stores are especially useful when developing generic Hooks that need to persist their own private data alongside the user's data.
Registering Scoped Stores
Stores must be created with a namespace to be added dynamically to an agent or instance.
const telemetryStore = createStore({ active: true }, "telemetry");
// Add it to the agent's context
agent.context.addStore(telemetryStore);Querying Scoped Stores
You retrieve the store by passing the namespace string to getStore().
import { tool } from "@fragola-ai/agent";
import { z } from "zod";
const pingTool = tool({
name: "ping",
description: "Check status.",
schema: z.object({}),
handler: (params, context) => {
// Retrieve the scoped store safely
const telemetry = context.getStore("telemetry");
if (telemetry?.value.active) {
return "Pong!";
}
return "Offline.";
}
});Removing Stores
You can dynamically detach stores when they are no longer needed, which is a common cleanup pattern when removing a Hook.
agent.context.removeStore("telemetry");Message Metadata & Typing
Fragola extends the default OpenAI message types to allow attaching custom meta properties to messages. This is incredibly useful for UI rendering (e.g., hiding hidden steps, displaying tool call latencies) or for internal bookkeeping.
The DefineMetaData Generic
To ensure type safety, Fragola requires you to define the exact shape of your metadata using the DefineMetaData utility type. It strictly categorizes metadata based on the message role: user, ai, or tool.
import type { DefineMetaData } from "@fragola-ai/agent";
// Define custom shapes for each role
type MyMetaData = DefineMetaData<{
user: { source: "web" | "sms" };
ai: { confidenceScore: number; tokensUsed: number };
tool: { latencyMs: number };
}>;
// Apply it when creating an agent
const agent = fragola.agent<MyMetaData>({
name: "TypedAgent"
});Now, whenever you interact with context.state.messages or fire an event, TypeScript will enforce these metadata structures!
agent.onAiMessage(({ message }) => {
// Strongly typed based on `MyMetaData["ai"]`!
message.meta = {
confidenceScore: 0.95,
tokensUsed: 120
};
return message;
});Stripping Metadata
OpenAI's API does not understand the meta field. If you send a message with meta attached, the OpenAI endpoint will return a 400 Bad Request.
Fragola handles this automatically under the hood: when an execution turn initiates, it uses the stripMessagesMeta helper to cleanly remove all custom metadata before transmitting the payload to the LLM. You never have to manually strip metadata from the conversation history.
import { stripMeta } from "@fragola-ai/agent";
const rawMsg = { role: "user", content: "Hi", meta: { source: "web" } };
const cleaned = stripMeta(rawMsg); // { role: "user", content: "Hi" }