metnos_http_server serves the web chat, turn API,
and Settings area. Its default is 127.0.0.1:8770; an installation
may change both address and port. The remote-executor protocol on
8765 and browser service on 8771 are separate
components with their own authority and routes.
Ask Metnos with a request such as:
Show me where I can inspect recent turns and change the model configuration.
The answer should begin with the route visible in the chat:
Settings > Activity > Turns and
Settings > System > Models. If the question came from
Telegram, the answer must state that Settings opens in the web chat. Only a
person asking for a technical integration then needs the HTTP paths
/admin/turns and /admin/virt.
The make_app factory creates one aiohttp
application with a 50 MiB request-body limit and mounts three route
registries:
| Module | Responsibility |
|---|---|
http_routes_agent.py | Chat, discovery, turns, sessions, dialogs, attachments, web pairing, and OAuth callback. |
http_routes_admin.py | Settings, users, models, services, devices, scheduler, Safety, and executor evolution. |
http_routes_stack.py | Combined readiness for the server, catalog, and browser service. |
The same middleware order applies to every route: the first layer classifies the caller; the second places that user's language in the request context. The language context is copied into the turn pool as well, so concurrent requests do not share a global language variable.
agent_server, normally on 127.0.0.1:8765, handles polling, heartbeats, signed bundles, and remote-executor results. The Telegram daemon may host it in its own thread.playwright_sidecar, normally on 127.0.0.1:8771, manages browser sessions and is not exposed as a user API.POST /agent/register also exists on the chat port and reuses agent_server's canonical handler; one-time-token semantics are not duplicated.| Method and path | Function | Access |
|---|---|---|
GET / | Web chat; an anonymous caller is redirected to administrator login. | User or administrator; initial anonymous entry is admitted for the redirect. |
GET /agent/health | Liveness, API version, product version, and uptime. | Anonymous. |
GET /.well-known/metnos.json | Node descriptor and pairing URL; no administrative key or fingerprint is disclosed. | Anonymous. |
POST /agent/register | Consumes a remote executor's one-time token. | Anonymous with a valid token. |
GET /pair/{token} | Consumes a web pairing token and sets the paired-device cookie. | Anonymous with a valid token. |
GET /oauth/callback | Completes an OAuth flow using short-lived state issued by Metnos. | Public for redirect purposes; state is the boundary. |
The PWA manifest, service worker, and files below /static/ are
available without authentication. Photographs below
/agent/photos/ instead require a signed, expiring URL: the URL is
itself a capability and must be treated as confidential.
| Group | Paths | Contract |
|---|---|---|
| Direct turn | POST /agent/turn | JSON or an SSE stream on the same connection. |
| Resumable turn | POST /agent/turn/submit; GET /agent/turns/{id}/stream; GET /agent/turns/{id} | Immediate acceptance, reconnectable events, and polling fallback. |
| History and judgement | GET /agent/turns/recent; POST /agent/turns/{id}/feedback; POST /agent/turns/{id}/retry | Conversation history, explicit feedback, and retry. |
| Writer session | /agent/session/register, takeover, ping, revoke, events | One writing device per user-channel pair and explicit transfer. |
| Dialogs | /agent/dialog/{id}/form, submit, cancel, preview, context | Input collection with ownership and, when needed, a short-lived delegable capability. |
| Attachments | /agent/gallery/{turn_id}, /agent/photos/{turn_id}/{idx}, /agent/photos/web | Owner-bound gallery; signed local-file URLs; web proxy with SSRF defences and size limits. |
Every route below /admin, except the login entry, requires the
administrator role. The main areas are:
/admin/virt and /admin/services for models and services;/admin/turns, /admin/runs, /admin/builds, and /admin/timers for activity;/admin/changes, /admin/executors, and /admin/praxis for lifecycle and the cognitive engine;/admin/safety, /admin/users, and /admin/devices for signatures, identities, and devices;/agent/stack/health for combined readiness, despite its /agent prefix.The code-level route registries remain authoritative. This grouping explains the surface without freezing a route count that would become stale.
Middleware assigns one of three roles: anonymous,
user, or admin:
admin;user as an installation-compatibility path;An invalid Bearer never falls through to local-network trust.
CF-Connecting-IP and X-Forwarded-For are considered
only when the TCP peer belongs to METNOS_TRUSTED_PROXIES; an
ordinary client cannot declare itself local through a header.
The administrator key lives at PATH_USER_CONFIG/admin.key, is
created with mode 0600, and also derives signatures and encryption
keys through separate domains. The administrator cookie lasts seven days and
is HttpOnly, Secure, and
SameSite=Strict; the user cookie lasts ninety days and is
HttpOnly, Secure, and
SameSite=Lax.
The active session is keyed by (user_id, channel). Hosts and
guests can therefore use the chat concurrently without sharing a lease,
conversation, or browser-local memory. The client also separates tokens,
command buffers, and history through an opaque scope derived from the user.
When a second device finds an active session, the server issues a one-time token and leaves three choices to the interface:
Transfer rechecks both user and old writer atomically. Recent history
requires a conversation belonging to the user. Turn status, stream, gallery,
feedback, and retry also verify the turn owner: knowledge of a
turn_id does not grant access. Older records without
owner_user_id use only deterministic conversation or actor
bindings; an identity-store failure is denied rather than converted into
permission.
POST /agent/turn accepts JSON or multipart data containing a
request and reference images. With Accept: text/event-stream it
sends thinking, progress, and
tool_call events followed by final or
error; periodic comments keep the connection alive. Without SSE
it returns one JSON document.
POST /agent/turn/submit first reserves pool capacity and then
returns 202 with a turn_id and
stream_url. The turn continues if the page refreshes or the
network drops. The stream accepts Last-Event-ID and replays later
events; GET /agent/turns/{id} provides a polling fallback.
In-memory events remain for a bounded interval after completion. Once they are gone, the status route searches the persisted daily record. Persistence does not replace the ownership check.
Blocking turns pass through a dedicated pool with a global limit, a finite
queue, and a per-principal limit. If capacity cannot be reserved within the
admission window, the server returns
503 turn_capacity_exhausted with Retry-After: 1. A
disconnect does not release capacity prematurely while the worker thread is
still running.
Administration collections that use
negotiate_collection return HTML when Accept
contains text/html, and JSON otherwise. The body receives a
SHA-256-derived ETag; an equal If-None-Match produces
304. This does not apply to every route: some are HTML-only,
JSON-only, or SSE.
The chat uses no-cache, no-store so the client receives the
current contract. Negotiated administration responses have a short private
cache; model configuration and services use no-store. Signed
attachment URLs have their own lifetime and must not be confused with a user
session.
Settings uses Jinja2 templates, the
runtime/ui_surfaces.py registry for navigation and descriptions,
htmx for selected updates, and uPlot only in the executor-statistics view.
There is no front-end build step.
Every visible string must come from the i18n catalog or already-localised
data. Language is an individual user preference held in a request-local
ContextVar; a new language requires catalog entries, a detection
lexicon, and corresponding documentation rather than phrase-specific
conditions in templates.
Settings is the interactive administration interface. The HTML report
generated by runtime.observability is a separate artifact described
in operational observability.
10.0.0.0/8, and 192.168.0.0/16; this does not mean “every private network” and does not replace device pairing.PATH_USER_STATE prevents two servers for the same installation./agent/health says the process responds; administrator-only /agent/stack/health also checks the catalog, browser contract, and adjacent service.curl -fsS http://127.0.0.1:8770/agent/health curl -fsS http://127.0.0.1:8770/.well-known/metnos.json curl -fsS -H "Authorization: Bearer <administrator-key>" \ http://127.0.0.1:8770/agent/stack/health
The first probe proves liveness only. The third is suitable evidence before
a restart or a full-stack test. Suites below tests/runtime/http/
cover authentication, sessions, turn isolation, SSE, caching, uploads,
rendering, and administration pages; this guide does not freeze a test count.
runtime/metnos_http_server.py — factory, middleware, lifecycle, and pool;runtime/http_auth.py — roles, cookies, Bearer tokens, networks, and trusted proxies;runtime/http_routes_agent.py — chat, turns, sessions, dialogs, and attachments;runtime/http_routes_admin.py — Settings and administrative actions;runtime/http_routes_stack.py — combined readiness;runtime/http_render.py — templates, negotiation, and ETags;runtime/turn_events.py — resumable events and their ownership.