Skip to main content

Agent actions

Every entrypoint and task receives an Agent as its first argument. Each method on the agent is an action that runs on the RunLLM server on behalf of your assistant. RunLLM creates the agent for you, so you never construct one yourself.

Actions raise an Exception if the server request fails.

Several actions accept a ChatMessage, which can be any of:

Answering questions​

answer()​

agent.answer(target: Conversation) -> AssistantChatMessage

Generates an answer to target.new_message using your RunLLM assistant and its knowledge base. The answer is not sent anywhere. Pass it to one of the send_to_* actions to deliver it.

The returned message's category tells you how confident the assistant is. See AnswerCategory.

answer = agent.answer(event.conversation)
if answer.category == AnswerCategory.IRRELEVANT:
return
agent.send_to_slack_thread(answer, to=event.conversation.surface)

chat()​

agent.chat(conversation: Conversation) -> AssistantChatMessage

Generates an answer for the conversation using the agentic RunLLM assistant, which can reason over the full conversation on the surface. Like answer(), it returns the message without sending it.

summarize()​

agent.summarize(text: str, custom_instructions: str = "") -> str

Generates a structured summary (title, overview, implications, and so on) of any text, such as a conversation transcript, meeting notes, or Jira ticket details.

ArgumentDescription
textThe text to summarize.
custom_instructionsOptional guidance for the summary, for example "Focus on business impact" or "Highlight action items".

fetch_jira_tickets()​

agent.fetch_jira_tickets(text: str, jira_domain_name: str) -> str

Finds Jira issue keys and URLs in text (for example PROJ-123 or https://acme.atlassian.net/browse/PROJ-123), fetches them from Jira, and returns a formatted string with each ticket's title, description, comments, and related issues. Returns an empty string if no tickets were found. Requires the Jira integration to be connected to your assistant.

details = agent.fetch_jira_tickets(event.conversation.new_message.text, "acme.atlassian.net")
if details:
summary = agent.summarize(details, "Focus on customer impact")
agent.send_to_slack_thread(summary, to=event.conversation.surface)

Tagging and categorizing​

tag()​

agent.tag(
conversation: Conversation,
options: Dict[str, str],
guidelines: Optional[str] = None,
) -> List[str]

Has the assistant pick every tag from options that fits the conversation, and applies them. Tags already on the conversation are skipped. Returns the newly selected tags, and adds them to conversation.tags.

ArgumentDescription
conversationThe conversation to tag.
optionsA mapping of tag name to a description of when the tag applies.
guidelinesOptional extra instructions for choosing tags.
agent.tag(
event.conversation,
options={
"billing": "Questions about invoices, pricing, or payment methods.",
"bug": "The user is reporting something that looks broken.",
},
)

categorize()​

agent.categorize(
conversation: Conversation,
categories: Dict[str, str],
guidelines: Optional[str] = None,
) -> str

Has the assistant pick exactly one category from categories that best describes the conversation, and returns its name. Unlike tag(), this doesn't apply anything to the conversation. Call apply_tag() if you want to record it.

tip

Because a category is always chosen, include a catch-all such as "other" for conversations that don't fit any of your categories.

category = agent.categorize(
event.conversation,
categories={
"how_to": "The user wants to know how to do something.",
"incident": "The user is reporting an outage or degraded service.",
"other": "Anything else.",
},
)
agent.apply_tag(category, to=event.conversation)

apply_tag()​

agent.apply_tag(tag: str, to: Conversation) -> None

Applies a single tag to the conversation, and adds it to conversation.tags.

Slack​

send_to_slack_thread()​

agent.send_to_slack_thread(
message: ChatMessage,
to: SlackThread,
ephemeral: bool = False,
prefix: Optional[str] = None,
enable_feedback: bool = True,
blocks: Optional[List[Dict[str, Any]]] = None,
) -> Optional[ButtonClicked]

Posts a message to a Slack thread. If to is a new thread returned by create_or_get_slack_thread() that hasn't been posted to yet, the first send creates the thread and sets its thread_ts.

ArgumentDescription
messageThe message to post.
toThe SlackThread to post to.
ephemeralIf True, posts an ephemeral message that only the user can see. Defaults to False.
prefixOptional text to put before the message.
enable_feedbackIf True, adds RunLLM's feedback footer to the message. Defaults to True.
blocksOptional extra Slack blocks to add after the message. Markdown blocks are always sent with verbatim set to true.

In addition to standard Slack blocks, blocks supports a RunLLM-specific buttons block. Each button has a text, and either an id (a button your workflow responds to, with an optional style of "primary" or "danger") or a url (a link button):

clicked = agent.send_to_slack_thread(
"Did this answer your question?",
to=thread,
enable_feedback=False,
blocks=[
{
"type": "buttons",
"buttons": [
{"id": "yes", "style": "primary", "text": "Yes"},
{"id": "no", "style": "danger", "text": "No"},
{"text": "Read the docs", "url": "https://docs.example.com"},
],
}
],
)
if clicked and clicked.id == "no":
...

When a message includes buttons with an id, the task suspends until the user clicks one, and then re-runs with send_to_slack_thread() returning a ButtonClicked whose id is the clicked button. Otherwise, it returns None. Read Buttons and suspended execution before using buttons.

create_or_get_slack_thread()​

agent.create_or_get_slack_thread(
session_id: int,
team_id: str,
channel_id: str,
) -> SlackThread

Returns a Slack thread in channel_id that's linked to the conversation session_id. Calling it again with the same arguments returns the same thread, so it's safe to call on every run. The thread isn't posted to Slack until you first call send_to_slack_thread() with it.

Use this to escalate conversations from any surface to an internal Slack channel.

ArgumentDescription
session_idThe conversation the thread belongs to, usually event.conversation.session_id.
team_idThe Slack workspace ID (starts with T).
channel_idThe Slack channel ID (starts with C). The RunLLM bot must be a member of the channel.

Chat widget​

send_to_chat_widget()​

agent.send_to_chat_widget(message: ChatMessage, to: ChatWidget) -> None

Sends a message to a chat widget conversation.

answer = agent.answer(event.conversation)
agent.send_to_chat_widget(answer, to=event.conversation.surface)

Zendesk​

create_zendesk_ticket()​

agent.create_zendesk_ticket(
subdomain: str,
conversation: Conversation,
tags: Optional[List[str]] = None,
title: Optional[str] = None,
description: Optional[str] = None,
followup_source_id: Optional[str] = None,
) -> ZendeskTicket

Creates a Zendesk ticket containing the conversation transcript, and returns it.

ArgumentDescription
subdomainYour Zendesk subdomain. For https://acme.zendesk.com, use acme.
conversationThe conversation to put in the ticket.
tagsOptional Zendesk tags to apply to the ticket.
titleOptional ticket subject. Defaults to the first user message in the conversation.
descriptionOptional template for the ticket body. Use {conversation} where the transcript should go, for example "Escalated from the web widget:\n\n{conversation}".
followup_source_idOptional ID of a closed ticket that this ticket follows up on.

send_to_zendesk_ticket()​

agent.send_to_zendesk_ticket(
message: ChatMessage,
to: ZendeskTicket,
requester_email: Optional[str] = None,
public: bool = False,
) -> None

Adds a comment to a Zendesk ticket.

ArgumentDescription
messageThe comment to add.
toThe ZendeskTicket to comment on.
requester_emailOptional email address to post the comment as. Defaults to bot@runllm.com.
publicIf True, the comment is visible to the requester. Defaults to False (internal note).

update_zendesk_ticket()​

agent.update_zendesk_ticket(
ticket: ZendeskTicket,
status: Optional[str] = None,
custom_fields: Optional[List[ZendeskCustomField]] = None,
) -> None

Updates a ticket's status and custom fields. Use ZendeskTicketStatus for the built-in statuses. On success, ticket.status is updated in place.

from runllm import ZendeskCustomField, ZendeskTicketStatus

agent.update_zendesk_ticket(
ticket,
status=ZendeskTicketStatus.PENDING,
custom_fields=[ZendeskCustomField(id=360012345678, value="ai_answered")],
)

Workflow control​

listen()​

agent.listen(
on: List[ChatSurface],
handler: TaskWrapper,
triggers: Optional[List[Trigger]] = None,
) -> None

Moves the workflow run to the handler task and starts listening for events on the surfaces in on. The current function keeps running until it returns, but future events in the conversation are routed to handler instead of the current task. See Moving between tasks.

ArgumentDescription
onThe surfaces to listen on. They're passed to handler as positional arguments in the same order.
handlerThe @task-decorated function to call on the next matching event. It must be included in Client.publish(..., tasks=[...]).
triggersOne trigger per surface in on, in the same order. The lists must be the same length.
Always pass triggers

Pass triggers explicitly. Use ConvoMessage() for Slack threads and chat widgets, and TicketComment() for Zendesk tickets.

agent.listen(
on=[event.conversation.surface, ticket],
handler=wait_for_agent_reply,
triggers=[ConvoMessage(), TicketComment()],
)

edit_message()​

agent.edit_message(convo: Conversation, text: str) -> None

Replaces the text of convo.new_message in RunLLM, and updates convo.new_message.text in place. This is useful for normalizing or enriching a question before calling answer(), for example by appending the details returned by fetch_jira_tickets().

save_state()​

agent.save_state(state: Dict[str, Any]) -> None

Saves each key-value pair in state. Values must be JSON-serializable. Existing keys are overwritten. State is shared by all runs of the workflow.

read_state()​

agent.read_state(key: str) -> Any

Returns the value saved under key, or None if it has never been set.