What changed
Each Daily Brief now ends with 1-3 specific actions, queued for you to mark taken or dismissed. The next morning’s brief receives that history and can reference it directly:“Monday’s coaching for Sarah hasn’t moved her CSAT yet; second pass needed. The flat AI deflection still tracks to last week’s call to widen the FAQ — push that this week.”If you took an action and the metric moved, the brief says so. If you dismissed it and the underlying issue is still live, the brief surfaces it again with that context. If it’s still pending after a few days, the brief notes the staleness rather than silently re-recommending.
Where actions show up
- In-app Daily Brief — the prose ends, then a small numbered list with a checkbox per action. Click to mark taken; “Dismiss” link to clear it from the queue without claiming it was done.
- Email Daily Brief — actions render as a “Today’s actions” block beneath the prose. The marking happens in the app (email links open the app); the email is the notification, not the interaction surface.
- Slack Daily Brief — same pattern: a “Today’s actions” section under the brief body with a one-click link back to the app to mark them.
Action shapes
The model is instructed to emit actions that are:- Verb-first: “Coach Sarah on response time”, “Investigate Tuesday’s CSAT dip”, “Push back on the new SLA targets”.
- Concrete: actionable by end-of-day, not vague directional advice.
- Under 90 characters: one line, no rationale (the rationale lives in the prose above).
Statuses
Each action carries one of three states:- Pending — default. Sits in the queue.
- Taken — you acted on it. Tomorrow’s brief receives this and references the result if visible in the metrics.
- Dismissed — not the right call. Tomorrow’s brief sees the dismissal and shouldn’t re-recommend the same thing without different framing.
What the next brief sees
The buildPrompt() function injects anACTION HISTORY block listing each recent action with its age, status, and (where available) the measured 7-day outcome:
Outcome attribution
Each action created from a Daily Brief — and each auto-flag action Forepost queues itself — is stamped at creation with two extra fields:- metric_kind: which metric the action is most likely to move (csat / volume / firstResponse / deflection / utilisation / backlogAge / oneTouch). Inferred from the action text by a keyword match (“coach” → csat; “hire” → utilisation; “deflect” → deflection; “backlog” → backlogAge; etc.). When nothing matches, the field stays null and the action is excluded from attribution.
- metric_baseline: the workspace value of that metric at the moment the action was created.
metric_delta_7d with an attributed_at timestamp. The brief’s history block then shows the outcome inline.
Honest semantics:
- Deltas below ±0.5 are tagged no movement — sub-resolution drift is noise, not a result.
- For metrics where higher is better (CSAT, deflection, one-touch), a positive delta is worked.
- For metrics where lower is better (FRT, backlog age, utilisation, volume), a negative delta is worked.
- Anything else is did not work.
GET /admin/attribution, which returns per-metric “worked / neutral / regressed” counts and average deltas.
Why we don’t store rationale
Actions are deliberately just text. We considered storing the model’s reason for each action and surfacing it in tomorrow’s history, but that doubled the prompt budget and added a layer of indirection that the leader doesn’t actually need. The prose brief is the rationale. The action queue is the pointer back to it. If you need the original context for a stale action, the brief that produced it is in the Archive.Draft and dispatch
Each pending action now carries three extra affordances in-app: Draft Slack, Draft email, and → Linear.- Draft Slack / Draft email — generate a copy-ready message the leader can send. One Haiku call per request; the draft is written in the leader’s voice (confident, direct, no exclamation marks, no greetings on Slack), references real numbers from the workspace, and ends with a specific ask. Drafts are saved in
action_draftsso coming back to the page shows the last generation without re-spending the call. Rate-limited at 30 drafts per hour per workspace. - → Linear — files the action as a Linear issue in the workspace’s connected Linear org. Title is the action text (truncated to 80 chars); description is the full text plus a footer link back to Forepost. Requires Linear to be connected in Settings → Integrations → Linear (Linear integration). Returns the issue URL on success.
API
For workspace owners using the public API:| Endpoint | Method | Purpose |
|---|---|---|
/actions | GET | List actions for the authenticated user (default: last 7 days). Query params: since (ms epoch), limit (max 200). |
/actions | POST | Create one or more pending actions. Body: { actions: [string], briefId?: number }. Used by the SPA after parsing a brief; not needed for typical integration. |
/actions/:id | PATCH | Update an action’s status. Body: { status: "pending" | "taken" | "dismissed" }. |
/actions/:id/draft | POST | Generate a Slack-DM or email draft. Body: { channel: "slack" | "email", recipient?: string }. Returns { draft: { id, channel, text, createdAt } }. |
/actions/:id/linear | POST | File the action as a Linear issue. Optional body: { teamId: string } to pick a Linear team explicitly; otherwise the first accessible team is used. Returns { ok, issue: { id, identifier, url } }. |
Limits
- Actions per brief: capped at 5 (the parser drops extras).
- Action text length: 240 chars max (truncated server-side).
- Action writes per user: 60/hour.