Three wiring gaps left by #329
c4394ad made agents speak the user's local time, sourced from users.preferences.timezone (the account blob, reused from #211; no migration; validated by isValidTimeZone from lib/cron-time.ts, so the platform has one timezone vocabulary). It also chose local-zone prose, which means SystemMessage.tsx needs no change — #336's local rendering and the agent's prose agree for free.
Three loose ends remain, each small and each in a lane that was closed at the time.
1. Nothing seeds the timezone, so "unset" is the standing case
preferences.timezone starts empty and there is no prompt to fill it. Until an owner discovers the field, every agent still says UTC — the exact symptom #329 was filed for, just now with a fix that nobody has switched on.
Seed it from Intl.DateTimeFormat().resolvedOptions().timeZone on first load via PUT /v1/preferences. That makes unset a transient state rather than a permanent one.
Seed, do not bind. #329 deliberately rejected the browser value as the source of truth — it changes when you travel, is absent on cron/pump/MCP paths, and cannot be corrected by the owner. Writing it once when empty is the useful half; re-asserting it on every load would reintroduce all three problems.
2. The UI ignores an explicit override
lib/messageStamp.ts's stampTitle uses toLocaleString(undefined, …) — the browser's zone, not the stored one. So an owner who sets a timezone different from their machine sees it honoured in the agent's prose and ignored in the UI, which is a two-clock defect again, one layer down.
Pass preferences.timezone from GET /v1/preferences as the timeZone option.
3. subordinate_status still emits raw UTC
lib/connectors/supervision.ts:254 emits asOf: new Date().toISOString() — this is the actual source of the "22:33:34 UTC" in the incident that prompted #329. formatInZone and utcOffsetLabel are exported from lib/agent-clock.ts for exactly this.
Worth checking the same pattern elsewhere: anywhere a tool payload carries a bare ISO string, a model will read it aloud verbatim.
Why together
All three are "the fix landed but the value never reaches the user". Individually each looks cosmetic; together they are the difference between #329 being done and #329 being available.
Acceptance
Three wiring gaps left by #329
c4394admade agents speak the user's local time, sourced fromusers.preferences.timezone(the account blob, reused from #211; no migration; validated byisValidTimeZonefromlib/cron-time.ts, so the platform has one timezone vocabulary). It also chose local-zone prose, which meansSystemMessage.tsxneeds no change — #336's local rendering and the agent's prose agree for free.Three loose ends remain, each small and each in a lane that was closed at the time.
1. Nothing seeds the timezone, so "unset" is the standing case
preferences.timezonestarts empty and there is no prompt to fill it. Until an owner discovers the field, every agent still says UTC — the exact symptom #329 was filed for, just now with a fix that nobody has switched on.Seed it from
Intl.DateTimeFormat().resolvedOptions().timeZoneon first load viaPUT /v1/preferences. That makes unset a transient state rather than a permanent one.Seed, do not bind. #329 deliberately rejected the browser value as the source of truth — it changes when you travel, is absent on cron/pump/MCP paths, and cannot be corrected by the owner. Writing it once when empty is the useful half; re-asserting it on every load would reintroduce all three problems.
2. The UI ignores an explicit override
lib/messageStamp.ts'sstampTitleusestoLocaleString(undefined, …)— the browser's zone, not the stored one. So an owner who sets a timezone different from their machine sees it honoured in the agent's prose and ignored in the UI, which is a two-clock defect again, one layer down.Pass
preferences.timezonefromGET /v1/preferencesas thetimeZoneoption.3.
subordinate_statusstill emits raw UTClib/connectors/supervision.ts:254emitsasOf: new Date().toISOString()— this is the actual source of the "22:33:34 UTC" in the incident that prompted #329.formatInZoneandutcOffsetLabelare exported fromlib/agent-clock.tsfor exactly this.Worth checking the same pattern elsewhere: anywhere a tool payload carries a bare ISO string, a model will read it aloud verbatim.
Why together
All three are "the fix landed but the value never reaches the user". Individually each looks cosmetic; together they are the difference between #329 being done and #329 being available.
Acceptance