Vocion 2.82
The third release index in two days. The 2026-09-15 release branch lands on main and is cut as v2.82.0, alongside three lines of new work: scoped, typed, human-editable memory behind an approval gate; a web connector and extractor that are honest about caps, dedup and what a sync did; and the Slack thread surface, a second verified measure source, adaptive thinking on Claude 4.6+, and two new manifesto principles.
This is the third release notes post in two days, and the first one dated 2026-09-16. The other two are Release notes — 2026-09-15 and Release notes — 2026-09-15 (afternoon), and nothing here replaces anything in either.
Where this went next. Six more versions shipped the same day, on top of this one. Vocion 2.88 covers
v2.82.1throughv2.88.0: the schema fix promised at the bottom of this post, a voice gate on outbound copy, the briefing rebuilt as a typed document, one list across four surfaces, full-width record pages, and@artifact.
Vocion core is at v2.82.0, cut from main at 4c5af85a. Three tags went out overnight ahead of it — v2.81.0, v2.81.1, v2.81.2 — because semantic-release cuts a version on every push to main, and the extractor work landed as four separate pull requests.
This is 2.82, not a 3. Read the GitHub notes for v2.82.0 and they look like an entire quarter, because they are cumulative: the release/2026-09-15-rail branch merged into main inside that tag, so everything the rail train carried — the icon rail, the artifact, the inbox, the nav sweep, team report v2, the workspace mailbox — appears in the tag's changelog for the first time despite having deployed the day before. All of it is already written up in the two 2026-09-15 posts. What is actually new in this release is below.
Three people built it. The memory line and the reconciliation are one engineer's; the connector and extractor work is another's; the Slack, analytics, manifesto and harness changes are the third's. Posts here are published under the team rather than by individual byline, as they have been since the feed started.
What you can do now that you could not yesterday
- Read, reword, re-file and re-scope the rules your agents have learned — and have an approved rule actually reach the model, which it previously did only if the model went looking.
- File a preference about yourself and have it apply at once, for you alone, without anyone else's agents inheriting it.
- Reply to Vocion in a Slack thread and have "can you add a screenshot of this?" resolve, because the system remembers what it posted.
- Have feedback in a Slack thread become a decision in your inbox rather than a polite reply.
- Read a traffic number on the team report that came from the analytics property rather than from the agent whose work it grades — and see "Google Analytics · not connected" instead of a zero when nobody asked it anything.
- Run a first sync over a large listing without it quietly stopping two-thirds of the way through.
Feature releases
| Change | Post | Source |
|---|---|---|
| Typed, scoped memory: preferences, knowledge and procedures, filed per workspace, agent, user, mission or workflow, editable before approval | Memory you can read, edit, scope and approve | 411574da, 5d2a1ffa, 537a78e4, 31589fca, 38c975e4 |
| The Product Design Manifesto gains §18 and §19; the pre-ship test grows to fifteen questions | Build what the work demanded, and extend the core | #359 |
Developer updates
| Subject | Post | Source |
|---|---|---|
One memory table behind a LangGraph BaseStore, scope in the key, a read-only mount, a digest middleware — and where the gate is structural versus instructed | The memory model, and what the approval gate actually holds | 411574da…31589fca, 384a2d49, and the post-tag fix 7f5ebfaf |
| Listing-first crawl ordering, feed discovery scoped to the listing path, caps sized per document, a dry-run line carrying the real dedup key, a record that cannot flag the card it refreshes | A sync that tells you what it did | #356, #362, #364, #365 |
Slack threads, screenshots, and feedback that becomes work
#360 closes three gaps that were found the way §18 says they should be: by hitting them. Someone replied to a release announcement in a Slack thread and the agent answered "No page context here — I can't tell what 'this' refers to."
Context. A slack_post table (migration 0102) records every message Vocion puts into Slack — announcement, reply, introduction — with its channel, timestamp, thread, workspace, agent and, for an announcement, what it was announcing. When a thread starts with our own post, "this" resolves with no Slack read scope at all, because Vocion should not need a permission to remember what it said. On top of that, buildSlackThreadContext adds the channel name, the posters and the other replies, each degrading independently, and the result travels as the turn's PageContext — the same slot a dashboard page fills, so this and here resolve identically on both surfaces.
When a scope is genuinely missing, the reply names it and says what it would have bought — "groups:history would let me read the message this thread started with" — once per thread, from scopeGapSentence in code rather than from a prompt. Only a real missing_scope counts: a bad token or an outage is not a permission to grant, and telling an admin to add a scope they already have is the same confident-wrong-answer failure in a different costume.
Screenshots. find_screenshots searches what the workspace already has — published image URLs from ingested web documents, image artifacts, a registered fixture library — and reports per result whether an outside service can fetch it, which decides how it can be shown. Replies and announcements carry images on a ladder: upload the bytes where the app holds files:write; otherwise a Block Kit image block, which Slack renders inline with only chat:write; never a bare link, which does not unfurl in a private channel. And POST /api/v1/chat-announcements posts an announcement with its screenshots and records it, so the original post carries the pictures instead of a later answer to "any screenshots?".
Feedback becomes work. classifyFeedback is a pure function over sentence shapes; the model is asked only when it returns unsure, so classifying "thanks!" never costs a turn. Real feedback records a learning candidate with the Slack permalink as its provenance and, where the workspace has a team that builds, files a recommendation in the Needs-you inbox with the thread verbatim and options Plan and start / Add to backlog / Decline. Approving it is what starts the work. Nothing executes from Slack; a Slack user id still authorises nothing.
Rendering a screenshot on demand is deliberately not built — it needs a headless render, a public serving URL for an authenticated artifact route, a per-route fixture set and a browser budget. The upload path is tested against a mocked Slack API, because the live app does not hold files:write.
A second verified measure source
#361. Team report v2 shipped with four provenance kinds, and the strongest of them — verified — supported exactly one connector. So every adoption number on the scorecard could only be agent-reported: the worker that did the work saying how much of it there was. Self-graded homework, on a product whose thesis is measured outcomes.
verified is now a nested discriminated union: kind picks the provenance arm, and inside it connector picks the system of record — hubspot or web-analytics. Reading a CRM and reading an analytics property are the same strength of evidence and two entirely different queries; a flat fifth kind would have had to invent a rank between verified and observed that does not exist. Both levels stay closed, so an unknown kind or an unknown connector fails at parse time rather than becoming a source that reads nothing and shows a zero wearing a green chip.
The filter is deliberately narrow — landing-page prefix, default channel group, event name, all ANDed. A session-level engagement predicate was dropped from the design rather than shipped, because the Data API has no such filter and the key could only have filtered something other than what it claimed to.
The better half is what happens when there is no number. A reading now carries which gap it is, and the chip describes the gap instead of the provenance: unconfigured renders "Google Analytics · not connected", error renders "read failed", unsupported renders "cannot be read". It is never green — an unread source has verified nothing — and an unreadable measure is excluded from teams on target rather than counted as a miss. One zero is still real: an analytics property answering "no sessions matched" is a measurement and shows as 0. Everything else in the read path throws rather than returning a number, so a caught failure cannot become a zero by accident.
The credential is the first measurement platform, and the only one resolved per organisation with no row id in hand — a measure names what it measures, not which secret to spend — which is why it is one-live where every connector platform is many. Access tokens are cached against a digest of the exact key that minted them, never against an organisation, so a rotated key takes effect on the next read. Nothing here has been run against a live property yet; the transport is mocked throughout.
Also in this release
Adaptive thinking on Claude 4.6+ (#357). Yesterday's sampling-parameter post ended by naming an open item: the extended-thinking branch still sent budget_tokens with temperature: 1, and the comment saying it must be revisited before 4.7 had not been acted on. It has now. On 4.6 and later the request goes out as thinking: { type: 'adaptive' } with no temperature and no budget; older models keep the budgeted form. The line is drawn at 4.6 — one generation earlier than the sampling guard — because adaptive is supported there, so there is no reason to keep sending a deprecated shape. The branch is opt-in and unset on the instance we operate, so nothing was failing live; the first person to enable it on a 5-family model would have lost every turn.
The browser test project (#358). Seventeen failures, all pre-existing, none of them product bugs. Fourteen came from one line: the browser project defaulted to a 414×896 viewport — a phone — and the chat rail is a side-by-side column only above 1200px, so every assertion looking for a complementary landmark was timing out against a sheet dialog it never meant to test. One viewport setting fixed those. The remaining three were assertions the branch's own work had already invalidated: a renamed control, a hook whose row had gained the rail's geometry, and two tests that contradicted each other about whether the chat empty state names an agent — the manifesto rule won, and the stale test was rewritten into a strictly stronger one. No product code changed; no test was deleted, skipped or weakened.
Deterministic credential ordering (#363). listTokens ordered by created_at alone. A rotation revokes and re-inserts in one transaction, so two rows share a millisecond, and with no second sort key Postgres may return either first — which meant the dashboard could show a rotated key above its replacement, and a test asserting that order failed at random in a full run while passing alone. Adding id as a tiebreak fixes the product behaviour and the flake together. The pull request is also candid about what it is not: a first attempt read the flake as cross-file test interference and scoped a dozen unscoped deletes, which turned one intermittent failure into thirty-eight consistent ones. That was reverted. The unscoped deletes remain a genuine latent hazard and are not what broke this test.
The reconciliation
Worth recording, because it is the least glamorous part of the release and the one most likely to matter to anyone running a fork.
main and release/2026-09-15-rail had forked 45 commits to 24 — the rail train on one side, a day of extractor and crawl work on the other — while production ran pinned to the branch. Merging them was not automatic:
- Canvas versus artifacts.
mainshipped the canvas grid; the rail replaced it the same day with one live versioned artifact per conversation. The rail's model wins, and the superseded grid was deleted rather than left importable. - Both sides added columns to the same tables.
conversationgainedsurfacefrom one andcontext_jsonfrom the other;learning_candidategained the scoped-memory references from one andsource_reffrom the other. All kept. - Both sides claimed migrations
0099–0102. The scoped-memory migrations were renumbered to0103–0107in the commit before the merge, so the chain reads base0000–0098, rail0099–0102, memory0103–0107.
And the merge shipped broken. Resolving a duplicated column block in Schema.ts removed the declarations but left the block's /** opener behind, which commented out every declaration after it; the Next build failed on a missing conversationMessageSchema. The one-line fix existed locally and was never staged before the merge commit.
That matters for anyone pinning a tag. v2.82.0 was cut from the merge commit 4c5af85a, so it carries the broken schema. The fix, 7f5ebfaf, landed on main eight minutes later and is not in this tag; it will be in the next one. Build from main rather than from v2.82.0, or cherry-pick that one line.
How to read these
Each post opens with a status line saying what state the change is in and which commits it is. Every falsifiable claim cites a file, a pull request or a commit, and developer posts end with a "Where this lives in the repo" section. Where something was not verified end to end — and in this release several things were not — the post says which part. The measurements quoted in the extractor post come from instrumented runs rather than a published benchmark, and the memory post separates what is enforced in code from what is currently asked for in a prompt.
Where this lives in the repo
- Core:
vocion/vocion-core. The tagv2.82.0is4c5af85a;mainis7f5ebfaf, one commit ahead of it. The tags between the previous production pin and this one:v2.81.0,v2.81.1,v2.81.2,v2.82.0. - Pull requests in this release, in merge order: #356, #357, #358, #359, #360, #361, #362, #363, #364, #365, plus the scoped-memory commits and the reconciliation merge.
- The feeds are feature releases and developer updates, summarised on /releases and available as RSS.
/blog/tag/releasestill redirects.