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:
| Runtime | Handler file | Handler symbol | Dependencies |
|---|---|---|---|
python (default) | handler.py | def handle(event) | optional requirements.txt |
nodejs | handler.js | exports.handle | optional package.json |
go | handler.go | func Handle | none — standard library only |
ruby | handler.rb | def handle | optional Gemfile |
In all four languages:
- Your handler receives one argument, the event: a dictionary/hash/object/map. A
GETrequest produces an empty{}event; aPOSTrequest'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.
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
statusCodekey itself — is serialized as the response body. - For CloudEvent deliveries the return value is discarded and the platform answers
204regardless; see HTTP and events.
Python
- File:
handler.py, definingdef handle(event). - Dependencies: an optional
requirements.txtnext tohandler.py, installed with pip at build time. - Sibling files: other
.pyfiles in the same directory can be imported byhandler.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, exportinghandle(exports.handle). - Dependencies: an optional
package.jsonnext tohandler.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, withpackage main, exporting exactly this signature:
func Handle(event map[string]any) (map[string]any, error)
- Return the result map and a
nilerror. A non-nil error is treated as a handler failure (a500response 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-levelhandle(event)method. - Dependencies: an optional
Gemfilenext tohandler.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.
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.