Every internal failure was mapped to a fixed sanitized code before anything
recorded the cause, so an intermittent execution_failed was undebuggable: an
exhausted model-call budget, a model turn with no text, and a genuine crash all
looked identical.
The bridge now writes one bounded line to container stderr on failure with the
code, the internal reason, the state and both call counters, and the failure
sites pass a reason (budget exhausted, empty final answer with its content block
types, token counts against the limits). The wire error is unchanged.
The backend logs the agent's terminal code together with its own call counters,
and the sanitized tail of container stderr rather than only its byte count.
deploy/logging.json gives every logger a timestamp (uvicorn's default config
leaves non-uvicorn loggers on logging's fallback handler); override with
CONFLUENCE_WEB_LOG_CONFIG.
The origin check is an exact match including the context path, so users had
to type "https://collab.lge.com/main" precisely. GET /api/v1/config now
returns the approved origins in canonical form (non-secret: they are the only
destinations the backend will talk to), and the UI swaps the URL text field
for a select listing them, keeping the element id, focus handling and the
Test connection flow unchanged. The text field remains the fallback when the
fetch fails. Backend validation of the submitted URL is untouched. Mock server
serves the endpoint; contract, API and e2e tests cover it.
The 100 Confluence / 50 model call caps per query were fixed Settings
defaults. CONFLUENCE_WEB_MAX_CONFLUENCE_CALLS and CONFLUENCE_WEB_MAX_MODEL_CALLS
now set them (1-1000 each, validated at startup). Cache hits stay free. The
request history still keeps at most 100 tool entries, so a Confluence budget
above 100 drops later entries with the existing history_overflow warning;
documented next to the setting.
In-memory AdmissionController (idle/reserved/running, FIFO tickets keyed by
the session cookie, reservation and heartbeat expiry, promotion after
cleanup, EMA wait estimate) behind POST /api/v1/queue/join,
GET /api/v1/queue/status and DELETE /api/v1/queue/ticket. POST /api/v1/query
claims the session's reservation first and joins implicitly when idle.
Settings: CONFLUENCE_WEB_QUEUE_{RESERVATION_SECONDS,HEARTBEAT_SECONDS,MAX_LENGTH}.
The 180 s query cap was enforced independently by the backend clamp, the
agent limits, and the container supervisor. All three now follow
CONFLUENCE_WEB_MAX_DEADLINE_SECONDS (default 900, allowed 60-3600): the
backend passes it into the container at launch, the supervisor reads it and
forwards it to the bridge, and both fall back to 900 s on invalid input. The
query timeout must not exceed it (startup fails otherwise). The supervisor
keeps a separate 180 s guard for a container that never receives a start
frame.
Add assets/book.svg as the tab icon: the backend serves assets/ and the CSP
allows same-origin images (the sanitizer still never emits <img>).
- Drop --init so the image supervisor stays namespace PID 1, as the
runtime handoff requires; add --memory-swap=1g and nosuid,nodev tmpfs
options to match the runtime's tested launch flags.
- Mount only frontend css/, js/ and vendor/ at the root origin so
index.html's relative asset paths resolve; dev/tests are not exposed.
- Add X-Content-Type-Options: nosniff to every response.
- Add CONFLUENCE_WEB_CONFLUENCE_PROXY (socks5/http, Confluence only) and
CONFLUENCE_WEB_MODEL_TIMEOUT_SECONDS; disable environment proxy
inheritance for both upstream clients; add socksio dependency.
FastAPI app, upstream Confluence/model adapters, authoritative history,
rootless container lifecycle, artifact storage and downloads, fake peers
under backend/dev, tests under tests/backend. Root pytest.ini deselects
the live marker by default; requirements gain the backend dependencies.