Commit Graph
7654 Commits
Author SHA1 Message Date
Marc-André Bombeck 3df5754ab1 Merge pull request #1172 from MBombeck/release/v1.41.0
v1.41.0
v1.41.0
2026-10-06 23:50:57 +02:00
Marc-André Bombeck 42e4292683 docs(changelog): note the dependency advisories 2026-10-06 23:26:51 +02:00
Marc-André Bombeck a3efedfb07 fix(deps): raise the MCP SDK to 1.32 and proxy-addr to 2.0.8 2026-10-06 23:26:40 +02:00
Marc-André Bombeck 4d6c342827 docs(changelog): name the thinking depths as the app does 2026-10-06 23:19:24 +02:00
Marc-André Bombeck 5bbc915073 chore(release): v1.41.0 2026-10-06 23:09:56 +02:00
Marc-André Bombeck 305765194e Merge remote-tracking branch 'origin/v141/integration' into v141/f1-providers 2026-10-06 21:50:22 +02:00
Marc-André Bombeck 1acd41eb58 fix(ai): keep tools on the forced final round and narrow reasoning-dialect learning
The Coach loop's final round sent toolChoice "none" without the tool
definitions. With tool_use/tool_result blocks in the history Anthropic
refuses such a request (400), and OpenAI Chat Completions refuses
tool_choice without tools, so every budget, time, cap and no-progress stop
ended in an error on those wires. The final round now carries the tools
with toolChoice "none", which every wire accepts, thinking included.

Codex kept reasoning items as provider state even when the request asked
for no encrypted reasoning; replaying such an item under store:false is a
400. Items are kept only when the request included
reasoning.encrypted_content and the item carries it.

The reasoning dialect cache learned from any 4xx that mentioned
reasoning or thinking (auth, rate limits, model names such as
deepseek-reasoner) and from replay errors, and kept the downgrade for the
life of the process. Learning now requires an HTTP 400 that refuses a
sent parameter, ignores replay errors and the model name, and a learned
downgrade expires after an hour.
2026-10-06 21:50:19 +02:00
Marc-André Bombeck 49ce23809a Merge held memory writes and trail data fixes into release/v1.41.0 2026-10-06 21:50:01 +02:00
Marc-André Bombeck 5361239ae5 fix(coach): keep a turn's fact and plan only with the answer that carries them
remember_fact and propose_plan wrote their rows during the tool round. An
answer that was then blocked, failed or abandoned left the fact in memory
without its note and Undo, and left the plan listed, activatable and fed
into later turns and the briefing. Both are now staged under an id minted
up front and written in the transaction that stores the assistant message,
only when its provenance carries them. A background proposal counts as
offered only once an answer carrying it is stored.

Plan text is model text: ifCue, thenAction and target pass the outbound
dose and risk screen in the person's locale, with their scheduled doses and
medication names, and the injection screen; a trip declines as "unsafe".

The trail route serves recalled facts and the proposal's fact to the owner
whatever the AI capability (only the model-written entries are gated), and
withholds medication facts while the medications module is off.

Accepting a fact proposal is decided under a per-message advisory lock, so
concurrent taps write one fact. Editing a fact re-reads its wording: text
naming a medication moves to "medication", other health wording moves a
non-health fact to "condition", and a health fact the person edited counts
as confirmed.
2026-10-06 21:49:27 +02:00
Marc-André Bombeck 8fd7e9f7a2 style: draw keyboard focus in a neutral tone everywhere 2026-10-06 21:37:48 +02:00
Marc-André Bombeck bd7a354ddd Merge the Coach interface polish into release/v1.41.0 2026-10-06 21:37:29 +02:00
Marc-André Bombeck fac55720a4 Merge the named-medication dose screen into release/v1.41.0 2026-10-06 21:31:50 +02:00
Marc-André Bombeck 27b94a6bf9 fix(safety): count drug names, class words and spelled counts in the dose screen
The dose-change class demanded a dosage-form noun next to every skip, stop
or extra imperative, and a drug name never counted. "Skip the amlodipine",
"then take an extra ramipril", "take one more tablet of ramipril", "Nimm
abends eine Tablette mehr", "Lass das Ramipril morgen weg" and "Stop your
blood thinner before the run" all passed.

A medication noun is now a dosage-form word, a drug-class word per locale
(blood thinner, Blutverdünner, anticoagulant, statin, insulin, ...), a
common generic-name stem (-pril, -sartan, -olol, -dipine, -formin, ...),
or one of the person's own medication names. The Coach loads the names
with the turn context and passes them to the reply screen and the trail
screen; a scheduled brand name is only known that way.

Add the extra / another / one more shape in en, de, fr, es, it, pl and ko,
spelled-out counts as a dose target, and stop / leave out / go without a
named medication. The number-free shapes use a two-word slot that a
preposition ends, so "take an extra minute to log your medication" and
"stop worrying about your medication" pass. A sentence that only names a
drug, a schedule description, a past change from the log, a negated
warning and a referral ("ask your doctor whether you should stop ...")
still pass.
2026-10-06 21:31:18 +02:00
Marc-André Bombeck e406b9c7ab fix(coach): calm the answer column, its replies and its focus marks
The running status line now carries its seconds right after the text, in
the line's own muted tone, instead of at the far edge of the column. The
live count and every finished round read "4 s", the same spacing as the
settled summary, with a no-break space so a round never wraps between
number and unit.

Reply pills (follow-ups, clarifying choices, memory and plan decisions)
sit directly under the answer, and the action row is the last thing in
the column. Beside a pointer the row stays invisible until hover or
focus; above the pills it left an empty band between an answer and its
replies. Hovering or focusing the message still reveals the row without
moving anything.

Keyboard focus in the Coach panel is a solid ring in the neutral
text-field focus tone, never the purple ring: the pills, Undo and Change
merge it over the button primitive, and the hand-built controls carry it
directly. A structural test keeps the purple ring out of the panel.

A disabled native select now looks disabled, like Input and Select, so a
thinking depth locked by the operator no longer reads as a live choice.
In Settings, thinking depth takes the shape of Tone and Reply length
beside it; a memory entry's Forget is an icon like its edit; the
operator's highest-depth label sits on the switches' left edge.
2026-10-06 21:24:24 +02:00
Marc-André Bombeck c7d659eb11 fix(coach): ask the capability before deferring a memory refresh, and pin test fixtures to the new quiet time 2026-10-06 21:02:41 +02:00
Marc-André Bombeck c56a05925c Merge time-of-day signals and dated latest readings into release/v1.41.0 2026-10-06 20:54:12 +02:00
Marc-André Bombeck 072c1e9394 Compare glucose signals by the hour and date stale strip readings
The signals of the day held today's newest single reading against the
7- and 30-day mean of every reading. For glucose, whose level climbs
with each meal, a fasting morning read as low against an all-day mean
and fed that into the briefing and status prompts. A type in
SAME_HOURS_TYPES now compares today's mean so far with the earlier
days cut at the local time of today's latest reading, the basis the
coincident-deviation flag and the metric strip already use. Below a
week of like-for-like days there is no signal. The signal carries
basis "sameHours" and both briefing prompts say what it means. Other
metrics keep the reading-against-window comparison.

The usual-range strip called the newest reading today's on every
metric page, even when it was days old. CoachReadBaseline now carries
latestDate (the reading's day in the account's zone) and latestIsToday
(isFromToday), on the REST response and the get_metric_baseline MCP
output. The strip names the date when the reading is not from today,
in all seven locales.
2026-10-06 20:53:39 +02:00
Marc-André Bombeck 36d48bb135 chore(i18n): ship the reasoning and memory strings in every locale and drop the retired self-context replies 2026-10-06 20:48:58 +02:00
Marc-André Bombeck 3b5fb1e900 Merge the Coach reasoning interface into release/v1.41.0 2026-10-06 20:48:42 +02:00
Marc-André Bombeck 7b36b09f1f test(e2e): drive the Coach stream frame by frame for the trail, pills and memory
The chat POST is answered from a stream the spec feeds, so each live
state is observed: the status line moving from thinking to fetching to
summarising, a table previewed before the first token, the trail closed
in every state and after a reload, at 1440 and 390 px in both themes.
Clarifying choices, memory and plan decisions answer through pills, an
entry in the memory list is edited in place, and the thinking depth
shows the operator's cap and lock. The dialog spec reads the pills
where it read the card.
2026-10-06 20:47:28 +02:00
Marc-André Bombeck c95dcb34db feat(settings): set how hard the Coach thinks, cap it as an operator, edit its memory
Thinking depth (Off, Low, Medium, High) sits under the model in the
Coach's quick settings and in Settings → Coach. It renders the resolved
block from the account payload: levels above the operator's cap are
disabled and say so, the field locks with one sentence when the
operator switched reasoning off, and Off reads Minimal where the
provider cannot stop thinking. The provider's own reasoning select
leaves the quick settings and says the Coach has its own.

The operator gets a switch to allow deeper thinking and a select for
the highest depth. The memory list shows where each entry came from,
lets it be edited in place, and is linked from the quick settings with
its count. The deep-linked data section no longer draws a ring.
2026-10-06 20:47:27 +02:00
Marc-André Bombeck 11bafd68b5 feat(coach): one quiet status line, reply pills and memory notes in the thread
The top of an answer is one line that says what the Coach is doing right
now (thinking, fetching a metric, summarising the readings, writing),
live from the activity frames, with the seconds so far. It never opens
by itself: the trail stays closed while the turn runs, when it asks,
when it stops early, after the answer and after a reload, and opens
only on a tap. Once the answer is in, the line settles into one summary
of lookups and seconds. A table read during the turn shows as a
one-line preview with a sparkline until the answer takes it over.

Clarifying choices, follow-ups and the answers to a fact or plan
proposal share one pattern: quiet pills in the answer's column whose
tap sends the label as the person's own message. The clarification
card and the in-thread plan card are gone. A fact the Coach kept is one
meta line with Undo, an assumption one line with Change. The remember
button on a message now keeps it in the Coach's memory.
2026-10-06 20:47:17 +02:00
Marc-André Bombeck 4298207500 feat(coach): draw a comparison of two periods or two metrics
The compare chart replaces the placeholder that rendered nothing: two
periods overlaid on one axis (the earlier one dashed), or two metrics on
two axes whose grid lines meet. Same tokens, legend and tooltip as the
other result charts, which stay as they were. The legend key is spelled
out so the chart runtime does not pull the Coach's schemas into every
chart.
2026-10-06 20:47:06 +02:00
Marc-André Bombeck df163123e3 Merge same-hours glucose standing and the updated_at guard into release/v1.41.0 2026-10-06 20:30:35 +02:00
Marc-André Bombeck 3e968f03a9 fix(insights): place a glucose day in progress against the same hours
The usual-range strip on a metric page placed the latest single reading
against a band built from whole-day means. For glucose the day mean climbs
with every meal, so at nine in the morning a fasting reading sat under a
band of whole days and the strip said "below your range" on an ordinary
morning; the MCP baseline read reported the same.

Glucose is now placed by its day mean. A day still in progress, in the
reader's own zone, is held against the earlier days cut at the local time
of today's latest reading, the comparison the health status already makes;
with fewer than seven earlier days that have readings in those hours there
is no verdict. The baseline carries `basis: "sameHours"` on the coach-read
route and the MCP read, and the strip names the range as the one for this
time of day, in all seven locales. Other metrics keep their placement.
2026-10-06 20:29:57 +02:00
Marc-André Bombeck 362818418e test(coach): count the conversation writes that may stamp updatedAt
The guard allowed every unnamed coachConversation write in a listed file,
so a background write added beside the rename and the document attach
would have passed. Each listed file now carries how many such writes it
holds, and a changed count fails.
2026-10-06 20:29:56 +02:00
Marc-André Bombeck e59fef2545 fix(coach): catalog the decline and clarify-choice strings, and let a briefing asked for by hand reason 2026-10-06 20:14:11 +02:00
Marc-André Bombeck 8f9c443d35 Merge dose-change screen into release/v1.41.0 2026-10-06 20:14:04 +02:00
Marc-André Bombeck c7844c3bc6 fix(safety): screen the dose-change class, not one sentence shape
"Increase your metformin to 2000 mg daily" passed the outbound dose
screen. The English change pattern accepted only "it" or "your dose"
between the verb and "to", so a drug name in that slot walked past it,
and no pattern covered an imperative to take a stated amount, a count
unit (tablets, capsules, puffs), or an imperative to skip or stop a
medication. Each locale bank had the same narrow object slot.

Add a per-locale dose-change class (en, de, fr, es, it, pl, ko) with
three shapes: an imperative or infinitive change verb with up to four
words of object and a target dose; an imperative to take or inject a
stated amount; and an imperative to halve, double, skip, pause or stop
a medication. Negated imperatives, past-tense log restatements,
present-tense descriptions ("you take 1000 mg"), clinician referrals
without a target, and nutrition advice (sodium, water, steps) still pass.

A unit now ends on an explicit lookahead instead of \b, so a lab
concentration ("under 140 mg/dL") no longer trips the dose bank while a
rate ("2000 mg/day") still does. "as usual" and "wie gewohnt" join the
continuation anchors, still gated on the user's schedule.
2026-10-06 20:12:05 +02:00
Marc-André Bombeck d403cf40af Merge reasoning settings, admin controls and background reasoning into release/v1.41.0 2026-10-06 20:07:52 +02:00
Marc-André Bombeck ae5cee331e feat(coach): resolve the turn's thinking depth from the person and the operator
The chat route hands the turn the level resolveCoachTurnReasoningLevel
returns: the stored Coach preference, absent read as medium, after the
operator's switch and cap. The turn still applies the operator-paid
ceiling once its chain is known. The controls loader no longer throws
when the request cache cannot serve it; it fails closed to off. The
admin guard pins the route to the resolver.
2026-10-06 19:57:04 +02:00
Marc-André Bombeck 85ebdad5c9 Merge remote-tracking branch 'origin/v141/integration' into v141/d-settings 2026-10-06 19:54:43 +02:00
Marc-André Bombeck 3f7a16316f chore(ai): drop an unused reasoning default 2026-10-06 19:54:42 +02:00
Marc-André Bombeck 8e7fd0b873 chore(coach): publish the trail route, list it among the mixed reads, and ship its strings in every locale 2026-10-06 19:53:43 +02:00
Marc-André Bombeck 2f5877cf97 Merge loop, budget, trail and clarifications into release/v1.41.0 2026-10-06 19:49:51 +02:00
Marc-André Bombeck de5b315999 feat(coach): give the turn the memory block, its recalled facts and a pending proposal
Build the memory block for the providers the turn sends to, send it on
both paths, keep the facts it recalled in the owner's trail, and offer a
health fact the background found as the answer's one note. Every turn
enqueues the memory refresh; the job's debounce and singleton key decide
when it runs. A remembered fact points back to the stored user message.
2026-10-06 19:48:55 +02:00
Marc-André Bombeck 4dd2a2b6bc feat(insights): show the daily briefing how active plans are going
The briefing prompt carries the server-computed progress line of up to
two active Coach plans, fenced as data because it quotes the person's
own goal words. The model may mention one and may not invent one; the
figures in the lines are admitted by the grounding gate like any other
figure the server handed over. The lines join the snapshot fingerprint
only when there is one, so an account without an active plan keeps its
stored fingerprint, and a failure to build them drops the lines, never
the briefing.
2026-10-06 19:46:44 +02:00
Marc-André Bombeck 656d8b8563 Merge remote-tracking branch 'origin/release/v1.41.0' into v141/d-settings 2026-10-06 19:44:56 +02:00
Marc-André Bombeck fadb2ff6ed feat(insights): let the daily briefing and the period narratives reason
runStatusCompletion and runBriefingCompletion take an optional
reasoningJob. Only a named job carries reasoning, resolved against the
operator's switch and cap and the payer; a call without one, which is
every status card, batch note, derived score and workout line, sends
exactly what it sent before. Thinking is reserved on the day's ledger
with the answer budget, and a reasoning status call falls back to a
90 s ceiling instead of 60 s.

The briefing reasons on its generation and phrasing re-roll, the weekly
and monthly narratives on their first pass; the JSON and grounding
repairs do not. An inventory test freezes which files may name a job.
2026-10-06 19:44:52 +02:00
Marc-André Bombeck df0689ffbe feat(auth): publish the resolved Coach reasoning on the account payload
GET /api/auth/me gains coachReasoning: the level a turn runs at, the
person's own pick, the highest level the settings may offer, whether
reasoning can run at all, whether the lowest option is a real off, and
the layer that decided. Resolved on the server so the web settings and
the native client read the same answer.

The web reads it through useCoachReasoning. The Coach preferences route
names the stored level on its wide event, and a unit test round-trips
the level through the account-settings backup section.
2026-10-06 19:44:52 +02:00
Marc-André Bombeck adb4cd5c40 feat(admin): carry the reasoning switch and cap on the assistant flags
PUT /api/admin/settings/assistant-flags accepts aiReasoningEnabled and
aiReasoningMaxEffort (low, medium, high), writes them field by field
with an audit row like the other switches, and both methods echo them
as reasoning: { enabled, maxEffort }.
2026-10-06 19:44:52 +02:00
Marc-André Bombeck 74e68745c1 feat(ai): resolve the reasoning level against the operator's controls
Add resolveReasoning, the one decision for how hard a call may think:
the operator's switch, then the provider's ability, then the requested
level (the person's Coach preference, or a background job's own level),
then the operator's cap, then a medium ceiling when the operator pays.
The answer names the layer that decided it.

loadReasoningControls reads the two app_settings columns, memoised per
request, read afresh in background runs, and fails closed to "off".
loadCoachReasoningState resolves the published Coach state from the
memoised provider presence; resolveJobReasoning resolves one background
call and never fails it.

A guard proves the switch and the cap over the whole input space and
requires every call that carries reasoning to take it from the
resolver.
2026-10-06 19:44:51 +02:00
Marc-André Bombeck 8f58cad7f3 fix(insights): say a steady usual value as one figure, not a range of equal ends 2026-10-06 19:41:39 +02:00
Marc-André Bombeck f46e0960fb Merge remote-tracking branch 'origin/v141/e-memory' into v141/b-loop 2026-10-06 19:39:10 +02:00
Marc-André Bombeck 88b2bf13b0 Merge remote-tracking branch 'origin/release/v1.41.0' into v141/b-loop 2026-10-06 19:39:06 +02:00
Marc-André Bombeck 8e74bc05c8 test(coach): cover the budget, the brake, the trail, questions and comparisons
Unit tests for the turn budget and the no-progress brake, the budgeted
loop (every stop reason, the round-by-round ledger, repeated calls,
questions, comparisons, reasoning and provider state), the trail recorder
and its screen, clarifications through the tool and the brake, the
comparison table and chart, the trail column and its owner-only read, the
reply frames, and the turn's frames end to end through the route. An
integration test proves the trail against a real database. Eval
scenarios cover a multi-round why question, a period comparison with its
chart, a question asked through the tool, and keep the charts the
existing table scenarios already showed.

Take the effective reasoning level from the route rather than resolving
it in the turn; the turn only caps it when the operator pays.
2026-10-06 19:38:57 +02:00
Marc-André Bombeck 0ba698c54f Merge memory, plans and voice into release/v1.41.0 2026-10-06 19:33:17 +02:00
Marc-André Bombeck 912558b96a feat(coach): answer first, short and without filler
Give the Coach one statement of how an answer is shaped, in the German and
English bodies and on every other locale: the first sentence answers, one
carrying number with its unit and window, the person's own baseline, an
assumption in one line, at most one next step and no habitual closing
question, 40 to 120 words, and a closed list of filler it never writes.

- Brief is 30 to 70 words in one paragraph, detailed up to 220 in three.
- Motivational-interviewing moves only when the person opens a change topic,
  at most one, never as a closing question; the confidence ruler only on an
  explicit plan request.
- The few-shot examples are rewritten to model the voice.
- Result tables: show the numbers whenever they carry the answer and span
  more than one value, a table for exact values or up to twelve like rows,
  one figure alone only for a moment.
- Follow-up chips are the answer's only "what next".
- A voice grader for the eval (length, filler, emoji, next steps, closing
  question, warm-up opening) plugs into the golden set as criteria; five
  voice cases in German and English.
- A guard holds both prompt bodies to the rules and every example answer to
  the grader, each check broken once on purpose.
2026-10-06 19:32:32 +02:00
Marc-André Bombeck acec87f02c feat(coach): give every turn its memory and let the Coach remember and propose plans
What a person told the Coach never reached a tool-mode turn: facts and plans
rode the snapshot's memory block, which tool mode does not send, and the
extraction ran only past twenty turns and failed to parse on every
non-Codex chain, because the status chain runs in JSON-object mode while the
prompt asked for a bare array.

- Memory block: WHAT YOU KNOW ABOUT THIS PERSON, built per turn behind the
  wire egress check (nothing is read on a refusal), fenced as data, at most
  1,500 characters, never trimmed before the inventory. Confirmed facts
  first, then the newest; active plans with a server-computed progress line;
  reminders the person asked for. Medication facts stay out while the
  medications module is off. Building it stamps last_used_at without moving
  updated_at. buildCoachToolRequest and buildCoachProviderPrompts take it as
  an optional memoryBlock; the snapshot memory keeps only the narrative and
  trend recall.
- remember_fact: preferences, goals and context are saved at once
  (source coach, source_message_id set); anything that names a condition,
  limitation or medication, by category or by wording, is only proposed.
  A fact must come from the person's current message; one note per answer.
- The person's answer reads the proposed fact from the encrypted trail on
  the assistant message, never from the request.
- propose_plan writes a proposed plan; the tap activates it with its review
  window counted from the tap. One per answer, three open at most.
- Progress: one sentence per active plan from the person's own day series
  and units, also offered to the briefing (two lines at most, behind the
  briefing capability). The plan review and the progress line read a step
  plan as daily totals instead of per-sample means.
- Background extraction asks for {"facts":[]} / {"plans":[]} and reads
  either shape, runs once a conversation has been quiet for 30 minutes, and
  stores health facts as proposals offered once at the next turn.
- Unanswered plan and fact proposals lapse after 14 days in the reminder
  sweep.
- Memory list API: POST saves one of the caller's own messages (the
  remember button), PATCH edits a fact's wording, GET reports source, origin
  and last use and never lists a waiting proposal.
- A structural guard keeps the egress check ahead of every read and the
  callers of both builders to the turn and the briefing.
2026-10-06 19:32:31 +02:00
Marc-André Bombeck 6112cfc6d5 test(ai): keep provider state out of logs and storage
providerState may be read only in the clients, the contract and the Coach tool loop, never on a line that logs, annotates or persists, and has no schema column.
2026-10-06 19:26:30 +02:00