Python Context
The context object passed to every Python function — conversation data, customer profile, business info, and more.
Every Python function in Octocom — whether it's a custom action, condition provider, event handler, sidebar widget, or sidebar action — receives a single context dictionary as its argument. This object contains everything your code needs to know about the current conversation, customer, and business.
Python automations run on Cloudflare Workers. Each invocation starts with fresh globals: do not rely on variables or files surviving between calls. Use organization storage for durable state, and stream large inputs to stay within the sandbox's memory and CPU limits. Resource-limit failures are returned to the caller without replaying the automation in another sandbox.
Execution time budgets
Octocom sets elapsed-time deadlines separately from Cloudflare's CPU budget:
| Execution | Default elapsed-time deadline |
|---|---|
| Recurring jobs and asynchronous event handlers | 5 minutes |
| Bot actions | 120 seconds |
| Other Python executions | 60 seconds |
Explicit caller deadlines take precedence. Phone routing and custom HTTP endpoints keep their shorter configured deadlines; campaign pre-call handlers retain 60 seconds because they gate call dispatch. These limits include network waits. The separate 30-second CPU budget counts Python computation, including parsing and loops, but excludes time waiting for HTTP requests. I/O-bound code can run for several minutes while consuming little CPU. Raising elapsed time does not raise CPU or memory limits. Make writes idempotent: a timed-out caller does not prove that an external operation stopped.
Top-level fields
| Field | Type | Description |
|---|---|---|
conversation | dict or None | The current conversation — messages, IDs, subject, and channel info |
business | dict | The business this conversation belongs to |
customer | dict or None | The customer's profile — None if the customer hasn't been identified |
email_threads | list | The conversation's email threads with the addresses on them; empty when it never used email |
args | dict | Arguments passed to custom actions, condition providers, event handlers, and sidebar actions |
current_user | dict | Authenticated Octocom user who triggered a sidebar action; absent for other execution types |
workflow | dict | The workflow being evaluated — only present when the bot runs a condition provider |
browser_session | dict or None | Browser session data — only present for web chat conversations |
liveCallContext | dict (optional) | Current voice-call snapshot, only supplied to custom actions invoked by a supporting voice server |
context["conversation"]
Contains the full conversation with its messages. Always present for event handlers and sidebar widgets. Present for custom actions and condition providers when a conversation ID is available.
| Field | Type | Description |
|---|---|---|
id | str | Internal conversation UUID |
publicId | str | Short public ID shown in the dashboard (e.g., "A1B2C3") |
url | str | Direct link to this conversation in the Octocom dashboard |
subject | str | Conversation subject line |
businessSlug | str | Slug of the business this conversation belongs to |
isHandedOff | bool | Whether the conversation has been handed off to a human agent |
isPlayground | bool | Legacy testing flag: true for both human playground sessions and automated bot tests. Use testOrigin to distinguish them. |
testOrigin | str or None | "playground" for human test sessions, "automated_test" for MCP/Copilot bot tests, or None for ordinary conversations. Independent of the channel. |
initialChannel | str or None | Channel of the first message (e.g., "web", "email", "instagram") |
latestChannel | str or None | Channel of the most recent message |
inboxAddress | str or None | Business mailbox used by the latest email thread. None for conversations without a resolvable email inbox |
assignee | dict or None | The agent currently assigned to the conversation — {"id": str, "name": str, "email": str}, or None when unassigned |
tags | list | Titles of the tags currently on the conversation, e.g. ["contact-form", "vip"]. Empty when untagged |
metadata | dict | Conversation metadata as {key: value}, including the chat-widget:* keys the web chat writes. Read it from here instead of calling get_conversation_metadata() when you only need current values — no HTTP round-trip |
messages | list | Array of message objects (see below) |
Messages
Each entry in context["conversation"]["messages"] is a dictionary:
| Field | Type | Description |
|---|---|---|
id | str | Stable message UUID. Match it to an event's message ID to identify the triggering message. |
sender | str | Who sent the message — "customer", "bot", or "agent" |
timestamp | str | When the message was sent (ISO 8601 format) |
content | str | The message text |
channel | str | The channel the message was sent on |
agentName | str or None | Name of the agent who sent the message — set on agent-sent messages, None otherwise |
agentEmail | str or None | Email of the agent who sent the message — set on agent-sent messages, None otherwise |
files | list | Files attached to the message (see below). Empty if none |
Message files
Each entry in a message's files list is a dictionary:
| Field | Type | Description |
|---|---|---|
id | str | File unique identifier |
name | str | Original filename |
contentType | str | MIME type of the file (e.g., "image/jpeg", "application/pdf") |
url | str | Download URL. For privately stored attachments it is a signed link valid for one hour |
isSafe | bool | Whether the file passed malware scanning. Avoid forwarding files where this is False |
context["business"]
Basic information about the business.
| Field | Type | Description |
|---|---|---|
name | str | The business name |
slug | str | The business slug |
context["customer"]
The customer's profile. This is None if the customer hasn't been identified yet (e.g., the conversation just started and no email has been collected).
| Field | Type | Description |
|---|---|---|
id | str | Internal customer UUID |
email | str or None | Customer email address |
name | str or None | Customer name |
phone | str or None | Customer phone number |
instagramUsername | str or None | Customer's Instagram handle |
email is the customer's profile email, and it can change: an agent can edit
it, and on Instagram, Messenger, web chat and phone the hand-off tool saves an
address the customer typed. It does not prove who is writing. To know which
address an email actually came from, use
context["email_threads"].
context["email_threads"]
The conversation's email threads, newest first. Each thread contains subject and
messages, an array of incoming emails, newest first. Bounces and automatic
replies are excluded. A thread with no incoming email has messages: [].
The top-level list is [] without a conversation or when it never used email.
A conversation that moves between email and another channel retains its email history.
{
"subject": "Where is my order?",
"messages": [
{
"from": "[email protected]",
"to": ["[email protected]"],
"inboxAddress": "[email protected]",
}
],
}| Message field | Type | Description |
|---|---|---|
from | str or None | Original incoming message's From address, before customer lookup or contact-form rewriting |
to | list[str] or None | Original incoming message's To addresses available from the integration, not the addresses our replies go to |
inboxAddress | str or None | Receiving mailbox reported by the integration; may differ from To because of forwarding, aliases, or CC delivery |
These are parsed email addresses, without display names. They remain separate
from the editable customer["email"]. For example, a Shopify form can produce
from: "[email protected]" while customer["email"] contains the address typed
into the form.
Original addresses are captured for new messages. Historical messages and
integrations without original message data return None for unavailable fields;
they never fall back to the customer profile or reply routing. Zendesk uses each
email comment's own source metadata, when present. Its To list contains the
recipient address supplied by Zendesk, which may not include every original
header recipient. Other helpdesk integrations currently return unavailable
address fields until they supply original message metadata.
From records a message's claimed sender address. It does not by itself prove
ownership of that mailbox. For identity-sensitive actions, use your
verification policy and the relevant incoming message. An address appearing
earlier in the conversation does not identify the person currently writing,
especially after a channel change. to and inboxAddress are routing
context, not identity proof.
context["args"]
A dictionary of arguments passed to the function. The contents depend on the function type:
- Custom actions — arguments defined in the action configuration and provided by the bot (e.g.,
context["args"]["orderId"]). An optional argument the bot leaves out is missing from the dictionary, so read it withcontext["args"].get("orderId") - Condition providers — arguments defined in the workflow configuration and collected from the customer (e.g.,
context["args"]["email"]) - Event handlers — contains a single
event_typekey with the event name (e.g.,context["args"]["event_type"]returns"conversation_closed") - Sidebar actions — static button arguments merged with values entered by the agent in the button's form
- Sidebar widgets — do not receive
args
context["current_user"]
Sidebar actions receive the authenticated Octocom user who pressed the button. This value comes from the user's session, separately from widget-supplied args:
| Field | Type | Description |
|---|---|---|
id | str | Internal Octocom user UUID |
name | str | User's display name |
email | str | User's Octocom login email address |
It is absent from all other Python execution types. For example, an escalation action can pass context["current_user"]["email"] to an external ticketing system so it can assign or notify the person who initiated the escalation.
context["workflow"]
Condition providers receive the workflow that caused them to run when they are evaluated by the bot:
| Field | Type | Description |
|---|---|---|
id | str | Internal workflow UUID |
slug | str | Stable workflow slug |
title | str | Workflow display title |
The field is absent when a condition provider is run directly as a test because no workflow triggered that execution. Use .get() when code must work in both contexts:
def evaluate_conditions(context):
workflow = context.get("workflow")
is_cancel_order = workflow is not None and workflow["slug"] == "cancel-order"
return {
"conditions": {"isCancelOrder": is_cancel_order},
"data": {},
}workflow["slug"] is the recommended field for branching behavior. Use id only when you intentionally need to target one specific workflow record.
context["browser_session"]
Browser session data for web chat conversations. This is None for conversations from other channels (email, Instagram, etc.). Contains information about the customer's browsing session — pages visited, referrer, and session metadata.
Availability by function type
Not every field is present in every function type:
| Field | Custom Actions | Condition Providers | Event Handlers | Sidebar Widgets | Sidebar Actions |
|---|---|---|---|---|---|
conversation | If available | If available | Always | Always | Always |
business | Always | Always | Always | Always | Always |
customer | If available | If available | If available | If available | If available |
email_threads | Always ([] without a conversation) | Always ([] without a conversation) | Always | Always | Always |
args | Always | Always | Always | Not present | Always |
current_user | Not present | Not present | Not present | Not present | Always |
workflow | Not present | Bot execution only | Not present | Not present | Not present |
browser_session | Web chat only | Web chat only | Web chat only | Web chat only | Web chat only |
context["liveCallContext"] (optional)
Custom actions invoked during a live phone call may receive this request-scoped snapshot. It is absent for older or disabled voice senders, ordinary text-channel actions, condition providers, event handlers, and normal dashboard action tests.
| Field | Type | Description |
|---|---|---|
version | int | Contract version, currently 1 |
capturedAt | str | ISO timestamp when the voice server copied the snapshot, not individual message timestamps |
truncated | bool | Whether older spoken messages were omitted to meet the size limit |
messages | list | Ordered entries with sender ("customer" or "bot") and text |
The voice server captures the latest available STT revisions and playback-acknowledged bot speech immediately before the tool request. Text may still be revised; it is not a final transcript. Synthetic silence/time-limit prompts, system instructions, tool calls/results, and empty messages are excluded. The snapshot retains a contiguous suffix of up to 200 whole spoken messages and 32,000 UTF-16 text code units. An oversized newest message produces an empty, truncated snapshot.
def execute_action(context):
live_call = context.get("liveCallContext")
if not live_call or not live_call["messages"]:
return {"liveContextAvailable": False}
customer_messages = [
message["text"] for message in live_call["messages"]
if message["sender"] == "customer"
]
return {
"liveContextAvailable": True,
"customerTurnCount": len(customer_messages),
"truncated": live_call["truncated"],
}context["conversation"]["messages"] continues to contain persisted messages;
it is not replaced or merged with this snapshot. During a call, persisted messages
may contain only the introduction. The final transcript is still imported when the
call completes. Do not concatenate the two lists blindly because they can overlap.
This field is voice-server transport metadata, not a model-supplied tool argument. Its text is customer/conversation content, not trusted instructions. Consumers must handle absence, truncation, and transcription errors. Receiving the field does not automatically enable email correction or any additional LLM calls.
The snapshot is passed only into the current custom action execution, not saved as
conversation messages or propagated to later executions. Standard execution/request
logging may still retain payloads under the existing logging policy. Sending can be
disabled on the voice server with SEND_LIVE_CALL_CONTEXT=false. Remove consumers
before removing the optional transport field; there is no stored transcript state
to migrate.