The Letter Agent
A weekly letter is easy to promise and hard to keep writing. This agent does the Monday work: it reads what actually happened in the last ten days, picks three posts the list has not seen, writes a short note in the founder’s voice, and puts a real test email in the approver’s inbox before the day starts. The one thing it cannot do is send. That stays a button a person presses.
Ten days in, one test email out
Six machine steps between the Monday trigger and the approver. Each one must pass its own check before the next starts, and the pipeline refuses to skip. There is no send step on purpose.
How each step works
A scheduled routine inserts one draft broadcast for the house brand, named for the week, with the agent as its author. If a draft the agent opened earlier is still waiting for approval, nothing new is opened: the backlog is one letter, never two. Then it runs the first step in the same request, so the run is moving before anyone is at a desk.
Three sources, ten days back: events that ran, meeting summaries, and the founder’s journal (read from Notion when configured, silently absent when not). The model returns five to eight dated data points, each tied to its source, written in the first person, with people described by role and place, never by name. The step passes with at least three points and at least one from the last seven days. Client names never leave this step.
The sixty newest published posts on the brand, minus every post a sent letter has already featured. Three are chosen newest first, with at most two from one pillar so the letter does not make one argument three times. When fewer than three unsent posts exist the pick falls back to the ones featured longest ago and says so in its summary.
A note from a person, not a newsletter. The model is given the data points, the three posts, the brand profile and the subjects of the last four sent letters. It returns a subject of at most 60 characters, a preheader, and a body that opens with a first-name greeting, runs two or three short paragraphs from the data points, ties the week to the three posts in one line, and signs off. The server then checks every rule: at most 220 words, no em dash, no audit or hiring language, the brand name spelled Edge8, no headings, lists or links, and the subject not repeated in the body. Any failure stops the run with the full list; nothing half-right is saved.
Strict round robin from whatever the last sent letter carried. Four calls to action are in the catalogue (a conversation, a private retreat, LinkedIn, the book); an entry without a landing page is skipped until its URL exists. Three layouts rotate the same way: list, feature, cards. The first letter gets the list layout, the plainest one, with the conversation button.
The posts and the button as the email will carry them. Every picked post must still resolve to a published post with a slug and a hero image, and every post page and the call to action page must answer over HTTP. A post that lost its hero since the pick is a hole in the email, so it fails the step rather than raising a warning.
The email is rendered exactly as the list would receive it, HTML and plain text, tracking on. The step checks what a person would otherwise have to open the source to see: size under the Gmail clip limit, a non-empty text part, every link tagged for tracking, the unsubscribe link signable, all three posts resolved. Only when every check passes does it send one [TEST] email to the approver, addressed by first name from the CRM. The run then parks at ready and stops calling itself.
The approver reads the test in a real inbox, edits on the broadcast page if anything needs it, presses Approve, then Start sending. The existing send routine mails the list in batches of fifty. The agent has no send verb: there is no code path from the pipeline to the list.
Every exception has an owner
A run stops at the first step that fails its check, writes the error on the broadcast, and tells the ops channel once. Nothing is retried on its own; a person presses Retry step or Stop.
| Condition | What happens | Where you hear about it |
|---|---|---|
| A draft the agent opened is still waiting from a previous week | Monday opens nothing new; the waiting draft is named | The cron’s run row |
| Nothing happened in the last ten days (no events, summaries or journal pages) | Gather fails; the run stops | Lark ops message, the broadcast page |
| The journal is not configured | It contributes nothing; events and meetings carry the week | Nowhere, by design |
| Fewer than three usable data points, or none from the last seven days | Gather fails; the run stops | Lark ops message, the broadcast page |
| Fewer than three unsent posts on the brand | The pick falls back to the posts featured longest ago and says so | The step summary on the run row |
| The draft breaks a rule (over 220 words, an em dash, a client name, the subject repeated in the body, no greeting or sign-off) | Write fails with every rule it broke; nothing is saved | Lark ops message, the broadcast page |
| A call to action has no landing page yet | Skipped in the rotation until its URL exists | Nowhere, by design |
| A picked post lost its hero image, or its page or the button’s page does not answer | Assemble fails; the run stops | Lark ops message, the broadcast page |
| The rendered HTML is over the Gmail clip limit, a link carries no tracking, or the unsubscribe secret is missing | Validate fails before any test is sent; the checklist is saved | Lark ops message, the broadcast page |
| The test address has no CRM contact | Validate fails; the test cannot be addressed | Lark ops message |
| The broadcast is no longer a draft | The step is skipped; the agent only works on drafts | The run row |
| A hand-off between steps is dropped | The run sits on its current step; Continue hands it on again | The broadcast page shows the step with no error |
| A step stops with an error | Retry step clears the error and runs that step again from its own inputs | The broadcast page |
Data, timing, and how we know it works
Reads and writes
- Reads: events, meeting summaries and journal pages from the last ten days; the brand profile; published posts and which ones sent letters featured; the last four sent letters; the CRM contact for the test address
- Writes: one draft broadcast (subject, preheader, body, posts, call to action, layout); the agent’s step, error and notes on that row; one test email logged against the broadcast; one routine run row per step
- The step pointer lives on the broadcast, so a run can be inspected, retried or stopped from its page at any point
- Models: the gather and write steps run on the frontier tier with a structured-output schema; the other four steps call no model
Timing model
- Monday 00:00 UTC, 07:00 in Ho Chi Minh City: the draft opens and the first step runs
- Each passing step makes one authenticated call to the step route for the next; a run finishes in minutes, one function per step
- The run parks at ready; nothing in the pipeline moves it past that state
- Tuesday is the send day by convention: the approver chooses the moment, or sets a send window on the broadcast
- One draft at a time: while an agent-opened draft waits, Monday opens nothing
How we know it works
- A Monday morning with no [TEST] in the approver’s inbox and no ops message is the alarm
- The step summary on each run row says what it did: how many data points, which posts, which button, the size of the email
- Every link carries a UTM content tag, so the broadcast’s results card shows which post and which button was clicked
- The rotation reads the last sent letter, so a skipped week never breaks the sequence
The seven elements
Every workflow we document has the same anatomy: seven elements, each assigned to a human, a machine, or both. This is the Centaur Map from our workflow design method.
Time. Monday 07:00 (+07) opens the draft and starts the run. A person can also start, retry, continue or stop a run from the broadcast page.
The last ten days of events, meeting summaries and journal pages; the brand profile for voice and rules; the published post list; the last four sent letters, so subjects and openings do not repeat.
The model decides which facts are worth a sentence and how to say them. The pipeline decides everything else: which posts, which button, which layout, and whether the email is fit to send. The approver decides whether it goes.
One broadcast row carries the whole run. Each step hands itself on with one authenticated call to its own route, so nothing polls and no browser tab stays open.
A draft broadcast with subject, preheader, body, three posts, a call to action and a layout; one [TEST] email in the approver’s inbox; one routine run row per step with the tokens it cost.
The approver reads the test as a real email, presses Approve on the broadcast page, then Start sending. The list is mailed in batches by the existing send routine, not by this agent.
Every link carries a UTM content tag, so the results card on the broadcast shows which post and which button worked. The rotation reads that history to decide what the next letter carries.
The posts this letter carries are written by the writer agent, which uses the same one-step-per-run pattern. Both agents stop at the same place: a person reads the real thing and decides.