Examples · 02 of 06
One sentence finds an address and starts a draft with it.
Two reflexes connect. What contact finds, mail takes,
because one declares what its result holds and the other lacks a value of that kind. A recorded session from
the product's own tests, and a pattern for your app.
The request and the result
01Two steps, one sentence.
Each part is decided on its own, in the order you typed it. The plan shows first. The stage plays three sentences in turn: one that ran, one declined at its first step, one refused.
- 1«them» names step 1.
mailneeded an address.contactdeclares one under[yields], and the plan takes it:takes email from 1. - 2A no ends the sentence at that step. In the second sentence, a timer asked at its turn. Declined, and the lights step after it was skipped. The exit code is the worst step's.
- 3A part that matches nothing refuses the whole sentence, before anything runs. Nothing is left half done.
The operations
02Two manifests, and the test that pins them.
These files come from the product's own test home. The contact body is a
stand-in that makes an address from the name. In yours, it would ask your directory.
reflex = 1 description = """ Look up a person's email address in the team directory. One person at a time; prints the address.""" not_for = ["writing to anyone", "phone numbers", "adding a person to the directory"] tags = ["people"] effect = "read" confirm = "Look up {name}'s address?" run = "contact.mts" [args.name] ask = "Whose address?" vocab = "people" [yields] email = "email" [examples] "look up dana's address" = {} "what is sam's email" = {} "find the address for dana" = {} [tests] "dana's email please" = {} "email dana" = false
export default async ({ name }: { name: string }) => { const email = `${name}@example.com` return { text: `${name} <${email}>`, data: { email } } }
dana = "Dana Ortiz, operations." sam = "Sam Lee, sales."
reflex = 1 description = """ Start an email in your mail app. To one address, a copy to another, with an optional subject; nothing goes out until you press send.""" not_for = ["reading or searching mail", "chat and text messages", "looking up an address"] tags = ["mail"] effect = "write" confirm = "Write to {to}?" run = "mail.mts" [args.to] ask = "To which address?" pick = "email" [args.cc] ask = "Copy whom?" pick = "email" optional = true [args.subject] ask = "What is the subject?" pick = "quoted" optional = true [examples] "email ana@example.com" = { to = "ana@example.com" } 'write to sam@example.com about "friday"' = { to = "sam@example.com", subject = "friday" } "send a note to team@example.com" = { to = "team@example.com" } [tests] 'mail dana@example.com "the minutes"' = { to = "dana@example.com", subject = "the minutes" } "read my mail" = false
test("a pronoun takes the address the step before yielded, into the ask it fills", async () => { const project = await load({ root: home, adapter: replay(answers) }) const woven = await project.weave("look up dana's address and email them") deepStrictEqual(woven.plan.binds, [{ from: 1, to: 2, arg: "to", field: "email", kind: "email", via: "fill" }]) equal(woven.plan.steps[1]?.decision.outcome, "ask") equal(woven.status, "ran") deepStrictEqual(woven.steps[1]?.bound, [{ arg: "to", from: 1, field: "email", value: "dana@example.com" }]) const round = woven.steps[1]?.rounds[0] equal(round?.decision.outcome, "run") if (round?.decision.outcome !== "run") return equal(round.decision.call, 'mail to="dana@example.com"') deepStrictEqual(round.result, { text: "drafted to dana@example.com" }) deepStrictEqual(woven.steps[0]?.rounds[0]?.result, { text: "dana <dana@example.com>", data: { email: "dana@example.com" } }) })
- 1The handoff is in the manifests.
[yields] email = "email"on one side,pick = "email"fortoon the other. Same kind, so it binds. - 2The lookup data is a vocabulary.
danaandsamare words you filled. The body turns the word into an address. Yours would query a directory. - 3The test replays a recording. No key, no network. It pins the bind, the ask it fills, the call, and both results.
One boundary
03A reference that takes nothing asks before anything runs.
«for them» points at step 1, but a timer needs no address. evoke says so, and asks whether to run the plan as it stands. Nothing is guessed.
$ evoke "look up dana's address and start a 10 minute timer for them" 1 contact name="dana" 0.90 2 timer duration="10 minute" 0.88 · after 1 step 2 refers to step 1, but takes nothing from it Run the plan as it stands? [y]es [n]o > y dana <dana@example.com> 10 minute timer started
The same holds for a reference two fields could satisfy: it stops with a line naming them. What a step takes, in the manual →
Before you run it
04What this pattern needs.
- Node 24.5 or newerAnd the SDK,
@evoke-build/evoke. The weave runs in your application. - A classifier keyFor a live sentence. The recorded test above needs none.
- Your directoryThe
contactbody shown makes an address from the name. Replace it with a lookup in your own directory, and keep the yield. - Your mailThe collection's
mailopens a draft in the Mac's mail app. The test's stand-in only prints the line.