Skip to main content
Version: 1.0.0

Project server API

touba serve connects an application to the Touba runtime over HTTP and SSE. The application authenticates people and presents their cases; the runtime retains authority over capabilities and effects.

Finish a session and archive state​

ActionRoute
Request operation cancellationPOST /interactions/sessions/{session_id}/operations/{operation_id}/cancel
Archive a completed sessionPOST /interactions/sessions/{session_id}/archive

Both calls accept an empty JSON body. Cancellation is cooperative: read the operation until it reaches a terminal state. Session archival retains its decisions and evidence.

To start with fresh server state during development, stop the server, resolve pending approvals and drain trigger queues, then run:

touba serve --check-state
touba serve --archive-state
touba serve --archive-state --apply

The preview lists files and sizes. --apply moves state into .touba/state-archives/<id>/, with an inventory and file hashes. Sources, packages, SDKs, manifests and secrets stay in place. This starts a fresh effect journal: use it with controlled development data. Keep existing state when continuing business operations.

Restoration refuses to overwrite current state:

touba serve --restore-state <id>
touba serve --restore-state <id> --apply

If archival is interrupted, the server waits for restoration. Use the build matching the restored state. Custom state paths require their own operational backup procedure.

Decision identifiers and comment​

client_ref identifies the submitting application. evidence_ref identifies the evidence read by the attestation adapter. Both may appear in a decision. Approval.comment exposes an Optional of Text, limited to 4,096 characters. It is user-entered text; subject_ref and attested_roles come from the validated attestation.

Prepare and start​

From the project root:

touba prepare --profile dev --mode write
touba prepare --profile dev --mode check
touba serve --project-root . --host 127.0.0.1 --port 8791 \
--auth-token-env TOUBA_SERVER_TOKEN \
--identity project-identity.json \
--agent-decision-provider providers/decision.mjs

Set a strong TOUBA_SERVER_TOKEN in the host environment. Never commit it or send it to a browser. Omit --identity when your --human-approval-host adapter attests approvals from the application directory, or when there are no human decisions. Omit the provider when no agent decision is needed. A provider module is trusted host code, not client-supplied code.

The server consumes .touba/build/dev/ directly and verifies input digests before execution. No AIR copy is needed. After editing, run prepare again, then POST /project/refresh. Stale preparation is refused. This development sandbox is not production activation. Without a preparation, explicitly compiled project AIR files remain supported.

Provider options also include --agent-decision-provider-metadata-json, --agent-decision-provider-content-mode inline|opaque_refs, --agent-decision-provider-initial-mode summary|capabilities, --agent-decision-provider-timeout-ms and --agent-decision-provider-max-retries. See the SDK guide. Tool secrets come from touba.secrets.dev.json or an explicit --secrets profile.

If you maintain an explicit manifest (including trigger activation without tools), pass --manifest touba.manifest.json to both prepare --mode write and prepare --mode check. Otherwise preparation uses its derived manifest, not an implicitly selected root file.

The --identity file must stay inside --project-root; relative paths resolve from that root. Outside paths fail with ToubaServeProductIdentityPathInvalid, detailed under ToubaServePreflightFailed. Keep this host configuration inside the project without committing secrets or personal identifiers.

Project entrypoints and hosts​

Declare the workflows the application may start in touba.project.json:

{
"entry": "strategy:app.expenses/ReviewExpense",
"entrypoints": ["strategy:app.expenses/Reconcile"]
}

entry selects the default workflow; entrypoints adds other workflows. After a change, run touba prepare --mode write. Select a workflow with touba run --project . --entry strategy:app.expenses/Reconcile. The server publishes these entries in GET /project and checks the same list before execution and resume. Internal agents are called by their authorized strategies. Keep internal write tools off this list when a strategy owns their approval procedure.

Both --agent-decision-provider and --human-approval-host accept JavaScript (.mjs) or Python (.py) modules. Install the Python SDK with touba sdk install python; the server uses the .venv interpreter. A module selected for both roles is loaded once. Its Python process closes when the server stops.

Exported roleJavaScriptPython
Agent decisiondecideAgentCapabilitydecide_agent_capability
Human attestationattestHumanApprovalattest_human_approval
Fixed initiatorinitiatorRefinitiator_ref
Attested per-run initiatorresolveInitiatorresolve_initiator
Initial mode by agentinitialModesinitial_modes

The initiator resolver receives run_id, entry_ref and inputs. It verifies application evidence before returning the initiator's opaque identifier. Resume preserves that identity. The client-supplied requested_by field is descriptive, not identity evidence.

With an approval host, the application owns its directory and authenticates its users. The server passes { request, decision, evidenceRef, hostDecision } to the adapter. The POST may include an opaque evidence_ref; otherwise evidenceRef uses request_id. The adapter verifies the request, signer and current rights. The attested signer must match user_ref and decided_by.

The public request exposes policy: role, timeout and signer separation. Accepted decisions retain their receipt. Removing the adapter after restart does not remove the attestation requirement. The --identity workflow remains available without an adapter; agent-initiated reviews retain this check.

Execution routes​

Application requests carry Authorization: Bearer <token>; JSON POSTs carry Content-Type: application/json. Contract names below are exact schema_version values, not software distribution versions.

Method and routeRequest contract or purpose
GET /healthprocess availability
GET /projectinspection, entrypoints and executable signatures
POST /project/refreshtouba-project-refresh-request-v1
GET /identityproject users, devices and assignments
POST /runs/sandboxtouba-sandbox-run-request-v1, execution_kind: "entrypoint"
POST /triggers/firesame contract, execution_kind: "trigger"
GET /interactions/sessionsbounded session index
POST /interactions/sessionstouba-interaction-session-create-v1
GET /interactions/sessions/<session_id>messages, results and operations
POST /interactions/sessions/<session_id>/messagestouba-interaction-message-request-v1; returns 202 with an operation
GET /interactions/sessions/<session_id>/operations/<operation_id>operation status
GET /approvals/pending/current?run_id=<run_id>touba-approval-request-v1
GET /reviews/pending/current?run_id=<run_id>touba-human-review-request-v1
POST /decisionstouba-approval-decision-v1 or touba-human-review-decision-v1
POST /runs/sandbox/resume{ "request_id": "..." }
POST /interactions/sessions/<session_id>/resumetouba-interaction-resume-request-v1 with request_id
GET /eventsSSE; Last-Event-ID resumes within available retention

The two execution routes share a schema but require different discriminants. Sending a trigger to /runs/sandbox is refused. For a trigger, supply the event's event_id, source_kind and inputs (normalized target arguments) as required by the request contract and declared trigger.

runtime_only refuses external effects. Explicitly choose allow_declared_external_effects for workflows that require them. This setting creates no grant and bypasses no approval.

Session, approval, decision, resume​

  1. Create a session with target_ref, target_kind: "strategy", presentation: "workflow", effect_policy and requested_by.
  2. Submit a message with a unique message_id, display_text, inputs and requested_by. Retain session_id, operation_id and run_id.
  3. Follow the operation with SSE or its GET route. At awaiting_human, fetch the request using that operation's run_id, not the global latest run.
  4. Present summary, allowed_decisions and expires_at. Copy the exact correlations from the request into the decision:
{
"schema_version": "touba-approval-decision-v1",
"request_id": "request-id-returned-by-server",
"run_id": "run-id-returned-by-server",
"trace_ref": "trace-reference-returned-by-server",
"user_ref": "user:reviewer",
"device_ref": "device:backend-reviewer",
"decision": "approve",
"decided_by": "user:reviewer",
"decided_at": "2026-10-15T12:00:00.000Z",
"comment": "Receipt and supplier verified.",
"client_ref": "expense-backend"
}
  1. After decision acceptance (202), resume the session with its request_id. Acceptance is not execution of an effect. Read the terminal result, including after rejection or expiry.

The summary contains only the explicit human.approve context, evaluated at the pause and limited to 4,096 characters. Select a useful, minimal dossier; truncated text is not a complete dossier. Common sensitive record fields are redacted and resolved secret values are forbidden in checkpoints. This is not universal personal-data detection: never place secrets in free text. Earlier show messages and unrelated inputs are not copied. Private context stays out of traces and SSE events.

Configure decision identities​

--identity loads a host-managed touba-product-identity-v1 file. This is neither a signup endpoint nor authentication. A minimal local configuration for a trusted backend is:

project-identity.json
{
"schema_version": "touba-product-identity-v1",
"generated_at": "2026-10-15T12:00:00.000Z",
"summary": {
"user_count": 1, "team_count": 0, "device_count": 1,
"active_assignment_count": 0, "revoked_device_count": 0
},
"users": [{
"user_ref": "user:reviewer", "display_name": "Expense reviewer",
"email": null, "status": "active", "team_refs": [],
"role_refs": ["role:reviewer"]
}],
"teams": [],
"roles": [{
"role_ref": "role:reviewer", "name": "Reviewer", "scope": "human_decision"
}],
"devices": [{
"device_ref": "device:backend-reviewer", "user_ref": "user:reviewer",
"kind": "service", "status": "paired",
"paired_at": "2026-10-15T12:00:00.000Z", "revoked_at": null
}],
"sessions": [], "assignments": [], "escalation_policies": [], "events": []
}

The user must be active and the device paired to that user. The human_decision role is project-wide. To restrict a reviewer to a case, use an active assignment correlated with request_id, run_id, trace_ref, user_ref and device_ref, without also giving that user the global role. Do not bypass checks to work around an expired assignment or revoked device.

Assignment authority requires an exact request/run/trace, user and device match, an active status, and assigned_at <= server time < expires_at when accepting the decision. Equality with the deadline rejects the assignment. A global role remains separate authority; remove it when assignment-only access is intended. To require distinct signers, declare distinct_from as "previous" on the approval and configure an attestation host. "initiator" excludes the run's attested initiator; exclude_signers names a cross-run exclusion rule verified by that host.

The shared Bearer token authenticates the backend, not each person. For multi-user clients, configure --identity-control-plane-url and --identity-service-token-env, then forward the human token in x-touba-identity-token. The server binds the actor to the introspected user. Request reads require decisions.read; decisions require decisions.act, in addition to device and assignment checks. Remote identity services require HTTPS. The application remains responsible for real authentication and business separation of duties.

Authority graph and available evidence​

GET /authority/graph identifies each compiled approval site separately. Two approvals in one strategy remain two nodes; source scanning does not duplicate them.

GET /runs/current returns a summary and GET /trace/current a minimized timeline. For native evidence, use GET /runs/<run_id>/trace: each initial execution and continuation retains its own segment, original events and AIR commitment, without renumbering.

The touba-run-trace-export-v1 export contains segments and complete. Each segment carries its state (recording, paused, terminal, unavailable), digest and predecessor digest. complete: true means the initial execution and all segments are available and the run has terminated, not that its business task succeeded. After a process crash, a segment may remain recording with trace: null and reason: "not_completed"; missing evidence is never reconstructed. Historical runs without native evidence return 404.

The project token is required when configured, plus executions.read when identity control is enabled. No AIR, source, manifest, business message or private checkpoint is automatically discovered or included. For local audit, extract each available segment's trace and explicitly supply the AIR matching its run.air_digest. Local digests are not signatures.

Traces use .touba/traces.sqlite (512 MiB of serialized content by default), with a separate metadata index limited to 128 MiB of SQLite pages and 256 segments per run. The index quota counts bytes, including metadata, without an arbitrary run-count ceiling. The server streams complete exports. Its preview retains the last 200 events and the full count.

Use GET /runs/<run_id>/trace?index=true for the touba-native-trace-index-v1 segment index, then /runs/<run_id>/trace/segments/0?limit=100 for a page. Pass the returned next as after until done: true. The same project permissions apply to both routes; mobile-only tokens cannot access them.

Archive the complete project state with the server stopped to preserve the links between traces, checkpoints and effects. A full journal stops progress before the next effect. No automatic deletion is enabled.

Startup and offline state checks validate retained evidence in batches of 100 records, capped at 16 MiB. Every batch is checked; historical corruption prevents recovery. This audit takes longer as history grows, while a current decision selects its records through indexed identifiers. Interaction recovery selects operations in the interface window and reads their checkpoints in pages. Older evidence remains stored and checked by the audit. These internal scans follow physical storage order; API cursors keep their identifier order. The validation cache keeps at most 16,384 SHA-256 fingerprints per validator, without retaining documents. Each read parses the current JSON text; a known fingerprint allows reuse of its pure validation. Authority checks still run. Each write validates the object before serialization and checks that the records read have not changed.

Operations and concurrency​

Sandbox results and session messages carry show output in messages, separately from the result returned by give. A client can display these messages in order, including before a human pause. A continuation adds only its new messages; retrying the same resume request does not duplicate recorded output.

This projection retains at most 100 messages, 4 KiB of text per message and 16 KiB total, without splitting UTF-8 characters. messages_truncated signals any shortening or omission. Content remains private business data exposed through authorized result/session routes, never the minimized timeline or progress SSE notifications. It is not a native audit trace.

The V1 development contract requires these fields in new results. An older incomplete human-wait result is refused without rewriting its file: retain the data and qualify migration before replacing a server with pending waits.

Keep correlations per operation, use SSE, and recover the existing operation after a disconnect before starting a fresh run. Interaction history is bounded to 100 sessions, 100 messages and 50 operations per session. Durable waits and decisions have a separate lifecycle; do not delete their store to free history.

Checkpoints, human requests and decisions, effect journals, sessions and asset metadata in touba serve use private SQLite stores under .touba: sandbox-checkpoints.sqlite, human-requests.sqlite, human-decisions.sqlite, project.effect-journal.sqlite, interaction-sessions.sqlite and interaction-assets.sqlite. A targeted operation reads and validates its records, then writes them in a short transaction with revision checks. Permissions and deadlines are never cached. Structural validation of strictly identical content may be reused in a bounded cache, without retaining an invocation's authorization.

Startup retains an exhaustive audit whose cost grows with history. The decision-history API still returns the complete history; it is not paginated. The 100-session interaction window remains a bounded projection: replacing inactive entries does not delete checkpoints, decisions or effects. Durable writes are synchronized to disk; measure their cost with your handlers and volumes. This local storage does not qualify multiple servers sharing a disk and does not promise fixed latency.

Synchronous progress events are batched before persistence and SSE notification. The counter retains every event and the window retains the latest 80 events. A crash may lose the final progress batch: this UI projection is not evidence of an effect. Checkpoints and the effect journal retain separate persistence.

There is no implicit one-run-per-second quota. Measure HTTP admission, handler execution, state reads and client refresh delay separately. Handlers and storage can limit throughput. Combine the Touba effect journal with service-side idempotency keys and reconciliation when needed: this is not a universal exactly-once guarantee.

Operations, backups and build changes​

touba serve --check-state also checks links between human checkpoints and interaction sessions, using private copies. An orphan link not allowed by the retention rules is refused, just as at startup. Preflight never repairs or modifies the original files.

Keep touba version with the exact CLI archive, SDK and configurations. Two development builds labeled 1.0.0 are not separate public releases; compare their build identities and documented changes before replacement.

For a coherent backup:

  1. Stop new admissions and trigger producers, wait for active handlers to finish, then stop the server. A persisted human wait can be backed up; do not delete it.
  2. Back up sources, handlers, dependencies, manifest, configurations, locks and the complete .touba directory together. Include explicitly configured state paths outside that directory. Effect journals, checkpoints, identities, decisions, sessions and native traces form a correlated set. Protect and encrypt copies containing secrets or business data, preserving access rights.
  3. Restore in isolation first, using the same toolchain and the same paths for absolute configuration references. Check waits, deadlines, native evidence and effect journals before enabling effects. A backup does not pause time.

The qualified restoration preserves a wait's identifier and native evidence, resumes after a decision and does not redispatch its effect on a second resume. This assumes a coherent snapshot and no unreconciled external effect since that snapshot. Restoring local state cannot restore an external database, payment or email: reconcile external records before resuming.

Development-state compatibility​

The current server refuses previous JSON stores, including empty stores. If an old default JSON store remains beside its configured SQLite store, startup is refused before recovery or execution. Keep a coherent stopped copy with its original build; do not rename JSON to SQLite or erase history to force startup. The separate touba run --human-decisions JSON input is unchanged.

Persisted results now require messages and messages_truncated. Incomplete historical results are refused without rewriting them. Do not invent an empty history or delete the store to start the server. Test a stopped copy of state with the new installation before upgrading. If incompatible, retain the old installation in isolation and finish its waits with its own state and valid decisions.

After reconciling operations and effects, retain that environment as a protected archive. Prepare a new instance from explicitly reviewed sources and host decisions, without importing incompatible state. Historical identifiers, decisions and evidence remain attached to the archive; they are not merged or presented as native history of the new instance. No automatic historical-state converter is provided. Unresolved waits block this transfer.

Operational boundaries​

NeedEngine/CLIProject server
Human waitexplicit host-managed checkpoint/resumepersisted wait and resume correlated with an authorized decision
DeadlineEngine does not provide an automatic expiry timerfixed 24 hours after creation, enforced against server time
Decision identityhost application's responsibilityhost attestation, or local identity/device and role/assignment checks
Native tracerun.trace or --trace-outGET /runs/<run_id>/trace segments with explicit completeness

OpenTelemetry export is a projection, not a replacement for native audit evidence. File export is available; native collector delivery is not provided. See trace audit and human-wait expiry.

See SDK, triggers, idempotent effects and agents and outbound effects.

Before changing builds, use the state preflight and preserved build.

Approval capacity​

touba serve --project-root . --max-pending-approvals 1000

The default is 100 and the maximum is 10,000. Each wait reserves capacity before pausing. At capacity, a new wait is refused before its continuation. Lowering the limit after restart preserves existing waits and still allows them to be processed. Choose the limit using measurements of your host and workload.

Trace events carry observed_at, the UTC instant observed by the runtime. sequence_index remains the ordering reference, including when the system clock changes.