evoke

The reflex format.

One file that a classifier reads and a program receives. This page is the standard: the format of that file, the decision made from it, the gate that runs it, the files around it, the adapter contract and the wire.

Identifier
reflex = 1 · the first key of every manifest
Status
Frozen. Keys are never removed or given new meanings. The number bumps only when an old client must refuse.
Since
evoke 0.1.0, 2026-09-21 · this page describes the format as 0.4.0 implements it
Schemas
reflex.json · overlay.json · vocabulary.json · evoke.json
Conformance
spec/ in the repository: the schemas, golden vectors for every rule, and transcripts of the terminal, word for word, run in CI without a key.
Licence
Apache-2.0, with the tool. Anyone may implement it.

The words MUST, MUST NOT, SHOULD and MAY are used as in RFC 2119. Where this page and the executable spec disagree, the spec is right, and this page has a bug.

Scope

01

What the format covers, and what it leaves to others.

A reflex is a directory: a manifest, reflex.toml, and the file or program it names. The manifest states meaning, never rendering. How its words become questions is the implementation's business, so one manifest serves every engine.

1.1Covered

  • The manifest. Its keys, its text rules, what is wording and what is contract.
  • The decision. The questions built from a manifest, the answers, the four outcomes.
  • The gate. How a confidence is read, how an effect sets its bar, what always asks.
  • The body. What a program receives, what it returns, how it is run.
  • The files. A project, an overlay, a vocabulary, the lock, and how they merge.
  • Distribution. Where a reflex comes from, how it is pinned, what a version means.
  • The adapter and the wire. The contract an engine fits, and the JSON every decision prints.

1.2Not covered

How an engine produces a probability. What a body does with its arguments. How a host draws a prompt. An implementation MAY do each as it likes, as long as what this page states holds.

Terms

02

The words, in one place.

Every term below is used on this page in exactly this sense. The glossary holds the full list.

Reflex
A directory: a manifest and the file or program it names. Its identity is its location at a git tag.
Manifest
reflex.toml: what the classifier reads and what the body receives.
Body
What run names: a JavaScript file exporting a function, or a program with its arguments.
Wording
What a user may override: descriptions, questions, option meanings, records.
Contract
What a user cannot override: run, argument names and sources, option keys, ranges, config keys, yields.
Argument
A question about the input and a value for the body, from exactly one source.
Source
Where an argument's values come from: options, vocab, pick or flag.
Span
An exact piece of the input, by character offsets.
Vocabulary
The user's closed list of words, shared by every argument that names it.
Overlay
The user's wording for one reflex, merged over the shipped manifest.
Project
A directory of files a user owns: evoke.toml, evoke.lock, overlays/vocab/.
Adapter
The classifier behind a decision, as an object that answers typed questions with probabilities.
Decision
One input decided: an outcome, the call, the confidence, every judgment.
Call
A reflex with its arguments filled in, on one line: lights room="den" state="off".
Effect
What running a reflex does to the world: read, write or destructive.
Bar
The probability a decision must clear for its effect. The adapter ships it. Also called the floor.
Confidence
The lowest top probability among the route and every argument question of the winner.
Weave
A sentence read as several steps, each decided on its own, the plan shown before anything runs.
Yield
A field of a result a later step may take, declared under [yields] with the kind that reads it.
Plan
The installed set compiled to questions. Its digest keys the cache and every decision.

The manifest

03

One file. What it reads is what it runs.

A manifest has no name and no version. Its name is whatever each user installs it as. Its version is the git tag it was fetched at. The whole shape, key by key:

reflex.tomlthe author's
reflex = 1                                   # the format's version; frozen

description = """
Turn the lights in one room on, off, or dim them.
Ceiling and lamp lights only."""
not_for = ["colour scenes and schedules", "asking whether a light is on"]
tags    = ["home", "lighting"]
effect  = "write"                            # absent means destructive
confirm = "Set the {room} lights {state}?"
run     = "lights.mts"                       # or an argv, as in 08

[config]
bridge = "Hue bridge address"
token  = { about = "Hue API key", secret = true }

[args.room]
ask   = "Which room?"
vocab = "rooms"                              # options come from the user

[args.state]
ask = "What should the lights do?"
options.on  = "Switch on."                   # options come from the author
options.off = "Switch off."
options.dim = "Lower the brightness without switching off."

[args.brightness]
ask      = "How bright, in percent?"
pick     = "number"                          # options come from the input
range    = [1, 100]
optional = true

[yields]                                     # for a later step to take
level = "number"

[examples]                                   # sent to the classifier
"turn on the kitchen lights"   = { state = "on" }
"dim the office to 30 percent" = { state = "dim", brightness = "30 percent" }

[tests]                                      # held out, never sent
"make it darker in here" = { state = "dim" }
"light a candle"         = false
  • 1Four keys are required. reflex, description, confirm and run. Everything else is optional, and a manifest with nothing else is a reflex that takes no arguments.
  • 2The first line of the description is the summary, like a git subject line. Listings and wide rankings show it. The rest draws the boundary.
  • 3Absent effect means destructive. The effect is the author's claim. A user may tighten it, never loosen it.
  • 4The same argument names are what the body destructures. The question the classifier answered is the parameter the program gets.

3.1Keys

KeyRequiredMeaning
reflexyes1. The format's version.
descriptionyesWhat the reflex does, in the user's words. Its first line is the summary.
not_fornoWhat it does not do: its near neighbours. Read as the no side of the fit question.
tagsnoWords for --tag to narrow a decision by. No other meaning.
effectnoread, write or destructive. Absent means destructive. A reflex has one effect; grouped actions take the worst case.
confirmyesThe one-line question a person answers. A {placeholder} names a required argument.
runyesThe body: a path ending in .mts or .mjs inside the directory, or an argv.
[config]noSettings the user provides: key = "about", or { about, secret = true }. A secret is only ever set from an environment variable.
[args.<name>]noThe arguments. Each has ask and exactly one source.
[yields]noWhat the body's data holds for a later step: per field, the kind that reads it, or { each = { … } } for a list of records.
[examples]noUtterances with what they assert. Sent to the classifier.
[tests]noThe same shape, held out. Never sent; run by evoke test.

3.2Text rules

Every string MUST be clean text: no control characters, except the line feed inside description, and no bidi controls. An implementation MUST refuse the rest, so a manifest can never repaint a terminal. Names of arguments, tags, config keys and vocabularies match [a-z][a-z0-9_]*. An argument name is never a JavaScript reserved word, so a body can destructure it. Option keys are one clean line each. none and unstated are reserved. Unknown keys are reported, never fatal.

3.3Wording and contract

A manifest is two things. A user MAY override the wording in an overlay. A user MUST NOT be able to change the contract: a contract key in an overlay makes the reflex inactive, never looser. Changing the contract is a version bump.

ContractWording
rundescription, not_for, tags, confirm
argument names and their sourcesevery ask
option keys, rangethe meaning of each option
config keys, yieldsexamples and tests

3.4Freezing

reflex = 1 is frozen. A key is never removed and never given a new meaning. A renamed key stays an alias. An unknown key is reported, never fatal, so the format is open to additions; the schema is strict, so an editor flags a typo first. The number bumps only when an old client must refuse a manifest. Every manifest ever tagged stays readable.

Arguments

04

One question. One source. Never a guess.

An argument is a question the classifier answers about the input, and a value the body receives. Each argument has an ask, and exactly one source for its values: the author, the user, or the input. An argument with two sources, or none, is invalid.

4.1The four sources

SourceValues come fromThe body receivesAsked when missing
optionsThe author: key = "meaning"The keyA numbered choice
vocabThe user: vocab/<name>.tomlThe word's value if set, else the wordA numbered choice, plus [+] add one
pickThe input, as typednumber the number; duration whole seconds; email, url, quoted the textTyped freely, read by the recognizer
flagThe inputtrue, or nothingNever

optional = true means an unstated argument is omitted and the body's own default applies. A required argument left unstated MUST be asked for. A flag is optional by nature and takes no optional. A shipped manifest MUST NOT assert a vocabulary argument in its records, because the words are the user's.

4.2Picks

A pick reads a piece of the input, word for word. Five recognizers exist, and they run in code. The classifier chooses among the candidates found. It never invents one. A pick with no candidate reads as unstated.

pickRecognizesValuerange
numberA bare number, unit words kept in the span: 30 percent; a leading minus is the number's: -5The numberyes
durationOne number and one unit: 10 minutes, 2 hours, 90 secondsWhole secondsyes
emailAn addressThe textno
urlA URLThe textno
quoted"…", “…” or ‘…’; a straight single quote is an apostropheThe text between the quotesno

Quotes hide what they enclose, and a typed span hides the bare numbers inside it. range = [min, max] compares the value, in seconds for a duration, and is allowed on number and duration only. Out of range, the implementation asks, with the reason on the line.

4.3Renames

To rename an argument, an author declares its former names and keeps them declared forever: was = ["state"]. was is flat and cumulative. A retired name never returns as a live argument, and it never leaves the list. Every user's overlay and every call written with the old name follows the rename at merge time.

4.4Placeholders

confirm names required arguments only. A placeholder for an optional argument or a flag is an error. In an argv, a placeholder is a whole element, naming an options, vocab or pick argument. An element whose optional argument is unstated is dropped. A value that would start with - is refused. Flags and literal braces need a file body.

Records

05

One line is documentation, tuning and test at once.

Two tables, one shape. [examples] are sent to the classifier: they teach. [tests] are held out, never sent: they check. A user's overlay adds lines of the same shape.

5.1Assertions

RecordMeans
"utterance" = {}The input routes here. Nothing is said about arguments.
{ arg = "key" }An option: its key.
{ arg = "span" }A pick: the exact span, which MUST occur in the utterance. For quoted, the text between the quotes.
{ arg = false }The argument is unstated.
{ flag = true }The flag is raised.
"utterance" = falseNever this reflex. It teaches the no side, beside not_for.

An argument a record does not name is not asserted. An example that asserts an option teaches it: "kill the lights" = { state = "off" } says this input is lights, and that kill means off.

5.2Identity

Records are keyed by the utterance as written, and the classifier reads that spelling. Two records are the same utterance under identity: NFC, lower-cased, whitespace collapsed and trimmed, and a trailing . ! ? or dropped. Duplicates in one file MUST be refused. A user's line naming a shipped utterance replaces it, so the highest layer decides each utterance's table and value.

5.3Limits

Lint reports and never refuses. A manifest SHOULD keep its summary under 100 characters and its description under 1 000, list at most eight not_for entries, offer at most 24 options on one argument, hold at most 40 records per table, and keep every utterance under 200 characters. Text that addresses a model instead of describing an action is named.

The decision

06

Three questions, one request, four outcomes.

For one input, every active reflex's manifest becomes questions, and one request carries them all. The adapter answers each with a probability. Code reads the answers and settles the outcome. The classifier proposes; the gate disposes.

6.1The questions

QuestionKindBuilt from
Which reflex?One choice over every active reflex, plus noneEach reflex's description as what, its not_for as what it is not for, and its examples
Does it fit?A yes/no per reflexdescription as yes, not_for as no
Which value?One choice per argument, plus unstatedThe ask, each option's meaning, and the examples that assert it

State is only the request: the sentence, and the questions built from installed manifests. Nothing else of the user's. A pick's candidates are found in code first, so the classifier chooses among spans that exist. An inactive reflex, one that lacks a word or a setting, is left out of every question.

6.2The answers

An implementation MUST validate every answer and fail closed. A choice is a distribution over the keys offered. An answer that names a value no argument offered, a key that is not a probability, or a question left unanswered, ends the decision with a fault, and nothing runs.

6.3The outcomes

OutcomeWhenExit
runConfidence cleared the bar for the winner's effect, every required argument was stated, nothing else needed attention.0
confirmThe call is complete, but something holds it at a question: destructive, no gate, under the bar, an unconsumed span, or a runner-up that fits.0 · 2
askThe winner is clear, but a required argument is missing or out of range. Its own question is asked, and the answer is gated again.0 · 2 · 3
abstainnone won, or the winner is under the route bar. The ranking prints and nothing runs.2

A decision MUST refuse an input over 2 000 characters, and MUST stop at 30 seconds, shared between the classifier's answer and the body's run. A prompt waiting for a person never counts. When the deadline passes, the implementation stops and says so. It never guesses.

6.4Every judgment, shown

An implementation MUST be able to show every judgment of a decision without running it, and to explain the last decision from its log alone. In evoke that is try and why.

evoke
$ evoke try "kill the lights"
  lights 0.90 · none 0.06 · timer 0.02 · volume 0.02
  room   unstated 0.75 · den 0.20 · office 0.05
  state  off 0.58 · on 0.30 · dim 0.10 · unstated 0.02
  fits   lights 0.70 · timer 0.05 · volume 0.05
  ask room · weakest: state 0.58

The route, each argument with unstated as a choice, the fit per reflex, then the outcome and the weakest judgment. The room was not said, so the outcome is an ask.

The gate

07

The weakest answer, against the bar of the effect.

Confidence is the lowest top probability among the route and every argument question of the winner. Unstated arguments and flags count too. Each effect has a bar, and the bars come from the adapter. Each number means the probability this is right.

7.1The bars

BarJevGates
route0.5Under it, abstain.
read0.6A read reflex runs at or above it.
write0.8A write reflex runs at or above it.
fits0.3A runner-up at or above it holds the outcome at confirm: the input may have asked for two things.
destructiveAlways confirms.

7.2Rules that hold

  • A destructive reflex always confirms. No number and no flag skips its question. There is no --yes. Unattended use is a bar in a file the user owns and trusted.
  • read never exceeds write. A user MAY move a bar for their own machine, under the adapter's table in evoke.toml, within that rule.
  • The core never rescales. The adapter's numbers are its own calibration. An adapter that ships no bars never runs anything on its own: every decision confirms.
  • Effect only tightens. A user's overlay may tighten an effect, never loosen it. The lock holds the effect the user consented to. Upstream may tighten it at any time; it loosens only through evoke update --accept.
  • A complete call still stops. A typed span no argument consumed, or a runner-up over its bar, holds the outcome at confirm, with the reason on the line.

The body

08

A function, or a program. Called once, with a deadline.

The body is what run names. It is called once per decision, with the arguments filled in, a context, and a deadline. It returns one line a person reads, and data an application may use.

lights.mtsa file body
import type { Reflex } from "./reflex.d.ts"

export default (async (
  { room, state, brightness = 30 },
  { config, signal },
) => {
  const on = state !== "off"
  const bri = state === "dim" ? brightness : 100
  await hue(config, room, { on, bri }, signal)
  return `${room} lights ${state}`
}) satisfies Reflex
reflex.tomlan argv body
run = ["networksetup", "-setairportpower", "en0", "{state}"]

# the program first, a literal
# a placeholder is a whole element
# never a shell
# config as EVOKE_CONFIG_<KEY>, the input as EVOKE_INPUT
# stdout is the result; a non-zero exit is failure
# no runtime needed

8.1What a file body receives and returns

A file body is a module ending in .mts or .mjs, inside the reflex directory, exporting a function by default. It is self-contained: web APIs and node: builtins are there, anything else is bundled into the file. It receives (args, { input, config, signal }): per argument, an option's key, a word's value or the word, a pick's value, or true for a flag; the sentence as typed; each config key as a string, secrets resolved for this run only; and a signal that is aborted at the deadline, on a decline, and on SIGTERM. It returns a string, or { text, data? }. Throwing is failure.

8.2The run

  • The environment is scrubbed. PATH, HOME, TMPDIR, LANG and TERM, nothing else. Secrets reach a body through config, never the environment.
  • The deadline is 30 seconds per decision, shared with the classifier's answer. Anything that must outlive the run detaches and returns at once.
  • A body's own stdout goes to stderr, so a stray line never corrupts a result. The result is what the function returns.
  • The process group is ended on timeout or decline: SIGTERM, a second's grace, then SIGKILL. A body's life is bounded by its caller's.
  • An argv runs directly, never through a shell. The program is a name on PATH or an absolute path, never a path relative to wherever the tool runs.

The project

09

Files you own, and one rule to merge them.

A project is a directory of files a user owns. An implementation reads the nearest one and writes only inside it. It holds only what the user wrote, the lock and generated types. No fetched code, no secrets, no cache. Safe for dotfiles.

evoke.tomlyours
adapter = "jev"                  # who decides; a name, not a path

[reflexes]                       # local name = where it comes from
lights = "radhi/home/lights"     # unpinned: update moves it
timer  = "radhi/timer@1.0.1"     # pinned
hello  = "./hello"               # local: never locked

[config.lights]
bridge = "10.0.0.2"              # a value, stored plain
token  = { env = "HUE_TOKEN" }   # a secret: the variable only

[adapters.jev]                   # per adapter, under its name
gate = { write = 0.85 }
evoke.lockwritten by add, update, remove
lock  = 1
evoke = "0.1.0"

[adapter]
name = "jev"
id   = "jev-1.13.0"

[reflexes.lights]
ref    = "radhi/home/lights"
tag    = "1.2.0"
commit = "7a498d11d5375f3cb65c575c3186bc64ecac7f52"
h1     = "h1:a30b1bbcf52390c4b1311685ceee9f886b81b769…"
effect = "write"                # the effect you consented to

9.1The overlay

overlays/<name>.toml is the user's wording for one reflex, in the manifest's own shape. It is merged over the shipped manifest by one rule: tables merge by key, and every value replaces whole. An overlay adds and replaces. It never deletes. Lists replace whole. The effective manifest is the shipped one with the overlay merged in.

overlays/lights.tomlyours
description = "Turn the lights in one room on, off, or dim them."   # replaces whole
effect      = "destructive"                                        # may only tighten

[args.state]
ask = "On, off or dim?"                                             # new wording for an argument
options.dim = "Lower the brightness; the lights stay on."           # an existing key only

[examples]
"kill the lights" = { state = "off" }                              # sent to the classifier

[tests]
"turn on the kitchen lights" = { state = "on" }                    # a shipped example, held out now

An overlay MAY change wording: description, not_for, tags, confirm, each argument's ask, the meaning of its existing options, examples and tests. It MUST NOT change the contract: a contract key makes the reflex inactive, and names the line to remove. An overlay that fails to read makes its reflex inactive, never silently looser. A user's records MAY assert vocabulary arguments.

9.2The vocabulary

vocab/<name>.toml is a closed list of the user's words. Every argument that names the vocabulary shares it. A package MUST NOT ship or write one. A word is one clean line, trimmed, spaces allowed, unique under identity. none and unstated are reserved. The meaning is what the classifier reads. The value, when set, is what the program receives instead of the word, and the classifier never sees it.

vocab/rooms.tomlyours
den    = "The TV room downstairs; also 'the snug'."
office = { what = "The upstairs study.", value = "group-7" }

9.3The lock and the store

The lock records, for each remote reflex, the ref, the tag, the commit, the content hash h1 of the reflex directory, and the effect the user consented to. It records the adapter by name and id. Fetched code lives in a store keyed by h1, and an implementation MUST re-hash it against the lock at every start. A copy that does not match is a miss. Local reflexes are never locked.

9.4Trust

A project outside the home project MUST be trusted before an implementation decides in it. Trust binds to the content of the four owned paths: evoke.toml, evoke.lock, overlays/, vocab/. The implementation's own writes renew it. Any other change stops the next run until the project is trusted again. Adapter names resolve only against the tool's built-ins, never from a project directory, so a cloned repository cannot choose the classifier.

Distribution

10

Git is the registry. A tag is a release.

A reflex lives in a repository, at a version tag. Publishing is git push --tags. There is no account, no registry API, no build step and no upload. No install ever depends on an index.

10.1Refs

FormMeans
owner/repoEvery reflex directory in the repository, on GitHub.
owner/repo/dirOne reflex, the directory dir; dir may nest.
owner/repo/dir@1.2.0Pinned. Tags are X.Y.Z or vX.Y.Z. The newest is the highest.
https://host/repo.git#dir@1.2.0Any git host over https or ssh. #dir and @tag are optional.
ssh://git@github.com/owner/repoThe same over ssh, the user before the host.
./dir, ../dirA local directory, relative to evoke.toml. Never fetched, never locked.

10.2Fetching

A fetch is bare and shallow, read without a checkout, once per repository and tag. Symlinks and submodules MUST be refused. No code runs at install time. A repository with no version tag cannot be installed. A published tag never changes: the lock records the tag, the commit and the content hash, and a tag that moved MUST be refused. A fix is a new tag.

10.3Versions

One tag covers the whole repository. The next version is decided by the diff of the contract against the newest tag, and evoke check prints it:

LevelWhen
sameWording only: description, not_for, tags, confirm, any ask, option meanings, records.
minorAdditions, like an argument, an option, a config key or a yield. Also a config key removed.
majorSomething a user's files or calls may not survive: an argument or option removed or renamed, a source or range changed, run changed, a yield removed or changed.

A was violation is refused outright: a retired name returning, or a name dropped from the list. An update never prompts, never blocks and never rewrites a user's files. It reports what was rewritten and taken, what went stale, what was orphaned, and an effect loosened upstream, which the user keeps until they accept it.

10.4Installing

At install, an implementation MUST lint the manifest, and SHOULD route the examples already installed over the new set, naming every phrase the newcomer would steal, with its one-line fix. Lint and the theft test report. They never refuse.

The adapter

11

Any engine that answers a closed choice with probabilities fits.

An adapter is the classifier behind a decision, as an object that answers typed questions with probabilities. The core names no engine. A project names an adapter, and only the user's machine resolves the name.

11.1The contract

AdapterTypeScript
interface Adapter {
  // opaque; changes whenever answers could; compared, never parsed
  id: string
  // per-call ceilings; a plan over them is refused at load
  limits?: { options?: number; tokens?: number }
  // each number means P(correct)
  gate?: { route: number; fits?: number; read: number; write: number }
  // a recording's plan digest; another plan refuses it
  plan?: string
  answer(state: { request: string }, questions: Record<string, Question>, signal: AbortSignal): Promise<Raw>
}

type Question =
  // a choice: otherwise is the key meaning "none of these"
  | { type: "choice"; ask: string; options: Record<string, Text>; otherwise?: string }
  // a yes/no: answered as { yes: p }
  | { type: "yesno"; ask: string; yes: Text; no: Text }
type Text = string | { what: string; not_for?: string[]; examples?: string[] }
type Raw  = Record<string, Record<string, number>>   // per question, a number per key
  • State is only { request }. An adapter sees the input and the questions, nothing else.
  • A choice answer is a distribution over the keys offered, summing to 1. An omitted key reads as 0. A yes/no answers { yes: p }. The core validates every answer and fails closed. A missing question is a fault.
  • otherwise marks the sentinel, none or unstated, in the structure, so an engine may abstain its own way.
  • gate is the adapter's own calibration. The core never rescales. Without one, every decision confirms.
  • answer is stateless and may be called with any subset of a plan's questions. An adapter that ignores its signal is raced against it anyway.
  • id covers everything that could shift answers: model, version, prompt rendering, calibration. It is pinned in the lock. The endpoint is the adapter's own, built in with its id.

11.2The recording

A recording is an adapter over a file: the declaration, then the answers, keyed by utterance identity and question id. It answers offline, byte for byte. It is how the transcripts of the spec run without a key, and how an application's tests replay real answers.

answers.tomla recording
id = "replay"

[gate]
route = 0.5
fits  = 0.3
read  = 0.6
write = 0.8

[answers."kill the lights in the den"]
route          = { lights = 0.91, timer = 0.02, volume = 0.01, none = 0.06 }
"fits.lights"  = { yes = 0.7 }
"fits.timer"   = { yes = 0.05 }
"lights.room"  = { den = 0.85, office = 0.05, unstated = 0.1 }
"lights.state" = { off = 0.88, on = 0.05, dim = 0.05, unstated = 0.02 }

A pick's candidates are keyed <start>-<end> by their character offsets in the input. The recording carries the adapter's declaration, so the gate in tests is the gate in production.

The wire

12

One JSON line per decision. The same object everywhere.

A decision is data. The CLI prints it as one line under --json, the log holds the same line, and the SDK's Decision is the same object. Keys are snake_case. An absent optional is omitted, never null. A tagged value carries type. A decision carries outcome. Numbers are numbers.

12.1Fields, by outcome

FieldrunconfirmaskabstainHolds
inputThe sentence as decided.
outcomerun, confirm, ask, abstain.
reflex·The winner's local name.
args·Per argument, a typed value; partial for an ask.
call··The call on one line.
effect··read, write, destructive.
confidence·The weakest judgment's probability.
weakest·{ question, top, p }.
judgmentsEvery choice read: { question, top, p }.
contendersThe ranking: { reflex, route, fits? }.
runner_up·The second reflex by route, { reflex, route, fits? }, when there is one.
unconsumed···Typed spans no argument consumed, each { start, end, text }.
prompt···{ own, template }: the tool's line and the reflex's question.
because···Why it stopped, in order: destructive, no_gate, under_floor, unconsumed_span, two_things.
missing···Per missing argument: arg, ask, because, choices.
traceOne entry per adapter call: { adapter, questions, ms }. Empty from the cache.
result···{ text, data? } when the body ran, else error with the failure's message.

12.2Values and questions

the four valuesJSON
{ "type": "option", "key": "off" }
{ "type": "word", "word": "office", "value": "group-7" }
{ "type": "pick", "span": { "start": 18, "end": 28, "text": "10 minutes" }, "value": { "type": "duration", "value": 600 } }
{ "type": "flag" }

A question id is route, fits.<reflex>, <reflex>.<argument>, or weave.<name>: a question the tool asks on its own account, beside a reflex's. A choice's keys are option keys, vocabulary words, <start>-<end> for a pick's candidates, yes and no for a flag, local names for the route, and the sentinels none and unstated.

Several steps

13

A sentence read as steps, each decided on its own.

Every sentence is read for its steps. One step is a decision as above. More than one is a weave: each part decided on its own, in the order the words give, a result of one step threaded into a later one, and the plan shown before anything runs.

13.1Reading

  • A connective is a question. Where and, then, but, a comma, after or before could separate two things, the classifier is asked whether it does.
  • Each part is decided as one input is. Routed, gated, its arguments read. A part that matches nothing is tried as another item of its neighbour's task first. Failing that, the whole request MUST be refused rather than half done.
  • A part that begins with a negation is left out. not, don't, never, without: what was said not to do is no step.
  • Order words order. then, after that, next; before you X, Y and Y after you X both read as X, then Y. Two writes never run side by side. Reads may.

13.2Binding

A body MAY declare what its result holds under [yields]. A later step that refers to it, it, them, that report, takes a field of the right kind into the argument it lacks. A required argument is filled with the value. An optional one is decided again with the value written into the words, under the same gate as any words. A reference several fields could satisfy MUST be asked, never guessed.

evoke
$ evoke "look up dana's address and email them"
  1  contact name="dana"  0.90
  2  mail · takes email from 1
dana <dana@example.com>
  2  mail to="dana@example.com"  0.88
drafted to dana@example.com

13.3Before anything runs

The plan is settled first. A step whose required argument no other step provides is asked for it up front. Then the plan is shown, numbered, one line per step. Each step then goes through the same gate as one input. A failure, a decline or a refusal ends the weave after its stage; the steps after it are skipped, and say so. The exit code is the worst step's. Under --json, one line per step carries step, steps, bound and status beside the decision's fields.

Security

14

What a sentence can and cannot do.

A fetched reflex runs as the user. Install and update are the trust decisions. Around them, an implementation of this format guarantees the following, and states plainly what it does not.

14.1Guaranteed

  • State is only the request. The adapter sees the sentence and the questions built from installed manifests. Nothing else of the user's.
  • A value is a closed choice or an exact span. Injected text can choose a call. It can never mint a value.
  • The effect gate covers what it chooses. A destructive reflex always confirms, and there is no --yes.
  • Prompts cannot be repainted. Control characters and bidi overrides are refused in every manifest, overlay and vocabulary string, and in every span. The tool's own line prints before the reflex's template.
  • Wording is linted and conflict-tested at install. Effect only tightens. The contract cannot be overridden. Tags carry no meaning but --tag.
  • Trust and the lock bind to content. A changed project stops. A tag that moved is refused. The store is re-hashed at every start.
  • Fetching is narrow. A strict ref grammar, https and ssh only, no symlinks, no submodules, no checkout, no hook, no install-time script.
  • Secrets are referenced, never stored. A project names a variable. The value reaches a body for the length of one run.

14.2Not guaranteed

Containment. A body runs with the user's rights under a scrubbed environment. The scrubbing prevents leaks. It does not contain malice. A read or write reflex over its bar runs without asking, and whatever it does with a span it is handed, an injected sentence can make it do. So its effect is the author's promise about exactly that. Read what you install. The manual's security page holds every claim, and how to try each without a key.

Conformance

15

The spec is executable. A claim is a transcript.

The repository's spec/ is the format's conformance suite. It is read by three test suites and owned by none of them.

15.1What conformance means

  • Schemas. JSON Schema for reflex.toml, an overlay, a vocabulary and evoke.toml, served at the addresses above. A conforming implementation reads every manifest they accept.
  • Vectors. One directory per function of the core, one case per file: the input by name, and the exact expected output, on the wire above. A conforming implementation produces the same value for the same input, including the same fault.
  • Transcripts. One directory per flow: the terminal, word for word, run offline on a recording. A conforming CLI prints the same lines and exits with the same codes.
  • No key. The whole suite runs without a classifier. The recording answers, and the vectors pin what the answers become.

15.2Exit codes

CodeMeaning
0Ran. Also every command that did what it said.
1The program, or the machine, failed. Also a test run with a failing case.
2Abstained, or the person declined.
3Needs a human: a missing key, an untrusted project, a prompt with no terminal, a line to fix.
4The adapter failed.

Every line that needs something from a person ends with the literal command that gives it, and the set of those commands is closed.

Grammars

16

The small grammars, in one place.

Every name, ref, tag and id on this page follows one of these. They are the appendix a reader checks a file against.

grammarsreflex = 1
name          = [a-z][a-z0-9_]*
argument name = name, never a JavaScript reserved word
local name    = name, never none | unstated | fits | weave
option key    = one clean line, never none | unstated
word          = one clean line, trimmed, spaces allowed, unique under identity
tag           = [v]X.Y.Z                    the newest is the highest
ref           = owner/repo[/dir][@tag] | <git url>[#dir][@tag] | ./dir | ../dir
call          = name (arg=value | flag)*   a value with spaces is quoted
question id   = route | fits.<reflex> | <reflex>.<argument> | weave.<name>
candidate     = <start>-<end>               character offsets of a pick's span
identity      = NFC, lower-cased, whitespace collapsed and trimmed, trailing . ! ? … dropped
h1            = "h1:" sha256 over sha256sum's lines of every file, paths in byte order
clean text    = no control characters, no bidi controls; a line feed only in description

A reflex directory's h1 skips the five names a project keeps: evoke.toml, evoke.lock, overlays/, vocab/, evoke.d.ts. So an author may keep a project beside their reflex, and none of it ships.

Next: write a reflex in ten minutes, or read the manifest, key by key in the manual.