Version: 1.0 Status: Specification Last Updated: 2025-10-17
This document specifies how the MUXI CLI communicates with MUXI Server. This is the contract between the two components.
All CLI → Server requests use HMAC-SHA256 authentication (see AUTH.md for full details).
Authorization: MUXI-HMAC key={KEY}, timestamp={TIMESTAMP}, signature={SIGNATURE}
Signature Computation:
message = "{timestamp};{method};{path}"
signature = base64(HMAC-SHA256(secret, message))
Example:
timestamp = "1729180800"
method = "POST"
path = "/formations/deploy"
message = "1729180800;POST;/formations/deploy"
signature = base64(HMAC-SHA256("sk_secret_123", message))
Authorization: MUXI-HMAC key=MUXI_key_abc, timestamp=1729180800, signature=YWJjZGVm...
POST /formations/deploy
Authorization: MUXI-HMAC key=..., timestamp=..., signature=...
Content-Type: application/gzip
Content-Length: <size>
Format: Gzipped tarball containing:
formation-bundle.tar.gz
├── formation.yaml # Required: Formation configuration
├── app.py # Formation code
├── requirements.txt # Python dependencies (optional)
├── .env # Environment variables (optional)
└── ... (other files)
formation.yaml structure:
id: my-api # Required: Unique formation ID
name: "My API" # Optional: Human-readable name
version: "1.0.0" # Optional: Formation version
runtime: "python:3.13" # Optional: Runtime specification
# Optional metadata
metadata:
description: "My awesome API"
author: "user@example.com"
tags: ["chat", "ai"]
# Optional environment variables
env:
MODEL_NAME: "gpt-4"
API_KEY: "${SECRET_API_KEY}" # Can reference secrets# CLI creates the bundle like this:
tar czf formation-bundle.tar.gz \
formation.yaml \
app.py \
requirements.txt \
.envPython example:
import tarfile
import gzip
import io
def create_formation_bundle(files: dict) -> bytes:
"""
Create a gzipped tarball from files.
Args:
files: Dict of filename -> content
Returns:
Gzipped tarball bytes
"""
# Create tar in memory
tar_buffer = io.BytesIO()
with tarfile.open(fileobj=tar_buffer, mode='w:gz') as tar:
for filename, content in files.items():
info = tarfile.TarInfo(name=filename)
info.size = len(content)
tar.addfile(info, io.BytesIO(content))
return tar_buffer.getvalue()
# Usage
bundle = create_formation_bundle({
'formation.yaml': yaml_content.encode(),
'app.py': app_code.encode(),
'requirements.txt': deps.encode()
})Server steps:
- Receive gzipped tarball
- Extract to temporary directory
/tmp/muxi-formations/{uuid}/ ├── formation.yaml ├── app.py └── ... - Read formation.yaml to get
id - Validate formation ID (unique, valid format)
- Allocate port from pool (e.g., 8001)
- Spawn formation with environment:
PORT=8001 python app.py
- Register mapping:
formation_id→port - Move extracted files to permanent location:
~/.muxi/server/formations/{formation_id}/ - Wait for health check (optional)
- Return success response
{
"id": "my-api",
"status": "running",
"port": 8001,
"pid": 12345,
"url": "http://localhost:3000/v1/my-api",
"created_at": "2025-10-17T10:30:00Z"
}400 Bad Request - Invalid bundle:
{
"error": "Invalid formation bundle",
"message": "formation.yaml not found in bundle",
"code": 400
}400 Bad Request - Invalid formation.yaml:
{
"error": "Invalid formation.yaml",
"message": "Missing required field: id",
"code": 400
}409 Conflict - Formation exists:
{
"error": "Formation already exists",
"message": "Formation with id 'my-api' is already deployed",
"code": 409
}500 Internal Server Error - Spawn failed:
{
"error": "Failed to spawn formation",
"message": "Command exited with code 1",
"code": 500
}The server MUST provide these environment variables to every formation:
| Variable | Type | Description | Example |
|---|---|---|---|
PORT |
int | Port to listen on | 8001 |
FORMATION_ID |
string | Formation identifier | my-api |
MUXI_SERVER_URL |
string | Server base URL | http://localhost:3000 |
MUXI_ENV |
string | Environment (dev/staging/prod) | production |
Example formation startup:
import os
from fastapi import FastAPI
app = FastAPI()
# Formation MUST read PORT from environment
port = int(os.getenv('PORT', 8000))
formation_id = os.getenv('FORMATION_ID')
@app.get("/health")
def health():
return {"status": "healthy", "formation_id": formation_id}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=port)Every formation MUST implement:
GET /health
Response (200 OK):
{
"status": "healthy"
}Or any 2xx status code with any body.
Server will check this endpoint every 30 seconds.
{server_url}/v1/{formation_id}/{path}
Examples:
Original request:
POST http://localhost:3000/v1/my-api/chat
Proxied to formation:
POST http://localhost:8001/chat
Headers:
- All original headers forwarded (except Host)
- X-Forwarded-For: client IP
- X-Forwarded-Proto: http/https
- X-Formation-ID: my-api
1. Parse URL: /v1/{formation_id}/{remaining_path}
2. Look up formation_id in registry → get port
3. If not found → 404 Formation Not Found
4. If formation unhealthy → 503 Service Unavailable
5. Proxy request to http://localhost:{port}/{remaining_path}
6. Forward response to client
Client sends:
POST /v1/my-api/chat/completions
Server forwards to formation:
POST /chat/completions
The /v1/{formation_id} prefix is stripped.
Server adds these headers:
X-Forwarded-For: <client-ip>
X-Forwarded-Proto: http
X-Forwarded-Host: localhost:3000
X-Formation-ID: my-api
X-Request-ID: <uuid>
Server preserves:
- All original request headers (except
Host) - All response headers from formation
404 Formation Not Found:
{
"error": "Formation not found",
"message": "No formation with id 'my-api'",
"code": 404
}503 Service Unavailable:
{
"error": "Formation unavailable",
"message": "Formation 'my-api' is not healthy",
"code": 503,
"status": "unhealthy"
}502 Bad Gateway:
{
"error": "Bad gateway",
"message": "Formation did not respond",
"code": 502
}GET /formations
GET /formations?status=running&limit=10&offset=0
Authorization: MUXI-HMAC key=..., timestamp=..., signature=...
{
"formations": [
{
"id": "my-api",
"name": "My API",
"version": "1.0.0",
"status": "running",
"port": 8001,
"pid": 12345,
"url": "http://localhost:3000/v1/my-api",
"health": "healthy",
"created_at": "2025-10-17T10:30:00Z",
"updated_at": "2025-10-17T10:30:00Z",
"restart_count": 0
}
],
"total": 1,
"limit": 10,
"offset": 0
}GET /formations/{id}
{
"id": "my-api",
"name": "My API",
"version": "1.0.0",
"status": "running",
"port": 8001,
"pid": 12345,
"url": "http://localhost:3000/v1/my-api",
"health": "healthy",
"working_dir": "/home/user/.muxi/server/formations/my-api",
"env": {
"PORT": "8001",
"FORMATION_ID": "my-api",
"MODEL_NAME": "gpt-4"
},
"created_at": "2025-10-17T10:30:00Z",
"updated_at": "2025-10-17T10:30:00Z",
"started_at": "2025-10-17T10:30:05Z",
"restart_count": 0,
"last_health_check": "2025-10-17T10:35:00Z"
}DELETE /formations/{id}
{
"id": "my-api",
"message": "Formation deleted successfully"
}Server actions:
- Stop the formation process (SIGTERM, then SIGKILL)
- Release the port back to pool
- Remove from registry
- Optionally delete files from
~/.muxi/server/formations/{id}/
POST /formations/{id}/stop
Response:
{
"id": "my-api",
"status": "stopped",
"message": "Formation stopped successfully"
}POST /formations/{id}/restart
Response:
{
"id": "my-api",
"status": "restarting",
"message": "Formation restarting",
"restart_count": 3
}GET /formations/{id}/logs?lines=100
{
"id": "my-api",
"logs": [
"[2025-10-17 10:30:05] INFO: Starting formation my-api",
"[2025-10-17 10:30:06] INFO: Listening on port 8001",
"[2025-10-17 10:30:15] INFO: Health check passed"
],
"lines": 3,
"total_lines": 150
}GET /health
No authentication required.
{
"status": "healthy",
"version": "1.0.0",
"uptime": 86400,
"formations": {
"total": 5,
"running": 4,
"stopped": 1,
"crashed": 0
}
}CLI stores credentials in ~/.muxi/profiles.yaml:
default:
key: "MUXI_abc123"
secret: "sk_xyz789"
server: "http://localhost:3000"
production:
key: "MUXI_prod_key"
secret: "sk_prod_secret"
server: "https://api.myserver.com"# Deploy formation
muxi formation deploy ./my-formation/
# List formations
muxi formation list
# Get details
muxi formation get my-api
# Logs
muxi formation logs my-api --follow
# Delete
muxi formation delete my-api
# Profile management
muxi config add-profile production \
--key=MUXI_prod_key \
--secret=sk_prod_secret \
--server=https://api.myserver.com$ muxi formation deploy ./my-formation/
1. Read formation.yaml from ./my-formation/formation.afs
2. Validate formation.yaml (has required 'id' field)
3. Create gzipped tarball of directory
4. Load credentials from ~/.muxi/profiles.yaml (default profile)
5. Compute HMAC signature
6. POST to /formations/deploy with gzip body
7. Display result| Version | Date | Changes |
|---|---|---|
| 1.0 | 2025-10-17 | Initial specification |
- AUTH.md - HMAC authentication details
- PRD.md - Product requirements
- docs/api-reference.md - User-facing API docs