Local control surface
Drive and observe MARS through the embedded dashboard API.
The shipped dashboard is part of the Go binary. It serves local
HTML pages, authenticated JSON control endpoints, bounded read APIs, and a
server-sent-events stream for live updates. Use this reference
when using the local operator UI or building a bounded companion, or
incident playbook around a running mars start or
mars serve process.
Start The Dashboard
mars start runs a scoped single-repo lifecycle.
mars serve runs the persistent orchestrator. Both can
expose the embedded dashboard. The listener is always loopback-only.
Page/login shells, embedded assets, and a minimal redacted status
projection remain an observer surface. Repos, roles, telemetry,
evolution, quality, throughput, orchestration, decisions, SSE, and browser mutations require
MARS_DASHBOARD_CONTROL_SECRET with at least 32 bytes,
login, the exact Host/Origin, and the current session CSRF token.
Single target lifecycle
MARS_DASHBOARD_CONTROL_SECRET='<owner-provided-32+-byte-secret>' \
mars start --repo /path/to/repo \
--dashboard-addr 127.0.0.1:9090 \
--addr 127.0.0.1:9091 \
--execution-profile host --acknowledge-host-execution
Use this when one target repo is the active autonomous lifecycle.
Persistent orchestrator
mars serve \
--addr 127.0.0.1:9091 \
--concurrency 2 \
--execution-profile host --acknowledge-host-execution
Use this when webhooks, cron, and multiple registered repos are managed by one process.
Open and verify
open http://127.0.0.1:9090
curl http://127.0.0.1:9091/healthz
curl http://127.0.0.1:9090/api/status
Port fallback:
scoped start can fall back to an available local
dashboard port when the requested dashboard address is occupied.
Use the URL printed by the running command.
Addresses
MARS uses two loopback-only HTTP surfaces during runtime. Keep them distinct
when scripting checks or debugging a port issue.
| Surface | Default shape | Contains | Use it for |
| Dashboard address | --dashboard-addr | Data-free page/login shells, embedded assets, minimal status, authenticated bounded /api/events, controls, and privileged read APIs. | Local observer shell and authenticated operator control. |
| Webhook/control address | --addr | /healthz, GitHub webhook receiver, Jira webhook receiver. | Health checks and external event ingress. |
curl http://127.0.0.1:9091/healthz
curl http://127.0.0.1:9090/api/status
Pages
The HTML pages are operator views. They do not replace repo-owned
artifacts such as tickets, feature contracts, traces, release notes,
or quality score documents.
| Path | Page | User question |
/ | Redirect | Redirects to /pipeline. |
/pipeline | Pipeline Flow | Which lifecycle role is active and what path is next? |
/orchestration | Orchestration | Which dispatch mode, role topology, decisions, and stop reasons are visible? |
/roles | Role Health | Which roles have recent score, success, failure, or quality signals? |
/throughput | Throughput | How many jobs are pending, running, completed, or failed? |
/debug | Debug | What does the operator need to inspect during a runtime incident? |
/evolution | Evolution History | What telemetry, repeated patterns, or self-improvement events are visible? |
Events Stream
GET /api/events is a server-sent-events stream. It
requires an authenticated session even on loopback and accepts GET
only. It
sends an initial keepalive comment, replays buffered recent events,
then streams new events as they are broadcast. Replay, event size,
client count, and per-session connections are bounded. Event payloads are
JSON strings with type and data fields.
Subscribe
curl -N http://127.0.0.1:9090/api/events
Typical event line
data: {"type":"status_change","data":"{\"state\":\"paused\"}"}
| Event type | When users see it | Common reaction |
status_change | Pause, resume, restart, stop, or worker state changes. | Refresh status or update companion UI state. |
scan_complete | A dashboard-triggered scanner pass finishes. | Inspect generated tickets and findings count. |
| Job and telemetry events | Runtime components broadcast dashboard updates. | Use as notification hints, then read durable repo evidence. |
Control Endpoints
POST /api/login accepts only JSON containing the
environment-configured secret and returns an opaque HttpOnly
SameSite=Strict session cookie plus the session CSRF token.
After the login-shell redirect, authenticated same-origin
GET /api/session bootstraps the current CSRF without a
second secret entry or URL/browser-storage persistence. Mutating
endpoints require POST, that cookie, exact same-origin
Origin, and X-MARS-CSRF-Token. Bodyless
mutations reject any body; JSON controls reject unknown fields,
trailing values, oversize values, and non-JSON content. They return
JSON with {"ok":true} on success, or an
error field when the request cannot be applied.
| Endpoint | Method | Request | Behavior |
/api/pause | POST | Empty body. | Pauses worker claims; running jobs are allowed to finish. |
/api/resume | POST | Empty body. | Resumes worker claims after a pause. |
/api/restart | POST | Empty body. | Warm restart: stop workers, reload manifests and triggers, start workers again. HTTP stays up. |
/api/stop | POST | Empty body. | Requests graceful orchestrator shutdown. |
/api/scan | POST | {"repo_id":"..."} | Runs scanner for a registered repo and can enqueue stale-ticket hygiene. |
/api/run-role | POST | {"repo_id":"...","role":"engineer"} | Validates the role in the repo manifest and enqueues a manual run. |
/api/emergency-stop | POST | Empty body. | Runs configured emergency-stop callbacks; raw callback errors remain in owner-only local logs. |
/api/logout | POST | Empty body. | Invalidates the current session; requires the same mutation policy. |
/api/session | GET | Authenticated session cookie. | Returns the current session CSRF for same-origin browser bootstrap. |
Status And Selection Endpoints
| Endpoint | Method | Response | User purpose |
/api/status | GET | healthy, paused, active_jobs, uptime_secs, repos. | Read live orchestrator state. |
/api/repos | GET | Array of registered repos with id and redacted basename. | Resolve repo IDs before scan or run-role without exposing personal paths. |
/api/repo-roles?repo_id=... | GET | Array of role names. | Populate role selectors for a repo. |
Read APIs
When the dashboard is created by the orchestrator, serve
attaches authenticated read APIs that power the dashboard pages.
Unauthenticated loopback requests receive 503 when no control
secret exists or 401 when login is required. Treat these as
local operational evidence views, not as durable storage.
| Endpoint | Response shape | User purpose |
/api/telemetry | {"events":[...],"stats":{...}} | Inspect recent telemetry events and failure category counts. |
/api/evolution | {"evolutions":[...],"telemetry":[...],"patterns":[...]} | Inspect self-improvement history and repeated telemetry patterns. |
/api/roles | {"roles":[...]} | Inspect role scores, sample sizes, success counts, and failure counts. |
/api/quality-score | text/markdown | Serve the first registered repo's docs/QUALITY_SCORE.md or the scoped repo quality score. |
/api/throughput | {"hourly":[...],"recent_jobs":[...],"summary":{...}} | Inspect recent job throughput and status distribution. |
/api/orchestration | {"repos":[...],"dispositions":[...],"decisions":[...]} | Inspect dispatch mode, role topology, dispositions, and decisions. |
/api/orchestration/decisions | {"decisions":[...]} | Inspect recent decisions, optionally filtered with ?repo_id=.... |
Responses And Errors
Scripts should branch on HTTP status and then read the response
body. The dashboard favors actionable text over opaque codes.
| Status | When it appears | What to do |
200 | Successful page, stream, read API, or control action. | Parse the JSON, markdown, HTML, or SSE response for the endpoint. |
400 | Host, body, value, or JSON shape is invalid. | Use the exact printed dashboard authority and documented bounded request. |
401/403 | Session, exact Origin, or CSRF is absent, expired, or mismatched. | Log in again through the exact dashboard origin. |
404 | Unknown page, unknown static asset, or unavailable quality score because no repo is registered. | Verify the dashboard URL and repo registration state. |
405 | Using GET for a post-only endpoint, or POST for a get-only endpoint. | Use the method in this reference. |
415 | A JSON endpoint received another content type. | Send exactly application/json. |
429 | Login, mutation, or SSE connection pressure exceeded a bound. | Honor Retry-After and retry later. |
500 | Callback failure, render failure, or control action error. | Read the error body and inspect the command log. |
501 | A dashboard was created without the corresponding callback. | Use the endpoint only from a full start or serve runtime. |
Successful control response
{"ok":true}
Control error response
{"ok":false,"error":"repo_id and role are required"}
Emergency-stop callback errors
{"ok":false,"error":"emergency stop did not complete; inspect the owner-only local command log"}
Recipes
Pause safely, inspect, then resume
Log in through the embedded browser, then use Pause and Resume.
Programmatic clients must implement the same session, exact
Origin, and CSRF exchange; anonymous curl mutations fail closed.
Resolve repo and role before manual run
curl http://127.0.0.1:9090/api/repos
curl "http://127.0.0.1:9090/api/repo-roles?repo_id=REPO_ID"
Use the authenticated dashboard controls to run the selected role.
Run a scanner pass
Authenticate in the embedded browser, choose a repository, and
use Scan. Afterward inspect generated tickets and the bounded
scan_complete event.
Watch orchestration decisions
curl http://127.0.0.1:9090/api/orchestration
curl "http://127.0.0.1:9090/api/orchestration/decisions?repo_id=REPO_ID"
Stop gracefully from a script
Use terminal q, Ctrl+C, or the authenticated dashboard
Stop button. Anonymous POST requests are deliberately rejected.
Security
The dashboard API is an operator control surface. MARS binds it only
to loopback. Optional remote browsing requires an exact HTTPS
reverse-proxy origin selected by --dashboard-trusted-origin
or MARS_DASHBOARD_TRUSTED_ORIGIN, and every remote route
requires a valid session. Forwarded headers are ignored.
- Use
127.0.0.1:PORT for local-only operation.
- Set the control secret only through
MARS_DASHBOARD_CONTROL_SECRET; never put it in flags, YAML, URLs, logs, or generated files.
- The embedded browser sends the secret only to the bounded login body; every later mutation uses the opaque session and session CSRF.
- Strict self-only CSP, safe DOM text construction, vendored pinned assets, and no runtime CDN make the dashboard usable with outbound networking disabled.
- Prefer repo-owned evidence and logs for audit trails; dashboard responses are live operational views.
Troubleshooting
| Symptom | Likely cause | Recovery |
/healthz works but /api/status fails | You are calling the webhook/control address instead of the dashboard address. | Use the dashboard URL printed by start or serve. |
/api/repo-roles returns 400 | Missing repo_id query parameter. | Call /api/repos, copy an ID, and retry with ?repo_id=.... |
/api/run-role returns an error | Role is not present in that repo's manifest or the repo ID is wrong. | Call /api/repo-roles?repo_id=... and choose one of the returned roles. |
| SSE stream connects but no updates arrive | No new dashboard events have been broadcast yet. | Trigger a benign status change, such as pause and resume, or inspect /api/status. |
Quality score endpoint returns 404 | No repo is registered or no scoped repo is available. | Register or start a repo, then run mars scores export --repo /path/to/repo if the artifact is missing. |
Control endpoint returns 501 | The dashboard was created without that callback. | Use a full MARS runtime rather than a test or standalone dashboard handler. |