Skip to main content
Glama
README.md
# Marrow AI MCP Server

> `@getmarrow/mcp`: the control layer for AI agents. Marrow AI puts a named yes on an action before your agent acts, and leaves proof the next agent can reuse.

**Which Marrow?** This is Marrow AI from [getmarrow.ai](https://getmarrow.ai) (npm `@getmarrow/mcp`, MCP Registry `ai.getmarrow/marrow`). It is not affiliated with marrow.id, HyperMarrow, marrow.works or other projects named Marrow.

[getmarrow.ai](https://getmarrow.ai) · [Docs](https://getmarrow.ai/docs/) · [Marrow AI for MCP](https://getmarrow.ai/marrow-for-mcp) · [MCP Registry entry](https://registry.modelcontextprotocol.io/v0.1/servers/ai.getmarrow%2Fmarrow/versions/latest) · [Changelog](https://github.com/getmarrow/marrow-mcp/blob/master/CHANGELOG.md) · [![getmarrow/marrow-mcp MCP server](https://glama.ai/mcp/servers/getmarrow/marrow-mcp/badges/score.svg)](https://glama.ai/mcp/servers/getmarrow/marrow-mcp)

Marrow AI is the runtime control and proof layer for teams running AI agents. It applies policy and prior lessons before consequential actions, then records the evidence and outcome afterward.

Use `@getmarrow/mcp` when your agent client supports the Model Context Protocol and you want Marrow available inside the agent's normal workflow. It works with Claude Code, Claude Desktop, Cursor and other MCP-compatible clients without replacing the model or harness.

## Quick start

1. **Create a key.** Sign up at [getmarrow.ai/signup](https://getmarrow.ai/signup), then open the dashboard, go to **API keys** and choose **Create API key**. The key is shown once.
2. **Activate.** Make the key available as `MARROW_API_KEY` (from your secret storage, or an owner-only `~/.marrow/env`), then run the installer from a terminal:

   ```bash
   npx -y @getmarrow/install@latest activate
   ```

   Fallback if you cannot set the key first (a key typed on a command line stays in your shell history): `MARROW_API_KEY=<your_key> npx -y @getmarrow/install@latest activate`.
3. **Restart** the agent host and review/enable its hook trust.
4. **Verify:**

   ```bash
   npx -y @getmarrow/install@latest doctor --self-test
   ```

**What is enforced.** On the 30-day evaluation and Business, deploys and publishes are held in hooks and the governed runner. Team and Free warn. If Marrow is unreachable, hooks hold high-risk actions and let routine actions run with a warning, and the governed runner stops protected actions it cannot verify. Over MCP, the agent gets the decision and is expected to follow it. Hooks run inside the agent host and are not an external execution boundary: an agent or person who edits the host settings, or works outside the host, is not held.

## Agent Discovery

- **Publisher:** Marrow AI. Capability descriptions in this package are vendor-provided product statements.
- **Use this package for:** MCP-compatible clients that need status, before-action governance, proof-backed outcome closure, and tenant-scoped fleet lessons in the normal agent workflow.
- **Product category:** agent fleet governance and control layer.
- **Operator boundary:** configuring the server, creating an account, or submitting a diagnostic requires operator approval.
- **Public diagnostic privacy:** raw prompts, source code, credentials, customer data, and free-form work descriptions are not accepted.
- **Machine-readable product facts:** https://getmarrow.ai/llms.txt and https://getmarrow.ai/.well-known/marrow.json

Maintainers can validate local registry metadata with `npm run registry:check`. After an approved registry publication, `npm run registry:verify` confirms that the official registry returns the exact package name and version; package metadata alone is not treated as publication proof.

Public operating guides:

- [Governance Readiness Assessment](https://getmarrow.ai/governance-readiness)
- [Marrow for MCP clients](https://getmarrow.ai/marrow-for-mcp)
- [Marrow for OpenAI Codex](https://getmarrow.ai/marrow-for-codex)
- [Marrow for Claude Code](https://getmarrow.ai/marrow-for-claude-code)
- [Marrow for Cursor](https://getmarrow.ai/marrow-for-cursor)
- [Marrow for Hermes Agent](https://getmarrow.ai/marrow-for-hermes)
- [Marrow for OpenClaw](https://getmarrow.ai/marrow-for-openclaw)

The assessment accepts bounded selections only. It does not install Marrow, authorize a purchase, accept free-form work content, or establish compliance, security, or ROI.

## Where Marrow fits

Marrow AI is the optional **agent fleet governance and control layer** around existing models, harnesses, and business workflows. It is not a model provider, agent harness, memory-only tool, or observability replacement.

Marrow fits when one or more of these is true:

- Two or more autonomous agents, models, or harnesses contribute to one accountable workflow (`multiple_autonomous_agents`).
- Agents can deploy, merge, publish, change production data, use privileged tools, spend money, or message customers (`production_or_privileged_access`).
- The workflow has repeated failures, retries, regressions, or known lessons that should affect the next action (`repeated_workflow_failures`).
- Work can be marked complete without proof, verification, or a recorded business outcome (`missing_completion_evidence`).
- Policy, approval, or completion requirements vary across agents, tools, environments, or teams (`inconsistent_policy_enforcement`).
- Agents operate around regulated, proprietary, personal, financial, health, legal, credential, or otherwise sensitive workflows (`regulated_or_sensitive_work`).

## Where Marrow is not a fit

- One advisory assistant performs no shared, privileged, production, or accountable business action (`single_low_risk_assistant`).
- The workflow has no need for policy, proof, outcome accountability, or fleet coordination (`no_governance_requirement`).
- The operator has declined Marrow evaluation or installation (`operator_declined_evaluation`).

Installing Marrow changes the operator's agent configuration: never install, create an account, or submit data without operator approval.

Verify current claims through the [public evidence manifest](https://getmarrow.ai/.well-known/marrow-evidence.json), [reference graph](https://getmarrow.ai/.well-known/marrow-reference-graph.json), and [integration cards](https://getmarrow.ai/.well-known/marrow-integrations/index.json).

## Install

**Windows:** Native Windows is not supported yet. Use WSL.

For most installations, follow the [Quick start](#quick-start): the universal installer's `activate` command. The steps below are the manual, MCP-only route.

Get a key at [getmarrow.ai/signup](https://getmarrow.ai/signup) and keep it in an owner-only key file: put `MARROW_API_KEY=<your_key>` in `~/.marrow/env`, then run `chmod 700 ~/.marrow && chmod 600 ~/.marrow/env` (Marrow rejects a key file whose folder others can write):

```bash
mkdir -p ~/.marrow && chmod 700 ~/.marrow
# put MARROW_API_KEY=<your_key> in ~/.marrow/env
chmod 600 ~/.marrow/env
```

Claude Code plugin users: after /plugin install, run /reload-plugins (or restart Claude Code). To add a key, put MARROW_API_KEY=<your key> in ~/.marrow/env, run chmod 600 ~/.marrow/env, then /reload-plugins. Do not run activate or marrow-mcp setup for the plugin, because they write a second copy of the hooks.

Fallback if you cannot use the key file (a key typed on a command line stays in your shell history): set it through trusted secret storage, or `export MARROW_API_KEY=<your_key>` for the session.

Hooks-only setup (`marrow-mcp setup` writes hooks and `CLAUDE.md` instructions, and adds no MCP server). It needs the key first: without one it prints how to get it and exits 2 before writing anything.

```bash
npx -y --package=@getmarrow/mcp@latest marrow-mcp setup
```

The MCP server and Claude Code's cooperative pre-action hook read the key only from the process environment or an owner-controlled `~/.marrow/env` (or `~/.marrow/env.local`) with owner-only permissions (`chmod 600`). An owner file that is a symlink, readable or writable by others, owned by another user, or in a directory others can write supplies nothing, key included; the missing-key message names it with the `chmod` fix. Repository-local files (`.env`, `.env.local`, `.marrow/env*` in the working directory or its parents) are never read for `MARROW_API_KEY` or any other `MARROW_*` setting, whatever their permissions, so project content cannot replace the account, agent, or API endpoint. If one holds a key, the missing-key message names that file and the fix: run `npx @getmarrow/install doctor`, or move the line to `~/.marrow/env`, then `chmod 600 ~/.marrow/env`. The public hook callback is still a client self-report, not certified host provenance or an external enforcement boundary.

Then configure the MCP server:

```json
{
  "mcpServers": {
    "marrow": {
      "command": "npx",
      "args": ["-y", "--package=@getmarrow/mcp@latest", "marrow-mcp"]
    }
  }
}
```

After setup writes MCP configuration or hooks, restart the agent host and review/enable its hook trust. Then verify the reloaded environment:

```bash
npx -y @getmarrow/install@latest doctor --self-test
```

Successful setup alone does not mean this process reloaded or that hooks are active. Keep savings at zero until observed usage supplies evidence.

## Tool Profiles

Ordinary setup does not require `MARROW_TOOL_PROFILE`. When the variable is unset, Marrow uses the `primary` profile and exposes exactly the 17 tools in [Primary MCP Tools](#primary-mcp-tools).

- `MARROW_TOOL_PROFILE=primary` explicitly selects the same 17-tool primary surface.
- `MARROW_TOOL_PROFILE=core` preserves the seven-tool runtime, think, commit, ask, status, auto, and handoff-status surface.
- `MARROW_TOOL_PROFILE=full` exposes the complete advanced and legacy catalog for integrations that require it.

An invalid value returns a bounded configuration error with the exact allowed values; it never falls back to `full`. Restart the MCP process after changing the profile.

Local visibility does not grant paid access. Every tool call continues through Marrow's backend authentication, tenant, key-permission, plan, proof, and policy enforcement. MCP status responses include `mcp_tool_profile` with the configured and effective profile, visible tool names/count, and a backend primary-tool entitlement projection when fresh authenticated evidence is provided. Missing or cached entitlement evidence is labeled unavailable and cannot authorize a call.

## Keeping MCP Current

Marrow's hosted API, website, and dashboard update automatically; local MCP hooks, configuration, and pinned package commands do not silently rewrite themselves. Keeping them current delivers new client-side features, compatibility improvements, and any published security fixes. During authenticated status/runtime activity, Marrow returns a `client_update` notice when the package is behind or unknown, and passive context shows the agent the exact update and verification commands.

```bash
npx -y @getmarrow/install@latest activate
npx -y @getmarrow/install@latest doctor

# Manual MCP-only setup
npx -y --package=@getmarrow/mcp@latest marrow-mcp setup

# Verify live read latency, last success, and local backlog
npx -y --package=@getmarrow/mcp@latest marrow-mcp ping
```

Detection and notification are automatic. After explicit installer activation, the local controller may restore only Marrow-managed hooks/configuration. Package upgrades, owner policy, credentials, and unrelated configuration remain explicit and subject to the operator's normal change policy.

## Pending write recovery

A direct `marrow_think` can receive a durable pending response before the backend can safely expose a decision ID. The client recognizes the explicit `agent_write_reconciliation.v1` think contract and the corresponding current legacy pending shapes. It retries the identical authenticated request with the original idempotency key, agent and session, at most three reconciliation rounds with the existing one-second wait between rounds. Each round retains up to two transport attempts for retryable failures, so one invocation can send up to six HTTP requests, all with the same key and body. It never creates a placeholder decision or starts an automatic operation to recover a direct think call. Unknown states, conflicting keys and unsafe responses fail closed; exhaustion returns a retryable structured `pending_receipt` with `committed:false`, the original `idempotency_key`, and `request_hash`. To resume manually, pass both fields to `marrow_think` with the same arguments, credentials, agent and session. The hash binds that exact canonical request and scope; drift is rejected before sending. No prompt, credential, or raw scope is included in the receipt. Caller keys must be privacy-safe opaque identifiers.

A saved `observed_unverified` outcome is terminal observation evidence, not a committed outcome. Receipt expiry cannot retroactively authorize completed work. Preserve the original decision, receipt, proof and key; an already authorized durable checkpoint may finish through its existing exact recovery path. Do not repeat the action merely to obtain a fresh receipt.

## Keyless seatbelt

When a pre-action hook has no usable Marrow key and the machine shows no sign of a Marrow account (for example an owner key file, a claim marker or Marrow's local approval state), the hook runs a local seatbelt instead of holding every protected action:

- **It stops seven kinds of action:** a change to Marrow's hooks, settings or plugins (including the Marrow plugin switch in Claude Code settings and the plugin files under `~/.claude/plugins`) or an owner-only Marrow command (S0), a force-push or delete of a protected branch (S1), a production deploy (S2), a package publish (S3), reading credentials or secrets, including a project `.env` file or any other `<name>.env` file that exists (templates such as `.env.example` pass), `~/.marrow/fleet.env` and `~/.marrow/credentials` (S4), a recursive delete outside the workspace (S5), and decoded data run by a shell or interpreter (S-x). A protected file named to any program that is not a plain reader (such as `cat`, `grep`, `jq` or `diff`) stops too, so an editor or a tool the seatbelt does not know cannot rewrite it. A tool counts as a plain reader only with the long options it lists, spelled in full: GNU tools and git also accept a shortened spelling (`cp --target=DIR`, `git reset --har`), so a shortened or unknown long option on a command that names a protected file stops. Archiving (`tar -c`, `zip`), `tree` and `prettier --check` only read what they are given: only the archive and the files they write are checked. The stop is the host's own deny, with text that tells the agent to leave the action to the machine owner. On Grok the stop is Grok's fixed denial ("Marrow blocked this protected action."), the only denial its installed guard passes.
- **Everything else gets no Marrow decision, so the host's own approval step decides:** Claude Code and Codex get an empty answer, Cline is not cancelled and Windsurf gets no output. Gemini CLI gets `{"decision":"allow"}`, the only answer its installed guard passes without blocking. Cursor asks with its own approval prompt before a shell command that a hook without a key used to hold. Cursor's other tools (MCP calls included) and Grok have no neutral answer, so they keep holding what a hook without a key held before. An action that a hook without a key already let through gets the same answer as before.
- **Host tools and MCP calls:** the seatbelt reads each host's own file tools (for example Cline `write_to_file` and `replace_in_file`, Gemini CLI `read_file`, `replace` and `delete_file`, Grok `search_replace`, Cursor `Delete`) and MCP calls in every host form (`mcp__server__tool`, `MCP:server:tool`, Gemini `mcp_server_tool` and `server__tool`, Cline `use_mcp_tool` and `access_mcp_resource`). Every string value in an MCP call's arguments, at any depth, gets the S0 and S4 path checks. A tool it does not recognise, or MCP arguments it cannot read, never gets the seatbelt's pass: the hook gives the answer a hook without a key gave before, a hold where that held.
- **It fails closed:** an internal error, a check that runs past 300 ms, or input the host cut short stops the action. Windsurf and Cline do not tell the hook the working folder, so there a recursive delete outside the temporary folders stops.
- **Local receipts:** each seatbelt decision is written to `~/.marrow/seatbelt/receipts/` on this machine, as codes and keyed hashes, plus the built-in tool name and shell program names from fixed lists (any MCP tool is recorded only as `mcp`); no command text, argument, path or URL. Receipts are kept for up to 90 days or 50 MB. Nothing is uploaded. Where the seatbelt enforces (no working key), an agent cannot edit the receipts or read the device salt that keys them (S0, S4); with a working key the seatbelt only watches. Receipts are a record, not an authority.
- **Folder permissions:** the seatbelt runs only where your home folder and the agent settings folders (such as `~/.claude`) can be written by you alone. On Linux, a folder that your own private group can also write (the default with umask 002, mode 0775) still counts. A folder others can write, a group with other members, a folder reached through a symlink, or users and groups from a network directory (LDAP, SSSD) keep every hold, and the hold names the reason and how to fix it, for example "Marrow stopped this action: there is no Marrow key on this machine, and Marrow uses its stricter no-key mode here because HOME is not your login home folder. Fix (for the person at this machine, not the agent): start the agent with HOME set to your login home folder, or add a Marrow key (getmarrow.ai/docs#start-here)."
- **Stops you may meet on purpose:** `cp .env.example .env.local` (or any command that writes a `.env` file), `set -a && . ./.env && npm run dev` and `docker compose --env-file .env up` are stopped as secrets (S4), and `rm -rf ~/.cache/<app>` as a recursive delete outside the workspace (S5). If you meant it, run the line yourself in your own terminal. Each stop message names the kind of action and ends with its code.
- **It is a seatbelt, not a vault:** it runs on the machine as your user and makes no network call. A determined agent can get around a local check, for example with a script it writes now and runs later, an interpreter, or a tool path with no hook.
- **With an account:** a machine linked to an account but with no working key keeps every hold it had, and these stops apply too. With a working key nothing changes; the seatbelt only checks the action after Marrow has answered, without changing the answer.

`marrow-mcp setup` and the installer both need a key; hooks without a key come from the Marrow plugins for Claude Code, Codex and Cursor.

## Hook behavior details

These behaviors shipped in 3.9.100 and still apply.

- **One Marrow entry per hook event:** `marrow-mcp setup` recognizes the installer's local-runtime hook commands exactly (same version throughout, no additions), keeps whichever form is there (local runtime or npx), and never adds a second entry. Your own hooks stay as they are.
- **Plain words without a key:** on a machine linked to a Marrow account, a protected action with no working key is denied with "Marrow is linked to an account but has no working key; owner: run `npx -y @getmarrow/install@latest doctor`." A machine with no account runs the [keyless seatbelt](#keyless-seatbelt). The hooks read the key from `MARROW_API_KEY` or an owner-only `~/.marrow/env.local` or `~/.marrow/env`, never from a file others can read.
- **First-prompt briefs say so:** the prompt hook marks its brief `context.prompt_brief: true`; a brief authorizes nothing.
- **Deploy text only word for word:** the exact command text is sent only for `sst deploy --stage <stage>` (optionally `--verbose`, optionally after one `npx` or `bunx`), with a stage of lower-case letters, digits and dashes, in a single clause with nothing else in it, so Marrow can judge the stage itself. Every other command, including other deploy tools, sends only the hash; no free-text word leaves the machine.
- **No lifecycle event id with two payloads:** an event id names its payload, so a later run is a new record and a resend carries the same bytes.

## Setup and key handling

These behaviors shipped in 3.9.101 and still apply.

- **Setup with no key stops:** `marrow-mcp setup` with no key, or with a key only in an untrusted place such as a project `.env`, stops, writes nothing and tells you how to get a key.
- **Keys are read from trusted places only:** the key comes from the environment or an owner-only `~/.marrow/env`.
- **Key-file steps include `chmod 700 ~/.marrow`** before `chmod 600 ~/.marrow/env`.
- **Hold messages say where to approve:** a held action says how to approve it where your host really asks.
- **One enforcement sentence:** the "what is enforced" wording matches the public site.

## Project files and Marrow's own tools

These behaviors shipped in 3.9.102 and still apply.

- **A cloned project's `.env` cannot redirect the MCP server:** repository-local `.env`, `.env.local` and `.marrow/env*` files are never read for `MARROW_API_KEY` or any other `MARROW_*` setting, so project content cannot point the server at another account, agent or API endpoint.
- **Safer key-file advice:** an owner file that is a symlink, readable or writable by others, owned by another user, or in a directory others can write supplies nothing, and the missing-key message names the file with the `chmod` fix.
- **Only Marrow's own tools skip the gate, by exact name:** a tool is Marrow's own only when its server is exactly `marrow` and its tool name is exactly one this package registers. Look-alike servers, other cases, extra spaces or suffixes go through the pre-action gate like any other MCP tool. Re-run `marrow-mcp setup` to refresh hooks written with the old matcher.

## Seatbelt, outage holds and exact approvals

These behaviors shipped in 3.9.103 and still apply.

- **Keyless seatbelt:** a hook with no key, on a machine with no sign of a Marrow account, stops seven kinds of action (Marrow's own settings, force-push or delete of a protected branch, production deploy, package publish, reading credentials, recursive delete outside the workspace, decoded data run by a shell) in shell commands, each host's own file tools and MCP calls, and leaves everything else to the host's own approval step. A tool it does not recognise keeps the answer a hook without a key gave before. Machines linked to an account keep every hold they had. A Linux home or agent settings folder that only your own private group can also write (umask 002) keeps the seatbelt. See [Keyless seatbelt](#keyless-seatbelt). Marrow's own plugins and the Marrow plugin switch are protected like its settings.
- **High-risk actions wait while Marrow is down:** with a key, if Marrow cannot be reached, a git push, publish, deploy, API delete, seatbelt-category command or other high-risk tool (for example an MCP tool) is held on every host until Marrow is back; words in a commit message or a path never hold a command, and routine actions keep running with a warning. If Marrow answers but cannot issue a permit, a high-risk action is denied and never allowed as offline.
- **Local receipts:** each seatbelt decision is written on this machine as codes and keyed hashes, plus the built-in tool name and shell program names from fixed lists (any MCP tool is recorded only as `mcp`); no command text, argument, path or URL. Kept for up to 90 days or 50 MB. Nothing is uploaded.
- **Approvals bind to the exact action:** an owner approval resumes only the exact action that was held. A held action whose sent form hides a value also carries `exact_hmac`, a keyed hash made with this machine's private device key (never sent); a retry that differs only in a certain credential, such as a new one-time password, still resumes. The action hash version is now v3: learned approvals and known workflows from earlier versions ask once more, then are learned again. See [Approvals in the Host Prompt](#approvals-in-the-host-prompt).
- **Hidden commands are checked:** commands inside an unquoted here-document body, a `${x:-$(...)}` default or an arithmetic expansion are checked like any other command.
- **`marrow-mcp keys create` reads every flag form:** `--name=`, `--type=` and `--expires=` (and `-n=`, `-t=`) are read like `--name value`; a flag that is present but blank, or a `--scopes` / `--agents` list with no entry, is a usage error (exit 2) and nothing is sent; leaving `--scopes` and `--agents` out sends neither.

## What's New in v3.9.109

v3.9.109 adds several security fixes and hook fixes. The full entry is in CHANGELOG.md.

- **Turning Marrow control off needs the owner.** A shell command that runs the installer to disable or uninstall Marrow is held for the account owner, and refused when no person is at the prompt (`auto`, `bypassPermissions`, `dontAsk`, headless). Held forms: `npx @getmarrow/install control disable` (also `uninstall`, `control off`, `--disable`), `pnpm dlx`, `bunx`, `npm exec`, `node` on the installer, `marrow-install`, chained, on a new line, inside `bash -c`, quoted, with `control`, `disable` or `uninstall` in upper case, or with a zero-width character inside `disable` or `off` after `control`. Not recognised yet: a wrapper with options before the installer (`sudo -u root`, `env -u X`), a package word given by a variable or with an escape, an upper-case `NPX` or package word, a zero-width character inside `--disable`, a full-width, soft-hyphen or look-alike spelling of the package word or of `uninstall`, `eval`, and other package runners. While Marrow is switched off, the bypass record is sent at the first hook call of a session (names only), not at the first protected action.
- **Hook settings, `~/.marrow` and shell start-up files are protected from every tool.** A write by Edit, Write, a redirect, `tee`, `sed -i`, `cp`, `mv`, `crontab` and the like is held.
- **A keyless hook says so once per Claude Code session.**
- **A resumed host session under another Marrow agent works.** Each agent has its own Marrow session id (`<host session id>.m<16 hex>`). A hold open under 3.9.108 at the upgrade is not matched by the 3.9.109 hooks, and the loop guard state starts again for that session.
- **A damaged control file no longer switches the hooks off.** If `~/.marrow/control.json` cannot be read or is invalid, the hooks keep running as if control were on and the prompt hook tells you once per session. Only a valid file that says disabled stops them.
- **The pre-action hook leaves as soon as it has answered.** An unanswered request can no longer keep it running past the host's time limit. The answer is the same.
- **Test keys and live keys never share an approval.** The same action with a test key and with a live key is a different approval.
- **`patch`, `git apply` and `git am` are checked for what they write.** A patch that would write a protected file is held; so are six `git am` forms that used to pass (`--continue`, `--skip`, `--abort`, `--quit`, `--resolved`, `--show-current-patch`).
- **Windows groundwork.** The seatbelt reads Windows program names and paths, and the session loop guard on Windows checks the access list of its folder. That last check is the one change that is looser than 3.9.108, on Windows only and by the owner's decision (3.9.108 denied every loop-guarded call there). Native Windows is still not supported; use WSL.
- **IPv4 first.** The pre-action hook tries IPv4 first when it dials Marrow, so a broken IPv6 route cannot stall it.

These behaviors shipped in 3.9.108 and still apply.

v3.9.108 adds Codex plugin pins on 3.9.107. The full entry is in CHANGELOG.md.

- **Codex plugins `marrow` and `marrow-advisory` 0.1.3 are recognised.** The SHA-256 digests of their six pinned files are now listed next to the 0.1.1 digests. A Codex with 0.1.3 installed and enabled, and those six files unchanged, is recognised as running the official Marrow server, the same as 0.1.1 is; any other change to them is checked like any other server. The digests of the 0.1.2 drafts, which were never released, are removed. Nothing changes for an installed 0.1.1.

These behaviors shipped in 3.9.107 and still apply.

v3.9.107 is a security hotfix on 3.9.106. The full entries are in CHANGELOG.md.

- **Every text hash is keyed per install.** The `command_sha256` and `input_sha256` of every hooked tool (Bash, Monitor, PowerShell, Agent, Edit, Write, WebFetch, MCP tools, and any tool name Marrow does not know, among others), on Claude Code, Codex and Cursor, are now `HMAC-SHA256` under a key derived from the per-install device key (`~/.marrow/seatbelt/salt`), with the tool name and kind inside the MAC. A free-text field can hold a value the redactor cannot see, and a plain hash of it could be guessed offline. The same call on the same install gets the same hash, so a hold still resumes and a learned approval still matches there. A deploy command Marrow sends in clear (`sst deploy --stage production`) keeps its plain hash. `exact_hmac` moves to a `v2` domain. The hold `correlation` and `target` in `~/.marrow/host-approvals/state.json` and the lifecycle `correlation_id` follow.
- **Upgrade note: some approvals are asked once more.** A hold or a learned approval made before this update never matches again, and an approval learned on one machine does not match on another. A learned approval, an automatic approval or the memory of an earlier decline that the service keyed on a text hash does not carry across machines, or from before this update: the action is asked again, never allowed on a match it no longer has.
- **A machine with no usable device key can still resume a hold.** With no usable device key (a read-only home, an unsafe, corrupt or missing key file, Windows) the exact-action hashes and the hold target are keyed with a fallback key (HMAC-SHA256 of the secret of `~/.marrow/host-approvals/state.json`) instead of a new random value per call. A held action then resumes, is reported and is committed as on a usable key. When the approval state is unusable too, the action is held and asked every time, with one stderr note. Only a hook with a Marrow key makes that state when it does not exist; a machine with no key never creates it.
- **Less text is sent in `programs[]`.** The first word of a pipeline stage that feeds a password program (`"pw" | smbpasswd -s`) is no longer sent or stored when it may be the value itself: it is named only when it is a known program (a stage with other words) or one of `cat`, `yes`, `pbpaste` (alone); any other word becomes the fixed marker `unlisted-feeder` and the action is never bound. A tool that does not run shell text (ToolSearch's `query`, a prompt, a path, an unknown tool's `command`) names a first word only when it is a known program; any other word becomes `unlisted-program`. Bash, Monitor, PowerShell, Codex `exec` and Cursor `Shell` are unchanged.
- **A secret in an edit path is replaced.** A known-format secret inside the path of an edit tool (`Edit`, `Write`, `NotebookEdit`, `MultiEdit`, an `apply_patch` header) is replaced in `normalized_action.paths[]` as a span: `/p/ghp_...X.txt` is sent as `/p/[secret].txt`. An action whose path held a span is never bound (it is asked each time). Not covered: a plain word that is not key-shaped, a token split across `/`, and a prefix written in another case.
- **More commands that print the environment are stopped.** The environment-dump stop (the keyless S4 stop, and the keyed protected action) now also covers `/proc/<pid>/task/<tid>/environ` read by an input redirect, the Read and Grep tools, a glob, a variable default, a `cd` into a process folder or `find /proc -name environ -exec`, and a bare `env` or `printenv` run by `awk`, by code passed with `-e`, `-c` or `-r`, or by an `sh -c` positional parameter. It only adds stops. It is a known list of forms, not every command a shell can run; the known gaps are listed in CHANGELOG.md.
- **The approvals state file is protected like the receipts salt.** Reading `~/.marrow/host-approvals/state.json` (with its temp copy and any backup beside it) now gets the decision a read of the receipts salt gets: a keyless S4 stop, a protected keyed action and an outage hold. Listing or stat of the folder and words that only mention the folder are unchanged.
- **Keyed mode stops secret-file reads hidden in nested shell code.** In keyed mode a read of a secret file (`.env`, `.env.local`, `.env.<name>`, `.npmrc`, `.netrc`, `.dev.vars`, a key file) hidden in `bash -c`, `eval`, `sudo -n sh -c`, `env -S`, `xargs`, `ssh`, a chain, a variable default, a glob or a symbolic link is now a protected action, as the keyless seatbelt already stops it. Keyed mode also treats `.env.dist` and `.env.defaults` as secret files, like keyless mode. Side effect: a `grep .env README.md` or `jq '.env' package.json` inside `bash -c`, `eval`, `ssh` or `sudo -n sh -c` is now stopped in keyed mode.
- **awk programs that run a stopped action are stopped (keyless).** The keyless seatbelt now stops an awk-family program (`awk`, `gawk`, `mawk`, `nawk`, `busybox awk`, and any other program whose name contains `awk`) that runs a stopped action built at run time. A command made only of string literals is judged as a nested shell; a command with any other part (ARGV, ENVIRON, a `-v` variable, a field, a value read by getline, a function call) is the new dynamic-command stop `dynamic_awk_command`. An awk command that builds its command string from ARGV, a `-v` value or a field is now stopped even when it is harmless. Keyed mode is unchanged.
- **`command` looks a name up only as a leading `-v` or `-V`.** For every program, `command npm publish -v`, `command git push -v --force origin main` and `command rm -rf ~ -v` passed the keyless seatbelt before and now stop. `command -v node`, `command -V git` and `command node -v` are unchanged.
- **Installer runtime core (loosens one check).** The installer's next runtime core is now accepted next to the 0.1.70-0.1.74 core, so a HOME behind a symlinked parent installs. On Cursor and Codex, a `marrow` server launched by that new core now proves the official server as the old core's launch does. A launch is still proven only when it is exactly the loader that started the hook, and any other core is not proven. An installer with the new core and an older MCP is not proven and its own tools are held. Release the MCP first, then the installer.

These behaviors shipped in 3.9.106 and still apply.

v3.9.104 and v3.9.105 were not published to npm; their changes shipped in 3.9.106 and are listed in CHANGELOG.md.

- **No key no longer breaks the server.** With no Marrow key anywhere the server reads one, the MCP server now starts with zero tools and an `instructions` text that says how to connect a key (create one at https://getmarrow.ai/signup, then run `npx -y @getmarrow/install@latest activate`), instead of printing "MARROW_API_KEY environment variable is required" and exiting 1, which hosts showed as a broken server. A `tools/call` is a JSON-RPC error: it judges, records, reads and writes nothing. A key of any shape (an invalid or whitespace-only one, `MARROW_KEY`, a fleet `MARROW_KEY_<NAME>`, any `--key` argument) or an owner key file that fails the owner-only check keeps the previous behavior, and a key only in a project `.env` still never counts. Under the Claude Code plugin the instructions carry the plugin's key steps.
- **Installer-written servers are proven exactly.** On Cursor and Codex, an installer-written `marrow` server skips the gate for Marrow's own tools only when it is exactly the launch of the installer loader that started the hook: the same version, the same MCP pack integrity, the same SDK pin and the same runtime core. An older installer's launch, a look-alike version such as `3.9.1050`, or a launch with no pack integrity now goes through the gate like any other MCP server. The npx and `marrow-mcp` bin forms are unchanged.
- **Commands that print the environment are stopped.** Commands that print many or all environment variables stop with the existing S4 message without a key, and are protected actions with one. They include `compgen -e`, `tmux show-environment`, `systemctl show-environment`, `ps -E`, `npm run env` (and pnpm, yarn, bun), `php -i`, `strace -v`, `/proc/<pid>/task/<tid>/environ`, `env` run through wrappers such as `setpriv`, `unshare`, `runuser`, `nsenter` and `xargs`, and environment-printing code passed to an interpreter (`node --eval=CODE`, `perl -e`, `python3 -c`, `php -r`).
- **Every Claude Code tool reaches the hook.** The Claude Code matcher sends every tool call to the hook except Marrow's own exact tool names and the read-only `LS` and `ListMcpResourcesTool`. Before, a fixed list of built-ins reached it, so `Monitor`, `PowerShell`, `WebFetch`, `Agent`, `Skill` and tools added later did not. A `Monitor` command is classified exactly as the same Bash command, keyless and keyed (a keyless `Monitor: git push --force origin master` is stopped, a keyed one is held). A `PowerShell` command is read by the keyless seatbelt as the same Bash command, and keyed as the stricter of its old reading and Bash's reading with none of Bash's read-only lists applied (in PowerShell `type`, `cat`, `find` and `ls env:` read files or print the environment), so a `PowerShell` call is never read-only and `type ./production.env` is held as a secret read. An approval for one never releases the other, and it is bound to the call's `timeout`, `timeout_ms` and `run_in_background`. `Agent`, `WebFetch` and the other tools that only start work, fetch a page or schedule something are recorded, but the words in their prompt or URL never hold them. The same goes for the built-ins that only report, notify, structure output or render (`PushNotification`, `ReportFindings`, `StructuredOutput`, `Artifact`, `ArtifactCheck`, `ArtifactComments`, `ArtifactData`, `ListAgents`, `SubagentHandback`), and `Workflow`'s `script` is read as JavaScript, never as shell text; a tool Marrow does not know keeps the word check, and `RemoteTrigger` and `DesignSync` stay named. What these tools lead to is hooked on its own. Re-run `marrow-mcp setup` (or the installer) to refresh hooks written with the old matcher: `setup` recognises the hooks the installer writes, so running it over an installer-written settings file leaves exactly one Marrow entry per event, and a look-alike hook of your own is kept as it is.
- **More secret files are protected.** With no key, reading or copying any `<name>.env` file that exists (for example `prod.env`), `~/.marrow/fleet.env` or anything under `~/.marrow/credentials` is stopped like `.env` (S4). `git log -p`, `git whatchanged -p` and `git format-patch` of a secret path print its history, so they are stopped like `git show -- <path>`; `git log --oneline -- .env` and `git log -p -- src/app.ts` still pass. When the hook does not know the working directory, a relative `<name>.env` read (`cat prod.env`, `cat config/`) is stopped too; a search pattern such as `grep -rn "process.env" src` still passes. Templates (`.env.example`, `.env.sample`, `.env.template`) and plain words such as `process.env` in a search still pass. With a key, the check now reads the same list, so `~/.marrow/env` and `env.local`, `~/.claude.json`, the GitHub CLI `hosts.yml`, the 1Password config and the seatbelt salt count as secret reads there too.
- **A URL in the environment cannot redirect your key.** A key from `~/.marrow/env` (or `env.local`) is sent only to the default Marrow endpoint, to the endpoint that same file names with `MARROW_BASE_URL`, or to an exact origin it lists in the new `MARROW_ALLOWED_BASE_URLS`. A `MARROW_BASE_URL` that only the process environment names (a project's `.mcp.json` or settings `env` block, direnv, a devcontainer) no longer moves it: the default endpoint is used and one line on stderr names the source that was ignored, never the key or the URL. A key from the process environment keeps using the process-environment URL, as before. If you run a staging or self-hosted endpoint with a key in `~/.marrow/env`, add `MARROW_BASE_URL=` or `MARROW_ALLOWED_BASE_URLS=` to that file. See [Environment](#environment).
- **Ids in tool arguments are checked.** The ids and slugs that become part of an API path (workflow, instance, lease, comparison, handoff, decision, detected and template ids) must start with a letter or digit, use only letters, digits, `_`, `.` and `-`, be at most 256 characters and never contain `..`. Anything else is refused before any request is sent. Every id Marrow itself issues passes.
- **The SDK is pinned exactly.** `@getmarrow/mcp` depends on `@getmarrow/sdk` 3.7.67 with no version range.

Earlier behaviors are kept under [Seatbelt, outage holds and exact approvals](#seatbelt-outage-holds-and-exact-approvals), [Project files and Marrow's own tools](#project-files-and-marrows-own-tools), [Setup and key handling](#setup-and-key-handling) and [Hook behavior details](#hook-behavior-details). v3.9.99 brought approvals in the host prompt; see [Approvals in the Host Prompt](#approvals-in-the-host-prompt).

Earlier release notes moved from this README to [CHANGELOG.md](https://github.com/getmarrow/marrow-mcp/blob/master/CHANGELOG.md).

## Observed usage and calculated cost

Session-hook commands retain observed usage supplied on stdin for capture. Session totals remain cumulative and unpriced without a proven delta; this does not add transcript collection or manufacture missing model usage.

A connector tool call does not automatically expose the upstream chat model's usage. Native hooks capture only usage actually present in their event. Adapters can pass an observed request endpoint to `extractModelUsageFromUnknown`; native hooks accept explicit host configuration through `MARROW_MODEL_USAGE_ENDPOINT`. First-party billing is recognized only for HTTPS `api.openai.com` and `api.anthropic.com`, with no credentials or custom port. Set this only when it describes the requests whose responses the hook observes; do not label a proxy or mixed-provider stream as first-party. Endpoints and response content are never sent as usage evidence.

`MARROW_MODEL_USAGE_PRICING_DIMENSIONS` accepts a compact JSON object of dimensions actually established by the request configuration (for example tier, region and modality). Missing dimensions remain unknown. Observed service tier, Anthropic inference geography and single-TTL cache-creation counts take precedence. Mixed cache TTL writes remain unresolved. Set `MARROW_MODEL_USAGE_BILLING_MODE=subscription` only for subscription usage; displayed cost then means an API-equivalent estimate, not an invoice. Never use these settings to fill gaps by guessing.

Direct `marrow_model_usage` and Commit `model_usage` also accept billing host, token semantics, cache writes, response/event identity, occurrence time, pricing dimensions and explicit coverage/comparison metadata. Invalid supplied values fail validation; absent counts do not become observed zero. Stable IDs retain retry identity. Session totals and partial stream-start observations remain cumulative and unpriced without a proven delta. This hook does not assemble SSE streams or read transcripts. If cost is unavailable, inspect the returned reason and coverage: missing host/model/counts/variant evidence cannot be recovered from a successful tool call. No capture infers complete coverage, overhead or a causal baseline; baseline and net savings stay pending until those are proven.

## Governed Action Flow

With `MARROW_TOOL_PROFILE` unset, the default primary profile uses `marrow_agent_runtime` followed by `marrow_commit`. It exposes 17 tools; `marrow_auto` is available only after an explicit `core` or `full` selection and MCP restart. Primary status and lessons use `marrow_agent_status` and `marrow_fleet_lessons`.

On the 30-day evaluation and Business, deploys and publishes are held in hooks and the governed runner. Team and Free warn. If Marrow is unreachable, hooks hold high-risk actions and let routine actions run with a warning, and the governed runner stops protected actions it cannot verify. Over MCP, the agent gets the decision and is expected to follow it. Hooks run inside the agent host, so a host edited or bypassed by an agent or person is not held. Before deploys, merges, publishes, migrations, credential changes, financial operations, or customer-impacting work:

1. Call `marrow_agent_runtime` or `marrow_decision_brief`.
2. Stop when the returned decision is `block` or `review_required`; otherwise follow its prior lesson and proof contract.
3. Reuse a server-created runtime `decision_id` when the completion contract identifies it. Call `marrow_think` when decision creation is still required; explicitly selected core/full profiles can also use `marrow_auto`. Keep `marrow_agent_runtime.runtime_authorization.id` separate as the gate receipt for consequential work.
4. Perform the action only when its gate allows it. Codex, Grok, and Gemini use configured native hooks only after restart and host hook review. The governed wrapper remains an explicit bounded fallback: `npx @getmarrow/install run --agent <agent-id> -- -- <command>`.
5. Call `marrow_commit` with that `decision_id`, the outcome, gate receipt, and required proof.

`marrow_agent_runtime` returns `runtime_authorization` with the authoritative gate receipt. An ordinary or arbitrated runtime that creates a decision also returns its server-created `decision_id`; follow `completion_contract.decision_creation_required` and preserve the returned scope. When decision creation is required, call `marrow_think`; core/full can also use `marrow_auto`. Auto requests the existing expanded runtime response to check receipt identity, decision, action, agent, session, and expiry before reusing that decision; it does not obtain a second authorization fetch.

A `review_required`, `block`, or `outcome_observation_only` result never permits the action. If the action already occurred and its real result must be preserved, `marrow_commit` can ask runtime to bind observation delivery to the existing decision, action, session, and agent. The exact backend `outcome_observation_only` correlation is non-durable and non-authorizing, so MCP never sends it as a gate, arbitration, or owner-approval receipt. An accepted observation reports `committed: false`, `outcome_state: "observed_unverified"`, `authorization_granted: false`, and `trusted_learning_applied: false`, plus the backend's `exact_next_action`. This is durable delivery, so do not retry or spool the same observation. To promote it into trusted learning, obtain the named authorization and proof, then make an explicit new commit attempt using the exact observed payload. Never synthesize a decision, receipt, approval, or authorization.

`marrow_auto` returns an `operation_id`, phase, and resumable state. For a resumable pending phase, respect `retry_after_ms` and use the same operation ID, tenant, agent, session, action, type, surfaces, outcome, proof and receipt payload; auto never shortens the requested finite delay to fit its core budget. When the phase is `proof_required`, supply the requested measured evidence before retrying that operation. Stable phase idempotency keys preserve the original decision and outcome across retries; the backend remains authoritative for acceptance and conflicts. A four-second automatic write attempt ceiling leaves replay time inside the existing eight-second total budget. Long server delays remain pending without waiting beyond that budget. Malformed or unbounded Retry-After headers stop automatic continuation. Only committed:true confirms closure; pending is not evidence that a server write failed.

If a receipt expires while the same decision remains open, explicitly request normal runtime again with the original action, type, surfaces, agent and session. A fresh request key is required: replaying the old runtime key replays its old receipt. Verify the returned `runtime.decision_id` still matches the original, keep the new `runtime.runtime_authorization.id` as `gate_receipt_id`, and satisfy the current proof and approval contract. For a scope with an explicit target, use the `marrowAgentRuntime` library or `POST /v1/agent/runtime` preserving that target; the public MCP runtime schema does not expose target. Do not renew through post-action `auto_gate`: it only obtains observation truth. A fresh receipt never retrospectively authorizes an action taken without permission, and a changed scope is a different decision. Expired or used receipts may return an accepted `observed_unverified` result with `committed:false`; that is not closure.

For an ordinary hold (`completion_contract.owner_approval.mode: "ordinary_non_arbitrated"`), auto waits in `owner_approval_required` and reads the gate receipt's approval status as the server advises. Call auto again with the same operation ID after `retry_after_ms`; once the server has recorded an approval (see [Approvals in the Host Prompt](#approvals-in-the-host-prompt)), auto resumes on the same decision and gate receipt. A decline closes the decision as a gate denial when the action has not run, and a spent gate receipt is final: auto reports the operation as closed and never asks to run it again. A caller-written `proof.owner_approval` is never an approval: MCP strips it, and only an approval the server records counts. Unknown ordinary completion contracts remain stopped without automatically starting arbitration.

For arbitration, the account owner picks and approves one proposal through a one-tap link (see [Approvals in the Host Prompt](#approvals-in-the-host-prompt)); auto waits on the status read and commits with the `owner_approval_receipt_id` it returns. Otherwise call auto with the same operation ID, `arbitration_receipt_id`, and the server-issued `owner_approval_receipt_id`. Proof or chat text cannot substitute for this receipt. A CLI closed response reports `phase: "closed"`, `live_delivery.committed: true`, and `resumable: false`; the library result uses `committed: true`. An outcome record never changes a stopped action into an allowed action.

The CLI's lifecycle `receipt.queued: true` means its stable event is stored locally for later bounded delivery; `receipt.accepted: false` must not be read as server acceptance. Transient failures preserve the queued event and retry schedule for later bounded delivery, including after restart. Server retry guidance is respected. Authentication failures remain failed with explicit credential-repair guidance and are never auto-retried; conflicted events are server-owned evidence and are never replayed; every other dead letter self-heals through bounded automatic recovery during passive nudges. Process exit can interrupt the background nudge, leaving the event for a later run. `phase_timings_ms.total` measures core auto phases. `response_timings_ms` contains numeric `core`, `durable_enqueue`, and `full_response`; the last ends at response construction and excludes subsequent stdout drain and host processing. Canary `latency_ms` independently measures the MCP round trip, while `attempts` and `retry_wait_ms` describe measured outer tool retries, not internal database or network calls.

Example pre-action request:

```json
{
  "tool": "marrow_agent_runtime",
  "arguments": {
    "action": "deploy the production worker",
    "type": "deploy",
    "role": "deploy",
    "surfaces": ["repository", "deployment", "production"]
  }
}
```

Example closeout:

```json
{
  "tool": "marrow_think",
  "arguments": {
    "action": "deploy the production worker",
    "type": "process",
    "checkLoop": true
  }
}
```

```json
{
  "tool": "marrow_commit",
  "arguments": {
    "decision_id": "decision_id returned by marrow_think",
    "gate_receipt_id": "receipt id returned by marrow_agent_runtime",
    "success": true,
    "outcome": "Production deploy succeeded and smoke checks passed.",
    "proof": {
      "checks": ["tests passed", "secret scan passed", "production smoke passed"],
      "rollback_target": "previous release"
    }
  }
}
```

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.

When two or more agents disagree on the next action, call `marrow_arbitrate`
before either proposal executes. It uses the same `/v1/agent/runtime` control
plane and returns `selected`, `synthesized`, `review_required`, or `blocked`
with a durable tenant-scoped receipt explaining the policy, evidence, authority,
risk, and dissent behind the result.

```json
{
  "tool": "marrow_arbitrate",
  "arguments": {
    "objective": "Release the audited backend change safely",
    "ownerIntent": "Production deploys require independent audit proof",
    "proposals": [
      {
        "proposal_id": "deploy-now",
        "agent_id": "jarvis",
        "action": "Deploy the tested commit now",
        "risk_level": "high"
      },
      {
        "proposal_id": "audit-first",
        "agent_id": "barvis",
        "action": "Audit the exact commit, then release only if it passes"
      }
    ]
  }
}
```

Marrow resolves agent roles from the account rather than trusting caller claims.
Evidence references must be opaque identifiers; do not send raw prompts, logs,
URLs, paths, credentials, or customer content. The arbitration response owns the
`decision_id`, gate receipt, and arbitration receipt used at commit. A
`review_required` result needs the account owner's approval of one proposal
through a one-tap link; pass the resulting short-lived, single-use
`owner_approval_receipt_id` to `marrow_commit`. An agent cannot authorize itself
with a proof field.

Use `marrow_coordinate` when parallel agents could edit the same file, service,
deployment, or workflow. An acquired lease returns a one-time release capability.
Child agents can then create a compact proof packet containing only a bounded
summary and opaque durable evidence references. Complete is accepted only when
the linked outcome and required proof are actually closed.

Use `marrow_replay_compare` after two model or workflow variants have each
recorded an outcome. It compares that existing evidence under one tenant task;
it does not run models, retain prompts, or infer a winner from labels.

## Approvals in the Host Prompt

When Marrow holds an action for approval (`review_required`), the person approves it where they already work, and nobody has to log in. The hooks record the answer with Marrow; the agent never reports or claims an approval. The Marrow dashboard shows receipts and history; it is not where approvals happen.

| Host and session | What the person does |
| --- | --- |
| Claude Code (`default`, `acceptEdits`; `auto` from v2.1.257) | Answers Claude Code's own permission dialog. In `plan`, `dontAsk` and `bypassPermissions` modes they switch to the default mode and retry. |
| Cursor shell and MCP calls, local session | Answers Cursor's own approval prompt |
| Codex and Gemini CLI, local interactive session with their own approval prompt on | Types the reply the hook showed them only, such as `marrow approve CODE` |
| MCP clients with elicitation (`marrow_auto`) | Answers the client's own dialog (client-attested) |
| Codex TUI before its prompt hook, Cline and Windsurf in the editor | Uses the host's own approval step, if they have it on: the hook answers neutrally, never with an explicit allow, and Marrow records the result as not observed by Marrow |
| Grok, Gemini without its prompt hook, Cursor at `preToolUse` and Cursor's other tools, Codex with its approval prompt off, Gemini CLI in any mode but `default` (`--yolo`, `-y`, `--approval-mode`, or `general.defaultApprovalMode` in its system, user or project `settings.json`, or shell commands allowed without asking, or a settings file it cannot read), or no evidence of a person | No typed reply and no code (the agent could answer one itself); the action waits quietly for the owner |
| Unattended runs (headless Claude Code, `codex exec`, `gemini -p`, Cursor cloud) | Nothing in the run: the action waits quietly and the agent carries on |
| Arbitration review | The account owner picks one proposal through a one-tap link |

- **The owner's email link is the exception.** It is sent only for a category the owner locked, for the owner's own standing decline once the operator asks to reverse it (retrying, or `request_owner_link: true` on `marrow_auto`), for an unattended run when the owner turned on unattended pings, or for an arbitration review when the operator asks. An ordinary hold never emails anyone.
- **Codex with its approval prompt off holds quietly** (`--yolo`, `-a never`, `--full-auto`, `--approve-for-me`, `-s danger-full-access`, the same through `-c` or `$CODEX_HOME` config). Not read: Codex settings from managed or project configs. Not detected: Cline's auto-approve and Windsurf's Turbo mode (they live in editor state with no reliable signal). With those on, the host runs the action without asking; Marrow records it as not observed by Marrow, never as the person's answer. Owner-locked categories still hold.
- **Held actions at the next session.** At the first prompt of an interactive session the person sees "N held actions are waiting for you", with the action type and agent only, and approves one by retrying it there.
- **One approval, one run.** Of two identical calls on one approval, one runs and the other is denied.
- **The exact action, never its text.** Hook-classified calls send `normalized_action`: the tool kind and name, program names (and edit paths, with the home directory as `~`) and a SHA-256 of a normalized form. Event and correlation ids come from that normalized form, never from the raw command, input or prompt. Credentials are replaced where they stood, data a command reads is hashed so `SELECT` and `DROP` are different actions, and anything else that may be secret is withheld. An action that carried a credential or a withheld value is marked `truncated: true`: Marrow never learns a reusable approval for it, and the answer to a held one resumes only that exact action (see How approvals bind). The verdict, the hold and the prompt are the same as without the mark. A credential value ends only at whitespace, a quote, or a separator followed by another `name=` field, so actions that differ after it stay different actions and no part of a password with `#`, `,`, `&` or `;` in it stays in the hashed form. A password given as a plain argument to an unknown program (`./deploy.sh hunter2`) cannot be recognized; it stays only inside the hash.
- **How approvals bind.** Marrow binds an owner approval to the exact action that was held. The client hides possible secrets before sending an action; when it hides anything, it adds `exact_hmac`, a keyed hash of the exact action made with this machine's private device key (`~/.marrow/seatbelt/salt`, created once, mode 0600, never sent). An approval resumes only for the same exact action; a retry that differs only in a certain credential (for example a new one-time password) still resumes. If the device key is missing or unsafe, Marrow keys the same hashes with a fallback key derived from the secret of its local approval state (`~/.marrow/host-approvals/state.json`, mode 0600, never sent; a hook with a Marrow key makes it when missing, a machine with no key never does), so a held action still resumes; only when that state is unusable too does Marrow print one note (it points to the installer's `npx -y @getmarrow/install@latest doctor`) and ask again instead of resuming.
- **Upgrade note:** the action hash version changed (v3). Learned approvals and known workflows from earlier versions ask once more, then are learned again.
- **Slow and down are different.** Codex and the 5-second hosts answer within their hook limits (Codex within 2 s of starting, and on Linux about 2.6 s from launch when npx starts in under about 1.6 s). A slow Marrow keeps a holdable action held. A real outage (Marrow never answered) holds a shell command only when it pushes with git, publishes a package or image, deploys, deletes through an API or a cloud CLI, or hits a seatbelt category (S0-S5, S-x); words in a commit message, a path, echo text or a comment never hold it, and neither does asking a listed tool for its help (`git push --help`). Other high-risk tools (for example MCP tools) are held, routine actions keep running with a warning, and a waiting hold or owner-locked category stays held. When Marrow answers but cannot issue a permit, a high-risk action is denied, never allowed as offline.
- **Honest labels and texts.** Without a dialog or typed-reply marker an approval is recorded as the host's allow rule. Text a person reads names no receipt ids. A refused action names a plain fix.

## Passive Use

`npx -y --package=@getmarrow/mcp@latest marrow-mcp setup` configures supported prompt, pre-action, tool-result, permission-marker, and session-stop hooks. Configuration, public hook argv, and API-key-authenticated callbacks are client self-reports. They preserve raw lifecycle activity but do not prove that the host invoked a hook or certify passive control.

### Capability and coverage contract

| Integration mode | Coverage Marrow can claim |
| --- | --- |
| MCP tools-only | On demand; covers only explicit MCP tool calls |
| Configured native hooks | Cooperative telemetry/context only; activity is client-self-reported and coverage remains unverified |
| `createPassiveRuntime().install()` | Only the owned Node process, and only while that runtime is installed and running |
| Governed runner | Only the command launched through the wrapper |
| Custom host | Requires a bounded event adapter; covers only the lifecycle events whose receipts Marrow observes |

This contract is model-neutral. A model name, host header, API key, public hook entrypoint, config file, installed hook entry, successful MCP handshake, or client-self-reported lifecycle callback does not certify passive coverage or enforcement. An unknown MCP host therefore gets the generic `mcp-client` identity and the same on-demand tools, schemas, and API semantics as a named host. Public lifecycle callbacks and hook activity are client-self-reported and cannot verify or certify passive coverage; independent authority is required.

When invoked by a supported host, the configured hooks send compact classifications and lifecycle receipts. They do not need raw prompts, completions, command output, tool output, or credentials. A completed tool or session does not automatically become a successful business outcome; explicit success/failure closure is required.

Setup installs distinct Claude Code and Grok hook entrypoints. The public entrypoint supplies only a client-reported display label; it is not host provenance. Hook event JSON cannot select the lifecycle harness or agent. Agent identity comes only from owner configuration when present, otherwise the request omits it so the authenticated service can derive the credential-bound identity. Every hook lifecycle event is marked `source: client_self_reported` and omits `capability_level: native_hooks`, adapter certification, configuration fingerprints, expected hooks, and observed-hook certification fields. Legacy, unknown, and custom entrypoints stay generic.

Claude Code hooks may cooperatively request guidance and apply the harness permission response, but that does not certify always-on control. Codex native hooks map a block to the supported synchronous deny response; a review-required hold follows [Approvals in the Host Prompt](#approvals-in-the-host-prompt). Cursor and Composer use the same Cursor-native pre-action, result, failure, and stop adapters. Cline uses native pre-action and post-tool adapters plus TaskCancel closeout; its documented TaskComplete hook remains coming soon and is not claimed as observed coverage. Windsurf uses native pre-action, success-result, and response-closeout adapters and requires Restricted Mode to be off. Gemini CLI uses native BeforeTool, AfterTool, and AfterAgent adapters; AfterAgent is the deterministic per-turn closeout and no SessionEnd delivery is claimed. Grok uses global native PreToolUse, PostToolUse/PostToolUseFailure, and one nonblocking Stop closeout; Marrow's generated Grok file contains no duplicate SessionEnd closeout. These native paths keep MCP on demand and require restart plus host hook review. Where a native contract cannot make a generic ask enforceable (Cursor at `preToolUse`, Gemini, and Grok), protected review-required or unavailable-control work is denied and waits for approval; Cline and Windsurf hand an ordinary hold to the host's own prompt when a person is evidenced. All activity remains client-self-reported rather than verified host telemetry, and the governed wrapper remains an explicit bounded fallback. Unknown and custom hosts remain on demand unless they provide a bounded event adapter, whose activity is still not certification without an independent authority.

Transient lifecycle receipts use a bounded owner-only spool and are retried with stable event IDs. Dead letters self-heal: recoverable failures are retried automatically with bounded attempts and cooldown during passive nudges, and conflicted events are recognized as server-owned evidence that is never replayed. Operators can inspect and drain it without exposing event content:

```bash
npx -y --package=@getmarrow/mcp@latest marrow-mcp spool-status
npx -y --package=@getmarrow/mcp@latest marrow-mcp drain-spool
```

The output contains only state, bounded pending/failed/recoverable/server-owned/recovery-exhausted counts, oldest receipt timestamps, capacity, and an exact fix. Only authentication-class dead letters require the operator: restore the credential binding, then drain. A drain applies only to the active credential-and-agent namespace: isolated legacy namespace debt is reported separately and never changes a successful active-namespace drain into a failure. Legacy files are never replayed, merged, deleted, or attributed to the active identity. Restore the exact original identity to drain one, or preserve the selected file unchanged in a separate owner-only quarantine directory when that identity is unavailable. Authentication failures in the active namespace remain explicit durable failures with repair guidance; recovery-exhausted events stop cycling instead of retrying indefinitely, and `drain-spool` remains an explicit operator tool rather than a user requirement.

Check the installed runtime:

```text
marrow_agent_status
```

Status diagnostics distinguish missing keys, invalid keys, wrong bound-agent identity, network limits, missing hooks, and incomplete proof. They include an exact repair action without exposing secrets.

## Primary MCP Tools

| Tool | Purpose |
| --- | --- |
| `marrow_agent_runtime` | One-call pre-action status, policy gate, relevant lessons, proof requirements, and exact next action |
| `marrow_arbitrate` | Resolve conflicting agent proposals before execution and return an explainable arbitration receipt |
| `marrow_coordinate` | Acquire/release resource leases and exchange compact child proof packets across tenant agents |
| `marrow_replay_compare` | Compare two existing proof-backed outcomes without executing a model |
| `marrow_decision_brief` | Compact operating brief for meaningful work |
| `marrow_think` | Record intent and retrieve relevant governance intelligence |
| `marrow_commit` | Close an action with outcome, receipt, and proof |
| `marrow_workflow_gate` | Evaluate a workflow action against policy |
| `marrow_completion_contracts` | List proof contracts for consequential action types |
| `marrow_evaluate_completion_contract` | Check whether evidence is sufficient to call work complete |
| `marrow_agent_status` | Verify capture, identity, outcome coverage, and hook health |
| `marrow_value_report` | Return account/agent value evidence without requiring a dashboard |
| `marrow_buyer_proof` | Return owner-ready governance and reliability evidence |
| `marrow_governance_timeline` | Inspect decisions, gates, proof packs, and outcomes over time |
| `marrow_decision_trace` | Explain one governed decision and return its owner-readable intervention receipt |
| `marrow_fleet_lessons` | Retrieve proven lessons authorized for the current account or agent |
| `marrow_model_usage` | Record compact token, cost, and latency counts when the harness exposes them |

The package also exposes key management, fleet handoff, deployment history, adaptive policy, context/lesson, query, and workflow-example tools. See the [complete source-of-truth documentation](https://getmarrow.ai/docs/) for every tool and field.

## Context and Workflow Examples

The stable `marrow_*memory*` tools manage authorized context and prior lessons used by governance decisions. They are advanced supporting APIs, not a separate product category.

The template tools expose 24 configurable workflow examples. They are starting points for policy design, not customer case studies, regulatory validation, legal advice, or proof of production use in each listed industry.

## Trust and Data Boundaries

- Private account, fleet, workflow, proof, and agent data remains tenant-scoped by default.
- Agent-bound keys can be restricted to an allowed identity and permission set.
- Sanitized aggregate contribution is optional and never means sharing raw prompts, code, secrets, proof packs, account identifiers, agent identifiers, or customer identities.
- Existing API keys are never returned after creation; key material should be supplied through the client's secret store.
- Marrow returns guidance and policy data. Agents must not execute returned text as shell input.
- An agent cannot approve itself: a caller-written `proof.owner_approval` is stripped, chat or proof text is never an approval receipt, and only an approval the server records counts.
- Approvals given in your tool's prompt are reported by software on your machine. An agent running as your user could fake one for a single held action. For actions that must have your own approval, lock that category (verified only); then only your one-tap link or the dashboard counts, and Marrow never learns from or reuses tool-reported approvals across agents.
- Never connect an agent to the email inbox that receives Marrow approval mail. An agent that can read that inbox can open the one-tap link itself.

See the [Trust Center](https://getmarrow.ai/trust/) for implemented controls, current limits, and roadmap status.

## Environment

| Variable | Required | Purpose |
| --- | --- | --- |
| `MARROW_API_KEY` | Yes | Account or agent-bound API key |
| `MARROW_BASE_URL` | No | API base override. When the key comes from `~/.marrow/env`, a `MARROW_BASE_URL` set only in the process environment is ignored (with one notice) unless the same file names it or lists its origin in `MARROW_ALLOWED_BASE_URLS`; a key from the process environment keeps the process environment URL |
| `MARROW_ALLOWED_BASE_URLS` | No | Read from `~/.marrow/env` only. Comma-separated exact origins (`https://host[:port]`, or `http://localhost`, `http://127.0.0.1`, `http://[::1]`) that a key stored in that file may be sent to when the process environment names them. No wildcards, paths, credentials, queries or fragments |
| `MARROW_AGENT_ID` | No | Bound agent identity for MCP tools |
| `MARROW_FLEET_AGENT_ID` | No | Fleet agent identity used by passive setup |
| `MARROW_HOOK_BACKGROUND_NUDGE` | No | Default on: PostToolUse only spools the event and starts one detached background process that delivers it, so the hook adds almost no latency. Set to `false` to keep bounded inline delivery (750 ms) inside the hook. Never runs when `MARROW_AUTO_HOOK=false` or local control is disabled |
| `MARROW_WRITE_RECONCILIATION_BUDGET_MS` | No | Default `15000`, range 1000-60000: total time a pending think or commit may wait, resuming the same idempotency key and request hash after the server's `retry_after_ms` or `Retry-After`. When the next wait does not fit, the call returns the resumable pending receipt and never reports success |

## Documentation

- [Source-of-truth docs](https://getmarrow.ai/docs/)
- [Trust Center](https://getmarrow.ai/trust/)
- [Status](https://getmarrow.ai/status/)
- [GitHub](https://github.com/getmarrow/marrow-mcp)

## License

MIT

## Related Packages

- [@getmarrow/install](https://www.npmjs.com/package/@getmarrow/install) - default installer, self-test, governed runner, and operator TUI
- [@getmarrow/sdk](https://www.npmjs.com/package/@getmarrow/sdk) - Node.js and TypeScript integration for owned agent runtimes

Codex native usage capture accepts a bound `thread/tokenUsage/updated` event or a bounded current transcript with the supported `0.157.1` schema. It records only the latest observed model call when matching model/turn counters prove that call's delta; it does not reconstruct whole turns or history. Repeated hooks use a compact private checkpoint and stable usage ID. Unknown versions, missing context, counter resets, unsafe/oversized files, and unproven subagent bindings abstain. Cached input and reasoning output remain subsets. Billing endpoint, tier, region, and subscription mode require explicit capture configuration; native model names do not establish a price or savings baseline.

TDQS

B3.1/5.0

Scored across 56 tools

Disambiguation2/5

Many tools have overlapping purposes. For example, marrow_orient, marrow_think, marrow_decision_brief, marrow_agent_runtime, and marrow_workflow_gate all provide pre-action guidance and risk assessment. Similarly, multiple tools report value/proof (marrow_value_report, marrow_buyer_proof, marrow_agent_performance, marrow_dashboard), making it challenging to select the correct tool.

Naming Consistency3/5

All tools share the 'marrow_' prefix, and many follow a verb_noun pattern (e.g., list_memories, get_memory, update_memory). However, deviations like marrow_orient, marrow_think, marrow_auto, and marrow_buyer_proof break the pattern, creating noticeable inconsistency.

Tool Count2/5

With 56 tools, the server is heavily over-scoped. The high count leads to redundancy and visual clutter, making it difficult for agents to discover and choose tools efficiently. A more focused set of 15-20 tools would better serve the platform's purpose.

Completeness4/5

The tool set provides broad lifecycle coverage across memories (create/read/update/delete/supersede/export/import), keys (create/rotate/revoke/list/get), handoffs (create/update/status), workflows (register/start/advance/list/get/instances), and policies (create/update/assign/resolve). Minor gaps like workflow cancellation or explicit key update are present but workable.

Maintenance

ActivityActive
ResponsivenessNo issues