Skip to main content
solid:agent-verb-manifest/v1

The Verb Spec

How a verb is declared, what shape it returns, how it reaches four transports, what proof it hands back, and what a human has to approve before it runs. This is the contract behind the verb surface — written for the agents that consume it.

verbs registered
shapes
4
transports
on MCP stdio

Manifest Format

Every verb is declared as a VerbRecord — a single JSON object that carries the verb's identity, schema, shape, and surface coverage.
VerbRecord
{
  "name": "payments.preview_refund_impact",
  "description": "Project the impact of refunding a transaction WITHOUT executing it.",
  "shape": "preview",
  "side_effects": "read",
  "input_schema": {
    "type": "object",
    "properties": {
      "transaction_id": { "type": "integer", "minimum": 1 }
    },
    "required": ["transaction_id"]
  },
  "output_schema": { "$ref": "/schema/shapes/preview.json" },
  "surfaces": ["http", "mcp_stdio", "webmcp", "ucp", "cli"],
  "requires_consent": false,
  "tier_floor": "starter"
}

Field reference

FieldTypeRequiredDescription
namestringyesDot-notation (payments.preview_refund_impact) or snake_case (charge_invoice). Must match ^[a-zA-Z][a-zA-Z0-9_.]*$.
descriptionstringyesWritten for an LLM, not a human. States what the verb does and what the agent should expect.
shapeenumyesOne of the shapes below.
side_effectsenumnoread (default, safe to call freely), write (mutates state), mixed (writes but reverses cleanly).
input_schemaJSON SchemayesOpenAI-compatible tool parameter schema.
output_schemaJSON SchemanoDescribes the response. If omitted, the response is { type: object, additionalProperties: true }.
surfacesstring[]noTransports: http, mcp_stdio, webmcp, ucp, cli. Defaults to all five.
requires_consentbooleannoWhether the runtime gates this verb behind human approval.
tier_floorstringnoMinimum subscription tier. Defaults to starter.

Naming conventions

Dot-notation for agent-attraction verbs (the multi-transport surface): {domain}.{verb} — the domain groups verbs logically (crm, payments, inventory, infrastructure, deal, customer).

Snake_case for ADA dispatcher verbs (the typed-phrase / consent-gated surface) — charge_invoice, subscription_upgrade_tier. These are the verbs that require human confirmation; the snake_case name is what the LLM emits in its function call.

Both styles coexist in one registry. The ADA-to-manifest bridge auto-mirrors snake_case names into the unified registry so discovery surfaces see the full set.

Registration semantics

RuleMeaning
One declaration per verbThe unified registry is the single source. MCP tool list, WebMCP manifest, UCP capabilities and CLI verbs all derive from it.
Last write winsRe-registering the same name overwrites the previous record.
Surface filteringA verb with surfaces: ["http", "cli"] will not appear in MCP or WebMCP tool lists.
Parity invariantA verb visible on one transport MUST be visible on every transport in its surfaces set. Enforced by integration tests.

company_id is NEVER in the schema

company_id is injected by the runtime from the authenticated session — never accepted from the LLM. Including it in input_schema.properties is a registration-time error. This is what stops cross-tenant data access via prompt injection. The same rule binds user_id: both come from the runtime, not the model.

Manifest endpoint

The live manifest is public and needs no key to read:

GET /api/v1/agent/verbs?surface=mcp_stdio

Returns VerbRecords filtered by surface, cached for 5 minutes. New verbs appear within 5 minutes of a backend deploy — no client-side package republish.

Shapes

A shape is the response contract. An agent that knows the shape knows how to read the answer before it calls the verb.

Every verb declares one. The registry is the authority on which shapes exist and how many verbs carry each — the tiles above read it live. The table below describes what each shape gives an agent, which is the part that does not drift.

ShapeWhat it gives the agent
receiptAttestation on a side-effecting write — audit id, rollback handle, idempotency key.
discoveryIntrospection: what exists, what this agent may call, what it costs.
transactionACID grouping over multi-step writes — start, append, commit, abort.
aggregateCollapse 5–8 sub-queries into one tenant-scoped read.
suggestRanked next-actions in a confidence envelope (confidence, band, reason, model_version, sample_size).
previewDry-run a write before commit. Returns reversible: true|false.
explainThe causal chain that produced a state.
revertSingle-action undo against whitelisted audit rows.
macroSaved verb chains, promoted from observed sequences.
reputationPer-verb reliability history — what happened last time.
subscribeCursor-polling observe streams over the audit log.
trailThe chain of actions that led here.
telemetryRuntime health of the agent surface itself.

Transport Bindings

One registry, four projections. Adding a verb to the unified registry lights up all four transports automatically.
                    UNIFIED_VERB_REGISTRY
                            │
     ┌──────────────┬───────┴──────────┬──────────────┐
    CLI         MCP stdio          WebMCP            UCP
 shell agent   Claude Desktop    in-browser     buyer-agent
               Cursor/Windsurf   Chrome agent   Gemini/ChatGPT

CLI

Shell-running agents (Claude Code, Codex, scripts) invoke verbs via the CLI.

discovery + invocation
solid verbs list --json          # full manifest
solid verbs list --shape preview # filter by shape

solid agent dispatch <verb> --args '{"key": "value"}' --json [--confirm] [--phrase "..."]

Write verbs require --confirm. Typed-phrase verbs require --phrase "EXACT PHRASE". Shortcut commands promote high-traffic verbs to first-class: solid infra diagnose, solid deal create, solid customer-context 42.

MCP stdio

MCP-speaking runtimes (Claude Desktop, Cursor, Windsurf, Cline) load the server via npx. Discovery is standard tools/list; invocation is standard tools/call. Verb names map 1:1 to tool names.

claude_desktop_config.json
{
  "mcpServers": {
    "solidnumber": {
      "command": "npx",
      "args": ["-y", "@solidnumber/mcp"],
      "env": { "SOLID_API_KEY": "sk_solid_..." }
    }
  }
}

The MCP server is a thin bridge. It fetches the manifest from /api/v1/agent/verbs?surface=mcp_stdio and caches it for 5 minutes, so new verbs appear in tools/list without an npm republish. Auth is the SOLID_API_KEY env var — optional for read verbs, required for writes.

WebMCP

In-browser agents discover verbs via the W3C draft API — navigator.modelContext.registerTool() on page load, registered per surface (dashboard, portal, developer, tenant-site, public). Auth is the session cookie; the browser's authenticated session binds the company_id. Tenants are born WebMCP-aware with no manual setup.

GET  /api/v1/webmcp/manifest?surface={dashboard|portal|developer|tenant-site|public}
POST /api/v1/webmcp/execute/{tool_name}
     { "input": { ... }, "surface": "dashboard" }

UCP (Universal Commerce Protocol)

Public buyer-agents (Gemini AI Mode, ChatGPT shopping) discover capabilities at GET /.well-known/ucp. Invocation is HTTP with RFC 9421 ES256 message signatures; verbs map to capabilities under the com.solidnumber.* namespace. Auth is a per-company JWK rotation lifecycle (pending → active → rotating → retired), private keys encrypted at rest and RLS-protected.

The differentiator: Solid# exposes a hierarchical agent graph (platform → company → sales / AR / AP / commissions / service / compliance / marketing / ops sub-agents) instead of the stock UCP single-checkout-endpoint model.

Error contract

All four transports return errors in the same envelope:

{
  "ok": false,
  "verb": "infrastructure_resize",
  "error": {
    "reason": "no_managed_droplet",
    "message": "This company has no managed droplet.",
    "hint": "Use subscription_upgrade_tier for capacity changes."
  }
}

reason is machine-readable — the agent pattern-matches on it. message is for humans and LLMs. hint is the actionable next step.

Receipt Contract

Every write verb returns a receipt. The receipt is the proof that an action happened, the handle to undo it, and the key to replay it safely.
FieldTypeDescription
audit_idintegerUnique ID in the audit log. Pass to audit.revert to undo.
rollback_handlestringOpaque token encoding the inverse action (e.g. infra:resize:1:s-2vcpu-4gb).
idempotency_keystringReplay-safe — calling the same verb with the same key is a no-op.
timestampISO 8601When the action was executed.
statusstringcompleted, pending, failed.

Rollback is best-effort, not guaranteed

Each action type has a whitelisted revert handler. If none exists, audit.revert returns a structured refusal with reason: "no_revert_handler" rather than pretending. An agent can check reversible: true in preview output to know ahead of time whether an action is undoable.

Idempotency

StateBehaviour
Key exists + succeededReturns the original receipt. No re-execution.
Key exists + failedRe-executes the action.
Key absentExecutes normally, stores the key.

If no key is supplied the runtime generates one. Keys expire after 24 hours.

Receipt chains

A transaction produces a parent receipt with child receipts per step. Reverting the parent reverts all children in reverse order.

{
  "transaction_id": "tx_abc123",
  "status": "committed",
  "audit_id": 4243,
  "steps": [
    { "verb": "crm_contact_update", "audit_id": 4244, "status": "completed" },
    { "verb": "email_send",         "audit_id": 4245, "status": "completed" }
  ],
  "rollback_handle": "tx:tx_abc123:rollback_all"
}

Treat rollback_handle as opaque — never parse or construct one.

Audit log

Every dispatch writes an AIAuditLog row — including denials. The row captures company and user, agent type, action type, category, status, request data, reversibility and approval trail.

Denied attempts are the most important rows. They prove the safety layer is working.

Write verbs are classified by risk. Each class defines the minimum approval gate the runtime enforces before the action executes.
ClassGateMin roleDaily capExample
READnoneread_onlynonecrm_contacts_search
WRITE_REVERSIBLEconfirmation clickemployeeada_writes_per_daycrm_contact_update
WRITE_FINANCIALtyped phraseadminada_financial_writes_per_daycharge_invoice
IRREVERSIBLEtyped phraseownerada_irreversible_writes_per_daygdpr_delete_contact
BLOCKEDneversubscription_cancel_immediate

Roles are hierarchical — read_only < employee < admin < owner — and each class enforces a minimum. A WRITE_FINANCIAL verb denies employee and read_only at dispatch time.

Typed-phrase protocol

  1. Agent calls the verb with _confirmed=false. The runtime returns a preflight response with metadata and no side effects.
  2. Runtime generates the required phrase — e.g. CHARGE $50.00 to invoice #4242.
  3. Frontend displays the phrase verbatim and asks the human to type it. No paraphrasing.
  4. Human types it. The frontend sends { verb, args, confirm: true, typed_phrase }.
  5. Runtime validates. The phrase is bound into an HMAC nonce at step 2, so a mismatch is denied with reason: "phrase_mismatch" and tampering between dispatch and confirm is cryptographically blocked.
  6. Action executes. Receipt is returned.

The nonce embeds a timestamp plus an HMAC-SHA256 of the dispatch payload, and expires after 5 minutes. Replay or tampering fails validation.

Daily caps

Cap keyDefaultPurpose
ada_writes_per_dayBounds reversible write volume
ada_financial_writes_per_dayBounds money-movement volume
ada_irreversible_writes_per_dayBounds destructive action volume

Caps reset at midnight UTC. Exceeding one returns reason: "daily_cap_exceeded".

Cross-transport consent

TransportConfirmation UX
CLI--confirm flag + --phrase "..." option
MCP stdioNo interactive prompt — write verbs return a dispatch_pending envelope and the agent must re-call with confirm: true.
WebMCPBrowser dialog with a phrase input field
UCPRFC 9421 signed request implies consent — the buyer-agent is pre-authorized by its principal

Blocked verbs

Some names are permanently blocked — never callable by an agent, regardless of role or confirmation:

subscription_cancel_immediatecustomer_deletecompany_deleteemployee_terminate

These exist as defense-in-depth against an LLM hallucinating a dangerous verb name. The safe alternatives (gdpr_delete_contact behind an IRREVERSIBLE gate) are named differently on purpose.

The spec is the contract. Solid# is the implementation.

Every verb in the registry, across four transports — receipt-backed, reversible, and serving real businesses in production.

SolidNumber — AI That Answers Calls, Books Jobs & Runs Your Business