Co-authored-by: Jason Carreira <jason@visotrust.com> Co-authored-by: Cameron <cameron@pfiffer.org> Co-authored-by: Charles Packer <packercharles@gmail.com> Co-authored-by: Sarah Wooders <sarahwooders@gmail.com> Co-authored-by: Letta <noreply@letta.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
5.4 KiB
Response Directives
LettaBot supports XML response directives -- lightweight actions that the agent embeds directly in its text responses. The bot parses and executes these directives before delivering the message, stripping them from the output so the user never sees raw XML.
This is cheaper than tool calls (no round trip to the server) and extends the existing <no-reply/> pattern.
How It Works
The agent includes an <actions> block at the start of its response:
<actions>
<react emoji="thumbsup" />
</actions>
Great idea!
The bot:
- Detects the
<actions>block during streaming (held back from display) - Parses the directives inside it
- Executes each directive (e.g. adds a reaction)
- Delivers only the clean text (
Great idea!) to the user
If the <actions> block is the entire response (no text after it), the directive executes silently with no message sent.
Supported Directives
<react>
Adds an emoji reaction to a message.
<react emoji="thumbsup" />
<react emoji="eyes" message="456" />
Attributes:
emoji(required) -- The emoji to react with. Accepts:- Text aliases:
thumbsup,eyes,fire,heart,tada,clap,smile,laughing,ok_hand,thumbs_up,+1 - Colon-wrapped aliases:
:thumbsup: - Unicode emoji: direct characters like
👍
- Text aliases:
message(optional) -- Target message ID. Defaults to the message that triggered the response.
<send-file>
Sends a file or image to the same channel/chat as the triggering message.
<send-file path="/tmp/report.pdf" caption="Report attached" />
<send-file path="/tmp/photo.png" kind="image" caption="Look!" />
<send-file path="/tmp/temp-export.csv" cleanup="true" />
Attributes:
path/file(required) -- Local file path on the LettaBot servercaption/text(optional) -- Caption text for the filekind(optional) --imageorfile(defaults to auto-detect based on extension)cleanup(optional) --trueto delete the file after sending (default: false)
Security:
- File paths are restricted to the configured
sendFileDirdirectory (defaults todata/outbound/under the agent's working directory). Paths outside this directory are blocked and logged. - Symlinks that resolve outside the allowed directory are also blocked.
- File size is limited to
sendFileMaxSize(default: 50MB). - The
cleanupattribute only works whensendFileCleanup: trueis set in the agent's features config (disabled by default).
<no-reply/>
Suppresses response delivery entirely. The agent's text is discarded.
<no-reply/>
This is a standalone marker (not inside <actions>) and must be the entire response text. Useful when the agent decides observation is more appropriate than replying (e.g. in group chats).
Attribute Quoting
The parser accepts multiple quoting styles to handle variation in LLM output:
<!-- All of these work -->
<react emoji="thumbsup" />
<react emoji='thumbsup' />
<react emoji=\"thumbsup\" />
Backslash-escaped quotes (common when LLMs generate XML inside a JSON context) are normalized before parsing.
Channel Support
| Channel | addReaction |
send-file |
Notes |
|---|---|---|---|
| Telegram | Yes | Yes | Reactions limited to Telegram's allowed reaction set. |
| Slack | Yes | Yes | Reactions use Slack emoji names (:thumbsup: style). |
| Discord | Yes | Yes | Custom server emoji not yet supported. |
| No | Yes | Reactions skipped with a warning. | |
| Signal | No | No | Directive skipped with a warning. |
When a channel doesn't implement addReaction, the directive is silently skipped and a warning is logged. This never blocks message delivery.
Emoji Alias Resolution
Each channel adapter resolves emoji aliases independently since platforms have different requirements:
- Telegram/Discord: Map text aliases (
thumbsup,fire, etc.) to Unicode characters - Slack: Maps Unicode back to Slack shortcode names, or passes
:alias:format through directly
The common aliases supported across all reaction-capable channels:
| Alias | Emoji |
|---|---|
eyes |
👀 |
thumbsup / thumbs_up / +1 |
👍 |
heart |
❤️ |
fire |
🔥 |
smile |
😄 |
laughing |
😆 |
tada |
🎉 |
clap |
👏 |
ok_hand |
👌 |
Unicode emoji can always be used directly and are passed through as-is.
Streaming Behavior
During streaming, the bot holds back display while the response could still be an <actions> block or <no-reply/> marker. Once the block is complete (or clearly not present), the cleaned text begins streaming to the user. This prevents raw XML from flashing in the chat.
Extending with New Directives
The parser (src/core/directives.ts) is designed to be extensible. Adding a new directive type involves:
- Add the tag name to
CHILD_DIRECTIVE_REGEX(e.g.<(react|send-file)) - Add a new interface to the
Directiveunion type - Add a parsing case in
parseChildDirectives() - Add an execution case in
executeDirectives()inbot.ts
See issue #240 for planned directives.
Source
- Parser:
src/core/directives.ts - Execution:
src/core/bot.ts(executeDirectives()) - Tests:
src/core/directives.test.ts - Original PR: #239