The inbox reads like a list of decisions, not a list of records
Every proposal in the Needs-you inbox is now described the way a person would describe it, grouped by the record it concerns, with a real age and columns that line up. Release notes 2026-09-15 — vocion-core#335, merged.

A queue of things waiting on you is only useful if you can tell, without opening anything, which one to open.
The Needs-you inbox already collected every decision an agent was waiting on into one list. What it put in that list was closer to the database row than to the decision. On a deployment we operate, a couple of hundred items rendered as a couple of hundred identical rows — the action id, the word "Review", and an age that said "just now" for all of them.
It now reads like something a colleague would say to you.
A row says what the decision is, which record it concerns, who proposed it, how confident they are, how much is at stake and how long it has waited.
A row describes the decision
services/inbox/describeActionRun.ts holds one describer per action id — pure functions over the stored row, no I/O — so each kind of proposal gets a sentence rather than a type name:
- an enrolment reads Enroll Jamie Smith (Redpoint IT) in MSP triage nurture
- a mail reads Email someone@example.com — Following up on Tuesday, or Draft email to … when it is a draft
- a CRM update names the record and the fields it would change
- anything without a describer falls back to the first sentence of the proposal's rationale, then to the action id spelled out in words
Every description carries a record reference (hubspot:deals:<id>, email:<address>), the proposing agent, the confidence, the amount where there is one, and a breadcrumb subline: record › action kind › proposed by agent.
Where a describer has to guess, it says so rather than inventing. A CRM update can only show old → new if the proposing agent supplied prior values on the proposal; the action schema has no "before" field, so until proposers add it a row shows the new value alone.
Rows about the same record group together
groupByRecord collapses proposals that share a record key into one row — four proposals, aged by the oldest, at the lowest confidence of the set — which opens a decision sheet at /dashboard/inbox/r/:recordKey. Approve or reject each proposal there, with the agent's own recommendation pre-selected, and submit once.
That sheet is the same component the asks use, with a review adapter. It decides through POST /api/v1/reviews/decide, which runs the same capability check the full review screens run — the sheet has no path of its own to authorise.
The age is real, and so are the counts
services/inbox/reviewRows.ts reads created_at, decided_at and decided_by off the action plane for the three tabs — Open, Decided, Snoozed — so rows no longer all sort at "now". The tab counts come from the same query that fills the tab, and a grouped sheet counts once, the way the page renders it.
Filter chips cover action kinds and proposing agents, each with its count. Search is debounced over the title, the subline, the agent and the action id.
Every control writes only to the URL — ?tab=&q=&sort=&kinds=&agents= — so a filtered view is a link you can bookmark or paste to someone. The default sort is oldest first; Highest value, Highest confidence and Newest are the alternatives, and the Decided tab defaults to Newest because on that tab recency is the question.
The header's "What changed" line now names the last actual decision — an answered ask or a decided proposal — rather than the most recent sync event.
44px rows, hairlines instead of cards, and numbers right-aligned in fixed columns so they line up down the list. Approve, Reject and Open appear on hover and on keyboard focus, and stay visible on touch.
Known limits
Snooze still has to be done from the review page, not from a row. The open action plane is loaded in memory, which is fine at a few hundred rows and is the thing to paginate first if a workspace reaches thousands.
Where this lives in the repo
Merged as vocion-core#335, and shipped in the 2026-09-15 release alongside the rest of this batch.
- Describers:
packages/core/src/services/inbox/describeActionRun.ts - Rows, grouping and tabs:
packages/core/src/services/inbox/reviewRows.ts,InboxService.listInbox - List and controls:
InboxRow,InboxControls, and the record sheet at/dashboard/inbox/r/:recordKey - Thirteen tests in the inbox suites: one describer test per action kind plus a fallback and a malformed payload, and PGlite tests for grouping, the real
created_at, search, the kind and agent filters, the three sorts, the decided tab, and the badge count matching the page
The row widths are being aligned to the shared COLUMN scale from the new pattern library — see A pattern library for list, detail and ledger.
Related: Needs you: every decision an agent is waiting on, answerable from a phone, which introduced the ask primitive this list is built on, and Agents now recommend a decision, whose recommendation the sheet pre-selects.