Daydreams Logo
Concepts

Contexts

Managing state, memory, and behavior for agent interactions.

In Daydreams, a Context defines a specific scope or environment for your agent's interactions or tasks. Think of it as a dedicated workspace that holds the state, memory, available tools (actions, inputs, outputs), and specific instructions relevant to that particular job.

You might use different contexts for:

  • Handling individual user chat sessions (chatContext).
  • Managing a specific game state (gameContext).
  • Executing a complex workflow or process (workflowContext).
  • Interacting with a specific external system (externalApiContext).

Each running instance of a context (e.g., the chat session with user A) maintains its own unique state and memory, separate from other instances (like the chat session with user B).

Defining a Context

You define context types using the context function from @daydreamsai/core.

import {
  context,
  action,
  type AnyAgent,
  type ContextState,
} from "@daydreamsai/core";
import { z } from "zod";
 
// 1. Define the structure of the persistent memory for this context type
interface ChatMemory {
  messageHistory: { sender: "user" | "agent"; text: string }[];
  userPreferences: Record<string, any>;
  lastInteractionTime?: number;
}
 
// 2. Define the Zod schema for arguments needed to identify a specific instance
const chatSchema = z.object({
  sessionId: z.string().describe("Unique identifier for the chat session"),
  userId: z.string().describe("Identifier for the user in the session"),
});
 
// 3. Define the context using the `context` function
const chatContext = context<
  ChatMemory, // Type for the persistent memory
  typeof chatSchema // Type for the identifying arguments
>({
  // Required: A unique identifier string for this *type* of context
  type: "chat",
 
  // Required: The Zod schema defining the arguments needed to identify
  // or create a specific *instance* of this context.
  schema: chatSchema,
 
  // Optional: Function to generate a unique string key for an instance from its arguments.
  // Use this if the 'type' alone isn't unique (e.g., multiple chat sessions).
  // The full instance ID becomes "<type>:<key>" (e.g., "chat:session-xyz").
  key: ({ sessionId }) => sessionId,
 
  // Optional: Defines the initial structure and default values for the
  // context instance's persistent memory (`ctx.memory`).
  // This runs only if no saved memory exists for this instance.
  create: (state, agent) => {
    agent.logger.info(
      "chatContext",
      `Creating new memory for session: ${state.key}`
    );
    return {
      messageHistory: [],
      userPreferences: {}, // Can use `state.options` if `setup` is used
    };
  },
 
  // Optional: Provides static or dynamic instructions to the LLM *when this context is active*.
  instructions: (state) =>
    `You are chatting with user ${state.args.userId}. Be helpful.`,
 
  // Optional: A description of this context type's purpose.
  description: "A chat session with a specific user.",
 
  // Optional: Function to format the context's *current memory state* for the LLM prompt.
  // Helps the LLM understand the current situation within this context instance.
  render: (state) => {
    // state.memory is typed as ChatMemory here
    const recentHistory = state.memory.messageHistory
      .slice(-5) // Show last 5 messages
      .map((msg) => `${msg.sender}: ${msg.text}`)
      .join("\n");
 
    return `
## Recent Chat History (Session: ${state.key}):
${recentHistory || "No messages yet."}
 
## User Preferences:
${JSON.stringify(state.memory.userPreferences)}
    `;
  },
 
  // --- Optional Lifecycle Hooks & Config ---
  // onStep: async (ctx, agent) => { /* Logic per step */ },
  // onRun: async (ctx, agent) => { /* Logic on run completion */ },
  // shouldContinue: (ctx) => ctx.memory.messageHistory.length < 50, // Example condition
  // onError: async (error, ctx, agent) => { /* Handle errors */ },
  // model: mySpecificLLM, // Override agent's default model
  // maxSteps: 15, // Limit run steps for this context type
});
 
// You can then associate actions, inputs, or outputs specifically for this context
// (See "Associating Components" section below)
// chatContext.setActions([...]);
// chatContext.setInputs({...});
// chatContext.setOutputs({...});

Key Definition Parameters:

  • type: Unique string identifying the context type.

  • schema: Zod schema for arguments needed to identify a context instance. Use .describe() on fields for clarity.

  • key: (Optional) Function (args) => string to create a unique instance key from arguments.

  • create: (Optional) Function (state, agent) => TMemory defining the initial structure of the instance's persistent memory (ctx.memory).

  • render: (Optional) Function (state) => string | XMLElement | ... formatting the current state.memory for the LLM prompt. Keep it concise and relevant.

  • instructions / description: (Optional) Provide guidance to the LLM about the context.

  • Lifecycle Hooks (onStep, onRun, etc.): (Optional) Add custom logic at different points in the execution cycle.

  • Configuration (model, maxSteps, etc.): (Optional) Override agent defaults for this context type.

Context Memory (ctx.memory)

The most important concept for managing state within a context is its persistent memory.

  • Definition: The structure and initial values are defined by the create function in your context definition.
  • Access: Within context lifecycle hooks (like onStep) and within the handler of any action, input, or output associated with this context, you can access the current instance's memory via ctx.memory.
  • Persistence: Changes made to ctx.memory during an agent run are automatically saved by the framework to the configured MemoryStore at the end of the run cycle.
  • Loading: When an agent run starts for a specific context instance, the framework loads its saved memory from the MemoryStore into ctx.memory.
// Inside an action handler associated with chatContext:
handler(args, ctx, agent) {
  // ctx.memory is typed as ChatMemory
  const history = ctx.memory.messageHistory;
  const lastMsg = history[history.length - 1];
 
  // Modify the memory
  ctx.memory.lastInteractionTime = Date.now();
  ctx.memory.messageHistory.push({ sender: "agent", text: "Acknowledged." });
 
  // This change will be saved automatically.
  return { success: true };
}

Associating Components (Actions/Inputs/Outputs)

While you can define components globally, it's often better to associate them directly with the context they belong to using the chained .setActions(), .setInputs(), and .setOutputs() methods. This provides better organization and ensures components have typed access to the correct context memory.

import { chatContext, ChatMemory } from "./chatContext"; // Assuming context is defined elsewhere
import { sendMessageAction } from "./actions/sendMessage"; // Assuming action is defined
 
// Associate the action with the chat context
const chatContextWithActions = chatContext.setActions([
  sendMessageAction,
  // other actions relevant only to chat...
]);
 
// Now, within sendMessageAction's handler, ctx.memory will be typed as ChatMemory
// and the action will only be available when chatContext is active.
 
// Similarly for inputs and outputs:
// chatContext.setInputs({ ... });
// chatContext.setOutputs({ ... });

You can also define them inline using the actions, inputs, or outputs properties directly within the context({...}) definition, though the chained methods often offer superior type inference.

Working with Context Instances

You typically interact with specific context instances through the agent object:

  • agent.getContext({ context: chatContext, args: { sessionId: "xyz", userId: "user1" } }): Retrieves the ContextState for the specified instance. If it doesn't exist, it creates it (running create if needed) and loads its memory.

  • agent.run({ context: chatContext, args: { sessionId: "xyz", userId: "user1" }, ... }): Starts (or continues) the agent's processing loop specifically for this context instance.

  • agent.getContextById("chat:session-xyz"): (Less common) Retrieves a context instance directly by its full ID if you know it.

  • agent.saveContext(contextState): Manually saves the state. Usually handled automatically by the agent lifecycle.

Context Memory vs. Working Memory

It's important not to confuse the persistent Context Memory (ctx.memory) with the temporary Working Memory.

  • Context Memory (ctx.memory): Specific to a context instance. Holds the persistent state (like chat history). Defined by create. Saved/loaded by the MemoryStore. Used by render.
  • Working Memory: Exists only for the duration of a single agent.run. Holds the temporary log of inputs, outputs, thoughts, and action calls/results for that specific run. Used to build the prompt at each step.

Best Practices

  • Scope Appropriately: Design contexts around distinct tasks or interaction boundaries.
  • Define Memory Clearly: Use interfaces (like ChatMemory) for your context memory structure. Initialize it properly in create.
  • Keep render Concise: Only include state in render that is essential for the LLM's immediate reasoning. Avoid overly large state dumps.
  • Use Zod Schemas: Clearly define schema for context arguments with .describe().
  • Associate Components: Link actions, inputs, and outputs to their relevant contexts for better organization and type safety.

Contexts provide the structure for organizing your agent's knowledge, state, and capabilities, enabling complex and stateful interactions.

On this page