Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Decisions

One input decided is a Decision. It is a union, narrowed by outcome and then by reflex. decide makes one. fill answers an ask. run runs a chosen call. handle does all of it.

decide(input, { tags?, signal? })

const d = await project.decide("kill the lights in the den", { tags: ["home"] })

Asks the adapter, reads its answers, and gates them. Runs nothing. tags offers only the reflexes carrying one of them. signal aborts the adapter call, and decide rejects with the signal's own reason.

The four shapes

outcomeCarries
"run"reflex, args, values, call, effect, confidence, weakest, judgments, contenders, runner_up?, whole?
"confirm"All of the above, plus prompt: { own, reason, template } and because: Cap[]: every reason it stopped, in order
"ask"reflex, partial args and values, unconsumed, missing: Missing[], and the judgments so far
"abstain"contenders, the ranking, and judgments

Every decision also carries three more things. input is the sentence as decided. plan is the digest it was decided under. trace has one entry per adapter call: { adapter, questions, ms }.

switch (d.outcome) {
  case "run":     await project.run(d); break
  case "confirm": if (await ui.confirm(d.prompt.template)) await project.run(d, { confirmed: true }); break
  case "ask":     project.fill(d, await ui.pick(d.missing)); break
  case "abstain": ui.say(d.contenders); break
}

args and values

args is what the classifier read, typed: { type: "option", key }, { type: "word", word, value? }, { type: "pick", span, value }, { type: "flag" }. values is what the body receives: the key, the word's value or else the word, the number or text, true. With Reflexes from evoke.d.ts, or a manifest handed as code, both are typed per reflex once d.reflex is known:

if (d.outcome === "run" && d.reflex === "lights") {
  d.values.room    // string
  d.values.state   // "on" | "off" | "dim"
}

An app that wants a reflex's wording with a body of its own calls decide, switches on d.reflex, and reads d.values.

Confirm

prompt.own is evoke's line: the call, the effect and the weakest judgment. prompt.reason says in one line why the call waits, a phrase for each cap, as the CLI prints it under the call. prompt.template is the reflex's own question, filled in. because lists why it stopped: destructive, no_gate, under_floor, one_view, respelt, text_read, more, cut, quoted, text_left, whole, detail, named, pointed (Outcomes). A confirm decision runs only with run(d, { confirmed: true }).

decide reads the input as one call. When its words say the call is wanted once more, like too or the same for, the decision is a confirm with more, since there is no second step to be the other time. steps reads every step of a sentence.

Ask and fill(d, given)

missing says, per argument, its ask, why it is missing, and what it may be. Missing means never stated, or a pick out of range. What it may be is the options, the vocabulary's words, or which recognizer reads it, with choices.recent listing the values recalled from the results the application handed decide, handle or weave as recent: [{ reflex, data }], newest first, when the pick's manifest names the field. Where the input's words say a pick's value aloud and read in more than one way, words holds them and choices.readings lists what they read as: offer those in place of the recalled values. For a word from a vocabulary, likely names the listed word to offer for a yes before the choices, which keep their order. fill takes what a person answered, by argument name: an option's key, a word, or the text a pick reads, a recalled value or a reading among them. It types the answer and gates again, synchronously:

const filled = project.fill(d, { room: "den" })       // a Decision again: run, confirm, or still an ask

A text that does not read is a DiagnosticError naming the argument and what it may be: room: "attic" is not one of den, office. A flag is never asked.

run(d, { signal? })

Runs the chosen call's body and resolves to { text, data? }, with contained when a file or an argv ran: whether this machine held it to its declaration, in the shape --json prints. A reflex handed as code runs in-process. A file runs in a child. An argv is spawned. A body's failure is a FailureError. signal aborts the body, and run rejects with the signal's reason. A decision made under another plan is refused as misuse, with a TypeError. A reflex that takes an earlier step's whole result is refused with a DiagnosticError: only weave hands one, so a host with its own loop learns at run that decide's decision was a taker's. A playbook is refused the same way, <name> is a plan of steps; say it in a sentence: only steps and weave make its plan.

handle(input, { confirm?, ask?, tags?, signal? })

The whole loop: decide, then ask and fill until nothing is missing, then confirm, then run.

const handled = await project.handle(input, {
  confirm: d => ui.confirm(d.prompt.template),   // true runs, false declines
  ask: d => ui.pick(d.missing),                  // answers by name, or undefined to decline
})
handled.outcomeWhen
"ran"The body ran: decision and result
"abstained"none won, or the route was under its floor
"declined"confirm returned false, ask returned undefined, or an answer changed nothing
"unanswered"A confirm or an ask was reached and no handler was given. The decision itself is the answer

An answer that does not read is asked again once. Twice is a decline. An empty input is refused before the adapter is asked. A decided reflex that takes an earlier step's whole result is refused before any confirm, and so is a playbook. Only a diagnostic, a fault or a failure throws.

steps(input, { tags?, signal? }) and weave(input, { confirm?, ask?, proceed?, tags?, signal? })

A sentence that asks for several things is a weave (Weaving). steps reads one into its plan, runs nothing, and returns it sealed as a plan file — the sentence, the plan, every answer the classifier gave, and the pins it was decided under — the object evoke try --save writes and evoke run <file> reads (The plan file):

const pinned = await project.steps("look up dana's address and email them")
const plan = pinned.weave
plan.steps       // [{ n: 1, text, decision, reflex: "contact", effect: "read", ... }, { n: 2, ... }]
plan.binds       // [{ from: 1, to: 2, arg: "to", field: "email", kind: "email", via: "fill" }]
plan.stages      // [[1], [2]]: a stage's steps run together; stages run in order
plan.verdict     // { outcome: "run" | "ask" | "confirm" | "refuse", because?: [...] }
pinned.set       // the plan digest it was decided over; pinned.answers, every answer the plan took

Each step carries its decision, the Decision decide would have made of its words alone; shared names the words the sentence said once for several steps that reached it, { service: { word: "checkout", via: "fill" } }; repair says how a part that was not decided on its own became a step, "narrowed", "spliced", "merged", "split", "corrected", "named", "first", "verb", "again" or "apart" (The JSON line). A binding whose via is "takes" is a whole result by the name its source returns, with no kind; a plan with no source for one, or two, has verdict.outcome "refuse" and no_source or several_sources among its because. A step a playbook wrote carries from, the playbook, its step and the slots the sentence filled (Playbooks); folded names the parts of the sentence that repeat one of its steps; and the verdict's because holds reviewed per playbook, with the sentence's own prompt. steps(input, { ask }) answers a slot the sentence lacks before the plan is made, as weave's ask does; such a plan is a sheet, and weave refuses it as a file, since a file holds no one's answers. weave settles what the plan asks first, then runs every step under handle's handlers, each told which step and round asks, a Turn:

const woven = await project.weave(input, {
  ask: (d, turn) => ui.pick(d.missing, turn.step),   // before anything runs, for a required argument no step provides
  confirm: (d, turn) => ui.confirm(d.prompt.template), // at the step's turn
  proceed: plan => ui.confirm("Run the plan as it stands?"),  // a step refers to another it takes nothing from
})
woven.status     // the worst step's: "ran", "failed", "declined", "refused", "unanswered"; or why nothing ran
woven.steps      // per step: { step, status, why?, bound, rounds: [{ round, input, decision, status, result? }] }

A confirm handler may wait: hand the decision, plain JSON, to a queue a second person reads, and resolve with their answer; the weave goes on where it waited, and nothing that ran runs twice. The whole plan may travel instead: JSON.stringify(pinned) is the file, and weave(pinned, { proceed, confirm?, ask?, signal? }) runs it on the same project, here or on another machine — its pins checked first, a DiagnosticError naming the first that moved and the call that makes the plan again; the plan made again from the file's answers under its gate and refused when it does not read the same; then proceed(pinned), required whatever the verdict, since a file's numbers are whoever wrote them; then every step under the handlers, a value bound at run time decided by this project's adapter. A plan that asks or refuses runs nothing.

const checker = await load({ root, adapter: jev() })
const woven = await checker.weave(JSON.parse(file) as Pinned, {
  proceed: pinned => ui.confirm(`Run the plan of ${JSON.stringify(pinned.input)}?`),
  confirm: (d, turn) => ui.confirm(d.prompt.template),
})
``` A body's failure is the
step's, with `status: "failed"`, never a throw. A step after one that stopped is `skipped`, with `why: { type:
"earlier_step" }`. A step bound to a list of records runs once per record, one
round each. A step that takes whole results receives them in its body's `args`, handed by the weave alone; its
`bound` entries name each argument, its source and the name, with no `value`.

`signal` aborts the adapter's call while the plan is made, as it does in `decide`, and cancels the run once it
is under way: every body is ended, its own `signal` aborted first, nothing starts after, and every step that did
not finish reads `skipped` with `why: { type: "cancelled" }`. `weave` then rejects with the signal's reason, the
record on it as `woven`:

```ts
try {
  await project.weave(input, { signal: controller.signal })
} catch (error) {
  if (error === controller.signal.reason) console.log(error.woven.steps) // what ran, and what was cancelled
  else throw error
}

Next: Adapters.

FAQPrivacyGitHub