Skip to content

Writing Functions

A Function tool is a serverless function - written in Python, JavaScript, or Lua - that runs on demand without you managing any infrastructure. You write the logic, define the input schema, and Rival handles execution. When someone calls your tool via the API or marketplace, your function runs, returns a result, and stops. There is no persistent process, no server to configure, and no runtime to maintain.


The handler pattern

Every Function tool follows the same structure regardless of which language you choose. Your code lives in a main file and exposes a single entry point - a function called cortexone_handler. When a caller invokes your tool, Rival calls this function with two arguments: the input payload and a context object.

The file names are:

  • Python: cortexone_function.py
  • JavaScript: cortexone_function.js
  • Lua: cortexone_function.lua

Handler signatures

Python

def cortexone_handler(event, context):
# your logic here
return {"statusCode": 200, "body": {"result": "value"}}

JavaScript

function cortexone_handler(event, context) {
// your logic here
return { statusCode: 200, body: { result: "value" } };
}

Lua

function cortexone_handler(event, context)
-- your logic here
return {statusCode = 200, body = {result = "value"}}
end

The event parameter

event is the input payload sent by the caller. It corresponds to the event schema you define in Step 2 of the tool builder. When the tool is invoked, the caller provides a JSON object that matches this schema - and Rival passes it to your handler as the event argument.

In Python this arrives as a dictionary. In JavaScript it’s a plain object. In Lua it’s a table. You access fields by key and use them in your logic.

If you define multiple events for a single tool, each event has its own named schema. At runtime, the specific event the caller selected determines the shape of the input your handler receives.


The return format

Your handler must always return an object with two fields:

  • statusCode - an integer representing the outcome. Use 200 for success, 400 for bad input, 500 for unexpected failures, and so on. These follow HTTP semantics.
  • body - the response payload. This can be a dictionary, object, or table containing whatever your tool produces. Rival serializes it automatically - you don’t need to convert it to a JSON string.

A response looks like this in each runtime:

# Python
return {"statusCode": 200, "body": {"summary": "...", "word_count": 142}}
// JavaScript
return { statusCode: 200, body: { summary: "...", wordCount: 142 } };
-- Lua
return {statusCode = 200, body = {summary = "...", word_count = 142}}

If your handler returns None, undefined, or nil, the platform will return a 500 error with a null body. Always return the full {statusCode, body} structure.


Error handling

There are two ways to signal errors from your handler.

Return an intentional error status when the problem is something your code detected - a missing required field, an invalid input value, or a precondition that wasn’t met. This is the preferred pattern for validation errors:

def cortexone_handler(event, context):
if not event.get("text"):
return {"statusCode": 400, "body": {"error": "text field is required"}}
# proceed with logic ...

Let unhandled exceptions propagate for unexpected failures. If your code raises an exception that you don’t catch, Rival catches it at the platform level and returns a 500 response with the error message in the body. This is appropriate for genuinely unexpected conditions - don’t swallow errors that indicate something is broken.


Multiple events

A single tool can expose multiple events, each with its own input schema. For example, a text analysis tool might have an event for summarization and a separate event for keyword extraction. The caller chooses which event to invoke, and the handler receives the corresponding payload.

Inside your handler, you can distinguish between events using a field in the event payload - by convention, a type or action field - and branch your logic accordingly. This lets you keep related functionality in a single tool rather than splitting it across multiple tools.


Keep code stateless

Each execution of your function is independent. There is no shared memory between calls, no persistent state, and no guarantee that two consecutive executions will run in the same environment. Write your handler so that it depends only on the event input and any external data sources it explicitly fetches (APIs, databases, digital assets).

If you need to persist data between executions, use an external store - a database, a file in Digital Assets, or a third-party service. Do not rely on global variables or module-level state.


Next steps

For runtime-specific details - available libraries, package management, and language behavior - see the individual runtime guides: