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

Playbooks

A playbook is a reflex whose body is a plan: steps, one sentence per step, and no run. A person reaches it with a short sentence that states the situation they face: «checkout is failing in eu-west». That sentence is routed by your description, not_for and examples, like any request. Each step is then decided over what that person has installed, as a typed sentence would be, and the whole plan prints before anything runs. The playbook's summary and examples state the situation, and its not_for names the steps typed alone: rules 5 to 7.

# outage/reflex.toml
reflex = 1

description = """
Handle a service that is down or failing.
Checks its errors, deploys and logs, finds the release behind the failure, rolls it back, tells the incident channel and updates the status page; the rollback asks again before it runs."""
not_for = [
  "a service's error rate on its own",
  "listing a service's deploys on its own",
  "pulling a service's logs on its own",
  "rolling back one release on its own",
  "posting to a channel or updating the status page on its own",
  "writing up an incident's postmortem",
  "asking whether a service is healthy",
  "a slow service that still answers",
]
tags = ["outage"]
effect = "destructive"
confirm = "Run the outage plan for {service}?"
steps = [
  "check {service}'s errors[ in {region}]",
  "list {service}'s deploys[ in {region}]",
  "search {service}'s logs[ in {region}]",
  "find the release behind it",
  "roll {service} back from that release[ in {region}]",
  "post to the incident channel",
  "update the status page for {service}",
]

[args.service]
ask   = "Which service is down?"
vocab = "services"

[args.region]
ask      = "In which region?"
vocab    = "regions"
optional = true

[examples]
"payments is down in us-east"    = {}
"search is broken"               = {}
"we have an outage on checkout"  = {}
"checkout is on fire in eu-west" = {}
"payments stopped working"       = {}

[tests]
"checkout is failing in eu-west"               = {}
"we lost search in us-east"                    = {}
"how bad are the errors on payments right now" = false
"downgrade search from 6.0.1"                  = false
"ping #ops that checkout recovered"            = false
"flip the status page to resolved for search"  = false
"prepare a retro on the checkout outage"       = false
"is the search service doing fine"             = false
"checkout feels sluggish this morning"         = false
$ evoke "checkout is failing in eu-west"
  1  errors service="checkout" region="eu-west"  0.95 · outage 1
  2  deploys service="checkout" region="eu-west"  0.95 · outage 2
  3  logs service="checkout" region="eu-west"  0.95 · outage 3
  4  suspect  0.95 · takes errors from 1, deploys from 2, logs from 3 · outage 4
  5  rollback service="checkout" region="eu-west" · takes release from 4 · outage 5
  6  post channel="#incident"  0.95 · outage 6
  7  status component="checkout"  0.95 · outage 7 · service as component
  outage service="checkout" region="eu-west" · destructive · weakest: route 0.95
  Run the outage plan for checkout?  [y]es [n]o [t]each > y

Every line of the plan ends with the playbook and the step that wrote it. Then comes the sentence's own line, the decision that picked the playbook, and one question over the whole plan. A step that takes a result waits for the steps it takes from, as in any sentence of several steps (Weaving).

A plan of sentences

A playbook is fetched, locked, tested, overlaid and shown like any reflex. What differs is the body: steps instead of run, and none of a body's keys, [needs], [config], [yields], returns, platforms or an argument's takes. evoke check refuses each of them. A plan holds at most 24 steps. It lists the situation's steps in the order they happen: rule 8. Its effect is your claim about the whole; add says when a step reaches a tighter one. The claim is the worst effect its steps reach: rule 10.

evoke new --playbook <name> writes one to start from: <name>/reflex.toml alone, a plan of three steps from the template, which checks clean.

Slots

A slot, {service}, is an argument of [args]: a vocabulary word, an option or a pick, never a flag. It is filled from the sentence, or asked before the plan prints, and written into the step as the person typed it, an option as its key. A word beyond the slots fills nothing. A value is never the playbook's own: a word the person lacks joins their vocabulary with [+] add one.

$ evoke "we have an outage"
  1  outage · asks service
  Which service is down?  [1] checkout  [2] payments  [3] search  [+] add one  > 2
  1  errors service="payments"  0.95 · outage 1
  …

Words in square brackets go only with the slot inside them: [ in {region}] is written when a region was stated and dropped when it was not. An optional argument stands only in a bracket, and a required one never does: evoke check refuses a slot in the wrong place. It also refuses a slot that is a flag or no argument at all, and an argument that no step names. A slot goes in every step whose reflex reads its value: rules 25 and 26.

Every step is decided when the plan is made

A step is decided by the classifier over what the person has installed, from its words alone, as a typed sentence would be. So each step is one action, worded as a person would type it: rules 24 to 29. evoke check refuses a step that reads as a call with a value, errors service={service}. Lint reports a step that holds a connective or a comma, states a channel or an address, or opens with check that. It also reports a step whose words are all the summary's or an example's. Diagnostics lists each line.

A step may reach another playbook, once: a plan inside a plan inside a plan is refused. A step that reaches the playbook it stands in is refused too. A step no reflex matches refuses the whole plan before anything runs:

  7  "update the status page for checkout" · no reflex · outage 7
[2]

evoke add shows what each step reaches on the set it joins, and evoke test fails a step that reaches nothing (Examples and tests).

One yes over the whole plan

Nothing runs before the whole plan is printed and one yes is given over it: your confirm when the plan is your playbook's alone, Run the plan as it stands? when the sentence holds more. A step's own confirm, a destructive one's above all, is asked again at its turn, every time. [t]each at the whole-plan question teaches the route to your playbook; at a step's turn it teaches the step's reflex from the filled sentence.

A result picks among the steps you listed

A step may run only under a value an earlier step yields. Write it as a table, say the sentence and when one field with the value it runs under, as text, and list the alternatives together right after the step whose result picks among them:

steps = [
  "drain connections off the primary",
  "fail over to the replica",
  "check whether writes land on the new primary",
  { say = "tell the ops channel the failover is done", when = { landing = "yes" } },
  { say = "fail back to the old primary",              when = { landing = "no" } },
]

A result never writes a step: it picks among the steps you listed, and the whole list prints before anything runs. The field is one that step's reflex declares under [yields] as one value, never a list. The plan refuses before anything runs when the step reaches a reflex that yields no such field, or the value is one the field cannot hold. evoke check refuses a when on step 1, where no step comes before it, and a when on two fields.

The step's line ends then 4 on "yes", 5 on "no", and each alternative's if 3 yields landing "yes". At that step's turn the result is compared with each value as text. The step it picks runs, the others are skipped clean and say so at the end, and a value no step lists picks nothing, the plan going on. A step without when runs either way. A step that may not run is one step: a later step cannot take its result, and its words may not reach another playbook. A check whose result picks is a reflex of its own that yields one field: rule 9.

$ evoke "drain the primary"
  1  drain  0.95 · runbook 1
  2  failover · destructive · weakest: route 0.95 · runbook 2
       it cannot be undone, so it always waits for a yes
  3  verify  0.95 · runbook 3 · then 4 on "yes", 5 on "no"
  4  tell channel="#ops"  0.95 · runbook 4 · if 3 yields landing "yes"
  5  failback · destructive · weakest: route 0.95 · runbook 5 · if 3 yields landing "no"
       it cannot be undone, so it always waits for a yes
  runbook · destructive · weakest: route 0.95
    it cannot be undone, so it always waits for a yes
  Run the failover runbook?  [y]es [n]o [t]each > y
primary drained
  2  failover · destructive · weakest: route 0.95
       it cannot be undone, so it always waits for a yes
  Promote the replica now?  [y]es [n]o [t]each > y
replica promoted
writes landing on the new primary
told #ops
  5  failback · destructive · weakest: route 0.95 · runbook 5 · if 3 yields landing "no" · skipped · not chosen: step 3 yielded landing "yes"
       it cannot be undone, so it always waits for a yes

What the person adds to the sentence

A part of the sentence that repeats a step of the plan folds into that step, and the plan says so: folded "show me the error rate" into 1. A part that states the situation a second time, «payments keep timing out, customers can't pay», folds into the plan the first part opened, so the plan holds each step once. Its folded line names that plan's first step: folded "customers can't pay" into 1. A part that says what not to do beside a plan is refused, and so is a part that opens with a condition, if, unless or in case. evoke judges no condition, so the line it prints says to ask for the check first, then what to do.

What is contract

steps is contract: an overlay may reword the description, the confirm and the asks, never a step. A step added, removed, reworded or reordered is a major version, and evoke check says so: steps the steps moved. A slot added or renamed rewords a step, so it is major too, and so is a when added, moved or changed.

add, test and show

add decides every step of a newcomer playbook over the set it joins, runs nothing, and prints what each reaches under its row; a step that reaches nothing installs and is reported, since the plan refuses when it is made. Two lines more may follow. claims write; its steps reach destructive says a step reaches a tighter effect than the playbook claims. step 3 reaches logs, which lacks the tag outage says a request narrowed with --tag outage would leave that step without its reflex. Every reflex the steps reach carries the playbook's tag: rule 40. add also prints also fits for the examples of the reflexes the steps reach, since the plan does hold their action. Typed alone, such a sentence still reaches its own reflex: Exceptions.

test decides the playbook's records as any reflex's, then its steps, each filled from the first record whose reading fills every slot it holds; a step no record fills is untested. Both say when the step before a branch reaches a reflex that yields no field the branch waits on, and test reports a claim the steps pass too. show prints the steps after confirm, numbered, a step that may not run ending in its when.

$ evoke test outage
  outage  16 passed · 7 steps route

In the SDK

playbook(manifest) hands a playbook as code, steps in its manifest and no body; project.steps(input, { ask }) answers a slot the sentence lacks before the plan is made (Decisions). A playbook is never called by name: run, handle and evoke run <name> refuse it with say it in a sentence.

Next: Examples and tests.

FAQPrivacyGitHub