GitHub
01 Getting Started

Introduction

Mainspring is an on-chain witness layer for autonomous agents. Every decision an agent makes — the intent, the inputs it weighed, the action it took — is sealed to Casper testnet as a permanent, independently verifiable attestation.

Where a log file can be rewritten, a Mainspring attestation cannot. It turns “trust me” into a receipt anyone — a counterparty, an auditor, a regulator — can verify without your cooperation.

2 commands
to give any agent a witness
Immutable
sealed to Casper testnet
Public
free to verify, forever
Stack
  • Transport: MCP · stdio
  • Runtime: Node.js 20+ / TypeScript
  • On-chain: Casper testnet
  • Encryption: AES-256-GCM
  • Storage: File JSON or Supabase
  • Hashing: SHA-256 · canonical JSON

Installation

Mainspring runs as a local MCP server over stdio. The normal user path is the npm package: install the CLI, let it create local config/data/log folders, then point your MCP client at the mainspring command.

# 1. Install the MCP server CLI
npm install -g mrmainspring

# 2. Create local config and print the MCP snippet
mainspring setup claude
# or: mainspring setup cursor

Paste the printed JSON into your MCP client configuration. The same command works for Claude Desktop, Cursor, or any MCP-compatible host:

{
  "mcpServers": {
    "mainspring": {
      "command": "mainspring"
    }
  }
}
Step 1
Install

Node.js 20+ and npm install -g mrmainspring put the mainspring command on PATH.

Step 2
Connect

mainspring setup claude or mainspring setup cursor creates local files and prints the MCP config.

Step 3
Verify

Restart the MCP client, then run mainspring doctor if the server does not appear.

Local env
# Check what Mainspring will use locally
mainspring doctor

# Optional advanced setup
mainspring init
mainspring config

No wallet, hosted account, or environment variable is required for local memory, Grimoire, audit, or payment preflight tools. Mainspring stores its default config under the user's app config folder and data under that local namespace.

For Casper anchoring during development, use the testnet — anchors are free and the contract behavior is identical to mainnet.

Quickstart

Once the MCP server is connected, your agent can call Mainspring tools directly. The local user path is: write a memory, get a deterministic receipt, then verify the hash. Casper submission stays pending unless you explicitly configure real testnet anchoring.

{
  "agent_id":  "atlas-treasury-02",
  "type":     "decision",
  "source": { "tool": "price_oracle", "symbol": "CSPR" },
  "body": {
    "action":    "TRANSFER 12400 CSPR",
    "rationale": "Rebalance to 30% stable reserve after weekly inflow."
  },
  "anchor": true
}
{
  "memory_id":     "mem_4f2c8e1a-...",
  "content_hash":  "9f3c4a71e0b2...",  // SHA-256 of full envelope
  "metadata_hash": "b8e109d2fa31...",  // SHA-256 of identity fields
  "anchor_status": "pending",           // local default until Casper is configured
  "anchor_id":    "c7f8a0e3..."
}

That receipt is a verifiable handle. Hand the content_hash to any counterparty; they can recompute it independently from the same envelope. With Casper testnet anchoring configured, the same hash-only proof can also be checked against the transaction record.

02 Architecture

Overview

Mainspring is a single MCP server with four internal services. All communication with the agent happens over stdio — no HTTP server, no port bindings. The only outbound connection is the optional call to casper-client for on-chain anchoring.

System architecture
Arquitectura de Mainspring Agent Process any LLM runtime MCP · stdio McpServer 11 tools · stdio transport Memory write · read · search · verify Grimoire secrets · policies · AES-256-GCM Payment fetch · receipt · x402 challenge Audit tail · redact · severity File JSON ─ or ─ Supabase casper-client · spawn() Casper Testnet memory-anchor contract · hashes only

The storage layer is swappable at startup. Both backends implement the same interface — switching from file to Supabase requires only an environment variable change, no code changes.

The Loop

Every decision runs the same clock. Mainspring is built like a watch — four movements that only mean something when they turn together.

  • Act — your agent decides and acts on available data.
  • Witness — Mainspring meters the decision at the source, hashing the full envelope before it can be altered.
  • Anchor — the content hash and anchor id stay as pending local metadata by default, or are submitted to Casper testnet through casper-client only when real submission is explicitly enabled.
  • Prove — anyone can recompute the hash from the stored envelope and verify it against the chain, independently, without trusting Mainspring.
03 Core Concepts

Attestations

An attestation is the sealed record of one decision. Mainspring stores the full envelope off-chain and anchors only its SHA-256 hash on Casper — nothing about the content ever reaches the blockchain.

Attestation · Casper Testnet Immutable
agent_idmainspring-witness
typesystem_event
anchor_tx91fb904e47b6…bc555c
networkCasper testnet
execution_errornull
sealed_at2026-06-06 UTC · verify ↗

Memory Envelope

Every memory written to Mainspring is serialized into a MemoryEnvelope before hashing. The schema is versioned and deterministic — the same inputs always produce the same hash.

MemoryEnvelope schema & hashing pipeline
Memory Envelope schema and hashing pipeline MemoryEnvelope sigil.memory.v1 schema_version "sigil.memory.v1" agent_id string memory_id mem_<uuid> type observation · decision payment · secret_usage · system_event source JsonObject body JsonObject created_at ISO 8601 prev_anchor_hash string | null Hashing pipeline content_hash SHA-256(canonical JSON completo) metadata_hash SHA-256(agent+mem+type+src+ts) Canonical JSON keys α-order · no spaces · no undefined anchor_id SHA-256( agent_id:mem_id:content_hash:prev) anchor states not_requested pending anchored failed

Canonical JSON sorts all object keys alphabetically at every depth level and omits undefined values. This guarantees the same input always produces the same hash regardless of the serializer used.

The prev_anchor_hash field chains attestations together. When a memory is anchored, its anchor_id is stored as the previous hash for the agent's next anchored write — creating a verifiable chain of custody.

Witnesses

A witness is the MCP server running alongside your agent. It is the sole trusted observer — it receives the raw decision data, computes the hash before any transmission can alter it, and dispatches the anchor submission.

The witness is non-blocking. Anchoring runs asynchronously — anchor_status: "pending" is returned immediately; the status transitions to "anchored" once casper-client confirms the transaction. If the anchor call fails, the memory is still stored locally with anchor_status: "failed" — no data is lost.

The witness never touches body content for anchoring — it only sees the hash. Body data stays in local storage (file or Supabase) and never reaches Casper.

04 MCP Tools

memory.write

Creates a new MemoryEnvelope, computes both hashes, persists the entry, and optionally submits an anchor to Casper. Every write is also recorded as a memory.created audit event.

Input
FieldTypeReqDescription
agent_idstringIdentifies the writing agent. Used as a key in the anchor chain.
typeenumobservation · decision · payment · secret_usage · system_event
sourceobjectContextual metadata about the memory origin (arbitrary JSON).
bodyobjectThe memory content (arbitrary JSON). Never sent on-chain.
anchorbooleanIf true, submit anchor to Casper immediately. Default: false.
Output
FieldTypeDescription
memory_idstringUnique identifier — mem_<uuid>
content_hashstringSHA-256 of the canonical full envelope (anchored on-chain)
metadata_hashstringSHA-256 of identity fields only (agent_id, memory_id, type, source, created_at)
anchor_statusenumnot_requested · pending · anchored · failed
anchor_idstring?Present when anchor_status ≠ not_requested

memory.read · search · verify

Three read-path tools that cover retrieval, discovery, and integrity verification.

memory.read

Reads one stored memory by agent_id + memory_id. Returns the full StoredMemoryEntry including both hashes and anchor state.

memory.search

Full-text search over body, source, and type fields for a given agent_id. Takes a query string and optional limit (default 20). Returns ranked matches.

memory.verify

Recomputes the content_hash locally from the stored envelope and compares it to the stored value. Returns valid: true|false, both hashes, and whether they match. Detects any tampering with the stored data.

{
  "valid":               true,
  "stored_hash":         "9f3c4a71e0b2…",
  "computed_hash":       "9f3c4a71e0b2…",
  "hashes_match":        true,
  "onchain_content_hash": "9f3c4a71e0b2…"  // present if anchored
}

Grimoire

The Grimoire is Mainspring's encrypted vault for secrets and spending policies. It provides two sub-systems: secret storage (AES-256-GCM) and policy enforcement for the x402 payment flow.

Secrets

Secrets are encrypted with AES-256-GCM using a 12-byte random nonce. The plaintext is never returned by any MCP tool — not even in confirmations. If you lose the plaintext, it is gone.

grimoire.secret.put — Input
FieldTypeDescription
namestringUnique identifier for this secret.
valuestringThe plaintext secret. Encrypted immediately; never stored in the clear.
typeenumcasper_private_key_ref · x402_client_key_ref · api_key · webhook_secret
scopesstring[]Permitted operations: x402:sign · casper:sign

grimoire.secret.list returns metadata only (name, type, scopes, created_at). The value field is never included in any response.

Policies

Policies gate x402 payment operations. Every payment.fetch call runs a policy check first — no payment proceeds without an active, matching policy.

{
  "policy_id":             "pol_cspr_transfers",
  "enabled":              true,
  "allowed_urls":          ["https://api.example.com/data"],
  "allowed_methods":       ["GET"],
  "max_amount_per_call":   "0.05",
  "max_amount_per_period": "1.00",
  "period_seconds":        86400
}

Policy denial reasons: policy_not_found · policy_disabled · url_not_allowed · method_not_allowed · amount_over_limit · invalid_amount. Each reason is audited.

Payments

Mainspring implements the x402 payment protocol — a three-stage state machine for policy-gated, challenge-based micropayments over HTTP. Each stage advances the same PaymentIntent record.

x402 payment state machine 3 stages
x402 payment states Stage 1 Stage 2 Stage 3 created payment.fetch() Policy check policy_denied url · method · amount policy_checked ready for challenge request_challenge=true HTTP 402 challenge challenge_received parsed · validated vs policy settlement attempt Settlement settlement_unavailable DisabledX402SettlementProvider settled verified provider result current flow configured settlement path

Default installs stop honestly at settlement_unavailable because the settlement provider is disabled. With X402_ENABLE_REAL_SETTLEMENT=true, X402_SIGNER_URL, a funded Casper testnet key, and the signer/resource/facilitator sidecars from the runbook, payment.fetch retries the paid resource with PAYMENT-SIGNATURE and persists settled only after PAYMENT-RESPONSE verifies with a Casper transaction hash.

Use payment.receipt to read a payment intent at any stage — it returns the full record including status, denial reason, and challenge requirements.

Audit

audit.tail returns the most recent audit events across all services. Every tool call that writes state records an event automatically — no opt-in required.

{ "limit": 20 }  // default: 20, max: 100
Event types
infomemory.created
infomemory.verify_succeeded
warnmemory.verify_failed
infopayment.policy_approved
warnpayment.policy_denied
infopayment.challenge_received
infopayment.settled
infosecret.stored
infopolicy.set

All audit metadata is sanitized before storage. Keys matching secret, token, key, password, private, payload, credential, authorization, or value are automatically redacted to [REDACTED]. Max depth: 6, max string: 2 048 chars, max total event size: 16 KB.

05 Deep Dive

Casper Anchoring

Anchoring writes a compact record to the memory-anchor Rust/Wasm contract on Casper. Only hashes are submitted — never memory bodies, secrets, or private keys.

anchor_id derivation

anchor_id = SHA-256(
  agent_id + ":" + memory_id + ":" + content_hash + ":" + prev_anchor_hash
)

When prev_anchor_hash is null (first anchor for an agent), the literal string "null" is used in the concatenation. This links successive anchors into a tamper-evident chain per agent.

Contract entry point

ArgTypeDescription
anchor_idStringUnique anchor identifier (hex-encoded SHA-256)
content_hashStringSHA-256 of the full envelope (hex)
metadata_hashStringSHA-256 of identity fields (hex)
agent_idStringAgent identifier — stored alongside the anchor

The contract rejects duplicate anchor_ids with error code DuplicateAnchor (0). All hash strings are validated against /^(hash-)?[a-f0-9]{64}$/i before being passed to the CLI — no unvalidated input reaches casper-client spawn().

Submission modes

  • transaction-package (default) — uses MEMORY_ANCHOR_PACKAGE_HASH
  • deploy-contract-hash — uses MEMORY_ANCHOR_CONTRACT_HASH

Deployed testnet contracts

MEMORY_ANCHOR_CONTRACT_HASH=hash-9a10301e…
MEMORY_ANCHOR_PACKAGE_HASH=hash-162da013…
CASPER_NETWORK_NAME=casper-test
CASPER_ENABLE_REAL_SUBMISSION=true  # explicit opt-in required

Full hashes are in the repository README under Casper Contract Status. Block explorer: testnet.cspr.live.

Storage Backends

Both backends implement identical interfaces. The choice is made at startup via SIGIL_STORAGE_BACKEND — no code changes required to switch.

·File (default)Supabase
ActivateSIGIL_STORAGE_BACKEND=fileSIGIL_STORAGE_BACKEND=supabase
Memory<user data>/memory.jsonsigil_memories
Grimoire<user data>/grimoire.jsonsigil_secrets / sigil_policies
Payments<user data>/payments.jsonsigil_payment_intents / sigil_payment_receipts
Audit<user data>/audit.jsonsigil_audit_events
SetupNone — auto-createdApply backend/supabase/schema.sql
Best forDevelopment, demos, single-agentProduction, multi-agent, persistence

Security Model

Never on-chain
  • Memory body content
  • Secret plaintext
  • Rationale text
  • Signed x402 payloads
  • Private keys
Never returned via MCP
  • Secret plaintext values
  • AES-GCM auth tags
  • Signed x402 payloads
  • Raw nonces
Always sanitized
  • Audit event metadata
  • Challenge requirements
  • Policy denial details

CLI arguments passed to casper-client are validated against /^(hash-)?[a-f0-9]{64}$/i before the process is spawned. This prevents command injection via malformed hash strings in any stored data.

The grimoire master key is intentionally excluded from all storage backends, audit logs, and MCP responses. If GRIMOIRE_MASTER_KEY is absent at startup, Mainspring generates one automatically for local use; keep production secrets in a private env file.

06 Resources

Environment Reference

Normal npm users do not need to copy .env.example or create a repo-local .env. Run mainspring setup; it creates the local config, data, and logs directories automatically. The variables below are advanced overrides for Supabase, real Casper testnet anchoring, x402 settlement sidecars, or repository development.

Advanced env

Use mainspring doctor to see the active config path. If you need custom values, put them in the user config file created by mainspring init, or point SIGIL_ENV_FILE at a private env file. Copying .env.example is only for local development from this repository.

Variable Default Description
SIGIL_ENV_FILE(user config)Optional path to a private env file. If unset, Mainspring checks the user's app config first, then repo-local env files for development.
SIGIL_DATA_DIR(user data)Root directory for file-based storage. Defaults to the user's Mr Mainspring app data directory.
SIGIL_STORAGE_BACKENDfilefile or supabase.
GRIMOIRE_MASTER_KEY(auto)Base64-encoded 32-byte AES-256-GCM master key. Generated automatically for local use when omitted.
PROJECT_URLSupabase project URL (required when backend=supabase).
SECRET_KEYSupabase service role key.
CASPER_RPC_URLCasper node RPC endpoint for casper-client.
CASPER_ACCOUNT_KEY_PATHPath to the account signing key PEM file.
CASPER_NETWORK_NAMEcasper-testcasper-test or casper (mainnet).
CASPER_ENABLE_REAL_SUBMISSIONfalseExplicit opt-in gate. Must be true to actually write to chain.
MEMORY_ANCHOR_CONTRACT_HASHDeployed contract hash (hash-<64 hex chars>).
MEMORY_ANCHOR_PACKAGE_HASHDeployed package hash. Used in transaction-package mode.
CASPER_ANCHOR_SUBMISSION_MODEtransaction-packagetransaction-package or deploy-contract-hash.
X402_ENABLE_REAL_SETTLEMENTfalseOpt-in gate for real x402 settlement. Requires a configured signer/facilitator/resource path.
X402_SIGNER_URLExternal signer sidecar URL. Required for real paid retries.
X402_FACILITATOR_URLhttp://localhost:4022x402 facilitator endpoint for challenge verification and settlement tests.

FAQ

Is my memory body stored on-chain?

No. Only a SHA-256 hash of the full envelope is anchored. Body content lives in your local storage (file or Supabase) and never leaves your infrastructure.

Can I verify a hash without Mainspring running?

Yes. The hash is deterministic: sort the envelope keys alphabetically, serialize to compact JSON, run SHA-256. Any runtime can recompute and verify against what's on Casper — independently.

What happens if anchoring fails?

The memory is still stored locally with anchor_status: "failed". No data is lost. You can inspect the audit log for the error detail and retry manually.

Is the grimoire master key stored anywhere?

Never. It lives only in your environment. If absent at startup, Mainspring generates one automatically for local use. Production secrets should live in a private env file.

Is real x402 settlement implemented?

Not by default. The local install keeps settlement disabled and returns settlement_unavailable. The real Casper testnet path is wired through the runbook sidecars and only returns settled when a verified settlement response includes a Casper transaction hash.

© 2026 Mainspring · Wound tight, kept honest. ← Back to site