Daydreams Logo
Advanced

Extensions vs Services

Understanding the difference between extensions and services in Daydreams.

What Are Extensions and Services?

Think of building an agent like assembling a computer:

  • Services are like individual components (hard drive, graphics card, RAM)
  • Extensions are like complete packages (gaming bundle, productivity suite)

Real Examples

Services: Individual Components

database-service.ts
// A service manages ONE specific thing
const databaseService = service({
  name: "database",
 
  // How to create the database connection
  register: (container) => {
    container.singleton("db", () => new Database(process.env.DB_URL));
  },
 
  // How to initialize it when agent starts
  boot: async (container) => {
    const db = container.resolve("db");
    await db.connect();
    console.log("Database connected!");
  },
});

Extensions: Complete Packages

discord-extension.ts
// An extension bundles EVERYTHING for a feature
const discordExtension = extension({
  name: "discord",
 
  // Services this extension needs
  services: [discordService], // Manages Discord client
 
  // All the Discord-related features
  contexts: { discord: discordContext },
  actions: [sendMessageAction, createChannelAction],
  inputs: { "discord:message": messageInput },
  outputs: { "discord:reply": replyOutput },
});

The Problem: Managing Complexity

Without this separation, you'd have to set up everything manually:

manual-setup.ts
// ❌ Without extensions/services - manual setup nightmare
const agent = createDreams({
  model: openai("gpt-4o"),
 
  // You'd have to manually configure EVERYTHING
  contexts: {
    discord: discordContext,
    twitter: twitterContext,
    database: databaseContext,
    // ... 50+ more contexts
  },
 
  actions: [
    sendDiscordMessage,
    createDiscordChannel,
    sendTweet,
    followUser,
    saveToDatabase,
    queryDatabase,
    // ... 100+ more actions
  ],
 
  // Plus manually manage all the connections, API clients, etc.
  // This becomes unmanageable quickly!
});

The Solution: Organized Architecture

With extensions and services, it's clean and simple:

organized-setup.ts
// ✅ With extensions/services - clean and simple
const agent = createDreams({
  model: openai("gpt-4o"),
 
  // Just add the features you want
  extensions: [
    discord, // Adds Discord support + client management
    twitter, // Adds Twitter support + API management
    mongoMemory, // Adds database memory + connection management
  ],
 
  // That's it! Each extension handles its own complexity
});

How They Work Together

Services Handle the "How"

Services manage the technical details of connecting to external systems:

discord-service.ts
const discordService = service({
  name: "discord",
 
  // HOW to create the Discord client
  register: (container) => {
    container.singleton(
      "discordClient",
      () =>
        new Client({
          intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
          token: process.env.DISCORD_TOKEN,
        })
    );
  },
 
  // HOW to initialize it
  boot: async (container) => {
    const client = container.resolve("discordClient");
    await client.login();
    console.log("Discord client ready!");
  },
});

Extensions Handle the "What"

Extensions bundle all the features users actually want:

discord-extension-complete.ts
const discord = extension({
  name: "discord",
 
  // Use the service for client management
  services: [discordService],
 
  // WHAT the agent can do with Discord
  contexts: {
    discord: context({
      type: "discord",
      schema: z.object({ guildId: z.string(), channelId: z.string() }),
 
      create: () => ({
        messageHistory: [],
        memberCount: 0,
      }),
 
      render: (state) => `
Discord Server: ${state.args.guildId}
Channel: ${state.args.channelId}
Members: ${state.memory.memberCount}
Recent messages: ${state.memory.messageHistory.slice(-3).join("\n")}
      `,
    }),
  },
 
  actions: [
    action({
      name: "send-discord-message",
      description: "Send a message to a Discord channel",
      schema: z.object({
        channelId: z.string(),
        content: z.string(),
      }),
 
      handler: async ({ channelId, content }, ctx) => {
        const client = ctx.container.resolve("discordClient");
        const channel = await client.channels.fetch(channelId);
        await channel.send(content);
        return { sent: true, messageId: result.id };
      },
    }),
  ],
 
  inputs: {
    "discord:message": input({
      subscribe: (send, agent) => {
        const client = agent.container.resolve("discordClient");
 
        client.on("messageCreate", (message) => {
          if (message.author.bot) return;
 
          send({
            type: "discord:message",
            data: {
              content: message.content,
              author: message.author.username,
              channelId: message.channel.id,
              guildId: message.guild?.id,
            },
          });
        });
      },
    }),
  },
 
  outputs: {
    "discord:reply": output({
      schema: z.object({
        content: z.string(),
        channelId: z.string(),
      }),
 
      handler: async ({ content, channelId }, ctx) => {
        const client = ctx.container.resolve("discordClient");
        const channel = await client.channels.fetch(channelId);
        await channel.send(content);
      },
    }),
  },
});

When to Use Each

Create a Service When:

  • Managing an external connection (database, API client)
  • Sharing utilities across multiple features
  • Handling lifecycle management (startup, shutdown)
when-to-use-service.ts
// ✅ Good service examples
const redisService = service({
  /* manage Redis connection */
});
const loggerService = service({
  /* configure logging */
});
const webhookService = service({
  /* handle webhook server */
});

Create an Extension When:

  • Bundling a complete feature set
  • Adding support for a new platform (Discord, Twitter, etc.)
  • Packaging related actions/contexts/inputs/outputs
when-to-use-extension.ts
// ✅ Good extension examples
const twitter = extension({
  /* everything for Twitter integration */
});
const tradingBot = extension({
  /* everything for trading features */
});
const gameEngine = extension({
  /* everything for game mechanics */
});

Practical Example: Building a Trading Extension

Let's see how they work together in practice:

1. First, Create Services for External APIs

trading-services.ts
const alpacaService = service({
  name: "alpaca",
  register: (container) => {
    container.singleton(
      "alpacaClient",
      () =>
        new AlpacaApi({
          key: process.env.ALPACA_KEY,
          secret: process.env.ALPACA_SECRET,
          paper: process.env.NODE_ENV !== "production",
        })
    );
  },
  boot: async (container) => {
    const client = container.resolve("alpacaClient");
    await client.authenticate();
  },
});
 
const marketDataService = service({
  name: "marketData",
  register: (container) => {
    container.singleton(
      "marketClient",
      () => new MarketDataClient(process.env.MARKET_DATA_KEY)
    );
  },
});

2. Then, Create Extension Using Those Services

trading-extension.ts
const trading = extension({
  name: "trading",
 
  // Use the services we created
  services: [alpacaService, marketDataService],
 
  // Bundle all trading features
  contexts: {
    portfolio: portfolioContext,
    watchlist: watchlistContext,
  },
 
  actions: [buyStockAction, sellStockAction, getQuoteAction, setStopLossAction],
 
  inputs: {
    "market:price-alert": priceAlertInput,
    "market:news": newsInput,
  },
 
  outputs: {
    "trading:order-confirmation": orderOutput,
    "trading:alert": alertOutput,
  },
});

3. Use the Extension in Your Agent

trading-agent.ts
const agent = createDreams({
  model: openai("gpt-4o"),
 
  // Just add the extension - everything works automatically!
  extensions: [trading],
 
  // Now your agent can trade stocks with full context awareness
});
 
// Agent can now:
// - Listen for price alerts (inputs)
// - Check portfolio status (contexts)
// - Execute trades (actions)
// - Send confirmations (outputs)
// - All using properly managed API connections (services)

Architecture Summary

architecture-flow.txt
Extension (trading)
├── Services (how to connect)
│   ├── alpacaService → manages trading API client
│   └── marketDataService → manages market data client

└── Features (what agent can do)
    ├── Contexts → portfolio, watchlist state
    ├── Actions → buy, sell, get quotes
    ├── Inputs → listen for price alerts
    └── Outputs → send trade confirmations

When you add the extension to your agent:
1. Services get registered and initialized automatically
2. All features become available to the LLM
3. API clients are properly managed and shared
4. Everything works together seamlessly

Key Differences

AspectServiceExtension
PurposeManages infrastructureProvides features
ContainsConnection logic, utilitiesActions, contexts, inputs, outputs
Lifecycleregister()boot()install() when added
ReusabilityUsed by multiple extensionsUsed by agents
AnalogyComputer componentSoftware package

Best Practices

Service Design

good-service.ts
// ✅ Good - focused on one responsibility
const databaseService = service({
  name: "database",
  register: (container) => {
    // Just database connection management
    container.singleton("db", () => new Database(process.env.DB_URL));
  },
});
 
// ❌ Bad - doing too many things
const everythingService = service({
  name: "everything",
  register: (container) => {
    // Don't mix database, API clients, loggers, etc.
    container.singleton("db", () => new Database(/* ... */));
    container.singleton("api", () => new ApiClient(/* ... */));
    container.singleton("logger", () => new Logger(/* ... */));
  },
});

Extension Design

good-extension.ts
// ✅ Good - cohesive feature set
const discord = extension({
  name: "discord",
  services: [discordService], // Only Discord-related services
  // All features work together for Discord integration
});
 
// ❌ Bad - unrelated features mixed together
const mixedExtension = extension({
  name: "mixed",
  services: [discordService, twitterService, databaseService],
  // Discord actions mixed with Twitter contexts - confusing!
});

Next Steps

Key Takeaways

  • Services manage "how" - Connection setup, lifecycle, dependencies
  • Extensions manage "what" - Features users actually want
  • Clean separation - Infrastructure vs functionality
  • Easy composition - Add extensions like LEGO blocks
  • Automatic management - Framework handles the wiring

This architecture lets you build complex agents by combining simple, focused pieces.