- 1Four keys are required.
reflex,description,confirmandrun. 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
effectmeans 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.
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
01What 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
02The 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
runnames: 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,pickorflag. - 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,writeordestructive. - 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
03One 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 = 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
3.1Keys
| Key | Required | Meaning |
|---|---|---|
reflex | yes | 1. The format's version. |
description | yes | What the reflex does, in the user's words. Its first line is the summary. |
not_for | no | What it does not do: its near neighbours. Read as the no side of the fit question. |
tags | no | Words for --tag to narrow a decision by. No other meaning. |
effect | no | read, write or destructive. Absent means destructive. A reflex has one effect; grouped actions take the worst case. |
confirm | yes | The one-line question a person answers. A {placeholder} names a required argument. |
run | yes | The body: a path ending in .mts or .mjs inside the directory, or an argv. |
[config] | no | Settings the user provides: key = "about", or { about, secret = true }. A secret is only ever set from an environment variable. |
[args.<name>] | no | The arguments. Each has ask and exactly one source. |
[yields] | no | What the body's data holds for a later step: per field, the kind that reads it, or { each = { … } } for a list of records. |
[examples] | no | Utterances with what they assert. Sent to the classifier. |
[tests] | no | The 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.
| Contract | Wording |
|---|---|
run | description, not_for, tags, confirm |
| argument names and their sources | every ask |
option keys, range | the meaning of each option |
config keys, yields | examples 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
04One 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
| Source | Values come from | The body receives | Asked when missing |
|---|---|---|---|
options | The author: key = "meaning" | The key | A numbered choice |
vocab | The user: vocab/<name>.toml | The word's value if set, else the word | A numbered choice, plus [+] add one |
pick | The input, as typed | number the number; duration whole seconds; email, url, quoted the text | Typed freely, read by the recognizer |
flag | The input | true, or nothing | Never |
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.
| pick | Recognizes | Value | range |
|---|---|---|---|
number | A bare number, unit words kept in the span: 30 percent; a leading minus is the number's: -5 | The number | yes |
duration | One number and one unit: 10 minutes, 2 hours, 90 seconds | Whole seconds | yes |
email | An address | The text | no |
url | A URL | The text | no |
quoted | "…", “…” or ‘…’; a straight single quote is an apostrophe | The text between the quotes | no |
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
05One 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
| Record | Means |
|---|---|
"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" = false | Never 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
06Three 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
| Question | Kind | Built from |
|---|---|---|
| Which reflex? | One choice over every active reflex, plus none | Each reflex's description as what, its not_for as what it is not for, and its examples |
| Does it fit? | A yes/no per reflex | description as yes, not_for as no |
| Which value? | One choice per argument, plus unstated | The 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
| Outcome | When | Exit |
|---|---|---|
| run | Confidence cleared the bar for the winner's effect, every required argument was stated, nothing else needed attention. | 0 |
| confirm | The 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 |
| ask | The 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 |
| abstain | none 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 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
07The 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
| Bar | Jev | Gates |
|---|---|---|
route | 0.5 | Under it, abstain. |
read | 0.6 | A read reflex runs at or above it. |
write | 0.8 | A write reflex runs at or above it. |
fits | 0.3 | A runner-up at or above it holds the outcome at confirm: the input may have asked for two things. |
| destructive | — | Always 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. readnever exceedswrite. A user MAY move a bar for their own machine, under the adapter's table inevoke.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
08A 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.
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
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,LANGandTERM, nothing else. Secrets reach a body throughconfig, 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, thenSIGKILL. A body's life is bounded by its caller's. - An argv runs directly, never through a shell. The program is a name on
PATHor an absolute path, never a path relative to wherever the tool runs.
The project
09Files 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.
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 }
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.
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.
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
10Git 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
| Form | Means |
|---|---|
owner/repo | Every reflex directory in the repository, on GitHub. |
owner/repo/dir | One reflex, the directory dir; dir may nest. |
owner/repo/dir@1.2.0 | Pinned. Tags are X.Y.Z or vX.Y.Z. The newest is the highest. |
https://host/repo.git#dir@1.2.0 | Any git host over https or ssh. #dir and @tag are optional. |
ssh://git@github.com/owner/repo | The same over ssh, the user before the host. |
./dir, ../dir | A 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:
| Level | When |
|---|---|
| same | Wording only: description, not_for, tags, confirm, any ask, option meanings, records. |
| minor | Additions, like an argument, an option, a config key or a yield. Also a config key removed. |
| major | Something 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
11Any 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
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. otherwisemarks the sentinel,noneorunstated, in the structure, so an engine may abstain its own way.gateis the adapter's own calibration. The core never rescales. Without one, every decision confirms.answeris stateless and may be called with any subset of a plan's questions. An adapter that ignores its signal is raced against it anyway.idcovers 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.
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
12One 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
| Field | run | confirm | ask | abstain | Holds |
|---|---|---|---|---|---|
input | ● | ● | ● | ● | The sentence as decided. |
outcome | ● | ● | ● | ● | run, 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 }. |
judgments | ● | ● | ● | ● | Every choice read: { question, top, p }. |
contenders | ● | ● | ● | ● | The 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. |
trace | ● | ● | ● | ● | One 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
{ "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
13A 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,afterorbeforecould 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, YandY after you Xboth read asX, 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 "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
14What 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,
httpsandsshonly, 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
15The 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 andevoke.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
| Code | Meaning |
|---|---|
0 | Ran. Also every command that did what it said. |
1 | The program, or the machine, failed. Also a test run with a failing case. |
2 | Abstained, or the person declined. |
3 | Needs a human: a missing key, an untrusted project, a prompt with no terminal, a line to fix. |
4 | The 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
16The 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.
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.