docs(adr): ADR 0021, chat completions' dialects and the Responses API (D45, D46)

D45: chat completions in each known host's dialect (OpenAI, Mistral,
Gemini, OpenRouter), in a common one to every other host. D46: OpenAI's
Responses API as a third protocol, stateless, its encrypted reasoning
sent back. D30's field at OpenAI is safety_identifier, at OpenRouter user.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-25 17:38:53 +01:00
commit e4c7259df3
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -5,7 +5,7 @@
keeps and does, where it lives, the proposal's points 4–8 and 10, how often it warns and
when it is built (D13–D18), who signs up and who operates (D19–D22), waiting accounts
(D23–D26), what people are told and what never goes (D27–D30), how the first step is
built (D31–D34) and the second (D35–D44).
built (D31–D34) and the second (D35–D46).
- **Builds on:** ADR 0016 (the register), ADR 0019 (private studysets), ADR 0020
(SimpleLogin), ADR 0022 (the hand-off and the connector gone).
- **Changes:** the rule of 10 Oct 2026 that Halkyon calls no language model with an API
@ -172,7 +172,8 @@ including/excluding private studysets?)"):
the person's own to send.
- **D30 The provider learns an opaque ID per person** (recommended, over nothing): with the
operator's key, each request carries an ID the instance derives from the account with a
key of its own (OpenAI's `user`, Anthropic's `metadata.user_id`), never the handle, so
key of its own (OpenAI's `safety_identifier`, OpenRouter's `user`, Anthropic's
`metadata.user_id`; servers with no such field get none), never the handle, so
the provider can act against one person's abuse without blocking the operator's key,
and can't tell who it is.
@ -289,6 +290,22 @@ On 25 Oct, how the second step is built (the assistants, their grants and figure
- **D44 Nobody is granted an assistant that isn't set**: a grant names the assistant by
its ID, and stays, unused, should the operator remove that assistant from the file;
put back, it holds again, with the person's agreement if the provider is the same.
- **D45 Chat completions are sent in each known host's dialect, and in a common one to
every other** (recommended, over the fields every service takes, which loses OpenAI's
`store: false` and its reasoning models' ceiling, and over a dialect the operator
names): OpenAI, Mistral and Gemini refuse a field they don't know, and they differ in
the ceiling's field (`max_completion_tokens`, `max_tokens`), in usage asked for or
always sent, and in `store`. By the base URL's host, OpenAI (api.openai.com), Mistral,
Gemini and OpenRouter each get only their fields; any other host gets what Ollama,
vLLM and llama.cpp all take. Replies are read leniently whatever the host, as each
streams usage, tool calls and errors in its own way.
- **D46 OpenAI's Responses API is a third protocol, `openai-responses`** (recommended,
over chat completions alone): from GPT-5.4 on, OpenAI's models call tools in chat
completions only without reasoning, and GPT-6 Astra and 6.1 Sol only through the
Responses API. Stateless: `store: false`, so OpenAI keeps no conversation, and each
request carries the whole of it, with the model's encrypted reasoning items as it
streamed them, sent back in their place (as Anthropic's thinking blocks are), and the
`phase` of its messages. Ollama, vLLM and OpenRouter speak it as well.
## Proposal
@ -306,19 +323,19 @@ On 25 Oct, how the second step is built (the assistants, their grants and figure
the list. The operator's app shows a badge; there is no mail, as the instance keeps no
addresses.
4. **The assistants**, set by the operator (D4): one or more, each with an ID, the name
people see ("Claude Sonnet"), its protocol (`anthropic`, or `openai` for every
OpenAI-compatible service), its base URL (the provider's own unless set), model and key
people see ("Claude Sonnet"), its protocol (`anthropic`, `openai` for every
OpenAI-compatible service, or `openai-responses`, D46), its base URL (the provider's own unless set), model and key
in `deploy/assistants.env` (D37: `ASSISTANTS=claude,gpt`, then `ASSISTANT_CLAUDE_NAME`,
`_PROTOCOL`, `_BASE_URL`, `_MODEL`, `_KEY`, `_IMAGES`, and the prices of D38). There
rather than in the app: the keys stay out of the database and its hourly backups, where
the operator keeps the instance's other secrets. None set: no assistant on the
instance, as today. Adding Lumo once its API is out, or switching a model, is a change
of these and a restart; no app changes.
5. **Two adapters of our own** behind one interface, over plain HTTP, no SDKs, in a pure
5. **Adapters of our own, one per protocol (D46),** behind one interface, over plain HTTP, no SDKs, in a pure
Dart package of their own, which the server uses for the operator's assistants and the
app for personal keys (10). Features use only what both protocols share: a system prompt and messages,
streamed text, and tool calls where a feature needs them. Tested against recorded
replies of each. Not chosen: LiteLLM as a sidecar (a Python proxy for about 100
streamed text, and tool calls where a feature needs them. Tested against documented
and recorded replies of each (D36). Not chosen: LiteLLM as a sidecar (a Python proxy for about 100
providers: one more container to run and update, for providers never used) and
langchain_dart (a large framework for a small job).
6. **Grants** (D2, D4): nobody has an assistant until the operator grants it to them, one