Common patterns

Common patterns #

Reach for the shape that matches the brief. Each is an idiom, not a whole file — the surrounding imports, constructions, and listen line are the anatomy chapter's job, and every type and field name here is illustrative.

inbound-intake #

Something arrives, a record is created or updated, the team is told.

person = write crm-[:People]-> {
  unique by (Email)
  Email: msg.From
  Name:  coalesce(ai("the sender's name, cleaned — no email address, no 'via', no company suffix: ${msg.From}"), msg.From)
}

intake = only(chat-[ch:Channels WHERE ch.Name == "intake"]->)
if (intake == null) { fail("no #intake channel") }
write intake-[:Messages]-> { Message: "New sender: ${msg.From} (${person.externalId})" }

Key the identity on something actually carried in the message, so a second arrival updates rather than duplicates. The ai() touch is a micro-judgement — tidying a display name; pulling an entity out of the prose would be an extract job — and it is wrapped in coalesce because Name is required and a judgement can come back with nothing.

multi-target #

Several systems hearing one event is several writes in one body; a later write quotes an earlier handle.

record = write crm-[:Companies]-> { … }

ops = only(chat-[ch:Channels WHERE ch.Name == "ops"]->)
if (ops == null) { fail("no #ops channel") }
write ops-[:Messages]-> { Message: "Logged to the CRM: ${record.externalId}" }

For writes that genuinely don't depend on each other, pass them to all as functions, and they run at once:

all(
  () => { write crm-[:Companies]-> { unique by (FUZZY Name), Name: msg.Subject } },
  () => { write sheet-[:Rows]->    { unique by (Company),    Company: msg.Subject } },
)

Each one is a function — () => { … } written where it stands, or the name of one declared elsewhere — and every one runs at once (all(f, g, { concurrency: 1 }) runs one at a time). all finishes when every function has; one that waits parks the run, so there is no await. A function reads anything bound before all and never another's bindings. Work that has to come back out is returned: the answer has one slot per function in the order written, and r[0] reads what the first returned. Functions that only act need no binding at all, as here.

compose-a-report #

Many small things into one readable artifact: a block returns a line each, Text.join assembles them, pdf.render turns the text into a PDF file for a file-typed field.

lines = msg-[f:Attachments]-> {
  return "- ${f.Name}"
}

digest = "Attachments on ${msg.Subject}:
${Text.join(lines, "\n")}"

write record-[:Files]-> { File: pdf.render(digest) }

pdf.render draws the string exactly as composed, so the layout you write is the layout in the file. Anything a block can iterate — extracted entities included — can feed one.

scheduled-digest #

A schedule fires, a query gathers, a message goes out. The tick carries nothing you need — the query is the input.

function `Weekly Digest`(t: <timer-[:Tick]->>) {
  lines = crm-[c:Companies WHERE c.`Created At` WITHIN 7d ORDER BY c.`Created At` DESC LIMIT 20]-> {
    return "- ${c.Name}"
  }

  ops = only(chat-[ch:Channels WHERE ch.Name == "ops"]->)
  if (ops == null) { fail("no #ops channel") }
  write ops-[:Messages]-> { Message: "New this week:\n${Text.join(lines, "\n")}" }
}

listen as "Weekly digest" to timer { schedule: "0 9 * * 1" } fire `Weekly Digest`

sections-from-a-type #

A report whose sections come from a type rather than from a list written beside it: group the findings by the value, walk the type's members in the order they were declared, and look each group up.

type Thesis = <"Consumer" | "Infra" | "Health">

node Finding: "each company mentioned" {
  headline: <text> "one line about it"
  thesis:   <Thesis> "which thesis it fits"
}

function `Thesis Recap`(go: <runs-[:Invocation]->>) {
  found    = extract([go.Text], Finding)
  rows     = map(found, (f) => { return { thesis: coalesce(f.thesis, "Consumer"), line: f.headline } })
  by       = List.groupBy(rows, (r) => r.thesis)
  sections = map(members(<Thesis>), (th) => "${th}: ${count(coalesce(by[th], []))} found")

  ops = only(chat-[ch:Channels WHERE ch.Name == "ops"]->)
  if (ops == null) { fail("no #ops channel") }
  write ops-[:Messages]-> { Message: Text.join(sections, "\n") }
}

extract-and-connect #

Records pulled out of a message and written with their relationships intact: the people are declared inside the company, so each one lands attached to the right one.

import { vcUrlRetrieval } from plugins

node Company: "each company this message is about" {
  name:    <text> "the company's name"
  website: <text> "the company's official website"
  node person: "each person at this company named in the message" {
    name:      <text> "the person's full name"
    job_title: <text | null> "the person's job title, if given"
  }
}

pages     = vcUrlRetrieval(msg.Body)
content   = [msg.Body, ...map(pages, (p) => coalesce(p.text, ""))]
companies = extract(content, Company, { tier: "careful" })

map(companies, (c) => {
  company = write crm-[:Companies]-> { unique by (FUZZY Name), Name: c.name, Domains: c.website }
  c-[p:person]-> {
    write company-[:Team]-> { unique by (FUZZY Name), Name: p.name, `Job Title` ?: p.job_title }
  }
})

Two things here you cannot derive:

declare-once #

The same record extracted in several places, written the same way each time — declare it once, with its words, and reuse it:

node Entry: "each company named in this text" {
  Name:        <text> "the company's name"
  Description: <text> "what the company does, in one sentence"
  node person: "each person at the company" { Name: <text> "their full name" }
}

function `Intake`(go: <runs-[:Invocation]->>) {
  found = extract([go.Text], Entry, { tier: "careful" })
  map(found, (e: <Entry>) => {
    company = write crm-[:Companies]-> { unique by (FUZZY Name), ?...e }
    e-[p:person]-> { write company-[:Team]-> { unique by (FUZZY Name), ?...p } }
  })
}

human-reviewed-intake #

"Let me paste something in, but let me check it before it's saved."

found = extract([go.Text, ...go-[:Files]->.File], Company)

q = write asks-[:Check]-> {
  Prompt: "Save these companies and people?"
  Detail: "Review the extracted records before they're written."
}

answer = await first(q-[:Response]->)
if (answer.Answer) {
  map(found, (c) => { … })
}

Two things make it this shape rather than a thinner one: