All docs

Docs · Core concepts

TXCL

the rule language

In Thanks, Computer, every operation is gated by a rule written in TXCL — a few readable lines saying when it resonates and what it contributes to the flow.

The smallest rule is one line:

EMIT .hello = "world"

Every event gets {"hello": "world"} merged in. Real rules almost always start with a condition — and that’s the idea to get first.

The resonator — fire only when needed

A stack may hold dozens of operations, but for any given event most of them should stay silent. The rule attached to each operation is called a resonator: a filter that decides whether the operation fires at all. Like a tuning fork, it’s cut for a particular kind of event and only rings when one matches — everything else passes by untouched.

The WHEN clause is the resonance condition:

WHEN @web.req.url.path == "/invoice"   # fire for one path
WHEN .amount > 1000                    # fire when the payload says so
WHEN .tier == "vip"                    # fire on what a previous step found

That last one is the trick that makes flows compose. Every operation’s output merges into the shared event, so a condition on .tier is really a condition on what an earlier step concluded. The classifier doesn’t call the VIP handler — it just emits .tier = "vip", and at the next step the VIP handler’s resonator picks it up. Steps chain by resonance, not by wiring.

That’s the shape it creates — handlers sitting in parallel at a step, the previous step’s conclusion deciding which one rings:

orders
input100classify200vipstandardoutput

A condition plus a dispatch is a working integration:

WHEN .tz == "ams"
EXEC "https://timeapi.io/api/v1/time/current/zone?timezone=Europe%2FAmsterdam"

Only events with .tz == "ams" reach the URL; the HTTP response merges back into the event.

The seven clauses

A resonator has up to seven clauses — all optional, evaluated in this order:

ClauseWhat it does
WHENThe resonance condition (omitted or * = always fire)
SETSet fields on the event before dispatch
SELECTProject what the operation receives
WITHPer-call directives — timeout, secrets.*, redact, …
PRIORITYTie-breaker among matches at the same step
EXECDispatch target — op://, http(s)://, txco://, mcp+https://
EMITOverlay values onto the response, after dispatch

A complete rule using most of them:

WHEN .user.id =~ /^u_/
SET .source = "thanks-computer"
WITH timeout = 2000
EXEC "https://api.example.com/enrich"
EMIT .enriched_at = &now("rfc3339")

Reading paths

Paths are dot-paths into the event JSON. .user.id reads the payload; @ is shorthand for ._txc. — the chassis’s envelope — so @web.req.url.path is the request path, @halt = true stops the flow, and @goto = "billing/0" jumps to another stack.

Values can compute

Values are literals or function calls: &uuid(), &now("rfc3339"), &json(...), &b64decode(...) — inline, pure computation without dispatching anything.

Private by convention

The last step’s context becomes the output. Output hides any part of the context document that starts with an _ by convention. Thus, _shh is private and hi is returned in the answer.

That’s most of it

Keywords are case-insensitive, # starts a comment, whitespace is free-form — a rule on one line and the same rule on five are identical. See Operations for what EXEC can point at.

Edit this page · View as markdown