# Trigora documentation > Complete documentation corpus. ## Build durable programs with Trigora https://trigora.dev/docs/index/ Choose Trigora Cloud, Trigora Pulse, or standalone TCC Engine. Trigora runs long-lived Programs that suspend at durable boundaries and resume from a committed continuation. Recovery restores the continuation directly instead of replaying the completed portion of the Program. - [Start with Trigora Cloud](https://trigora.dev/docs/quickstart/): Deploy and run Programs on the managed runtime. - [Run Trigora Pulse](https://trigora.dev/docs/pulse/overview/): Run Programs on your machines. Trigora stores the checkpoint. - [Embed TCC Engine](https://trigora.dev/docs/engine/overview/): Bring your own storage, scheduler, and effects. - [Learn how TCC works](https://trigora.dev/docs/concepts/recovery/): Read the execution model: continuations, durable boundaries, and recovery without history replay. **Supported authoring languages:** TypeScript, Python, and Rust. TCC’s execution model is language-independent, and support for additional authoring languages is planned. Constructs outside the supported language semantics fail at compile time. Use `@trigora/sdk` to author Programs for Trigora. To embed TCC Engine in your own infrastructure, use its published npm, PyPI, or Cargo packages. These are separate APIs; see [TCC Engine overview](https://trigora.dev/docs/engine/overview/) for the available packages and integration model. The [quickstart](https://trigora.dev/docs/quickstart/) takes you from an empty directory to an Execution that suspends and resumes when it receives an event. ## Quickstart https://trigora.dev/docs/quickstart/ Deploy a Program, suspend it on an event, then resume it. In this guide, you will author and deploy a Program, start an Execution, suspend it on an event, and resume it by sending that event. ## Install ```bash npm install @trigora/sdk @trigora/client trigora ``` The npm `trigora` package provides a Node launcher for the Rust CLI. Node.js 22 and 24 are supported for the npm CLI launcher and TypeScript adapter. Python-only and Rust-only projects do not require Node.js. Installation for those languages is on [Install](https://trigora.dev/docs/getting-started/install/). Local recovery is `trigora-local` writing `.trigora/state.db`. See the runtime chain in the [CLI reference](https://trigora.dev/docs/reference/cli/). ## Write a Program ```ts import { effect, waitForEvent } from "@trigora/sdk"; export default async function approval() { const result = await effect("generate", async () => { return 42; }); const review = await waitForEvent("approved"); return { result, review }; } ``` `effect` is the durable boundary for external work. `waitForEvent` suspends the execution until something sends `approved`. Waiting does not hold compute. This hello-world stays sequential. `Promise.all`, `gather`, and `join`, and `Promise.race` and `race`, are on [Structured concurrency](https://trigora.dev/docs/authoring/structured-concurrency/). Point [`trigora.toml`](https://trigora.dev/docs/client/config/) at the file: ```toml [project] name = "approval" programs = ["src/**/*.ts"] ``` The Program id is `approval`, taken from the named default export. Because the Program has no parameters, this example does not provide start input. To receive start input, declare parameters. Those values are an ordered argument list: ```ts export default async function research(topic, limit) { return { topic, limit }; } ``` ```bash trigora start research --input '["tcc", 10]' ``` See [Programs](https://trigora.dev/docs/concepts/programs/). ## Deploy Create an API token in [Trigora Cloud](https://cloud.trigora.dev) and set it. There is no `trigora login` command. ```bash export TRIGORA_TOKEN=... trigora deploy ``` The CLI also reads `TRIGORA_TOKEN` from a `.env` file in the project. `trigora init` writes `.env.example` with the variable commented out. ## Run it ```bash trigora start approval trigora send approved trigora executions inspect ``` `start` prints the execution id. After `start`, the execution runs the effect, commits that outcome, and waits. `send` delivers `approved`. The execution resumes from the wait and returns `{ result, review }`. It does not run `generate` again. **Local, without Cloud:** `trigora init` then `trigora dev`. Leave that process running. `start` and `send` target that local runtime by default. Kill `trigora dev` while the execution is waiting, start it again, then `send` — the wait restores from local SQLite. Next: [Your first Program](https://trigora.dev/docs/getting-started/first-program/) · [Structured concurrency](https://trigora.dev/docs/authoring/structured-concurrency/) · [Events](https://trigora.dev/docs/cloud/events/) · [How recovery works](https://trigora.dev/docs/concepts/recovery/) ## Agent quickstart https://trigora.dev/docs/agent-quickstart/ Instructions a coding agent needs to add Trigora durable execution to an existing codebase. This page is written for coding agents such as Cursor, Claude Code and Codex, and for the people who supervise them. It covers what an agent must know to add Trigora to an existing project without breaking recovery: where checkpoints happen, which language forms compile, how to treat side effects, what recovery does not promise, and how to verify it with `trigora verify`. ## The prompt Paste this into your coding agent. Every docs page also has a **Copy page for your agent** button under its title. ```md title="Prompt for your coding agent" wrap Add Trigora durable execution to this codebase. 1. Read https://trigora.dev/docs/agent-quickstart/ and https://trigora.dev/llms.txt, then https://trigora.dev/docs/quickstart/ and https://trigora.dev/docs/authoring/supported-semantics/. 2. Inspect the project and choose the matching SDK: TypeScript (@trigora/sdk, Node.js 22 or 24), Python or Rust. Python-only and Rust-only projects do not require Node.js. 3. Write one small Program for an existing long-running task. Put every model, tool and network call inside effect("", fn). Effect names and event names must be string literals. 4. Keep durable calls (effect, invoke, waitForEvent, sleep) in the program entry, not in helper functions or closures. Use Promise.all, gather or join only in the forms the structured concurrency docs allow. 5. effect() is not exactly-once for external systems: a crash before the outcome commits can run it again. Pass an idempotency key to any API that charges, sends or writes. 6. Run `trigora verify --faults all --effects fixtures.json`, with a fixture result for every effect key. It crashes the Program after each committed boundary, resumes it, and exits non-zero if the result differs or an effect runs twice or goes missing. Fix any failure before moving on. Then run it with `trigora dev`, kill the process while the execution waits, restart it, and confirm that nothing before the wait runs again. 7. Ask me before choosing Trigora Cloud or Trigora Pulse, before creating a TRIGORA_TOKEN and before running `trigora deploy`. Pulse v1.0.0 is published as ghcr.io/trigora-dev/pulse:1.0.0. It runs the Node adapter on Node.js 22 or 24. `trigora pulse start` starts that host; `trigora start` does not. There is no Python or Rust Pulse adapter. Never print, log or commit secrets. 8. Follow this project's conventions, and tell me what configuration I need to provide. ``` Machine-readable sources: [`/llms.txt`](https://trigora.dev/llms.txt) is the index, [`/llms-full.txt`](https://trigora.dev/llms-full.txt) is the full documentation corpus, and [`/skills/trigora/SKILL.md`](https://trigora.dev/skills/trigora/SKILL.md) is the same guidance as an agent skill file. ## Checkpoint boundaries A durable boundary is a durable operation: `effect`, `waitForEvent` / `wait_for_event`, `sleep`, or `invoke`, and in Rust also `join` and `race`. At each one the host can commit the Program's control position and the values it needs to continue. Ordinary assignments between them do not create separate commits. See [Durable boundaries](https://trigora.dev/docs/concepts/durable-boundaries/). - Durable operations are explicit imports. A local function with the same name is not a durable operation. - Durable operations stay in the program entry. A helper or closure frame cannot perform one; a closure value may live across a wait. - Effect keys and event names are string literals. `effect(fn)` without a name is rejected. - Keep each effect name stable. A name built from data that changes across a retry creates a second effect instead of a reuse. See [Effect identity](https://trigora.dev/docs/concepts/effect-identity/). ## Supported runtime behaviour Each language is a declared subset. Ordinary computation between durable operations is supported; constructs outside the profile fail at compile time during `trigora dev` or deployment, and never run under another recovery model. - Read [Supported language semantics](https://trigora.dev/docs/authoring/supported-semantics/) and [Unsupported constructs](https://trigora.dev/docs/authoring/unsupported/) before restructuring code. - `Promise.all`, `Promise.race`, `gather`, `race`, and Rust `join` / `race` compile only in the forms on [Structured concurrency](https://trigora.dev/docs/authoring/structured-concurrency/). `Promise.any`, `Promise.allSettled` and stored promises do not. - Node.js 22 and 24 are supported for the npm CLI launcher and TypeScript adapter. Python-only and Rust-only projects do not require Node.js. See [Install](https://trigora.dev/docs/getting-started/install/). ## Side effects and idempotency After an effect outcome commits, recovery returns the stored value and does not run the function again. If the process dies after the function started and before the outcome committed, it can run again, and the external system sees that. - Put every model, tool and network call inside a named `effect`. - For APIs that charge, send or write, pass an idempotency key and keep it in a system that survives retries. Trigora does not provide exactly-once delivery to external systems. - `trigora cancel` stops an Execution. It does not undo a committed effect; compensate in your own system if you need to. See [Production considerations](https://trigora.dev/docs/concepts/production/). ## Recovery limits Recovery restores a continuation instead of replaying the completed prefix. It does not eliminate failures in the systems your effects call, and it is not a general latency guarantee. - Recovery cost scales with live state; see the [research limitations](https://trigora.dev/research#limitations). - Waiting Executions stay on the artifact they started with. Deploying a new version does not migrate them. See [Artifact pinning](https://trigora.dev/docs/concepts/artifact-pinning/). - Inputs, event payloads and results are stored as JSON on the Execution. Keep them small, and never put credentials in them. ## Verify recovery `trigora verify` is the fastest proof that a Program recovers. It records a healthy baseline run, then crashes the Program after committed boundaries, resumes it, and checks the result against the baseline. It runs in-process on a temporary SQLite host: it does not start `trigora dev` or contact Trigora Cloud or Pulse. ```bash trigora verify --faults all --effects fixtures.json trigora verify --faults all --effects fixtures.json --out verify.json ``` - A pass means every tested fault point recovered, the terminal status and result matched the baseline, and no completed effect was repeated or dropped. The verdict is one line, `✔ Recovery verified at every checkpoint`. - The command exits non-zero when a recovery fails, the result differs, an effect runs twice or an effect is missing, so it works as a CI gate. - `--faults sample`, the default, checks up to 8 boundaries. `--faults all` checks every committed boundary. - A Program with effects is refused without `--effects`. Prefer a fixtures file, a JSON object of effect key to result, so no real API is called. `--effects live` runs the real handlers during the baseline; ask before using it on calls that charge, send or write. - `--events` supplies event payloads; an awaited event with no queued payload receives `"ok"`. Sleeps are fast-forwarded. `--input` is the same argument vector as `trigora start`. `trigora bench ` profiles one healthy run: boundaries committed, checkpoint time, continuation size and restore latency. See [Benchmark and verify](https://trigora.dev/docs/testing/bench-and-verify/). ## Pulse [Pulse v1.0.0](https://github.com/trigora-dev/trigora/releases/tag/pulse-v1.0.0) is the published host, `ghcr.io/trigora-dev/pulse:1.0.0`, for Linux AMD64 and ARM64. You run the process. Trigora stores the checkpoint. - The Pulse adapter is Node.js 22 or 24. TypeScript authoring elsewhere does not add a Python or Rust Pulse adapter. - `trigora pulse start` compiles the project and starts the host and adapter. It requires `TRIGORA_TOKEN` on the host unless `--ephemeral` is set. It does not create an execution. - `trigora start` targets local `trigora dev` or, with `--remote`, Trigora Cloud. It is not the Pulse start path. - A waiting execution stays on the artifact hash it started with. Keep that artifact running until those executions finish. See [Versions](https://trigora.dev/docs/pulse/versions/). ## Prove it with trigora dev ```bash trigora init trigora dev ``` Leave `trigora dev` running and, from another shell, start the Program. While it waits, kill `trigora dev`, start it again, then send the event. The wait restores from local SQLite and nothing before it runs again. ```bash trigora start trigora send trigora executions inspect ``` **Ask before deploying:** An agent should ask before choosing Trigora Cloud or Trigora Pulse, before creating a `TRIGORA_TOKEN` and before running `trigora deploy`. Store the token in the environment or a `.env` file; never print, log or commit it. See [API tokens](https://trigora.dev/docs/cloud/api-tokens/). Next: [Quickstart](https://trigora.dev/docs/quickstart/) · [Benchmark and verify](https://trigora.dev/docs/testing/bench-and-verify/) · [Durable operations](https://trigora.dev/docs/authoring/durable-operations/) · [How recovery works](https://trigora.dev/docs/concepts/recovery/) ## Install https://trigora.dev/docs/getting-started/install/ Install the Trigora SDK, client, and CLI. ## TypeScript ```bash npm install @trigora/sdk @trigora/client trigora ``` | Package | Role | |---|---| | `@trigora/sdk` | Author Programs: `effect`, `waitForEvent`, `sleep`, `invoke` | | `@trigora/client` | Start, send, cancel, and read executions on Cloud or local `trigora dev` | | `trigora` | CLI: `init`, `dev`, `deploy`, `pulse`, `start`, `send`, `cancel`, `whoami` | The npm `trigora` package provides a Node launcher for the Rust CLI. Node.js 22 and 24 are supported for the npm CLI launcher and TypeScript adapter. Python-only and Rust-only projects do not require Node.js. Runtime CLI commands target `trigora dev` by default. Add `--remote` to target Trigora Cloud. `trigora deploy` and `trigora whoami` are Cloud-only and need `TRIGORA_TOKEN`. These packages do not embed TCC Engine. Published engine packages are on [TCC Engine overview](https://trigora.dev/docs/engine/overview/). ## Python Authoring is the `trigora` package in [trigora-python](https://github.com/trigora-dev/trigora-python): `program`, `effect`, `wait_for_event`, `sleep`, `invoke`, `gather`, and `race`. HTTP is `trigora-client`, imported as `trigora_client`. ```bash pip install trigora trigora-client ``` `trigora` depends on `trigora-cli`, which provides the `trigora` command. A Python-only project does not require Node. The Python authoring package installs the compatible TCC compiler automatically. The CLI uses that compiler when it builds Python Programs. `trigora-client` does not install the compiler. The Python client uses Cloud when a token is set; otherwise it uses `TRIGORA_RUNTIME_URL` or `http://127.0.0.1:3477`. ## Rust The crates live in [trigora-rust](https://github.com/trigora-dev/trigora-rust). ```bash cargo add trigora trigora-client cargo install trigora-cli ``` | Crate | Role | |---|---| | `trigora` | Author Programs: `effect`, `sleep`, `invoke`, `join`, `race`, `wait_for_event`. The entry is `pub async fn main`. | | `trigora-client` | Start, send, cancel, and read executions on Cloud or local `trigora dev` | | `trigora-cli` | Installs the `trigora`, `trigora-local`, and `tcc-rust-compile` binaries | `cargo add trigora` does not install a command. A Rust-only project does not require Node. The Rust client uses Cloud when a token is set; otherwise it uses `TRIGORA_RUNTIME_URL` or `http://127.0.0.1:3477`. See [Rust](https://trigora.dev/docs/authoring/rust/). ## Node Node.js 22 and 24 are supported for the npm CLI launcher and TypeScript adapter. That adapter compiles Programs and runs TypeScript and JavaScript effects. Python-only and Rust-only projects do not require Node.js. ## Trigora packages and TCC Engine Programs use the Trigora authoring packages listed above, not TCC Engine packages. The packages `@tcc-engine/host-node` and `@tcc-engine/frontend-rust` are not published. See [TCC Engine overview](https://trigora.dev/docs/engine/overview/) for the packages available to engine embedders. ## Your first Program https://trigora.dev/docs/getting-started/first-program/ A default-export Program with one effect and one wait. A Program is one durable function. This one takes no parameter. ```ts import { effect, waitForEvent } from "@trigora/sdk"; export default async function approval() { const result = await effect("generate", () => 42); const review = await waitForEvent("approved"); return { result, review }; } ``` Put it on the `programs` glob in [`trigora.toml`](https://trigora.dev/docs/client/config/). The export name is the id you pass to `trigora start`. ```bash trigora start approval ``` What happens: 1. Trigora starts an Execution of `approval`. 2. `generate` runs. Its outcome is committed. 3. The Execution waits on `approved`. Compute is released. 4. `trigora send approved` delivers the event. 5. The Execution resumes after the wait and returns both values. If the process dies during the wait, recovery loads the committed continuation and stays at the wait. It does not run `generate` again. Python and Rust use the same structure with language-specific names. The Python Program id is the file stem. A Rust Program id is the source file stem, or the Cargo package name for `lib.rs` and `main.rs`. ```python from trigora import effect, program, wait_for_event @program async def approval(): result = await effect("generate", lambda: 42) review = await wait_for_event("approved") return {"result": result, "review": review} ``` ```rust use trigora::{effect, wait_for_event}; pub struct Approval { pub result: f64, pub review: bool, } pub async fn main() -> Result { let amount: f64 = 42.0; let result = effect("generate", move || amount).await?; let review: bool = wait_for_event("approved").await?; Ok(Approval { result, review }) } ``` Calling the function yourself fails. `trigora start` targets `trigora dev` by default. Add `--remote` to start on Trigora Cloud. A language client uses Cloud when a token is set, and the local runtime otherwise. Start input is an ordered argument list: ```ts export default async function research(topic, limit = 10) { return { topic, limit }; } ``` ```bash trigora start research --input '["tcc"]' ``` Python is one `@program` async function. Rust is one `pub async fn main`. See [Programs](https://trigora.dev/docs/concepts/programs/). Structured concurrency is a separate page: [Structured concurrency](https://trigora.dev/docs/authoring/structured-concurrency/). ## Projects https://trigora.dev/docs/cloud/projects/ A project is the namespace inside a workspace. A workspace is the tenant: members, billing, and API tokens. Owners and admins invite people by email. The invited address must match a verified account. Roles are owner, admin, and member. A project is the namespace inside that workspace for Programs and Executions. Every workspace has a `default` project. Clients and the CLI use it when you omit a project. Pass `projectId` to `createClient`, or send `X-Trigora-Project-Id`, to target another project. ```ts import { createClient } from "@trigora/client"; const trigora = createClient({ token: process.env.TRIGORA_TOKEN, projectId: "proj_...", }); const { projects } = await trigora.listProjects(); ``` `createProject` creates another project in the workspace the token can access. The dashboard project picker lists projects and can create one. Local `trigora dev` starts with a `default` project. Projects created through the local `/v1/projects` API are persisted in the local SQLite state. ## Secrets https://trigora.dev/docs/cloud/secrets/ Project-scoped credentials for effect execution on Trigora Cloud. Project secrets are credentials for effect code on Trigora Cloud. They are encrypted at rest. The API and dashboard return the name and timestamps. They do not return the value. Set a secret with the CLI. The command always uses `TRIGORA_TOKEN` and the project named in `trigora.toml`. ```bash trigora secrets set OPENAI_API_KEY ``` The value is read from a hidden prompt, or from stdin when piped. It is not a command argument. List and delete print names only. ```bash trigora secrets list trigora secrets delete OPENAI_API_KEY ``` Names look like environment variables, such as `OPENAI_API_KEY`. Names that start with `TRIGORA_` are reserved. Effect code reads the current value from the runtime environment: ```ts await effect("call-openai", async () => { const key = process.env.OPENAI_API_KEY; }); ``` Python effect code reads `os.environ`. Rust effect code reads `std::env::var`. There is no secret SDK. A replace or delete applies on the next effect attempt. An attempt that has already started keeps the environment it started with. Waiting until tomorrow and then resuming uses the values stored at that attempt. Program artifacts stay immutable. Credentials are not pinned to a Program version. A Program deployed before secrets support must be redeployed once before its effects can receive managed secrets. After that deploy, changing a value does not require another deploy. Workspace owners and admins can create, replace, and delete secrets in the dashboard. Members can list names. A workspace API token can also change secrets, because the CLI authenticates with `TRIGORA_TOKEN`. `trigora dev` does not read Cloud secrets. Local effect execution uses the process environment, including a project `.env` file. Do not put credentials in event payloads or Program constants. Those values may be stored with the Execution. See [Production considerations](https://trigora.dev/docs/concepts/production/). ## Programs https://trigora.dev/docs/cloud/programs/ Deploy Program versions to Trigora Cloud. A Cloud Program is the deployed definition. The source is a TypeScript default-export async function, one Python `@program` async function, or Rust `pub async fn main`. `trigora deploy` compiles it locally and uploads the artifact plus the effect bundle. [`trigora.toml`](https://trigora.dev/docs/client/config/) also reconciles that project’s [triggers](https://trigora.dev/docs/cloud/triggers/). A file with no `[[triggers]]` clears them. ```bash export TRIGORA_TOKEN=... trigora deploy trigora deploy --program approval ``` List what the Cloud project has: ```bash trigora programs --remote ``` This command targets `trigora dev` by default. Add `--remote` to target Trigora Cloud. Or with the client: ```ts const { programs } = await trigora.listPrograms(); const program = await trigora.getProgram("approval"); ``` The TypeScript program id is the named default export, or the file stem when the default export is anonymous. Python uses the file stem. Rust Program ids use the source file stem. For `lib.rs` and `main.rs`, the Cargo package name is used instead. Deploys create versions. Executions pin the version they started on. See [Versions](https://trigora.dev/docs/cloud/versions/) and [Artifact pinning](https://trigora.dev/docs/concepts/artifact-pinning/). Effect worker names are private. They are not part of the public Program object. ## Versions https://trigora.dev/docs/cloud/versions/ Each deploy pins an artifact. Executions keep that pin. `trigora deploy` creates a Program version. The version records the artifact hash and engine pin. An Execution stores `programVersionId` and `artifactHash` when it starts. Recovery uses the stored artifact. A later deploy does not rewrite an Execution that is already waiting. If a caller supplies a different artifact on resume, the host rejects it as a mismatch. ```ts const versions = await trigora.listProgramVersions("approval"); ``` HTTP: `GET /v1/programs/:id/versions`. Read [Artifact pinning](https://trigora.dev/docs/concepts/artifact-pinning/) for why the hash is part of recovery, not just a label. ## Executions https://trigora.dev/docs/cloud/executions/ One run of a Program on Trigora Cloud. An Execution is one run. Status is `running`, `waiting`, `completed`, `failed`, or `cancelled`. While it waits, `wait` says why: | `wait.type` | Meaning | |---|---| | `event` | Waiting for `wait.event` | | `timer` | Sleeping until `wait.wakeAt` | | `child` | Waiting for child `wait.executionId` | ```bash trigora start approval --remote trigora executions --remote trigora executions inspect --remote ``` These commands target `trigora dev` by default. Add `--remote` to target Trigora Cloud. `start` accepts optional JSON input. That value is the ordered argument vector. The Execution record also keeps `input`. See [Programs](https://trigora.dev/docs/concepts/programs/). ```bash trigora start approval --remote --input '{"note":"later"}' ``` The continuation lives in the Cloud host. The API mirrors status and the public wait. See [Inspect execution](https://trigora.dev/docs/client/inspect/). ## Triggers https://trigora.dev/docs/cloud/triggers/ Project resources that start a Program. A trigger is not a different kind of Execution. Executions can be started directly through the client, CLI, or API, or automatically through Triggers. A Trigger is a project deployment resource. It is not part of the TCC Program artifact and it does not create a different kind of Execution. `client.start` and `POST /v1/executions` stay as they are. ## Ways to start an Execution | Mechanism | What it does | |---|---| | Direct start | Starts an Execution explicitly | | Webhook trigger | Starts a new Execution from HTTP | | Cron trigger | Starts a new Execution on a schedule | | Event | Resumes an existing waiting Execution | | Timer / `sleep` | Resumes an existing Execution later | A cron trigger is not `sleep`. A webhook trigger is not event delivery. See [Events](https://trigora.dev/docs/cloud/events/) and [Timers](https://trigora.dev/docs/cloud/timers/). ## Direct start ```ts const execution = await client.start("research-agent", { topic: "durable execution", }); ``` This requires no trigger. The JSON object is the Program input. The CLI equivalent for Cloud is `trigora start --remote`. ## Webhook triggers ```toml [[triggers]] name = "github" type = "webhook" program = "github-handler" ``` Deploy assigns a URL: ```text https://hooks.trigora.dev/t_... ``` There is no path and no alias. `t_…` is the public id. It is not written into `trigora.toml`. A webhook trigger has no `input` field. The JSON body is the program input. ```http POST /t_... Content-Type: application/json { "action": "opened" } ``` That JSON is the Program input. The platform does not wait for the Program result. | Response | When | |---|---| | `202` `{ "executionId" }` | The body is valid JSON | | `400` | The body is not JSON, or it is larger than 1 MiB | | `405` | The method is not POST | | `404` | The public id is unknown | The request body is not logged. ## Cron triggers ```toml [[triggers]] name = "nightly-report" type = "cron" program = "report" schedule = "0 2 * * *" timezone = "UTC" [triggers.input] period = "daily" ``` `schedule` is a 5-field cron expression. `timezone` defaults to `UTC` when you omit it. `[triggers.input]` is optional static JSON. Nested tables and arrays are allowed. TOML datetimes are not. Each tick starts a new Execution. Overlap is allowed: a tick does not wait for an earlier Execution to finish. If a start fails, that tick stays due and the next minute retries it. The retry key is `cron::`. The same tick returns the same Execution. The next tick starts a new one. ## Deploying triggers Triggers are declared in [`trigora.toml`](https://trigora.dev/docs/client/config/). `trigora deploy` discovers Programs, deploys them, then reconciles the full trigger list for that project. `name` is the reconcile identity. `program` must be a discovered program id. Platform ids (`trg_…`, `t_…`) are never written into the file. A file with no `[[triggers]]` replaces the project’s triggers with an empty list. ## Trigger lifecycle A firing starts the Program’s current deployed version. The Execution pins that version. Changing or deleting a trigger does not alter an Execution that already exists. Removing a trigger from `trigora.toml` removes that trigger on the next deploy. The webhook URL is stable while the trigger name stays. ## Observability Ingress context is stored on the Execution. It is not the Program entry’s identity. ```json { "type": "webhook", "triggerId": "trg_…", "publicId": "t_…" } ``` ```json { "type": "cron", "triggerId": "trg_…", "scheduledAt": "…" } ``` The dashboard Program page lists each trigger’s name, type, status, and either the webhook URL or `schedule · timezone`. ## Events https://trigora.dev/docs/cloud/events/ Resume a waiting Execution by sending a named event. `waitForEvent("approved")` suspends until a matching event arrives. Send it with the CLI or the client. ```bash trigora send approved trigora send approved --payload '{"reviewer":"ada"}' trigora send approved --remote ``` This command targets `trigora dev` by default. Add `--remote` to target Trigora Cloud. ```ts await trigora.sendEvent(executionId, "approved", { reviewer: "ada" }); ``` The payload becomes the value `waitForEvent` returns. The event name must be a string literal in the Program. `event("approved")` is an optional typed helper. A plain string is enough. While the Execution is suspended, its public wait record has this structure: ```ts { type: "event", event: "approved" } ``` Sending an event to an Execution that is not waiting on that name fails with `event_mismatch`. The Execution stays where it was. See [waitForEvent](https://trigora.dev/docs/authoring/wait-for-event/). ## Timers https://trigora.dev/docs/cloud/timers/ sleep suspends until a wake time. Waiting is not billed as compute. `sleep` is a durable timer. The Execution commits a wake time and leaves `running`. ```ts import { sleep } from "@trigora/sdk"; await sleep(60_000); ``` Python: ```python from trigora import sleep await sleep(60) ``` While the timer is pending, the public wait is `{ type: "timer", wakeAt }`. The scheduler wakes the Execution at that time. You do not send an event to finish a sleep. Suspended wall-clock time is not a compute charge. See [Usage & billing](https://trigora.dev/docs/cloud/usage-and-billing/). If the process dies before `wakeAt`, recovery loads the continuation and keeps the same timer. It does not start the delay over from zero. ## Child executions https://trigora.dev/docs/cloud/child-executions/ invoke starts a child Program and waits for it. `invoke` starts another Program as a child Execution and suspends the parent until that child finishes. ```ts import { invoke } from "@trigora/sdk"; const report = await invoke("summarize", "tcc"); ``` The program id is a string. TypeScript also accepts a program function reference. The values after the name are the ordered argument vector. The parent’s public wait is `{ type: "child", executionId }`. ```python from trigora import invoke, program @program async def research(): return await invoke("summarize", "tcc") ``` The child is one `@program` function in a file whose stem is `summarize`. See [invoke](https://trigora.dev/docs/authoring/invoke/). The child has its own artifact pin, continuation, and status. A completed child result is reused if the parent resumes. The child is not started again. Several children can start from `await Promise.all` / `await Promise.race`, `await gather` / `await race`, or Rust `join` / `race` of `invoke` calls. A detached race loser keeps running. See [Structured concurrency](https://trigora.dev/docs/authoring/structured-concurrency/) and [invoke](https://trigora.dev/docs/authoring/invoke/). ## Cancellation https://trigora.dev/docs/cloud/cancellation/ Cancel a running or waiting Execution. ```bash trigora cancel trigora cancel --remote ``` This command targets `trigora dev` by default. Add `--remote` to target Trigora Cloud. ```ts await trigora.cancelExecution(executionId); ``` HTTP: `POST /v1/executions/:id/cancel`. The status becomes `cancelled`. A cancelled Execution does not resume on a later `send` or timer. `result()` on the TypeScript client rejects. The Python client raises `TrigoraError` with status 409 when it polls a cancelled Execution. Cancel is for the Execution you name. It is not a substitute for making external side effects idempotent. An effect that already committed stays committed. See [Production considerations](https://trigora.dev/docs/concepts/production/). ## API tokens https://trigora.dev/docs/cloud/api-tokens/ TRIGORA_TOKEN authenticates the CLI and client to Trigora Cloud. Cloud auth is a bearer token. Create it in Trigora Cloud, then: ```bash export TRIGORA_TOKEN=... trigora whoami ``` `TRIGORA_TOKEN` authenticates Trigora Cloud operations. A workspace API token is a machine credential with fixed authority. It is not the role of the person who created it, and it can write project secrets. Runtime CLI commands are local by default and use Cloud only with `--remote`. `trigora deploy` and `trigora whoami` are always Cloud operations. `whoami` prints the workspace the token can access. If the variable is missing, those Cloud operations fail with `TRIGORA_TOKEN is not set.` The CLI loads a project `.env`. A token already set in the shell wins over the file. ```bash # .env TRIGORA_TOKEN=... ``` `@trigora/client` reads the same variable when you omit `token`: ```ts import { createClient } from "@trigora/client"; const trigora = createClient(); ``` With a token, the client uses `https://api.trigora.dev` unless `TRIGORA_API_BASE_URL` or `url` is set. Without a token, it uses `TRIGORA_RUNTIME_URL` or `http://localhost:3477`. Do not commit tokens. There is no `trigora login` command and no `TRIGORA_DEPLOY_TOKEN`. ## Usage and billing https://trigora.dev/docs/cloud/usage-and-billing/ Cloud and Pulse share one workspace credit. Waiting is free. Trigora Cloud meters three things: | Meter | Launch rate | |---|---| | Execution compute | $0.40 / CPU-hour of measured execution CPU | | Durable operations | $0.03 / 1,000 ops | | Durable storage | $0.50 / GB-month | Suspended or waiting wall-clock time is not billed because an Execution is asleep. Execution compute is CPU time measured inside the effect worker while your code runs. A network wait inside an effect is not CPU. Code that never enters the worker is not included. A durable operation is a logical transition: start, an effect attempt, an event or wait resolution, a timer firing, a child start, a child completion delivered to the parent, or an explicit cancel. Internal snapshot writes, journal lookups, and infrastructure retries are not separate meters. Each committed attempt has one idempotency key, so a retry after a crash is not billed again. **Free.** $5 monthly usage included, shared by Cloud and Pulse. No overages. When that credit is exhausted, new top-level starts are refused. Executions that already exist continue, including timers, events, child executions, and recovery. **Pro.** One $50/month workspace subscription. The $50 is fully credited toward usage across Cloud and Pulse. The invoice is the greater of that usage and $50. The credit does not reduce the $50 license, and it is not added again on top of usage. Cloud durable operations are $0.03 per 1,000. Pulse durable operations are $0.02 per 1,000. Compute is billed only for Cloud. Pulse storage uses the same $0.50 per GB-month. The two operation prices are converted to cents before they share the credit, so the counts are not added together. **Enterprise** and commercial self-hosting are separate agreements. See [Pricing](https://trigora.dev/pricing/) and [Licensing](https://trigora.dev/docs/engine/licensing/). Rates can change prospectively. They do not rewrite charges already incurred. The [Terms](https://trigora.dev/terms/) are the self-serve agreement. ## Trigora Pulse https://trigora.dev/docs/pulse/overview/ Run your code on your infrastructure. Trigora stores the checkpoint and recovers the execution. Run your code on your infrastructure. Let Trigora handle durability. Trigora Pulse lets you run long-running Programs on your own machines while Trigora manages durable checkpoints, recovery, and execution coordination. You keep control of compute, deployments, and the application environment without building a durability system. Pulse is an execution mode of the same workspace plan as Trigora Cloud. Free, Pro, and Enterprise are not separate Pulse subscriptions. Compute stays on your machines and is not charged. Waiting wall-clock is free. Trigora bills the durable operations and storage that a commit landed. | Your infrastructure | Trigora Pulse | | --- | --- | | Runs your application and effects | Stores durable execution state | | Controls compute resources | Coordinates execution ownership | | Manages deployments and application versions | Enables recovery after failures | | Pays its compute provider | Charges for durable operations and storage | `trigora dev` and `trigora verify` stay on local SQLite. They do not contact Pulse. The [Node guide](https://trigora.dev/docs/pulse/node/) is the way to start a host and the Node adapter. [Deploy](https://trigora.dev/docs/pulse/deploy/) is the two-container layout. The Node adapter is the supported way to run callbacks. A callback is ordinary JavaScript: Pulse does not wrap it in a sandbox of its own. ## Node https://trigora.dev/docs/pulse/node/ Install a TypeScript project, start Pulse 1.0.0, and recover one execution. Node.js 22 and 24 are supported for the npm CLI launcher and the Pulse Node adapter. Pulse runs TypeScript programs. There is no Python or Rust Pulse adapter. The published host is [Pulse v1.0.0](https://github.com/trigora-dev/trigora/releases/tag/pulse-v1.0.0), image `ghcr.io/trigora-dev/pulse:1.0.0`. A normal deployment pulls that image. Building `trigora-pulse` from source is for a change to the host, not for running a Program. [Deploy](https://trigora.dev/docs/pulse/deploy/) is the two-container layout. ## Install ```bash npm install @trigora/sdk trigora ``` The npm `trigora` package launches the Rust CLI. `trigora pulse start` and `trigora pulse connect` are the Pulse commands. `trigora dev` is the local SQLite runtime. `trigora deploy` and `trigora start` talk to local development or Trigora Cloud. They do not start a Pulse execution. ## Create a Program This is the Program the [recovery proof](https://trigora.dev/docs/pulse/recovery/) runs. Effect `a` commits, the execution waits, then effect `b` runs. The first effect is not repeated after a crash if its result is already in the journal. ```ts import { appendFileSync } from "node:fs"; import { effect, waitForEvent } from "@trigora/sdk"; export default async function program() { const first = await effect("a", () => { appendFileSync(process.env.PULSE_EFFECT_LOG ?? "", "a 1\n"); return 1; }); await waitForEvent("continue"); const second = await effect("b", () => { appendFileSync(process.env.PULSE_EFFECT_LOG ?? "", "b 2\n"); return 2; }); return { first, second }; } ``` Point [`trigora.toml`](https://trigora.dev/docs/client/config/) at the file: ```toml [project] name = "pulse-demo" programs = ["src/**/*.ts"] ``` ## Set TRIGORA_TOKEN ```bash export TRIGORA_TOKEN=... ``` The token belongs on the host. It selects the workspace at `https://api.trigora.dev`. Do not combine it with `TRIGORA_PULSE_AUTHORITY_URL`, `TRIGORA_PULSE_WORKSPACE_ID`, or `TRIGORA_PULSE_WORKSPACE_TOKEN`. Those three are only for a local direct client. Without a token, the host prints `Durability is not configured.` and exits 2. `--ephemeral` is the only start that skips the token. It keeps the checkpoint in memory and does not recover after the host exits. ## Start the host and adapter ```bash trigora pulse start ``` The command compiles the project, starts `trigora-pulse`, and starts the Node adapter beside it. The adapter registers the handlers. When that registration finishes it prints: ```text ready ``` `trigora pulse start` does not create an execution. `trigora start` is not a substitute. It targets `trigora dev` or, with `--remote`, Trigora Cloud. ## Start an execution The application starts an execution by sending `{ type: "start", executionId }` on the adapter session after registration. The Compose reference does that when `PULSE_EXECUTION_ID` is set. [Deploy](https://trigora.dev/docs/pulse/deploy/) shows the two containers and that variable. For this Program, set `PULSE_EFFECT_LOG` to a file both you and the effect can write. After the start is accepted, that file contains: ```text a 1 ``` Effect `b` has not run. The execution is waiting for `continue`. ## Inspect Read the effect log and the host log. `a 1` with no `b 2` means the first effect committed and the wait has not been released. The dashboard `situation` is a projection (`terminal`, `owned`, `dormant`, or `recoverable`). It can lag the checkpoint. See [Limits](https://trigora.dev/docs/pulse/limits/). ## Restart and recover 1. Stop the host and the application. The lease expires. The execution stays on the pinned artifact. 2. Start a new host and application with the same artifact. Do not set `PULSE_EXECUTION_ID` or `PULSE_RESUME_EXECUTION_ID`. 3. The replacement discovers the execution. Effect `a` is not written again. 4. When `continue` is delivered, effect `b` writes `b 2` once. The log is `a 1` followed by `b 2`. The steps, including how the reference entrypoint delivers `continue`, are in [Recovery](https://trigora.dev/docs/pulse/recovery/). Trigora does not replace the container for you. ## Connect to a host that is already running `trigora pulse connect` does not start a host. Container deployment uses it in the application container. `--dir` is the socket directory the host already bound. Both processes must run as the same user. The host rejects a different uid. ```bash trigora pulse connect --dir /pulse ``` `--prepare` writes the handler manifest, prints its path, and exits without registering: ```bash trigora pulse connect --dir /pulse --prepare ``` ## Troubleshooting | What you see | What it means | | --- | --- | | `Durability is not configured.` and exit 2 | `TRIGORA_TOKEN` is missing or empty, and the host is not `--ephemeral`. | | `TRIGORA_TOKEN cannot be combined with TRIGORA_PULSE_AUTHORITY_URL...` | The customer token and the three direct-client variables are set together. Use one mode. | | `Incompatible Pulse host` and exit 1 | The host version or protocol does not match the CLI. Pulse 1.0.0 is host `1.0.0` and protocol `1`. | | `socket peer was rejected` | The adapter uid is not the host uid. Use the same user on both containers. | | Host log event `waiting-for-version` | This process can see the execution, but its artifact hash is not the pinned one. Keep a host that still runs the pinned artifact. See [Versions](https://trigora.dev/docs/pulse/versions/). | ## Deploy https://trigora.dev/docs/pulse/deploy/ Run the Pulse host and the Node adapter as two containers you operate. You operate both containers. Trigora provides the remote durability authority. The host container runs the published image. The application container compiles the project and runs `trigora pulse connect` against the host's socket. ```bash docker pull ghcr.io/trigora-dev/pulse:1.0.0 ``` That image is [Pulse v1.0.0](https://github.com/trigora-dev/trigora/releases/tag/pulse-v1.0.0): host `1.0.0`, adapter protocol `1`, Linux AMD64 and ARM64. Building `trigora-pulse` from source is for a change to the host. It is not the production install. ## Compose Both services share one socket directory and the same numeric user. The example uses uid `1000`. Any uid works when both containers use it and that user can write the socket directory. The host rejects a peer uid that is not its own. `TRIGORA_TOKEN` belongs only on the host. Do not put it on the application container. The socket stays on the shared volume. Do not publish it. ```yaml services: pulse: image: ghcr.io/trigora-dev/pulse:1.0.0 user: "1000:1000" environment: TRIGORA_TOKEN: ${TRIGORA_TOKEN:?} volumes: - pulse-sock:/pulse healthcheck: test: ["CMD", "trigora-pulse", "--check", "/pulse"] app: image: pulse-app:local user: "1000:1000" depends_on: pulse: condition: service_healthy environment: PULSE_DIR: /pulse PULSE_EXECUTION_ID: ${PULSE_EXECUTION_ID:-} PULSE_RESUME_EXECUTION_ID: ${PULSE_RESUME_EXECUTION_ID:-} volumes: - pulse-sock:/pulse volumes: pulse-sock: ``` The application container registers with that host: ```bash trigora pulse connect --dir /pulse ``` `--prepare` writes the handler manifest and exits. The reference image uses it when it needs to select one source file before it connects. `PULSE_EXECUTION_ID` starts that execution. `PULSE_RESUME_EXECUTION_ID` resumes an id you already have. Leave both empty when the replacement should discover abandoned work on its own. The artifact the adapter registers has to be the artifact the execution was pinned to. Stop the containers with a grace period longer than the host's 10-second shutdown deadline. The reference uses 15 seconds for `docker stop` and 30 seconds for a Pod. A Docker named volume is the socket directory that the recovery proof uses. A Docker Desktop bind mount cannot `chmod` a Unix socket. ## Kubernetes Kubernetes can run the same two processes. The reference is one Pod: the host and the application are sidecars, they share an `emptyDir` at `/pulse`, and the token Secret is mounted only on the host container. An init container gives the shared uid ownership of that volume. Both containers then run as that uid. That layout is for people who already run Kubernetes. It is not an install path, and Trigora does not schedule or replace Pods for you. A replacement Pod can resume an execution id you already have. Bringing up a compatible host, and keeping it able to see the execution, is your deployment. See [Recovery](https://trigora.dev/docs/pulse/recovery/). ## Recovery https://trigora.dev/docs/pulse/recovery/ A compatible replacement resumes from the committed checkpoint without an execution id. Pulse restores the continuation that committed. It does not replay the finished part of the Program. When a process stops, its lease expires. Another process with the same artifact and handler digest can claim the execution and continue. A claim that loses the fence cannot commit. An effect whose result is in the committed journal is not dispatched again. An external call can run again when the journal entry for that attempt did not commit. The callback should tolerate that. Timers, events, and child results wake the execution after the claim. Waiting does not require a connected process, and waiting time is not billed. The checkpoint bytes still occupy storage while the execution is suspended. ## Crash and resume The container proof uses the [two-effect Program](https://trigora.dev/docs/pulse/node/). Effect `a` writes `a 1` and commits. The execution then waits for `continue`. Effect `b` has not run. 1. Start the host and the application with `PULSE_EXECUTION_ID` set. Wait until the effect log contains `a 1`. Effect `b` has not run. 2. Stop both containers. The lease expires. The execution stays on the pinned artifact. 3. Start a new host and a new application with the same artifact. Do not set `PULSE_EXECUTION_ID` or `PULSE_RESUME_EXECUTION_ID`. 4. The replacement discovers the execution. Effect `a` is not written again. When `continue` is satisfied, effect `b` writes `b 2` once. The log is `a 1` followed by `b 2`. A host running a different artifact does not claim that execution and does not run either effect. Discovery is not deployment. A compatible host can find eligible work after its lease wait. Trigora does not provision that host, replace a container, or schedule a Pod. You start the replacement. ## Versions https://trigora.dev/docs/pulse/versions/ An execution stays on the artifact hash it started with. The artifact hash is the pinned version. A host registers the hash of the code it is running. Discovery offers an execution only to a host with that hash and a compatible handler digest. A host running a different artifact does not receive the execution. The host log can say `waiting-for-version` when it can see the mismatch. The dashboard does not decide that. It shows the hash from the projection. Deleting the version does not move the execution onto another artifact. The execution stays waiting for a process that still has the pinned hash. There is no continuation migration from one program version to another. ## Compatibility [Pulse v1.0.0](https://github.com/trigora-dev/trigora/releases/tag/pulse-v1.0.0) is the published release. | Component | Pulse v1.0.0 | | --- | --- | | Host | 1.0.0 | | Host protocol | 1 | | Node.js adapter | Node 22 and 24 | | Linux architectures | AMD64 and ARM64 | | Maximum serialized continuation | 16 MiB | | Program-version continuation migration | Not supported | | Python / Rust Pulse adapters | Not supported | A continuation stays on the artifact hash it started with. Keep a process that still runs that artifact while those executions are active. A host whose artifact hash differs does not claim them. ## Deploying a new application version Ship the new artifact as a new deployment. Leave a host and adapter that still run the previous artifact until every execution pinned to that hash has finished. A replacement that only has the new hash will not claim the older executions. Those executions wait. They do not move forward on the new code, and they do not fail over to it. ## Limits https://trigora.dev/docs/pulse/limits/ Execution limits, recovery guarantees, and what you still operate. ## Execution limits Checkpoints are capped at 16 MiB. Store larger data outside the Program and keep a reference in the continuation. The Node adapter is the supported callback runtime on Node 22 and Node 24. Programs are TypeScript. There is no Python or Rust Pulse adapter. Callbacks are ordinary JavaScript. Pulse does not wrap them in a sandbox of its own. ## Recovery guarantees An effect whose result is in the committed journal is not dispatched again. An external effect can run again when its journal did not commit. One process owns an execution at a time. A claim that loses the fence cannot commit. A second process does not commit under the same fence. A deleted version stays waiting for the pinned artifact. Discovery does not move it onto another hash. The dashboard reads the projection, not the checkpoint. `situation` is that projection's label: `terminal`, `owned`, `dormant`, or `recoverable`. It can lag the checkpoint, so `recoverable` can appear while another process already holds the lease. It does not mean a compatible worker is connected, and it does not mean the next claim will succeed. The generation and the observed time on the page are from the projection read. ## Operational responsibilities You run the process. You deploy it, replace it, and keep a compatible artifact available for executions that are still pinned to it. Trigora does not provision the host. Waiting wall-clock is free. Checkpoint bytes still occupy storage while the execution is suspended. Compute on your machines is not a Trigora charge. ## Durable operations https://trigora.dev/docs/authoring/durable-operations/ effect, waitForEvent, sleep, and invoke are the durable boundaries. A durable operation is an explicit point where Trigora can commit continuation state. Between those calls, control flow is ordinary code. | Operation | TypeScript | Python | Rust | What it commits | |---|---|---|---|---| | Effect | `effect(name, fn)` | `effect(name, fn)` | `effect(name, closure)` | The outcome of the body, keyed by `name` | | Event wait | `waitForEvent(name)` | `wait_for_event(name)` | `wait_for_event(name)` | That the Execution is waiting on `name` | | Timer | `sleep(duration)` | `sleep(duration)` | `sleep(duration_ms)` | The wake time | | Child | `invoke(program, ...args)` | `invoke(name, *args)` | `invoke(child, args)` | The child Execution and, later, its result | `Promise.all` / `Promise.race`, `gather` / `race`, and Rust `join` / `race` are control flow over those operations, not extra operations. See [Structured concurrency](https://trigora.dev/docs/authoring/structured-concurrency/). Names and event strings must be literals. `effect(fn)` without a name is rejected. These calls throw if you invoke the Program file directly. They run only inside an Execution started by the CLI or client. See each page for signature, failure, and one example: [Effects](https://trigora.dev/docs/authoring/effects/), [waitForEvent](https://trigora.dev/docs/authoring/wait-for-event/), [sleep](https://trigora.dev/docs/authoring/sleep/), [invoke](https://trigora.dev/docs/authoring/invoke/). ## Effects https://trigora.dev/docs/authoring/effects/ effect(name, fn) runs external work and commits the outcome. ## Signature ```ts function effect(name: string, run: () => T | Promise): Promise ``` ```python def effect(name: str, fn: Callable[[], T]) -> T ``` ## Parameters - `name` — non-empty string. In a Program it must be a string literal. This is the effect identity. - `run` / `fn` — the work. It runs at the boundary. It is not itself a second Program. ## Return value The resolved value of `run`. On recovery after that outcome is committed, the same value is returned and `run` is not executed again. ## Durable semantics The effect is one durable operation. The key is the name at that position in the Program. A committed outcome is reused. If the process dies before the outcome is committed, the effect may run again. Trigora does not make the external system exactly-once. See [Effect identity](https://trigora.dev/docs/concepts/effect-identity/) and [Production considerations](https://trigora.dev/docs/concepts/production/). ## Failure - An empty name throws before the effect runs. - If `run` throws and that failure is not a committed success, the Execution can fail. - Calling `effect` outside an Execution throws and tells you to start the Program with the client while `trigora dev` is running, or through Cloud after deploy. ## Example ```ts const result = await effect("generate", async () => { return 42; }); ``` ## waitForEvent https://trigora.dev/docs/authoring/wait-for-event/ Suspend until a named event is sent to the Execution. ## Signature ```ts function waitForEvent( eventOrName: EventDefinition | string, options?: { timeout?: string | number; match?: Record }, ): Promise ``` ```python def wait_for_event(name: str) -> object ``` The TypeScript options object is part of the SDK type. The operation you can rely on for Cloud and `trigora dev` is the event name: `trigora send ` resumes that wait. A send for a different name returns `event_mismatch`. ## Parameters - `eventOrName` — a string literal, or `event("approved")` from `@trigora/sdk`. - Python takes the name string only. ## Return value The JSON payload of the sent event. Omit `--payload` and the payload is empty. ## Durable semantics The Execution commits the wait and leaves `running`. Status becomes `waiting` with `{ type: "event", event }`. No compute is held. Recovery restores that wait. It does not replay earlier effects. ## Failure - An empty name throws. - A send while the Execution is not waiting on that name fails with `event_mismatch`. - Outside an Execution, the call throws. ## Example ```ts const review = await waitForEvent("approved"); ``` ```bash trigora send approved --payload '{"ok":true}' ``` ## sleep https://trigora.dev/docs/authoring/sleep/ sleep commits a wake time and suspends. ## Signature ```ts function sleep(duration: string | number): Promise ``` ```python def sleep(duration: int | float | str) -> None ``` ## Parameters - `duration` — a number of milliseconds, or a duration string. Use a number unless you have confirmed the string form against the runtime you are deploying to. ## Return value `void` when the timer fires. ## Durable semantics The Execution stores `wakeAt` and waits with `{ type: "timer", wakeAt }`. A crash before the wake does not restart the duration from zero. The scheduler, not `trigora send`, resumes the Execution. Waiting wall-clock is not compute billing on Cloud. ## Failure An unparseable duration string fails at that call. Outside an Execution, the call throws. ## Example ```ts await sleep(60_000); ``` ## invoke https://trigora.dev/docs/authoring/invoke/ Start a child Program and wait for its result. ## Signature ```ts function invoke(program: string, ...args: unknown[]): Promise ``` ```python def invoke(name: str, *args: object) -> object ``` ```rust invoke(child, args).await? ``` Rust takes one owned value. A tuple is the argument vector. A bare value is a one-element vector. `()` is empty. ## Parameters - `program` / `name` / `child` — program id. TypeScript uses the named default export or the file stem. Python uses the file stem. Rust Program ids use the source file stem. For `lib.rs` and `main.rs`, the Cargo package name is used instead. - The remaining values are the ordered argument vector. `invoke(name)` supplies `[]`. An explicit `null` or `None` is one element, not an empty vector. Binding follows the child’s language. See [Programs](https://trigora.dev/docs/concepts/programs/). ## Return value The child Execution’s result, after the child completes. ## Durable semantics `invoke` starts one child and suspends the parent with `{ type: "child", executionId }`. The child has its own continuation and artifact pin. A committed child result is reused if the parent resumes. The child is not created again for that invoke. Several `invoke` calls can sit inside `await Promise.all` / `await Promise.race`, `await gather` / `await race`, or Rust `join` / `race`. See [Structured concurrency](https://trigora.dev/docs/authoring/structured-concurrency/). A race winner does not cancel a sibling child that already started. ## Failure - An empty program name fails the compile or the start. - If the child fails, the parent sees that failure at `invoke`. - Outside an Execution, the call fails. ## Example ```ts export default async function analyze(topic, limit = 1) { return { topic, limit }; } ``` ```ts import { invoke } from "@trigora/sdk"; export default async function research() { return await invoke("analyze", "tcc", 2); } ``` The Python child lives in `analyze.py`. The program id is the file stem. ```python from trigora import invoke, program @program async def analyze(topic, limit=1): return {"topic": topic, "limit": limit} ``` ```python from trigora import invoke, program @program async def research(): return await invoke("analyze", "tcc", 2) ``` ## Structured concurrency https://trigora.dev/docs/authoring/structured-concurrency/ Run durable operations concurrently and preserve their state across suspension and recovery. TypeScript `Promise.all` / `Promise.race`, Python `gather` / `race`, and Rust `join` / `race` compile to the same durable join operations. Each requires the `durable.concurrent_group` capability. The continuation stores the durable join state rather than a host promise or Python awaitable. ```text TypeScript await Promise.all / await Promise.race Python await gather / await race Rust join / race All Fork + JoinAll / JoinAny ``` `join(effect("a", || 1.0), effect("b", || 2.0))` has the same durable semantics as `await Promise.all([effect("a", () => 1), effect("b", () => 2)])`. The Rust frontend accepts exactly two arguments, while the engine supports groups of 1–32 branches. The supported forms for each language are documented under [TypeScript](https://trigora.dev/docs/authoring/typescript/), [Python](https://trigora.dev/docs/authoring/python/), and [Rust](https://trigora.dev/docs/authoring/rust/). ## Examples ```ts import { effect, sleep, waitForEvent } from "@trigora/sdk"; const [left, right] = await Promise.all([ effect("left", async () => 1), effect("right", async () => 2), ]); const winner = await Promise.race([ waitForEvent("ready"), sleep(60_000), ]); ``` ```python from trigora import effect, gather, race, sleep, wait_for_event found = await gather( effect("left", lambda: 1), effect("right", lambda: 2), ) winner = await race( wait_for_event("ready"), sleep(60_000), ) ``` A group has 1 to 32 branches. Each branch is `effect`, `waitForEvent` / `wait_for_event`, `sleep`, or `invoke`. Rust `join` and `race` take exactly two of those calls. ## What stays durable Branches remain durable across suspension and recovery. Registration is one branch at a time. A crash between registrations resumes the group. It does not start a second copy of a branch that already registered. `Promise.all`, `gather`, and `join` return the branch values in input order, including when completions arrive in another order. `Promise.race` and `race` persist the first durably accepted winner. That is the first completion the engine commits after every branch is registered, not wall-clock order. When several effects finish during registration, the lowest branch index wins. A crash before that commit can still accept a different winner. A crash after it cannot. After a race commits a winner, the other branches detach and can no longer affect the parent result. Effects and child Executions that have already started are not cancelled. Sibling-branch cancellation is not supported. If a branch fails, the all-join fail-fasts into the surrounding `try` / `catch` or `try` / `except` after that failure is committed. Later sibling completions cannot change the outcome. A race throws when the accepted winner fails, and the same handler runs once. `Promise.all`, `Promise.race`, `gather`, and `race` are not themselves durable operations. The durable operations inside them are. See [Durable operations](https://trigora.dev/docs/authoring/durable-operations/). ## TypeScript https://trigora.dev/docs/authoring/typescript/ Author TypeScript Programs with `@trigora/sdk`. TypeScript Programs compile against the `ts.subset.v1` language profile. `@trigora/sdk` is how you build Programs for Trigora. It is not the engine embed API. Declare which files to compile in [`trigora.toml`](https://trigora.dev/docs/client/config/). ```ts import { effect, waitForEvent } from "@trigora/sdk"; export default async function approval() { const result = await effect("generate", () => 42); const review = await waitForEvent("approved"); return { result, review }; } ``` ## Supported syntax - A default-export async function. Parameters are plain identifiers. A missing or `undefined` argument uses a call-time default that may read earlier parameters, otherwise it is `undefined`. Extra arguments are ignored. Explicit `null` stays `null`. The program id is the export name, or the file stem when the export is anonymous. See [Programs](https://trigora.dev/docs/concepts/programs/). - `effect`, `waitForEvent`, `sleep`, `invoke`, and `execution.id` / `execution.attempt` / `execution.programId` / `execution.signal`. Durable operations stay in the program entry. - String-literal effect keys and event names. - Ordinary computation between durable operations, including objects, arrays, helpers, and one-level destructuring. Defaults on those helpers run at the call and may use earlier parameters. See [Supported language semantics](https://trigora.dev/docs/authoring/supported-semantics/). - Direct `await Promise.all([...])` and `await Promise.race([...])`, including `return await` and an outer use such as `format(await Promise.all([...]))`. The argument is a fixed array literal of 1–32 `effect`, `waitForEvent`, `sleep`, or `invoke` calls. See [Structured concurrency](https://trigora.dev/docs/authoring/structured-concurrency/). ## Unsupported syntax - A rest parameter, a binding pattern, or optional `?` on the entry. - A stored promise (`const pending = Promise.all([...]); await pending`), `return Promise.all([...])` or `return Promise.race([...])` without `await`, spread, a dynamic array, `.then`, `Promise.any`, and `Promise.allSettled`. - `effect(fn)` without a name. - Running the file with `node` or `tsx`. That throws. Use `trigora dev` or Cloud. - Any construct outside `ts.subset.v1`. The compiler reports these constructs before the Program can run. ## Additional notes Python and Rust are supported as well. More authoring languages are planned. See [Unsupported constructs](https://trigora.dev/docs/authoring/unsupported/) for the current language boundaries. Webhook helpers at `@trigora/sdk/stripe` and `@trigora/sdk/github` verify request signatures. They neither create durable operations nor configure Trigora triggers. See [Supported language semantics](https://trigora.dev/docs/authoring/supported-semantics/) and [Limits](https://trigora.dev/docs/reference/limits/). ## Python https://trigora.dev/docs/authoring/python/ Author Python Programs with `trigora`. Python Programs compile against the `py.subset.v1` language profile. The Python authoring package is `trigora` in [trigora-python](https://github.com/trigora-dev/trigora-python). It does not start Executions. The HTTP client is `trigora-client`, imported as `trigora_client`. The CLI compiles and deploys. ```python from trigora import effect, program, wait_for_event @program async def approval(): result = await effect("generate", lambda: 42) review = await wait_for_event("approved") return {"result": result, "review": review} ``` The program id is the file stem, not the function name. ## Supported syntax - Exactly one top-level `@program` async function. Parameters are plain. Defaults are compile-time constants: `None`, a bool, a finite number, or a string. See [Programs](https://trigora.dev/docs/concepts/programs/). - `effect(name, fn)`, `wait_for_event(name)`, `sleep(duration)`, and `invoke(name, *args)`. Durable operations stay in the program entry. - `await gather(...)` and `await race(...)`, including `return await`. Each takes 1–32 direct durable calls. See [Structured concurrency](https://trigora.dev/docs/authoring/structured-concurrency/). - String-literal effect keys and event names. - Ordinary computation between durable operations, including objects, arrays, helpers, nested functions, and lambdas. A name assigned in a nested function is a new local unless it is declared `nonlocal`. See [Supported language semantics](https://trigora.dev/docs/authoring/supported-semantics/). ```python from trigora import effect, gather found = await gather( effect("a", lambda: 1), effect("b", lambda: 2), ) ``` `gather` returns one value. Assign it to one local. ## Unsupported syntax - Zero or two `@program` functions, a sync `@program`, or a bare `async def` with no marker. - `*args`, keyword-only parameters, positional-only parameters, or a mutable literal default on the entry. - A bare `gather(...)` or `race(...)`, an assignment of the call before `await`, a stored awaitable, a pre-awaited argument, `*args`, a list, or a nested `gather` or `race`. - `asyncio.gather`, `asyncio.wait`, and `asyncio.FIRST_COMPLETED`. - Calling the entry yourself. Use `trigora dev` or Cloud. Compile with the `trigora` CLI. See [Install](https://trigora.dev/docs/getting-started/install/) and [Python client](https://trigora.dev/docs/client/python/). ## Rust https://trigora.dev/docs/authoring/rust/ Author Rust Programs with the `trigora` crate. Rust Programs compile against the `rust.subset.v1` language profile. The Rust crate is `trigora` in [trigora-rust](https://github.com/trigora-dev/trigora-rust). It authors Programs. The CLI compiles and deploys them. The client is `trigora-client`. ```rust use trigora::{effect, wait_for_event}; pub struct Approval { pub result: f64, pub review: bool, } pub async fn main() -> Result { let amount: f64 = 42.0; let result = effect("generate", move || amount).await?; let review: bool = wait_for_event("approved").await?; Ok(Approval { result, review }) } ``` Point [`trigora.toml`](https://trigora.dev/docs/client/config/) at the crate sources: ```toml [project] name = "approval" programs = ["src/**/*.rs"] ``` Rust Program ids use the source file stem. For `lib.rs` and `main.rs`, the Cargo package name is used instead. The artifact function name is `main`. ## Supported syntax - One `pub async fn main` with plain owned parameters. Exact arity, no defaults. It returns `Result`. See [Programs](https://trigora.dev/docs/concepts/programs/). - `effect`, `sleep`, `invoke`, `join`, `race`, and `wait_for_event`. All are awaited. Names are string literals. - Ordinary computation between durable operations, including structs, enums, `Option`, `Result`, `Vec`, `if`, `loop`, `while`, `for`, and `move` closures. Numbers use the `f64` domain. Helper functions cannot await. - `join(left, right)` and `race(left, right)`, which provide the same durable join semantics as `Promise.all` / `gather` and `Promise.race` / `race`. The Rust frontend accepts exactly two branches; the engine supports groups of 1–32 branches. See [Structured concurrency](https://trigora.dev/docs/authoring/structured-concurrency/). ```rust use trigora::{effect, invoke, join, race, sleep}; pub async fn main(topic: String) -> Result<(), String> { let report: String = effect("research", move || topic).await?; let _notes: String = invoke("summarize", report).await?; let pair = join( effect("left", || 1.0), effect("right", || 2.0), ).await?; let _winner: f64 = race( effect("fast", || 1.0), effect("slow", || 2.0), ).await?; let _ = pair.len(); let _ = sleep(60_000.0).await?; Ok(()) } ``` `invoke("child", value)` is a one-element vector. `invoke("child", (a, b))` is two arguments. `invoke("child", ())` is empty. An effect closure that captures values must use `move`. Each captured value is stored under its binding name, and the host runs the effect body when the durable operation is dispatched. ## Unsupported syntax - A second `pub async fn main`, a missing argument, or an extra argument. - `&` and `&mut` in user code, integer types other than the `f64` domain, traits, macros, and async helpers. - Calling `main` yourself. Use `trigora dev` or Cloud. See [Rust SDK](https://trigora.dev/docs/reference/rust-sdk/) and [Supported language semantics](https://trigora.dev/docs/authoring/supported-semantics/). ## Supported language semantics https://trigora.dev/docs/authoring/supported-semantics/ Trigora Programs can use ordinary application logic between durable operations. The execution model is language-independent; each authoring language defines the source syntax that compiles to it. **Supported language profiles:** TypeScript (`ts.subset.v1`), Python (`py.subset.v1`), and Rust (`rust.subset.v1`). TCC’s execution model does not depend on a single source language. Each frontend compiles its supported syntax into an artifact that the engine can resume, and reports unsupported syntax at compile time. Support for additional authoring languages is planned. See [Unsupported constructs](https://trigora.dev/docs/authoring/unsupported/). Each language profile defines a single Program entry point. Starting an Execution binds an ordered argument list to that entry point; resuming an Execution restores the existing continuation without binding the arguments again. - TypeScript: a default-export async function. A missing or `undefined` argument uses a call-time default from earlier parameters, otherwise it is `undefined`. Extra arguments are ignored. Explicit `null` stays `null`. - Python: one `@program` async function. Extra arguments are a start error. An omitted argument uses a compile-time constant default when one exists, otherwise it is a start error. Explicit `None` stays null. The program id is the file stem. - Rust: one `pub async fn main`. Exact arity, no defaults. A missing argument is a start error. Rust Program ids use the source file stem. For `lib.rs` and `main.rs`, the Cargo package name is used instead. Durable operations are explicit imports: `effect`, `waitForEvent` / `wait_for_event`, `sleep`, `invoke`, and in Rust also `join` and `race`. A local function with the same name is not a durable operation. Durable operations stay in the program entry. Effect keys and event names are string literals. ## Ordinary computation Between durable operations, the supported language profiles include: - Arithmetic, short-circuit logic, branches, loops, index and length, in-place updates, and one-level destructuring. - Objects, arrays, structs, enums, `Option`, `Result`, and `Vec` are continuation values. Assignment copies a reference for TypeScript and Python objects and arrays. Mutation is visible through aliases. - Helpers and closures that do not suspend. A helper or closure frame cannot perform a durable operation. A closure value may live across a wait. - `NaN`, `Infinity`, `-Infinity`, and `-0` round-trip where the language has them. An unsupported mix of operators is a runtime type error. - Explicit `null` / `None` stays null. Each language keeps its own rules: - TypeScript defaults run at the call and may use earlier parameters. Python assignment in a nested function is a new local unless declared `nonlocal`. - Python defaults are compile-time constants: `None`, a bool, a finite number, or a string. `True + 1` is `2`. Divide-by-zero raises. - Rust numbers are `f64`. A capturing effect closure is `move`. Structured concurrency uses the same durable join semantics across languages: TypeScript `await Promise.all` / `await Promise.race`, Python `await gather` / `await race`, and Rust `join` / `race`. See [Structured concurrency](https://trigora.dev/docs/authoring/structured-concurrency/). Engine-native authoring is a separate integration model. See [Engine-native primitives](https://trigora.dev/docs/engine/primitives/). ## Unsupported constructs https://trigora.dev/docs/authoring/unsupported/ Constructs outside the supported TypeScript, Python, and Rust language profiles. The compiler reports unsupported constructs during `trigora dev` or deployment, before an Execution can start. Ordinary computation between durable operations is supported, including one-level destructuring. See [Supported language semantics](https://trigora.dev/docs/authoring/supported-semantics/) for the complete supported language inventory. ## Unsupported syntax - A rest parameter, `*args`, a binding pattern, or a second program entry. Entry parameters and defaults that do compile are on [Programs](https://trigora.dev/docs/concepts/programs/). - TypeScript `this`, classes, prototypes, generators, and async arrows. - A helper or closure frame that performs a durable operation. Durable operations stay in the program entry. A closure value may live across a wait. - Classes, traits, macros, and async helpers in Rust. See [Rust](https://trigora.dev/docs/authoring/rust/). - Stored promises, spread or dynamic arrays inside `Promise.all` / `Promise.race`, `.then`, `Promise.any`, and `Promise.allSettled`. - A bare, assigned, stored, pre-awaited, starred, listed, or nested `gather` / `race`, and `asyncio.gather`, `asyncio.wait`, and `asyncio.FIRST_COMPLETED`. The forms that compile are on [Structured concurrency](https://trigora.dev/docs/authoring/structured-concurrency/). - Unnamed effects. `effect` requires a non-empty string key. - Non-literal effect keys and event names. - Executing the Program module as a normal script. ## Compile-time behavior Unsupported constructs produce a compile error; Trigora does not substitute a different execution or recovery model. `Promise.any` and `Promise.allSettled` are not supported. Support for additional authoring languages is planned. ## TypeScript client https://trigora.dev/docs/client/typescript/ The TypeScript HTTP client for Trigora Cloud and local trigora dev. `@trigora/client` talks to Trigora. It does not embed TCC Engine and it does not compile Programs. ```ts import { createClient } from "@trigora/client"; const trigora = createClient({ token: process.env.TRIGORA_TOKEN, }); ``` `createClient()` with no arguments: - If `TRIGORA_TOKEN` is set, uses `TRIGORA_API_BASE_URL` or `https://api.trigora.dev`. - Otherwise uses `TRIGORA_RUNTIME_URL` or `http://localhost:3477`. Optional `projectId` sends `X-Trigora-Project-Id`. Optional `url` overrides the base URL. ```ts const run = await trigora.start("approval", {}); await run.send("approved", { ok: true }); const value = await run.result(); ``` `start` posts `{ programId, input }`. `input` is the ordered argument vector. See [Programs](https://trigora.dev/docs/concepts/programs/). `result()` polls until `completed`, `failed`, or `cancelled`. Failure rejects with the execution error. Network and HTTP failures throw `TrigoraRuntimeError` (`status`, `code`). Other methods: `whoAmI`, `listProjects`, `createProject`, `deployProgram`, `listPrograms`, `getProgram`, `listProgramVersions`, `getExecution`, `listExecutions`, `sendEvent`, `cancelExecution`, `getResult`. `listPrograms`, `listProgramVersions`, and `listExecutions` take an optional `{ limit?, cursor? }`. Deploy from application code is `deployProgram` with an already built artifact. Day to day, use `trigora deploy`, which compiles and then uploads. See [TypeScript client](https://trigora.dev/docs/reference/typescript-client/). ## Python client https://trigora.dev/docs/client/python/ The trigora-client package for Trigora Cloud and local trigora dev. `trigora-client` is the HTTP client. Import it as `trigora_client`. It does not author Programs. Authoring stays in `trigora`. A token selects Trigora Cloud (`TRIGORA_API_BASE_URL` or `https://api.trigora.dev`). Otherwise the client uses `TRIGORA_RUNTIME_URL` or `http://127.0.0.1:3477`. ```python from trigora_client import Client client = Client(token=None) run = client.executions.start("approval", ["tcc"]) run.send("approved", {"ok": True}) print(run.result()) ``` `start(program_id, input=None, *, url=None, token=None)` is the same call without constructing `Client`. `input` is the ordered argument vector. Omitting it sends `{}`. `ExecutionHandle.send(name, payload=None)` POSTs `/v1/executions/{id}/events`. `cancel()` POSTs cancel. `result()` polls until `completed`, then returns the result. `failed` and `cancelled` raise `TrigoraError`. `programs.list()`, `programs.get(program_id)`, and `programs.versions(program_id)` read Programs and their deployed versions. `programs.list`, `programs.versions`, and `executions.list` take `limit` and `cursor`. HTTP errors surface `status` and `code` from the JSON body when the API sends them. See [Python client reference](https://trigora.dev/docs/reference/python-client/) and [Python SDK](https://trigora.dev/docs/reference/python-sdk/). ## Rust client https://trigora.dev/docs/client/rust/ The trigora-client crate for Trigora Cloud and local trigora dev. `trigora-client` talks to Trigora. It does not embed TCC Engine and it does not compile Programs. A token selects Trigora Cloud (`TRIGORA_API_BASE_URL` or `https://api.trigora.dev`). Otherwise the client uses `TRIGORA_RUNTIME_URL` or `http://127.0.0.1:3477`. ```rust let run = client.executions().start("approval", input)?; run.send("approved", payload)?; let value = run.result()?; ``` `start` posts `{ programId, input }`. `input` is the ordered argument vector. See [Programs](https://trigora.dev/docs/concepts/programs/). `result()` polls until `completed`, `failed`, or `cancelled`. Failure returns the execution error. Other methods: `whoami`, `projects().list`, `projects().create`, `deploy`, `programs().list`, `programs().get`, `programs().versions`, `executions().list`, and on a handle `send`, `cancel`, and `result`. `programs().list`, `programs().versions`, and `executions().list` take `Option`. Deploy from application code uploads an already built artifact. Day to day, use `trigora deploy`, which compiles and then uploads. See [Rust client](https://trigora.dev/docs/reference/rust-client/). ## trigora.toml https://trigora.dev/docs/client/config/ The project file. Programs are discovered. Triggers are declared. `trigora.toml` is the project deployment manifest. Programs stay discovered from globs. Triggers are declared beside them. A trigger is not part of a Program artifact. ```toml [project] name = "acme-agents" programs = ["src/**/*.py"] [[triggers]] name = "approval-webhook" type = "webhook" program = "approval" [[triggers]] name = "nightly-research" type = "cron" program = "research-agent" schedule = "0 2 * * *" timezone = "UTC" [triggers.input] scope = "daily" limit = 100 [triggers.input.filters] active = true regions = ["us", "eu"] ``` `[project].name` is required. `programs` lives in that table. Unknown fields are errors. Key order does not matter. Platform ids are never written into the file. ## Programs `programs` accepts one glob or an array of globs. Discovery names: | Language | Program id | |---|---| | TypeScript | Named default export, otherwise the file stem | | Python | The file stem | | Rust | The source file stem. For `lib.rs` and `main.rs`, the Cargo package name | An anonymous TypeScript default export compiles with artifact function name `"default"`. That name is compiler metadata. The Program id is the file stem. `trigora deploy --program` selects one of those ids. See [Programs](https://trigora.dev/docs/cloud/programs/). ## Triggers `[[triggers]]` entries are project resources. `name` is unique in the project and is how deploy reconciles them. `program` must name a discovered program. A file with no `[[triggers]]` replaces the project’s triggers with an empty list. | Type | Fields | |---|---| | `webhook` | `name`, `type`, `program`. No `schedule`, no `timezone`, no `input`. The POST body is the program input. | | `cron` | `schedule` is required. `timezone` defaults to `UTC`. `input` is optional. | Cron input decodes to JSON: strings, integers, floats, booleans, arrays, and tables. TOML datetimes are rejected. Deploy replaces the project’s triggers with this list. Removing an entry removes that trigger. See [Triggers](https://trigora.dev/docs/cloud/triggers/). ## CLI https://trigora.dev/docs/client/cli/ trigora init, dev, deploy, pulse, secrets, start, send, cancel, bench, verify, and whoami. ```bash npm install trigora ``` The npm package provides a Node launcher for the Rust CLI. Node.js 22 and 24 are supported for the npm CLI launcher and TypeScript adapter. Python-only and Rust-only projects do not require Node.js. See [Install](https://trigora.dev/docs/getting-started/install/). Authentication uses `TRIGORA_TOKEN`; the CLI does not provide a `trigora login` command. `TRIGORA_TOKEN` authenticates Trigora Cloud. It does not choose the target of a runtime command. ## Dual-target These commands target `trigora dev` by default. Add `--remote` to target Trigora Cloud. `--remote` requires `TRIGORA_TOKEN`. | Command | What it does | |---|---| | `trigora programs` | List programs | | `trigora executions` | List executions | | `trigora executions inspect ` | One execution | | `trigora start ` | Start an execution | | `trigora send ` | Deliver an event | | `trigora cancel ` | Cancel | ```bash # Local — default trigora programs trigora executions trigora executions inspect trigora start approval trigora send approved trigora cancel # Trigora Cloud trigora programs --remote trigora executions --remote trigora executions inspect --remote trigora start approval --remote trigora send approved --remote trigora cancel --remote ``` `--input` and `--payload` are JSON, or a path to a JSON file. `--input` is the ordered argument vector when it is a JSON array. A single JSON value is one argument. Omitting it sends `{}`. ```bash trigora start approval --input '{"note":"stored"}' trigora send approved --payload '{"ok":true}' ``` ## Cloud-only `trigora deploy`, `trigora secrets`, and `trigora whoami` always talk to Trigora Cloud. They require `TRIGORA_TOKEN`. They do not take `--remote`. | Command | What it does | |---|---| | `trigora deploy` | Compile and upload to Cloud | | `trigora secrets` | List, set, or delete project secrets | | `trigora whoami` | Workspace for the Cloud token | ```bash export TRIGORA_TOKEN=... trigora deploy trigora deploy --program approval trigora secrets set OPENAI_API_KEY trigora whoami ``` `trigora secrets set` reads the value from a prompt or stdin. See [Secrets](https://trigora.dev/docs/cloud/secrets/). ## Local measurement `trigora bench` and `trigora verify` run in-process on a temporary SQLite host. They do not start `trigora dev`, and they do not talk to Trigora Cloud. See [Benchmark and verify](https://trigora.dev/docs/testing/bench-and-verify/). ```bash trigora bench my-program trigora verify my-program --faults all ``` ## Pulse `trigora pulse` runs a customer-operated host. It is not `trigora dev` and it is not `trigora deploy`. | Command | Purpose | | --- | --- | | `trigora pulse start` | Compile this project, start the Pulse host, and start the Node adapter beside it | | `trigora pulse connect --dir /pulse` | Register handlers with a host that is already running | | `trigora pulse connect --dir /pulse --prepare` | Write the handler manifest and exit without registering | ```bash export TRIGORA_TOKEN=... trigora pulse start trigora pulse connect --dir /pulse trigora pulse connect --dir /pulse --prepare ``` `trigora pulse start` requires `TRIGORA_TOKEN` unless you pass `--ephemeral`. The token stays on the host. `--ephemeral` keeps the checkpoint in memory and does not recover after the host exits. Do not combine `TRIGORA_TOKEN` with `TRIGORA_PULSE_AUTHORITY_URL`, `TRIGORA_PULSE_WORKSPACE_ID`, or `TRIGORA_PULSE_WORKSPACE_TOKEN`. `trigora pulse start` prints `ready` after the adapter registers. It does not create an execution. `trigora start` targets `trigora dev`, or Trigora Cloud with `--remote`. It is not the Pulse start path. The application starts an execution by sending `{ type: "start", executionId }` on the adapter session. The Compose reference does that when `PULSE_EXECUTION_ID` is set. See [Node](https://trigora.dev/docs/pulse/node/) and [Deploy](https://trigora.dev/docs/pulse/deploy/). `--dir` is the socket directory the host already bound. Both sides must run as the same user. | Command | Where it runs | | --- | --- | | `trigora dev` | Local SQLite runtime | | `trigora deploy` | Trigora Cloud | | `trigora pulse start` | Your host, with Trigora storing the checkpoint | ## Local setup | Command | What it does | |---|---| | `trigora init` | Writes the scaffold for TypeScript, Python, or Rust | | `trigora dev` | Local runtime on `localhost:3477` | `trigora dev` follows the chain in the [CLI reference](https://trigora.dev/docs/reference/cli/): `trigora` spawns `trigora-local`, which drives `tcc-host-sqlite`. ```bash trigora dev --host localhost --port 3477 ``` Full flags, including per-language `init` files: [CLI reference](https://trigora.dev/docs/reference/cli/). ## Start execution https://trigora.dev/docs/client/start/ trigora start and client.start begin one Execution. ## CLI ```bash trigora start approval trigora start research --input '["tcc", 10]' trigora start approval --remote ``` `` is the program id. `--input` is optional JSON. A JSON array is the ordered argument vector. A single JSON value is one argument. Omitting `--input` sends `{}`. See [Programs](https://trigora.dev/docs/concepts/programs/). This command targets `trigora dev` by default. Add `--remote` to target Trigora Cloud. ## TypeScript ```ts const run = await trigora.start("research", ["tcc", 10]); ``` `start` returns a handle, not the Program’s return value. The handle’s `result()` waits for completion. If the Program hits `waitForEvent`, `result()` keeps polling until you `send`. ## Python ```python from trigora_client import start run = start("research", ["tcc", 10]) ``` A token selects Cloud. Otherwise this uses local `trigora dev`. See [Python client](https://trigora.dev/docs/client/python/). ## Failure Unknown program ids fail the request. The Execution is not created. A Program that throws before a durable boundary fails the Execution. A Program that reaches a wait stays `waiting` and does not count as failure. ## Send event https://trigora.dev/docs/client/send/ Deliver a payload to an Execution waiting on that event name. ## CLI ```bash trigora send approved trigora send approved --payload '{"ok":true}' trigora send approved --remote ``` This command targets `trigora dev` by default. Add `--remote` to target Trigora Cloud. ## TypeScript ```ts await run.send("approved", { ok: true }); await trigora.sendEvent(executionId, "approved", { ok: true }); ``` ## Python ```python run.send("approved", {"ok": True}) ``` ## Durable semantics The event resolves the current `waitForEvent` / `wait_for_event`. The payload is the wait’s return value. Earlier `effect` outcomes stay committed. The Program continues after the wait. ## Failure `event_mismatch` when the Execution is not waiting on that name. You can send again with the name it is actually waiting on. Sending does not restart the Program. ## Cancel execution https://trigora.dev/docs/client/cancel/ Stop an Execution. It does not resume. ```bash trigora cancel trigora cancel --remote ``` This command targets `trigora dev` by default. Add `--remote` to target Trigora Cloud. ```ts await run.cancel(); await trigora.cancelExecution(executionId); ``` ```python run.cancel() ``` HTTP: `POST /v1/executions/:id/cancel`. Status becomes `cancelled`. Later `send` does not resume it. `result()` rejects. A Python `result()` poll raises `TrigoraError` for a cancelled Execution. Cancel does not roll back an effect outcome that already committed. External systems need their own compensation or idempotency. See [Cancellation](https://trigora.dev/docs/cloud/cancellation/). ## Inspect execution https://trigora.dev/docs/client/inspect/ Read status, wait, result, and artifact pin. ```bash trigora executions trigora executions inspect trigora executions inspect --remote ``` This command targets `trigora dev` by default. Add `--remote` to target Trigora Cloud. ```ts const execution = await trigora.getExecution(executionId); ``` The record includes: - `status` — `running`, `waiting`, `completed`, `failed`, or `cancelled` - `wait` — present while waiting: `event`, `timer`, or `child` - `input`, and `result` or `error` when finished - `programId`, `programVersionId`, `artifactHash` - `attempt`, `createdAt`, `updatedAt` - `parentExecutionId` when this Execution is a child `GET /v1/executions/:id/result` returns `{ status, result?, error? }` without the rest of the record. Inspect does not resume or replay the Program. It reads the control-plane mirror. The continuation itself stays in the host. ## Benchmark and verify durable programs https://trigora.dev/docs/testing/bench-and-verify/ Profile a healthy local run with trigora bench, and verify recovery with trigora verify. Trigora can profile the cost of durability and verify recovery at committed continuation boundaries. Use `trigora bench` for a healthy local run. Use `trigora verify` to inject crashes after committed boundaries, resume, and check that recovery matches the baseline. Both commands compile the program and run it in-process on a temporary SQLite host. They do not start `trigora dev`, call Trigora Cloud, or run the host conformance suite. ```bash trigora bench my-program trigora bench my-program --out bench.json trigora verify my-program trigora verify my-program --faults all trigora verify my-program --faults all --out verify.json ``` `--input` is the same JSON argument vector as [`trigora start`](https://trigora.dev/docs/client/start/): JSON text, or a path to a JSON file. A JSON array is the ordered arguments. A single JSON value is one argument. Omitting it sends `{}`. ## What bench measures `trigora bench` runs the program once, to completion, with no crash armed. The report describes that healthy run: - how many durable boundaries the run committed - healthy runtime, with sleep delays excluded - checkpoint time and continuation size - continuation restore latency - effect calls, reused effects, events awaited, and CPU time ## What verify proves `trigora verify` takes the same healthy baseline, then crashes after selected committed boundaries and resumes. The crash is in-process: the host stops after persisting checkpoint `N` (`after_persist_checkpoint:N`) and does not kill the process. A passing check means every tested fault point recovered, the terminal status and result matched the baseline, and completed effects were not repeated or dropped. The process exits non-zero when a recovery fails, the result differs, an effect runs twice, or an effect is missing. The printed verdict is one line: `✔ Recovery verified at every checkpoint`, or `✔ Recovery verified at N sampled checkpoints`. ## Two different latency measurements **Continuation restore latency** and **recovery latency** are different measurements. Do not treat one as the other. Continuation restore latency is the time to decode one committed continuation and construct the resumed engine state with `Engine::resume`. It does not include executing the recovered suffix. `trigora bench` reports it as `continuation_restore_ms`. Recovery latency is the time to resume a run that has already crashed after a committed boundary and bring that run to the baseline state. `trigora verify` reports it as `recovery_ms`. It covers the recovery, not the decode-and-resume sample above. The restore sample is taken from continuation bodies saved during the healthy run. The clock stops when `resume` returns, before the host callback and before any later copy of the continuation. ## Fault points `--faults sample` is the default. It checks up to 8 boundaries, spread from the first checkpoint to the last. If the run has 8 or fewer boundaries, sample checks all of them. `--faults all` checks every committed boundary. ## Effects A program with durable effects is refused unless you explicitly provide effect results or opt into live baseline execution. `--effects fixtures.json` is a JSON object of effect key to result. Those results are used for the baseline and for fault runs. With `--effects live`, live handlers run only during the healthy baseline. Fault-injection runs reuse the baseline's recorded results and do not call the handlers again. ## Events and sleeps `--events` is a JSON object of event name to payload, or to an array of payloads. The host queues those payloads and delivers them. An awaited event with no queued payload is delivered as `"ok"`. Sleeps are fast-forwarded. Healthy runtime does not include sleep delays. ## Reports `--out` writes a JSON report with `schema_version` 1. Percentiles are nearest-rank. The report includes the program, engine version, host protocol version, artifact hash, the healthy `baseline`, per-checkpoint samples, and, for verify, a `fault_injection` object. ## TCC Engine overview https://trigora.dev/docs/engine/overview/ TCC Engine is the portable continuation engine behind Trigora Cloud and Trigora Pulse. You can also embed it in your own infrastructure through a separate set of APIs. TCC Engine checkpoints a logical continuation at durable boundaries and resumes by loading that continuation. It does not replay the completed prefix. Trigora Cloud and Trigora Pulse both run on this engine. Pulse keeps the process on your machines and uses Trigora for the checkpoint. Embedding the engine is a separate choice, with its own license. Choose the integration that matches your deployment model: | You want to | Use | |---|---| | Deploy Programs to Trigora Cloud or run them with `trigora dev` | `@trigora/sdk`, `@trigora/client`, and the `trigora` CLI | | Run Programs on your machines while Trigora stores the checkpoint | [Trigora Pulse](https://trigora.dev/docs/pulse/overview/): `trigora pulse start` or `trigora pulse connect`, with `@trigora/sdk` | | Embed TCC Engine in your own host | The published packages below from [tcc-engine](https://github.com/trigora-dev/tcc-engine) | Pulse runs the process on your machines and still uses Trigora for durability. Embedding TCC is the other choice: you bring the storage, the scheduler, and the effects. ```text npm: @tcc-engine/frontend-typescript, @tcc-engine/bindings-javascript PyPI: tcc-engine Cargo: tcc-ir, tcc-state, tcc-core, tcc-host, tcc-host-sqlite, tcc-rust-frontend ``` The Node and Python SQLite hosts in the repository are reference implementations for the host protocol and conformance suite; they are not published packages. Similarly, `@tcc-engine/host-node` and `@tcc-engine/frontend-rust` are not available as published packages. The Trigora CLI includes `tcc-rust-compile`, while `tcc-host-sqlite` provides the persistent Rust host used by `trigora dev`. See [Reference SQLite hosts](https://trigora.dev/docs/engine/sqlite-hosts/). ```text TCC Engine ↓ host_protocol_version: 1 ↓ your host ├── persistence ├── effects ├── events ├── timers └── scheduling ``` Persistence belongs to the host rather than the engine. The reference hosts use SQLite, while Trigora Cloud provides its own persistence layer. Both implement host protocol version 1 and are validated against the same conformance suite. See [Host protocol](https://trigora.dev/docs/engine/host-protocol/) and [Host conformance v1](https://trigora.dev/docs/engine/conformance/). Source: [github.com/trigora-dev/tcc-engine](https://github.com/trigora-dev/tcc-engine). ## Embed in Node https://trigora.dev/docs/engine/embed-node/ Use the published Node packages to compile Programs for TCC Engine and run the engine through JavaScript bindings. These packages are for embedded-engine integrations, separate from `@trigora/sdk` and `trigora dev`. Published Node packages: - `@tcc-engine/frontend-typescript` — compile the TypeScript subset to an artifact - `@tcc-engine/bindings-javascript` — JavaScript bindings, including the engine interpreter `@tcc-engine/host-node` and `@tcc-engine/frontend-rust` are not published packages. The Node SQLite host in the [tcc-engine](https://github.com/trigora-dev/tcc-engine) repository is a reference implementation for the host protocol and conformance suite; `trigora dev` uses the Rust `tcc-host-sqlite` host instead. Application Programs use the Trigora authoring SDK rather than these embedding packages. Installation instructions and API details for the published engine packages are available in [tcc-engine](https://github.com/trigora-dev/tcc-engine). A custom host must implement the required host behavior; importing `@trigora/sdk` does not add Trigora Cloud semantics to it. A custom Node host must implement persistence, effect dispatch, events, timers, and child activation, then pass [host conformance v1](https://trigora.dev/docs/engine/conformance/). The reference hosts demonstrate conformant SQLite implementations, but custom hosts may use a different database. For a managed runtime, follow the [Trigora Cloud quickstart](https://trigora.dev/docs/quickstart/). When you embed TCC Engine, your host is responsible for operating persistence and recovery. ## Embed in Python https://trigora.dev/docs/engine/embed-python/ The published `tcc-engine` package provides the compiler. The repository also contains a Python SQLite host as a host-protocol reference implementation. The `trigora` package provides the Python authoring markers and primitives used by the compiler. It neither embeds TCC Engine nor provides HTTP access; use `trigora_client` for the Trigora HTTP API. The Python authoring package depends on the published `tcc-engine` compiler. The package does not include a host implementation. The [tcc-engine](https://github.com/trigora-dev/tcc-engine) repository includes a Python SQLite host for reference and conformance testing. It is not distributed as an installable package. SQLite belongs to that host implementation, not to the engine core. The Python and Node reference hosts and `tcc-host-sqlite` are all validated by the same conformance suite. Use the reference hosts as implementation guidance when building your own infrastructure. For local Trigora development, use `trigora dev`; for a managed runtime, use Trigora Cloud. See [Reference SQLite hosts](https://trigora.dev/docs/engine/sqlite-hosts/) and [Custom host](https://trigora.dev/docs/engine/custom-host/). ## Engine-native primitives https://trigora.dev/docs/engine/primitives/ `@trigora/sdk` authors Programs for Trigora. Engine-native primitives target an embedded TCC Engine host directly. The two authoring surfaces serve different runtimes: ```text @trigora/sdk → Programs for Trigora Cloud, trigora dev, and Trigora Pulse Engine-native primitives → artifacts you run on an embedded TCC host ``` `effect`, `waitForEvent`, `sleep`, and `invoke` in `@trigora/sdk` are the Trigora programming model. The same Programs run on Trigora Cloud, on `trigora dev`, and through Pulse. Pulse runs them with the Node adapter. The engine identifies durable operations from the frontend’s resolved imports, not from a function that happens to share a name. For an embedded-engine integration, use the primitive package documented in [tcc-engine](https://github.com/trigora-dev/tcc-engine). `@trigora/client` can connect to a custom host only if that host separately implements the Trigora HTTP API, including `/v1/executions`. The TCC host protocol and the Trigora HTTP API are distinct interfaces. See [Host protocol](https://trigora.dev/docs/engine/host-protocol/). ## Reference SQLite hosts https://trigora.dev/docs/engine/sqlite-hosts/ `tcc-host-sqlite` is the persistent Rust host used for local development. The Node and Python SQLite hosts are reference implementations for the host protocol and conformance suite. The reference hosts demonstrate TCC Engine recovery independently of Trigora Cloud. `trigora dev` drives the Rust host: ```text trigora → trigora-local → tcc-host-sqlite → TCC Engine ``` | Host | Where it runs | Store | |---|---|---| | Rust | `tcc-host-sqlite`, driven by `trigora-local` | SQLite (`.trigora/state.db`) | | Node | Node SQLite host in the tcc-engine repository | SQLite | | Python | Python SQLite host in the tcc-engine repository | SQLite, host-side | `tcc-host-sqlite` is the persistent Rust host used by `trigora dev` and passes [host conformance v1](https://trigora.dev/docs/engine/conformance/). The Node and Python hosts are repository-based conformance references rather than published packages. All three hosts are validated against the same semantic suite without requiring a shared SQL schema. Conformance tests terminate the process at defined crash points and resume from the durable store. Continuations contain logical Program state rather than a process heap or WASM memory image. Killing `trigora dev` during a wait, then starting it again, restores the wait from `.trigora/state.db`. That is the local version of the [quickstart](https://trigora.dev/docs/quickstart/) loop. Trigora Cloud uses its own persistence implementation rather than these SQLite stores, while implementing the same host protocol. See [Custom host](https://trigora.dev/docs/engine/custom-host/). ## Host protocol https://trigora.dev/docs/engine/host-protocol/ Host protocol version 1 defines the persistence and recovery contract between TCC Engine and a host. A TCC host durably stores continuation state and coordinates effects, events, timers, and child activation on behalf of the engine. ```text TCC Engine ↓ host_protocol_version: 1 ↓ your host ├── persistence ├── effects ├── events ├── timers └── scheduling ``` Host protocol version 1 is required. Trigora Cloud rejects requests with a missing or unrecognized version, and the reference hosts implement the same version. The conformance suite verifies these rules: 1. A write becomes durable only when the store commits and returns `persist_confirmed`; an earlier `ack` does not confirm durability. 2. Resume reconstructs state from a snapshot and semantic deltas. The host runs the stored artifact, while any artifact supplied by the caller serves only as a consistency check. A different hash produces an artifact mismatch. 3. `invoke` uses a stable child id and activates the child only after the parent and child records are durable, preventing duplicate child Executions. 4. A crash before commit leaves no child. A crash after commit and before activation resumes the same child row. The engine fixtures and conformance runner in [tcc-engine](https://github.com/trigora-dev/tcc-engine) define the protocol behavior. The Trigora HTTP API is a separate interface for Programs and Executions: implementing `/v1/executions` does not implement the TCC host protocol, and implementing the host protocol does not provide the Trigora HTTP API. ## Host conformance v1 https://trigora.dev/docs/engine/conformance/ A shared semantic test suite for the Rust, Node, and Python hosts and Trigora Cloud. Host conformance v1 validates host behavior rather than a particular SQL schema. At defined protocol hooks, the suite terminates the host and rebuilds it from durable storage to verify recovery across crash windows. The suite runs against `tcc-host-sqlite`, the Node and Python reference hosts, and Trigora Cloud. `tcc-host-sqlite` is the persistent Rust host used by `trigora dev`. Trigora Cloud runs the shared suite against its in-memory driver and adds tests for reopening its SQL store, recovering child activation, and rejecting an invalid `host_protocol_version`. These Cloud-specific tests extend coverage without defining a separate protocol. Passing the suite confirms that a host matches the protocol’s defined crash-window behavior. It does not certify the host’s process supervisor, storage system, regional failure handling, or uptime. The runner and test cases are included with [tcc-engine](https://github.com/trigora-dev/tcc-engine). Run the suite against a custom host driver before describing the host as compatible with protocol version 1. See [Custom host](https://trigora.dev/docs/engine/custom-host/). ## Custom host https://trigora.dev/docs/engine/custom-host/ Build a TCC host that persists, dispatches effects, and passes conformance v1. A custom host provides the persistence and runtime services that TCC Engine requires. ```text TCC Engine ↓ host_protocol_version: 1 ↓ your host ├── persistence ├── effects ├── events ├── timers └── scheduling ``` You implement: - **Persistence** — commit continuation snapshots and deltas before confirming durability. An early acknowledgement is not a durable commit. - **Effects** — run the effect body and record the outcome the engine will reuse. - **Events** — hold waits until your scheduler delivers a matching event. - **Timers** — store `wakeAt` and resume at that time. - **Scheduling** — activate child executions only after parent and child state is durable, with a stable child id. Run [host conformance v1](https://trigora.dev/docs/engine/conformance/) against the completed driver. `tcc-host-sqlite` and the Node and Python SQLite hosts provide reference implementations; a custom host does not need to subclass them. Use the protocol notes and fixtures in [tcc-engine](https://github.com/trigora-dev/tcc-engine) as the implementation contract. Set `host_protocol_version` to `1`; no additional protocol version field is defined. If you only need managed Programs and Executions, use [Trigora Cloud](https://trigora.dev/docs/quickstart/) instead of a custom host. ## Licensing https://trigora.dev/docs/engine/licensing/ Cloud Terms, MIT Trigora source, and the BUSL-1.1 terms for TCC Engine. Trigora Cloud, the open-source Trigora packages, and TCC Engine are governed by separate terms: | Surface | License | |---|---| | Trigora Cloud | The [Terms of Service](https://trigora.dev/terms/) at trigora.dev. Enterprise order forms can add SLAs, a DPA, or support. | | Trigora source: the CLI, `@trigora/contracts`, and the TypeScript, Python, and Rust SDKs and clients | MIT, in [trigora](https://github.com/trigora-dev/trigora), [trigora-typescript](https://github.com/trigora-dev/trigora-typescript), [trigora-python](https://github.com/trigora-dev/trigora-python), and [trigora-rust](https://github.com/trigora-dev/trigora-rust) | | TCC Engine, including reference hosts | [Business Source License 1.1](https://github.com/trigora-dev/tcc-engine) | Distributed Trigora binaries that link TCC Engine components do not relicense those components under MIT. That split is recorded in `LICENSE_NOTICES` and the bundled TCC Engine license. The Cloud Terms do not grant a right to embed TCC Engine in production. Pricing for Cloud and Pulse is [on the pricing page](https://trigora.dev/pricing/). One plan covers both. Pulse is the durability service with customer-operated compute: you run the published host, `ghcr.io/trigora-dev/pulse:1.0.0`, and Trigora stores the checkpoint. That is not a license to vendor or redistribute TCC Engine. Embedding TCC Engine in your own host is a separate arrangement. It is governed by the engine license below, not by running the Pulse image and not by the Cloud Terms. TCC Engine is BUSL-1.1. Non-production use is permitted. Production use may be permitted under the Additional Use Grant based on Annual Gross Revenue and whether the use is a Competitive Execution Offering. See the TCC Engine LICENSE for the controlling terms. Production use under $2M consolidated annual gross revenue is permitted by that grant, unless the use is a Competitive Execution Offering. Production at or above $2M, and any Competitive Execution Offering, need a commercial license. On the Change Date, the Change License is Apache-2.0. The Change Date is specified in the engine LICENSE. Review the license file in the engine repository before vendoring or distributing TCC Engine. This page summarizes the licensing structure; the applicable license text controls. ## Programs https://trigora.dev/docs/concepts/programs/ A Program is the durable function you deploy. Every Program artifact has one entry function identified by `program.entry`. | Language | Source | |---|---| | TypeScript | A default-exported async function | | Python | Exactly one `@program` async function | | Rust | Exactly one `pub async fn main` | The program id is not always that function’s name: - TypeScript: the named default export, or the file stem when the export is anonymous. An anonymous export’s artifact function name is `"default"`. That name is compiler metadata. - Python: the file stem. The `@program` function may have any name. - Rust: the source file stem. For `lib.rs` and `main.rs`, the Cargo package name is used instead. The artifact function name is `main`. **Arguments are an ordered list.** A fresh start binds that list to the entry parameters. Resume restores the continuation and does not bind again. After binding, each parameter is an ordinary local. - TypeScript: a missing or `undefined` argument uses a call-time default that may read earlier parameters. Otherwise it is `undefined`. Extra arguments are ignored. Explicit `null` stays `null`. - Python: extra arguments are a start error. An omitted argument uses a compile-time constant default when the function has one. Otherwise it is a start error. Explicit `None` stays null. - Rust: exact arity, no defaults. A missing argument is a start error. ```ts export default async function research(topic, limit = 10) { return { topic, limit }; } ``` ```python from trigora import program @program async def research(topic, limit=10): return {"topic": topic, "limit": limit} ``` `trigora start research --input '["tcc"]'` supplies that vector. TypeScript fills `limit` from the default. Python does the same because `10` is a compile-time constant. See [Start execution](https://trigora.dev/docs/client/start/) and [invoke](https://trigora.dev/docs/authoring/invoke/). ```ts import { effect, waitForEvent } from "@trigora/sdk"; export default async function approval() { const result = await effect("generate", () => 42); const review = await waitForEvent("approved"); return { result, review }; } ``` `trigora deploy` publishes a version of that Program. An [Execution](https://trigora.dev/docs/concepts/executions/) is one run of one version. Progress commits at [durable operations](https://trigora.dev/docs/authoring/durable-operations/). Between them, the program runs ordinary computation: arithmetic, objects and arrays, helpers, closures, branches, and loops. Unsupported constructs fail at compile time. See [Supported language semantics](https://trigora.dev/docs/authoring/supported-semantics/). `@trigora/sdk` is this model. It is not [TCC Engine](https://trigora.dev/docs/engine/overview/). ## Executions https://trigora.dev/docs/concepts/executions/ An Execution is one run of a Program version. An Execution is one run. It has a status: | Status | Meaning | |---|---| | `running` | The host is advancing it | | `waiting` | Suspended on an event, timer, or child | | `completed` | Returned a result | | `failed` | Stopped with an error | | `cancelled` | Stopped by cancel | A waiting Execution exposes a public wait: `{ type: "event", event }`, `{ type: "timer", wakeAt }`, or `{ type: "child", executionId }`. The continuation is in the host. The API mirrors status, the wait, input, result, and the artifact pin. Inspecting an Execution does not replay it. Start with `trigora start` or `client.start`. Resume an event wait with `trigora send`. Cancel with `trigora cancel`. Recovery loads the committed continuation at the last durable boundary. It does not start the function over. See [Recovery](https://trigora.dev/docs/concepts/recovery/). ## Durable boundaries https://trigora.dev/docs/concepts/durable-boundaries/ The points where continuation state can commit. A durable boundary is a durable operation: `effect`, `waitForEvent` / `wait_for_event`, `sleep`, or `invoke`. At each durable operation, the host can commit the Program’s control position and the values required to continue. After a failure, the Execution resumes from that boundary without rerunning earlier code to reconstruct its position. Durable boundaries are explicit; ordinary assignments between them do not create separate commits. Adding more boundaries increases the number of persistence operations a host performs, but recovery still resumes from a continuation rather than replaying the completed prefix. See [How recovery works](https://trigora.dev/docs/concepts/recovery/) and [Durable operations](https://trigora.dev/docs/authoring/durable-operations/). ## Continuations https://trigora.dev/docs/concepts/continuations/ A continuation is logical program state, not a memory snapshot. A TCC continuation is the logical state required to resume a Program at a durable boundary: control position, destination-live values, and the identity of the artifact that compiled to that point. It is not: - the process heap - a snapshot of process memory - a snapshot of WASM linear memory The host stores that logical state. Another process can load it and continue. The original process can be gone. Values that are still live at the boundary are part of the continuation. Values that are dead are not a history log the engine replays. Effect outcomes that already committed are reused by identity, not by running the effect function again. See [Effect identity](https://trigora.dev/docs/concepts/effect-identity/). The technical report is the longer argument: [Transparent Continuation Checkpointing](https://trigora.dev/research/whitepaper/). ## How recovery works https://trigora.dev/docs/concepts/recovery/ Recovery restores the latest committed continuation and resumes the Execution from its durable boundary. ```text completed prefix ↓ durable boundary ↓ committed continuation ↓ failure ↓ load continuation ↓ resume ``` The completed prefix is the work the Program already finished. At a durable boundary the host commits a continuation. If the process dies after that commit, a new process loads the continuation and resumes. It does not walk the prefix again to rediscover the position. What is restored: - **Control position** — the Program is at that boundary, not at the first line. - **Artifact identity** — the stored artifact hash is authoritative. A different blob is a mismatch, not a new compile of the same source. - **Destination-live durable values** — values the continuation still needs. - **Committed effect outcomes** — reused. The effect function is not the recovery mechanism. What recovery is not: - a copy of process memory - a heap snapshot - a WASM memory snapshot - prefix replay of retained history - exactly-once I/O to systems outside the host - a promise that recovery time is constant History can still exist for audit. Recovery does not use it to rebuild the present. For a direct comparison with history replay, see [Replay vs continuation recovery](https://trigora.dev/docs/concepts/replay-vs-recovery/). The [technical report](https://trigora.dev/research/whitepaper/) defines the formal claim and presents the supporting measurements. ## Artifact pinning https://trigora.dev/docs/concepts/artifact-pinning/ An Execution resumes the artifact it started with. `trigora deploy` stores an artifact and a hash. Starting an Execution records `programVersionId` and `artifactHash`. On resume the host runs that stored artifact. A caller-supplied artifact is only a check. If the hash differs, resume fails with an artifact mismatch. A newer deploy does not silently retarget a waiting Execution. That pin is part of recovery. The continuation’s control position is meaningful for the artifact that produced it. Mixing a new blob with an old continuation is not a resume. See [Versions](https://trigora.dev/docs/cloud/versions/) and [How recovery works](https://trigora.dev/docs/concepts/recovery/). ## Effect identity https://trigora.dev/docs/concepts/effect-identity/ A committed effect outcome is reused by its key. The function may run again if it never committed. `effect("generate", fn)` has an identity: the name at that position in the Program. After the outcome commits, recovery returns the stored value and does not call `fn` again. If the process dies after `fn` started and before the outcome commits, `fn` can run again. The external system sees that. Trigora does not add exactly-once delivery to Stripe, email, or any other API. Use a stable name. Do not build the name from data that changes across a retry of the same logical step, or you will get a second effect instead of a reuse. Unnamed `effect(fn)` is rejected. See [Effects](https://trigora.dev/docs/authoring/effects/) and [Production considerations](https://trigora.dev/docs/concepts/production/). ## Replay vs continuation recovery https://trigora.dev/docs/concepts/replay-vs-recovery/ Replay reconstructs position from history. TCC loads a committed continuation. ```text Replay start ── A ── B ── C ── D ── ✕ └──── reconstruct ───► D TCC start ── A ── B ── C ── D ── ✕ │ restore ``` History replay keeps a log of completed steps and runs the Program forward until it reaches the failure. Prefix length is part of recovery work. TCC commits a continuation at D’s durable boundary and restores it during recovery. The completed prefix is not an input to recovery. More precisely, TCC eliminates prefix replay when recovering from a committed continuation checkpoint. This does not imply constant-time recovery, exactly-once external I/O, or zero checkpoint overhead. Checkpoint cost depends on the frequency of durable boundaries and the persistence guarantees of the host. Read the comparison and its labels on [Research](https://trigora.dev/research/recovery-vs-history/), and the claim in the [technical report](https://trigora.dev/research/whitepaper/). The operational version is [How recovery works](https://trigora.dev/docs/concepts/recovery/). ## Production considerations https://trigora.dev/docs/concepts/production/ Operational guidance for running Trigora Programs in production. Review these considerations before a production workload on Trigora Cloud or Trigora Pulse. Cloud runs the process for you. Pulse runs on machines you operate, and you keep a compatible artifact available while older executions are still pinned to it. That operational split is in [Pulse limits](https://trigora.dev/docs/pulse/limits/). ## Effects and external systems After an effect outcome is committed, recovery reuses that outcome. If the process fails after the effect starts but before its outcome commits, the effect may run again. For external APIs that are not inherently idempotent, provide an idempotency key to `fn` and store it in a system that survives retries. Trigora does not provide exactly-once delivery to external systems. ## Artifact pinning Waiting Executions stay on the artifact they started with. Deploying a new version does not migrate them. Cancel and start again if you need the new code. See [Artifact pinning](https://trigora.dev/docs/concepts/artifact-pinning/). ## Cancellation and retries `trigora cancel` stops that Execution. It does not undo a committed effect. Compensate in your own system if you need to. Trigora does not expose a separate retry-policy object. Re-execution of an uncommitted effect follows the failure behavior described above rather than a configured retry count. ## Secrets Store `TRIGORA_TOKEN` in the environment or a `.env` file, never in source control. Event payloads and Program constants may be stored with the Execution, so do not use them for credentials or other sensitive values. Trigora Cloud stores project secrets encrypted at rest. Effect code reads the current values from the runtime environment when the effect runs. A replace or delete applies on the next effect attempt. An attempt that has already started keeps the environment it started with. Secret values are not pinned to a Program version. A Program deployed before secrets support must be redeployed once before its effects can see managed secrets. After that deploy, changing a value does not require another deploy. `trigora dev` does not use Cloud secrets. It uses the environment of the local process, including a project `.env` file. See [Secrets](https://trigora.dev/docs/cloud/secrets/). ## Payloads Inputs, event payloads, and results are stored as JSON on the Execution record and count toward durable storage. Trigora does not publish a separate byte limit for these payloads. Keep them small, and use external object storage rather than event payloads for large files. ## Language Constructs outside the supported language profiles fail at compile time. See [Unsupported constructs](https://trigora.dev/docs/authoring/unsupported/). `Promise.all`, `Promise.race`, `gather`, `race`, and Rust `join` / `race` compile only in the forms documented under [Structured concurrency](https://trigora.dev/docs/authoring/structured-concurrency/). Unsupported forms cannot be deployed and never run under an alternate recovery model. ## Billing Cloud meters durable operations, measured execution CPU, and durable storage. Pulse meters durable operations and storage. Pulse compute is not charged. Waiting is free. Free includes $5 of monthly usage across Cloud and Pulse and has no overages. When that credit is exhausted, new top-level starts are refused. Executions that already exist continue, including timers, events, child executions, and recovery. Pro is one $50/month workspace subscription, fully credited toward usage on both. The invoice is the greater of usage and $50. Details: [Usage & billing](https://trigora.dev/docs/cloud/usage-and-billing/). ## Recovery Recovery restores a continuation instead of replaying the completed prefix. This does not eliminate failures in external systems called by effects. The measurements in the technical report describe the tested workloads and should not be interpreted as a general latency guarantee. See [How recovery works](https://trigora.dev/docs/concepts/recovery/). ## TypeScript SDK https://trigora.dev/docs/reference/typescript-sdk/ Signatures for @trigora/sdk. Import from `@trigora/sdk`. These functions throw outside an Execution. Program globs and triggers live in [`trigora.toml`](https://trigora.dev/docs/client/config/). ## effect ```ts function effect(name: string, run: () => T | Promise): Promise ``` Commits the outcome of `run` under `name`. Reused after commit. May run again if the outcome never committed. Empty `name` throws. [Details](https://trigora.dev/docs/authoring/effects/). ## waitForEvent ```ts function waitForEvent( eventOrName: EventDefinition | string, options?: { timeout?: string | number; match?: Record }, ): Promise ``` Suspends until `trigora send` delivers that event name. Returns the payload. [Details](https://trigora.dev/docs/authoring/wait-for-event/). ## sleep ```ts function sleep(duration: string | number): Promise ``` Number of milliseconds, or a duration string. Prefer a number. [Details](https://trigora.dev/docs/authoring/sleep/). ## invoke ```ts function invoke(program: string, ...args: unknown[]): Promise ``` Starts one child and returns its result. The values after the program id are the ordered argument vector. [Details](https://trigora.dev/docs/authoring/invoke/). ## execution ```ts execution.id execution.attempt execution.programId execution.signal ``` Reads the current Execution. `signal` is the cancellation signal for this attempt. ## event ```ts function event(name: string): EventDefinition ``` Optional typed name for `waitForEvent` and `send`. A string works without it. ## TypeScript client https://trigora.dev/docs/reference/typescript-client/ Signatures for createClient. ```ts import { createClient, TrigoraRuntimeError } from "@trigora/client"; function createClient(options?: { url?: string; token?: string; projectId?: string; fetch?: typeof fetch; }): TrigoraClient ``` Default URL is Cloud when `token` or `TRIGORA_TOKEN` is set (`https://api.trigora.dev`, or `TRIGORA_API_BASE_URL`). Otherwise `http://localhost:3477` or `TRIGORA_RUNTIME_URL`. ## Execution handle ```ts type ExecutionHandle = { readonly id: string; result(): Promise; send(event: EventDefinition | string, payload: TPayload): Promise; cancel(): Promise; }; ``` `result()` polls until a terminal status. `failed` rejects. `cancelled` rejects. ## Methods | Method | HTTP | |---|---| | `whoAmI()` | `GET /v1/whoami` | | `listProjects()` | `GET /v1/projects` | | `createProject(body)` | `POST /v1/projects` | | `deployProgram(body)` | `POST /v1/programs/deploy` | | `listPrograms(page?)` / `programs(page?)` | `GET /v1/programs` | | `getProgram(id)` | `GET /v1/programs/:id` | | `listProgramVersions(id, page?)` | `GET /v1/programs/:id/versions` | | `start(program, input)` | `POST /v1/executions` | | `getExecution(id)` | `GET /v1/executions/:id` | | `listExecutions(page?)` / `executions(page?)` | `GET /v1/executions` | | `sendEvent(id, name, payload?)` | `POST /v1/executions/:id/events` | | `cancelExecution(id)` | `POST /v1/executions/:id/cancel` | | `getResult(id)` | `GET /v1/executions/:id/result` | `listPrograms`, `listProgramVersions`, and `listExecutions` take an optional `{ limit?, cursor? }`. `listProjects` does not. Failures throw `TrigoraRuntimeError` with `status` and `code`. See [Errors](https://trigora.dev/docs/reference/errors/). ## Python SDK https://trigora.dev/docs/reference/python-sdk/ Signatures for the trigora authoring functions. ```python from trigora import effect, gather, invoke, program, race, sleep, wait_for_event ``` `effect`, `wait_for_event`, `sleep`, and `invoke` fail if called outside an Execution. `program` marks the entry. It is not a durable operation. ## effect ```python def effect(name: str, fn: Callable[[], T]) -> T ``` `name` must be a non-empty string. `fn` must be callable. [Details](https://trigora.dev/docs/authoring/effects/). ## wait_for_event ```python def wait_for_event(name: str) -> object ``` `name` must be a non-empty string. [Details](https://trigora.dev/docs/authoring/wait-for-event/). ## sleep ```python def sleep(duration: int | float | str) -> None ``` [Details](https://trigora.dev/docs/authoring/sleep/). ## invoke ```python def invoke(name: str, *args: object) -> object ``` `name` is the child program id, the file stem. The remaining values are the ordered argument vector. [Details](https://trigora.dev/docs/authoring/invoke/). ## gather and race ```python def gather(*branches: object) -> object def race(*branches: object) -> object ``` Each takes 1–32 direct durable calls. [Details](https://trigora.dev/docs/authoring/structured-concurrency/). The Program entry is one `@program` async function. The program id is the file stem. ## Python client https://trigora.dev/docs/reference/python-client/ Methods on trigora_client. Package: `trigora-client`. Import: `trigora_client`. A token selects Cloud (`TRIGORA_API_BASE_URL` or `https://api.trigora.dev`). Otherwise the client uses `TRIGORA_RUNTIME_URL` or `http://127.0.0.1:3477`. ```python from trigora_client import Client, TrigoraError, start def start(program_id: str, input: Any | None = None, *, url: str | None = None, token: str | None = None) -> ExecutionHandle ``` ## Client ```python Client(url=None, token=None, project_id=None) client.whoami() client.deploy(body) client.projects.list() / client.projects.create(body) client.programs.list(*, limit=None, cursor=None) client.programs.get(program_id) client.programs.versions(program_id, *, limit=None, cursor=None) client.executions.list(*, limit=None, cursor=None) client.executions.start(program_id, input=None) ``` `programs.list`, `programs.versions`, and `executions.list` take `limit` and `cursor`. `projects.list` does not. `token` defaults to `TRIGORA_TOKEN` when set. ## ExecutionHandle ```python handle.id handle.send(name, payload=None) handle.cancel() handle.result(poll_seconds=0.05) ``` `result` polls `GET /v1/executions/{id}`. `completed` returns the result. `failed` and `cancelled` raise `TrigoraError`. `TrigoraError` has `status` and `code`. ## Rust SDK https://trigora.dev/docs/reference/rust-sdk/ Authoring functions in the trigora crate. Import from the `trigora` crate. These functions fail outside an Execution. ```rust use trigora::{effect, invoke, join, race, sleep, wait_for_event}; ``` The Program entry is one `pub async fn main`. Parameters are exact arity with no defaults. It returns `Result`. Rust Program ids use the source file stem. For `lib.rs` and `main.rs`, the Cargo package name is used instead. See [Rust](https://trigora.dev/docs/authoring/rust/). ## effect ```rust effect(name, closure).await? ``` `name` is a non-empty string literal. A capturing closure is `move`. [Details](https://trigora.dev/docs/authoring/effects/). ## sleep ```rust sleep(duration_ms).await? ``` `duration_ms` is an `f64` millisecond count. [Details](https://trigora.dev/docs/authoring/sleep/). ## invoke ```rust invoke(child, args).await? ``` A tuple is the argument vector. A bare value is one argument. `()` is empty. [Details](https://trigora.dev/docs/authoring/invoke/). ## join and race ```rust join(left, right).await? race(left, right).await? ``` `join` returns `Vec` in input order. `race` returns the winning `T`. Both arguments are direct durable calls, and both branches share `T`. The Rust frontend accepts exactly two branches, while the engine supports groups of 1–32 branches. [Details](https://trigora.dev/docs/authoring/structured-concurrency/). ## wait_for_event ```rust wait_for_event(name).await? ``` `name` is a non-empty string literal. [Details](https://trigora.dev/docs/authoring/wait-for-event/). ## Rust client https://trigora.dev/docs/reference/rust-client/ Methods on the trigora-client crate. Crate: `trigora-client`. A token selects Cloud (`TRIGORA_API_BASE_URL` or `https://api.trigora.dev`). Otherwise the client uses `TRIGORA_RUNTIME_URL` or `http://127.0.0.1:3477`. ## Execution handle ```rust handle.send(name, payload)?; handle.cancel()?; handle.result()?; ``` `result()` polls until a terminal status. `failed` and `cancelled` return an error. Program lists, version lists, and execution lists take `Option`. `None` omits both query parameters. `projects().list()` takes no page. ```rust pub struct Page { pub limit: Option, pub cursor: Option, } ``` ## Methods | Method | HTTP | |---|---| | `whoami()` | `GET /v1/whoami` | | `projects().list()` | `GET /v1/projects` | | `projects().create(body)` | `POST /v1/projects` | | `deploy(...)` | `POST /v1/programs/deploy` | | `programs().list(page)` | `GET /v1/programs` | | `programs().get(id)` | `GET /v1/programs/:id` | | `programs().versions(id, page)` | `GET /v1/programs/:id/versions` | | `executions().start(program, input)` | `POST /v1/executions` | | `executions().list(page)` | `GET /v1/executions` | | handle `send` | `POST /v1/executions/:id/events` | | handle `cancel` | `POST /v1/executions/:id/cancel` | | handle `result` | `GET /v1/executions/:id/result` | See [Errors](https://trigora.dev/docs/reference/errors/). ## CLI reference https://trigora.dev/docs/reference/cli/ Commands and flags for the trigora binary. `trigora` is a Rust CLI. There is no login command. Node.js 22 and 24 are supported for the npm CLI launcher and TypeScript adapter. Python-only and Rust-only projects do not require Node.js. `TRIGORA_TOKEN` authenticates Trigora Cloud. It does not choose the target of a dual-target command. Those commands target `trigora dev` by default. `--remote` selects Cloud and requires the token. ## Dual-target `programs`, `executions`, `start`, `send`, and `cancel` accept `--remote`. ### programs ```bash trigora programs trigora programs --remote ``` ### executions ```bash trigora executions trigora executions --remote trigora executions inspect trigora executions inspect --remote ``` ### start ```bash trigora start trigora start --input trigora start --remote ``` `--input` is JSON text or a path to a JSON file. A JSON array is the ordered argument vector. A single JSON value is one argument. Omitting it sends `{}`. See [Programs](https://trigora.dev/docs/concepts/programs/). ### send ```bash trigora send trigora send --payload trigora send --remote ``` `--payload` is JSON text or a path to a JSON file. ### cancel ```bash trigora cancel trigora cancel --remote ``` ## Cloud-only `deploy`, `whoami`, and `secrets` always talk to Trigora Cloud. They require `TRIGORA_TOKEN`. They do not take `--remote`. ### deploy ```bash trigora deploy trigora deploy --program ``` Compiles discovered Programs and uploads them. ### whoami ```bash export TRIGORA_TOKEN=... trigora whoami ``` Prints the workspace for that token. ### secrets ```bash trigora secrets list trigora secrets set OPENAI_API_KEY trigora secrets delete OPENAI_API_KEY ``` Lists, sets, or deletes project secrets for the project named in `trigora.toml`. `set` reads the value from a hidden prompt, or from stdin when the value is piped. The value is not an argument. `list` and `delete` print names only. See [Secrets](https://trigora.dev/docs/cloud/secrets/). ## init ```bash trigora init trigora init --language python --name approval --example trigora init --language rust --no-example trigora init --force ``` `--language` is `typescript`, `python`, or `rust`. `--name` sets the project name. `--example` writes a starter program. `--no-example` omits that file. `--force` overwrites existing files. A terminal prompts for language, name, and whether to create an example. Without a terminal and without `--language`, init stays TypeScript. The example is on unless you pass `--no-example`. Each scaffold writes `trigora.toml` and `.env.example`. With the example, it also writes the program file: | Language | Project file | Program | |---|---|---| | TypeScript | `package.json` | `src/program.ts` | | Python | `pyproject.toml` | `src/program.py` | | Rust | `Cargo.toml` | `src/lib.rs` | See [trigora.toml](https://trigora.dev/docs/client/config/). ## pulse `trigora pulse start` compiles the project, starts the Pulse host, and starts the Node adapter. `trigora pulse connect` registers handlers with a host that is already running. Neither command is `trigora dev` or `trigora deploy`. ```bash trigora pulse start trigora pulse start --ephemeral trigora pulse connect --dir /pulse trigora pulse connect --dir /pulse --prepare ``` | Flag | Command | What it does | | --- | --- | --- | | `--ephemeral` | `start` | Keep the checkpoint in memory. Does not require `TRIGORA_TOKEN`. Does not recover after the host exits. | | `--dir` | `connect` | Socket directory the host already bound. Required. | | `--prepare` | `connect` | Write the handler manifest, print its path, and exit without registering. | `start` without `--ephemeral` requires `TRIGORA_TOKEN` on the host. The command fails closed when the token is missing. Do not set that token together with `TRIGORA_PULSE_AUTHORITY_URL`, `TRIGORA_PULSE_WORKSPACE_ID`, or `TRIGORA_PULSE_WORKSPACE_TOKEN`. The adapter prints `ready` after registration. That is not an execution. `trigora start` does not create a Pulse execution. The application sends `{ type: "start", executionId }` on the adapter session. See [Node](https://trigora.dev/docs/pulse/node/). ## dev ```bash trigora dev trigora dev --host localhost --port 3477 ``` Local HTTP runtime. Default URL `http://localhost:3477`. ```text trigora → trigora-local → tcc-host-sqlite → TCC Engine ``` `trigora` spawns `trigora-local`. That process drives `tcc-host-sqlite`, which persists continuations in `.trigora/state.db`. The Node helper compiles TypeScript and JavaScript and runs live effects for those Programs. Python-only and Rust-only projects do not start Node. ## bench ```bash trigora bench trigora bench --input --out bench.json ``` Measures one healthy local run. See [Benchmark and verify](https://trigora.dev/docs/testing/bench-and-verify/). ## verify ```bash trigora verify trigora verify --faults sample trigora verify --faults all --effects fixtures.json --out verify.json ``` Crashes after committed boundaries on a temporary in-process SQLite host and checks recovery against the healthy baseline. `--faults` is `sample` (the default) or `all`. These commands do not start `trigora dev` and do not target Trigora Cloud. `--effects` is required when the program has effects. `live` runs real handlers only while recording the baseline. Fault runs replay those recorded results. Any other value is a JSON file of effect key to result. `--events` is a JSON object of event name to payload, or to an array of payloads. `--input` matches `trigora start`. ## HTTP API https://trigora.dev/docs/reference/http-api/ The /v1 routes the CLI and clients call. Base URL: - Cloud: `https://api.trigora.dev` - Local: `http://localhost:3477` while `trigora dev` is running Cloud requests send `Authorization: Bearer `. Optional `X-Trigora-Project-Id` selects a project. If you omit it, the workspace default project is used. Local `trigora dev` starts with a `default` project and does not require a bearer token. Projects created through `POST /v1/projects` are persisted in the local SQLite state. Local `trigora dev` implements the developer runtime subset of `/v1`. `whoami`, trigger management and firing, billing, and usage remain Cloud-only. `trigora dev` does not serve `GET /v1/whoami`. The local deploy API can store and version Program artifacts. Deployed artifacts with no effect bundle can run directly. `trigora dev` does not execute uploaded Cloud effect bundles; effectful local Programs should be discovered from project source and run through the normal local development path. ## Routes | Method | Path | Purpose | |---|---|---| | `GET` | `/v1/whoami` | Token’s workspace | | `GET`, `POST` | `/v1/projects` | List or create projects | | `POST` | `/v1/programs/deploy` | Upload a compiled Program | | `GET` | `/v1/programs` | List Programs | | `GET` | `/v1/programs/:id` | One Program | | `GET` | `/v1/programs/:id/versions` | Versions | | `GET` | `/v1/programs/:id/triggers` | Triggers for one Program | | `PUT` | `/v1/projects/:projectId/triggers` | Replace the project’s trigger list | | `POST` | `/v1/executions` | Start. Body `{ programId, input? }` | | `GET` | `/v1/executions` | List | | `GET` | `/v1/executions/:id` | Inspect | | `POST` | `/v1/executions/:id/events` | Body `{ name, payload? }` | | `POST` | `/v1/executions/:id/cancel` | Cancel | | `GET` | `/v1/executions/:id/result` | `{ status, result?, error? }` | List routes accept `limit` and `cursor`. Webhook ingress is a separate host, with no workspace token: | Method | Path | Purpose | |---|---|---| | `POST` | `https://hooks.trigora.dev/:publicId` | Start the Program for that webhook trigger. Body is the Program input. `202` returns `{ executionId }`. | See [Triggers](https://trigora.dev/docs/cloud/triggers/). Workspace, billing, and usage routes exist under `/v1/workspaces/*`, `/v1/billing/*`, and `/v1/usage`. The CLI does not wrap every billing call. Metering is described in [Usage & billing](https://trigora.dev/docs/cloud/usage-and-billing/). Errors are JSON `{ "error": { "code", "message" } }`. See [Errors](https://trigora.dev/docs/reference/errors/). ## Errors https://trigora.dev/docs/reference/errors/ API error codes the client and CLI surface. Failed HTTP calls return: ```json { "error": { "code": "event_mismatch", "message": "..." } } ``` `@trigora/client` throws `TrigoraRuntimeError` with `status` and `code`. The Python client raises `TrigoraError` the same way. The CLI prints the message. | Code | When | |---|---| | `unauthorized` | Missing or rejected Cloud token | | `forbidden` | Token cannot access the project | | `not_found` | Generic missing resource | | `program_not_found` | `start` or deploy target missing | | `project_not_found` | `X-Trigora-Project-Id` is unknown | | `execution_not_found` | Bad execution id | | `execution_not_waiting` | Send or a wait operation against an Execution that is not waiting | | `event_mismatch` | Event name does not match the current wait | | `execution_not_cancellable` | Cancel is not valid for this status | | `invalid_input` | Body failed validation | | `bad_request` | Malformed request | | `conflict` | State conflict | | `compiler_failed` | Frontend rejected the Program | | `rate_limited` | Too many requests | | `internal_error` | Host or API failure | The `compiler_failed` code indicates that the Program did not compile. Update the source to use supported language semantics and compile it again; recovery is not involved. See [Unsupported constructs](https://trigora.dev/docs/authoring/unsupported/). A missing `TRIGORA_TOKEN` on a Cloud CLI command fails in the CLI before HTTP, with `TRIGORA_TOKEN is not set.` ## Limits https://trigora.dev/docs/reference/limits/ The language, product, billing, and recovery limits that apply to Trigora Programs. Constructs outside the supported language profiles fail at compile time. ## Language - TypeScript (`ts.subset.v1`): one default-export async function. A missing or `undefined` argument uses a call-time default, otherwise it is `undefined`. Extra arguments are ignored. Explicit `null` stays `null`. The program id is the named export, or the file stem when the export is anonymous. - Python (`py.subset.v1`): one `@program` async function. Extra arguments are a start error. An omitted argument uses a compile-time constant default when one exists, otherwise it is a start error. The program id is the file stem. - Rust (`rust.subset.v1`): one `pub async fn main`. Exact arity, no defaults. A missing argument is a start error. Rust Program ids use the source file stem. For `lib.rs` and `main.rs`, the Cargo package name is used instead. - Durable operations: `effect`, `waitForEvent` / `wait_for_event`, `sleep`, `invoke`. Rust also has `join` and `race`. They stay in the program entry. - Effect keys and event names are string literals. - Between durable operations, ordinary computation includes arithmetic, objects, arrays, structs, enums, helpers, closures, and loops. A helper or closure frame cannot perform a durable operation. See [Supported language semantics](https://trigora.dev/docs/authoring/supported-semantics/). - Structured concurrency uses TypeScript `await Promise.all` / `await Promise.race`, Python `await gather` / `await race`, and Rust `join` / `race`. The engine supports groups of 1–32 branches, while the Rust frontend accepts exactly two. See [Structured concurrency](https://trigora.dev/docs/authoring/structured-concurrency/). - Python `asyncio` concurrency primitives are not supported. TypeScript `Promise.any` and `Promise.allSettled` are also unsupported. - `invoke` passes the same ordered argument vector a fresh start binds. Resume does not bind again. Unsupported constructs fail at compile time (`compiler_failed` or a CLI compile error). ## Product - Authenticate Trigora Cloud commands with `TRIGORA_TOKEN`; the CLI does not provide a `trigora login` command. - Trigora does not provide generated typed clients. - Webhook bodies are capped at 1 MiB. Cron schedules are 5-field. A webhook trigger has no static input. See [Triggers](https://trigora.dev/docs/cloud/triggers/). - Waiting Executions do not move to a newly deployed artifact. - Pulse checkpoints are capped at 16 MiB. The Pulse adapter is Node.js 22 or 24. There is no Python or Rust Pulse adapter, and no migration of a continuation onto another program version. See [Pulse limits](https://trigora.dev/docs/pulse/limits/). ## Billing Waiting wall-clock time is not billed separately. Trigora Cloud meters CPU time, durable operations, and durable storage. See [Pricing](https://trigora.dev/pricing/) for published rates. No separate payload byte limit is published here. ## Recovery Recovery resumes from a committed continuation rather than replaying history. It does not provide exactly-once external I/O or guarantee constant-time recovery. See [How recovery works](https://trigora.dev/docs/concepts/recovery/) and [Production considerations](https://trigora.dev/docs/concepts/production/).