Actions
Define capabilities and interactions for your Daydreams agent.
Actions are the primary way Daydreams agents perform tasks, interact with external systems (like APIs or databases), and modify their own state. They are essentially functions that the agent's underlying Large Language Model (LLM) can choose to call based on its reasoning and the current situation.
Think of actions as the tools or capabilities you give your agent.
Defining an Action
You define actions using the action
helper function from @daydreamsai/core
.
Here's the essential structure:
Working with Action Arguments (args
)
The handler
function receives the action's arguments in the first parameter
(args
). These arguments have already been:
- Generated by the LLM based on your action's
schema
. - Parsed and validated by the framework against your
schema
.
You can directly use the properties defined in your schema within the handler, with type safety if using TypeScript.
Managing State (ctx.memory
)
Actions often need to read or modify the persistent state associated with the
current context instance (e.g., the specific chat session or project the
agent is working on). You access this state via ctx.memory
.
Important Memory Scopes in ctx
:
ctx.memory
: (Most commonly used) Persistent memory for the current context instance. Use this for state related to the specific chat, project, game, etc.ctx.actionMemory
: (Less common) Persistent memory tied to the action definition itself, across all contexts. Configure via thememory
option inaction()
. Useful for action-specific counters, rate limits, etc.ctx.agentMemory
: Persistent memory for the main agent context (if one was defined globally). Use for global agent settings or state.
Always choose the memory scope that matches where your state needs to live.
Associating Actions with Contexts
While you can define actions globally when calling createDreams
, it's often
more organized and efficient to associate actions directly with the Context
they primarily relate to. This makes the action available only when that
specific context is active and ensures the action handler has direct, typed
access to that context's memory.
There are two ways to associate actions with a context definition:
1. Using the actions
property:
2. Using the .setActions()
method (often better for typing):
Benefits of Context-Specific Actions:
- Scoped Availability: The
incrementValue
action will only appear in the LLM's available actions when an instance ofmyContext
is active in the agent's run. - Typed State Access: Within the handler,
ctx.memory
is correctly typed according to the context's memory interface (MyContextMemory
in this example). - Organization: Keeps related logic bundled together.
Note: This same pattern applies to defining context-specific Inputs
and
Outputs
using .setInputs({...})
and .setOutputs({...})
.
Interacting with External Systems
Actions are the natural place to interact with external APIs, databases, or
other services. Remember to use async
/await
for any I/O operations.
Important Considerations for Handlers
When writing action handlers, keep these points in mind:
-
Asynchronous Execution: Handlers often perform I/O. Always use
async
/await
for promises. The framework runs handlers concurrently up to a configured limit, so they might not execute instantly if the agent is busy. -
Cancellation (
ctx.abortSignal
): For actions that might run for a long time (e.g., complex calculations, polling), you must check for cancellation requests. Thectx.abortSignal
signals if the overall agent run has been cancelled. -
Retries (
retry
option): You can configure actions to automatically retry on failure using theretry
option in the action definition (e.g.,retry: 3
). If you use retries, try to make your handler idempotent – running it multiple times with the sameargs
should produce the same final state without unintended side effects (e.g., don't create duplicate records). -
Error Handling: Use
try...catch
blocks within your handler to catch errors from external calls or internal logic. Return a structured error response so the agent (or logs) can understand what went wrong. You can also use the advancedonError
hook in the action definition for centralized error handling logic.
How the LLM Chooses Actions
The LLM doesn't directly execute your code. Instead:
- The framework presents the
name
anddescription
of all available actions to the LLM in its prompt. - Based on its instructions and the current context, the LLM decides which action to use.
- It formulates the arguments for the action based on the
schema
you provided (including any.describe()
hints). - The framework intercepts the LLM's request, validates the arguments against
the
schema
, and then securely executes yourhandler
function with the validatedargs
.
Therefore, clear name
s and detailed description
s are vital for the LLM to
use your actions correctly.
Best Practices
- Clear Naming & Descriptions: Make them unambiguous for the LLM.
- Precise Schemas: Use Zod effectively, adding
.describe()
to clarify arguments for the LLM. - Structured Returns: Return objects from handlers with clear success/error status and relevant data.
- Use
async
/await
: Essential for any I/O. - Handle Errors: Use
try...catch
and return meaningful error information. - Check for Cancellation: Implement
ctx.abortSignal
checks in long-running handlers. - Consider Idempotency: Especially if using the
retry
option. - Choose the Right Memory Scope: Use
ctx.memory
for context instance state unless you specifically need action (ctx.actionMemory
) or global (ctx.agentMemory
) state. - Keep Actions Focused: Aim for single responsibility per action.
- Use
agent.logger
: Log important steps and errors for debugging.
Actions are the core mechanism for adding custom capabilities and stateful interactions to your Daydreams agents.