`horde/oai` is a separate ASGI process that serves `/v1/chat/completions` and reaches the Horde over HTTP and Redis; it does not import the Flask application (ADR 0023).
- `FrontSettings` reads every `HORDE_OAI_*` variable through pydantic-settings; an invalid value stops the start and states the variable
- `horde/oai/schema.find_refused_parameter` returns the first unsupported key, role, content part or `horde` option for a 400 that states it, and `drop_default_no_ops` removes the `DEFAULT_NO_OP_PARAMETERS` keys holding their default, since stock clients send them unprompted; `ChatCompletionRequest` validates the rest against the shared `ChatHordeOptions`
- `HordeClient` rotates calls across the configured Horde API roots, skips and backs off a root that refuses or drops a connection, and sends cancels through a separate smaller pool, so slow cancels do not starve submits
- `CompletionSignalListener` holds one pattern subscription per process; after `HORDE_OAI_REDIS_LIVENESS_SECONDS` without a message it sends a PING with a token and subscribes again when the PONG does not arrive in time, because a black-holed connection does not deliver or raise anything
- `ChatRequestWatcher` reads the status on each signal, on a jittered fallback poll that stretches with the reported wait, and once more at the deadline; fail-fast is decided only from a status read in the current wake, and a failed deciding read is retried after `signal_recheck_seconds`, so a transient error does not cancel a request whose worker may have started; a request seen before that answers 404 waits up to the same interval for its faulted signal
- `create_app` serves `POST /v1/chat/completions`, `GET /v1/models`, `GET /v1/models/{id}` and `GET /healthz`; a completion request is read under a byte ceiling and read timeout (413 `RequestTooLarge`, 408 `RequestBodyTimeout`), screened, admitted through a per-source then a global in-flight cap, submitted to `/v2/generate/chat/async`, and answered as `chat.completion` or as server-sent events
- A bearer token is accepted only when every character is printable non-space ASCII, so a token that cannot be sent as a native header is 401 `InvalidAuthorizationHeader`
- `horde/oai/sse.py` buffers a stream: keepalive comments with the queue position while the request waits, then the role, content, finish and optional usage chunks and `[DONE]`; `horde/oai/mapping.py` builds the native body, the completion with `x_horde` details and estimated usage when the worker did not report any, and the OpenAI error envelope
- Once the Horde holds a request, or may hold one after a lost submit answer, every error carries `x-should-retry: false`, so a client SDK does not create a second charged job
- Cancels on disconnect, deadline, fail-fast and shutdown are bounded, shielded from the client's disconnect and sent at most once per request
- `python -m horde.oai` runs the app under uvicorn, trusting `X-Forwarded-For` from `HORDE_OAI_FORWARDED_ALLOW_IPS` and draining open requests for `HORDE_OAI_SHUTDOWN_DRAIN_SECONDS` on stop; `docker-compose.yaml` adds the `aihorde_oai` service
- Runtime dependencies fastapi, uvicorn, httpx, sse-starlette, pydantic-settings and sniffio, dev dependencies respx and openai; sniffio is declared because httpcore imports it on every request and a failed import is not cached
- `tests/unit/oai` drives the app against a respx-mocked Horde and fakeredis: settings, poll cadence, the completion and model routes, error envelopes, the request lifecycle and cancels, screening, admission, the Horde client and the listener's liveness ping
Establishes additional unit/integration testing, the CI to run it, and the runtime fixes those tests surfaced
Testing
- Add unit (ORM/function) and integration (API route) tiers over a shared dependency runtime that auto-provisions Postgres, Redis, and S3-compatible object storage (Garage) via testcontainers, with an external-deps mode.
- Add fixtures/factories plus coverage for user roles, kudos pricing, worker.can_generate, countermeasures, WP activation, and adversarial malformed-input route sweeps.
- Modularize the locust suite into locustsuite/ and add a stability-only smoke gate (check_smoke_results.py).
CI / tooling
- Rework pr/main/trusted-fork/publish workflows around composite actions (setup-test-python, start-test-stack) and a Dockerized PG+Redis+Garage stack.
- Bump the Python floor to 3.12 (+3.13 matrix) and consolidate on uv.
Runtime hardening
- Make telemetry activation opt-in (telemetry_enabled): the image stays telemetry-capable but a bare server.py is inert unless an OTLP endpoint or AI_HORDE_TELEMETRY_ENABLED is set.
- Turn NUL bytes, out-of-range user ids, and duplicate worker names into clean 4xx responses instead of session-poisoning 500s.
- Fix get_anon() to resolve the anon account by oauth_id (re-enabling the kudos-transfer guards); shrink and instrument the SQLAlchemy pool (15/5) to expose connection-hold inefficiency as pool timeouts rather than PG refusals.
* docs: readme.md introduction; register info rework
My hope is that a more non-technical opening will help explain the project better to a wider audience of people. Additionally, I reworked the registration section to hopefully be clearer at a glance without having to parse multiple paragraphs of prose.
* chore: add tazlin's email for license notices
* docs: rewrites, links to expanded docs; md linting fixes
* docs: add links to defs/detailed explanations
* docs: fix typo
* docs/style: fix formatting typo
* docs: add info about kudos econ. on discord
* docs: fix typo
* docs: link to `haidra-assets/docs`
Pull Horde's custom postgres image
Build aihorde Docker image in two stages to minimise rebuild time
Establish minimal necessary configuration to bring up a working composition
Fix pre-commit config so black and ruff hooks run
Catch and report exceptions in is_redis_up()
Add missing final EOL markers to some files (per black)
Update documentation