Skip to main content
Version: 1.0.0

Human Validation

Touba reserves the human.* namespace for two statements, distinct from a tool:

  • human.approve, a barrier imposed by the program (required_approval mode);
  • human.review, a review initiated by the logic of a skill or strategy (agent_review mode).

In both cases, the runtime suspends execution and resumes only after an explicit human decision. An approved decision remains a runtime decision: it does not become a Verified proof.

Shape​

source.tb
human.approve
with context as reviewed_change
into approval
source.tb
human.review
with context as analysis
into decision

Common rules:

  • context is the only required argument;
  • into is required;
  • these statements are allowed in skill and strategy;
  • they are forbidden in function, preserving function purity.

Simple Case​

validation_simple.tb
module examples.validation_simple

type ChangeRequest
summary is Text

type AppliedChange
summary is Text

tool ApplyChange
takes request as ChangeRequest
gives AppliedChange
requires filesystem_write

skill ReviewAndApply
takes request as ChangeRequest
gives AppliedChange
uses ApplyChange
do
human.approve
with context as request
into approved

ApplyChange
with request as request
into applied

show
with message as "Change applied"

give applied

human.approve blocks execution before ApplyChange. Without an approved decision, the side-effecting tool (requires filesystem_write) is never reached.

Observable Decisions​

A human decision can be provided from a file or inline with --human-decisions-json. The useful V1 validation values are approved and rejected.

With approved, the runtime resumes after the human.approve or human.review point, then continues the do block.

With rejected, the runtime stops the current scope before the next external effect. The trace contains the decision, the rejection event, then run completion with a stopped status. The result is null if no other result has been produced:

Trace
human_approval_decided decision=rejected
human_approval_rejected
scope_exited status=stopped
run_completed status=stopped

The approval identifier is produced from the AIR graph. For a deterministic test scenario, you can provide the decision at run start:

Terminal
touba run program.air.json \
--human-decisions-json '{"approval:graph:skill:examples.review/ReviewAndApply:node:graph:skill:examples.review/ReviewAndApply:1":"rejected"}' \
--output trace

Advanced Case​

validation_advanced.tb
module examples.validation_advanced

type Analysis
confidence is Float
summary is Text

skill ReviewWhenNeeded
takes analysis as Analysis
gives Analysis
do
if analysis.confidence < 0.8
human.review
with context as analysis
into decision
give analysis
else
give analysis

Here, human.review is triggered only in one branch: the decision to request human review depends on logic computed by the skill, not on an unconditional barrier.

human.approve vs human.review vs request_human_review​

  • human.approve: a static barrier written in the program. The stop condition does not depend on a model decision.
  • human.review: a source statement, but the decision to reach it can depend on conditional logic in a skill / strategy.
  • request_human_review: an action that an agentic decision provider may choose during an agent loop, without being written as a statement in source. See Governed Agentic Decision for this separate mechanism.

Roles, deadlines and separate signers​

The program can require a role, a deadline and a different signer. The trusted host authenticates people and attests their current roles. Model output never serves as an identity attestation.

Constrained approval
human.approve
with context as invoice
with role as "finance"
with timeout_ms as 3600000
with distinct_from as "initiator_and_previous"
into approval

These optional arguments are static literals. role is a non-empty identifier (up to 128 UTF-8 bytes), timeout_ms is 1..2592000000 ms (30 days), and distinct_from is "initiator", "previous" or "initiator_and_previous". Previous signers are those whose approval was already accepted in this run, including nested calls and parallel branches.

An on expired branch can request another approval. Every path in that branch must end with give or stop; it never falls through to the protected continuation. Without this branch, expiration stops the run. At the exact deadline, expiration wins. A timely accepted decision remains valid for its one occurrence after restart; each new request checks current rights again.

Opaque signer identities stay in the private checkpoint, not the public trace. See the roles and deadlines tutorial.

Runtime Contract​

  • human.approve and human.review are not tools: they do not go through AirTool.id and have no handler.
  • Pauses and resumes are explicit: Touba does not read stdin implicitly and does not invent an interactive prompt.
  • An accepted human decision does not automatically transform a result into a formal proof (Verified).
  • Inline implementations, arbitrary human.* names, and richer human workflow handlers are handled through dedicated interface surfaces.

Resume and External Effects​

A typed agent called from a source workflow keeps its own inputs separate from its caller's values across resume. Identically named parameters cannot replace the caller's values; only the returned result is bound by into. This remains true across multiple pauses. An agent-call checkpoint missing either scope is refused before invoking the provider. Checkpoints can contain business data: protect their storage; they are not public traces.

--human-decisions injects decisions at the start of a run. Each decision authorizes one occurrence, not future loop iterations or later invocations of the same approval statement. The AIR approval reference remains static; a new encounter requires a new decision for the current checkpoint. This path serves deterministic scenarios. To resume after an external effect has already been produced, use a resume checkpoint.

For an explicit human_approval node or an agentic request_human_review in a sequential stack of source calls and agent-selected capabilities, Touba can write a resume checkpoint:

Terminal
touba run program.air.json \
--human-checkpoint-out human.checkpoint.json \
--output trace

Resume then consumes that checkpoint and an explicit human decision:

Terminal
touba run program.air.json \
--human-resume-checkpoint human.checkpoint.json \
--human-decisions-json '{"approval:example":"approved"}' \
--output trace

This checkpoint avoids redispatching already-produced effects in this bounded case. Without a checkpoint, a human pause after external dispatch is refused with HumanApprovalPauseRequiresDurableResume.

Human checkpoints also preserve the source-call stack of skills and strategies, including separate values, types and loop cursors for each invocation. An approval inside a subworkflow, or a review requested by an agent called from that chain, resumes at its saved position and returns through its callers. Completed calls are not replayed; budgets, consumed active time and effect IDs remain run-wide. Errors after resume follow the declared failure paths. Invocation depth is bounded to 64 calls.

The entire stack is validated before any new effect. Only the current V1 format is accepted, including call_stack and every required scope; incomplete older states are not converted. Resolved secrets are refused in ancestor scopes too. A local checkpoint is trusted host state, not a signed attestation.

The stack also preserves agent-selected capabilities, including a subagent requesting review or calling a subworkflow. After the human decision, Touba finishes the pending call, adds its result to the caller's observations and resumes that loop at the next step. It does not ask the model to repeat an already made selection. A single host-selected capability remains a call/return, without adding a model loop. Earlier step ceilings remain effective; a human decision is not reused across invocations.

Human approvals are also supported inside parallel groups: each wait belongs to one branch occurrence. The group suspends new starts and drains in-flight calls to serializable boundaries. Its checkpoint preserves completed branches, continuations, budgets and consumed decisions. A decision does not authorize other branches. Completed branches are not dispatched again after resume.

The recovery guide distinguishes human continuations, parallel progress checkpoints and the sequential durable-resume profile. None guarantees crash recovery at an arbitrary instruction or universal exactly-once external effects.