A review decision proposes a learning candidate instead of writing a rule
Release notes 2026-09-15 — vocion-core#318 (merged): approving or rejecting an agent-proposed action no longer writes a generated rule into the agent's learning step; it queues a candidate for the feedback classifier, deduplicated, for a person to accept.
Shipped: vocion-core#318, merged to main on 2026-09-15. Part of the 2026-09-15 release notes.
The outcome
A learning store should hold rules a person stood behind, not sentences a template generated. Every approve or reject on an agent-proposed action used to write one generated learning row straight into the proposing agent's step — "REJECTED (confidence 0.72): … do not propose this class again without stronger evidence". Over weeks that store filled with near-duplicates nobody had read, and the agent read all of them on every turn.
What changed
ReviewService.recordActionDecisionLearning no longer calls addLearning. It enqueues a feedback job — { source: 'review', externalId: 'action_run:<id>:<signal>', payload: { text, targetSlug, agentSlug, sourceRunId, … } } — with a polarity hint: an approve with a reason is reinforce, a reject is correct. A decision with no reason queues nothing.
From there the existing pipeline takes over (packages/core/src/services/feedback/): the feedback worker classifies the note, duplicate detection checks it against the step's current rules, and the rule recorder writes a pending learning candidate. Candidates show on /dashboard/learnings for a person to approve or reject — the same surface a Slack or Drive comment lands on.
decide and the v1 decide route accept learn?: boolean (default true). learn: false stops both queueing paths, for automated callers such as a cron that rejects past-event proposals in bulk.
Tenant impact, stated plainly
This changes learning behaviour for every tenant. Nothing is written into a learning step by a decision any more; only a candidate is created, and only when there was a note. Steps stop growing on their own. Workspaces that relied on the automatic rule — there is no evidence any did well — should expect fewer, better rules and a candidates queue that needs a glance.
Why
The Product Design Manifesto: every correction is information, and repeated corrections should become rules — through a person's judgement, once, rather than a template's, every time. The Needs-you inbox shipped in the same release uses the same path for asks answered with reject or Other plus a note (services/feedback/askFeedbackQueue.ts), so the two decision surfaces feed one learning loop.
Tests: seven cases in ReviewService.decisionLearning.test.ts cover polarity, step targeting, the no-reason case and learn: false.
Where this lives in the repo
- The change:
services/ReviewService.ts(recordActionDecisionLearning) and its testsReviewService.decisionLearning.test.ts - The pipeline it now feeds:
services/feedback/runFeedbackQueue.ts,FeedbackWorkerService.ts,services/feedback/classifier.ts,duplicateDetection.ts,ruleRecorder.ts - Candidates surface:
services/LearningCandidateService.ts,/dashboard/learnings learn?: booleanon the decide body:app/api/v1/reviews/decide/route.ts- PR: #318