evoke

Examples · 06 of 06

Maker-checker approval: one person asks, another approves.

Two-person approval for a payment typed in plain words. A reflex is a small program you install and ask for in your words. Here one reflex pays a payee on your list. The maker's sentence becomes a decision that waits as plain data until the checker, a second person, confirms it. Paying is destructive, so it never runs without a yes, and in an app your code decides who gives it.

The request and the result

01

The maker types. The checker confirms.

The decision travels from one person to the other through a queue. Deciding and paying are two separate moments. Recorded on 21 September 2026 against Jev, TypeSafe AI's model, which reads each sentence.

~/desk
$ node checker.ts pay payee="acme" account="ops" amount="12400" ref="invoice 8812" pay payee="acme" account="ops" amount="12400" ref="invoice 8812" · destructive · weakest: route 1.00 paid 12400 from acc_ops, transfer tr_0f3a
  1. 1The maker's sentence becomes a call. The payee and the account are words from your lists. The amount is a number read from the sentence, between 1 and 250,000.
  2. 2The decision waits as plain data. It holds the call, the effect, the least sure answer and why it stopped. «weakest: route 1.00» means the least sure answer was the choice of reflex, at 1.00.
  3. 3Only confirmed: true runs it. The checker reads the call and the reason, then runs that same decision. In this example, one file turns the decision into JSON and back. Your app would store it between the two people.

The operations

02

One payment reflex, used by the maker and the checker.

The payment reflex as a file and as code, your payees, and the checker that runs a queued decision. In code, with() gives the same reflex each team's own payees and accounts.

pay/reflex.tomlthe author's
reflex = 1

description = """
Pay an approved payee from one of our accounts.
One transfer, released after a second person confirms it."""
not_for = [
  "adding or changing a payee",
  "moving money between our own accounts",
  "asking a balance",
]
tags    = ["treasury"]
effect  = "destructive"
confirm = "Pay {payee} {amount} from {account}?"
run     = "pay.mts"

[needs]
hosts = ["*"]                          # the payments API

[config]
api   = "The payments API address"
token = { about = "The payments API token", secret = true }

[args.payee]
ask   = "Which payee?"
vocab = "payees"

[args.account]
ask   = "From which account?"
vocab = "accounts"

[args.amount]
ask   = "How much?"
pick  = "number"
range = [1, 250000]

[args.ref]
ask      = "What reference?"
pick     = "quoted"
optional = true

[examples]
'pay Acme 12400 from ops, ref "8812"'              = { amount = "12400", ref = "8812" }
"send 950 to the cleaners from the office account" = { amount = "950" }
"wire 3200 to Acme from treasury"                  = { amount = "3200" }

[tests]
'settle 18000 with Acme, ref "Q3"' = { amount = "18000", ref = "Q3" }
"pay Acme from ops"                = { amount = false, ref = false }
"add Acme as a new payee"          = false
vocab/payees.tomlyours
acme     = { what = "Acme, the packaging supplier.", value = "py_acme" }
cleaners = { what = "Blitz, the office cleaners.", value = "py_blitz" }
  1. 1A payee is a word. The classifier reads the word and its meaning. The program gets its value, the payee's id in your payments system. No sentence can name a payee you did not list.
  2. 2The token is a secret. The manifest declares it, and you name the variable that holds it. The value is read from the environment for one run, and never stored.
  3. 3The decision survives a trip through JSON. run() takes it back, and refuses one made under other reflexes or other words. So the checker loads the same ones as the maker.

One boundary

03

It asks for the missing amount, then asks for a yes.

At a terminal, the same reflex asks for what the sentence lacked, then asks for a yes. A no exits with 2, and nothing was paid.

evoke
$ evoke "pay Acme from ops"
  How much?  > 12400
  pay payee="acme" account="ops" amount="12400" · destructive · weakest: route 1.00
  Pay acme 12400 from ops?  [y]es [n]o [t]each > n
[2]

evoke was sure at 1.00, and it still asked. At a terminal, no flag skips a destructive reflex's question. In an app, only run(d, { confirmed: true }) runs it, so your code gives the yes and decides who may. Every value is one the reflex allows, but a reading can still be wrong, so a payment always waits for a yes. What the classifier cannot do → Why a payment always waits for a yes →

Set it up

04

Pass one payment from maker to checker, with the SDK.

code.ts above holds the reflex, your lists, the maker and the checker, so it runs on its own. Point it at a test instance of your payments API: the checker's run makes a real transfer.

  1. Make the project

    A folder for the app, with a package of ES modules and the SDK.

    mkdir -p ~/approval && cd ~/approval && npm init -y && npm pkg set type=module
    npm install @evoke-build/evoke
  2. Save the file, with your lists

    Save code.ts from the code tab above in ~/approval. Put your payees and accounts in its lists, with the ids your payments system uses as values.

  3. Pass one payment through

    The maker's sentence is decided and sent through JSON. The checker prints the call and why it stopped, then gives the yes itself, with confirmed: true, so the transfer is made at once. Your app would show the call to a second person, and pass confirmed: true only on their yes.

    export TYPESAFE_API_KEY=<value>
    export PAYMENTS_API=<address>
    export PAYMENTS_TOKEN=<value>
    node code.ts

In an app that serves several teams, pass each team's lists with with(). The checker must build the same lists, or run() refuses the decision.