Touba Project and prepare
Select a handler process
A host decision can provide a binding's complete process:
kind, command, args and, when needed, cwd.
{
"bindings": {
"tool:orders.review/ReadOrder": {
"handler": {
"kind": "process",
"command": ".venv/bin/python",
"args": ["-B", "handlers/read_order.py"],
"cwd": "."
}
}
}
}
touba prepare --mode plan --host-decisions host-decisions.json
touba prepare --mode write --host-decisions host-decisions.json
On Windows, use .venv/Scripts/python.exe. The process is replaced as a
whole: previous arguments are not retained.
Choose either handler or package_handler for a binding.
Edit fragment-managed bindings in their fragment.
Grants, secrets and the effect contract remain separate explicit decisions.
This guide explains how to organize a small Touba project without manually managing every derived artifact.
The commands in this guide assume that the Touba CLI is available in the terminal. Installing only the VS Code extension through VSIX does not provide the touba command.
Role
A Touba project connects these elements:
| Element | Role |
|---|---|
.tb file | developer-readable source |
| module | logical identity declared in the file |
| project configuration | entry, profiles, and explicit authority-configuration references |
| build projection | AIR, assembled manifest, and run plan derived for one profile |
| manifest | effective tool execution contract derived from declared authority |
| handler | host code called by a tool |
| ownership | team responsibility, owners, and review gates |
A .tb file does not always need a manifest or handler. A project needs them when prepared AIR contains a tool to execute, whether that tool is declared in the current file or imported from another module in the same project.
Explicit host decisions
Pass decisions to prepare instead of writing a manifest-editing script.
For example, for ReadOrder in the orders.review module:
touba prepare --mode plan --host-decisions-json '{"bindings":{"tool:orders.review/ReadOrder":{"grants":["network"],"effect_contract":{"kind":"none"},"timeout_ms":5000}}}'
The report shows the diff and source requirements. After reviewing it, repeat
with --mode write, then --mode check. Only write modifies files. Adapt
the reference and decisions to your project; kind: "none" is only suitable
for a handler with no external writes.
Allowed fields: grants, secrets (handles, never values), memories,
effect_contract, timeout_ms, max_payload_bytes, sandbox. The top-level
agent_secret_scopes object selects authorized handles per agent. Selections
must match source-declared requirements. Omitted fields are preserved; an
empty list revokes that selection. Missing permissions remain visible and
are never filled automatically.
Repeated decisions are idempotent. Invalid input prevents all preparation writes. Modify fragment-owned bindings in their source fragment instead; any drift against the current manifest is refused until explicitly reconciled. The manifest remains runtime authority, with no additional registry. Existing locks must be checked and explicitly renewed after an authority change. Never pass secret values on the command line.
Module Convention
Keys in touba.project.json and its configuration object are closed:
unknown keys fail with ProjectConfigFieldUnknown. Put contexts inside
configuration.contexts, never in a root-level contexts field.
Inside a project, the convention is:
src/examples/search.tb
with:
module examples.search
The folder part gives the logical domain. The filename gives the last module segment.
Initialize a Project
Syntax:
touba init
Effect:
- creates expected project directories;
- creates
touba.project.jsonwith JavaScript,@touba/sdk, thetouba-artifacts-v1layout, andpure/dev/prodprofiles; - separates derived outputs under
.touba/build/, state under.touba/state/, and local evidence under.touba/evidence/; - prepares project exclusions;
- creates no manifest until a
toolexists.
For Python handlers, use touba init --language python, then
touba sdk install python. For JavaScript, use touba sdk install javascript.
Both SDKs use the same concepts; only the host language changes.
Explicit host authority
prepare exposes source requirements in manifest_requirements. Generated
manifests grant no permissions, secrets or memory scopes; triggers are disabled.
Review requirements, implement handlers and configure host permissions explicitly.
needs_changes means work remains, not that execution is ready.
Pass --manifest touba.manifest.json to keep the reviewed manifest outside the
build, or configure its path in the project. Preparation preserves existing host
decisions and business handlers. New requirements never silently expand authority.
Only touba-artifacts-v1 is accepted: .touba/build/<profile>/ for builds,
.touba/state/ for state. The single strategy, or otherwise single executable
entry, is selected when unambiguous. Otherwise use --entry <ref>. No historical
layout fallback or implicit migration is performed.
Default project entry
With several strategies, add the top-level property "entry": "strategy:app.support/Run" to touba.project.json, using an actual entry reference and preserving other fields. Both write and check preparations then share this choice.
For a one-off selection, pass the same --entry <ref> to both commands. A write with that option followed by check without it does not check the same effective project: EffectiveProjectDrift or select_entry is expected. No strategy is selected arbitrarily.
Create a Module
Syntax:
touba new module examples.hello
Effect:
src/examples/hello.tb
with:
module examples.hello
A project module name must contain at least two lowercase segments, for example examples.hello.
Create a contextual package
touba package new acme.review --context --yes creates the author manifest, procedure and README.
Use touba package check review, then touba package add ./review to register it.
See packages and context activation.
Prepare a Project Without a Tool
Example:
module examples.hello
strategy RunHello
gives Text
do
show
with message as "Touba ready"
give "done"
Command:
touba prepare --mode plan
Expected result:
- the report indicates which AIR would be produced;
- no manifest is requested;
- no handler is generated.
To write derived artifacts:
touba prepare --mode write --profile pure
The project receives derived AIR under .touba/build/pure/air/. No manifest is
produced because the program declares no tool.
Prepare a Project with a Tool
Example:
module examples.search
type SearchRequest
query is Text
type SearchResult
title is Text
tool SearchDatasets
takes request as SearchRequest
gives SearchResult
requires network
Command:
touba prepare --mode write --profile dev
Expected result:
- AIR is produced;
touba.project.jsonfixes the host language, SDK, and run profiles;.touba/build/dev/touba.manifest.jsonreceives the effective binding forSearchDatasets;.touba/build/dev/touba.run-plan.jsoncomposes the entry and derived artifacts whenentryis declared;.touba/build/dev/touba.effective-project.jsonexplains the resolved contract and each section’s provenance;- a handler stub is created under
handlers/examples/search/.
The handler remains business code to complete. The stub is not a functional implementation.
The manifest contains no secret value. Review it as the project execution contract.
Compose a Binding Fragment
A binding fragment describes an explicit wiring intent. It can come from a module, a local package, or a generation tool, but it is not a runtime artifact.
{
"fragment_schema_version": "touba-binding-fragment-v1",
"source": {
"kind": "module",
"module": "examples.search"
},
"bindings": {
"tool:examples.search/SearchDatasets": {
"handler": {
"kind": "process",
"command": "/absolute/path/to/node",
"args": ["handlers/examples/search/search_datasets.handler.mjs"],
"cwd": "."
},
"grants": ["network"],
"secrets": [],
"timeout_ms": 10000,
"max_payload_bytes": 1048576,
"payload_constraints": {
"input": {
"request.query": {
"non_empty": true,
"max_length": 80
}
}
}
}
}
}
Composition:
touba prepare --binding-fragment bindings/search.json --mode write
prepare verifies that the tool exists in assembled AIR, that required grants
and secrets are present, and that payload constraints are valid. It then writes
the profile’s assembled manifest. touba run receives that derived manifest
through an explicit run plan, never the fragment.
Validate Resource Bindings
A resource binding connects a logical Touba resource to explicit host wiring. For resource bindings, Touba validates the contract while the backend remains host-provided. With resource binding transmission (RB-2), touba run --resource-bindings sends to the handler only the bindings associated with resources actually used by the called tool.
{
"resource_bindings_schema_version": "touba-resource-bindings-v1",
"resources": {
"memory:examples.memory/Notes": {
"kind": "memory_backend",
"provider": "host.project_memory",
"config": {
"endpoint_url": "https://memory.example.internal/notes"
},
"grants": ["memory.read"],
"secrets": ["DATABASE_URL"],
"required_secrets": ["DATABASE_URL"],
"limits": {
"max_payload_bytes": 1048576
}
}
}
}
Project validation:
touba prepare \
--resource-bindings touba.resources.json \
--require-resource-bindings \
--mode check
Runtime validation:
touba run --project . \
--entry strategy:examples.memory/Run \
--profile dev
Touba refuses an unknown resource, unsupported family, inactive binding, missing required grant or secret, missing memory scope, or a field that contains a raw payload or secret value.
In the handler protocol, the transmitted binding looks like this:
{
"resource_bindings": [
{
"resource_ref": "memory:examples.memory/Notes",
"kind": "memory_backend",
"provider": "host.project_memory",
"grants": ["memory.read"],
"secret_handles": ["DATABASE_URL"],
"config": {
"endpoint_url": "https://memory.example.internal/notes"
},
"limits": {
"max_payload_bytes": 1048576
}
}
]
}
The handler uses this information as host wiring. It does not decide whether the resource is authorized: AIR, manifest, scope, grant, secret, and resource-binding checks have already been performed by Touba before dispatch.
Prepare a Project with Pure Imports
The pure import slice (IM-1) allows explicit imports of type, non-sensitive define, and pure function.
Shared module:
module examples.common
type SearchRequest
query is Text
rows is Int
define DefaultRows as Int = 5
function NormalizeQuery
takes query as Text
gives Text
do
give query
Importing module:
module examples.search
import type SearchRequest from examples.common
import define DefaultRows from examples.common
import function NormalizeQuery from examples.common
strategy RunSearch
gives Text
do
show
with message as "search project ready"
give "done"
Command:
touba prepare --mode write
Expected result:
- project modules are compiled explicitly;
- the prepared AIR for
examples.searchcontains resolved pure imports; - no manifest or handler is created if no
toolis declared; - Touba surfaces can inspect imports from prepared AIR.
Prepare a Project with Local Effectful Imports
Touba allows local imports of tool, skill, strategy, agent, and memory declared in another module of the same project. The import makes the declaration available for composition, but does not move execution authority.
Declaring module:
module examples.data_tools
type DatasetSearchRequest
query is Text
rows is Int
type DatasetSearchResult
title is Text
url is Text
tool SearchDatasets
takes request as DatasetSearchRequest
gives DatasetSearchResult
requires network
Importing module:
module examples.research_app
import type DatasetSearchRequest from examples.data_tools
import type DatasetSearchResult from examples.data_tools
import tool SearchDatasets from examples.data_tools
skill BuildDatasetBrief
takes request as DatasetSearchRequest
gives DatasetSearchResult
uses SearchDatasets
do
SearchDatasets
with request as request
into result
give result
Command:
touba prepare --mode write
Expected result:
- the prepared AIR for
examples.research_appcontains the resolvedtoolimport; - the tool keeps its canonical reference
tool:examples.data_tools/SearchDatasets; touba.manifest.jsonreceives or verifies the binding for this canonical reference;- the handler stub is created under
handlers/examples/data_tools/, not under the importing module; - grants, secrets, limits, payload constraints, and supply-chain lock remain explicit.
Other effectful imports follow the same rule:
- an imported
skillorstrategykeeps its graph and dependencies in prepared AIR; - an imported
agentremains closed until itscallable_byexplicitly names the caller, including inter-module forms; - an imported
memoryremains a logical resource; an owner must declare the requiredreadorwritescope before calling an access tool.
Prepare a Project with a Local Package
A local Touba package makes declarations available across projects. It grants no execution authority.
Possible structure:
my-project/
src/examples/package_app.tb
authors/research/
touba.package.json
src/org/example/research.tb
Package manifest:
{
"package_schema_version": "touba-package-v1",
"name": "org.example.research",
"version": "0.1.0",
"root": ".",
"touba": {
"air_profile": "touba-air-json-canonical-v1",
"runtime_compatibility": ">=1.0 <2.0"
},
"exports": {
"types": [
"type:org.example.research/DatasetSearchRequest",
"type:org.example.research/DatasetSearchResult"
],
"defines": [],
"functions": [],
"tools": [
"tool:org.example.research/SearchDatasets"
],
"skills": [],
"strategies": [],
"agents": [],
"memories": []
},
"provenance": {
"kind": "local_path",
"source": "."
}
}
Project configuration:
{
"project_version": "touba-project-v1",
"language": "javascript",
"sdk": "@touba/sdk",
"packages": [
{
"manifest": "authors/research/touba.package.json",
"editable": true,
"version": "0.1.0"
}
],
"run_profiles": {
"dev": {
"require_supply_chain_lock": false
},
"prod": {
"require_supply_chain_lock": true,
"supply_chain_lock": "touba.lock.json"
}
}
}
Module supplied by the package:
module org.example.research
type DatasetSearchRequest
query is Text
rows is Int
type DatasetSearchResult
title is Text
url is Text
tool SearchDatasets
takes request as DatasetSearchRequest
gives DatasetSearchResult
requires network
Project module:
module examples.package_app
import type DatasetSearchRequest from org.example.research
import type DatasetSearchResult from org.example.research
import tool SearchDatasets from org.example.research
skill BuildDatasetBrief
takes request as DatasetSearchRequest
gives DatasetSearchResult
uses SearchDatasets
do
SearchDatasets
with request as request
into result
give result
Command:
touba prepare --mode write
Expected result:
- the package is loaded because it is declared explicitly;
- only refs listed in
exportsare importable; - the prepared project AIR contains the useful imported declarations;
- if an exported
toolis actually imported,prepareproduces the expected binding intouba.manifest.jsonand the stub under the declaring module; - the package cannot grant a grant, secret, handler, memory scope, agent caller, or active trigger.
Equivalent option without modifying touba.project.json:
touba prepare --package authors/research/touba.package.json --mode write
Prepare a Run Plan
A run plan groups project execution paths into an explicit artifact. It points to prepared AIR, the manifest if tools exist, the secrets profile if secrets are required, and the lock if the run profile requires it.
For a simple project:
touba prepare \
--run-plan touba.run-plan.json \
--entry skill:examples.package_app/BuildDatasetBrief \
--mode write
Produced shape:
{
"run_plan_schema_version": "touba-run-plan-v1",
"entry_ref": "skill:examples.package_app/BuildDatasetBrief",
"air": ".touba/air/examples/package_app.air.json",
"manifest": "touba.manifest.json",
"secrets_profile": "touba.secrets.dev.json",
"profile": "dev",
"outputs": {
"mode": "show"
}
}
The run plan contains no business inputs, raw prompts, or secret values. Execution inputs are still supplied at run time.
For a strict profile:
touba prepare \
--run-plan touba.run-plan.json \
--entry skill:examples.package_app/BuildDatasetBrief \
--profile prod \
--mode write
In this case, if the prod profile requires a supply-chain lock, the plan also points to touba.lock.json.
Validate Multi-Team Ownership
A touba.ownership.json artifact describes responsible teams, owners, review gates, and reviews supplied for a profile.
{
"ownership_schema_version": "touba-ownership-v1",
"teams": {
"team:platform": {
"display_name": "Platform"
},
"team:security": {
"display_name": "Security"
}
},
"owners": {
"tool:examples.search/SearchDatasets": ["team:platform"]
},
"review_gates": {
"prod": {
"requires_review_for": ["tool_binding", "required_capability"],
"reviewers": ["team:security"]
}
},
"reviews": [
{
"review_id": "review:search/security-001",
"profile": "prod",
"target_ref": "tool:examples.search/SearchDatasets",
"reviewer": "team:security",
"decision": "approved"
}
]
}
Validation:
touba prepare \
--ownership touba.ownership.json \
--profile prod \
--mode check
With --require-ownership, Touba refuses if the ownership file is absent.
touba.ownership.json grants no runtime permission. It creates no handler, grant, secret, memory scope, agent caller, provider, lock, or manifest. It verifies that expected responsibility and reviews are present before moving toward a sensitive profile.
Check Coherence
Syntax:
touba prepare --mode check
This command fails if:
- the expected manifest is missing;
- a tool binding is missing;
- an expected handler is missing;
- an expected grant, secret, or memory access is not granted;
- the expected run plan is missing, drifted, or contains a sensitive field;
- an expected review gate is missing for the requested profile;
- the ownership artifact attempts to introduce runtime authority;
- the source file path does not match the module name.
Run Afterwards
prepare does not launch the runtime.
To execute a program with a tool, the manifest remains explicit:
touba run program.air.json \
--manifest touba.manifest.json
If a run plan was prepared:
touba run --run-plan touba.run-plan.json \
--inputs-json '{"request":{"query":"transport"}}'
The runtime refuses before execution if the plan points to missing AIR, unknown entry, missing required manifest or secrets profile, or an incoherent strict supply-chain lock.
For a prod profile run, the project also forces the supply-chain lock declared in touba.project.json:
touba run program.air.json \
--profile prod \
--manifest touba.manifest.json
For a program without a tool, no manifest is necessary:
touba run program.air.json
Project Contract
prepare coordinates the project-level contract:
- pure imports (IM-1):
type, non-sensitivedefine, and purefunction; - effectful imports (IM-2):
tool,skill,strategy,agent, andmemory, without implicit authority; - explicit packages (PKG-1) through
touba.project.jsonor--package, without registry resolution or implicit authority; - run plans (RP-1) through
touba prepare --run-planandtouba run --run-plan, without sensitive payloads or automatic discovery; - binding fragments (BF-1) through
touba prepare --binding-fragment, composed into the assembled manifest without direct fragment execution; - resource bindings (RB-1 / RB-2) through
touba prepare --resource-bindingsandtouba run --resource-bindings, then transmitted to concerned handlers with host-provided backends.
The project contract remains explicit: no aliases, wildcards, re-exports, inline handlers in .tb, automatic manifest discovery by the runtime, automatic .env reads, or silently added grants or secrets.
These boundaries prevent an imported or prepared file from adding invisible execution authority. For tools, import makes the tool available while manifest, handler, grants, secrets, constraints, supply-chain lock, and runtime refusals remain explicit. For agents and memories, callable_by rules and scopes remain verified by project assembly.
Host decisions from a file and by module
touba prepare --mode plan --host-decisions host-decisions.json
touba prepare --mode write --host-decisions host-decisions.json
touba prepare --mode check --host-decisions host-decisions.json
{
"modules": {
"orders.review": { "grants": ["network"], "timeout_ms": 5000 }
},
"bindings": {
"tool:orders.review/ReadOrder": {
"effect_contract": { "kind": "none" }
}
}
}
A module key matches its exact declared name. Module decisions cover grants, secrets and limits; each tool receives only grants and handles it requests. Binding decisions override the corresponding module decision, including an empty list to remove a grant. Effect contracts remain explicit per binding.
For multiple workflows, keep entry as the default and add entrypoints in
touba.project.json. The project server and
touba run --project . --entry <reference> use that same list.
Remove an unused binding
After removing a tool from source, explicitly request removal of its old binding:
{"remove_bindings":["tool:orders.review/OldTool"]}
touba prepare --mode plan --host-decisions host-decisions.json
touba prepare --mode write --host-decisions host-decisions.json
touba prepare --mode check
The plan shows remove_binding. A tool still declared in source is protected;
remove a fragment-owned binding from that fragment. Handler files remain for
your review. Report imports are grouped by source relationship, so preparing
several entry points keeps each relationship once.