Daydreams Logo
Concepts

Inputs

How Daydreams agents receive information and trigger processing.

Inputs are the mechanism by which Daydreams agents receive information from the outside world. They act as the triggers that initiate or contribute to an agent's processing cycle (agent.run). An input could represent a user message, a blockchain event, an API webhook, a sensor reading, or any other data source relevant to the agent's task.

Defining an Input

Input sources are defined using the input helper function exported from @daydreamsai/core. Each input definition connects an external data source to the agent's core processing loop.

import { input, context, type AnyAgent } from "@daydreamsai/core";
import { z } from "zod";
import { EventEmitter } from "events"; // Example: Using a simple Node.js EventEmitter
 
// Assume myContext is defined elsewhere
declare const myContext: any;
 
// Example: An input source listening to a simple EventEmitter
const myEventEmitter = new EventEmitter();
 
const eventInput = input({
  // Required: A unique identifier for this input source type.
  // Helps in logging and debugging. e.g., "myApi:webhook", "discord:message"
  type: "custom:event",
 
  // Optional (but recommended): Zod schema for the *data payload* that will be
  // passed into the agent via the `send` function below. This validates
  // the data structure *before* it triggers the agent's run cycle.
  schema: z.object({
    eventId: z.string().describe("Unique ID for the incoming event"),
    payload: z.any().describe("The actual data content of the event"),
    source: z.string().optional().describe("Origin of the event"),
  }),
 
  // Required (usually): Connects to the external data source and calls `send`
  // when new data arrives.
  subscribe: (send, agent: AnyAgent) => {
    // `send` is the crucial function provided by the framework. You call this
    // to push validated data into the agent for processing.
    // `agent` is the agent instance, useful for accessing shared services or config.
 
    const listener = (eventData: {
      id: string;
      data: any;
      origin?: string;
    }) => {
      console.log(`External event received: ${eventData.id}`);
 
      // 1. Determine the target Context and its arguments.
      // This tells the agent *which* context instance should handle this input.
      // This might be static or dynamically determined based on eventData.
      const targetContext = myContext; // Replace with your actual context definition
      const contextArgs = { someId: eventData.id }; // Args matching targetContext.schema
 
      // 2. Prepare the data payload according to this input's `schema`.
      const inputData = {
        eventId: eventData.id,
        payload: eventData.data,
        source: eventData.origin,
      };
 
      try {
        // 3. Validate the payload before sending (optional but good practice)
        eventInput.schema?.parse(inputData); // Use the defined schema
 
        // 4. Call `send` to push the input data into the agent.
        // This triggers the agent's processing cycle for the target context instance.
        send(targetContext, contextArgs, inputData);
      } catch (validationError) {
        agent.logger.error(
          "eventInput:subscribe",
          "Invalid event data received",
          { eventData, validationError }
        );
        // Decide how to handle invalid data (e.g., log, ignore)
      }
    };
 
    // Attach the listener to the external source
    myEventEmitter.on("newEvent", listener);
    agent.logger.info("eventInput", "Subscribed to newEvent");
 
    // IMPORTANT: Return a cleanup function.
    // This is called when the agent stops, ensuring you detach listeners,
    // close connections, clear intervals, etc.
    return () => {
      myEventEmitter.off("newEvent", listener);
      agent.logger.info("eventInput", "Unsubscribed from newEvent");
    };
  },
 
  // Optional: Pre-process data *after* `send` is called but *before* the log entry
  // (InputRef) is created. Useful for adding metadata based on context state.
  handler: async (data, ctx, agent) => {
    // `data` is the payload passed to `send`.
    // `ctx` is the ContextState of the target context instance.
    // Example: Check if this event was already seen in this context instance
    const seen = ctx.memory.processedEventIds?.includes(data.eventId) ?? false;
    return {
      data: data, // Can optionally transform the data here
      params: { seen: String(seen) }, // Add parameters to the InputRef log entry
    };
  },
 
  // Optional: Customize how this input is represented in logs or prompts.
  format: (ref) => {
    // `ref` is the InputRef object (log entry)
    return `Event Received: ID=${ref.data.eventId}, Source=${ref.data.source ?? "N/A"}`;
  },
 
  // Optional: One-time setup logic when the agent starts.
  install: async (agent) => {
    // E.g., agent.container.resolve('myApiClient').connect();
    agent.logger.info("eventInput", "Install hook executed");
  },
});

Key Concepts:

  • type: A unique string identifying your input source.
  • schema: (Recommended) A Zod schema defining the structure of the data payload you intend to pass via send. Validation happens automatically if provided.
  • subscribe(send, agent): The core function where you connect to your external data source (API, websocket, event emitter, polling mechanism, etc.).
    • When data arrives, you call the provided send function.
    • You must return a cleanup function to disconnect/unsubscribe when the agent stops.
  • send(context, args, data): This framework-provided function is your gateway into the agent.
    • context: The context definition object that should handle this input.
    • args: An object matching the context.schema, identifying the specific instance of the context.
    • data: The payload containing the actual input information, ideally matching this input's schema.
  • handler(data, ctx, agent): (Optional) Pre-processing function executed after send but before the input log entry is finalized. Allows data transformation or adding metadata (params) based on the target context's state (ctx).
  • format(ref): (Optional) Customize the string/XML representation of the input log entry (InputRef).

How Inputs Trigger the Agent

  1. External Event: Your subscribe function detects an event or receives data.
  2. send Called: Your code calls send(context, args, data).
  3. Agent Invoked: The framework receives the call, creates a basic log entry (InputRef) for this input, and starts or queues an agent.run cycle for the specified context instance identified by args.
  4. Pre-processing (handler): If you defined an input.handler, it runs, potentially modifying the data or adding parameters to the InputRef.
  5. Run Cycle: The agent proceeds with its run cycle (Agent Lifecycle), processing this new InputRef along with other state information to generate a response or perform actions.

Inputs within Extensions

Inputs are commonly bundled within Extensions to package integrations cleanly. The structure is the same, but the input definitions live inside the inputs property of the extension definition.

import { extension, input /* ... */ } from "@daydreamsai/core";
import { z } from "zod";
import { myService } from "./myService";
import { myContext } from "./myContext";
 
export const myApiExtension = extension({
  name: "myApi",
  services: [myService], // Optional services used by the input
  contexts: { myContext }, // Optional contexts targeted by the input
 
  inputs: {
    // Input definitions go here, keyed by their type
    "myApi:webhook": input({
      type: "myApi:webhook", // Redundant but ensures key matches type
      schema: z.object({
        /* ... webhook payload schema ... */
      }),
      subscribe(send, agent) {
        const apiClient = agent.container.resolve<MyApiClient>("myApiClient"); // Use a service
 
        const webhookHandler = (payload: any) => {
          const validatedData =
            myApiExtension.inputs["myApi:webhook"].schema.parse(payload);
          // Determine context/args based on payload
          const contextArgs = { entityId: validatedData.entityId };
          send(myContext, contextArgs, validatedData);
        };
 
        apiClient.registerWebhookListener(webhookHandler);
        return () => apiClient.removeWebhookListener(webhookHandler);
      },
      // ... other input properties ...
    }),
    // ... potentially other inputs for this extension ...
  },
});

Examples

(Keep the existing CLI, Telegram, and Twitter examples here, perhaps slightly simplifying the code snippets to focus on the subscribe and send pattern.)

CLI Input (from @daydreamsai/cli)

// Simplified example
input({
  type: "cli:input",
  subscribe(send, { container }) {
    const rl = container.resolve<readline.Interface>("readline");
    const listen = async () => {
      while (true) {
        // Basic loop, real implementation handles abort
        const line = await rl.question("> ");
        if (line === "exit") break;
        // Send to a default CLI context instance
        send(cliContext, { user: "cli_user" }, line);
      }
    };
    listen(); // Start listening
    return () => {
      /* Abort logic here */
    };
  },
});

Telegram Message Input (from @daydreamsai/telegram)

// Simplified example
input({
  type: "telegram:message",
  schema: z.object({
    /* ... */
  }),
  subscribe(send, { container }) {
    const tg = container.resolve<Telegraf>("telegraf");
    tg.on("message", (ctx) => {
      if ("text" in ctx.message) {
        const dataPayload = {
          /* ... extract user, text ... */
        };
        // Send data to the specific telegram chat context
        send(telegramChat, { chatId: ctx.chat.id }, dataPayload);
      }
    });
    // Telegraf handles cleanup
    return () => {};
  },
});

Inputs are the crucial link between your agent and the information it needs to react to, enabling dynamic and event-driven behavior.

On this page