10 min read

Version History

  • 4 (2026-05-24) - Added the /events endpoint and the servesEvents field on /meta, letting a module emit a stream of business events that MindFront drains on a poll. Backwards compatible.
  • 3 (2026-02-09) - Added optional pictogram and typicalHumanProcessTimeInMinutes fields to action schema. Added optional tldr and attachment_urls fields to action success responses. Backwards compatible.
  • 2 (2025-07-22) - Removed output schema requirement. Backwards compatible with V1.
  • 1 (2024-10-16) - Initial release.

Overview

This document outlines the Protocol specification for creating a service that implements the Module Service Protocol, compatible with MindFront. By implementing this protocol, your internal services become powerful tools that MindFront can use to perform real work.

Your service remains entirely your own IP and is completely independent of MindFront. MindFront never needs to view, introspect, or have access to your code or logic.

Protocol Philosophy

  • This is an RPC-style protocol over HTTP. HTTP is used only as a transport layer.
  • The outcome of an operation is determined exclusively by the status field within the JSON response body.
  • Your service should return an HTTP 200 OK for all protocol-level responses, including logical errors (failure, invalidInput). A non-200 status code (like 404 or 500) should only be used for transport-level errors where a JSON response body cannot be formed.

High-level Fiber overview

flowchart LR
  subgraph YM["Your Module Service"]
    direction LR
      meta["/meta"]
      actions["/action/*"]
      events["/events"]
    end
      
  MindFront --Reads--> meta
  MindFront --Calls--> actions
  MindFront --Drains--> events

  actions --> YI["Your Data/Services"]
  YI --> events

Fiber Protocol Sequence

sequenceDiagram
    participant SG as MindFront
    participant CM as Custom Module

    Note over SG,CM: Module Registration
    SG->>CM: GET /meta
    CM-->>SG: Provide metadata
    
    Note over SG,CM: Action Execution (Optional)
    SG->>CM: POST /action/yourAction
    activate CM
    CM-->>SG: Return JSON result
    deactivate CM

    Note over SG,CM: Event Drain (Optional — servesEvents)
    SG->>CM: GET /events
    activate CM
    CM-->>SG: Return queued events, clear queue
    deactivate CM
Base URL

You may choose any base URL for your service. We highly encourage using a direct WireGuard connection for security. Alternatively, use HTTPS with a robust authentication mechanism via custom headers.


Meta Endpoint

A GET request to /meta must return a JSON object that declares the module’s capabilities. MindFront polls this endpoint approximately every 5 seconds to detect changes in real-time.

Response Schema
{
  "protocolVersion": 4,
  "moduleVersion": "x.y.z",
  "moduleName": "string",
  "description": "string",
  "actions": [ /* Array of Action Schema objects */ ],
  "servesEvents": false
}
FieldDescription
protocolVersionThe Fiber version. Must be 4.
moduleVersionThe semantic version of your module. Parsed as SemVer; falls back to the raw string if unparseable.
moduleNameA unique, human-readable name for your module.
descriptionA brief explanation of the module’s purpose.
actions(Optional) An array of action objects. Can be omitted or be an empty array ([]).
servesEvents(Optional) true if this module exposes a /events endpoint. Defaults to false. When true, MindFront polls /events on a fixed cadence and drains queued business events.

Note: Your /meta handler must respond within 5 seconds or MindFront marks the module degraded. The handler is polled every 5 seconds and a fresh catalogue is rebuilt whenever the response changes.

Action Schema

Each object in the actions array is defined by this schema:

FieldDescription
nameUnique identifier for the action (e.g., createOrder). Must match ^[a-zA-Z0-9_]+$.
descriptionA clear explanation of what the action does, for AI consumption.
routeThe API endpoint for this action (e.g., /action/createOrder).
riskLevelThe required approval workflow. Must be one of: "safe", "machineApprovalRequired", "humanApprovalRequired", or "forbidden".
inputA JSON Schema object defining the parameters for this action.
pictogram(Optional) A Carbon Design System pictogram name (e.g., "user-profile", "cloud-upload"). Used as the action’s icon in the MindFront UI.
typicalHumanProcessTimeInMinutes(Optional) A number representing the estimated time in minutes this task would take a human to perform manually. Used by MindFront to communicate time savings.

Validation: Actions with a missing name, missing route, or an unrecognised riskLevel are silently dropped from the live catalogue. If your action isn’t appearing in MindFront, check those three fields first.

Risk Level Evaluation

This flowchart illustrates how MindFront handles the riskLevel for actions.

flowchart TB
    A[Start: Action Request] --> B{Check riskLevel}
    B -->|safe| C[Execute Action]
    B -->|machineApprovalRequired| D{Automated Check}
    B -->|humanApprovalRequired| E{Human Review}
    B -->|forbidden| F[Reject Action]
    D -->|Pass| C; D -->|Fail| F
    E -->|Approved| C; E -->|Rejected| F
    C --> G[Return Result]; F --> H[Return Error]
    G --> I[End]; H --> I[End]

Action Endpoints

Request Headers from MindFront

MindFront sends the following headers with every POST request to your action endpoints:

HeaderExample ValueDescription
X-MindFront-Request-Id123e4567-...A unique UUIDv4 for this specific HTTP request. Useful for logging and tracing.
X-MindFront-Task-Ida1b2c3d4-...The unique ID of the job, conversation, or decision the action is running inside.
X-MindFront-User-Emailsalice@co.comEmail address of the human this action is running on behalf of. Omitted on autonomous runs (background decisions and reflections).
X-MindFront-User-Idsb2c3d4e5-...ID of the human this action is running on behalf of. Omitted on autonomous runs.
X-MindFront-User-NamesAlice JonesDisplay name of the human this action is running on behalf of. Omitted on autonomous runs.
X-MindFront-Timestamp1748102580Unix seconds when MindFront dispatched the request.
X-MindFront-Module-Service-Protocol-Version4Protocol version of the request.
X-MindFront-Server-VersionmainlineBuild identifier of the MindFront server making the call.
Custom Headersmy-secret-tokenYour administrator can configure any number of custom headers (e.g., for an API key).

For backwards compatibility every header is emitted twice — once under X-MindFront-* (the canonical name above) and once under X-SynthGrid-* (legacy name from earlier MSP versions). New bridges should read the X-MindFront-* set; the legacy prefix will be sunset in a future release.

Calling an Action

  • Request: POST to the defined route with a JSON body pre-validated by MindFront against your input schema. The request times out after 100 seconds — your action must respond within that window.
  • Response: An HTTP 200 OK with a JSON body indicating the outcome.

Success Response:

{
  "status": "success",
  "data": { /* A non-null JSON object representing the result of the action */ },
  "tldr": "A short one-line summary of what just happened.",
  "attachment_urls": [ "https://example.com/report.pdf" ]
}
  • tldr (Optional): A short, human-readable summary of the outcome (e.g., "Updated 3 contacts"). Used as the action’s success line in the MindFront UI.
  • attachment_urls (Optional): An array of URLs MindFront will fetch and ingest into its document store as file deliverables on the action’s outcome. Use this to return generated reports, exports, or any binary your action produced.

Error Response:

{
  "status": "failure" | "invalidInput",
  "error": { "message": "A clear, user-facing explanation of the error." }
}
  • invalidInput: Use when the input is schema-valid but fails a business logic check (e.g., an ID does not exist).
  • failure: Use for all other operational errors (e.g., a downstream service is unavailable).

Status values are compared case-insensitively, so "Success" and "SUCCESS" parse the same as "success".

Events Endpoint

A module that sets "servesEvents": true in /meta must expose a GET /events endpoint. MindFront polls this endpoint on a fixed cadence and drains the result — each event is delivered exactly once, and the module is the sole owner of the cursor.

  • Request: GET /events (no body, no query parameters).
  • Success Response: An HTTP 200 OK with Content-Type: application/json and a body that is a JSON array of event objects. Return [] when nothing new has arrived. Order events by time ascending.
  • Side-effect: Once you have written the response, treat those events as delivered. They must not appear in a subsequent /events call. If MindFront crashes between receiving your response and persisting the events, those events are lost — this is an accepted at-most-once tradeoff in exchange for a stateless client.

Event Schema

Each entry in the array:

{
  "id": "ev-00012345",
  "time": "2026-05-24T09:14:22.123Z",
  "kind": "DailyVisit",
  "headline": "Acme — Dave Patel",
  "pictogram": "user-multiple",
  "groupKey": "company:12345",
  "externalUrl": "https://your-app.example.com/visits/DV-2526-0042",
  "data": { /* arbitrary JSON, your domain shape */ }
}
FieldRequiredDescription
idyesOpaque identifier, unique within the module. Any format — ULID, GUID, "table:autoid". MindFront uses it for dedup-within-a-batch and tracing only; the protocol is drain-on-read so this is never used as a cursor.
timeyesISO-8601 UTC. When the underlying event actually happened in the source system (e.g., the row’s createdAt), not when you emitted it. Used as the ordering key in the MindFront inbox.
kindyesStable category label — the same string for every event of this type (e.g., "DailyVisit", "PurchaseIndent", "SalesOrder"). Free vocabulary, human-readable, no namespacing.
headlineyesInstance-specific one-line summary, short enough for an inbox row.
pictogramyesCarbon Design System pictogram name. Use "information" if you have no opinion.
groupKeyyesThread/stacking key. Events sharing a groupKey collapse to one row in the MindFront inbox. Pick the entity the human would think of as “the same thing being updated” — typically a company id, a deal id, an order id. If the event has no natural sibling, set groupKey equal to id so it sits on its own. Not a dedup key — two events with the same groupKey are both real events, just related.
externalUrlnoFull URL to view this event in your source system. Omit if you have no addressable view.
datayesArbitrary JSON — your domain shape. MindFront surfaces it verbatim; no schema is enforced.

Reducing DB load

A common implementation pattern: run a background timer (e.g., every 60 seconds) that queries your database for new rows since the last cursor, builds events, and appends them to an in-memory queue. GET /events then drains the queue without touching the database. This gives a fixed upper bound on database load — independent of how often MindFront polls.


Complete Example

This runnable Python script implements a service with one action and one data endpoint.

#!/usr/bin/env python3
import json
from http.server import HTTPServer, BaseHTTPRequestHandler

METADATA = {
    "protocolVersion": 4,
    "moduleVersion": "1.0.1",
    "moduleName": "SimpleCRM",
    "description": "A basic CRM with actions for customers.",
    "actions": [{
        "name": "getCustomer", "description": "Retrieves details for a single customer by ID.",
        "route": "/action/getCustomer", "riskLevel": "safe",
        "pictogram": "user-profile",
        "typicalHumanProcessTimeInMinutes": 2,
        "input": {
            "type": "object", "properties": {"customerId": {"type": "string"}}, "required": ["customerId"]
        }
    }]
}

# Dummy in-memory data store
CUSTOMERS = { "CUST-001": {"name": "Alice Corp", "status": "active"}, "CUST-002": {"name": "Bob Inc", "status": "inactive"} }

class Handler(BaseHTTPRequestHandler):
    def send_json(self, data, status_code=200):
        self.send_response(status_code)
        self.send_header("Content-type", "application/json")
        self.end_headers()
        self.wfile.write(json.dumps(data).encode())

    def do_GET(self):
        if self.path == "/meta": self.send_json(METADATA)
        else: self.send_error(404)

    def do_POST(self):
        content_length = int(self.headers.get('Content-Length', 0))
        input_data = json.loads(self.rfile.read(content_length))

        if self.path == "/action/getCustomer":
            customer_id = input_data.get("customerId")
            if customer_id in CUSTOMERS:
                self.send_json({
                    "status": "success",
                    "data": {"id": customer_id, **CUSTOMERS[customer_id]},
                    "tldr": f"Loaded {CUSTOMERS[customer_id]['name']}"
                })
            else:
                self.send_json({"status": "invalidInput", "error": {"message": f"Customer ID '{customer_id}' not found."}})
        else:
            self.send_error(404)

if __name__ == "__main__":
    HTTPServer(("", 8080), Handler).serve_forever()

Best Practices & FAQs

Data Handling

  • Return Meaningful Data: An action’s data payload should always contain the result of the operation (e.g., the full record that was updated). This provides confirmation and enables action chaining.
  • Handling null vs. Missing Keys: Your service can assign different semantic meaning to a key being absent versus a key being present with a null value (e.g., null could mean “explicitly unset this optional field”).
  • Payload Sizes: Keep JSON data payloads to a reasonable size. Return large file outputs via attachment_urls — MindFront fetches each URL and ingests it into the document store.

Security & Networking

  • Authentication: The recommended method is a direct WireGuard connection. For other setups, use custom headers to pass API keys or tokens; your administrator will configure these in MindFront.
  • Idempotency: While MindFront does not repeat requests, network retries can occur. Use the X-MindFront-Request-Id header in your logs to trace requests and in your application logic to de-duplicate operations if necessary.

Development Lifecycle

  • Environments: Manage dev/staging/prod by running separate instances of your MSP service and having your MindFront administrator point each environment to the appropriate base URL.
  • Updating: Simply deploy your new service code and restart it. MindFront will detect changes via the /meta poll within seconds. The moduleVersion is for display only and does not control updates.

Common Questions

  • How do I enrich the UI for parameters? Use standard JSON Schema keywords in your input schema. Try to avoid nesting, etc.
  • What if my action is slow? The user will see a “processing” state in the MindFront UI for the duration of the request (up to the 100s timeout). Design your actions to be as fast as possible. For long-running reports, consider an action that triggers the report and another that retrieves it by ID.
  • Can my service push events into MindFront? Yes — set "servesEvents": true in /meta and expose a GET /events endpoint. See the Events Endpoint section. MindFront polls and drains the endpoint; each event surfaces in the inbox as a row grouped by groupKey.
  • Can my service create tasks in MindFront? Not directly. Emit a Fiber event with the relevant kind and let an administrator wire a routine that turns matching events into tasks.
  • Can my service receive events from MindFront? Indirectly. An administrator can configure MindFront to call one of your actions in response to a MindFront event (e.g., “when a task is completed, call the logTaskCompletion action”).