Skip to main content
Version: 1.0.0

Tutorial - Use Native Calls Under Touba Authority

This tutorial shows how to let a model propose a native function call without giving it execution authority. The Touba program keeps its contracts; only the representation used by the provider changes.

At the end, you will have an agent that can select and reuse its skills through the SDK native interface while the runtime still validates every decision.

What Changes and What Does Not​

ElementBehavior
.tb sourceunchanged; no special syntax is required
AIR and runtime protocolunchanged
SDK providerdecisionInterface: "native" enables model function calls
executionremains exclusively controlled by the Touba runtime
default moderemains structured until the host explicitly selects native

The native interface is useful when the selected model supports strict function calls and structured outputs. It does not turn a skill, function, strategy, or agent into a Touba tool: it only provides a common representation during decision making.

Prerequisites​

  • the touba command;
  • the @touba/sdk JavaScript SDK;
  • Node.js to load the provider module;
  • a model that supports function calls and structured outputs;
  • a provider key stored in the environment or in a host secret manager.

Install the SDK archive​

Use Node.js 24 and npm. In a macOS/Linux terminal:

Terminal
mkdir -p native-calculator-demo
cd native-calculator-demo
mkdir -p providers vendor
npm init -y
node --input-type=module -e 'import {execFileSync} from "node:child_process"; import {copyFileSync} from "node:fs"; const v=JSON.parse(execFileSync("touba",["version","--json"],{encoding:"utf8"})); copyFileSync(v.distribution.sdk_archive,"vendor/touba-sdk.tgz");'
npm install --offline --ignore-scripts --save-exact ./vendor/touba-sdk.tgz

Step 1 - Write the Touba Program​

Create native-calculator.tb:

native-calculator.tb
module examples.native_calculator

skill Double
description "Doubles one integer and returns the result."
takes item as Int
gives Int
do
give item * 2

skill AddTen
description "Adds ten to one integer and returns the result."
takes item as Int
gives Int
do
give item + 10

agent Calculator
description "Selects declared arithmetic skills for the requested calculation."
takes start as Int
gives Int
responsibility "Starting from start, invoke Double on the initial value, invoke Double again on the observed result, then invoke AddTen on that result. Return the final observed integer."
uses Double
uses AddTen
callable_by strategy RunCalculation

strategy RunCalculation
description "Runs one governed calculation."
takes start as Int
gives Int
uses Calculator
do
Calculator
with start as start
into result
give result

The agent scope is declared by uses. The provider can only propose Double or AddTen. The callable_by rule still reserves entry into Calculator for the declared strategy.

Compile the program:

Terminal
touba compile native-calculator.tb --out native-calculator.air.json

Step 2 - Enable the Native Interface in the Provider​

Create providers/calculator.provider.mjs:

providers/calculator.provider.mjs
import { createOpenAiDecisionProvider } from "@touba/sdk";

export const provider_metadata = Object.freeze({
provider_kind: "sdk_ai",
provider_name: "calculator-provider",
provider_version: "1.0.0",
model_family: "openai",
prompt_template_id: "calculator-native-v1",
decision_schema_version: "touba-agent-decision-v1",
});

export const decideAgentCapability = createOpenAiDecisionProvider({
model: "gpt-4.1-nano",
decisionInterface: "native",
instructions: [
"Complete the requested calculation using observed data.",
"Use only capabilities authorized by the Touba runtime.",
"Return a final answer only after the required calculation is complete.",
].join(" "),
maxOutputTokens: 1000,
});

Application-controlled instructions use English to keep the provider protocol consistent. Business inputs and answers may remain in the user's language.

For another AI SDK-compatible provider, use the same option with createAiSdkDecisionProvider:

Supply a real model object from the installed provider to createAiSdkDecisionProvider({ model, decisionInterface: "native" }); see the SDK guide.

If the model rejects temperature or forced calls, add temperature: null or nativeToolChoice: "auto", respectively. These are transport options, not permissions: Touba still rejects a selection without a valid call. Also qualify the connector's reasoning settings and output limit before running a paid workflow.

Step 3 - Run the Agent​

Expose the key in the environment. Do not place it in source, inputs, or the provider file:

Terminal
: "${TOUBA_AGENT_DECISION_OPENAI_API_KEY:=${OPENAI_API_KEY:?Set OPENAI_API_KEY in your shell or secret manager}}"
export TOUBA_AGENT_DECISION_OPENAI_API_KEY

Run the strategy:

Terminal
touba run native-calculator.air.json \
--entry strategy:examples.native_calculator/RunCalculation \
--agent-decision-provider providers/calculator.provider.mjs \
--agent-decision-provider-initial-mode capabilities \
--max-agent-steps 3 \
--max-provider-calls 8 \
--inputs-json '{"start":6}' \
--output json \
--trace-out native-calculator.trace.json > result.json
node --input-type=module -e 'import assert from "node:assert/strict"; import {readFileSync} from "node:fs"; const run=JSON.parse(readFileSync("result.json","utf8")); assert.equal(run.status,"success"); assert.equal(run.result,34); assert.deepEqual(run.trace.events.filter(e=>e.kind==="agent_capability_selected").map(e=>e.payload.selected_ref),["skill:examples.native_calculator/Double","skill:examples.native_calculator/Double","skill:examples.native_calculator/AddTen"]); console.log("NATIVE_CALCULATION_OK");'

The capabilities initial mode saves one round trip when you know the task requires a capability. Keep the default summary mode when the model must be able to answer without consulting the catalog.

This command performs billable calls to the actual provider. Expected calculation: 6 → 12 → 24 → 34. The same capability is reused. A provider or model failure remains a failed test; do not substitute a precomputed answer.

This sequence requires six provider calls without retries: three selections, two intermediate orientations and one finalization. The limit of eight leaves room for retries; it does not represent eight skill calls.

Step 4 - Understand the Actual Cycle​

Decision cycle
Touba agent
-> structured orientation, or an explicitly requested initial catalog
-> authorized catalog built by the runtime
-> native functions derived from signatures
-> one proposed operation or typed final result
-> SDK normalization without execution
-> runtime validation, then execution or result return
-> observation associated with the call arguments
-> next decision or structured finalization

During the catalog phase, the SDK exposes one native function per authorized capability, plus touba_finish for a final result matching gives. The model must propose exactly one call. The SDK does not provide an execute function to the provider, so receiving a call has not produced an effect yet.

The runtime receives a normal Touba decision and checks the target, arguments, and authority before entering the capability. touba_finish becomes an answer_directly decision without a business effect; its result must match the agent's gives type. Free prose without this control is still rejected.

Step 5 - Reuse a Capability​

An authorized capability is not consumed by its first call. The agent may select Double several times with identical or different arguments. Every invocation is validated and counted separately.

Reuse stops when the task completes or when a limit applies, including:

  • maxAgentSteps for agentic steps;
  • max_provider_calls for provider calls;
  • max_tool_calls for tools that are actually dispatched;
  • duration and execution budgets declared by the project.

The used-capabilities history describes what happened. It is not a list of capabilities forbidden during the rest of the run.

Step 6 - Check the Authority Boundary​

Conversation history is enabled by default: the SDK keeps connector messages between steps of this invocation. The two calls to Double remain distinct operations, each with its runtime result. To observe size, add history: { onEvent: event => console.info(event) } to the provider configuration. These events do not contain messages. For size limits and restart persistence with SQLite, see Agent conversation history. History storage does not replace checkpoints or the effect journal.

Model proposalTouba control
selected native functionthe target must belong to uses
generated argumentssignatures and types are checked again
another agent callcallable_by is checked on every invocation
memory accesssource scopes, grants, and resource bindings remain required
secret useonly the authorized handle can cross; the resolved value stays out of the prompt
validation requestthe decision belongs to the human, not the model
external effectmanifest, grants, effect identity, and runtime journal still apply
resumeretained observations do not redispatch past effects

An unknown function name, multiple calls in one proposal, invalid arguments, or an incomplete provider response is refused before capability execution.

Step 7 - Inspect the Result​

Terminal
touba inspect native-calculator.trace.json

The trace distinguishes the decision, selected capability, budget consumption, execution, and terminal answer. It must not contain the provider key or a resolved secret value.

Choose Between structured and native​

Choose structuredChoose native
portable default behaviormodel reliable with strict function calls
provider without native-function supportcloser fit with the provider's native interface
existing integration based on generateObjectrich catalog with directly exposed signatures

Both interfaces use the same runtime protocol and authority boundary. Switching interfaces does not require a .tb program change.

Common Errors​

SymptomCheck
no native call is producedverify function-call support in the model and transport
decision is refusedverify uses, callable_by, descriptions, and argument types
IncompleteModelDecision responseverify that provider generation actually completed
a limit is reached before the answeradjust budgets without removing required controls
observation is not available inlineverify whether opaque_refs was selected intentionally

For more detail, read the Touba SDK guide and the governed agentic decision concept.