Skip to main content

Runtimes

A runtime is the language environment your function runs in. This page gives you the exact handler contract — file name, function name, and dependency mechanism — for each of the four runtimes, plus a complete, deployable example for each.

The shared contract

Every runtime behaves the same way, because each one runs behind a small platform-provided web server (the "shim") that speaks one contract:

RuntimeHandler fileHandler symbolDependencies
python (default)handler.pydef handle(event)optional requirements.txt
nodejshandler.jsexports.handleoptional package.json
gohandler.gofunc Handlenone — standard library only
rubyhandler.rbdef handleoptional Gemfile

In all four languages:

  • Your handler receives one argument, the event: a dictionary/hash/object/map. A GET request produces an empty {} event; a POST request's JSON body becomes the event.
  • Your handler returns a dictionary/hash/object/map, which is serialized as the JSON response body.
  • Dependencies are installed once, at build time, when you deploy — not on each request.
Only Python deploys from the CLI today

platformctl functions deploy sends the runtime folded into the framework form field (function-nodejs, function-go, function-ruby), and POST /v1/agents accepts only adk, langgraph, crewai, and function — so a CLI deploy of a Node.js, Go, or Ruby function fails with unsupported 'framework': one of "adk", "langgraph", "crewai", "function". Deploy those three through the API instead, with framework=function plus a separate runtime field, as each section below shows.

The statusCode convention

The HTTP status of the response defaults to 200. To set a different one, include a statusCode key in your return value:

def handle(event):
if "user_id" not in event:
return {"statusCode": 400, "error": "user_id is required"}
return {"statusCode": 200, "user": event["user_id"]}

Two things to know:

  • The whole return value — including the statusCode key itself — is serialized as the response body.
  • For CloudEvent deliveries the return value is discarded and the platform answers 204 regardless; see HTTP and events.

Python

  • File: handler.py, defining def handle(event).
  • Dependencies: an optional requirements.txt next to handler.py, installed with pip at build time.
  • Sibling files: other .py files in the same directory can be imported by handler.py — the whole directory is uploaded and placed on the import path.

Complete minimal example:

def handle(event: dict) -> dict:
name = event.get("name", "world")
return {"statusCode": 200, "body": f"hello, {name}, from a Python function"}

Deploy it (Python is the default, so no flag is needed):

platformctl functions deploy ./my-function --name my-function

Node.js

  • File: handler.js, exporting handle (exports.handle).
  • Dependencies: an optional package.json next to handler.js; production dependencies are installed with npm at build time and resolve from the handler's own directory.

Complete minimal example:

'use strict';

function handle(event) {
const name = (event && event.name) || 'world';
return { statusCode: 200, body: `hello, ${name}, from a Node.js function` };
}

module.exports = { handle };

Deploy it through the API, with framework=function and runtime=nodejs:

tar -czf my-function.tar.gz -C my-function .
curl -s -X POST "$CAI_API/v1/agents" \
-H "Authorization: Bearer $CAI_TOKEN" \
-F "name=my-function" \
-F "framework=function" \
-F "runtime=nodejs" \
-F "code=@my-function.tar.gz"

You should see (HTTP 202 — the build runs in the background):

{"agent": "my-function", "build_id": "2f6f2f6e-8a1e-4c3b-9d2a-1b2c3d4e5f6a"}

Then poll platformctl status my-function until it reads ready.

Go

Go is the compiled runtime, and it works differently from the other three: your handler.go is compiled into the platform's shim binary at build time, producing a single static executable. Because your code joins the shim's own Go module, it must use only the Go standard library — there is no mechanism to add third-party Go dependencies today.

  • File: handler.go, with package main, exporting exactly this signature:
func Handle(event map[string]any) (map[string]any, error)
  • Return the result map and a nil error. A non-nil error is treated as a handler failure (a 500 response for a plain HTTP call).

Complete minimal example:

package main

func Handle(event map[string]any) (map[string]any, error) {
name, _ := event["name"].(string)
if name == "" {
name = "world"
}
return map[string]any{
"statusCode": 200,
"body": "hello, " + name + ", from a Go function",
}, nil
}

Deploy it the same way, with runtime=go:

tar -czf my-function.tar.gz -C my-function .
curl -s -X POST "$CAI_API/v1/agents" \
-H "Authorization: Bearer $CAI_TOKEN" \
-F "name=my-function" \
-F "framework=function" \
-F "runtime=go" \
-F "code=@my-function.tar.gz"

If the directory has no handler.go, the build fails with:

go function requires a handler.go with a Handle(event) func

Ruby

  • File: handler.rb, defining a top-level handle(event) method.
  • Dependencies: an optional Gemfile next to handler.rb, bundled at build time.

Complete minimal example:

def handle(event)
name = (event['name'] if event.is_a?(Hash)) || 'world'
{ 'statusCode' => 200, 'body' => "hello, #{name}, from a Ruby function" }
end

Deploy it the same way, with runtime=ruby:

tar -czf my-function.tar.gz -C my-function .
curl -s -X POST "$CAI_API/v1/agents" \
-H "Authorization: Bearer $CAI_TOKEN" \
-F "name=my-function" \
-F "framework=function" \
-F "runtime=ruby" \
-F "code=@my-function.tar.gz"

How the runtime is chosen

Through the API, the runtime is the runtime form field on POST /v1/agents, sent alongside framework=function (valid values: python, nodejs, go, ruby; python is the default). See the agents API reference.

platformctl functions deploy picks a runtime from the files in your directory — handler.js means nodejs, handler.go means go, handler.rb means ruby, and anything else means python — or from an explicit --runtime flag. But it then folds that choice into the framework form field rather than sending a runtime field, so anything other than Python is rejected before the build starts:

unsupported 'framework': one of "adk", "langgraph", "crewai", "function"

Once deployed, the platform remembers the language: the runtime is folded into the function's stored framework token (function, function-nodejs, function-go, function-ruby), so redeploying the function rebuilds it in the same language automatically.

A stray handler file changes what the CLI picks

Detection is file-based. A handler.js sitting next to your handler.py makes platformctl functions deploy choose Node.js — which today means the deploy fails with the unsupported 'framework' error above instead of building your Python function. Deploy from a directory that holds only the handler you mean to ship, or pass --runtime python to be explicit.

Next steps

  • HTTP and events — GET vs. POST, the 8 MiB event cap, and CloudEvents.
  • Troubleshooting — build failures and runtime errors, with the real messages.