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.
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.
[
{"title":"Montréal parks","topic":"public parks"},
{"title":"Québec parks","topic":"public parks"},
{"title":"Bicycle routes","topic":"transport"}
]
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"
};
}
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");
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
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
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)
});
{
"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.
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
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_readfrom 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.