Skip to main content
Version: 1.0.0

Tutorial: a package context and a governed tool

This walkthrough performs a real read of a local JSON catalog. The pack supplies procedure, the tool performs the read, and the runtime checks authority. No paid call or network access is required.

1. Prepare the project​

Prerequisites: an installed Touba distribution, Node.js 24, and npm. These commands target a macOS/Linux terminal. The bundled SDK is copied into the project and installed offline, without a link to the Touba checkout.

Terminal
mkdir -p dataset-brief-demo
cd dataset-brief-demo
mkdir -p vendor handlers providers data
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"})); if(!v.distribution.sdk_archive) throw new Error("Install the Touba toolchain distribution first"); copyFileSync(v.distribution.sdk_archive,"vendor/touba-sdk.tgz");'
npm install --offline --ignore-scripts --save-exact ./vendor/touba-sdk.tgz
touba init
touba package new acme.dataset --context --yes
mkdir -p dataset/scripts

package new --context creates the author manifest, procedure and README. Other files are included only when declared.

2. Supply data, script, and procedure​

Create each file at the indicated path, relative to the new project root.

data/datasets.json
[
{"title":"Montréal parks","topic":"public parks"},
{"title":"Québec parks","topic":"public parks"},
{"title":"Bicycle routes","topic":"transport"}
]
dataset/scripts/normalize_dataset.mjs
import { readFile } from "node:fs/promises";

export async function normalizeDataset(request) {
if (!Number.isInteger(request.max_rows) || request.max_rows < 1) {
throw new Error("max_rows must be a positive integer");
}
const rows = JSON.parse(await readFile(
new URL("../../../data/datasets.json", import.meta.url), "utf8"
));
const selected = rows.filter(row => row.topic === request.query).slice(0, request.max_rows);
return {
title: request.query,
summary: selected.map(row => row.title).join("; ") || "No matching dataset"
};
}
configure-pack.mjs
import { readFileSync, writeFileSync } from "node:fs";
const root = "dataset";
const manifest = JSON.parse(readFileSync(root + "/touba.package.json", "utf8"));
writeFileSync(root + "/SKILL.md", [
"# Dataset brief", "",
"Use BuildDatasetBrief to read the local dataset catalog for the requested topic.",
"Return the resulting title and summary. Do not invent additional datasets.",
"Scripts are resources; only the governed tool can execute the normalization."
].join("\n") + "\n");
manifest.contexts = [{
id: "dataset-brief", description: "Prepare an accurate brief of local datasets.",
procedure: "SKILL.md", activation: { model_invocable: true, user_invocable: true },
resources: [{ name: "normalizer", path: "scripts/normalize_dataset.mjs", kind: "script",
description: "Implementation used by the governed handler", loadable_as_context: false }],
execution_links: [{ label: "Build dataset brief", target_ref: "skill:examples.dataset/BuildDatasetBrief",
description: "Use the declared dataset skill." }]
}];
writeFileSync(root + "/touba.package.json", JSON.stringify(manifest, null, 2) + "\n");
Terminal
node configure-pack.mjs
touba package check dataset
touba package add ./dataset

Installation computes and locks the actual file inventory. The script remains unloadable as model context and activation never executes it.

3. Declare the Touba program​

dataset_brief.tb
module examples.dataset

type DatasetBriefRequest
query is Text
max_rows is Int

type DatasetBrief
title is Text
summary is Text

tool RenderDatasetBrief
description "Read the local dataset catalog and summarize matching entries."
takes request as DatasetBriefRequest
gives DatasetBrief
requires filesystem_read

skill BuildDatasetBrief
description "Build a brief from the requested topic and maximum row count."
takes request as DatasetBriefRequest
gives DatasetBrief
uses RenderDatasetBrief
do
RenderDatasetBrief
with request as request
into brief
give brief

agent DatasetAssistant
responsibility "Produce an accurate brief of matching local datasets using the declared capability."
takes request as DatasetBriefRequest
gives DatasetBrief
uses BuildDatasetBrief
callable_by strategy RunDatasetBrief

strategy RunDatasetBrief
takes request as DatasetBriefRequest
gives DatasetBrief
uses DatasetAssistant
do
DatasetAssistant
with request as request
into brief
give brief

Inputs are declared and passed explicitly at every boundary. A pack is not listed in uses: it is not an executable capability.

4. Connect the handler and provider​

handlers/render_dataset_brief.handler.mjs
import { defineTool } from "@touba/sdk";
import { normalizeDataset } from "../packages/acme.dataset/scripts/normalize_dataset.mjs";

await defineTool({
toolRef: "tool:examples.dataset/RenderDatasetBrief",
inputSignature: [{ name: "request", type_ref: "type:examples.dataset/DatasetBriefRequest" }],
outputType: "type:examples.dataset/DatasetBrief",
requiredGrants: ["filesystem_read"],
run: ({ request }) => normalizeDataset(request)
});
touba.manifest.json
{
"manifest_version": "touba-manifest-v1",
"bindings": {
"tool:examples.dataset/RenderDatasetBrief": {
"effect_contract": { "kind": "none" },
"handler": {
"kind": "process",
"command": "node",
"args": [
"handlers/render_dataset_brief.handler.mjs"
],
"cwd": "."
},
"grants": [
"filesystem_read"
],
"timeout_ms": 5000,
"max_payload_bytes": 1048576
}
}
}

The local provider below is deterministic: it requests the catalog, calls the business capability, and returns its observation. It verifies the workflow without a provider key; it does not simulate a model response. A model provider uses the same governed interface.

providers/dataset-provider.mjs
export function decideAgentCapability(request) {
if (request.observations?.length) {
return { action: "answer_directly", result: request.observations.at(-1).value };
}
if (request.mode === "summary") {
return { action: "request_capabilities" };
}
return {
target_ref: "skill:examples.dataset/BuildDatasetBrief",
arguments: { request: request.inputs.request }
};
}

5. Validate, activate, and run​

Terminal
touba compile dataset_brief.tb --out dataset_brief.air.json
touba package check packages/acme.dataset
touba run dataset_brief.air.json \
--manifest touba.manifest.json \
--agent-decision-provider providers/dataset-provider.mjs \
--skill-pack packages/acme.dataset \
--activate-skill-pack skill_pack:acme.dataset/dataset-brief \
--skill-pack-discovery-max-bytes 4096 \
--skill-pack-activation-max-bytes 16384 \
--agent-model-input-max-bytes 65536 \
--inputs-json '{"request":{"query":"public parks","max_rows":5}}' \
--trace-out trace.json --output json
touba inspect trace.json

The expected result is {"title":"public parks","summary":"Montréal parks; Québec parks"}. The trace records skill_pack_discovery_listed, skill_pack_activated, skill_pack_resource_loaded, skill_pack_resource_refused with script_content_not_loaded, followed by capability selection and tool_binding_authorized. Refusing to load script code as context is expected: the handler explicitly imports it to perform authorized work.

--skill-pack offers this pack for discovery; --activate-skill-pack loads its context. Without activation, the program can still call its authorized capability, but no activation is attested. These budgets belong to this run, not to an imposed developer testing limit. touba run never scans directories implicitly.

Package checking validates the declared files. Loading against AIR verifies execution-link targets; the runtime then checks the current agent’s uses.

6. Check refusals​

Use separate test copies. For manifest changes, edit the author package before installation; editing installed files first triggers the integrity check.

  • Change the script after installation: PackageIntegrityMismatch, before provider or handler.
  • Activate a reference not supplied by --skill-pack: configuration refused.
  • Set --skill-pack-activation-max-bytes 1: SkillPackBudgetExceeded, without a tool call.
  • Mark the script loadable_as_context: true: PackageContextInvalid.
  • Remove filesystem_read from manifest grants: ToolCapabilityNotGranted, without a handler read.
  • Point the execution link at an absent capability: SkillPackExecutionLinkUnauthorized.
  • Set activation.user_invocable: false: user activation refused.

A pack grants no permissions, secrets, or authority. A script executes only through the Touba capability and its explicit binding. Grants do not constitute a host-process sandbox.