Mandaire MCP Wire Contract v1.0.17, Surface layer • Protocol version: 1.0.14 • Published: 2026-06-13 • Download raw markdown

Mandaire MCP Wire Contract v1.0.17

Owner: Surface layer

Canonical source: ~/openclaw/mandaire_mcp/server.py, auth_provider.py, core/mandaire/a2a/

Protocol version: 1.0.14

Spec URL: https://mandaire.org/spec/v1.0

Produced: 2026-05-20 (Surface first-session deliverable)

Supersedes: v0.1 (2026-05-18)

Revised: 2026-06-30 (v1.0.17: §4.1 + §4.2: context_card family envelope fields documented (card_id, mode, persisted, outcome, allowed_summary, withheld_topics, owner_approval_required, safe_to_send, wire_version); enforce mode behavior noted (shipped #151510). §15 context_card wire gap closed.)

Review dispatch targets:


Change log: v1.0.16 → v1.0.17

AreaChange
§4.1 JSON SchemaAdded 9 conditional top-level fields for the context_card from_kind family: card_id, mode, persisted, outcome, allowed_summary, withheld_topics, owner_approval_required, safe_to_send, wire_version. These are merged via extras at the _mandaire_envelope boundary (server.py:49003). Only present when from_kind ∈ {context_card, context_card_preview, context_card_create}.
§4.2 Field semanticsCorresponding rows added for all 9 context_card fields. Enforce-mode semantics: var/feature_flags/context_card_contract.json {"mode":"enforce"} live on .16 (2026-06-30 #151510). Leak-class violation → safe_version nulled, outcome=DENY, safe_to_send=False. Owner-keyed cards unaffected.
§15 Open ItemsContext card wire fields gap closed; moved to Closed table.

Change log: v1.0.15 → v1.0.16

AreaChange
§15 Open ItemsSuper-tool drift cron: LIVE 2026-06-28mcp-tool-count-check.timer (weekly Mon 05:00 Pacific, Persistent). Baseline-set drift check via live list_tools() — alerts Surface P3 by tool name on any unreviewed addition; removal logged only. Baseline: 9 tools (mandaire, mandaire_read, mandaire_write, mandaire_channel, mandaire_confirm + server_status, health, get_protocol_spec, tool_telemetry). When Surface adds a reviewed super-tool, update BASELINE_TOOLS in scripts/mcp_tool_count_check.py.
§15 Open ItemsChokepoint cron placement (ADR-0291): LIVE 2026-06-28. safety_chokepoint_coverage_check.py runs every 3h on mnd-app (.16) via crontab (not systemd timer — consistent with .16 infra). Reads served telemetry via MANDAIRE_BRAIN_DIR=/data/brain (required; bare canonical() under env-stripped cron resolves to OS-disk copy, not LUKS telemetry). Log: /home/david/logs/safety_chokepoint_coverage_check.log. ubu03 instance removed. Last verified: branch c_verified, total=76 calls, zero false dispatch. mcp_telemetry is per-VM-local by design (ADR-0291).

Change log: v1.0.14 → v1.0.15

AreaChange
§13.3 A2A registered skillsEnrichment skills live (co-hosted 2026-06-13): Added enrichment.coverage_check, enrichment.enrich_entity, enrichment.freshness_sweep (LIVE) and enrichment.cohort_enrich, enrichment.linkedin_enrich (Stub). Handlers wired in core/mandaire/a2a/skills.py; registered in SKILLS dict. Skill count: 7 LIVE + 4 Stub = 11 total (was 5 live + 2 stub = 7 total).
§13.5 Per-skill wire reference (new)Documents per-skill input (params.message.parts[].text + params.metadata fields) and response shape for each live skill. Sourced from core/mandaire/a2a/skills.py to prevent spec drift.

Change log: v1.0.13 → v1.0.14

AreaChange
§10.9 Requester key (new)SEC-070 D2: GET /.well-known/mandaire-requester-key.json (2026-06-13): Publicly-discoverable Ed25519 JWK for cross-tenant request signing (ADR-0224 prerequisite). No auth required. Returns {"kty":"OKP","crv":"Ed25519","use":"sig","kid":"...","x":"..."} or 404 when no key exists. Live at mcp.mandaire.com and mcp.mandaire.app. Cross-user verbs themselves (ADR-0224) remain gated on Architecture layer-3 authorization.

Change log: v1.0.12 → v1.0.13

AreaChange
§10.4 ScopeADR-0199 incremental scope consent wire documented: token missing the required scope is rejected with an explicit re-consent error (no silent downgrade).
§10.7 SDK endpointADR-0233 POST /sdk/v1/signal (control-plane, from_kind-discriminated, mTLS+CN-gated, allowlist mnd-app-*, kinds=diagnostic) and POST /sdk/v1/write added alongside /sdk/v1/select.
§13 A2ASkill registry synced to live wire: 5 live skills (added research.inferred_asset_query + co-hosted inference.claim_lookup). §13.4 + §15 updated: v0.4 OAuth is condition-gated on the multi-user / external-peer milestone (not date-scheduled), confirmed by Research 2026-06-13.

Change log: v1.0.11 → v1.0.12

AreaChange
§2 MCP ToolsADR-0021 CRUD split live: Tool table updated, 8 tools now registered (was 5). Added mandaire_read (read-only SELECT, _meta envelope), mandaire_write (INSERT/UPDATE/DELETE), mandaire_channel (channel-aware SELECT). mandaire remains but transitions to META/discovery after shim retirement.
§2 super-tool watermarkUpdated from >5 to >8 (CRUD split adds 3 permanent tools).
§10.4 ScopeAdded mandaire-v2 scope (planned; CURRENT_SCOPE still "mandaire", shim retirement pending).
§10.5 M2M / machine token (new)Added ADR-0139 B3 endpoint: POST /auth/machine/token (grant_type=client_credentials) → ES256 JWT.
§10.6 ES256 JWKS (new)JWKS endpoint: GET /.well-known/jwks.json. JWT issuer: https://mcp.mandaire.app.
§10.7 SDK endpoint (new)ADR-0141 S-O5: GET /sdk/v1/select on port 8765 (internal). Agent-auth via X-Mandaire-Agent-CN header. CN must be one of {chief, briefing, david-cli}. Returns full-fidelity mandaire_read response. Port 8768 nginx mTLS frontend pending Mandaire CA bootstrap.
§14.3 spec URL noteCorrected, spec is live at mandaire.org/spec/v1.0.

Change log: v1.0.10 → v1.0.11

AreaChange
§3.2 catalog countPublish gate check (App #70296): Corrected from "91 kinds" to "96 catalog entries (95 in _VALID_FROM_KINDS; ai_observation routes via alias map, not tuple, per App N2)." Prior hand-maintained count had drifted. Verified: _VALID_FROM_KINDS = 96 entries / 95 unique (free duplicated); spec documents all 95 + ai_observation = 96. Delta vs live = 0 undocumented kinds.
§3.2 count gateGate PASS: 0 undocumented SENSITIVE or RESTRICTED kinds in the delta. All 11 RESTRICTED and 42 SENSITIVE kinds fully documented.
§3.2 disclosure_demoFast-follow (App #70296): Added M1 convergence cross-ref pointer to §4.1 from the catalog row, discoverable at point of use for renderers hitting the list shape.

Change log: v1.0.9 → v1.0.10

AreaChange
§4.3 multi-claim aggregationInference #70282 (commit 187de03 co-doc): Documented the multi-claim provenance aggregation rule for inference_claim (N claims → single envelope provenance). Rule: MOST CONSERVATIVE source_kind wins. Ranking: llm_inferred < derived_index < observed/user_authored. One llm_inferred in the set pulls the whole envelope to −0.15. Per-claim source_kind preserved in result.claims[].source_kind for granular renderer hedging. count=0 → provenance=None (0.00; no found=false on this handler so no −0.30).
§4.3 handler statusderived_index=neutral is FINAL, not pending. Handlers live at commit 187de03.

Change log: v1.0.8 → v1.0.9

AreaChange
§3.2 Finances sectionB1 (BLOCKING, App #70277): Added tax_event (RESTRICTED), was live in _VALID_FROM_KINDS @7461 + RESTRICTED @7510 but entirely absent from spec. Compliance weight: a shipped RESTRICTED financial-PII surface must be in the audited surface contract.
§12.1 RESTRICTED tierB1: Added tax_event to RESTRICTED enumeration. Live RESTRICTED set = 11 kinds (10 documented in v1.0.8 + tax_event).
§3.2 catalog countH2 (HIGH, App #70277): Corrected to 91 kinds (was 90; +tax_event). Live _VALID_FROM_KINDS = 95 unique (96 entries, free duplicated in tuple @7372 and @7401; de-dup Surface-side pending). Count SSOT note added: generate from live tuple at publish time, not hand-maintained.
§5 ai_observation INSERT responseH3 (HIGH, App #70277): Added 4 live fields missing from documented response: claim_source_source, claim_type_source, verification_state, claim_signature. All present in live handler @15049.
§4.1 disclosure_appliedM1 (MEDIUM, App #70277): Added live-shape reconciliation note. Three shapes exist in live code: (1) [], engine not engaged; (2) dict via _disclosure_state(), Judgment engine ran (canonical shape defined in v1.0.8); (3) list-of-`{field, withheld, reason\policy_tuple}, disclosure_demo/compose path. Shapes (2) and (3) must converge to ONE before v0.1 PRODUCTION. Reserved: []` = engine not engaged; object = Judgment ran.

Change log: v1.0.7 → v1.0.8

AreaChange
§12.1 RESTRICTED tierF1 (HIGH, Judgment #70273): Added personality_profile to RESTRICTED list, was in §3.2 catalog but missing from §12.1, causing gate divergence. Added SSOT note: gate MUST derive classification from §3.2 tier column, not this table.
§4.6 Safety shimF2 (HIGH, Judgment #70273): S2/S3 HOLD suppression rule added, when Judgment disclosure-policy HOLD fires on S2/S3 topics, external response must equal NO_INFO_RESPONSE template byte-for-byte; blocked_reason goes to audit log only.
§12.4 New sectionF3 (MEDIUM, Judgment #70273): Judgment disclosure-policy gate documented (pre-fetch path, distinct from safety.review() post-handler). block_class enum: `safety_content \disclosure_policy \safety_tier_gate`.
§11.2 error codesF3 (MEDIUM, Judgment #70273): Added block_class field note for Judgment-integrated error envelopes.
§4.1 disclosure_appliedF4 (MEDIUM, Judgment #70273): Sub-schema defined, object form (Judgment ran, fields enumerated) vs [] (stub, Judgment did not run). Distinction reserved explicitly.
§4.1 judgment_appliedF6 (LOW, Judgment #70273): Added v1.1-planned field for AOR-2 / GDPR Article 30 evidence that Judgment ran. Currently disclosure_applied: [] is indistinguishable from Judgment not running.
§3.1 caller.disclosure_modeF5 (LOW, Judgment #70273): Added enum for valid values per project_policy_schema_v0_1.md. Unrecognized values default to hold.
§15 open itemsAdded judgment_applied wiring + Judgment HOLD error shape as v0.1 PRODUCTION items.
Section reference noteJudgment dispatch referenced §11/§7.4/§8 (older draft structure); current draft: §12/§4.6/§4.1. All sections located by content; noting for spec-publishing audit.

Change log: v1.0.5 → v1.0.6

AreaChange
§4.3 confidence scoringF1: Reverted erroneous v1.0.1 change. derived_index is NOT in the +0.20 bucket, live code (server.py:8616-8622) does not include it. derived_index = NEUTRAL (0.0 delta). The v1.0.1 changelog entry was wrong; live code's omission is more correct than the prior spec.
§4.3 confidence scoringF2: Documented source_kind (inference.db column, 4-value CHECK constraint) and provenance (envelope scorer key, emitted by handlers) as DISTINCT namespaces. Added intent mapping table. Pending Inference handler changes at server.py:5695/5739 (co-bump after those ship).
§4.3 SSOT noteF3: Corrected derived_index prevalence from 99.8% to 92.8% (live: 503 rows, derived_index 467/92.8%, llm_inferred 23/4.6%, observed 13/2.6%, user_authored 0).
§4.1 envelopeF4: Marked _valid_until and _prior_id as v1.1-planned. Not emitted live (_get_inference_claims does not select valid_until; prior_id is per-claim only). Wire to envelope-level deferred pending handler stabilization.
§4.4 compressionF5: Dropped origin from compression-exempt list. Not a from_kind in §3.2 catalog (orphan entry).
§15 confidence_breakdown[]F6: Calibration gate note updated, GT-B coverage ~3% (far short of per-recipient/topic CI threshold). Stays v1.1 candidate; Inference will notify when calibration crosses usable threshold.

Change log: v1.0.4 → v1.0.5

AreaChange
§3.2 dev_architecture_decisionMAJOR BUMP v1.0 → v2.0 per Dev #64082 + App #64067 meaning-doc. Wire changes: PK renamed idarchitecture_decision_id; title+chosen merged into decision_statement; 4 new required fields (evaluation_matrix, consequences, accepted_tradeoffs, exit_criteria); status enum 3-value → 5-value (drafted\ratified\revisited\superseded\rejected); made_by enum added (agent\agent_with_buyer_ratification); special_class_flags conditional-required sub-fields for schema_migration and payment.
§3.2 auto-mirror shapeAuto-mirror INSERT from dev_decision_ledger now produces v2.0 schema rows. alternatives_evaluated auto-constructed from ledger's alternatives_considered; evaluation_matrix/consequences/accepted_tradeoffs/exit_criteria are null in auto-mirror (caller populates at ratification). Response includes note field flagging null required fields.
§3.2 UPDATE mutable setNew mutable fields: supersedes_architecture_decision_id (renamed from superseded_by), ratified_by, ratified_at, ratification_notes, revisited_at, superseded_at, made_by. Auto-timestamp logic: ratified_at set automatically on status=ratified if not already set.
v1→v2 migrationHandler detects v1 table (missing evaluation_matrix column) and DROP+recreates with v2 schema. Safe: no live data in dev_architecture_decision (bootstrapped this session).
Schema filedomains/dev/schemas/dev_architecture_decision.v2.json (v1.json retained with deprecation marker).
§3.2 consent_management SELECTFixed per-integration iteration logic (App #64084). Previously collapsed all integration_auth grants to a single row (last-write-wins on consent_type key, incorrect for multi-integration). Now one row per (consent_type, integration_name) pair, each integration gets its own active_grants entry.
§3.2 consent_management INSERTAdded applies_to_integration_name field (REQUIRED when applies_to_consent_type=integration_auth, optional otherwise). Stored as integration_name on the withdrawal event row. Included in INSERT response and Privacy erasure dispatch.

Change log: v1.0.3 → v1.0.4

AreaChange
§3.2 dev namespaceHandlers SHIPPED for dev_intent_brief, dev_decision_ledger, dev_architecture_decision per Dev #64027. Catalog text updated to final semantic descriptions. Added UPDATE verb to dev_intent_brief + dev_architecture_decision. dev_tech_debt remains gated (v0.2 wave).
§3.2 dev descriptionsReplaced placeholder ETA-text with final descriptions from Dev's schema comments. Auto-mirror trigger from dev_decision_ledgerdev_architecture_decision documented (IRREVERSIBLE class or tag in promo-trigger set of 8).
§5.29 correctionReplaced §5.20.2 cites (audit-loop discipline, wrong section) with canonical §5.29 (reversibility enum: REVERSIBLE\IRREVERSIBLE\PARTIAL). Affects reversibility_class notes in §3.2 dev section.
Catalog count90 kinds (unchanged); dev_tech_debt count held pending v0.2.

Change log: v1.0.2 → v1.0.3

AreaChange
§3.2 statusCorrected: App rigorous per-kind grep confirmed all 87 existing catalog entries are SHIPPED. Prior "17 PLANNED" estimate was a regex false-negative on App's side. Preamble updated accordingly.
§3.2 new kindsAdded lineage (STANDARD, Enrichment/graph), strategic_context (STANDARD, alias → context), list_topics (STANDARD, alias → topic). Catalog grows to 90.
§3.2.1 new sectionAliases and legacy migration pointers: flag_inferenceai_observation, correct_profilecorrection verb=INSERT, eventsevent, photosphoto, strategic_contextcontext, list_topicstopic.
§3.2.2 new sectionDeprecated kinds: synthesis (90-day window), list_state (immediate, never had a from_kind handler), file_store_status (immediate, routes via from_kind=file).
§3.2 consentAdded consent_management (SENSITIVE), Privacy AOR-2 / SOC 2 P2. SELECT: active consent grants. INSERT: withdrawal + Privacy erasure dispatch.
§3.2 dev namespaceAdded dev_* gated namespace: dev_intent_brief, dev_decision_ledger, dev_architecture_decision, dev_tech_debt (all SENSITIVE, gated pending Dev schemas).

Change log: v1.0.1 → v1.0.2

AreaChange
§4.1 expectednessHIGH: fixed field/value mismatch, score corrected from string enum to float [0.0–1.0]; surface_recommendation now carries the 5-value enum (SUPPRESS, SUPPRESS_OBVIOUS_SURFACE_NOVEL, SURFACE_NOVEL, SURFACE_INTERESTING_IF_TRUE, SURFACE_AS_IS); framing_hint added (optional). Prior schema rejected every valid response.
§4.3 confidence~~HIGH: derived_index added to +0.20 bucket.~~ RETRACTED in v1.0.6. Live code (server.py:8616-8622) never included derived_index in +0.20. SSOT note retained; percentage + mapping corrected in v1.0.6.
§4.1 cognitive_modeMEDIUM: 6 missing fields added under properties (not required) for minor-bump compatibility: length, structure, decisions, max_words, policy_source, user_mode_axes. Will be promoted to required in v1.1.
§4.1 envelopeMEDIUM: 3 optional inference-origin fields added: _inference_origin (field-level provenance list), _valid_until (v1.1-planned, not emitted live), _prior_id (v1.1-planned, not emitted live).
§4.2 semanticsUpdated expectedness description to match corrected schema.
§15 confidence_breakdown[]CONFIRMED v1.1: gated on AOR-3 calibration data quality (FP rate currently unmeasurable; GT coverage 0%). Anticipated shape documented in §15 for planning.

Change log: v1.0 → v1.0.1

AreaChange
§3.2 catalogAdded status column (SHIPPED/DRAFT/PLANNED/DEPRECATED) to all tables; deprecated kinds listed in §3.2 footer
§3.2 tierspersonality_profile escalated SENSITIVE → RESTRICTED (App #63870 recommendation; Big Five inferred from inbound text; no disclosure-engine rule yet)
§3.2 footerAdded genetic-data review gate
§5 entity_refsF1: added uuid field; clarified uuid wins over integer entity_id; stable alias (email/phone) recommended for cross-rebuild durability
§5 schemaF2: purpose moved to request-envelope-level note (not payload field)
§5 claim_tierF3: closed enum added (factual/behavioral/preference/preference_negative/boundary)
§7 key_peopleF1 applied: entity_iduuid (preferred) + entity_id (best-effort)
§9 resolutionF4: step 7 added, contact-tagged entities win ties at every preceding step
§3.2 identityIdentity contract (uuid stable, entity_id not invariant) lifted to §3.2 preamble

Change log: v0.1 → v1.0

AreaChange
From_kind catalog87 active kinds (was ~60); see §3.2 for full list
ai_observationNew INSERT path with complete payload schema (§5)
decisionNew SELECT path: audit click-through via from_id (§6)
situation_briefPromoted to v0.2: Analysis situation_lookup() primary path live; structured output schema documented (§7)
catch_me_upv0.2.2 output schema documented (§8)
entity_lookupOutput schema documented (§9)
ProvenanceArticle 50 provenance triple attached to every envelope (§4.5)
Safety tier gateR2 enforcement live: RESTRICTED / SENSITIVE / STANDARD tiers (§11)
Health kinds7 health from_kinds added (RESTRICTED tier) (§3.2)
free_accessfree_access_token + free_access_audit from_kinds (§3.2)
disclosure_demoNew demo wedge from_kind (§3.2)
Versioning policyFormal policy codified (§12)

1. Transport

SettingValue
ProtocolMCP (Model Context Protocol) via FastMCP
Default transportstreamable-http
Alt transportstdio (Claude Desktop; MCP_TRANSPORT=stdio)
Bind127.0.0.1:8765 (nginx-proxied externally)
Mount path/mcp
Session modelStateless (stateless_http=True)
Public URLshttps://mcp.mandaire.com (primary), https://mcp.mandaire.app (alias)
SSEDeferred to v0.2 streaming path

2. Registered MCP Tools

8 tools registered (ADR-0021 CRUD split live as of 2026-06-06).

ToolAnnotationsPurposeTier
mandairereadOnlyHint=TruePrimary read entry point; also META/discovery (transitions after shim retirement)All tiers
mandaire_readreadOnlyHint=TrueREAD-ONLY SELECT with _meta envelope ({result, _meta})read_only, write, channel, viewer_read
mandaire_writereadOnlyHint=FalseINSERT/UPDATE/DELETE/UPSERT/CORRECT verbschannel (personal) only
mandaire_channelreadOnlyHint=TrueChannel-aware SELECT (injects disclosure_mode=channel)channel (personal) only
healthreadOnlyHint=TrueHTTP /health probe (no auth required)All
server_statusreadOnlyHint=TrueDB counts + valid_from_kinds catalogAll
get_protocol_specreadOnlyHint=TrueMachine-readable protocol surfaceAll
tool_telemetryreadOnlyHint=TrueMCP call telemetry from mcp_telemetry.dbAll

Super-tool watermark: >8 registered MCP tools = regression. PR-time check enforces this.

Grant tier → tool access (enforced at tools/list + server-side dispatch):

TierAllowed tools
read_onlymandaire, mandaire_read
viewer_readmandaire, mandaire_read
writemandaire, mandaire_read, mandaire_write
channelmandaire, mandaire_read, mandaire_write, mandaire_channel
None (no active grant row)[], fail-closed

Scope (current vs planned):

Annotation presets:


3. Request Envelope, mandaire()

3.1 Full JSON Schema


{
  "$schema": "https://json-schema.org/draft/2020-12",
  "title": "MandaireRequest",
  "type": "object",
  "properties": {
    "verb": {
      "type": "string",
      "enum": ["SELECT", "INSERT", "UPDATE", "DELETE", "READ", "GET", "QUERY", "CREATE", "ADD", "PATCH", "REMOVE"],
      "default": "SELECT",
      "description": "Operation verb. SELECT/READ/GET/QUERY are aliases. INSERT/CREATE/ADD are aliases. UPDATE/PATCH are aliases. DELETE/REMOVE are aliases."
    },
    "from_kind": {
      "type": ["string", "null"],
      "description": "The data domain to query. See §3.2 for full catalog."
    },
    "from_id": {
      "type": ["string", "null"],
      "description": "Canonical ID for direct-lookup path. UUID or integer string depending on from_kind."
    },
    "from_match": {
      "type": ["string", "null"],
      "description": "Free-text match string (SELECT only). Used for fuzzy name/topic matching."
    },
    "fields": {
      "type": ["array", "null"],
      "items": {"type": "string"},
      "description": "Field projections within from_kind. Behavior is from_kind-specific."
    },
    "payload": {
      "oneOf": [
        {"type": "object"},
        {"type": "array"},
        {"type": "null"}
      ],
      "description": "Write payload (INSERT/UPDATE/DELETE). Shape is from_kind-specific. See §5 for ai_observation schema."
    },
    "since": {
      "type": ["string", "null"],
      "format": "date-time",
      "description": "ISO 8601 UTC lower bound (inclusive). e.g. '2026-01-01T00:00:00Z'"
    },
    "until": {
      "type": ["string", "null"],
      "format": "date-time",
      "description": "ISO 8601 UTC upper bound (exclusive)."
    },
    "sources": {
      "type": ["array", "null"],
      "items": {"type": "string"},
      "description": "Source filter. Restricts which data sources contribute."
    },
    "filters": {
      "type": ["object", "null"],
      "description": "Pre-computation filters (SQL WHERE equivalent). Shape is from_kind-specific."
    },
    "having": {
      "type": ["object", "null"],
      "description": "Post-computation filters (SQL HAVING equivalent)."
    },
    "purpose": {
      "type": ["string", "null"],
      "description": "Intent of the call. Required for SENSITIVE and RESTRICTED from_kinds. Suggested values: preparing_for_meeting | drafting_reply | researching_history | evaluating_relationship | disambiguating | ai_writeback_from_conversation"
    },
    "context_note": {
      "type": ["string", "null"],
      "description": "Working hypothesis for retrieval bias. Steers semantic search."
    },
    "caller": {
      "type": ["object", "null"],
      "properties": {
        "who": {"type": "string"},
        "channel": {"type": "string"},
        "disclosure_mode": {
          "type": "string",
          "enum": ["direct", "relay", "delegated", "federated", "professional_update", "investment_discussion", "casual_chat", "medical_consultation", "legal_proceeding"],
          "description": "Maps to `context` axis of the 5-axis disclosure tuple. Unrecognized values default to hold (no policy match → hold per project_policy_schema_v0_1.md §Evaluation Order)."
        }
      },
      "description": "Caller context. Default: self/mirror mode (David's own room)."
    },
    "depth": {
      "type": "string",
      "enum": ["minimal", "standard", "deep", "exhaustive"],
      "default": "standard",
      "description": "Composition depth. deep/exhaustive on person/household/relationship escalates to SENSITIVE tier."
    },
    "format_hint": {
      "type": "string",
      "enum": ["auto", "prose", "structured", "both", "ui"],
      "default": "auto",
      "description": "Output format preference."
    },
    "max_chars": {
      "type": ["integer", "null"],
      "minimum": 1,
      "description": "Cap on rendered_text length."
    },
    "ask": {
      "type": ["string", "null"],
      "description": "Free-text question. No from_kind → recall fallback."
    }
  },
  "required": []
}

3.2 From_kind Catalog (v1.0.11, 96 catalog entries: 95 in _VALID_FROM_KINDS + ai_observation via alias map)

Identity contract: uuid is the stable entity identity across ER rebuilds. Integer entity_id is NOT invariant, it may rotate when entities are merged. Email/phone aliases are stable across rebuilds and recommended for cross-rebuild durability. Any cross-reference that must survive an ER merge must use uuid, not entity_id.

Status taxonomy: All 91 kinds in this catalog are SHIPPED (confirmed live handlers in mandaire_mcp/server.py). Deprecated kinds are documented in §3.2.2. Gated kinds (namespace reserved, handler pending) are marked with a GATED note in their catalog entry.

Live count note (App H2 #70277): Live _VALID_FROM_KINDS = 95 unique entries (96 in tuple, free duplicated at positions @7372 and @7401; Surface de-dup pending). The 4-kind gap between documented (91) and live (95) is aliases and potential undocumented kinds. SSOT rule: generate the advertised count from len(set(_VALID_FROM_KINDS)) at publish time, not hand-maintained.

Genetic/health data gate: Any from_kind returning genetic, genomic, or familial disease-risk data requires RESTRICTED tier review + explicit purpose declaration before deployment. No such kinds are currently active; this gate applies to future additions.

Core life-graph

from_kindTierVerb supportDescription
personSTANDARD (SENSITIVE at depth≥deep)SELECTPeople lookup: entity profile, aliases, interaction counts
householdSTANDARD (SENSITIVE at depth≥deep)SELECTHousehold membership + shared context
relationshipSTANDARD (SENSITIVE at depth≥deep)SELECTEntity-pair relationship: tie strength, history
emailSTANDARDSELECTEmail messages (threat model B: is_retrieved_content=true)
messageSTANDARDSELECTiMessage/WhatsApp messages (threat model B)
photoSTANDARDSELECTPhoto timeline for entity
eventSTANDARDSELECTCalendar events
tripSTANDARDSELECTTravel + trip records
noteSTANDARDSELECT, INSERTNotes (threat model B on SELECT)
taskSTANDARDSELECT, INSERTTasks from tasks.db
stateSTANDARDSELECT, INSERT, UPDATEEphemeral key-value state store
inferenceSTANDARDSELECT, UPDATE, DELETEInference claims from inference.db
correctionSTANDARDINSERTEntity field corrections
topicSTANDARDSELECTTopic corpus: drifts / decisions / threads
contextSTANDARDSELECTContext docs (topic_corpus)
areaSTANDARDSELECTLife areas listing
recallSTANDARDSELECTFTS + semantic search across recall.db
freeSTANDARDSELECTFree-form question (recall + optional synthesis)
fileSTANDARDSELECTFiles from corpus (threat model B)
attachmentSTANDARDSELECTEmail attachments
systemSTANDARDSELECTServer metadata: DB counts, valid_from_kinds
server_statusSTANDARDSELECTAlias for from_kind=system

Operating profile

from_kindTierDescription
profile / user_profileSENSITIVEUser profile prose
communication_guideSENSITIVECommunication style guide
assistant_identitySENSITIVEAssistant identity doc
operating_instructionsSENSITIVECurrent operating instructions
trust_vectorSENSITIVETrust calibration state
correction_historyRESTRICTEDFull aggregate of field corrections
briefing_styleSENSITIVEPreferred briefing format
operating_profileSENSITIVEFull operating profile composite

Decisions + standing state

from_kindTierDescription
decisionsSENSITIVEStanding decisions list
disclosure_policyRESTRICTEDPer-(person, topic, context) disclosure policy graph
decisionSENSITIVESingle decision: from_id= for click-through
compare_optionsSENSITIVETradeoff matrix for in-flight decision
recent_decisionsSENSITIVEAlias for topic+fields=decisions

Composed surfaces

from_kindTierDescription
catch_me_upSENSITIVEFull catch-up composite: tasks, threads, drifts, decisions, events, commitments
situation_briefSENSITIVEStructured brief for a named ongoing situation (from_match required)
user_contextSENSITIVEIdentity + user_profile + active_situations
situationSENSITIVEactive_situations.db umbrella

Communication surfaces

from_kindTierDescription
communication_styleSENSITIVEPer-recipient style + David's register
draft_replySENSITIVEComposed: inbound thread + recipient style + similar prior exchanges
draft_emailSENSITIVEComposed: new outbound with recipient style + last contact
outbound_styleSENSITIVEComposed: L1 generic + L2 channel + L3 mode + L4 recipient

Activity / silence / commitments

from_kindTierDescription
outbound_silenceSENSITIVEDavid sent last, awaiting reply (cadence-aware)
followup_dueSENSITIVEDavid received last, owes reply (cadence-aware)
unread_inbound_importantSENSITIVERecent inbound from importance-class senders
open_commitmentsSENSITIVELife commitments (sent + inbound) with temporal anchor
active_threadsSENSITIVEAlias for topic+fields=threads
recent_driftsSENSITIVEAlias for topic+fields=drifts

Pattern detection

from_kindTierDescription
trendSENSITIVETime-series direction for any cross-source metric
anomalySENSITIVECross-source change detector: recent vs baseline z-score
ratio_shiftSENSITIVEPair-metric ratio changes
drift_alertSENSITIVEComposite of trend + anomaly + ratio_shift
anomaly_scanSENSITIVEFind contact_keys that went silent
recurring_meetingSENSITIVEDetect weekly/biweekly/monthly cadence + breaks

Pipeline / review

from_kindTierDescription
pipeline_stageSTANDARDPer-phase pipeline.db progress + watermarks
pipeline_approvalSTANDARDCloud-batch review-gate state
roadmapSTANDARDtasks.db WHERE tags LIKE '%roadmap%'
mcp_findingSTANDARDINSERT: proactive agent improvement signals
open_writeback_slotsSTANDARDOpen nil-result slots for principal (feedback loop)

Dimension / pipeline graph

from_kindTierDescription
dimensionSTANDARDList 5-W dimensions + leaf counts
dimension_node / nodeSTANDARDGet one leaf (WHO/WHAT/WHEN/HOW/WHERE)
timelineSTANDARDCross-dim query: turns matching WHO+WHAT+WHEN+HOW+WHERE

Enrichment + graph

from_kindTierDescription
enrichSTANDARDWeb-enrichment overlay on entity: company/title/role/news
social_graphSENSITIVEMulti-entity edge traversal from people.db
cohortSTANDARDInstitutional/temporal/topical entity groupings
entity_stateSTANDARDTie strength + CI + tie_trend + interaction_frequency from inference.db
inference_claimSTANDARDCanonical inferences: claim_text + value + CI + evidence_refs
personality_profileRESTRICTEDBig Five inferred from entity's inbound text
surfacing_scoreSENSITIVERank candidates by time-criticality × importance × user-mode-match

Cognitive / input modeling

from_kindTierDescription
user_modeSENSITIVE6-axis input-mode detection: WHAT/WHERE/WHEN/HOW/WHY/HOW_MUCH
conflictSTANDARDScheduling-conflict detection: overlapping calendar events
calendarSTANDARDCalendar discovery: list calendars with owner + source + event_count
agent_statusSTANDARDPer-agent heartbeat + current in-flight queue item + last outcome

AI writeback

from_kindTierVerbsDescription
ai_observationSTANDARDINSERT, SELECTAI-synthesized inference writeback to inference.db
from_kindTierVerbsDescription
free_access_tokenSTANDARDINSERT, DELETE, SELECTCreate/revoke/list bearer-secret free-access links
free_access_auditSTANDARDSELECTAudit log of free-user queries

Health (RESTRICTED tier, audience_tier_max=1)

from_kindTierDescription
latest_vitalsRESTRICTEDMost recent HR / RR / SpO2 / BP / weight
vitals_trendRESTRICTEDTime-series of an HKQuantityType over N days
lab_resultsRESTRICTEDFHIR Observation lab values
medications_activeRESTRICTEDMedicationRequest where status=active
immunizationsRESTRICTEDVaccination history
ecg_summaryRESTRICTEDECG classifications over time
conditionsRESTRICTEDActive medical conditions

Finances (RESTRICTED tier, audience_tier_max=1)

from_kindTierVerbDescription
tax_eventRESTRICTEDSELECTTax document data: result={tax_returns[], w2_forms[], count}. Extras: audience_tier_max:1, pii_class:"financial", privacy_policy:"raw_text/source_file withheld", filter_hint. raw_text and source_file deliberately excluded from output. Filters: year (YYYY format), limit (default 50). purpose required.

Disclosure

from_kindTierDescription
disclosure_demoSENSITIVEPer-(recipient, topic, context) disclosure wedge; synthetic data + hardcoded policy. See §4.1 M1 note: this handler emits disclosure_applied as a list-of-`{field, withheld, reason\policy_tuple}`, diverges from the canonical object form; must converge before v0.1 PRODUCTION.
from_kindTierVerbsDescription
consent_managementSENSITIVESELECT, INSERTSELECT: active consent grants for principal. active_grants[] is one row per active grant, integration_auth grants produce one row per integration (integration_name field, e.g. "gmail", "icloud_mail"). revoked_grants[] same shape with revoked=true, revoked_at, revocation_event_id. INSERT consent_type=consent_withdrawal: records withdrawal, dispatches to Privacy for DSAR erasure within 30 days. Art. 17(3)(b): consent log retained regardless. applies_to_integration_name required (and included in response) when applies_to_consent_type=integration_auth.

mandaire.dev namespace (v1.0 SHIPPED; dev_architecture_decision bumped v2.0 per Dev #64082)

Schemas: domains/dev/schemas/dev_.v1.json (dev_architecture_decision: dev_architecture_decision.v2.json). Storage: brain/dev.db. All reversibility_class values use canonical OS.md §5.29 enum (REVERSIBLE\|IRREVERSIBLE\|PARTIAL). dev_tech_debt is the only remaining gated kind (v0.2 wave).

from_kindTierVerbsDescription
dev_intent_briefSENSITIVESELECT, INSERT, UPDATEPre-execution document the AI-CTO produces BEFORE any non-trivial build step. Names what success looks like, what could go wrong (pre-mortem), what is explicitly NOT in scope. Buyer ratifies before code is written. The single artifact that distinguishes Mandaire .dev from Cursor/Devin/Cowork/Operator per PRODUCT.md competitive table. 1 per build cycle. Schema at domains/dev/schemas/dev_intent_brief.v1.json. HARD_RULE #14 floor: handler surfaces _hr14_block warning on SELECT when scope_of_effort.reversibility_class=IRREVERSIBLE AND status NOT IN (ratified, edited_then_ratified). UPDATE mutable fields: status, ratified_at, ratified_by, scope_amendments[] (append-only).
dev_decision_ledgerSENSITIVESELECT, INSERTPer-build-decision record that accumulates across all builds in a project. Every code-touching choice gets a time-stamped row with rationale + alternatives + reversibility_class. High-volume (5-50 per build; 1k-10k lifetime per project). Ledger never starts over (CR1 immutability: supersession = new row + parent_decision_id, not mutation). claim_signature dedup: sha256(decision_text+affects) prevents agent-loop redundancy. Auto-mirror trigger: INSERT with reversibility_class=IRREVERSIBLE OR tags ∩ {auth, data_substrate, multi_tenancy, language_choice, deployment_substrate, payment, schema_migration, data_loss_risk} → atomic mirror INSERT to dev_architecture_decision (v2.0 schema). Schema: domains/dev/schemas/dev_decision_ledger.v1.json (v1.1, extended promo-trigger tag set per App #64039).
dev_architecture_decisionSENSITIVESELECT, INSERT, UPDATEv2.0 (MAJOR BUMP per Dev #64082 + App #64067 meaning-doc). Load-bearing framework-level choices (database engine, auth model, language, deployment substrate, multi-tenancy boundary) that constrain all subsequent builds. Low-volume, high-weight. Created directly OR auto-mirrored from dev_decision_ledger promo trigger. PK field: architecture_decision_id (renamed from v1's id). SELECT default status filter: ratified. INSERT required fields: architecture_decision_id, project_id, decision_statement (≤500 chars, falsifiable), alternatives_evaluated[] (minItems:1, each {name, disqualifier}), evaluation_matrix (5 axes: cost/reversibility/scaling_ceiling/complexity/risk, each maps alternative→{text,score}), rationale (≤500 chars), consequences[] (3-7 deterministic implications), accepted_tradeoffs[] (2-5 items), exit_criteria[] (1+ revisit conditions), reversibility_class, status, made_by, drafted_at, schema_version. Status enum (5 values): `drafted\ratified\revisited\superseded\rejected. made_by enum: agent\agent_with_buyer_ratification (flips to latter on ratify). special_class_flags: conditional-required sub-fields when schema_migration=true (migration_plan + rollback_strategy) or payment=true (compliance_check_pci_dss + billing_state_migration_plan). UPDATE mutable fields: status, supersedes_architecture_decision_id, linked_decision_ledger_ids[] (append-only), ratified_by, ratified_at, ratification_notes, revisited_at, superseded_at, made_by. UPDATE ratify rule: status=ratified requires ratified_by; auto-sets made_by=agent_with_buyer_ratification. Schema: domains/dev/schemas/dev_architecture_decision.v2.json`.
dev_tech_debtSENSITIVESELECT, INSERTGATED, v0.2 wave. Tech-debt ledger. P1 per App #63923 demotion. Meaning-doc + schema land in v0.2 alongside taste_memory extraction. Handler returns 'schema not yet landed' until v0.2 ships.

New in v1.0.3

from_kindTierDescription
lineageSTANDARDUpstream/downstream traversal from `from_id=entity_uuid>. Traverses inference.db prior_id chain + people.db merge graph. filters.node_kind: claim (default) or entity`.
strategic_contextSTANDARDAlias for from_kind=context with from_match=. Legacy search_context / get_strategic_context migration target. Routes to context handler unchanged.
list_topicsSTANDARDAlias for from_kind=topic with no from_match. Lists topic corpus with optional filters.area, filters.type, filters.domain, filters.limit.

3.2.1 Aliases and Legacy Migration Pointers

These are accepted from_kind values that route to a canonical handler. Callers SHOULD use the canonical form. Aliases are supported indefinitely unless explicitly deprecated with a 90-day window per §14.1.

AliasCanonicalNotes
flag_inferenceai_observationLegacy tool name from pre-super-tool era. Both SELECT and INSERT route identically.
correct_profilecorrection with verb=INSERTLegacy tool name. Use mandaire(verb="INSERT", from_kind="correction", ...) directly.
eventseventPlural form.
photosphotoPlural form.
strategic_contextcontextSee §3.2 new entries.
list_topicstopicSee §3.2 new entries.
commitmentopen_commitments"open commitments" synonym.
cross_source_recallcatch_me_upMulti-source synthesis synonym.
documentrecallDocument search → recall with ask.
server_statussystemStatus alias, returns same system payload.
user_profileprofileProfile alias.
dimension_nodenodeDimension node alias (canonical is node).

3.2.2 Deprecated Kinds

Deprecated from_kinds return a migration hint but remain in _VALID_FROM_KINDS until the deprecation window closes per §14.1 (90 days minimum).

from_kindStatusMigration pathDeprecated since
synthesisDEPRECATED, 90-day windowmandaire(from_kind="system", fields=["synthesis"])2026-05-20 (v1.0.3). Window closes 2026-08-18.
list_stateDEPRECATED, immediatemandaire(from_kind="state", filters={"scope": "..."}), never had a real from_kind handler; was documentation error in migration help-text.2026-05-20 (v1.0.3).
file_store_statusDEPRECATED, immediatemandaire(from_kind="file", ask="store_status"), same; was documentation error in migration help-text.2026-05-20 (v1.0.3).

4. Response Envelope

All responses use the same envelope. Error responses include the same metadata keys.

4.1 Full JSON Schema


{
  "$schema": "https://json-schema.org/draft/2020-12",
  "title": "MandaireResponse",
  "type": "object",
  "required": ["ok", "verb", "result", "rendered_text", "structured",
               "approval_required", "sources_used", "disclosure_applied",
               "absent_knowledge_caveats", "suggestions", "confidence",
               "signals", "cognitive_mode", "expectedness"],
  "properties": {
    "ok":                      {"type": "boolean"},
    "verb":                    {"type": "string"},
    "result":                  {"oneOf": [{"type": "object"}, {"type": "array"}, {"type": "null"}]},
    "rendered_text":           {"type": "string"},
    "structured":              {"oneOf": [{"type": "object"}, {"type": "array"}, {"type": "null"}]},
    "artifact_id":             {"type": ["string", "null"]},
    "approval_required":       {"type": "boolean"},
    "sources_used":            {"type": "array", "items": {"type": "string"}},
    "disclosure_applied": {
      "oneOf": [
        {
          "type": "array",
          "maxItems": 0,
          "description": "[], engine not engaged: Judgment did not run OR nothing withheld. Reserved meaning; do NOT use for populated results."
        },
        {
          "type": "object",
          "description": "Judgment ran. Object form = full Judgment engine evaluation result. CONVERGENCE NOTE (App M1 #70277): live code produces three shapes, (1) [] via _disclosure_state()-fallback paths, (2) this dict via _disclosure_state() @server.py:1581, (3) list-of-{field,withheld,reason|policy_tuple} via disclosure_demo/compose path @16885. Shapes (2) and (3) must converge to this object form before v0.1 PRODUCTION. Until then, renderers should handle all three gracefully.",
          "required": ["mode", "policy_version", "receiver_kind", "context"],
          "properties": {
            "mode":                {"type": "string", "enum": ["external", "internal", "relay", "direct"]},
            "policy_version":      {"type": "string", "description": "Disclosure policy schema version, e.g. '0.1'"},
            "receiver_kind":       {"type": "string", "enum": ["family", "friend", "manager", "report", "investor", "client", "public", "internal"]},
            "context":             {"type": "string", "description": "Interaction context from caller.disclosure_mode"},
            "gating_reason":       {"type": ["string", "null"]},
            "redacted_fields":     {"type": "array", "items": {"type": "string"}},
            "pii_classes_blocked": {"type": "array", "items": {"type": "string"}},
            "rule_id":             {"type": ["string", "null"]},
            "spillover_risk":      {"type": "number", "minimum": 0.0, "maximum": 1.0}
          }
        }
      ]
    },
    "absent_knowledge_caveats":{"type": "array", "items": {"type": "string"}},
    "suggestions":             {"type": "array"},
    "confidence":              {"type": "number", "minimum": 0.0, "maximum": 1.0},
    "signals":                 {"type": "array", "items": {"type": "string"}},
    "cognitive_mode": {
      "type": "object",
      "required": ["cycle", "tone", "preferred_kinds", "avoid", "hour_pt", "weekday", "iso_pt", "rendering_hint"],
      "properties": {
        "cycle":           {"type": "string"},
        "tone":            {"type": "string"},
        "preferred_kinds": {"type": "array", "items": {"type": "string"}},
        "avoid":           {"type": "array", "items": {"type": "string"}},
        "hour_pt":         {"type": "integer"},
        "weekday":         {"type": "string"},
        "iso_pt":          {"type": "string"},
        "rendering_hint":  {"type": "string"},
        "length":          {"type": "string", "description": "Rendering length policy compiled from briefing_style_by_mode.md (v1.0.1, optional, promoted to required in v1.1)"},
        "structure":       {"type": "string", "description": "Structure policy (prose | bullets | mixed)"},
        "decisions":       {"type": "string", "description": "Decision-surfacing policy for this mode"},
        "max_words":       {"type": "integer", "description": "Hard cap on rendered output word count"},
        "policy_source":   {"type": "string", "description": "Artifact path for the briefing_style_by_mode.md that produced this policy"},
        "user_mode_axes":  {"type": "object", "description": "6-axis input mode (WHAT/WHERE/WHEN/HOW/WHY/HOW_MUCH). WHEN populated from clock; other axes nullable hooks for from_kind=user_mode to fill."}
      }
    },
    "expectedness": {
      "type": "object",
      "required": ["score", "rationale", "surface_recommendation"],
      "properties": {
        "score":                  {"type": "number", "minimum": 0.0, "maximum": 1.0, "description": "Continuous novelty score; lower = more expected/routine"},
        "rationale":              {"type": "string"},
        "surface_recommendation": {"type": "string", "enum": [
          "SUPPRESS",
          "SUPPRESS_OBVIOUS_SURFACE_NOVEL",
          "SURFACE_NOVEL",
          "SURFACE_INTERESTING_IF_TRUE",
          "SURFACE_AS_IS"
        ]},
        "framing_hint":           {"type": "string", "description": "Optional renderer framing suggestion"}
      }
    },
    "_inference_origin":       {"type": "array", "items": {"type": "string"}, "description": "Optional. Field names in result whose value originated from inference.db (vs deterministic count or raw observed). Lets renderers distinguish LLM-derived fields from algorithmic ones. e.g. ['tie_strength', 'tie_trend', 'info_quality']"},
    "_valid_until":            {"type": ["string", "null"], "format": "date-time", "description": "v1.1-planned. For from_kinds returning inference claims: the validity window of the primary claim (from inference.db valid_until). NOT emitted live, _get_inference_claims does not SELECT valid_until. Null until handler ships."},
    "_prior_id":               {"type": ["integer", "null"], "description": "v1.1-planned. For inference SELECT envelopes: the prior_id chain pointer if this claim supersedes a previous one. NOT emitted at envelope level live, prior_id is per-claim only inside claims[]. Non-null enables renderers to display supersession history."},
    "judgment_applied": {
      "type": "object",
      "description": "v1.1-planned. Evidence that Judgment ran (AOR-2 audit trail / GDPR Article 30). NOT emitted live, Judgment integration not yet wired. When absent, disclosure_applied: [] is indistinguishable from Judgment not running; judgment_applied closes that gap.",
      "properties": {
        "version":        {"type": "string"},
        "decision":       {"type": "string", "enum": ["ALLOW", "HOLD", "WARN"]},
        "policy_version": {"type": "string"},
        "rule_id":        {"type": ["string", "null"], "description": "Matched rule_id from policy, or null if default-hold (no rule match)"}
      }
    },
    "error":                   {"type": "string", "description": "Present when ok=false only"},
    "hint":                    {"type": "string", "description": "Present with error, not on success"},
    "is_retrieved_content":    {"type": "boolean", "description": "true when from_kind ∈ {email, message, note, recall, file, image}"},
    "safety_warning":          {"type": "string", "description": "Present when safety.review() returns WARN"},
    "provenance": {
      "type": "object",
      "description": "Article 50 provenance triple",
      "properties": {
        "producer":  {"type": "string"},
        "channel":   {"type": "string"},
        "audience":  {"type": "string"},
        "ts":        {"type": "string", "format": "date-time"}
      }
    },
    "_compression": {
      "type": "object",
      "description": "Present when envelope was compressed by local Qwen 14B (>24,000 chars)",
      "properties": {
        "applied":          {"type": "boolean"},
        "original_chars":   {"type": "integer"},
        "compressed_chars": {"type": "integer"},
        "model":            {"type": "string"},
        "provider_used":    {"type": "string"},
        "fallback_used":    {"type": "boolean"}
      }
    },
    "card_id":                 {"type": ["string", "null"], "description": "context_card family only. Stable card ID; non-null only when from_kind=context_card_create AND persisted."},
    "mode":                    {"type": ["string", "null"], "description": "context_card family only. dry_run | preview | persist."},
    "persisted":               {"type": ["boolean", "null"], "description": "context_card family only. True when card written to context_cards (context_card_create)."},
    "outcome":                 {"type": ["string", "null"], "description": "context_card family only. PERMIT | DENY | WITHHOLD policy verdict."},
    "allowed_summary":         {"type": ["string", "null"], "description": "context_card family only. Prose of what the card would share; null on DENY."},
    "withheld_topics":         {"type": "array", "items": {"type": "string"}, "description": "context_card family only. Topics withheld per per-(R,T,C,S) policy; [] when all allowed."},
    "owner_approval_required": {"type": ["boolean", "null"], "description": "context_card family only. True if safe_to_send=False OR high-regret OR outcome != PERMIT."},
    "safe_to_send":            {"type": ["boolean", "null"], "description": "context_card family only. Fail-closed in enforce mode — any leak-class violation sets safe_version=null, outcome=DENY."},
    "wire_version":            {"type": "string", "description": "context_card family only. Wire protocol version, e.g. 'context_card/v0.2'."}
  }
}

4.2 Field semantics

FieldMandatoryNotes
okyestrue = inner call succeeded
verbyesEcho of requested verb
resultyesRaw inner output. {} on error. On compression: {"compressed_summary": str, "_original_result_was_compressed": true}
rendered_textyesHuman-readable prose; respect max_chars. Empty on error
structuredyesMachine-readable payload; preserved unmodified even on compression
artifact_idnoPresent when a durable artifact was created
approval_requiredyesTrue when result contains user-confirmable write
sources_usedyesList of source identifiers contributing to result
disclosure_appliedyesCurrent disclosure state
absent_knowledge_caveatsyesHedges Mandaire flagged. Surface these to the user.
suggestionsyesAdvisory follow-up call shapes. Hints only, do not auto-fire
confidenceyesEnvelope-level [0.0–1.0]. Drives hedging in rendered output
signalsyesEvidence trail for confidence score
cognitive_modeyesAlways present. Renderer MUST adapt output shape to this
expectednessyesObject: score (float 0–1), surface_recommendation (SUPPRESS / SUPPRESS_OBVIOUS_SURFACE_NOVEL / SURFACE_NOVEL / SURFACE_INTERESTING_IF_TRUE / SURFACE_AS_IS), rationale, optional framing_hint
errorconditionalPresent when ok=false
hintconditionalPresent with error
is_retrieved_contentconditionaltrue when from_kind ∈ {email, message, note, recall, file, image}. Threat model B marker, body items also receive sentinel wrapping
safety_warningconditionalDefamation disclaimer when safety.review()=WARN
provenanceconditionalArticle 50 triple (producer, channel, audience, ts)
_compressionconditionalPresent when local Qwen compressed the envelope
card_idconditionalcontext_card family only. Stable card ID; non-null only when from_kind=context_card_create AND persisted
modeconditionalcontext_card family only. dry_run | preview | persist
persistedconditionalcontext_card family only. True when card written to context_cards
outcomeconditionalcontext_card family only. PERMIT | DENY | WITHHOLD policy verdict
allowed_summaryconditionalcontext_card family only. Prose of what the card would share; null on DENY
withheld_topicsconditionalcontext_card family only. Topics withheld per per-(R,T,C,S) policy; [] when all allowed
owner_approval_requiredconditionalcontext_card family only. True if safe_to_send=False OR high-regret OR outcome≠PERMIT
safe_to_sendconditionalcontext_card family only. Fail-closed in enforce mode (live 2026-06-30 #151510) — leak-class violation → safe_version nulled, outcome=DENY
wire_versionconditionalcontext_card family only. Wire protocol version, e.g. "context_card/v0.2"

4.3 Confidence scoring

Additive/subtractive from 0.40 base:

DriverDelta
Base (success)+0.40
Per source_used (up to 3)+0.15 each
result.found == True+0.10
result.provenance in {user_authored, user_confirmed, observed}+0.20
result.sample_size >= 10+0.05
result.provenance in {ai_inference, auto_derived, llm_inferred}−0.15
result.provenance == derived_index0.00 (neutral, deterministic index output, not user-confirmed truth)
result.found == False−0.30
Per absent_knowledge_caveat (up to 3)−0.10 each
Error response0.0, signals=["error_response"]

Inner result may carry its own confidence float; envelope takes max(computed, inner).

Two distinct namespaces (v1.0.6): source_kind and provenance are NOT the same field.

Intent mapping (post-handler-fix, Inference + Surface co-ratified):

source_kind in inference.dbhandler emits provenancescorer delta
observed / user_authoredobserved / user_authored+0.20
derived_indexderived_index0.00 (neutral)
llm_inferredllm_inferred−0.15

Dead wire values (user_confirmed, ai_inference, auto_derived): valid in scorer table for non-inference callers; handlers must never emit them for inference.db-sourced reads.

Multi-claim provenance aggregation rule (inference_claim from_kind, shipped commit 187de03):

inference_claim returns N claims, each carrying its own source_kind. The envelope-level provenance is a single value, a coarse trust signal. The handler resolves it by taking the most conservative (weakest) source_kind present across all returned claims:

Ranking (weakest → strongest)Delta
llm_inferred−0.15
derived_index0.00
observed / user_authored+0.20

One llm_inferred claim anywhere in the set pulls the entire envelope to −0.15. Rationale: the envelope confidence must never over-promise, conservative aggregation prevents a mostly-observed result from being reported at +0.20 when it contains a single LLM-derived claim.

Per-claim source_kind is preserved in result.claims[].source_kind for granular per-claim hedging by renderers that need finer resolution.

count=0provenance=None → 0.00 delta (neutral; no found=false signal on this handler, so no −0.30).

4.4 Compression

Large envelopes (>24,000 chars) are compressed by local Qwen 14B before returning. Exempt from_kinds (renderer needs IDs for follow-up calls): person, relationship, household, social_graph, entity_state, personality_profile, inference_claim, email, event, decision, compare_options, topic, context, file, outbound_style, user_mode, surfacing_score.

4.5 Provenance (Article 50)

Every envelope carries a provenance triple via attach_provenance() from core.mandaire.transport.provenance. Fail-open: attachment failure never blocks response.

4.6 Safety shim

Every external MCP response runs through safety.review() at _instrumented_call_tool.

ResultWire behavior
PASSResult transmitted unchanged
WARNsafety_warning field added; result transmitted
HOLDResult replaced with {"error": "response blocked by safety review", "blocked_reason": "...", "disclosure_applied": []}

S2/S3 HOLD suppression (Judgment integration, v0.1 PRODUCTION): When Judgment's disclosure-policy gate fires a HOLD on S2/S3 sensitivity topics (SENSITIVE/RESTRICTED from_kinds), the external response MUST equal the NO_INFO_RESPONSE template byte-for-byte, identical to a GENUINE_UNKNOWN response. blocked_reason and block_class MUST NOT appear in the external response for S2/S3 HOLD (they go to the audit log only). This preserves the 8-property pooling checklist Property 1: S2/S3 topic existence must be non-distinguishable from the wire.

The existing safety content HOLD (STANDARD/S1 topics, fired by safety.review()) MAY retain blocked_reason in the external response, the suppression requirement applies only to S2/S3 HOLD paths. See §12.4.


5. ai_observation, INSERT Payload Schema


{
  "$schema": "https://json-schema.org/draft/2020-12",
  "title": "AiObservationInsertPayload",
  "type": "object",
  "required": ["claim", "confidence", "trigger_reason"],
  "properties": {
    "claim": {
      "type": "string",
      "description": "The observation text. PII-detected: SSN/payment_card/password/api_key/otp patterns are blocked."
    },
    "confidence": {
      "type": "number",
      "minimum": 0.0,
      "maximum": 1.0,
      "description": "Confidence in the claim."
    },
    "trigger_reason": {
      "type": "string",
      "enum": ["user_direct_statement", "ai_synthesis", "nil_result_writeback", "user_confirmed_prior"],
      "description": "Why this observation is being written."
    },
    "claim_tier": {
      "type": "string",
      "enum": ["factual", "behavioral", "preference", "preference_negative", "boundary"],
      "description": "Claim classification tier."
    },
    "entity_refs": {
      "type": "array",
      "items": {"type": "object"},
      "description": "Entity references for cross-linking to people.db. Each item: {name?, email?, phone?, uuid?, entity_id?}. If both uuid and entity_id supplied, uuid wins. Integer entity_id is best-effort resolution-time and may rotate across ER rebuilds. Email/phone aliases are stable across rebuilds and recommended for cross-rebuild durability."
    },
    "evidence_refs": {
      "type": "array",
      "items": {"type": "string"},
      "description": "Source references supporting the claim."
    },
    "writeback_slot_id": {
      "type": ["string", "null"],
      "description": "If filling a nil-result slot, the slot ID from open_writeback_slots."
    },
    "property": {
      "type": ["string", "null"],
      "description": "Property name the claim is about (e.g. 'relationship_quality', 'preference_food')."
    },
    "subject_kind": {
      "type": ["string", "null"],
      "description": "Subject type: person | relationship | self | topic"
    },
    "subject_key": {
      "type": ["string", "null"],
      "description": "UUID of the subject entity (for person/relationship subjects)."
    }
  }
}

INSERT response (ai_observation):


{
  "ok": true,
  "verb": "INSERT",
  "result": {
    "id": "<integer inference.db row id>",
    "kind_stored": "AI_OBSERVATION",
    "tier": "high | medium | low",
    "user_visible": "<boolean: confidence >= 0.65>",
    "claim_tier": "<string>",
    "claim_source": "<string>",
    "source_attribution": "<string>",
    "pending_review_until": "<ISO 8601 or null>",
    "entity_resolution_status": "<object>",
    "dedup": "<boolean: true if this is a duplicate of existing active row>",
    "claim_source_source": "<string, classifier that produced claim_source>",
    "claim_type_source": "<string, classifier that produced the claim type/tier>",
    "verification_state": "<string, e.g. 'pending_review' | 'verified' | 'rejected'>",
    "claim_signature": "<string, dedup hash: sha256(claim_text + affects); present on both new and dedup rows>"
  },
  "artifact_id": "<inference.db row id as string>"
}

Constraints:

SELECT ai_observation:


mandaire(verb="SELECT", from_kind="ai_observation", from_match="<query>", filters={"limit": 20})

Returns array of inference rows matching the query via recall_search.


6. decision, SELECT Schema (Audit Click-Through)


mandaire(verb="SELECT", from_kind="decision", from_id="<audit_ref_uuid>")

Output:


{
  "ok": true,
  "verb": "SELECT",
  "result": {
    "from_kind": "decision",
    "audit_ref": "<uuid>",
    "found": true,
    "evaluations": [
      {
        "decision": "<text>",
        "recipient": "<name>",
        "decision_date": "<ISO 8601>",
        "confidence": 0.85,
        "source_kind": "user_authored",
        "evidence_refs": []
      }
    ]
  }
}

If from_id not provided, returns recent decisions list (same as from_kind=recent_decisions).


7. situation_brief, SELECT Schema

Required: from_match (or from_id / ask) containing the topic string.


mandaire(
  verb="SELECT",
  from_kind="situation_brief",
  from_match="tesla roof leak",
  filters={"detail": "standard"}
)

filters.detail values:

ValueToken targetChar cap
brief4001,600
standard (default)8003,200
full1,5006,000

Output (result field):


{
  "title": "<situation title>",
  "status": "active_unresolved | resolved | not_found | unknown",
  "confidence": 0.85,
  "signals": ["source: analysis_situation_lookup (2026-05-19)"],
  "key_facts": [
    {
      "claim": "<fact text, max 200 chars>",
      "confidence": 0.9,
      "source_kind": "user_authored | observed | ai_inference",
      "source_refs": ["<source identifier>"]
    }
  ],
  "open_items": [
    {"item": "<text>", "priority": "high | medium | low"}
  ],
  "recent_events": [
    {"ts": "<ISO 8601>", "event": "<text, max 100 chars>", "source": "<source>"}
  ],
  "key_people": [
    {"name": "<name>", "role": "<role>", "uuid": "<stable uuid or null>", "entity_id": "<integer, best-effort, may rotate across ER rebuilds>"}
  ],
  "suggested_next_action": "<text>",
  "absent_knowledge_caveats": ["<caveat>"],
  "disclosure_applied": [],
  "_detail_level": "standard",
  "_version": "v0.2"
}

Primary path: Analysis situation_lookup() (situations.db, ~200ms warm). Falls back to pre-built context doc (topic_corpus), then recall_search (may be 8-15s cold if SentenceTransformer not warm).

Sources: analysis_situation_lookup > topic_corpus > recall

Suggestions always included:


[
  {"verb": "SELECT", "from_kind": "situation_brief", "from_match": "<topic>", "filters": {"detail": "full"}},
  {"verb": "SELECT", "from_kind": "context", "from_match": "<topic>"}
]

8. catch_me_up, SELECT Schema


mandaire(verb="SELECT", from_kind="catch_me_up")

Optional filters:

Output (result field):


{
  "ok": true,
  "version": "v0.2.2-2026-05-15-commitment-resolution",
  "live_tasks": [
    {"title": "<task>", "status": "<status>", "tags": [], "deadline": "<ISO 8601 or null>"}
  ],
  "filtered_out_stale_tasks": {
    "count": 2,
    "reason": "deadline_passed or likely_resolved (corpus evidence)",
    "items": []
  },
  "recent_drifts": [],
  "active_threads": {},
  "recent_decisions": [],
  "upcoming_events": [],
  "outbound_silence": [],
  "followup_due": [],
  "open_commitments_outbound": [],
  "open_commitments_inbound": [],
  "recently_resolved_commitments": [],
  "live_threads": [
    {
      "entity_id": "<uuid>",
      "name": "<name>",
      "channels": [],
      "decay_score": 0.85,
      "last_contact": "<ISO 8601>"
    }
  ],
  "live_mailing_lists": [],
  "stale_filter_metric": {
    "total_tasks_in_pending": 10,
    "live_count": 8,
    "stale_count": 2
  },
  "cache_hit": false
}

Cache: 5-minute TTL (write-invalidation stamp). cache_hit=true when cache served.

Sources: pending_tasks, comms, topic_corpus, calendar, task_resolution_v0_2, commitments_v0_1, active_threads_decay_v0_2, people_aliases

Note for renderer: Narrate with confidence markers (CONFIRMED / OBSERVED / STALE / GAP).


9. entity_lookup (via from_kind=person), Output Schema

Reached via from_kind=person with from_match=.

Input:


mandaire(verb="SELECT", from_kind="person", from_match="Bala", purpose="researching_history")

Candidate list (within result):


{
  "found": true,
  "candidates": [
    {
      "entity_id": 10522,
      "name": "Balaji T Kuppuswamy",
      "uuid": "<uuid>",
      "type": "person | org",
      "has_profile": true,
      "interactions": 548,
      "sources": ["gmail", "imessage"]
    }
  ],
  "interaction_total": 548
}

Resolution precedence:

1. Exact email match (aliases.alias_type='email')

2. Exact phone match (aliases.alias_type='phone', normalized)

3. Exact name match

4. Multi-token LIKE match

5. Prefix LIKE match (single token ≥3 chars)

6. name_variant alias match

7. Contact-tagged entities win ties at every preceding step, an entity with a contact tag ranks above an untagged entity at the same precedence level.

Merged-entity guard: Rows with name.startswith("[MERGED") or notes containing "merged_into" are excluded from candidates.

Identity contract: uuid is the stable identity; entity_id (integer) is NOT invariant across merges.


10. Auth

10.1 Required headers (MCP/HTTP)

HeaderRequiredDescription
AuthorizationyesBearer at_<40-byte-urlsafe-token>

10.2 OAuth 2.1 + PKCE flow


1. Discovery:  GET {issuer}/.well-known/openid-configuration
2. DCR:        POST {issuer}/oauth/register → {client_id, client_secret}
3. Authorize:  GET {issuer}/oauth/authorize?...&code_challenge=...&code_challenge_method=S256
               (Authelia SSO gates this; code prefix: "ac_", TTL: 600s)
4. Exchange:   POST {issuer}/oauth/token (grant_type=authorization_code)
               → {"access_token": "at_...", "expires_in": 86400, "refresh_token": "rt_..."}
5. Refresh:    POST {issuer}/oauth/token (grant_type=refresh_token)
6. Revoke:     POST {issuer}/oauth/revoke

10.3 Token TTLs

TokenPrefixTTL
Access tokenat_24h
Refresh tokenrt_30 days
Auth codeac_10 minutes

10.4 Scope

Valid scopes: mandaire (default, shim period), mandaire-v2 (post-shim). Scope bump triggers re-consent cycle for all existing clients.

Incremental scope consent (ADR-0199): a client presenting a token that is missing the current required scope is rejected with an explicit re-consent error rather than a silent downgrade. The client must re-run the consent flow to acquire the elevated scope; partial-scope tokens never silently grant reduced access.

10.5 Machine token (M2M, ADR-0139 B3)

For headless agents needing a JWT without a user OAuth flow:


POST /auth/machine/token
Content-Type: application/json
{"grant_type": "client_credentials", "client_id": "<id>", "client_secret": "<secret>"}

→ {"access_token": "<ES256 JWT>", "token_type": "Bearer", "expires_in": 3600}

JWT issuer: https://mcp.mandaire.app. Algorithm: ES256. Valid for 1h (not 24h).

10.6 JWKS endpoint (ADR-0139 B1)

Public key for JWT validation:


GET /.well-known/jwks.json
→ {"keys": [{"kty": "EC", "crv": "P-256", "kid": "...", "x": "...", "y": "..."}]}

Callers SHOULD cache the JWKS for the JWT's TTL; re-fetch on kid mismatch.

10.7 SDK endpoint, agent-auth (ADR-0141 S-O5, internal only)

Privileged full-fidelity read endpoint for trusted internal agents (Chief, Briefing, david-cli):


GET  /sdk/v1/select      (port 8765, tailnet-only; NOT exposed externally)
POST /sdk/v1/write       (symmetric with select; same mTLS+CN gate)
POST /sdk/v1/signal      (control-plane signal, ADR-0233 D1)
X-Mandaire-Agent-CN: chief    ← set by nginx mTLS frontend on port 8768

/sdk/v1/signal (ADR-0233): cross-VM control-plane signal channel, from_kind-discriminated. Same mTLS + CN gate; CN allowlist defaults to mnd-app-david (env MANDAIRE_SDK_SIGNAL_CN_ALLOWLIST, or pattern ^mnd-app-[a-z0-9-]+$). Allowed kinds default to diagnostic (env MANDAIRE_SDK_SIGNAL_KINDS). Not a data-read path; carries diagnostic / control signals only.

10.8 SDK signal endpoint: control-plane signals (ADR-0233 D1, internal only)

Cross-VM diagnostic and feedback endpoint for mnd-app-side principals (SDK agents):

POST /sdk/v1/signal       (port 8765, tailnet-only; NOT exposed externally)
Content-Type: application/json
X-Mandaire-Agent-CN: mnd-app-david    ← set by nginx mTLS frontend

Body: {"from_kind": "diagnostic", "payload": {...}}

→ {"status": "recorded", "from_kind": "diagnostic"}

CN gate: Must match pattern mnd-app-[a-z0-9][a-z0-9-]* (or explicit MANDAIRE_SDK_SIGNAL_CN_ALLOWLIST env list). CN absent or non-matching → 403.

from_kind discriminator: Only diagnostic permitted by default. Expandable via MANDAIRE_SDK_SIGNAL_KINDS env (comma-separated). Unknown kind → 400.

Rationale (ADR-0233): Separates cross-VM control-plane signals (health probes, telemetry) from the data plane (/sdk/v1/select). Avoids overloading the SELECT endpoint with write/signal semantics. Single endpoint, from_kind-discriminated, so future signal kinds extend without new routes.

10.9 Requester key discovery (SEC-070 D2)

Publicly-discoverable Ed25519 signing key for cross-tenant request authentication (ADR-0224 prerequisite):

GET /.well-known/mandaire-requester-key.json    (no auth, public)

→ HTTP 200 + Cache-Control: public, max-age=3600
{
  "kty": "OKP",
  "crv": "Ed25519",
  "use": "sig",
  "kid": "<key-id>",
  "x": "<base64url-encoded public key>"
}

→ HTTP 404 when no key has been generated yet (OS D1 prerequisite not met)

Purpose: Cross-tenant peers resolve this endpoint to verify that incoming request payloads were signed by this Mandaire instance. Used by the five ADR-0224 cross-user verbs (request_context, offer_context, evaluate_disclosure, claim_context, acknowledge_disclosure); those verbs are not yet implemented (gated on Architecture layer-3 authorization).

Key lifecycle: OS layer generates and rotates key material; Surface reads and serves the current JWK file. kid is derived from a SHA-256 fingerprint of the public key (first 16 hex chars). Cache-Control of 1h prevents stale serving during rotation.

Live: https://mcp.mandaire.com/.well-known/mandaire-requester-key.json and https://mcp.mandaire.app/.well-known/mandaire-requester-key.json


11. Error Codes

11.1 MCP-level errors (HTTP status)

StatusMeaning
401Missing or expired Bearer token
403Valid token but insufficient scope
400Malformed JSON / missing required field
500Server error (see error envelope)

11.2 Application-level errors (ok=false in envelope)

error valueTrigger
"from_kind required"verb=SELECT and no from_kind or ask
"from_match required for from_kind=situation_brief"situation_brief without topic
"ask required for from_kind=recall"recall without ask
"purpose required for SENSITIVE from_kind"SENSITIVE kind, purpose absent
"purpose required for RESTRICTED from_kind"RESTRICTED kind, purpose absent
"unknown from_kind: "from_kind not in _VALID_FROM_KINDS catalog
"payload.claim required"ai_observation INSERT, claim absent
"payload.confidence required (0.0-1.0)"ai_observation INSERT, confidence absent
"payload.trigger_reason required"ai_observation INSERT, trigger_reason absent
"CROSS_USER_WRITEBACK_BLOCKED"ai_observation INSERT with mismatched user targets
"pii_pattern_rejection"ai_observation INSERT, PII detected in claim
"response blocked by safety review"safety.review()=HOLD (STANDARD/S1 topics, blocked_reason returned)
"response blocked by disclosure policy"Judgment gate HOLD on S2/S3 topics, external response = NO_INFO_RESPONSE template; blocked_reason suppressed (see §12.4 + §4.6)

block_class field (Judgment integration, v0.1 PRODUCTION): Error envelopes include block_class (string) when Judgment is wired. Values: "safety_content" | "disclosure_policy" | "safety_tier_gate". For S2/S3 disclosure_policy blocks, block_class goes to audit log only, NOT returned to the external caller.

11.3 A2A error codes

CodeMeaning
-32001Auth failure (bad/missing shared secret)
-32002Hop limit exceeded
-32003Rate limit exceeded
-32601Method not implemented / unknown skill
-32602Missing required param
-32603Skill execution failed

12. Safety Tier Gate (R2)

Applied at mandaire() entry, after verb validation, before any handler runs.

12.1 Tiers

Tierfrom_kindsBehavior
RESTRICTEDdisclosure_policy, correction_history, personality_profile, tax_event, all 7 health kinds (latest_vitals, vitals_trend, lab_results, medications_active, immunizations, ecg_summary, conditions)purpose required. Audit-logged (log.warning). Safety authorization callback deferred. (11 kinds total as of v1.0.9)
SENSITIVE~30 kinds (see §3.2 tier column)purpose required. Error envelope if absent.
STANDARD~55 kindsNo gate.

SSOT gate discipline: The gate implementation MUST derive RESTRICTED/SENSITIVE/STANDARD tier from the tier column in §3.2, not from a hardcoded enumeration of the table above. §3.2 is authoritative; §12.1 is a human-readable summary. Any divergence between them is a spec-publishing error, §3.2 wins.

12.2 Depth-conditional escalation

person, household, relationship at depth="deep" or depth="exhaustive" → treated as SENSITIVE regardless of catalog tier.

12.3 Purpose check

Presence-only check. Not validated against an enum. Suggested values: preparing_for_meeting, drafting_reply, researching_history, evaluating_relationship, disambiguating, ai_writeback_from_conversation.

12.4 Judgment disclosure-policy gate (v0.1 PRODUCTION, forward design)

Judgment's gate fires pre-fetch (before any handler runs). This is distinct from the safety.review() shim (§4.6) which fires post-handler. These are different failure modes with different caller remediation paths.

block_class enum, appears in error envelopes when Judgment is wired:

block_classTriggerCaller remediation
safety_contentsafety.review() HOLD (§4.6, post-handler)No recourse
disclosure_policyJudgment gate HOLD (pre-fetch, S2/S3 topic)May provide additional purpose, context, or authorization token
safety_tier_gateTier gate enforcement: SENSITIVE/RESTRICTED without purposeInclude purpose field in request

S2/S3 HOLD external response: byte-equal to NO_INFO_RESPONSE template (see §4.6 S2/S3 suppression note). block_class and blocked_reason go to audit log only, NOT returned to the external caller. Existence of S2/S3 content must be non-distinguishable from GENUINE_UNKNOWN on the wire.

Error envelope shape for Judgment-integrated blocks:

Non-S2/S3 blocks (safety_tier_gate + non-sensitive disclosure_policy):


{
  "ok": false,
  "error": "response blocked by disclosure policy",
  "block_class": "disclosure_policy",
  "hint": "Provide additional purpose or authorization context.",
  "disclosure_applied": []
}

S2/S3 disclosure_policy HOLD, block_class and blocked_reason OMITTED from external response (audit log only); caller cannot distinguish from GENUINE_UNKNOWN:


{
  "ok": false,
  "error": "response blocked by disclosure policy",
  "disclosure_applied": []
}

13. A2A Protocol

Endpoint: https://research.mandaire.com/a2a

Version: 0.1.0

Wire owner: Surface (absorbed from Research at activation)

13.1 Endpoints

MethodPathAuth
GET/.well-known/agent-card.jsonNone
POST/a2aBearer shared secret
GET/healthzNone

13.2 Request envelope


{
  "jsonrpc": "2.0",
  "method": "message/send",
  "params": {
    "skill": "research.<skill_id>",
    "message": {"parts": [{"kind": "text", "text": "..."}]},
    "metadata": {"limit": 8, "mode": "both"}
  },
  "id": "<caller-generated id>"
}

Required headers: Authorization: Bearer

Optional: X-A2A-Trace-Id, X-A2A-Parent-Trace, X-A2A-Hop-Count, X-Mandaire-Caller

13.3 Registered skills

Skill IDStatusCost classNotes
research.recall_searchLivefreeFTS + semantic over recall.db
research.entity_lookupLivefreeName/email/phone lookup in people.db
research.inferred_asset_queryLivefreeweb_enrichment.db + observations.db read
research.web_enrichLiveclaude_sonnet (~90-180s)WebSearch + Claude enrichment per entity
inference.claim_lookup (co-hosted)LivefreeActive claims over inference.db; supports as_of replay
enrichment.coverage_check (co-hosted)Livefreeweb_enrichment.db coverage stats (no text needed)
enrichment.enrich_entity (co-hosted)Liveclaude_sonnet (~90-180s)Full web enrichment; metadata.force=true to re-enrich
enrichment.freshness_sweep (co-hosted)Liveclaude_sonnet × NBatch stale-entity re-enrichment; cost gate at N>20
research.web_browseStubN/A
research.synthesizeStubN/A
enrichment.cohort_enrichStubN/ACost-gate design pending
enrichment.linkedin_enrichStubN/ABatch-only API design pending

13.4 Auth

v0.1 (active): Shared bearer secret at ~/openclaw/Brain/credentials/a2a_research_v01.secret. Sufficient because all live peers are internal fleet agents on the same trust boundary.

v0.4 (condition-gated, not date-scheduled): OAuth 2.1 client_credentials via Authelia (https://auth.mandaire.com/api/oidc/token). Scopes: research:read, research:write (Research-owned); inference:read (Inference-owned). The flip condition is: admit a genuinely external / untrusted peer, OR the disclosure engine reaches ENFORCEMENT for cross-agent callers. When either fires, OAuth enrollment precedes bearer decommission; no silent rotation. Confirmed by Research 2026-06-13.

13.5 Per-Skill Wire Reference

All skills use the §13.2 request envelope. This section documents params.message.parts[].text (the query input) and params.metadata fields per live skill, plus the response shape. Stub skills return {"status": "not_yet_implemented", "skill": "<id>"}.

research.recall_search

research.entity_lookup

research.web_enrich

research.inferred_asset_query

inference.claim_lookup

enrichment.coverage_check

enrichment.enrich_entity

enrichment.freshness_sweep


14. Versioning Policy

14.1 Version bump rules

Change typeVersion bump
New from_kind addedMinor (v1.X)
New optional envelope fieldMinor (v1.X)
New required envelope fieldMajor (vX.0)
from_kind renamed or removedMajor (vX.0)
Envelope field renamed or removedMajor (vX.0)
Auth flow changeMajor (vX.0)
Tier gate escalation (STANDARD→SENSITIVE)Minor with notice
Tier gate escalation (SENSITIVE→RESTRICTED)Major with notice

14.2 Guarantees

14.3 Current spec URL

https://mandaire.org/spec/v1.0 (hosted by nginx; Surface owns keeping it in sync with this doc).


15. Open Items (Forward Design)

ItemOwnerStatus
A2A v0.4: OAuth client credentials replacing shared secretSurface (AOR-3)Condition-gated on multi-user / external-peer milestone (not date-scheduled); confirmed Research 2026-06-13
research.web_browse + research.synthesize stubsResearchNot implemented
result.confidence_breakdown[] per-recipient CIInferencev1.1 candidate when AOR-3 calibration data available
mandaire.org/spec page at /wire/v1.0Surface (AOR-4)LIVE at https://mandaire.org/spec/v1.0
ADR-0021 scope bump (mandaire → mandaire-v2)SurfaceReady; timing gate, David must be present to re-auth (queued for post-Costa-Rica)
ADR-0141 B2 nginx mTLS port 8768InfraPending Mandaire CA bootstrap (David at terminal on mnd-infra)
MCP-as-ingest / self-client patternSurface (AOR-5)SDK endpoint live (/sdk/v1/select, port 8765); mTLS frontend pending
Weekly super-tool drift checkSurface + OSLIVE 2026-06-28mcp-tool-count-check.timer weekly Mon 05:00 Pacific. Baseline-set drift (not count threshold): alerts Surface P3 by name on unreviewed addition; removal logged only. Update BASELINE_TOOLS in scripts/mcp_tool_count_check.py when adding a reviewed super-tool.
Judgment HOLD error shape wiring (§12.4 block_class, S2/S3 suppression)Judgment + Surfacev0.1 PRODUCTION gate, spec-settled in v1.0.8; code wiring pending Judgment activation
judgment_applied envelope field emissionJudgment + Surfacev1.1 candidate, closes AOR-2 GDPR Article 30 audit gap (currently disclosure_applied: [] indistinguishable from Judgment not running)
Judgment disclosure-policy gate (pre-fetch) wiringJudgment + Surfacev0.1 PRODUCTION gate, requires Judgment policy engine live on MCP path

Appendix A: Telemetry Schema

Every call recorded to ~/openclaw/brain/mcp_telemetry.db:


tool_calls(
  id                           INTEGER PRIMARY KEY,
  ts_utc                       TEXT,
  request_id                   TEXT,
  client_id                    TEXT,
  principal                    TEXT,
  tool_name                    TEXT,
  args_json                    TEXT,      -- truncated at 2000 chars
  args_size_bytes              INTEGER,
  latency_ms                   INTEGER,
  status                       TEXT,      -- 'ok' | 'error' | 'empty'
  error_class                  TEXT,
  error_message                TEXT,      -- truncated at 500 chars
  result_size_bytes            INTEGER,
  result_summary               TEXT,
  disclosure_applied_populated INTEGER,   -- 1=non-stub, 0=stub, NULL=pre-migration
  safety_review_latency_ms     INTEGER,
  cache_hit                    INTEGER,
  source_kind                  TEXT,
  confidence_score             REAL,
  inference_ids_json           TEXT
)

Safety review SLO: p99 < 10ms.


Spec produced by Surface (Mandaire) 2026-05-20. Dispatch targets: Product (§7 semantic shapes), Inference (§4.3 confidence contract + §15 confidence_breakdown), Judgment (§11 safety tier gate + §12 pre-flight hook shape).