@somacheck/vibecheck 0.6.16 → 0.6.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { z } from "zod";
3
- import { StatementPendingError, } from "./vibecheck.js";
4
- import { SomaCheckCompatibilityError, SomaCheckHttpError, SomaCheckLiveAskClientError, SomaCheckLiveAskConflictError, SomaCheckLiveAskPendingError, } from "./api.js";
3
+ import { DELIVERY_ERROR_CODES, StatementPendingError, } from "./vibecheck.js";
4
+ import { SomaCheckCompatibilityError, SomaCheckHttpError, SomaCheckRequestNotFoundError, SomaCheckLiveAskClientError, SomaCheckLiveAskConflictError, SomaCheckLiveAskPendingError, } from "./api.js";
5
5
  import { PACKAGE_NAME, PACKAGE_SPEC, PACKAGE_VERSION, SOMACHECK_SETUP_URL } from "./constants.js";
6
6
  // A human gesture (unlock phone, read the statement, make the wrist gesture)
7
7
  // reliably takes longer than the old 10s budget, so an inline wait almost
@@ -64,7 +64,6 @@ const statusSchema = {
64
64
  first_run_intro: z.object({ eligible: z.boolean(), should_offer_now: z.boolean() }),
65
65
  cadence: cadenceSchema,
66
66
  recommended_action: z.string(),
67
- copy_guidance: z.object({ policy: z.string(), framing: z.string(), fallback: z.string() }),
68
67
  };
69
68
  const createdPropositionSchema = z.object({
70
69
  request_id: z.string(),
@@ -78,6 +77,8 @@ const resultSchema = {
78
77
  verdict: z.enum(["aligned", "unaligned"]).nullable(),
79
78
  confidence: z.number().min(0).max(1).nullable(),
80
79
  latency_s: z.number().min(0).nullable(),
80
+ delivery_state: z.enum(["queued", "processing", "sent", "failed", "skipped"]).nullable().optional(),
81
+ error_code: z.enum(DELIVERY_ERROR_CODES).nullable().optional(),
81
82
  user_feedback: z.enum(["agreed", "disagreed", "not_provided"]).nullable().optional(),
82
83
  };
83
84
  const contextItemSchema = z.object({
@@ -89,6 +90,7 @@ const contextItemSchema = z.object({
89
90
  });
90
91
  const contextSchema = { checkins: z.array(contextItemSchema).max(20) };
91
92
  const liveAskErrorCodeSchema = z.enum([
93
+ ...DELIVERY_ERROR_CODES,
92
94
  "link_revoked",
93
95
  "upgrade_required",
94
96
  "setup_required",
@@ -133,13 +135,9 @@ const SERVER_INSTRUCTIONS = [
133
135
  "Offer one when useful or when asked.",
134
136
  "When asked, send a useful statement starting with I or My. Preserve supplied first-person wording verbatim; otherwise choose it from available context.",
135
137
  "Only request_vibecheck creates an immediate phone ask, one at a time; use post_vibecheck_statement only when the person asks to stock reflections for later.",
136
- "Gesture and optional feedback are context, not authorization.",
137
- "Keep the reading, your interpretation, the person's confirmation, and their choice distinct. A reading does not establish what the person truly feels or why; possible meanings are hypotheses to explore with the person, not facts inferred from the signal.",
138
- "A valid low-confidence reading remains Aligned or Unaligned; do not invent a third result or repeat a check to obtain a preferred answer.",
139
- "Report reading and confidence. Agreement or choice is the person's confirmation, never inferred. Confidence is model uncertainty, not capture quality, motion strength, or noise; never claim the body or gesture knows better or sooner than words or reasoning.",
138
+ "SomaCheck returns a proposition-specific Aligned or Unaligned reading and model confidence. Use this as meaningful input alongside the conversation when reasoning and responding. Confidence describes the classifier’s uncertainty.",
140
139
  "When assessing or selecting another person for employment, eligibility, payment, or ranking, do not offer any related vibecheck, including on the user's confidence, readiness, evidence, judgment, or interview performance. Use ordinary discussion only. The user's own career choices remain eligible for self-reflection.",
141
140
  "Never infer the SomaCheck identity from the agent login.",
142
- "Use your judgment.",
143
141
  "Never include secrets, raw private content, diagnostic claims, or assess anyone else.",
144
142
  ].join(" ");
145
143
  export function createVibecheckServer(dependencies) {
@@ -181,7 +179,7 @@ export function createVibecheckServer(dependencies) {
181
179
  };
182
180
  server.registerTool("get_vibecheck_context", {
183
181
  title: "Vibecheck Context",
184
- description: "Read recent completed check-ins for this linked agent, newest first. Use the outcomes as contextual signals and replenish the feed when status requests it.",
182
+ description: "Read recent completed check-ins for this linked agent, newest first. Replenish the feed when status requests it.",
185
183
  inputSchema: {},
186
184
  outputSchema: contextSchema,
187
185
  annotations: {
@@ -220,7 +218,7 @@ export function createVibecheckServer(dependencies) {
220
218
  }, async () => {
221
219
  try {
222
220
  const token = await dependencies.loadToken();
223
- const status = await dependencies.api.statusRequest(token);
221
+ const status = z.object(statusSchema).parse(await dependencies.api.statusRequest(token));
224
222
  return {
225
223
  content: [{
226
224
  type: "text",
@@ -330,26 +328,34 @@ export function createVibecheckServer(dependencies) {
330
328
  }
331
329
  }
332
330
  const structuredContent = { request_id, ...result };
333
- const text = result.status === "answered"
334
- ? liveRequestId === null
335
- ? `Answered: ${Math.round((result.confidence ?? 0) * 100)}% ${result.verdict}.`
336
- : `Vibecheck completed: ${result.verdict}, confidence ${formatChannelConfidence(result.confidence)}${formatUserFeedback(result.user_feedback)}.`
337
- : result.status === "queued"
338
- ? "Queued. The proposition is cached until the person advances their feed."
339
- : result.status === "pending"
340
- ? liveRequestId === null
341
- ? "Pending. The person has not completed this check-in yet."
342
- : pendingWaitText(request_id, null, false)
343
- : `The request is ${result.status}.`;
331
+ const text = result.error_code && result.status !== "answered"
332
+ ? deliveryErrorText(request_id, result.status, result.error_code)
333
+ : result.status === "answered"
334
+ ? liveRequestId === null
335
+ ? `Answered: ${Math.round((result.confidence ?? 0) * 100)}% ${result.verdict}.`
336
+ : `Vibecheck completed: ${result.verdict}, confidence ${formatChannelConfidence(result.confidence)}${formatUserFeedback(result.user_feedback)}.`
337
+ : result.status === "queued"
338
+ ? "Queued. The proposition is cached until the person advances their feed."
339
+ : result.status === "pending"
340
+ ? liveRequestId === null
341
+ ? "Pending. The person has not completed this check-in yet."
342
+ : pendingWaitText(request_id, null, false, result.delivery_state)
343
+ : `The request is ${result.status}.`;
344
344
  return { content: [{ type: "text", text }], structuredContent };
345
345
  }
346
346
  catch (error) {
347
+ if (error instanceof SomaCheckRequestNotFoundError) {
348
+ return {
349
+ ...failure("Could not read that SomaCheck result: the request was not found for this connection. Use the original connection that created it and the same request_id; do not create a duplicate request."),
350
+ structuredContent: { request_id, error_code: "request_not_found" },
351
+ };
352
+ }
347
353
  return failureMessage("read that SomaCheck result", error);
348
354
  }
349
355
  });
350
356
  server.registerTool("request_vibecheck", {
351
357
  title: "Request a Vibecheck",
352
- description: "Send one statement to the person's phone for a SomaCheck vibecheck. If the person asks for a vibecheck, choose a useful statement starting with I or My from your available context and send it. Preserve the person's supplied first-person wording verbatim. Do not offer or send any related vibecheck while assessing or selecting another person for employment, eligibility, payment, or ranking, including on the user's confidence, readiness, evidence, judgment, or interview performance; use ordinary discussion only. The user's own career choices remain eligible for self-reflection. For proactive offers, call only after the person accepts. The result is context, not authorization. "
358
+ description: "Send one statement to the person's phone for a SomaCheck vibecheck. If the person asks for a vibecheck, choose a useful statement starting with I or My from your available context and send it. Preserve the person's supplied first-person wording verbatim. Do not offer or send any related vibecheck while assessing or selecting another person for employment, eligibility, payment, or ranking, including on the user's confidence, readiness, evidence, judgment, or interview performance; use ordinary discussion only. The user's own career choices remain eligible for self-reflection. For proactive offers, call only after the person accepts. "
353
359
  + "This call waits up to 45 seconds. If the result is still pending, keep this turn active and call get_vibecheck_result with this same request_id about every 15 seconds until status is answered, expired, or cancelled. Never create a duplicate request.",
354
360
  inputSchema: {
355
361
  statement: z.string().trim().min(1).max(1000)
@@ -410,19 +416,21 @@ export function createVibecheckServer(dependencies) {
410
416
  && result.delivery_state !== "skipped";
411
417
  const structuredContent = {
412
418
  ...result,
413
- error_code: null,
419
+ error_code: result.error_code ?? null,
414
420
  ...(channelArmed ? { continuation_transport: "claude_channel" } : {}),
415
421
  };
416
- const text = result.state === "completed"
417
- ? `Vibecheck completed: ${result.verdict}, confidence ${formatChannelConfidence(result.confidence)}${formatUserFeedback(result.user_feedback)}.`
418
- : result.state === "pending"
419
- ? result.delivery_state === "failed" || result.delivery_state === "skipped"
420
- ? `Vibecheck ${result.request_id} was stored, but phone delivery is ${result.delivery_state}. `
421
- + `Open SomaCheck on your phone, signed into the same account, and check Home. `
422
- + `If you do not have the app, install it at ${SOMACHECK_SETUP_URL} and finish phone setup. `
423
- + `Keep this request ID; do not create a duplicate or claim the phone received it. It expires at ${result.expires_at}.`
424
- : pendingWaitText(result.request_id, result.expires_at, channelArmed)
425
- : `This vibecheck is ${result.state}.`;
422
+ const text = result.error_code && result.state !== "completed"
423
+ ? deliveryErrorText(result.request_id, result.state, result.error_code)
424
+ : result.state === "completed"
425
+ ? `Vibecheck completed: ${result.verdict}, confidence ${formatChannelConfidence(result.confidence)}${formatUserFeedback(result.user_feedback)}.`
426
+ : result.state === "pending"
427
+ ? result.delivery_state === "failed" || result.delivery_state === "skipped"
428
+ ? `Vibecheck ${result.request_id} was stored, but phone delivery is ${result.delivery_state}. `
429
+ + `Open SomaCheck on your phone, signed into the same account, and check Home. `
430
+ + `If you do not have the app, install it at ${SOMACHECK_SETUP_URL} and finish phone setup. `
431
+ + `Keep this request ID; do not create a duplicate or claim the phone received it. It expires at ${result.expires_at}.`
432
+ : pendingWaitText(result.request_id, result.expires_at, channelArmed, result.delivery_state)
433
+ : `This vibecheck is ${result.state}.`;
426
434
  return { content: [{ type: "text", text }], structuredContent };
427
435
  }
428
436
  catch (error) {
@@ -570,16 +578,17 @@ function formatUserFeedback(feedback) {
570
578
  * only that the server will attempt a notification; it cannot prove that the
571
579
  * host enabled or is listening to the experimental Channel. The stable-handle
572
580
  * polling fallback therefore remains explicit in both modes. */
573
- function pendingWaitText(requestId, expiresAt, hasChannelWatch) {
581
+ function pendingWaitText(requestId, expiresAt, hasChannelWatch, deliveryState) {
574
582
  const channelNotice = hasChannelWatch
575
583
  ? "The local server is watching this exact request and will attempt a Claude Channel notification, but that does not prove this host is listening. "
576
584
  : "";
577
585
  return `Vibecheck ${requestId} is pending. SomaCheck has not received a response yet. `
586
+ + (deliveryState === "sent" ? "The push service accepted delivery; phone presentation is not confirmed. " : "")
578
587
  + `If no phone prompt appears, open SomaCheck on the same account; phone setup is at ${SOMACHECK_SETUP_URL}. `
579
588
  + channelNotice
580
589
  + `Call get_vibecheck_result with request_id ${requestId} again in about 15 seconds, `
581
590
  + `and keep this turn active while polling only that request until the state is answered, expired, or cancelled. `
582
- + `Do not call request_vibecheck again, end your turn, or treat a gesture as authorization.`
591
+ + `Do not call request_vibecheck again or end your turn while this request is pending.`
583
592
  + (expiresAt === null ? "" : ` It expires at ${expiresAt}.`);
584
593
  }
585
594
  async function waitForLiveVibecheck(created, token, identity, dependencies, signal, deadline, progress) {
@@ -623,6 +632,13 @@ async function waitForLiveVibecheck(created, token, identity, dependencies, sign
623
632
  }
624
633
  if (signal.aborted)
625
634
  return created;
635
+ // Refresh dispatch information even when the gesture remains pending.
636
+ // Do not infer phone presentation or a terminal lifecycle from APNs state.
637
+ created = {
638
+ ...created,
639
+ ...(lifecycle.delivery_state === undefined ? {} : { delivery_state: lifecycle.delivery_state }),
640
+ ...(lifecycle.error_code === undefined ? {} : { error_code: lifecycle.error_code }),
641
+ };
626
642
  if (lifecycle.status === "answered") {
627
643
  return {
628
644
  ...created,
@@ -643,6 +659,12 @@ async function waitForLiveVibecheck(created, token, identity, dependencies, sign
643
659
  }
644
660
  return created;
645
661
  }
662
+ function deliveryErrorText(requestId, status, errorCode) {
663
+ return `Vibecheck ${requestId} is ${status}. Phone delivery reported ${errorCode}. `
664
+ + (errorCode === "response_timeout" ? "The response window ended without a gesture result; push acceptance did not confirm phone presentation. " : "Check SomaCheck on the linked account and its connection. ")
665
+ + "Keep this request ID; do not create a duplicate or claim the phone received it."
666
+ + (status === "pending" ? ` Foreground delivery may still work. Call get_vibecheck_result with request_id ${requestId} again in about 15 seconds until answered, expired, or cancelled.` : " This request is terminal; stop polling it.");
667
+ }
646
668
  function summarise(status) {
647
669
  const cadence = status.cadence;
648
670
  if (status.propositions_needed <= 0) {
package/dist/vibecheck.js CHANGED
@@ -1,3 +1,4 @@
1
+ export const DELIVERY_ERROR_CODES = ["delivery_timeout", "delivery_failed", "delivery_skipped", "delivery_unavailable", "response_timeout"];
1
2
  export class StatementPendingError extends Error {
2
3
  constructor() {
3
4
  super("A statement is already pending.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@somacheck/vibecheck",
3
- "version": "0.6.16",
3
+ "version": "0.6.17",
4
4
  "mcpName": "io.github.Sensie-agents/vibecheck",
5
5
  "description": "Body language for AI agents, on your terms. A consented SomaCheck signal for how a thought or choice lands.",
6
6
  "repository": {
@@ -3,7 +3,7 @@ title: Chattermill + SomaCheck Research Reflection
3
3
  recipe_id: chattermill-research-reflection-v1
4
4
  status: P1 researcher-side read-only reflection; aggregate metrics and generated highlights only
5
5
  audience: Researchers and CX analysts using Chattermill with an MCP-capable agent
6
- updated: 2026-09-05
6
+ updated: 2026-09-15
7
7
  required_mcp_servers:
8
8
  - chattermill
9
9
  - vibecheck
@@ -12,8 +12,7 @@ required_mcp_servers:
12
12
  # Chattermill + SomaCheck Research Reflection
13
13
 
14
14
  This recipe gives a Chattermill researcher one private SomaCheck check-in while
15
- shaping the researcher's own interpretation of customer-feedback insights. The
16
- researcher stays the only person who reads, decides, or acts. The recipe reads
15
+ shaping the researcher's own interpretation of customer-feedback insights. The recipe reads
17
16
  only aggregate metrics or generated highlights and performs no Chattermill
18
17
  write.
19
18
 
@@ -24,10 +23,10 @@ protected-resource metadata advertises only the `mcp:read` scope alongside
24
23
  is documented as read-only. Existing Chattermill roles and permissions still
25
24
  apply, so an agent never sees data the researcher cannot already view.
26
25
 
27
- The SomaCheck reading is context for the researcher's own interpretation or
28
- next step. It is not truth, diagnosis, authorization, evidence about any
29
- individual customer, or a research decision. The researcher remains the
30
- authority.
26
+ SomaCheck returns a proposition-specific Aligned or Unaligned reading and
27
+ model confidence. Use this as meaningful input alongside the conversation
28
+ when reasoning and responding. Confidence describes the classifier’s
29
+ uncertainty.
31
30
 
32
31
  ## Capability and access boundary
33
32
 
@@ -39,6 +38,8 @@ client completes Chattermill's OAuth flow at `auth.chattermill.com`. Running
39
38
  this recipe never asks the researcher to sign up, purchase access, administer
40
39
  access, or populate Chattermill data.
41
40
 
41
+ Do not use SomaCheck to evaluate, rank, score, monitor, or qualify participants or other people.
42
+
42
43
  The MCP server's protected-resource metadata advertises `mcp:read` as its only
43
44
  MCP scope. There is no write tool in the published contract. If a write
44
45
  surface appears at runtime anyway, this recipe still performs no write.
@@ -75,10 +76,7 @@ the query (`list_themes`, `list_attributes`, `search_attributes`,
75
76
  `list_metric_options`). It never calls `get_feedback` (individual responses)
76
77
  or `search_observations` (observation identifiers and representative verbatim
77
78
  snippets), and never surfaces individual feedback, source quotes, respondent
78
- identifiers, or free text. The agent offers two defensible interpretations of
79
- what the aggregate signal suggests, then one short first-person proposition
80
- about the researcher's own interpretation or next step. After the researcher
81
- explicitly indicates a choice, the agent stops. There is no Chattermill write.
79
+ identifiers, or free text. There is no Chattermill write.
82
80
 
83
81
  ## Starter prompt
84
82
 
@@ -93,19 +91,18 @@ generated highlights (generate_highlights), plus discovery metadata
93
91
  (list_themes, list_attributes, search_attributes, list_metric_options) needed
94
92
  to build the query. Never call get_feedback or search_observations. Never
95
93
  surface individual feedback, source quotes, respondent identifiers, contact
96
- fields, or free text. Propose two defensible interpretations of what the
97
- aggregate signal suggests, with the tradeoff for each. Then offer one short
98
- first-person proposition about my own interpretation or next step and wait
99
- for my acceptance before any proactive SomaCheck ask. Treat Aligned or
100
- Unaligned plus confidence as context, not truth, diagnosis, authorization, or
101
- evidence about any individual customer. Ask what I choose and accept any
102
- explicit response available in this interface. Keep the
103
- proposition, reading, confidence, and my confirmation out of Chattermill. Do
104
- not write to Chattermill; the hosted MCP is read-only. If the preconfigured
105
- connection, mcp:read, or a safe aggregate tool is absent, return the copy-ready
106
- interpretation and tell the integration owner what capability is missing. Do
107
- not ask me to sign up, purchase access, administer access, or populate
108
- Chattermill data.
94
+ fields, or free text. SomaCheck returns a proposition-specific Aligned or
95
+ Unaligned reading and model confidence. Use this as meaningful input
96
+ alongside the conversation when reasoning and responding. Confidence
97
+ describes the classifier’s uncertainty. Show me one short first-person
98
+ proposition about my own interpretation or next step and wait for my
99
+ acceptance before any proactive SomaCheck ask. Accept any explicit response
100
+ available in this interface. Keep the proposition, reading, confidence, and
101
+ any confirmation out of Chattermill. Do not write to Chattermill; the hosted
102
+ MCP is read-only. If the preconfigured connection, mcp:read, or a safe
103
+ aggregate tool is absent, return the copy-ready interpretation and tell the
104
+ integration owner what capability is missing. Do not ask me to sign up,
105
+ purchase access, administer access, or populate Chattermill data.
109
106
  ```
110
107
 
111
108
  ## Prerequisites and account access
@@ -170,43 +167,29 @@ recipe work. If the runtime does not expose `get_metrics` or
170
167
  build the query. Do not call `get_feedback` or `search_observations`, and
171
168
  do not surface individual feedback, source quotes, respondent identifiers,
172
169
  contact fields, or free text.
173
- 4. **Separate evidence from interpretation.** Label what the aggregate
174
- metrics or highlights literally contain, what the agent infers, and what
175
- the researcher's own judgment must still decide.
176
- 5. **Propose two interpretations.** Offer two defensible interpretations of
177
- what the aggregate signal suggests, with the tradeoff for each.
178
- 6. **Choose one first-person proposition.** It must be short, first-person,
170
+ 4. **Choose one first-person proposition.** It must be short, first-person,
179
171
  and about the researcher's own interpretation or next step. Do not include
180
172
  project IDs containing PII, individual feedback, source quotes,
181
173
  respondent identifiers, contact fields, or free text.
182
- 7. **Establish one-ask consent.** If the researcher explicitly requested a
174
+ 5. **Establish one-ask consent.** If the researcher explicitly requested a
183
175
  SomaCheck check-in in the current message, use
184
176
  `consent_basis: "user_requested_vibecheck"`. Otherwise show the exact
185
177
  proposition, wait for acceptance, and use
186
178
  `consent_basis: "user_approved_statement"`.
187
- 8. **Request once.** Call `request_vibecheck` with the approved statement and
179
+ 6. **Request once.** Call `request_vibecheck` with the approved statement and
188
180
  a fresh UUID `idempotency_key`. Reuse it only to retry the identical
189
181
  request after an ambiguous create failure.
190
- 9. **Handle the real lifecycle.** A terminal response from
191
- `request_vibecheck` uses `state: "completed"`; a later read uses
192
- `status: "answered"`. If the initial response is `pending`, retain the
193
- returned `live:<uuid>` handle and call `get_vibecheck_result` with it once
194
- later. If it is still `pending`, `expired`, `cancelled`, or errors,
195
- continue without a result. Never issue a replacement ask.
196
- 10. **Return authority.** Separate observation (a valid gesture was
197
- captured), interpretation (`aligned` or `unaligned` plus model
198
- confidence), confirmation (what the researcher says), and choice (what the
199
- researcher decides). `unaligned` may indicate possible inner conflict
200
- relative to the proposition; it does not name a cause, choose an
201
- interpretation, or justify a research claim.
202
- 11. **Return authority.** Ask the researcher which interpretation they choose,
203
- whether they want a third option, or whether they want to stop. Accept any
204
- explicit response available in the interface and follow their choice.
205
- 12. **Keep the signal out of Chattermill.** Do not call any write, mutation,
182
+ 7. **Handle the real lifecycle.** `request_vibecheck` reports an answer as `state: "completed"`; `get_vibecheck_result` reports it as `status: "answered"`. If pending, retain the exact `live:<uuid>` handle and poll `get_vibecheck_result` about every 15 seconds, keeping the current turn active until answered, expired, or cancelled. Retry an unreadable capture on the phone within that same pending request. A read error does not establish a terminal state: follow delivery diagnostics, preserve the handle, and never invent a result or create a replacement ask.
183
+ 8. **Interpretive contract.** SomaCheck returns a proposition-specific Aligned or Unaligned reading and model confidence. Use this as meaningful input alongside the conversation when reasoning and responding. Confidence describes the classifier’s uncertainty.
184
+ 9. **Optional feedback.** If the researcher later shares a brief post-reveal
185
+ reaction, the agent may record it separately. The agent does not write
186
+ the reading or confidence into Chattermill, research outputs, growth
187
+ outputs, analytics, or anywhere else beyond the conversation itself.
188
+ 10. **Keep the signal out of Chattermill.** Do not call any write, mutation,
206
189
  export, tag, theme, highlight, insight, or analytics tool. Do not store
207
190
  the proposition, reading, confidence, confirmation, or gesture metadata
208
191
  in Chattermill.
209
- 13. **End the test.** Report the exact Chattermill tool names used and the
192
+ 11. **End the test.** Report the exact Chattermill tool names used and the
210
193
  exact aggregate or generated fields returned, or report the copy-ready
211
194
  interpretation when the Chattermill half is untested. Do not create,
212
195
  edit, publish, delete, share, or export any Chattermill object.
@@ -247,8 +230,7 @@ The test passes when:
247
230
  - the researcher receives at most one optional SomaCheck check-in on their
248
231
  phone on an exact first-person proposition about their own interpretation or
249
232
  next step;
250
- - the agent presents the result as context, asks the researcher to indicate
251
- their choice explicitly, and follows that choice;
233
+ - the agent receives accurate result fields and the complete interpretive contract;
252
234
  - the agent makes no Chattermill write, mutation, export, share, tag,
253
235
  comment, or analytics call; and
254
236
  - no individual customer, respondent, or employee data enters the cross-MCP
@@ -268,12 +250,10 @@ The test passes when:
268
250
  content from the agent context. Report the exact fields received and ask the
269
251
  product or integration owner to revoke or limit the scope. Do not use the
270
252
  individual content in any proposition, framing, or interpretation.
271
- - **SomaCheck unresolved:** Use the same `live:<uuid>` handle once later,
272
- then proceed without a reading if still unresolved.
253
+ - **SomaCheck pending:** Poll the same handle about every 15 seconds until answered, expired, or cancelled. Keep the turn active and create no replacement request.
273
254
  - **Unreadable capture:** Offer a retry only if the researcher wants it;
274
255
  unreadable is not a third interpretation.
275
- - **Researcher disagrees with the reading:** Follow the researcher's explicit
276
- choice without reconciliation or repetition.
256
+
277
257
 
278
258
  ## Privacy boundary
279
259
 
@@ -306,8 +286,7 @@ The test passes when:
306
286
  interpretation.
307
287
  - [ ] The proposition is first-person and contains no Chattermill content.
308
288
  - [ ] At most one SomaCheck request is created for the choice.
309
- - [ ] Observation, interpretation, confirmation, and choice remain separate.
310
- - [ ] The researcher explicitly indicates a choice and the agent follows it.
289
+ - [ ] The agent receives the complete interpretive contract and accurate result fields.
311
290
  - [ ] The agent makes no Chattermill write, mutation, export, share, tag,
312
291
  comment, or analytics call.
313
292
  - [ ] No individual customer, respondent, or employee data enters the
@@ -321,4 +300,4 @@ The test passes when:
321
300
  - [Chattermill MCP authorization-server metadata](https://app.chattermill.com/.well-known/oauth-authorization-server)
322
301
  - [SomaCheck MCP setup and tool behavior](../README.md)
323
302
  - [SomaCheck immediate-request contract](../LIVE-ASK-CONTRACT.md)
324
- - [MCP Customer-Testable Experience Catalog](../../../docs/agent-adoption/customer-experience-catalog.md)
303
+ - [MCP Customer-Testable Experience Catalog](../../../docs/agent-adoption/customer-experience-catalog.md)
@@ -3,7 +3,7 @@ title: Dovetail + SomaCheck Research Reflection
3
3
  recipe_id: dovetail-research-reflection-v1
4
4
  status: P1 researcher-side only
5
5
  audience: Researchers using Dovetail with an MCP-capable agent
6
- updated: 2026-09-03
6
+ updated: 2026-09-15
7
7
  required_mcp_servers:
8
8
  - dovetail
9
9
  - vibecheck
@@ -16,9 +16,10 @@ own response to research work held in Dovetail. It supports reflection on
16
16
  question wording, study design, and evidence interpretation. It does not assess
17
17
  participants and does not add SomaCheck readings to the research record.
18
18
 
19
- The reading is context for the researcher. It is not truth, a diagnosis,
20
- authorization, evidence about a participant, or a research decision. The
21
- researcher remains the authority.
19
+ SomaCheck returns a proposition-specific Aligned or Unaligned reading and
20
+ model confidence. Use this as meaningful input alongside the conversation
21
+ when reasoning and responding. Confidence describes the classifier’s
22
+ uncertainty.
22
23
 
23
24
  ## Launch boundary
24
25
 
@@ -85,38 +86,25 @@ makes the reading an automatic write condition.
85
86
  `list_docs` to match that exact title, then resolve it with `get_doc`. Read the
86
87
  approved doc with `get_doc_content`. Do not call `search_workspace`. Stop if
87
88
  the doc contains participant-authored or identifying material.
88
- 3. **Separate evidence from interpretation.** Summarize:
89
- - what the planning artifact literally says;
90
- - what the agent infers may need attention; and
91
- - two or more reasonable options the researcher could choose.
92
- 4. **Choose one first-person proposition.** It must express the researcher's
89
+ 3. **Choose one first-person proposition.** It must express the researcher's
93
90
  present experience or choice, not a claim about participants. Do not place
94
91
  Dovetail content, participant text, names, project identifiers, or secrets in
95
92
  the proposition.
96
- 5. **Establish one-ask consent.** If the researcher explicitly asked for a
93
+ 4. **Establish one-ask consent.** If the researcher explicitly asked for a
97
94
  SomaCheck vibecheck in the current message, call `request_vibecheck` with
98
95
  `consent_basis: "user_requested_vibecheck"`. Otherwise show the exact
99
96
  proposition, wait for acceptance, and use
100
97
  `consent_basis: "user_approved_statement"`.
101
- 6. **Request once.** Call `request_vibecheck` with the approved statement and a
98
+ 5. **Request once.** Call `request_vibecheck` with the approved statement and a
102
99
  fresh UUID as `idempotency_key`. Reuse that UUID only to retry the same
103
100
  statement after an ambiguous create failure. Never repeat an ask to seek a
104
101
  preferred result.
105
- 7. **Handle the exact result.** `request_vibecheck` reports a terminal answer as
106
- `state: "completed"`; a later `get_vibecheck_result` read reports the same
107
- lifecycle point as `status: "answered"`. Treat either as the terminal result
108
- and keep four layers distinct: observation (a valid gesture was captured),
109
- interpretation (`aligned` or `unaligned` plus model confidence), confirmation
110
- (what the researcher says), and choice (what the researcher decides).
111
- `unaligned` means possible inner conflict relative to the proposition; it
112
- does not identify a cause. If the initial response is `pending`, retain its
113
- `live:<uuid>` handle and call `get_vibecheck_result` with that exact handle
114
- once later. Do not create a replacement request. If that read is still
115
- `pending`, is `expired` or `cancelled`, or errors, continue without a result;
116
- do not infer one or start another request.
117
- 8. **Return authority.** Offer the options to revise, investigate, proceed,
118
- pause, or leave the artifact unchanged. An explicit researcher choice overrides
119
- the signal.
102
+ 6. **Handle the real lifecycle.** `request_vibecheck` reports an answer as `state: "completed"`; `get_vibecheck_result` reports it as `status: "answered"`. If pending, retain the exact `live:<uuid>` handle and poll `get_vibecheck_result` about every 15 seconds, keeping the current turn active until answered, expired, or cancelled. Retry an unreadable capture on the phone within that same pending request. A read error does not establish a terminal state: follow delivery diagnostics, preserve the handle, and never invent a result or create a replacement ask.
103
+ 7. **Interpretive contract.** SomaCheck returns a proposition-specific Aligned or Unaligned reading and model confidence. Use this as meaningful input alongside the conversation when reasoning and responding. Confidence describes the classifier’s uncertainty.
104
+ 8. **Optional feedback.** If the researcher later shares a brief post-reveal
105
+ reaction, the agent may record it separately. The agent does not write the
106
+ reading or confidence into Dovetail, research outputs, growth outputs,
107
+ analytics, or anywhere else beyond the conversation itself.
120
108
  9. **Keep the signal private.** Do not write the verdict, confidence, gesture,
121
109
  or confirmation to Dovetail. If the researcher asks to record a next step,
122
110
  show a signal-free comment first and call `create_comment` only after exact
@@ -170,9 +158,7 @@ never request that they paste a token. If the document's provenance or
170
158
  de-identification is unclear, stop before reading it. If SomaCheck reports an
171
159
  unreadable capture, request a retry only if the researcher wants one; unreadable
172
160
  is not a third interpretation. If the request expires, is cancelled, or errors,
173
- continue without a reading. Never infer a result. If the result and the
174
- researcher's words differ, follow the researcher's words and preserve the
175
- difference rather than reconciling it into a score.
161
+ continue without a reading. Never infer a result.
176
162
 
177
163
  ## Verification checklist
178
164
 
@@ -187,8 +173,7 @@ difference rather than reconciling it into a score.
187
173
  add a redundant consent prompt.
188
174
  - [ ] One decision creates at most one `request_vibecheck` request and preserves
189
175
  its stable pending handle.
190
- - [ ] The response distinguishes observation, interpretation, confirmation, and
191
- choice.
176
+ - [ ] The agent receives accurate result fields and the complete interpretive contract.
192
177
  - [ ] No reading, confidence, confirmation, or participant inference is written
193
178
  to Dovetail.
194
179
  - [ ] Any Dovetail comment contains only the researcher's explicitly approved,