Root Functions
Deploy sandboxed JavaScript your AI can call — with sealed secrets and an egress-gated fetch.
A Root Function is a small piece of JavaScript that runs inside the engine's V8 isolate. It's how you give your AI custom capabilities: call an external API, transform data, glue services together — without standing up a separate service. The Functions tab lists them, lets you edit the body, deploy, invoke, and view run history.
The author contract
A function body must evaluate to a callable that takes (input, env):
async (input, env) => {
// input — the argument passed at invoke time
// env — your project's secrets, resolved by name
const r = await fetch("https://api.example.com/things/" + input.id, {
headers: { Authorization: `Bearer ${env.EXAMPLE_API_KEY}` },
});
return { ok: r.ok, thing: await r.json() };
}The runtime invokes (<body>)(input, env), awaits the result if it's a
promise, and returns it as JSON. Execution is bounded by a wall-clock timeout.
Environment (secrets)
env is your project's secrets, resolved by name. The
resolution order is process env first, then the local secrets file, so the
same env.X works in the cloud (provisioner injects it) and locally (root secrets set X … writes ~/.config/thinkingroot/secrets.toml). Write once, run
in both places.
Network: an egress-gated fetch
Functions get a familiar fetch(url, opts) — but every call is checked against
your project's egress allowlist first. If a host isn't
allowlisted (when an allowlist is set), the call is refused before any
network request:
egress blocked: host 'evil.example' is not in this project's TR_OUTBOUND_ALLOWLISTfetch returns { status, ok, headers, text(), json() }. With no allowlist set
(local/desktop dev), all hosts are allowed.
Deploy, invoke, inspect
In the Functions tab: write the body, Deploy, then Invoke with a JSON input and see the result. Past runs (status, output, errors) are listed per function.
# Deploy
curl -X PUT "$GATEWAY/engine/api/v1/ws/$WS/functions" \
-H "Authorization: Bearer tr_sk_…" -H 'Content-Type: application/json' \
-d '{ "name": "fetch_thing", "body": "async (input, env) => ({ id: input.id })" }'
# Invoke
curl -X POST "$GATEWAY/engine/api/v1/ws/$WS/functions/fetch_thing/invoke" \
-H "Authorization: Bearer tr_sk_…" -H 'Content-Type: application/json' \
-d '{ "id": 42 }'
# Runs
curl "$GATEWAY/engine/api/v1/ws/$WS/functions/fetch_thing/runs" \
-H "Authorization: Bearer tr_sk_…"root function deploy fetch_thing --code ./fetch_thing.js
root function invoke fetch_thing --input '{"id":42}'
root function listEvery invocation is recorded (status, output, error) so runs are auditable.
Self-extending agents (JIT)
Root Functions are also the noun the engine's just-in-time capability acquisition produces. When an agent hits a wall, it classifies the gap (knowledge, capability, or impossible) and climbs an acquisition ladder — reuse a tool, load or define a skill, install an MCP server, or author and deploy a Root Function — then retries. Capabilities the agent writes this way persist as ordinary Root Functions you can inspect.
Honest about maturity
The manual Root Function path (deploy → invoke → runs) is built and verified end-to-end. The fully autonomous JIT loop is implemented but not yet proven across many real apps — treat it as capable, not battle-tested.
Build feature
The executor is gated behind the engine's root-functions build feature, which
the cloud image enables. On a build without it (e.g. a lean desktop build),
deploy/invoke return a clear "feature not enabled" error rather than failing
silently.