Lab Notes
Tooling

Media Lab Server

The local HTTP server (`127.0.0.1:8765`) for image and music generation. Architecture, API, lifecycle, and integration with the Coordinator.

Status

Implemented. The server is a standalone process that the Coordinator calls through HTTP. It runs on the local host and is not exposed to the public internet.

Purpose

The Media Lab Server is a small HTTP service that exposes the lab's image and music generation capabilities through a web UI and an HTTP API. The Coordinator calls the API to generate images and music in response to user requests; the user can also visit the web UI directly in a browser.

This page documents the server's architecture, the endpoints, the lifecycle, and the integration with the Coordinator.

Why a server, not a CLI tool

A server is preferred over a CLI tool for generation because:

  • Stateful UI. A web UI can show progress, history, and downloads.
  • Long-running requests. Generation can take 30 seconds to 3 minutes; an HTTP request fits naturally.
  • Model host isolation. The server can be restarted independently of the Coordinator.
  • Multiple clients. The user can hit the UI from a browser while the Coordinator also calls the API.

The server is the only HTTP server in the lab that exposes generation capabilities.

Listening address and port

FieldValueReason
Host127.0.0.1Localhost only; not exposed to the LAN or WAN.
Port8765Reserved for the Media Lab Server.
ProtocolHTTP/1.1Simple; no TLS needed because it is localhost.

The server is bound to localhost only. It is not reachable from the LAN or the internet. The user can access the UI at http://127.0.0.1:8765/ in a browser on the same machine.

Architecture

The server is a Python process. The architecture:

Loading diagram…
Media Lab Server architecture: HTTP API, web UI, in-process job queue, image and music workers, output directory.

The server has:

  • An HTTP API that accepts job requests.
  • A web UI that submits jobs and shows progress.
  • A job queue that serializes generation requests.
  • An image worker that calls the image model.
  • A music worker that calls the music model.
  • An output directory where results are written.

Endpoints

GET /api/status

Returns the server's health and current load.

Response (200):

{
  "status": "ok",
  "version": "0.4.0",
  "queue": {
    "pending": 0,
    "running": 0,
    "completed": 12
  },
  "models": {
    "image": "image-01",
    "music": "Music-2.6"
  }
}

POST /api/image

Submits an image generation job.

Request:

{
  "prompt": "A sunset over the ocean, oil painting style",
  "size": "1024x1024",
  "quality": "high",
  "count": 1
}

Response (202):

{
  "job_id": "img_abc123",
  "status": "pending",
  "poll_url": "/api/jobs/img_abc123"
}

POST /api/music

Submits a music generation job.

Request:

{
  "prompt": "Lo-fi hip hop, rainy night, piano and vinyl crackle",
  "duration_s": 120,
  "instrumental": true
}

Response (202):

{
  "job_id": "mus_def456",
  "status": "pending",
  "poll_url": "/api/jobs/mus_def456"
}

GET /api/jobs/<id>

Polls the status of a job.

Response (200) for a completed image job:

{
  "job_id": "img_abc123",
  "type": "image",
  "status": "completed",
  "created_at": "2026-06-10T22:30:00Z",
  "completed_at": "2026-06-10T22:30:42Z",
  "outputs": [
    {
      "url": "/outputs/img_abc123_0.png",
      "size_bytes": 1843200
    }
  ]
}

Response (200) for a completed music job:

{
  "job_id": "mus_def456",
  "type": "music",
  "status": "completed",
  "created_at": "2026-06-10T22:30:00Z",
  "completed_at": "2026-06-10T22:32:18Z",
  "outputs": [
    {
      "url": "/outputs/mus_def456_0.mp3",
      "duration_s": 120,
      "size_bytes": 2400000
    }
  ]
}

Response (200) for a running job:

{
  "job_id": "img_abc123",
  "status": "running",
  "progress": 0.65
}

GET /api/jobs

Lists recent jobs. Pagination through ?limit=N&offset=M.

Response:

{
  "total": 50,
  "limit": 20,
  "offset": 0,
  "jobs": [
    {"job_id": "img_abc123", "type": "image", "status": "completed", "created_at": "..."},
    {"job_id": "mus_def456", "type": "music", "status": "completed", "created_at": "..."}
  ]
}

GET /outputs/<file>

Returns the generated file. Static endpoint; the file path is the file name in the output directory.

GET /

The web UI. Single-page application that submits jobs and shows progress.

Job lifecycle

A job goes through these states:

Loading diagram…
Job lifecycle state machine: pending → running → completed or failed.
StatusMeaning
pendingThe job is in the queue; no worker has picked it up.
runningA worker is generating the output.
completedThe output is ready; outputs field is populated.
failedGeneration failed; error field is populated.

The state machine is in-process; the queue is an in-memory list. If the server is restarted, the queue is lost. Pending jobs are re-submitted by the client.

Image generation

The image worker calls the model provider's image API. The default model is image-01. The supported sizes:

SizeUse case
512x512thumbnails, small previews
1024x1024default, square images
1536x1024landscape images
1024x1536portrait images
2048x2048high-resolution images

The image is returned in PNG by default. The output is written to the output directory as img_<job_id>_<index>.png.

The average generation time is:

SizeAverage duration
512x5128-12 s
1024x102412-18 s
1536x102418-25 s
2048x204830-45 s

The duration depends on the provider's load.

Music generation

The music worker calls the provider's music API. The default model is Music-2.6. The supported parameters:

ParameterDefaultRangeNotes
duration_s601 to 300Provider caps at 5 min
instrumentaltrueboolIf false, lyrics required
lyrics(none)stringRequired if instrumental: false
temperature1.00.0 to 2.0Higher = more creative
seed(none)integerReproducible outputs

The output is MP3 by default. The output is written to the output directory as mus_<job_id>_<index>.mp3.

The average generation time:

DurationAverage duration
30 s30-60 s
60 s60-120 s
120 s120-240 s
300 s300-600 s

Quota management

The server tracks the daily quota for image and music generation. The quota is reset at midnight UTC. The default quotas are:

ResourceDaily quotaSource
Image50 images/dayPlan default
Music100 songs/dayPlan default

The server rejects jobs that would exceed the quota with 429 Too Many Requests and a JSON body:

{
  "error": "quota_exhausted",
  "resource": "image",
  "used": 50,
  "limit": 50,
  "resets_at": "2026-06-11T00:00:00Z"
}

The Coordinator checks the quota before submitting a job to avoid wasted round-trips.

Output directory

The server writes outputs to ~/.openclaw/media/outbound/ by default. The Coordinator can configure the path through the MEDIA_LAB_OUTPUT_DIR environment variable.

The directory structure:

~/.openclaw/media/outbound/
├── img_<job_id>_<index>.png
├── mus_<job_id>_<index>.mp3
└── .trash/
    └── <files moved to trash, retained for 7 days>

Old files are cleaned up by a retention job that runs daily at 03:00 local time. The default retention is 30 days.

Lifecycle

The server is supervised by launchd (macOS) or systemd (Linux). The supervisor's responsibilities:

  • Start on boot. The server starts when the user logs in.
  • Restart on crash. The supervisor restarts the server if it crashes.
  • Reload on configuration change. The supervisor sends SIGHUP to reload the configuration.

Launchd plist (macOS)

The plist is in {workspace-root}/services/media-lab-server/launchd.plist. The launchd label is ai.openclaw.media-lab-server. The supervisor's log is at ~/.openclaw/media/logs/launchd.out.log.

Systemd unit (Linux)

The unit is in /etc/systemd/system/openclaw-media-lab.service. The unit name is openclaw-media-lab. The unit's log is at /var/log/openclaw/media-lab.log.

Configuration

The server's configuration is in {workspace-root}/.openclaw/media-lab-server/config.toml.

FieldDefaultPurpose
host127.0.0.1Bind address.
port8765Bind port.
output_dir~/.openclaw/media/outbound/Output directory.
retention_days30Output retention.
image_modelimage-01Default image model.
music_modelMusic-2.6Default music model.
quota_image50Daily image quota.
quota_music100Daily music quota.
log_levelINFOLog level.
log_formatjsonLog format.

Logging

The server logs to ~/.openclaw/media/logs/server.log. The format is JSON Lines by default. Each log entry includes:

  • timestamp: ISO 8601 UTC.
  • level: INFO, WARN, ERROR.
  • event: the event name (e.g., job_submitted, job_started, job_completed).
  • job_id: the job ID.
  • duration_ms: for completed jobs.
  • error: for failed jobs.

Example:

{"timestamp": "2026-06-10T22:30:00Z", "level": "INFO", "event": "job_submitted", "job_id": "img_abc123", "type": "image"}
{"timestamp": "2026-06-10T22:30:42Z", "level": "INFO", "event": "job_completed", "job_id": "img_abc123", "type": "image", "duration_ms": 42000}

Health checks

The server exposes GET /api/status for health checks. The Coordinator and the audit scripts poll this endpoint periodically. The expected response is 200 OK with the server's status.

A failed health check indicates:

  • The server is down (restart the supervisor).
  • The server is overloaded (wait and retry).
  • The model provider is down (check the provider's status page).

Failure modes

FailureResult
Model provider is downReturn failed with provider error.
Quota exhaustedReturn 429 with quota info.
Output directory is fullReturn failed with disk full error.
Job queue is fullReturn 503 with retry-after header.
Server crashesSupervisor restarts. Queue is lost.
Restart during jobJob is lost; client must resubmit.

Integration with the Coordinator

The Coordinator calls the server through the API. The Coordinator's typical flow:

Loading diagram…
Coordinator-to-Media Lab Server flow: quota check, submit, poll, deliver.

The Coordinator polls the job until it is completed or failed. The polling interval is 5 seconds for image jobs, 10 seconds for music jobs.

Security

The server is bound to localhost. It does not require authentication because the only clients are local processes. If the user wants to expose the server to the LAN (e.g., to call from a phone), the user must:

  1. Change the bind address to 0.0.0.0.
  2. Add a reverse proxy (e.g., nginx) with TLS.
  3. Add authentication (e.g., a token in the request header).

The lab does not provide these features out of the box.

See also