@remits/remits-cli 0.1.156 → 0.1.157

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/index.js CHANGED
@@ -13,9 +13,9 @@
13
13
  - L5223 Component Summaries, Status, And Sync Gates
14
14
  - L7547 Branches, Promotion, Commit, And Test Runs
15
15
  - L9676 Tokens, Tools, Verification, And Config
16
- - L11103 Service Dashboard And WebSocket Listener
17
- - L13602 Agent And Ticket Workflows
18
- - L17778 Help, Auto Update, And Command Dispatch
16
+ - L11174 Service Dashboard And WebSocket Listener
17
+ - L13673 Agent And Ticket Workflows
18
+ - L17849 Help, Auto Update, And Command Dispatch
19
19
  */
20
20
 
21
21
  /*
@@ -112,7 +112,7 @@ let LOCAL_ACTOR_CONTEXT = null;
112
112
 
113
113
  // Flags that are legitimately repeatable accumulate into an array instead of last-wins. Every other
114
114
  // flag keeps last-wins so existing callers are unaffected.
115
- const REPEATABLE_FLAGS = new Set(['expected-removed', 'expectedRemoved', 'names', 'tag', 'tags', 'key', 'keys', 'case-key']);
115
+ const REPEATABLE_FLAGS = new Set(['expected-removed', 'expectedRemoved', 'names', 'tag', 'tags', 'key', 'keys', 'case-key', 'score', 'scores', 'lower-is-better']);
116
116
 
117
117
  // `-m "msg"` is how git spells a commit message, and agents type it from habit. Before this the parser took a
118
118
  // single-dash token as the VALUE of whatever flag preceded it: `--json -m "x"` silently turned JSON mode
@@ -10343,6 +10343,19 @@ function corpusAiLabel(row = {}) {
10343
10343
  return 'no AI';
10344
10344
  }
10345
10345
 
10346
+ function corpusScoreSummary(scores = {}) {
10347
+ return Object.keys(scores).map((k) => k + ' ' + (scores[k] && scores[k].mean !== undefined ? scores[k].mean : scores[k]) +
10348
+ (scores[k] && scores[k].cases !== undefined ? '/' + scores[k].cases : '')).join(', ');
10349
+ }
10350
+
10351
+ function corpusScoreDeltas(scores = {}) {
10352
+ return Object.keys(scores).map((k) => {
10353
+ const s = scores[k] || {};
10354
+ const delta = s.delta !== undefined && s.delta !== null ? ' (' + (Number(s.delta) >= 0 ? '+' : '') + s.delta + ')' : '';
10355
+ return k + ' ' + (s.base !== undefined ? s.base : '?') + ' -> ' + (s.head !== undefined ? s.head : '?') + delta + (s.regressed ? ' REGRESSED' : '');
10356
+ }).join(', ');
10357
+ }
10358
+
10346
10359
  function corpusLines(sub, data = {}) {
10347
10360
  const lines = ['Corpus: ' + (data.corpus || '?') + ' (owner account ' + (data.ownerAccountId || '?') + ')'];
10348
10361
  if (sub === 'cases') {
@@ -10358,6 +10371,12 @@ function corpusLines(sub, data = {}) {
10358
10371
  (data.runs || []).forEach((r) => lines.push('- ' + r.taskId + ' ' + r.cases + ' case(s) ' + Object.keys(r.outcomes || {}).map((k) => k + ' ' + r.outcomes[k]).join(', ') +
10359
10372
  ' ' + corpusAiLabel(r) + ' ' + (r.dataMode || '?') + ' ' + (r.branchName || '?') + (r.workspace ? ' [ws:' + r.workspace + ']' : '') +
10360
10373
  (r.laneContentHash ? ' lane ' + String(r.laneContentHash).slice(0, 12) : '') + ' ' + formatTime(r.recordedAt)));
10374
+ (data.runs || []).forEach((r) => {
10375
+ const extra = [];
10376
+ if (r.scores && Object.keys(r.scores).length) extra.push('scores ' + corpusScoreSummary(r.scores));
10377
+ if (r.review) extra.push('graded ' + r.review.graded + ', spot-checked ' + r.review.reviewed + (r.review.agreement !== undefined ? ' (agreement ' + r.review.agreement + ')' : ''));
10378
+ if (extra.length) lines.push(' ' + r.taskId + ': ' + extra.join(' '));
10379
+ });
10361
10380
  if ((data.runs || []).some((r) => r.aiMode === 'mocked')) {
10362
10381
  lines.push('Runs marked AI MOCKED replayed every AI turn from aiMock - they measure the harness, not the model.');
10363
10382
  }
@@ -10366,10 +10385,12 @@ function corpusLines(sub, data = {}) {
10366
10385
  lines.push('Base ' + cmp.baseTaskId + ' -> head ' + cmp.headTaskId + ': ' + Object.keys(cmp.counts || {}).map((k) => k + ' ' + cmp.counts[k]).join(', '));
10367
10386
  lines.push('AI mode: base ' + (cmp.baseAiMode || '?') + ', head ' + (cmp.headAiMode || '?'));
10368
10387
  if (cmp.aiModeChanged) lines.push('WARNING: ' + cmp.aiModeChanged);
10369
- (cmp.cases || []).filter((c) => c.direction !== 'same').forEach((c) => {
10388
+ if (cmp.scores && Object.keys(cmp.scores).length) lines.push('Scores (cases both runs measured): ' + corpusScoreDeltas(cmp.scores));
10389
+ (cmp.cases || []).filter((c) => c.direction !== 'same' || c.changedScores).forEach((c) => {
10370
10390
  lines.push('- ' + c.caseKey + ' ' + c.direction + (c.base ? ' ' + c.base.outcome : '') + (c.head ? ' -> ' + c.head.outcome : '') +
10371
10391
  (c.expectedChanged ? ' (EXPECTED ANSWER CHANGED between runs)' : ''));
10372
10392
  (c.changedMetrics || []).slice(0, 8).forEach((m) => lines.push(' ' + m.metric + ': ' + JSON.stringify(m.base) + ' -> ' + JSON.stringify(m.head)));
10393
+ Object.keys(c.changedScores || {}).slice(0, 8).forEach((k) => lines.push(' score ' + k + ': ' + c.changedScores[k].base + ' -> ' + c.changedScores[k].head));
10373
10394
  if (c.head && c.head.outcomeReason && c.head.outcome !== 'passed') lines.push(' reason: ' + String(c.head.outcomeReason).slice(0, 300));
10374
10395
  });
10375
10396
  } else if (sub === 'retire') {
@@ -10380,6 +10401,28 @@ function corpusLines(sub, data = {}) {
10380
10401
  if ((data.cases || []).some((c) => c.status === 'retired' || c.status === 'restored')) {
10381
10402
  lines.push('Retired cases stay readable in results and compare; they are just no longer handed to runs. `corpus cases --include-retired` lists them, `corpus retire --restore` puts them back.');
10382
10403
  }
10404
+ } else if (sub === 'gate') {
10405
+ const gate = data.gate || {};
10406
+ lines.push('Gate ' + (gate.baseTaskId || '?') + ' -> ' + (gate.headTaskId || '?') + ': ' + (gate.passed ? 'PASSED' : 'DID NOT PASS') +
10407
+ (gate.tolerance !== undefined ? ' (tolerance ' + gate.tolerance + ')' : ''));
10408
+ if (gate.scores && Object.keys(gate.scores).length) lines.push('Scores: ' + corpusScoreDeltas(gate.scores));
10409
+ (gate.reasons || []).forEach((r) => lines.push('- ' + r));
10410
+ if (gate.expectedChanged && gate.expectedChanged.length) lines.push('Left out (expected answer changed between runs): ' + gate.expectedChanged.join(', '));
10411
+ (gate.changedCases || []).slice(0, 15).forEach((c) => lines.push(' ' + c.caseKey + ' ' + c.direction +
10412
+ Object.keys(c.changedScores || {}).map((k) => ' ' + k + ' ' + c.changedScores[k].base + '->' + c.changedScores[k].head).join('')));
10413
+ } else if (sub === 'review') {
10414
+ if (data.review) {
10415
+ lines.push('Recorded: ' + data.review.caseKey + ' in run ' + data.review.taskId + ': ' + data.review.verdict + (data.review.score !== undefined && data.review.score !== null ? ' (score ' + data.review.score + ')' : ''));
10416
+ } else {
10417
+ const sample = data.sample || [];
10418
+ lines.push('Spot-check sample: ' + sample.length + ' graded case(s), unreviewed first');
10419
+ sample.forEach((c) => {
10420
+ const g = c.grade || {};
10421
+ lines.push('- ' + c.caseKey + ' grade ' + (g.verdict || '?') + ' ' + (g.score !== undefined ? g.score : '') + (c.review ? ' reviewed: ' + c.review.verdict : ' NOT REVIEWED'));
10422
+ if (g.reasons) lines.push(' ' + String(g.reasons).slice(0, 300));
10423
+ });
10424
+ if (sample.length) lines.push('Record a check: remits-cli corpus review --corpus ' + (data.corpus || '<name>') + ' --task-id <taskId> --case <caseKey> --verdict agree|disagree [--score n] [--note "..."]');
10425
+ }
10383
10426
  } else if (sub === 'consistency') {
10384
10427
  const con = data.consistency || {};
10385
10428
  lines.push('Case ' + con.caseKey + ': ' + con.runs + ' run(s), ' + (con.stable ? 'STABLE (identical metrics every run)' : (con.variants || []).length + ' distinct metric results') +
@@ -10390,7 +10433,7 @@ function corpusLines(sub, data = {}) {
10390
10433
  return lines;
10391
10434
  }
10392
10435
 
10393
- // `corpus import|cases|results|runs|compare|consistency`: test corpora through the platform's own corpus(name).
10436
+ // `corpus import|cases|results|runs|compare|consistency|retire|gate|review`: test corpora through the platform's own corpus(name).
10394
10437
  async function corpusCommand(flags, subcommand) {
10395
10438
  const cwd = process.cwd();
10396
10439
  ensureLocalState(cwd);
@@ -10510,8 +10553,36 @@ async function corpusCommand(flags, subcommand) {
10510
10553
  if (!retireKeys.length) throw new Error('corpus retire requires --case <caseKey> (repeatable) or --keys a,b,c');
10511
10554
  data = await post({ command: 'retire', corpus: corpusName, keys: retireKeys, restore: flagEnabled(flags.restore) });
10512
10555
  if (data && data.success === false) process.exitCode = 1;
10556
+ } else if (sub === 'gate') {
10557
+ const scores = parseCorpusFilterValues(flags, ['score', 'scores']);
10558
+ const lowerIsBetter = parseCorpusFilterValues(flags, ['lower-is-better']);
10559
+ data = await post({ command: 'gate', corpus: corpusName, base: flags.base, head: flags.head, tolerance: flags.tolerance,
10560
+ scores: scores.length ? scores : undefined,
10561
+ lowerIsBetter: lowerIsBetter.length ? lowerIsBetter : undefined,
10562
+ allowModeChange: flagEnabled(flags['allow-mode-change']) });
10563
+ const gate = (data && data.gate) || {};
10564
+ if (!gate.passed) process.exitCode = 1;
10565
+ await appendVerificationPacket(api, cwd, session, accountId, flags, {
10566
+ type: 'corpus_gate',
10567
+ success: !!gate.passed,
10568
+ claim: 'Corpus ' + corpusName + ' gate ' + (gate.baseTaskId || '?') + ' -> ' + (gate.headTaskId || '?') + ': ' + (gate.passed ? 'passed' : 'did not pass'),
10569
+ world: Object.assign({}, (data && data.world) || {}, { host: normalizeBaseUrl(baseUrl), branchName, workspace, dataMode }),
10570
+ revision: collectVerificationSource(cwd, flags),
10571
+ evidenceCategories: ['corpus_gate'],
10572
+ corpusGate: { corpus: corpusName, baseTaskId: gate.baseTaskId, headTaskId: gate.headTaskId, passed: !!gate.passed, scores: gate.scores, reasons: gate.reasons }
10573
+ }, { quiet: jsonOutput });
10574
+ } else if (sub === 'review') {
10575
+ const taskId = flags['task-id'];
10576
+ if (!taskId) throw new Error('corpus review requires --task-id <taskId> (and --case <caseKey> --verdict agree|disagree to record a check)');
10577
+ if (flags.verdict) {
10578
+ const caseKey = flags.case || flags['case-key'];
10579
+ if (!caseKey) throw new Error('corpus review --verdict requires --case <caseKey>');
10580
+ data = await post({ command: 'review', corpus: corpusName, taskId, caseKey, verdict: flags.verdict, score: flags.score, note: flags.note });
10581
+ } else {
10582
+ data = await post({ command: 'review', corpus: corpusName, taskId, size: flags.sample || flags.size });
10583
+ }
10513
10584
  } else {
10514
- throw new Error('Unknown corpus subcommand: ' + sub + ' (use import, cases, results, runs, compare, consistency or retire)');
10585
+ throw new Error('Unknown corpus subcommand: ' + sub + ' (use import, cases, results, runs, compare, consistency, retire, gate or review)');
10515
10586
  }
10516
10587
  if (jsonOutput) {
10517
10588
  console.log(JSON.stringify(data, null, 2));
@@ -18144,6 +18215,8 @@ function printCorpusHelp() {
18144
18215
  console.log(' remits-cli corpus compare --corpus <name> --base <taskId> --head <taskId> [--json]');
18145
18216
  console.log(' remits-cli corpus consistency --corpus <name> --case <caseKey> [--limit 20] [--json]');
18146
18217
  console.log(' remits-cli corpus retire --corpus <name> --case <caseKey> [--case ...] [--restore] [--json]');
18218
+ console.log(' remits-cli corpus gate --corpus <name> [--base <taskId>] [--head <taskId>] [--tolerance 0.02] [--score answer] [--lower-is-better <name>] [--allow-mode-change] [--json]');
18219
+ console.log(' remits-cli corpus review --corpus <name> --task-id <taskId> [--sample 5] | --case <caseKey> --verdict agree|disagree [--score n] [--note "..."]');
18147
18220
  console.log('');
18148
18221
  console.log('A corpus is a named, durable set of cases an evaluation-style Test runs again and again (front stage:');
18149
18222
  console.log('corpus(name)). Cases live in a platform table owned by one account and visible to the accounts beneath');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remits/remits-cli",
3
- "version": "0.1.156",
3
+ "version": "0.1.157",
4
4
  "description": "Local CLI for auth, component sync, and live test execution against Remits",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -46,6 +46,7 @@ reference below, load it before you act, not after something surprises you.
46
46
  | work from a non-trunk checkout, or promote a branch | `branch-variants.md` | what a variant is, which world your checkout resolves, `--as-account` vs `--variant-branch`, tombstones, the promotion loop and its phases, subscribe/retire |
47
47
  | touch a support ticket | `support-tickets.md` | the ticket record and its verbs, routed vs claimed vs owned, autonomous workers, the repository edit lease, ask/answer/resume, delivery |
48
48
  | register this session as an agent, or ask why a routed ticket never started | `agent-sessions.md` | `serve` vs `register`, what registration does, worker spawning per agent kind, routing order, the control center, multi-session auth |
49
+ | build or evaluate cited Q&A, review learnings, or investigate answer quality | `ai-quality.md` | front-stage guide routing, interaction/session pivots, baseline vs proposal measurements, human checks and explicit corpus gates |
49
50
  | investigate live behavior | `investigation.md` | which tool reads which record, correlation keys, reading a record's `content`, HTTP audits, AI activity, node/`localMode`, the production support flows |
50
51
  | call any `mcp_*` tool | `tool-reference.md` | every tool's parameters, semantics, and traps — read the tool's entry before building its input |
51
52
  | run, describe, poll, or interrupt an Action from the CLI | `tool-reference.md` → `mcp_run_action` | Action execution through `remits-cli tool --name mcp_run_action`; there is no separate top-level `remits-cli action` / `run action` wrapper |
@@ -158,12 +159,11 @@ reference named after it.
158
159
  (`component-resolution.md`, `command-reference.md`)
159
160
  - **Host and data mode are two independent decisions.** `--base-url` picks the Remits host,
160
161
  `--data-mode` picks the data segment on it. Neither implies the other. (`command-reference.md`)
161
- - **Localhost being down is not a verification blocker.** `remits-cli` can talk to any reachable Remits
162
- host, including the deployed production host, by passing `--base-url`. If `http://localhost:8080`
163
- refuses a connection, retry the same stage/test/tool/status command against the intended deployed host
164
- with the same account, branch, workspace and data-mode facts; do not stop unless no reachable host or
165
- valid session exists. A deployed host can still run test-lane verification: `--base-url` selects the
166
- server, while `test run` still defaults to `--data-mode test`. (`command-reference.md`)
162
+ - **Use a reachable host that has the implementation you are verifying.** For component work against
163
+ existing capabilities, localhost being down need not block verification: pass the intended deployed
164
+ host with `--base-url`, preserving account, branch, workspace and data-mode facts. For unreleased
165
+ platform capabilities, a deployed old binary is not a substitute; use the updated local/test runtime
166
+ and follow that repository's startup/restart instructions. (`command-reference.md`, `ai-quality.md`)
167
167
  - **`test run` ignores the stored session data lane.** It defaults to `test` even when `whoami` shows
168
168
  the session parked on prod; production Test runs require explicit prod provenance (`--data-mode prod`)
169
169
  or Test source declaring `dataMode 'prod'` / `[dataMode:'prod']`. Check returned `dataModeSource`
@@ -0,0 +1,118 @@
1
+ # Answer Quality And Reviewed Learning
2
+
3
+ Load this when building cited Q&A, investigating a wrong answer, changing retrieval or prompts,
4
+ or measuring a proposed learning before review. Component APIs belong in the account's front-stage
5
+ guides; this reference owns the CLI investigation and measurement workflow.
6
+
7
+ ## Table of Contents
8
+
9
+ - [Find the right guide](#find-the-right-guide)
10
+ - [Investigate one answer](#investigate-one-answer)
11
+ - [Measure a change](#measure-a-change)
12
+ - [Review and gate](#review-and-gate)
13
+ - [Keep the regression](#keep-the-regression)
14
+
15
+ ## Find the right guide
16
+
17
+ | Building or changing | Read in the account repository |
18
+ |---|---|
19
+ | Search Tool → citations → feedback/review page | `guides/features/ai-interactions.md` → *Wiring a question-to-review workflow* |
20
+ | Learning proposals, review, retirement, Agent memory | `guides/features/agent-memory.md` → *Reviewing learnings* |
21
+ | Rewrite, learned wording, rerank, answer spans, retrieval misses | `guides/features/schema-vertex-ai-extension.md` → *Retrieval quality* |
22
+ | Named retrieval/answer scores, rubric grader, baseline/candidate design | `guides/test-components.md` → *Measuring a proposed learning* and *Human Review And Release Gates* |
23
+ | Per-question guidance and timeout recovery | `guides/agent-components.md` → *ai.turnContext* and *Wiring tools to an agent* |
24
+
25
+ Use the built-in interaction and learning stores. `interactions()` and `memories()` are component DSL
26
+ builders, not top-level CLI commands. Call them from an application's Action/review UI or a Test;
27
+ do not bypass their scope and review behavior with SQL. The CLI has `corpus review` and `corpus gate`.
28
+
29
+ ## Investigate one answer
30
+
31
+ Start from the answer's `interactionId`, its sources and feedback. `interactions().trace(id)` gives
32
+ its provider rounds and tool calls; `sessionId` and `traceId` pivot into `mcp_ai_session_search`
33
+ (`investigation.md`, then that tool's entry in `tool-reference.md`). Inspect what the model actually
34
+ received before changing a prompt. For normalization, `unstructured()` also returns `aiSessionId`
35
+ when a model was called, plus `traceId`.
36
+
37
+ Decide what failed before choosing a remedy:
38
+
39
+ - Expected source missing: inspect the actual retrieval and `index_explain` diagnostics in the
40
+ authorized data lane. Test documents are not indexed. Diagnose eligibility/indexing before tuning
41
+ wording; `not_reached` describes a bounded search window, not proof that a document is irrelevant.
42
+ - Source retrieved but answer wrong: inspect the offered passages, per-turn instructions and answer
43
+ trace. A citation is not proof that the source supports the claim.
44
+ - Coverage absent: include a should-decline corpus case; a grounded decline can be the correct result.
45
+ - Terminology mismatch: propose `kind: 'phrasing'` with indexed wording in `details` and a source
46
+ document id in `subjectRef`, then measure it. Feedback alone does not alter retrieval.
47
+
48
+ `quality.rerank.applied: false` is fallback, not successful reranking. `indexSearchMock` bypasses
49
+ rewriting, learned retrieval and ranking; it proves how the caller uses supplied search results.
50
+ Use live-provider evidence for claims about those steps. Ranking charges are outside chat-token budgets.
51
+
52
+ ## Measure a change
53
+
54
+ Use the existing account Test/corpus workflow. Keep durable regression components in the account
55
+ repository, and stage the workset in a dedicated workspace. Establish host, execution account, source
56
+ layer and data lane before running (`components status`). Compare the same cases, expected answers,
57
+ source inputs and model settings. Retrieve and score through the actual application path; a parallel
58
+ search written just for the evaluator may measure something the answering Agent never saw.
59
+
60
+ 1. Capture a baseline with approved memories only. Keep each question in a fresh Agent session.
61
+ 2. Make the source/prompt change, or enable `includeProposed` in the Test's Agent/retrieval path.
62
+ Preview includes all relevant proposals in scope, not one selected id. Keep the proposal set
63
+ stable. Learning records are shared across data lanes; a workspace does not isolate them.
64
+ 3. Re-stage and run the candidate under an explicit `withAiBudget` in the Test. Retain both task ids,
65
+ per-case `scores: [retrieval: ..., answer: ...]`, grader results and retrieval diagnostics.
66
+
67
+ ```bash
68
+ remits-cli components stage --workset --account-id 1 --base-url http://localhost:8080 --data-mode test
69
+ remits-cli test run --test "Support Answer Evaluation" --account-id 1 --base-url http://localhost:8080 --data-mode test --json
70
+ ```
71
+
72
+ The suite name and account above are examples: create/use your application's suite and resolved account.
73
+ Repeat after the candidate edit and stage. `--test` selects a suite; `--names` selects exact case names
74
+ (or a `|`-separated list), not substrings or a regex. Check `total` and outcomes; a zero-case run proves
75
+ nothing. `--wait false` only launches; finish with `test status --task-id <id>`.
76
+
77
+ Use deterministic search/AI mocks for protocol regressions and live models against pinned corpus
78
+ artifacts for answer quality. Real index measurements need an explicitly authorized production data
79
+ lane and a read-only application path; changing the lane of a workflow with writes is not a shortcut.
80
+ The intended host must also have the implementation being verified: a deployed old binary cannot
81
+ verify unreleased platform changes, even with the same account and test lane.
82
+
83
+ ## Review and gate
84
+
85
+ With the same explicit host/account/data-mode options as the runs:
86
+
87
+ ```bash
88
+ remits-cli corpus compare --corpus support-golden --base BASE_TASK_ID --head CANDIDATE_TASK_ID
89
+ remits-cli corpus review --corpus support-golden --task-id CANDIDATE_TASK_ID --sample 5
90
+ remits-cli corpus review --corpus support-golden --task-id CANDIDATE_TASK_ID --case CASE_KEY --verdict agree --note "Checked against the source"
91
+ remits-cli corpus gate --corpus support-golden --base BASE_TASK_ID --head CANDIDATE_TASK_ID --score retrieval --score answer
92
+ remits-cli evidence
93
+ ```
94
+
95
+ Replace uppercase ids with the returned task/case ids. Each repeated `--score` is a required metric;
96
+ `--scores retrieval,answer` is equivalent. Pin baseline and candidate instead of relying on latest runs.
97
+ The gate exits nonzero for regressions, lost/absent measurements or an unaccepted mocked/live mode
98
+ change. It records `corpus_gate` evidence. Inspect measured case counts and excluded changed expected
99
+ answers; also assess the application's minimum accuracy and the human spot checks. A non-regression
100
+ gate alone does not establish an absolute quality target or statistical significance.
101
+
102
+ Stop a release workflow on a failing gate. Approval and component sync do not automatically invoke it.
103
+ Human review preserves the model grade; it does not replace it. Approve/reject through the application's
104
+ `memories()` review path after the decision, and keep the relevant task ids in the review note. Editing
105
+ an approved memory affects later answers immediately; use a proposal when a replacement needs measurement.
106
+
107
+ ## Keep the regression
108
+
109
+ Use `auxiliary: false` for durable Tests and their helper components. Set `regression: true` for suites
110
+ the account expects in its normal regression, including explicitly budgeted provider suites when
111
+ their cost/dependency is intentional. Otherwise leave costly/exploratory measurements opt-in.
112
+ Describe the proof boundary in the sidecar and README: mocked protocol, live provider smoke,
113
+ measured corpus quality, or UI journey. Retire/reject synthetic memories in cleanup.
114
+
115
+ Read `component-integrity.md` before landing. Source files alone do not update the platform's durable
116
+ regression selection: use normal account commit/push/sync when authorized, or stage for local runs.
117
+ Read `evidence` before reporting results; distinguish checked-in/staged source, measured behavior,
118
+ human approval and deployment.
@@ -65,8 +65,10 @@ Do not assume `--data-mode prod` implies the deployed prod host, or that `--data
65
65
  localhost. Likewise, do not assume localhost is required because the current checkout is a back-stage
66
66
  repo or because a previous command used localhost. If host matters, read `~/.remits-cli/sessions.json`,
67
67
  `remits-cli whoami`, or the recent `remits-cli evidence` world blocks, then pass `--base-url` explicitly.
68
- Only call verification blocked after trying the intended reachable host and finding that no authenticated
69
- session or usable network path exists.
68
+ For existing capabilities, try the intended reachable host before declaring a connection blocker.
69
+ For unreleased platform capabilities, also verify that the host has the required implementation.
70
+ Staged component source cannot add missing platform APIs to an older deployed binary; use the updated
71
+ local/test runtime and follow that platform repository's startup/restart instructions.
70
72
 
71
73
  ### Tool Execution Lifecycle
72
74
 
@@ -266,6 +268,9 @@ remits-cli corpus cases --corpus <name> [--split S] [--tag T|--tags T,U] [--key
266
268
  remits-cli corpus runs --corpus <name> [--limit 20] [--json] # runs that measured the corpus: outcomes, AI mode (live|MOCKED|none), cost, world
267
269
  remits-cli corpus results --corpus <name> [--case <caseKey>] [--task-id <taskId>] [--json]
268
270
  remits-cli corpus compare --corpus <name> --base <taskId> --head <taskId> [--json] # per-case direction, metric deltas, expectedChanged
271
+ remits-cli corpus gate --corpus <name> --base <taskId> --head <taskId> [--score retrieval --score answer] [--lower-is-better <name>] [--tolerance 0.02] [--allow-mode-change] [--json]
272
+ remits-cli corpus review --corpus <name> --task-id <taskId> [--sample 5] [--json]
273
+ remits-cli corpus review --corpus <name> --task-id <taskId> --case <key> --verdict agree|disagree [--score n] [--note "..."]
269
274
  remits-cli corpus consistency --corpus <name> --case <caseKey> [--json] # did the case produce the same metrics every run?
270
275
  remits-cli corpus retire --corpus <name> --case <caseKey> [--case ...] [--restore] [--json] # drop a case from future runs; past measurements stay readable
271
276
  ```
@@ -318,6 +323,16 @@ of the same **shape** - same data lane, branch, workspace and case count - and s
318
323
  skipped; it refuses rather than comparing across worlds. `test status --data-mode prod` on a run recorded in
319
324
  the test lane returns the run's real lane and now says that is what happened.
320
325
 
326
+ For the baseline → proposed learning → human review workflow, see `ai-quality.md` and
327
+ `guides/test-components.md` → *Measuring a proposed learning*. `--score` is repeatable;
328
+ `--score retrieval --score answer` requires both metrics (`--scores retrieval,answer` is equivalent).
329
+ `interactions()` and `memories()` are component DSL APIs, not top-level CLI subcommands.
330
+
331
+ `corpus gate` exits nonzero for a score regression, missing measurements, or an unaccepted AI-mode
332
+ change, and records `corpus_gate` evidence. Run it explicitly before sync or approving a learning;
333
+ neither operation invokes it automatically. Prefer explicit run ids and check their world.
334
+ `corpus review` samples graded cases or records a human spot check; it preserves the model grade.
335
+
321
336
  A run labelled **`AI MOCKED`** replayed every AI turn from `aiMock`: it produces the same outcomes, scores and
322
337
  metrics a measured run would, so read that label before treating green as evidence. `compare` warns when base and
323
338
  head used different AI modes; `consistency` warns before calling a case unstable when its runs mixed modes.
@@ -68,6 +68,10 @@ The right model is:
68
68
 
69
69
  Never treat "it looks right in prod data inspection" as sufficient proof that a code change is verified.
70
70
 
71
+ For cited answers, retrieval changes or reviewed learnings, load `ai-quality.md`. It connects the
72
+ component workflow to measured baseline/candidate runs, human checks and `corpus gate`; a passing
73
+ mocked Test alone cannot establish improved answer quality.
74
+
71
75
  ## Verification Envelopes
72
76
 
73
77
  For a concrete user workflow, browser-facing change, branch-variant release, support ticket, or any loop
@@ -57,6 +57,8 @@ different lanes (see `platform-overview.md` → *Test and Production Data Lanes*
57
57
  - **`sessionKey`** — salted user-activity session key. Pivot `mcp_user_activity` `sessions` → `story`;
58
58
  each beat then carries a `traceId` / `threadGroupingId` for trace and log investigation.
59
59
  - **`sessionId`** — pivots into persisted AI activity via `mcp_ai_session_search`.
60
+ - **`interactionId`** — one Agent answer, including feedback and citations. In component code,
61
+ `interactions().get(id)` / `.trace(id)` open it; use its `sessionId` / `traceId` for deeper forensics.
60
62
 
61
63
  ### Reading a record's `content` — persisted context, not a memory dump
62
64
 
@@ -109,7 +111,13 @@ embeddable patterns: `features/http-audits.md` (`mcp_get_guide`).
109
111
 
110
112
  ### AI activity
111
113
 
112
- Every `ai()` call and Agent turn is persisted. Two ways in:
114
+ For a rated or corrected Agent answer, start with `interactions().trace(interactionId)` from an
115
+ Action or Test using the component DSL. `correlation: request_ids` identifies its recorded rounds;
116
+ legacy `time_window` matches may overlap adjacent answers. Empty rounds can mean logging was off or
117
+ telemetry expired. There is no top-level CLI `interactions` command: use your review Action, or pivot
118
+ its session/trace into the tools below. `ai-quality.md` routes the full correction/evaluation workflow.
119
+
120
+ AI request logging provides two deeper ways in:
113
121
 
114
122
  - **`mcp_ai_session_search`** — search groupings and open their detail. A grouping spans every session
115
123
  sharing one grouping id: the agent turns plus its guardrail and internal `ai()` calls. Use the