Daydreams Logo
Concepts

Inputs

How Daydreams agents receive information and trigger processing.

What is an Input?

An input is how your agent listens to the outside world. If outputs are how your agent "speaks", inputs are how your agent "hears" things happening.

Real Examples

Here are inputs that make agents responsive:

Discord Messages

discord-input.ts
// Agent listens for Discord messages
const discordMessage = input({
  type: "discord:message",
  schema: z.object({
    content: z.string(),
    userId: z.string(),
    channelId: z.string(),
  }),
  subscribe: (send, agent) => {
    discord.on("messageCreate", (message) => {
      send(
        chatContext,
        { channelId: message.channel.id },
        {
          content: message.content,
          userId: message.author.id,
          channelId: message.channel.id,
        }
      );
    });
 
    return () => discord.removeAllListeners("messageCreate");
  },
});

CLI Commands

cli-input.ts
// Agent listens for terminal input
const cliInput = input({
  type: "cli:input",
  schema: z.string(),
  subscribe: (send, agent) => {
    const readline = require("readline").createInterface({
      input: process.stdin,
      output: process.stdout,
    });
 
    readline.on("line", (input) => {
      send(cliContext, { sessionId: "cli" }, input);
    });
 
    return () => readline.close();
  },
});

API Webhooks

webhook-input.ts
// Agent listens for API webhooks
const webhookInput = input({
  type: "api:webhook",
  schema: z.object({
    event: z.string(),
    data: z.any(),
  }),
  subscribe: (send, agent) => {
    const server = express();
 
    server.post("/webhook", (req, res) => {
      send(
        webhookContext,
        { eventId: req.body.id },
        {
          event: req.body.event,
          data: req.body.data,
        }
      );
      res.status(200).send("OK");
    });
 
    const serverInstance = server.listen(3000);
    return () => serverInstance.close();
  },
});

The Problem: Agents Need to Know When Things Happen

Without inputs, your agent can't react to anything:

deaf-agent.txt
User sends Discord message: "Hey agent, what's the weather?"
Agent: *doesn't hear anything*
Agent: *sits idle, does nothing*
User: "Hello??"
Agent: *still nothing*
// ❌ Agent can't hear Discord messages
// ❌ No way to trigger the agent
// ❌ Completely unresponsive

The Solution: Inputs Enable Listening

With inputs, your agent can hear and respond:

listening-agent.txt
User sends Discord message: "Hey agent, what's the weather?"
Discord Input: *detects new message*
Agent: *wakes up and processes the message*
Agent: *calls weather API*
Agent: *responds via Discord output*
Discord: "It's 72°F and sunny in San Francisco!"
// ✅ Agent hears the message
// ✅ Automatically triggered to respond
// ✅ Completes the conversation

How Inputs Work in Your Agent

1. You Define What the Agent Listens For

define-inputs.ts
const agent = createDreams({
  model: openai("gpt-4o"),
  inputs: [
    discordMessage, // Agent listens to Discord
    cliInput, // Agent listens to terminal
    webhookInput, // Agent listens to webhooks
  ],
});

2. Inputs Watch for Events

When you start your agent, inputs begin listening:

listening-pattern.ts
// Discord input starts watching for messages
discord.on("messageCreate", (message) => {
  // When message arrives, input sends it to agent
  send(chatContext, { channelId: message.channel.id }, messageData);
});
 
// CLI input starts watching for terminal input
readline.on("line", (input) => {
  // When user types, input sends it to agent
  send(cliContext, { sessionId: "cli" }, input);
});

3. Inputs Trigger the Agent

When an input detects something, it "sends" the data to your agent:

input-flow.txt
1. Discord message arrives: "What's the weather?"
2. Discord input detects it
3. Input calls: send(chatContext, {channelId: "123"}, {content: "What's the weather?"})
4. Agent wakes up and starts thinking
5. Agent sees the message and decides what to do
6. Agent calls weather action and responds

Creating Your First Input

Here's a simple input that listens for file changes:

file-watcher-input.ts
import { input } from "@daydreamsai/core";
import { z } from "zod";
import fs from "fs";
 
export const fileWatcher = input({
  // Type the agent uses to identify this input
  type: "file:watcher",
 
  // Schema defines what data the input provides
  schema: z.object({
    filename: z.string(),
    content: z.string(),
    event: z.enum(["created", "modified", "deleted"]),
  }),
 
  // Subscribe function starts listening
  subscribe: (send, agent) => {
    const watchDir = "./watched-files";
 
    // Watch for file changes
    const watcher = fs.watch(watchDir, (eventType, filename) => {
      if (filename) {
        const filepath = `${watchDir}/${filename}`;
 
        try {
          const content = fs.readFileSync(filepath, "utf8");
 
          // Send the file change to the agent
          send(
            fileContext,
            { filename },
            {
              filename,
              content,
              event: eventType === "rename" ? "created" : "modified",
            }
          );
        } catch (error) {
          // File might be deleted
          send(
            fileContext,
            { filename },
            {
              filename,
              content: "",
              event: "deleted",
            }
          );
        }
      }
    });
 
    // Return cleanup function
    return () => {
      watcher.close();
    };
  },
});

Use it in your agent:

agent-with-file-watcher.ts
const agent = createDreams({
  model: openai("gpt-4o"),
  inputs: [fileWatcher],
});
 
// Now when files change in ./watched-files/:
// 1. File watcher detects the change
// 2. Input sends file data to agent
// 3. Agent can process and respond to file changes

Working with Context Targeting

Inputs need to know which context should handle the incoming data:

context-targeting.ts
const chatInput = input({
  type: "chat:message",
  schema: z.object({
    message: z.string(),
    userId: z.string(),
  }),
  subscribe: (send, agent) => {
    chatService.on("message", (data) => {
      // Target the specific chat context for this user
      send(
        chatContext,
        { userId: data.userId },
        {
          message: data.message,
          userId: data.userId,
        }
      );
    });
 
    return () => chatService.removeAllListeners("message");
  },
});

This creates separate context instances for each user:

  • User "alice" gets context instance chat:alice
  • User "bob" gets context instance chat:bob
  • Each maintains separate conversation memory

Real-Time vs Polling Inputs

Real-Time (Event-Driven)

realtime-input.ts
// ✅ Good - responds immediately
subscribe: (send, agent) => {
  websocket.on("message", (data) => {
    send(context, args, data);
  });
 
  return () => websocket.close();
};

Polling (Check Periodically)

polling-input.ts
// Sometimes necessary for APIs without webhooks
subscribe: (send, agent) => {
  const checkForUpdates = async () => {
    const newData = await api.getUpdates();
    if (newData.length > 0) {
      newData.forEach((item) => {
        send(context, { id: item.id }, item);
      });
    }
  };
 
  const interval = setInterval(checkForUpdates, 5000); // Every 5 seconds
 
  return () => clearInterval(interval);
};

Multiple Inputs Working Together

Your agent can listen to multiple sources simultaneously:

multiple-inputs.ts
const agent = createDreams({
  model: openai("gpt-4o"),
  inputs: [
    discordMessage, // Discord messages
    slackMessage, // Slack messages
    emailReceived, // New emails
    webhookReceived, // API webhooks
    fileChanged, // File system changes
    timerTick, // Scheduled events
  ],
});
 
// Agent now responds to any of these inputs automatically

Error Handling and Validation

Always handle errors gracefully in your inputs:

error-handling-input.ts
const robustInput = input({
  type: "api:events",
  schema: z.object({
    eventId: z.string(),
    data: z.any(),
  }),
  subscribe: (send, agent) => {
    api.on("event", (rawData) => {
      try {
        // Validate the data first
        const validData = {
          eventId: rawData.id,
          data: rawData.payload,
        };
 
        // Schema validation happens automatically
        send(eventContext, { eventId: rawData.id }, validData);
      } catch (error) {
        agent.logger.error("api:events", "Invalid event data", {
          rawData,
          error: error.message,
        });
        // Don't crash - just log and continue
      }
    });
 
    return () => api.removeAllListeners("event");
  },
});

Best Practices

1. Use Clear Types and Schemas

good-input-definition.ts
// ✅ Good - clear purpose and validation
const userMessage = input({
  type: "user:message",
  schema: z.object({
    content: z.string().min(1).max(2000),
    userId: z.string().uuid(),
    timestamp: z.number(),
  }),
  // ...
});
 
// ❌ Bad - unclear and unvalidated
const dataInput = input({
  type: "data",
  schema: z.any(),
  // ...
});

2. Always Return Cleanup Functions

cleanup-function.ts
// ✅ Good - proper cleanup
subscribe: (send, agent) => {
  const listener = (data) => send(context, args, data);
 
  eventSource.addEventListener("event", listener);
 
  return () => {
    eventSource.removeEventListener("event", listener);
    eventSource.close();
  };
};
 
// ❌ Bad - no cleanup (memory leaks!)
subscribe: (send, agent) => {
  eventSource.addEventListener("event", (data) => {
    send(context, args, data);
  });
 
  return () => {}; // Nothing cleaned up!
};

3. Handle Connection Failures

reconnection-input.ts
subscribe: (send, agent) => {
  let reconnectAttempts = 0;
  const maxReconnects = 5;
 
  const connect = () => {
    try {
      const connection = createConnection();
 
      connection.on("data", (data) => {
        reconnectAttempts = 0; // Reset on successful data
        send(context, args, data);
      });
 
      connection.on("error", () => {
        if (reconnectAttempts < maxReconnects) {
          reconnectAttempts++;
          setTimeout(connect, 1000 * reconnectAttempts);
        }
      });
 
      return connection;
    } catch (error) {
      agent.logger.error("connection failed", error);
    }
  };
 
  const connection = connect();
 
  return () => connection?.close();
};

4. Target the Right Context

context-routing.ts
subscribe: (send, agent) => {
  service.on("event", (event) => {
    // Route to appropriate context based on event type
    if (event.type === "user_message") {
      send(chatContext, { userId: event.userId }, event.data);
    } else if (event.type === "system_alert") {
      send(alertContext, { alertId: event.id }, event.data);
    } else if (event.type === "game_move") {
      send(gameContext, { gameId: event.gameId }, event.data);
    }
  });
 
  return () => service.removeAllListeners("event");
};

Key Takeaways

  • Inputs enable responsiveness - Without them, agents can't hear anything
  • Subscribe pattern - Watch external sources, call send() when data arrives
  • Context targeting - Route inputs to appropriate context instances
  • Always cleanup - Return functions to disconnect when agent stops
  • Validate data - Use schemas to ensure incoming data is correct
  • Handle errors gracefully - Don't let bad input data crash your agent

Inputs are what turn your agent from a one-time script into a responsive, always-listening assistant that can react to the world in real-time.