docs(adr): ADR 0021, how the assistants are built (D35–D44)
Step 2's decisions: the answer streams over Serverpod's own stream; the adapters are tested on replies written from the providers' documentation and on recorded ones later; the assistants, their keys and prices sit in deploy/assistants.env, passed whole to the server alone; the instance's /privacy describes the whole instance; the agreement is kept on the grant; figures are kept 3 months, by UTC's days and months; a member offers no assistant yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
31b9c17850
commit
115683e180
1 changed files with 77 additions and 12 deletions
|
|
@ -4,8 +4,8 @@
|
|||
developer: D1–D6 on 23 Oct; on 25 Oct what an assistant may see and do (D7–D12), what it
|
||||
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), and how the first step
|
||||
is built (D31–D34).
|
||||
(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).
|
||||
- **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
|
||||
|
|
@ -226,6 +226,70 @@ On 25 Oct, how the first step is built (sign-ups and operators):
|
|||
opening the waiting accounts, the accounts and the operators; a badge on the Settings
|
||||
cog while someone waits, as the sync trouble dot is.
|
||||
|
||||
On 25 Oct, how the second step is built (the assistants, their grants and figures):
|
||||
|
||||
- **D35 The answer streams over Serverpod's own stream** (recommended, over the whole
|
||||
answer at once and over a route sending Server-Sent Events): `assistant.ask` is an
|
||||
endpoint method returning a stream, over the WebSocket the client already keeps, signed
|
||||
in as every call is (Caddy upgrades it as it is). Text appears as the model writes it,
|
||||
and a round of the tools' loop ends as soon as its tool call is complete. A route of
|
||||
our own would have needed its own check of the session, for nothing Serverpod's stream
|
||||
lacks.
|
||||
- **D36 The adapters are tested on replies written from the providers' documentation
|
||||
first, and on recorded ones later** (recommended, over recording first, which would
|
||||
have held the step up until the developer had keys, and over the documentation alone):
|
||||
the fixtures follow Anthropic's and OpenAI's documented stream formats event by event;
|
||||
`tool/record_llm.dart`, which the developer runs once with their own key, writes the
|
||||
real replies beside them, and the tests run on both, so a provider that strays from its
|
||||
documentation is caught there, not in someone's hands.
|
||||
- **D37 The assistants are set in `deploy/assistants.env`** (recommended, over the whole
|
||||
of `deploy/.env` going to the server and over three fixed numbered slots): Compose names
|
||||
every variable it passes to a container, and the IDs an operator chooses can't be named
|
||||
in `compose.yaml` ahead. The file of their own is passed whole to the server alone
|
||||
(`env_file`, optional, so an instance without it starts as before), git-ignored,
|
||||
mode 600, with an example committed. It holds the keys too (`ASSISTANT_CLAUDE_KEY`):
|
||||
everything about the assistants is in one place, outside the database and its backups,
|
||||
and nothing of `deploy/.env` reaches the server that doesn't today. It holds the key of
|
||||
D30's IDs as well (`ASSISTANT_ID_KEY`, at least 32 characters, or a server with
|
||||
assistants doesn't start): changing it gives everyone a new ID at the providers, and
|
||||
costs nothing else.
|
||||
- **D38 Prices sit beside the model** (recommended, over typing them in the app):
|
||||
`ASSISTANT_CLAUDE_PRICE_INPUT` and `_PRICE_OUTPUT`, per million tokens, in
|
||||
`ASSISTANT_CURRENCY` (USD unless set, as the providers bill), so a model and its price
|
||||
change together, with one restart. A request's cost is worked out when it is counted
|
||||
and kept with the figures, so a later price leaves the past as it was billed. A limit
|
||||
is checked before each request against what was counted: the last request before it
|
||||
may go past it by its own answer, which the request's ceiling on output tokens keeps
|
||||
small.
|
||||
- **D39 The instance's `/privacy` describes the whole instance** (recommended, over the
|
||||
assistants alone): made from its settings on its web server, in English and German as
|
||||
the profile pages are, readable before signing up: who may sign up, where data and
|
||||
backups are, what is kept (addresses only as hashes, no IP address past the hour its
|
||||
limits count, sessions), private studysets, then each assistant (provider, model, what
|
||||
goes, D27) and whether personal keys are allowed. halkyon.li has no website page, and a
|
||||
family member reads this one before they sign up.
|
||||
- **D40 The agreement is kept on the grant** (recommended, over the person's sealed
|
||||
settings): the grant records when the person agreed and to which provider (its
|
||||
protocol and host), and `assistant.ask` refuses (`notAgreed`) until they have, so a
|
||||
modified app can't skip it and every device of theirs knows. Should the operator move
|
||||
an assistant to another provider, the person is asked again; a new model of the same
|
||||
provider asks nothing. The operator sees that they agreed, and when.
|
||||
- **D41 Figures are kept 3 months** (the developer's choice, over 13 months, which were
|
||||
recommended, and over keeping them as long as the account): enough for the last bills,
|
||||
less kept about each person. Older days are deleted daily, and all of them with the
|
||||
account.
|
||||
- **D42 Days and months are UTC's** (recommended, over the instance's time zone, which no
|
||||
setting holds today): the providers bill by UTC, so the figures add up to their bills;
|
||||
a day's limit resets at midnight UTC, which the app shows in the person's own time.
|
||||
- **D43 A member service offers no assistant yet** (recommended, over building the
|
||||
register's routes for grants now): grants and figures live on the register (Learn's
|
||||
server), whose service asks. Todo, a member, answers that it has none, until a member
|
||||
needs the assistant; then the member asks the register before forwarding, as it asks
|
||||
everything about accounts (ADR 0016).
|
||||
- **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.
|
||||
|
||||
## Proposal
|
||||
|
||||
1. **Who signs up, per instance:** `SIGN_UP`: `open` (as today), `approval`, or
|
||||
|
|
@ -243,9 +307,9 @@ On 25 Oct, how the first step is built (sign-ups and operators):
|
|||
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) and model in
|
||||
`deploy/.env` (`ASSISTANTS=claude,gpt`, then `ASSISTANT_CLAUDE_NAME`, `_PROTOCOL`,
|
||||
`_BASE_URL`, `_MODEL`), and its key in the passwords (`ASSISTANT_CLAUDE_KEY`). There
|
||||
OpenAI-compatible service), 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
|
||||
|
|
@ -258,9 +322,9 @@ On 25 Oct, how the first step is built (sign-ups and operators):
|
|||
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
|
||||
assistant at a time, each grant with its limits. The person agrees once to what goes where: the provider's name, that the
|
||||
instance sees the text on its way, and that the operator sees how much they used,
|
||||
never what.
|
||||
assistant at a time, each grant with its limits. The person agrees once to what goes
|
||||
where: the provider's name, that the instance sees the text on its way, and that the
|
||||
operator sees how much they used, never what; the grant keeps their agreement (D40).
|
||||
7. **Asking:** the field offers the assistants granted beside the web search (the one last
|
||||
used first), the app shows what it will send, the person sends it to the instance
|
||||
(`assistant.ask`, the answer streamed), the instance checks the grant and what is left
|
||||
|
|
@ -275,14 +339,15 @@ On 25 Oct, how the first step is built (sign-ups and operators):
|
|||
a personal key, a switch beside it).
|
||||
8. **Limits and statistics** (D3): per grant, per day and per month (a day's limit stops a
|
||||
runaway loop early). Counted per person, day and assistant: requests, input and output
|
||||
tokens, and the cost at the prices the operator enters per model (per million tokens;
|
||||
without prices, tokens only, and limits in tokens). Neither prompts nor answers are
|
||||
kept. The operator sees everyone's figures in the app, per person and month; each
|
||||
tokens, and the cost at the prices the operator enters per model (per million tokens,
|
||||
D38; without prices, tokens only, and limits in tokens), by UTC's days (D42), for
|
||||
3 months (D41). Neither prompts nor answers are kept. The operator sees everyone's figures in the app, per person and month; each
|
||||
person sees their own.
|
||||
9. **What people are told** (D27): the provider and model, what is sent, that the instance
|
||||
sees it in passing, that the operator sees each person's figures, that the instance
|
||||
keeps no content, and that conversations are kept sealed on the person's devices
|
||||
(D13).
|
||||
(D13): on the consent sheet, in Settings › Assistant, and on the instance's `/privacy`
|
||||
(D39).
|
||||
10. **Personal keys** (D5, D6), where the operator switches them on (`ASSISTANT_OWN_KEYS`,
|
||||
off unless `on`; `instance.info()` says so, and the app offers them only then): a
|
||||
person adds a provider in Settings (protocol, base URL, model, their key), and may add
|
||||
|
|
|
|||
Loading…
Reference in a new issue