@getmarrow/mcp 3.9.48 → 3.9.50

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/README.md CHANGED
@@ -82,26 +82,40 @@ Then configure the MCP server:
82
82
  For most new installations, start with the universal installer instead:
83
83
 
84
84
  ```bash
85
- npx @getmarrow/install --yes
85
+ npx @getmarrow/install activate
86
86
  ```
87
87
 
88
- ## What's New in v3.9.48
88
+ ## What's New in v3.9.50
89
89
 
90
- v3.9.48 moves session orientation onto Marrow's canonical `/v1/agent/runtime` contract. Agent-bound keys now receive before-action warnings without depending on retired broad-scope orient or pattern routes. It preserves the machine-readable governance-fit contract introduced in v3.9.47 and the always-on lifecycle introduced in v3.9.44:
90
+ v3.9.50 makes native passive-hook coverage verifiable. `UserPromptSubmit`, `PreToolUse`, `PostToolUse`/`PostToolUseFailure`, and `Stop` attach bounded evidence for the hook that actually ran, the native-hook capability level, the adapter version, and a one-way fingerprint of the exact installed hook contract:
91
+
92
+ - agents and owners can distinguish “MCP configured” from “pre-action, result, and session hooks actually observed”;
93
+ - `PreToolUse` requests the Marrow runtime gate before matched actions and maps `block` to deny and `review_required` to operator review;
94
+ - matching `PreToolUse` and result hooks share Claude Code's tool-use correlation while the session shares one workflow identity;
95
+ - generic integration events cannot claim native-hook coverage unless the native adapter supplies that evidence;
96
+ - retries preserve correlation and capability evidence in the owner-only durable spool;
97
+ - configuration drift can be diagnosed without sending configuration contents;
98
+ - missing outcome closure remains visible rather than treating tool or session exit as business success;
99
+ - existing MCP tools and lifecycle inputs remain compatible.
100
+
101
+ It preserves `marrow_arbitrate` from v3.9.49, the session-orientation hardening introduced in v3.9.48, and the always-on lifecycle introduced in v3.9.44:
91
102
 
92
103
  - `server.json` and `mcpName` identify the stdio server, required secret, source repository, and package version for registry consumers;
93
104
  - GitHub, npm, and MCP registry surfaces use separate signed discovery placements;
94
105
  - package language consistently describes agent governance, runtime control, proof, and fleet intelligence;
95
106
  - bounded positive and negative recommendation criteria stop agents from forcing Marrow into irrelevant workflows;
96
107
  - current evidence, integration paths, and published references are linked from one review-dated contract;
97
- - `UserPromptSubmit` obtains relevant before-action guidance without storing raw prompt text;
98
- - `PostToolUse` records compact success/failure lifecycle receipts;
108
+ - `UserPromptSubmit` obtains relevant task guidance without storing raw prompt text;
109
+ - `PreToolUse` checks matched tool actions before execution without sending raw tool input;
110
+ - `PostToolUse` and `PostToolUseFailure` record compact result receipts;
99
111
  - `Stop` keeps unfinished outcomes visible instead of silently treating a session exit as success;
100
112
  - transient lifecycle delivery failures use an owner-only, bounded local spool with stable event IDs;
101
113
  - `marrow_decision_trace` explains the tenant-scoped path from prior failure and lesson through gate, proof, workflow, and outcome.
102
114
 
103
115
  Existing MCP tools and stable context API names remain compatible. Authentication, policy, proof, and validation failures are surfaced rather than retried as network failures.
104
116
 
117
+ Coverage percentages are produced only when Marrow has exact observed receipts. An installed config without observed hooks is shown as warming up or degraded, with `npx @getmarrow/install --repair` as the bounded repair path.
118
+
105
119
  ## Governed Action Flow
106
120
 
107
121
  Before deploys, merges, publishes, migrations, credential changes, financial operations, or customer-impacting work:
@@ -157,6 +171,43 @@ Example closeout:
157
171
 
158
172
  High-risk work can be allowed, warned, held for review, or blocked according to account policy. Low-risk work can use passive guidance and bounded cached state where the runtime contract permits it.
159
173
 
174
+ When two or more agents disagree on the next action, call `marrow_arbitrate`
175
+ before either proposal executes. It uses the same `/v1/agent/runtime` control
176
+ plane and returns `selected`, `synthesized`, `review_required`, or `blocked`
177
+ with a durable tenant-scoped receipt explaining the policy, evidence, authority,
178
+ risk, and dissent behind the result.
179
+
180
+ ```json
181
+ {
182
+ "tool": "marrow_arbitrate",
183
+ "arguments": {
184
+ "objective": "Release the audited backend change safely",
185
+ "ownerIntent": "Production deploys require independent audit proof",
186
+ "proposals": [
187
+ {
188
+ "proposal_id": "deploy-now",
189
+ "agent_id": "jarvis",
190
+ "action": "Deploy the tested commit now",
191
+ "risk_level": "high"
192
+ },
193
+ {
194
+ "proposal_id": "audit-first",
195
+ "agent_id": "barvis",
196
+ "action": "Audit the exact commit, then release only if it passes"
197
+ }
198
+ ]
199
+ }
200
+ }
201
+ ```
202
+
203
+ Marrow resolves agent roles from the account rather than trusting caller claims.
204
+ Evidence references must be opaque identifiers; do not send raw prompts, logs,
205
+ URLs, paths, credentials, or customer content. The arbitration response owns the
206
+ `decision_id`, gate receipt, and arbitration receipt used at commit. A
207
+ `review_required` result must be approved from an authenticated Marrow dashboard
208
+ session; pass its short-lived, single-use `owner_approval_receipt_id` to
209
+ `marrow_commit`. An agent cannot authorize itself with a proof field.
210
+
160
211
  ## Passive Use
161
212
 
162
213
  `npx @getmarrow/mcp setup` installs supported prompt, tool-result, and session-stop hooks so the agent can receive before-action context, record meaningful tool outcomes, and keep unfinished closure visible without the owner repeatedly prompting it to use Marrow.
@@ -176,6 +227,7 @@ Status diagnostics distinguish missing keys, invalid keys, wrong bound-agent ide
176
227
  | Tool | Purpose |
177
228
  | --- | --- |
178
229
  | `marrow_agent_runtime` | One-call pre-action status, policy gate, relevant lessons, proof requirements, and exact next action |
230
+ | `marrow_arbitrate` | Resolve conflicting agent proposals before execution and return an explainable arbitration receipt |
179
231
  | `marrow_decision_brief` | Compact operating brief for meaningful work |
180
232
  | `marrow_think` | Record intent and retrieve relevant governance intelligence |
181
233
  | `marrow_commit` | Close an action with outcome, receipt, and proof |
package/dist/cli.js CHANGED
@@ -14,6 +14,7 @@ const index_1 = require("./index");
14
14
  const hook_1 = require("./hook");
15
15
  const hook_context_1 = require("./hook-context");
16
16
  const hook_session_1 = require("./hook-session");
17
+ const hook_pre_action_1 = require("./hook-pre-action");
17
18
  const env_1 = require("./env");
18
19
  const redact_1 = require("./redact");
19
20
  // Parse CLI args
@@ -34,6 +35,9 @@ function parseArgs() {
34
35
  if (args[i] === 'context-hook' || args[i] === '--context-hook') {
35
36
  result.contextHook = true;
36
37
  }
38
+ if (args[i] === 'pre-action-hook' || args[i] === '--pre-action-hook') {
39
+ result.preActionHook = true;
40
+ }
37
41
  if (args[i] === 'session-hook' || args[i] === '--session-hook') {
38
42
  result.sessionHook = true;
39
43
  }
@@ -115,6 +119,13 @@ ${MARROW_BLOCK_END}`;
115
119
  else {
116
120
  process.stdout.write('UserPromptSubmit hook already installed — Marrow context and passive decision briefs are injected on matching prompts.\n');
117
121
  }
122
+ const preActionHookInstall = (0, hook_pre_action_1.installPreActionHook)(process.cwd());
123
+ if (preActionHookInstall.installed) {
124
+ process.stdout.write('Installed PreToolUse hook — Marrow now checks each matched action before execution.\n');
125
+ }
126
+ else {
127
+ process.stdout.write('PreToolUse hook already installed — matched actions are checked before execution.\n');
128
+ }
118
129
  const sessionHookInstall = (0, hook_session_1.installSessionEndHook)(process.cwd());
119
130
  if (sessionHookInstall.installed) {
120
131
  process.stdout.write('Installed Stop hook — unfinished outcomes remain visible for reconciliation.\n');
@@ -230,6 +241,9 @@ if (process.argv[2] !== 'keys') {
230
241
  else if (cliArgs.contextHook) {
231
242
  void (0, hook_context_1.runContextHookCommand)();
232
243
  }
244
+ else if (cliArgs.preActionHook) {
245
+ void (0, hook_pre_action_1.runPreActionHookCommand)();
246
+ }
233
247
  else if (cliArgs.sessionHook) {
234
248
  void (0, hook_session_1.runSessionHookCommand)();
235
249
  }
@@ -262,26 +276,6 @@ if (process.argv[2] !== 'keys') {
262
276
  let thinkCallCount = 0;
263
277
  let orientCallCount = 0;
264
278
  let initialized = false;
265
- const pendingDecisions = new Map();
266
- const PENDING_TTL_MS = 30 * 60 * 1000; // 30 min TTL
267
- function actionHash(action) {
268
- const normalized = action.toLowerCase().trim().replace(/\s+/g, ' ');
269
- let h = 5381;
270
- for (let i = 0; i < normalized.length; i++) {
271
- h = ((h << 5) + h) ^ normalized.charCodeAt(i);
272
- h = h >>> 0;
273
- }
274
- return h.toString(36) + '_' + normalized.slice(0, 32);
275
- }
276
- // [FIX #11] Actually call cleanupPending to prevent unbounded map growth
277
- function cleanupPending() {
278
- const now = Date.now();
279
- for (const [key, val] of pendingDecisions) {
280
- if (now - val.timestamp > PENDING_TTL_MS) {
281
- pendingDecisions.delete(key);
282
- }
283
- }
284
- }
285
279
  function formatWarningActionably(w) {
286
280
  const pct = Math.round(w.failureRate * 100);
287
281
  return `⚠️ ${w.type} has ${pct}% failure rate — check what went wrong last time before proceeding`;
@@ -594,6 +588,8 @@ if (process.argv[2] !== 'keys') {
594
588
  description: 'Optional required proof pack for gated work: summary, checks, outcome, blockers, commits_prs_shas, rollback_target, handoff_result_file, deployment_and_smoke.',
595
589
  },
596
590
  gate_receipt_id: { type: 'string', description: 'Receipt id from marrow_agent_runtime.gate_receipt.id for risky work.' },
591
+ arbitration_receipt_id: { type: 'string', description: 'Required for arbitrated work: use marrow_arbitrate.arbitration.receipt_id from the same runtime response.' },
592
+ owner_approval_receipt_id: { type: 'string', description: 'Single-use owner approval receipt issued by authenticated dashboard review for review_required arbitration.' },
597
593
  action: { type: 'string', description: 'Optional original action. If provided and gate_receipt_id is omitted, MCP can fetch a matching runtime gate receipt before commit.' },
598
594
  type: { type: 'string', description: 'Optional original action type for auto gate lookup, e.g. deploy, publish, merge, handoff, implementation.' },
599
595
  surfaces: { type: 'array', items: { type: 'string' }, description: 'Optional surfaces for auto gate receipt, e.g. github, cloudflare, npm, production.' },
@@ -1025,6 +1021,61 @@ if (process.argv[2] !== 'keys') {
1025
1021
  required: ['action'],
1026
1022
  },
1027
1023
  },
1024
+ {
1025
+ name: 'marrow_arbitrate',
1026
+ description: 'Resolve conflicting next-step proposals from two or more tenant agents before execution. ' +
1027
+ 'Uses the existing Marrow runtime gate and returns a durable arbitration receipt with the selected, ' +
1028
+ 'synthesized, review-required, or blocked action and exactly why it changed.',
1029
+ inputSchema: {
1030
+ type: 'object',
1031
+ properties: {
1032
+ objective: { type: 'string', description: 'The shared owner or workflow objective.' },
1033
+ ownerIntent: { type: 'string', description: 'Optional bounded owner intent used for deterministic alignment.' },
1034
+ conflictType: {
1035
+ type: 'string',
1036
+ enum: ['action_conflict', 'policy_conflict', 'evidence_conflict', 'authority_conflict', 'risk_conflict'],
1037
+ },
1038
+ proposals: {
1039
+ type: 'array',
1040
+ minItems: 2,
1041
+ maxItems: 8,
1042
+ items: {
1043
+ type: 'object',
1044
+ properties: {
1045
+ proposal_id: { type: 'string', description: 'Stable opaque proposal id.' },
1046
+ agent_id: { type: 'string', description: 'Existing agent id in this Marrow account.' },
1047
+ action: { type: 'string', description: 'Proposed next action.' },
1048
+ rationale: { type: 'string', description: 'Optional concise rationale; redacted before transport.' },
1049
+ confidence: { type: 'number', minimum: 0, maximum: 1 },
1050
+ risk_level: { type: 'string', enum: ['low', 'medium', 'high'] },
1051
+ requires_owner_approval: { type: 'boolean' },
1052
+ evidence: {
1053
+ type: 'array',
1054
+ maxItems: 8,
1055
+ items: {
1056
+ type: 'object',
1057
+ properties: {
1058
+ kind: { type: 'string' },
1059
+ reference: { type: 'string', description: 'Opaque identifier only; no URL, path, or raw evidence.' },
1060
+ },
1061
+ required: ['kind', 'reference'],
1062
+ },
1063
+ },
1064
+ },
1065
+ required: ['proposal_id', 'agent_id', 'action'],
1066
+ },
1067
+ },
1068
+ action: { type: 'string', description: 'Optional runtime action label.' },
1069
+ type: { type: 'string', description: 'Optional runtime action type. Defaults to coordination.' },
1070
+ agentId: { type: 'string', description: 'Requesting agent id. Defaults to MARROW_AGENT_ID.' },
1071
+ sessionId: { type: 'string', description: 'Optional workflow session id.' },
1072
+ surfaces: { type: 'array', items: { type: 'string' } },
1073
+ context: { type: 'object', description: 'Optional non-sensitive runtime metadata.' },
1074
+ proof: { type: 'object', description: 'Optional proof already available to the runtime gate.' },
1075
+ },
1076
+ required: ['objective', 'proposals'],
1077
+ },
1078
+ },
1028
1079
  {
1029
1080
  name: 'marrow_governance_control_plane',
1030
1081
  description: 'Return Marrow control-plane proof: governance, runtime gates, proof packs, fleet intelligence, supported harnesses, and exact next action.',
@@ -1319,7 +1370,7 @@ if (process.argv[2] !== 'keys') {
1319
1370
  success(id, {
1320
1371
  protocolVersion: '2024-11-05',
1321
1372
  capabilities: { tools: {}, prompts: {} },
1322
- serverInfo: { name: 'marrow', version: '3.9.48' },
1373
+ serverInfo: { name: 'marrow', version: '3.9.50' },
1323
1374
  });
1324
1375
  // Auto-enroll: emit enrollment notification on connection
1325
1376
  if (AUTO_ENROLL) {
@@ -1502,6 +1553,8 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1502
1553
  caused_by: args.caused_by,
1503
1554
  proof: args.proof,
1504
1555
  gate_receipt_id: args.gate_receipt_id,
1556
+ arbitration_receipt_id: args.arbitration_receipt_id,
1557
+ owner_approval_receipt_id: args.owner_approval_receipt_id,
1505
1558
  action: args.action,
1506
1559
  type: args.type,
1507
1560
  surfaces: args.surfaces,
@@ -1590,8 +1643,6 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1590
1643
  const outcome = args.outcome;
1591
1644
  const outcomeSuccess = args.success ?? true;
1592
1645
  const type = args.type || 'general';
1593
- // [FIX #11] Cleanup pending decisions on each auto call
1594
- cleanupPending();
1595
1646
  // [FIX #8] Include pending flag so agent knows logging is deferred
1596
1647
  const response = {
1597
1648
  action,
@@ -1845,6 +1896,29 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1845
1896
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1846
1897
  return;
1847
1898
  }
1899
+ if (toolName === 'marrow_arbitrate') {
1900
+ const result = await (0, index_1.marrowArbitrate)(API_KEY, BASE_URL, {
1901
+ objective: (0, redact_1.redactSensitiveText)(args.objective),
1902
+ owner_intent: typeof args.ownerIntent === 'string' ? (0, redact_1.redactSensitiveText)(args.ownerIntent) : undefined,
1903
+ conflict_type: args.conflictType,
1904
+ proposals: Array.isArray(args.proposals)
1905
+ ? args.proposals
1906
+ : [],
1907
+ action: typeof args.action === 'string' ? (0, redact_1.redactSensitiveText)(args.action) : undefined,
1908
+ type: args.type,
1909
+ agent_id: args.agentId || AGENT_ID,
1910
+ session_id: args.sessionId || SESSION_ID,
1911
+ surfaces: Array.isArray(args.surfaces) ? args.surfaces : undefined,
1912
+ context: args.context && typeof args.context === 'object' && !Array.isArray(args.context)
1913
+ ? (0, redact_1.redactSensitiveValue)(args.context)
1914
+ : undefined,
1915
+ proof: args.proof && typeof args.proof === 'object' && !Array.isArray(args.proof)
1916
+ ? (0, redact_1.redactSensitiveValue)(args.proof)
1917
+ : undefined,
1918
+ }, SESSION_ID, FLEET_AGENT_ID);
1919
+ success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1920
+ return;
1921
+ }
1848
1922
  if (toolName === 'marrow_governance_control_plane') {
1849
1923
  const result = await (0, index_1.marrowGovernanceControlPlane)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
1850
1924
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });