MindFront Fiber
Version History
- 4 (2026-05-24) - Added the
/eventsendpoint and theservesEventsfield 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
pictogramandtypicalHumanProcessTimeInMinutesfields to action schema. Added optionaltldrandattachment_urlsfields 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
statusfield within the JSON response body. - Your service should return an HTTP
200 OKfor all protocol-level responses, including logical errors (failure,invalidInput). A non-200 status code (like404or500) 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
}
| Field | Description |
|---|---|
protocolVersion | The Fiber version. Must be 4. |
moduleVersion | The semantic version of your module. Parsed as SemVer; falls back to the raw string if unparseable. |
moduleName | A unique, human-readable name for your module. |
description | A 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
/metahandler 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:
| Field | Description |
|---|---|
name | Unique identifier for the action (e.g., createOrder). Must match ^[a-zA-Z0-9_]+$. |
description | A clear explanation of what the action does, for AI consumption. |
route | The API endpoint for this action (e.g., /action/createOrder). |
riskLevel | The required approval workflow. Must be one of: "safe", "machineApprovalRequired", "humanApprovalRequired", or "forbidden". |
input | A 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, missingroute, or an unrecognisedriskLevelare 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:
| Header | Example Value | Description |
|---|---|---|
X-MindFront-Request-Id | 123e4567-... | A unique UUIDv4 for this specific HTTP request. Useful for logging and tracing. |
X-MindFront-Task-Id | a1b2c3d4-... | The unique ID of the job, conversation, or decision the action is running inside. |
X-MindFront-User-Emails | alice@co.com | Email address of the human this action is running on behalf of. Omitted on autonomous runs (background decisions and reflections). |
X-MindFront-User-Ids | b2c3d4e5-... | ID of the human this action is running on behalf of. Omitted on autonomous runs. |
X-MindFront-User-Names | Alice Jones | Display name of the human this action is running on behalf of. Omitted on autonomous runs. |
X-MindFront-Timestamp | 1748102580 | Unix seconds when MindFront dispatched the request. |
X-MindFront-Module-Service-Protocol-Version | 4 | Protocol version of the request. |
X-MindFront-Server-Version | mainline | Build identifier of the MindFront server making the call. |
| Custom Headers | my-secret-token | Your 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:
POSTto the definedroutewith a JSON body pre-validated by MindFront against yourinputschema. The request times out after 100 seconds — your action must respond within that window. - Response: An HTTP
200 OKwith 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 OKwithContent-Type: application/jsonand a body that is a JSON array of event objects. Return[]when nothing new has arrived. Order events bytimeascending. - Side-effect: Once you have written the response, treat those events as delivered. They must not appear in a subsequent
/eventscall. 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 */ }
}
| Field | Required | Description |
|---|---|---|
id | yes | Opaque 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. |
time | yes | ISO-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. |
kind | yes | Stable category label — the same string for every event of this type (e.g., "DailyVisit", "PurchaseIndent", "SalesOrder"). Free vocabulary, human-readable, no namespacing. |
headline | yes | Instance-specific one-line summary, short enough for an inbox row. |
pictogram | yes | Carbon Design System pictogram name. Use "information" if you have no opinion. |
groupKey | yes | Thread/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. |
externalUrl | no | Full URL to view this event in your source system. Omit if you have no addressable view. |
data | yes | Arbitrary 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
datapayload should always contain the result of the operation (e.g., the full record that was updated). This provides confirmation and enables action chaining. - Handling
nullvs. Missing Keys: Your service can assign different semantic meaning to a key being absent versus a key being present with anullvalue (e.g.,nullcould mean “explicitly unset this optional field”). - Payload Sizes: Keep JSON
datapayloads to a reasonable size. Return large file outputs viaattachment_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-Idheader 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
/metapoll within seconds. ThemoduleVersionis 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
inputschema. 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": truein/metaand expose aGET /eventsendpoint. See the Events Endpoint section. MindFront polls and drains the endpoint; each event surfaces in the inbox as a row grouped bygroupKey. - Can my service create tasks in MindFront?
Not directly. Emit a Fiber event with the relevant
kindand 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
logTaskCompletionaction”).