# https://github.com/mentiora-ai/loom Project Manual

Generated at: 2026-06-22 06:40:03 UTC

## Table of Contents

- [Project Overview & Architecture](#page-overview)
- [Core Action Surface, Determinism & Replay](#page-actions)
- [MCP Server, SDKs & Schema Validation](#page-integrations)
- [Security, Operations & Failure Modes](#page-operations)

<a id='page-overview'></a>

## Project Overview & Architecture

### Related Pages

Related topics: [Core Action Surface, Determinism & Replay](#page-actions), [MCP Server, SDKs & Schema Validation](#page-integrations), [Security, Operations & Failure Modes](#page-operations)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [loom-core/src/lib.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/lib.rs)
- [loom-core/src/manifest_writer/manifest_writer.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/manifest_writer/manifest_writer.rs)
- [loom-core/src/replay_engine/mod.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/replay_engine/mod.rs)
- [loom-core/src/error_types/error_types.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/error_types/error_types.rs)
- [loom-core/src/determinism_harness/mod.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/determinism_harness/mod.rs)
- [loom-core/src/observability/interface_tests.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/observability/interface_tests.rs)
- [loom-core/src/exporters/exporters.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/exporters/exporters.rs)
- [loom-core/src/vault/impl_local.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/vault/impl_local.rs)
- [loom-core/src/budget_enforcer/mod.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/budget_enforcer/mod.rs)
- [loom-core/src/receipt_builder/interface_tests.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/receipt_builder/interface_tests.rs)
- [loom-mcp/src/mcp_dispatcher/mcp_dispatcher.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/mcp_dispatcher/mcp_dispatcher.rs)
- [loom-mcp/src/tool_cache/interface_tests.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/tool_cache/interface_tests.rs)
- [mocks/README.md](https://github.com/mentiora-ai/loom/blob/main/mocks/README.md)
- [typescript-sdk/package.json](https://github.com/mentiora-ai/loom/blob/main/typescript-sdk/package.json)
</details>

# Project Overview & Architecture

## Purpose and Scope

Loom is a browser-automation and recording platform originally extracted from Mentiora's code-pipeline project. Its public pre-1.0 release is tagged as `0.9.0` (2026-05-04), with the current patch at `0.11.1` (2026-06-11). The system is designed around three concerns: deterministic browser capture, a tamper-evident per-session manifest, and an MCP (Model Context Protocol) surface for agentic consumers.

The repository is organized as a Rust workspace plus a TypeScript SDK. The Rust workspace contains multiple purpose-specific crates, and `loom-core` is the central library that re-exports the major subsystems:

> `pub mod error_types; pub mod exporters; pub mod importers; pub mod manifest_writer; pub mod observability; pub mod profile_registry; pub mod receipt_builder; pub mod replay_engine; pub mod session_manager; pub mod session_scope; pub mod startup_manager; pub mod vault;`
> Source: [loom-core/src/lib.rs:1-15]()

The system aims to deliver two headline properties: (a) a hash-chained `manifest.jsonl` whose replay equality is a cross-run "did anything change?" oracle (NFR-DET-01), and (b) an MCP server that exposes every schema-driven RPC method as a `loom.*` tool, with an implicit session auto-created on first call.

## High-Level Architecture

The runtime is split into several layers that share no implicit state. The MCP layer receives tool calls from clients; an RPC client forwards them to a daemon; the daemon drives a Chromium session via CDP and writes a JSONL manifest through `loom-core`. A `loom-shims` boundary encapsulates platform IPC.

```mermaid
flowchart LR
  subgraph Client["Agent / MCP Client"]
    TS[TypeScript SDK<br/>typescript-sdk/]
  end
  subgraph MCP["loom-mcp (stdio)"]
    Dispatch[McpDispatcher]
    Cache[ToolCache<br/>loom.* prefix]
  end
  subgraph Daemon["loom-host daemon"]
    RPC[RpcClient]
    Shim[loom-shims IPC]
    CB[Chromium CDP]
  end
  subgraph Core["loom-core"]
    SM[session_manager]
    RB[receipt_builder]
    MW[manifest_writer]
    RE[replay_engine]
    DH[determinism_harness]
    V[vault]
    EX[exporters]
    OBS[observability]
  end
  TS -->|JSON-RPC over stdio| Dispatch
  Dispatch --> Cache
  Dispatch --> RPC --> Shim --> CB
  CB -->|DOM/SS/Network| RB
  RB --> MW
  MW -->|manifest.jsonl + CAS blobs| Core
  RE --> MW
  DH -. validates .-> RE
  V --> RB
  EX --> MW
  OBS -. spans/logs .-> Dispatch
```

The dispatcher is the only entry point that holds the implicit-session lifecycle and the cancellation token used for clean shutdown:

> `/// Cancelled when shutdown is requested — by the MCP `shutdown` method or the process signal task. … actually terminates the server (its `AtomicBool` predecessor was stored into but never read by anything).`
> Source: [loom-mcp/src/mcp_dispatcher/mcp_dispatcher.rs:25-32]()

## Core Subsystems

### Manifest Writer and Hash Chain

`ManifestEntry` is a tagged enum serialized via JCS, where every line is hash-chained to the previous one. The header carries `session_id`, `started_at_ms`, `prev_hash: Option<String>`, optional `budgets`, optional `capture_policy`, and a determinism `seed` so replay reconstructs the same in-Chromium `Math.random`/`Date.now` values rather than silently defaulting to `Seed(0)`:

> `/// settle-capture (D3): the determinism seed this session was created with. Recorded so replay reconstructs the SAME seed … which diverged the in-Chromium Math.random/Date.now for any --seed N session.`
> Source: [loom-core/src/manifest_writer/manifest_writer.rs:38-44]()

### Receipt Builder and Error Codes

The receipt layer is the per-action outcome surface. `ReceiptCode` is a stable 22-code enum serialized as snake_case; `WebActionCompleted` is the explicit OK sentinel. Click/hide-miss/timeout scenarios each carry their own discriminator so callers can branch without parsing strings:

> `/// Stable 22-code receipt status/error discriminator. … `WebActionCompleted` is the sentinel used by OK receipts across all action tiers.`
> Source: [loom-core/src/error_types/error_types.rs:18-25]()

Receipt shapes differ per tier: click receipts carry hash fields and no inline blob, while navigate receipts carry blob refs because the DOM snapshot is too large to inline. This split is regression-pinned by interface tests:

> `click_receipt_has_hash_fields_not_blob_fields` and `navigate_receipt_has_blob_fields_not_hash_fields`
> Source: [loom-core/src/receipt_builder/interface_tests.rs:20-35]()

### Replay Engine and Determinism Harness

The replay engine consumes a closed manifest and reissues actions against a fresh Chromium, refusing to proceed when source artifacts are dirty or when non-deterministic captures are detected. The harness sits above it and is the public entry point used by CI to assert "two independent runs produce identical manifests":

> `// `replay_engine` — see crate root.` … `pub use impl_replay::{collect_content_refs, non_deterministic_refusal, unclean_source_refusal};`
> Source: [loom-core/src/replay_engine/mod.rs:1-5]()

Cross-run equality (`field_diffs=0` between two fresh recordings of the same actions) shipped in `0.10.1` (2026-06-10), turning the hash chain into a true cross-run oracle rather than just a self-replay check.

### Vault and Budget Enforcer

The vault holds session credentials and refuses any grant unless the operator has acknowledged the threat model document. OAuth-style credentials are accepted; API-key credentials are explicitly rejected. The grant options are validated against a required set of headings in `vault_threat_model.md`:

> `grant_rejects_api_key_type` — `err.code == LoomErrorCode::VaultRejection`; context `vault_credential_type_unsupported`.
> Source: [loom-core/src/vault/impl_local.rs:118-134]()

`budget_enforcer` complements this by capping per-session resources (wall-clock, actions, bytes) so a runaway page cannot outlive its intended kill deadline while still appearing budgeted:

> `// budget_enforcer — see crate root.`
> Source: [loom-core/src/budget_enforcer/mod.rs:1-5]()

### Observability and Exporters

Observability provides span lifecycle helpers, a redaction layer (so secret-tagged `LogField` values are never written), and fail-open metric counters. The interface test `span_start_returns_handle_carrying_supplied_name` pins the basic handle semantics:

> `Observability::new(PathBuf::from("/tmp/loom-test/loom.log"), false)` and `obs.span_start("session.create", vec![])` returning a handle whose `.name` matches.
> Source: [loom-core/src/observability/interface_tests.rs:14-25]()

`exporters` reads the WAL plus content-store blobs and produces JSON, gzipped tarball, or HAR 1.2 artifacts:

> `// `export_json`: JSON manifest with `manifest` + `content_blob_index` keys.`
> Source: [loom-core/src/exporters/exporters.rs:14-25]()

## MCP Surface and Tool Cache

The MCP server speaks `protocol_version: "2024-11-05"` over stdio. Every schema-driven RPC method is exposed as a tool whose name is the method prefixed with `loom.` — for example, `action.web.click` becomes `loom.action.web.click`. The cache uses a single constructor (`tool_from_method`) and the MCP wire format requires `inputSchema` (camelCase), which the `Tool` struct serializes correctly:

> `assert!(s.contains(""inputSchema""), "got {s}");`
> Source: [loom-mcp/src/tool_cache/interface_tests.rs:50-66]()

The dispatcher keeps an `implicit_session` that is lazily created on the first tool call, since most MCP clients never call a session-creation RPC first. A `recreated_count` atomic tracks transparent re-creations triggered by eviction, so determinism-pinning clients can detect, rather than blindly trust, self-healing:

> `/// Process-local observability surfaced by `loom.session.info` so determinism-pinning clients can DETECT a transparent recreation rather than trust it; it is never part of the manifest/hash chain, and resets to 0 on MCP-process restart.`
> Source: [loom-mcp/src/mcp_dispatcher/mcp_dispatcher.rs:55-64]()

## Release and Failure-Mode Highlights

The community context shows several recurring failure surfaces that have driven recent releases:

- **Schema validation regressions** (v0.11.1): the daemon validated per-method schemas from files that `loom postinstall` never shipped, producing misattributed `schema_violation` errors on documented `until`/`timeout_ms` args.
- **MCP screenshot delivery** (v0.9.9): captured PNGs were double-encoded as `CBOR{data:base64}` envelopes with no MCP image content type; never reached consumers as renderable bytes.
- **Linux enablement** (v0.9.2): `loom doctor` reported `chromium binary not found` after `postinstall`, and every `web.*` action surfaced as `surface_trap` because the shim's IPC socket raced Tokio's I/O driver on fd 3.
- **Scheduled e2e real-world runs** (issue #179): continue to fail intermittently on `ubuntu-latest` and `macos-latest`; artifacts under `real-world-results-ubuntu-latest` carry the daemon log and per-site results.

The TypeScript SDK (`typescript-sdk/package.json`) targets Node with TS 5.7, uses `tsx` for tests, and exposes the same `loom.*` tool surface for JS/TS agent hosts:

> `"test": "node --import tsx/esm --test 'tests/**/*.ts'"`, `"keywords": ["browser", "automation", "loom"]`
> Source: [typescript-sdk/package.json:7-12, 19-23]()

Mocks are co-located with their owning crates and gated by `#[cfg(any(test, feature = "mock"))]`, which avoids a synthetic crate that would have to depend on every system simultaneously:

> `loom-core = { workspace = true, features = ["mock"] }`
> Source: [mocks/README.md:6-12]()

## See Also

- Determinism Harness & Cross-Run Hash Equality
- Manifest Writer and Hash-Chained WAL
- MCP Dispatcher and Tool Cache
- Vault Threat Model and Credential Lifecycle
- Receipt Builder and Error Type Reference

---

<a id='page-actions'></a>

## Core Action Surface, Determinism & Replay

### Related Pages

Related topics: [Project Overview & Architecture](#page-overview), [MCP Server, SDKs & Schema Validation](#page-integrations)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [loom-core/src/lib.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/lib.rs)
- [loom-core/src/core_api_facade/core_api_facade.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/core_api_facade/core_api_facade.rs)
- [loom-core/src/determinism_harness/determinism_harness.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/determinism_harness/determinism_harness.rs)
- [loom-core/src/determinism_harness/mod.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/determinism_harness/mod.rs)
- [loom-core/src/replay_engine/mod.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/replay_engine/mod.rs)
- [loom-core/src/replay_engine/impl_replay.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/replay_engine/impl_replay.rs)
- [loom-core/src/session_manager/session_manager.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/session_manager/session_manager.rs)
- [loom-core/src/receipt_builder/receipt_builder.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/receipt_builder/receipt_builder.rs)
- [loom-core/src/receipt_builder/capture_policy.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/receipt_builder/capture_policy.rs)
- [loom-core/src/receipt_builder/impl_capture.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/receipt_builder/impl_capture.rs)
- [loom-core/src/error_types/error_types.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/error_types/error_types.rs)
- [loom-mcp/src/tool_cache/interface_tests.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/tool_cache/interface_tests.rs)
- [loom-mcp/src/mcp_dispatcher/mcp_dispatcher.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/mcp_dispatcher/mcp_dispatcher.rs)
</details>

# Core Action Surface, Determinism & Replay

Loom is a browser-automation daemon whose public contract is the `web.*` action surface exposed over RPC and MCP, paired with a deterministic recording and replay pipeline. The action surface, the determinism harness, and the replay engine form a single coherent subsystem: every recorded action is hash-stamped under a virtual clock and seeded RNG, and every replay consults that hash chain to detect drift. This page documents how those three layers fit together.

## Architecture at a Glance

`loom-core` is the only crate that owns execution semantics. `CoreApiFacade` is the single entry point and the only type that escapes the crate; `loom-host` and `loom-rpc` interact with the daemon exclusively through it. Source: [loom-core/src/core_api_facade/core_api_facade.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/core_api_facade/core_api_facade.rs). The facade holds `Arc`s of nine internal modules, including `SessionManager`, `DeterminismHarness`, `ReplayEngine`, `ManifestWriter`, `BudgetEnforcer`, `ContentStore`, `Vault`, `StartupManager`, and `Observability`. Source: [loom-core/src/lib.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/lib.rs).

```mermaid
flowchart LR
    MCP["MCP Dispatcher<br/>(loom.web.* tools)"] --> RPC["loom-rpc"]
    RPC --> Facade["CoreApiFacade"]
    Facade --> SM["SessionManager<br/>(per-session task)"]
    SM --> DH["DeterminismHarness<br/>(vclock + RNG + JCS)"]
    SM --> TAP["SideEffectTape"]
    TAP --> RE["ReplayEngine"]
    SM --> RB["ReceiptBuilder"]
    RB --> MW["ManifestWriter<br/>(hash-chained WAL)"]
    Facade --> OM["Observability<br/>(OTel + redaction)"]
```

## The Core Action Surface

The action surface is the set of `web.*` methods that agents and tests call. Each method maps to a `RpcMethodSchema` whose doc-comment becomes the MCP tool description, and whose name is transformed into a `loom.`-prefixed MCP identifier. Source: [loom-mcp/src/tool_cache/interface_tests.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/tool_cache/interface_tests.rs). The `TOOL_NAME_PREFIX` is fixed to `loom.`, and the helper `rpc_to_mcp_name` enforces it. Examples include `web.click`, `web.navigate`, `web.screenshot`, `web.wait_for`, `web.set_input_files`, and `web.evaluate`. The MCP dispatcher enforces protocol version `2024-11-05` and dispatches `tools/list`, `tools/call`, `resources/list`, `resources/read`, and `prompts/list`. Source: [loom-mcp/src/mcp_dispatcher/mcp_dispatcher.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/mcp_dispatcher/mcp_dispatcher.rs).

Each action produces a receipt. The stable outcome discriminator is `ReceiptCode`, a 22-variant enum whose wire form is snake_case. Source: [loom-core/src/error_types/error_types.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/error_types/error_types.rs). The sentinel for a successful action is `WebActionCompleted`; surface errors include `WebNavigationFailed`, `WebSelectorNotFound`, `WebEvaluateThrew`, `WebActionTimeout`, and `ElementNotHitTestable` (a SemVer-minor addition emitted when `DOM.querySelector` succeeds but the element has no usable hit-test geometry). Adding a variant is SemVer-minor; removing one is major.

## Determinism Harness

`DeterminismHarness` is the single home for all determinism mechanisms. Source: [loom-core/src/determinism_harness/determinism_harness.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/determinism_harness/determinism_harness.rs). It exposes five mechanisms:

1. **Virtual clock** — `clock_now` returns a monotonically advancing u64 of nanoseconds since session start. Wall-clock reads are banned during normal operation; the `--no-virtual-clock` flag switches it to pass-through mode.
2. **Seeded RNG** — `rng_next` consumes from a ChaCha20 stream seeded at session creation (`rand_chacha::ChaCha20Rng`), satisfying the BC HARD #5 determinism invariant.
3. **JCS canonicalization** — `canonicalize` uses `serde_jcs` (RFC 8785) and is the ONLY canonicalizer in `loom-core`. `clippy::disallowed_methods` forbids direct `serde_json::to_string` in manifest/receipt paths.
4. **Side-effect tape** — every clock read, RNG draw, network response, and blob ref is recorded as a `TapeFrame` on the `SideEffectTape`.
5. **Replay-mode vtable swap** — `install_replay_mode(tape)` swaps the host-fn vtable so that `clock_now`, `rng_next`, and `net_request` resolve against the tape, not live sources.

The harness is constructed once at session creation and lives inside the per-session tokio task owned by `SessionManager`. Source: [loom-core/src/session_manager/session_manager.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/session_manager/session_manager.rs).

## Replay Engine

The replay engine consumes a recorded manifest and re-executes its actions, comparing each receipt against the stored one. Source: [loom-core/src/replay_engine/mod.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/replay_engine/mod.rs). It refuses to reproduce certain traces before execution even starts:

- **Aborted sessions** — a session that ended via `abort()` is never replayed, because its trace is abandoned, not abandoned-and-recoverable. Source: [loom-core/src/replay_engine/impl_replay.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/replay_engine/impl_replay.rs).
- **Non-deterministic sources** — recordings taken with `--no-determinism` (real clock, unseeded RNG) return a typed `NotReplayable` error rather than degrading to a request-shape error.
- **Unclean sources** — covered by `unclean_source_refusal`, which both `replay()` and `validate()` consult to keep the refusal set consistent.

`collect_content_refs` walks a receipt for `(sha256, kind)` blob references so the replay engine can resolve them from the content store.

## Receipts and Capture Profiles

`ReceiptBuilder` produces typed receipts per action: `build_click_receipt` returns a click receipt with `dom_after_hash` set and `dom_after_blob_ref` left `None`; `build_navigate_receipt` does the inverse, embedding the after-snapshot as a blob ref. Source: [loom-core/src/receipt_builder/receipt_builder.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/receipt_builder/receipt_builder.rs) and the interface test at [loom-core/src/receipt_builder/interface_tests.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/receipt_builder/interface_tests.rs).

Capture profiles control which fields reach the wire. The single source of truth for "keep or drop" is `keep_field(scope, profile, field)` in `capture_policy.rs`, exhausted over all `(CaptureScope, CaptureProfile, CaptureField)` triples and guarded by a test. Source: [loom-core/src/receipt_builder/capture_policy.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/receipt_builder/capture_policy.rs). The `Minimal` profile strips all `*_blob_ref` fields and downgrades the navigate blob-after refs into hash-only fields; `Full` is a no-op because the caller has already set the before-refs directly on the struct. Source: [loom-core/src/receipt_builder/impl_capture.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/receipt_builder/impl_capture.rs). Both the receipt-side and the wire-side enforcer consult the same `keep_field` table to prevent drift between layers.

## Common Failure Modes

| Symptom | Likely cause | First check |
| --- | --- | --- |
| `schema_violation` on `loom.web.navigate` | Per-method schema on disk is stale or missing — `loom postinstall` did not regenerate embedded schemas. | Re-run `loom postinstall`; see [v0.11.1 release notes](https://github.com/mentiora-ai/loom/releases/tag/v0.11.1). |
| `surface_trap` on every `web.*` action (Linux) | Shim IPC socket raced Tokio I/O on fd 3. | Upgrade to ≥ 0.9.2. |
| Replay returns `NotReplayable` | Source recording used `--no-determinism` or was aborted. | Re-record with determinism enabled. |
| Drift after self-replay | Side-effect tape was not honored, or clock/RNG host-fn was not swapped. | Verify `install_replay_mode` ran. |

## See Also

- Determinism contract: [loom-core/src/determinism_harness/determinism_harness.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/determinism_harness/determinism_harness.rs)
- Replay refusal logic: [loom-core/src/replay_engine/impl_replay.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/replay_engine/impl_replay.rs)
- Capture policy truth table: [loom-core/src/receipt_builder/capture_policy.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/receipt_builder/capture_policy.rs)
- MCP tool naming: [loom-mcp/src/tool_cache/interface_tests.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/tool_cache/interface_tests.rs)
- Release context: [v0.11.0 Hardening Sweep](https://github.com/mentiora-ai/loom/releases/tag/v0.11.0), [v0.10.1 Cross-Run Determinism](https://github.com/mentiora-ai/loom/releases/tag/v0.10.1)

---

<a id='page-integrations'></a>

## MCP Server, SDKs & Schema Validation

### Related Pages

Related topics: [Project Overview & Architecture](#page-overview), [Core Action Surface, Determinism & Replay](#page-actions)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [loom-mcp/src/mcp_dispatcher/mcp_dispatcher.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/mcp_dispatcher/mcp_dispatcher.rs)
- [loom-mcp/src/mcp_dispatcher/mod.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/mcp_dispatcher/mod.rs)
- [loom-mcp/src/mcp_main/mod.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/mcp_main/mod.rs)
- [loom-mcp/src/mcp_observability/mod.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/mcp_observability/mod.rs)
- [loom-mcp/src/tool_cache/tool_cache.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/tool_cache/tool_cache.rs)
- [loom-mcp/src/error_mapper/error_mapper.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/error_mapper/error_mapper.rs)
- [loom-mcp/src/resource_tracker/resource_tracker.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/resource_tracker/resource_tracker.rs)
- [loom-mcp/src/stdio_transport/interface_tests.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/stdio_transport/interface_tests.rs)
- [typescript-sdk/package.json](https://github.com/mentiora-ai/loom/blob/main/typescript-sdk/package.json)
</details>

# MCP Server, SDKs & Schema Validation

## Overview & Role

The `loom-mcp` crate is a Model Context Protocol (MCP) server front-end that exposes loom's deterministic browser-automation verbs (e.g. `web.click`, `web.navigate`, `web.set_input_files`, `web.screenshot`) and session-control verbs (e.g. `session.reset`, `session.info`) to agentic clients such as Claude Code. It implements MCP protocol version `2024-11-05` and serialises responses as standard MCP `text` and `image` content blocks (`Source: [loom-mcp/src/mcp_dispatcher/mcp_dispatcher.rs]()`).

The server speaks JSON-RPC 2.0 over stdio, routes incoming requests through a centralised `McpDispatcher`, and proxies them to the loom daemon over its local IPC socket via an `RpcClient`. The dispatcher owns a cached tool list, a resource tracker, a structured-observability handle, a cancellation token, and an implicit session that is auto-created on the first tool call (`Source: [loom-mcp/src/mcp_dispatcher/mcp_dispatcher.rs]()`).

## Architecture

```mermaid
flowchart LR
    A[MCP Client<br/>e.g. Claude Code] -->|stdio JSON-RPC 2.0| B[StdioTransport]
    B --> C[McpDispatcher]
    C --> D[ToolCache<br/>loom.* tool list]
    C --> E[ResourceTracker<br/>loom://session/&lt;id&gt;/manifest]
    C --> F[McpObservability<br/>tracing + redaction]
    C --> G[ImplicitSession<br/>lazy, self-healing]
    G -->|CBOR over Unix socket| H[RpcClient]
    H --> I[loom-daemon<br/>CDP / shim]
```

The dispatcher advertises `tools`, `resources`, and `prompts` capabilities during `initialize`, with `listChanged` flags and `resources.subscribe` enabled (`Source: [loom-mcp/src/mcp_dispatcher/mcp_dispatcher.rs]()`). The `ToolCache` mirrors the daemon's `rpc.schemas` registry locally and transforms RPC method names by prepending the `loom.` prefix (e.g. `action.web.click` → `loom.action.web.click`) (`Source: [loom-mcp/src/tool_cache/tool_cache.rs]()`).

## Tool Surface, Resources & Schema Validation

The dispatcher exposes five MCP methods: `tools/list`, `tools/call`, `resources/list`, `resources/read`, and `prompts/list` (`Source: [loom-mcp/src/mcp_dispatcher/mcp_dispatcher.rs]()`). Tool definitions are built from the daemon's `RpcMethodSchema` records and tagged with the `loom.` prefix constant `TOOL_NAME_PREFIX` (`Source: [loom-mcp/src/tool_cache/tool_cache.rs]()`). Session-level verbs (`loom.session.*`) are not registered through `rpc.schemas`; the dispatcher injects them as built-in tools so MCP consumers can call `session.reset`, `session.info`, `session.diff`, `session.validate`, and `session.export` directly. These built-in tools accept a `budget` object (e.g. `{"session_walltime_ms": 30000}`) to cap runaway pages with `error.kind = "budget_exceeded"` (`Source: [loom-mcp/src/mcp_dispatcher/mod.rs]()`).

Schema validation is the contract gatekeeper between MCP clients and the daemon. As of **v0.11.1** the daemon validates `tools/call` arguments against an **embedded** schema registry rather than per-method files on disk, fixing a v0.11.0 regression where `loom.web.navigate` was rejected with a misattributed `schema_violation` (`field 'params' expected field_unknown got object`) while `web.wait_for` accepted the same `until`/`timeout_ms` parameters. The embedded-first approach ensures `postinstall` cannot leave the daemon with missing or stale schemas (`Source: [release notes v0.11.1](https://github.com/mentiora-ai/loom/releases/tag/v0.11.1)`).

Resources are addressed by URI scheme. Session manifests are exposed as `loom://session/<id>/manifest` with a default cache TTL of 30 seconds, and binary blobs are addressed as `loom://blob/<sha256-hex>` (`Source: [loom-mcp/src/resource_tracker/resource_tracker.rs]()`). Content blocks are restricted to the standard MCP `text` and `image` variants; JSON payloads are stringified into a `text` block to avoid tripping strict client validators (e.g. Claude Code's Zod schema). Screenshot-producing verbs emit a base64-encoded `image` block alongside a text receipt that still carries the `screenshot_after_hash` for wire-contract parity (`Source: [loom-mcp/src/error_mapper/error_mapper.rs]()`).

## Error Mapping, Observability & SDKs

The `ErrorMapper` is the single conversion point from `LoomError` to an MCP `ToolResult { isError: true, content }`. JSON-RPC protocol errors follow the spec-defined codes: `-32700` (parse), `-32600` (invalid request), `-32601` (method not found), `-32602` (invalid params), and `-32603` (internal) (`Source: [loom-mcp/src/stdio_transport/interface_tests.rs]()`). The observability layer emits `mcp_request_start` and `mcp_request_end` spans carrying the MCP method, optional tool name, outcome, error code, and latency in microseconds (`Source: [loom-mcp/src/mcp_observability/mod.rs]()`).

Two redaction paths protect vault material in logs and responses. When `redact_vault` is enabled, arguments for tools in `REDACTED_TOOL_NAMES` are replaced with the string `"[REDACTED]"`, and cookie tools (`COOKIE_REDACTED_TOOL_NAMES`) have only the `value` field of any nested `cookies` array stripped while names and structure remain visible for auditability (`Source: [loom-mcp/src/mcp_observability/mod.rs]()`). The implicit session is recreated transparently on eviction using the env-derived `SessionOptions` baseline (`LOOM_MCP_SESSION_SEED`, `LOOM_MCP_SESSION_CLOCK_ANCHOR`, `LOOM_MCP_SESSION_PROFILE`), and a `recreated_count` counter exposed via `loom.session.info` lets determinism-pinning clients detect transparent recreation (`Source: [loom-mcp/src/mcp_dispatcher/mod.rs]()`).

The **TypeScript SDK** is the canonical out-of-process consumer; its `package.json` defines `tsc --project tsconfig.json` as the build step, `node --import tsx/esm --test 'tests/**/*.ts'` for tests, and depends on `@types/node ^25.6.0`, `tsx ^4.19.2`, and `typescript ^5.7.3` (`Source: [typescript-sdk/package.json]()`). The crate-level mock harnesses are gated by `#[cfg(any(test, feature = "mock"))]` and live inside the owning crate (e.g. `loom-mcp/src/mocks.rs`) so each stub stays co-located with the types it replaces (`Source: [mocks/README.md]()`).

## See Also

- [Determinism & Replay Engine](./determinism-replay.md)
- [Daemon IPC & Shim Protocol](./daemon-ipc-shim.md)
- [Vault & Cookie Lifecycle](./vault-cookies.md)

---

<a id='page-operations'></a>

## Security, Operations & Failure Modes

### Related Pages

Related topics: [Project Overview & Architecture](#page-overview), [Core Action Surface, Determinism & Replay](#page-actions), [MCP Server, SDKs & Schema Validation](#page-integrations)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [loom-core/src/lib.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/lib.rs)
- [loom-core/src/vault/mod.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/vault/mod.rs)
- [loom-core/src/vault/impl_local.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/vault/impl_local.rs)
- [loom-core/src/vault/audit_payloads.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/vault/audit_payloads.rs)
- [loom-core/src/error_types/error_types.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/error_types/error_types.rs)
- [loom-core/src/error_types/mod.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/error_types/mod.rs)
- [loom-core/src/session_manager/session_manager.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/session_manager/session_manager.rs)
- [loom-core/src/observability/interface_tests.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/observability/interface_tests.rs)
- [loom-core/src/manifest_writer/manifest_writer.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/manifest_writer/manifest_writer.rs)
- [loom-core/src/budget_enforcer/mod.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/budget_enforcer/mod.rs)
- [loom-core/src/replay_engine/mod.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/replay_engine/mod.rs)
- [loom-core/src/determinism_harness/mod.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/determinism_harness/mod.rs)
- [loom-mcp/src/mcp_dispatcher/mcp_dispatcher.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/mcp_dispatcher/mcp_dispatcher.rs)
- [loom-mcp/src/error_mapper/interface_tests.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/error_mapper/interface_tests.rs)
- [loom-mcp/src/tool_cache/interface_tests.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/tool_cache/interface_tests.rs)
- [mocks/README.md](https://github.com/mentiora-ai/loom/blob/main/mocks/README.md)
</details>

# Security, Operations & Failure Modes

This page describes how Loom protects credentials, manages long-lived operations, and surfaces failures across its daemon, MCP, and shim boundaries. It consolidates the threat model, observability, session/ budget controls, and the stable error taxonomy that downstream consumers (such as `agentic-test-studio`) depend on for trust.

## Vault & Credential Security

The `vault` module ([loom-core/src/vault/mod.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/vault/mod.rs)) is the single boundary for storing and granting secrets used by web sessions. Granting a credential is gated by two explicit security checks inside [loom-core/src/vault/impl_local.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/vault/impl_local.rs):

1. **Threat-model acknowledgement.** A grant request with `threat_model_acknowledged = false` returns `LoomErrorCode::VaultRejection` with context code `vault_threat_model_missing`. Operators must confirm they have read the threat-model document that ships with the binary — its required headings are asserted in tests (`Attacker Classes`, `Security Goals`, `Trust Boundaries`, `Abuse Cases`).
2. **OAuth-only enforcement.** `CredentialType::ApiKey` is rejected with `vault_credential_type_unsupported`; only `oauth2` is in the allowed list. This narrows the credential surface and aligns replay with token-refresh semantics.

Audit payloads are JCS-canonical DTOs embedded in `ManifestEntry::AuditEntry::canonical_bytes` ([loom-core/src/vault/audit_payloads.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/vault/audit_payloads.rs)). The on-wire shape is **hash-chain-load-bearing** — reordering a field breaks NFR-DET-01 cross-run determinism. To minimise secret exposure, `extract_cookie_names` parses the raw vault blob but never returns raw values; on parse failure it returns an empty vector and lets the typed CDP-encode error surface to the caller.

## Session Lifecycle, Budgets & Observability

Each session runs as a finite-state machine owned by `SessionManager` ([loom-core/src/session_manager/session_manager.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/session_manager/session_manager.rs)):

```mermaid
stateDiagram-v2
    [*] --> Created
    Created --> Active: warm_create (ULID + dir + WAL fsync + task spawn)
    Active --> Closed: normal completion
    Active --> Aborted: operator abort()
    Active --> Killed: BudgetEnforcer kill-callback
    Active --> Crashed: task panic
    Closed --> [*]
    Aborted --> [*]
    Killed --> [*]
    Crashed --> [*]
```

The `warm_create` path performs **no** WASM, Chromium, or network work — only ULID generation, directory creation, WAL header append + `fsync`, and a tokio task spawn. Abort propagation uses `Arc<Notify>` plus `Arc<AtomicBool>`; host-fn entries poll the bool and `tokio::select!` against the notify. A kill-callback `Arc<dyn Fn(SessionId, KillReason)>` is registered into `BudgetEnforcer` so a budget breach can interrupt a specific session's task without creating a cycle (see [loom-core/src/budget_enforcer/mod.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/budget_enforcer/mod.rs)).

For long-lived daemons, the **session reaper** (added in 0.10.1) closes idle sessions and supports the `loom session reap` operator command surfaced by `ErrorMapper::to_tool_result` (e.g. `concurrent session cap reached (2/2)` with structured `{active, cap, hint}` context, [loom-mcp/src/error_mapper/interface_tests.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/error_mapper/interface_tests.rs)).

`Observability` ([loom-core/src/observability/interface_tests.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/observability/interface_tests.rs)) exposes three contract surfaces: a redaction layer, a span lifecycle (`span_start` returns a handle carrying the name; `span_end` consumes it), and a `BudgetSnapshot` that is **integer-only**. `LogField` carries an explicit `secret: bool` flag so the redactor can scrub PII before write.

The MCP dispatcher ([loom-mcp/src/mcp_dispatcher/mcp_dispatcher.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/mcp_dispatcher/mcp_dispatcher.rs)) adds an **implicit session** auto-created on the first tool call, reused for the MCP server's lifetime, and **transparently recreated** after idle eviction. `recreated_count` is surfaced via `loom.session.info` so determinism-pinning clients can detect a transparent recreation; it is **not** part of the manifest/hash chain and resets on MCP-process restart.

## Failure Modes & Error Taxonomy

Loom deliberately separates two error families:

| Family | Module | Purpose | Stability |
|---|---|---|---|
| `ReceiptCode` | `error_types` ([loom-core/src/error_types/error_types.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/error_types/error_types.rs)) | Public action-outcome discriminator in every receipt | Stable 22-code enum; SemVer-minor to add, major to remove |
| `LoomErrorCode` | internal | Daemon-level infrastructure errors | Internal |

`ReceiptCode::WebActionCompleted` is the OK sentinel; `as_wire()` matches serde output (snake_case) and tests verify parity. Adding a variant is a minor change — consumers parse the string form, not the variant index. A new variant, `WebElementNotVisible`, was added recently and remains a SemVer-minor addition.

### Known Historical Failures

- **0.9.1 daemon-stall.** A bug fixed post-0.9.0 caused the daemon to stop accepting requests; the patch also added `daemon.health({deep: true})`.
- **0.9.2 Linux postinstall regressions** ([loom-core/src/lib.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/lib.rs) module surface). Fresh Linux installs would `postinstall` correctly then fail in two ways: `loom doctor` reported `chromium binary not found`, and every `web.*` action surfaced as `surface_trap` because the shim's IPC socket raced Tokio's I/O driver on fd 3. 0.9.2 fixed both.
- **0.11.1 schema regression.** A v0.11.0 regression made MCP `tools/call` reject the documented `until`/`timeout_ms` args on `loom.web.navigate` with `schema_violation` (`field 'params' expected field_unknown got object`) while `web.wait_for` accepted the same params. Root cause: the daemon validated against per-method schema files on disk that `loom postinstall` never embedded; v0.11.1 switched to embedded-first validation.

### MCP Error Mapping Contract

`ErrorMapper::to_tool_result` ([loom-mcp/src/error_mapper/interface_tests.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/error_mapper/interface_tests.rs)) returns a `ToolResult` whose single content block is the standard MCP `text` content type with the `TypedReceipt` JSON-serialised inside. This was a v1.0.1 fix so strict MCP clients (Claude Code) accept the payload. Internal helpers (`from_rpc_io`, `from_hello_mismatch`, `from_unknown_tool`, `from_schema_parse`) map every wire failure to a stable `LoomErrorCode` — no inline code invention. Tool names use the `loom.` prefix and snake_case dotted signatures ([loom-mcp/src/tool_cache/interface_tests.rs](https://github.com/mentiora-ai/loom/blob/main/loom-mcp/src/tool_cache/interface_tests.rs)).

## Hardening & Replay Integrity

The replay engine ([loom-core/src/replay_engine/mod.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/replay_engine/mod.rs)) enforces `non_deterministic_refusal` and `unclean_source_refusal`, and `collect_content_refs` is the single point that walks a manifest to enumerate its content-store dependencies. Together with `DeterminismHarness` ([loom-core/src/determinism_harness/mod.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/determinism_harness/mod.rs)) and the JCS-canonical manifest chain ([loom-core/src/manifest_writer/manifest_writer.rs](https://github.com/mentiora-ai/loom/blob/main/loom-core/src/manifest_writer/manifest_writer.rs)), these guarantees make the manifest hash a true "did anything change?" oracle across independent recordings (NFR-DET-01, delivered in 0.10.1). The v0.11.0 hardening sweep (~100 audit findings) was regression-pinned via interface tests, with mocks co-located per crate and gated by `#[cfg(any(test, feature = "mock"))]` ([mocks/README.md](https://github.com/mentiora-ai/loom/blob/main/mocks/README.md)).

**Operational checklist for failure investigation:**

1. Run `loom doctor` — confirms the chromium binary and post-install schema embed (post-0.9.2 fix path).
2. Run `daemon.health({deep: true})` to probe the shim and IPC socket.
3. Inspect the receipt's `code` field — match against the 22-code `ReceiptCode` enum.
4. For session caps, parse `data.active`, `data.cap`, `data.hint` and run `loom session reap`.
5. For determinism drift, compare manifest hash chains across two independent fresh recordings (`field_diffs=0` ⇒ no change).

## See Also

- [Vault & Credentials](./Vault-and-Credentials)
- [Session Manager & Lifecycle](./Session-Manager)
- [Error Taxonomy & Receipt Codes](./Error-Taxonomy)
- [Replay & Determinism](./Replay-and-Determinism)
- [MCP Integration](./MCP-Integration)

---

<!-- evidence_pipeline_checked: true -->
<!-- evidence_injected: true -->

---

## Pitfall Log

Project: mentiora-ai/loom

Summary: Found 8 structured pitfall item(s), including 1 high/blocking item(s). Top priority: Runtime risk - Runtime risk requires verification.

## 1. Runtime risk - Runtime risk requires verification

- Severity: high
- Evidence strength: source_linked
- Finding: Project evidence flags a runtime risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/mentiora-ai/loom/issues/179

## 2. Configuration risk - Configuration risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a configuration risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: capability.host_targets | https://github.com/mentiora-ai/loom

## 3. Capability evidence risk - Capability evidence risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: README/documentation is current enough for a first validation pass.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: capability.assumptions | https://github.com/mentiora-ai/loom

## 4. Maintenance risk - Maintenance risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a maintenance risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/mentiora-ai/loom

## 5. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: downstream_validation.risk_items | https://github.com/mentiora-ai/loom

## 6. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: risks.scoring_risks | https://github.com/mentiora-ai/loom

## 7. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: issue_or_pr_quality=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/mentiora-ai/loom

## 8. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: release_recency=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/mentiora-ai/loom

<!-- canonical_name: mentiora-ai/loom; human_manual_source: deepwiki_human_wiki -->
