Skip to navigation

Inputs, outputs, and environment

A code tool has four parts: the inputs it receives, the environment variables it reads for configuration and secrets, the outputs it returns, and the code that produces the result. The inputs, environment variables, and outputs are defined when the tool is created and referenced by name in the code.

Inputs

Each input to a code tool comes from one of two sources:

  • Supplied by the AI Agent: the Agent works the value out from the conversation. Agent-supplied inputs can be text, a number, true/false, or a list.
  • Linked to a variable: the value is read from a saved variable. An object must be linked to a variable; the Agent cannot supply one directly.

Define each input the tool needs, along with its source and type. The Agent only fills in the inputs marked as Agent-supplied.

Environment variables

Environment variables hold configuration and secrets, for example a base URL or an API key. Keeping these outside the code means the same tool can point at different systems or credentials without rewriting it.

Read them in code with os.getenv("NAME") or os.environ["NAME"] after import os. Only the variables you declare here are exposed; nothing from Ada’s own environment is reachable.

Each environment variable takes its value from one of three sources:

  • Value: a fixed string you type, for example a base URL.
  • Sensitive value: a secret you type into this tool. It is stored encrypted.
  • Sensitive Variable: a sensitive variable your AI Agent captured, or a saved token. Use this source for credentials an API tool also uses, such as a client ID or a client secret.

Choose Sensitive Variable for any secret that is already stored in Ada. Ada refuses a sensitive value that you select under another source when you save the tool.

Prefer Sensitive Variable over Sensitive value for a credential you also use in an API tool. A typed secret is a copy. If you rotate the credential, you must edit each tool that holds a copy.

A sensitive variable holds data the Agent captured in one conversation. A token is stored once and used by every conversation.

Secrets are stored redacted. Once saved, a secret’s value is not shown again in the dashboard.

Outputs

Each output is a field the tool returns. For every output, choose how it is used:

  • Exposed to the AI Agent: the Agent can use the value in its replies.
  • Saved to a variable: the value is written to a variable for later steps to read.

An output can be exposed to the Agent, saved to a variable, or both. Declare one output per field so each value is addressed on its own.

Let Playbooks branch when the tool fails

For every other output, you choose which part of the returned value to select. A run that fails returns nothing, so those outputs hold no value. The run status is the exception. It reports how the run ended, so it always holds a value.

To let a Playbook recover from a failed run:

  1. Enable Let playbooks branch when this tool fails in the Outputs section. Ada adds the output and names it ada_run_status. The name is fixed, and the Path field does not apply.
  2. Save the output to a variable.
  3. Read that variable from an IF/ELSE step in your Playbook. See Code tool error handling.

This setting is rolling out. If the Outputs section does not show it yet, contact your Ada representative.

The status is one of five values:

ValueMeaning
okThe code ran and returned a result.
timeoutThe code exceeded a time or memory limit. A retry can succeed.
errorThe code raised an error, or Ada could not run it. A retry usually does not help. It does help when the failure was on Ada’s side.
invalid_inputAn input value was the wrong type. The code did not run.
disabledAda turned the tool off after repeated timeouts.

The code

The tool returns the value of its last expression: whatever the final line evaluates to becomes the result. You don’t wrap it in a function or write a return. Your inputs are available as variables by name, and you call the available functions below directly.

Supported Python

The code runs on a sandboxed Python interpreter — a restricted subset, not full Python. Here is exactly what’s available.

You can use

  • Variables, arithmetic, string operations, f-strings (including format specs like f"{price:.2f}"), and .format().
  • Conditionals (if), loops (for / while), comprehensions, try / except, your own def and lambda functions, and class definitions.
  • Import these standard-library modules as usual: json, re, datetime, math, os, pathlib, sys, and typing. (pathlib can only build paths; reading a file is blocked.)
  • The current date and time: after from datetime import datetime, date, datetime.now() and date.today() return the time in your AI Agent’s configured timezone.

You can’t use

  • Any other import. No requests, random, collections, itertools, and so on. A disallowed import is caught when you save, even inside try / except, and a saved tool that still contains one fails when it runs. Common gaps (hashing, Base64, URL-encoding, UUIDs) are covered by the built-in functions instead.
  • Generators, del, and match. No generators (yield), no del statement, and no match statement. These are caught when you save.
  • eval, exec, open and file access, input, and dynamic tricks like globals().
  • % string formatting. Use f-strings or .format() instead. This fails when the tool runs, not when you save it.

The result

The result is the tool’s last expression, and it must be a JSON value: text, a number, true/false, a list, or an object with text keys, not a file or an image. A few common Python types aren’t JSON and must be converted before you return them: a datetime (call .isoformat()), a set or tuple (wrap in list(...)), or bytes (decode it first). Returning nothing (None) counts as no result.

To make a network request, read an environment variable, or read and write variables, use the built-in functions below rather than a library. For the full sandbox limits, see Limits and best practices.

Available functions

Ada provides these functions directly in your code, no import needed.

fetch — make an HTTP request

response = fetch("GET", "https://api.example.com/orders/123")
# -> {"status_code": 200, "headers": {...}, "body": "..."}

fetch(method, url, headers=None, body=None, timeout=None) returns an object with status_code, headers, and body. The body is a string, so parse it with json.loads if it’s JSON. Requests reach only the domains on your allowed-domains list (plus your own Ada instance); a blocked or failed request raises an error you can catch. Redirects aren’t followed, so a request to a redirecting URL returns the 3xx response, not the final page. Per run: up to 10 requests, a 1 MB response each, and about 60 seconds of network time.

get_variable and set_variable — read and write variables

tier = get_variable("customer_tier")
set_variable("last_lookup_status", "ok")

get_variable(name) returns a saved variable’s current value, and raises if it doesn’t exist. set_variable(name, value) saves a value, applied only if the run succeeds. Sensitive-scoped variables and tokens can’t be read or written from code. Declare them as environment variables instead.

These two functions need a live conversation. A test run from the code editor has none, so a snippet that calls them fails there with a name error. Test that snippet in a conversation instead.

send_email — send an email from the conversation

# Send verification code
# Inputs (bound to variables): account_email (text)
# Outputs: sent, error
# Settings. Change these lines to change the email.
EMAIL_SUBJECT = "Your verification code"
BCC = [] # addresses that get a copy, for example ["audit@example.com"]
HTML = True # True: the body is HTML. False: the body is plain text.
REPLY_TO = None # None: replies reach your Agent. Or "noreply@example.com".
code = str(int(uuid4().replace("-", "")[:8], 16) % 900000 + 100000)
set_variable("email_verification_code", code)
body = (
"<p>Hi,</p>"
f"<p>Your one-time verification code is <strong>{code}</strong>.</p>"
"<p>It expires in 10 minutes.</p>"
)
result = send_email(
to=account_email,
subject=EMAIL_SUBJECT,
body=body,
bcc=BCC,
html=HTML,
reply_to=REPLY_TO,
)
# -> {"sent": True, "error": None}
{"sent": result["sent"], "error": result["error"]}

The code reaches the Playbook through the email_verification_code variable that set_variable writes, so a later step can compare it with what the customer types. Never return the code as an output and never let the AI Agent read that variable. An output or a variable the AI Agent can see is one a customer can ask it for, which lets them pass verification without the inbox.

send_email(to, subject, body, bcc=None, html=False, reply_to=None) sends one email through your Agent’s Email channel and returns sent and error. Name every argument in the call, as in the example, and keep the values a teammate may change in named settings at the top of the tool. A teammate who is not a programmer can then change the subject or turn HTML off without reading the rest. The email goes out the way your Agent’s email replies do: from the channel’s address, with the channel’s sender name. A refused send doesn’t raise: sent is False and error names the reason. Save the result to an output so a Playbook can branch on it. Only an argument of the wrong type raises, for example a number passed as subject, and that fails the run.

errorMeaning
invalid recipientto is not a valid email address.
invalid bcc recipientOne bcc address is not valid.
invalid reply-to addressreply_to is not a valid email address.
reply-to domain not allowedreply_to is not on a domain your Email channel has an address on.
empty subjectsubject is blank.
empty bodybody is blank.
line break in headersubject contains a line break. An address with a line break is reported as invalid.
field too longsubject or an address is over its length limit.
email channel not configuredYour Agent has no Email channel set up.
conversation send limit reachedThis conversation already sent 10 emails.
recipient daily send limit reachedThis address already received 15 emails from your Agent today, as to or in bcc.
client daily send limit reachedYour Agent already sent 5,000 emails today.
run time budget exceededEarlier fetch calls in this run spent the tool’s time for outbound calls, so the send did not start.
blocked in simulationA test run that blocks the tool’s external calls skipped the send. No limit was used.
  • to is one address. To send a copy to someone else, use bcc.
  • bcc is a list of up to 5 addresses. A repeated address, or the to address, is sent once.
  • html=True sends the body as HTML, so a link is an <a href="..."> tag. Otherwise the body is plain text.
  • An HTML body is cleaned before it goes out. Paragraphs, line breaks, headings, lists, bold, italic, code, tables and links stay. Scripts, styles, images and every other tag or attribute are removed, and the text inside them stays. A link keeps its address only when its domain is on your allowed domains list for code tools. Otherwise only the link text stays. This means text an end user typed, such as a name, can go in an HTML body without giving them a way to put their own link in an email from your address. Add only domains you fully control to that list. The check covers the domain, not the path, so a link can point at any page on an allowed domain.
  • reply_to replaces the reply-to address. By default it is the channel’s own address, so a reply reaches your Agent as an email conversation. Pass a no-reply address when replies should go nowhere, for example for a one-time code. The address must be on a domain your Email channel has an address on, verified or not. Any other domain is refused.
  • Use send_email for a message tied to the conversation, such as a code, a receipt or a confirmation. It is not for a campaign or a newsletter. The email has no unsubscribe link.

The subject and body go out exactly as written, with no greeting or sign-off added. The email has no attachments. Per conversation: up to 10 emails. Per address: up to 15 emails a day. Per Agent: up to 5,000 emails a day. Contact your Ada team to raise the per-Agent limit. The subject is limited to 255 characters and the body to 20,000.

This function is available only while email sending from code tools is turned on for your Agent, and the Email channel must be set up. An address on your own domain is used only after its DNS records are verified. Until then the email goes out from the channel’s Ada address. Contact your Ada team to turn it on. Like get_variable, it needs a live conversation, so a test run from the code editor fails with a name error.

Utilities — hashing, encoding, and IDs

Common helpers that would otherwise need a library, available directly (no import):

  • uuid4() — a random UUID string.
  • sha256(text) and hmac_sha256(key, text) — hex digests.
  • b64encode(text) and b64decode(text) — Base64 encode and decode.
  • quote(text) and urlencode(mapping) — URL-encode a single value, or a set of query parameters.

log — write to the run log

log("looked up order", order_id)

log(...) (and print(...)) adds a line to the tool’s run log for debugging. Logs show in the conversation’s run detail, have secrets redacted, and are never shown to the AI Agent.

Voice capture

For voice-enabled AI Agents, code-tool inputs support the same voice capture settings as API tools (speech, keypad, or SMS), along with confirmation before the value is used. This lets an Agent collect an input reliably over a voice call. SMS capture is available only while Agent SMS is enabled.