metnos_http_server serves the web chat, turn API,
and Settings area. A new guided installation defaults to
0.0.0.0:8770 so the UI is reachable from the same private LAN; phase
4 can select loopback-only access or a different port. The installer prints the
exact usable URLs and saves them. The remote-executor protocol on
8765 and browser service on 8771 are separate
components with their own authority and routes.
At the end of phase 6, the installer shows one complete, one-use
administrator URL for the server and, when LAN access is enabled, one for every
detected private IPv4 address. It saves the same URLs in
~/.local/share/metnos/install_summary.md.
127.0.0.1;You do not need to derive an address or replace a placeholder: use one of
the printed URLs exactly as shown, including its scheme, address, port, and
temporary token. If the token has expired, run
./.venv/bin/python -m install --force-phase 6 again or sign in at
/admin/login. Do not forward the port from a router or expose the
plain-HTTP listener directly to the Internet.
Opening the Web UI from another browser is only interface access and does not require installing or pairing a remote executor. Pairing is needed only when the remote computer must also perform operations on its own local data.
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. Standard
forwarding headers such as 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, is
HttpOnly, uses Secure when the external request is
HTTPS, and is
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.