|
1
|
+ |
# Sarah memory, voice, session, and control audit
|
|
2
|
+ |
|
|
3
|
+ |
**Date:** 2026-08-23
|
|
4
|
+ |
**Issue:** `OpenAgentsInc/openagents.com` #89
|
|
5
|
+ |
**Question:** Of the gaps reported after the launch episode, which still exist
|
|
6
|
+ |
in `main`, and what is the evidence either way?
|
|
7
|
+ |
|
|
8
|
+ |
Each gap below states its current status and the exact test that holds it. A
|
|
9
|
+ |
gap marked *held* was already closed in code but had no test naming the
|
|
10
|
+ |
behavior the report asked about; a gap marked *fixed* needed code in this
|
|
11
|
+ |
change.
|
|
12
|
+ |
|
|
13
|
+ |
## 1. Automatic memory saves, consent, and version history
|
|
14
|
+ |
|
|
15
|
+ |
**Held.** A profile-memory write requires consent evidence in the current
|
|
16
|
+ |
message, and `OpenAgents.Memory.Consent` returns
|
|
17
|
+ |
`:memory_consent_required` or `:memory_consent_mismatch` rather than writing
|
|
18
|
+ |
silently. Correction supersedes instead of overwriting, so the prior claim
|
|
19
|
+ |
stays readable as history and `/memory` shows the superseded row.
|
|
20
|
+ |
|
|
21
|
+ |
Evidence: `test/openagents/tools/profile_memory_tools_test.exs`,
|
|
22
|
+ |
`test/openagents/profile_memory_test.exs`,
|
|
23
|
+ |
`test/openagents_web/live/memory_live_test.exs`.
|
|
24
|
+ |
|
|
25
|
+ |
## 2. Voice tool calls in the same ordered activity stream as text
|
|
26
|
+ |
|
|
27
|
+ |
**Fixed.** Voice tool steps were durable in `voice_tool_steps`, but the
|
|
28
|
+ |
transcript only projected the steps of a live session. After the call ended, a
|
|
29
|
+ |
reload showed the spoken answer with no record of the tools that produced it,
|
|
30
|
+ |
while a text turn kept its activity attached to its assistant message.
|
|
31
|
+ |
|
|
32
|
+ |
`Voice.list_tool_step_activity_by_message/1` now joins tool steps to the
|
|
33
|
+ |
assistant message through the voice response receipt, and `ChatLive` merges
|
|
34
|
+ |
that projection with the text projection and orders both by step sequence. One
|
|
35
|
+ |
assistant message therefore carries one ordered activity list regardless of
|
|
36
|
+ |
modality.
|
|
37
|
+ |
|
|
38
|
+ |
Evidence: `test/openagents_web/live/chat_live_test.exs`, "voice tool activity
|
|
39
|
+ |
stays in the ordered transcript after the call ends";
|
|
40
|
+ |
`test/openagents/voice_test.exs`, "a refused host limit is durable and keyed to
|
|
41
|
+ |
the assistant message it belongs to".
|
|
42
|
+ |
|
|
43
|
+ |
## 3. Tool-call budget, visible limit, and typed terminal reason
|
|
44
|
+ |
|
|
45
|
+ |
**Fixed on the voice side.** The text runtime already budgets the loop and
|
|
46
|
+ |
records a typed `tool_call_limit_reached` outcome on the step that exceeded it,
|
|
47
|
+ |
with a runaway backstop above the budget
|
|
48
|
+ |
(`test/openagents/turn_tool_loop_test.exs`). The voice runtime sent the same
|
|
49
|
+ |
typed refusal to the provider but wrote nothing durable, so a truncated call
|
|
50
|
+ |
read back as a complete one.
|
|
51
|
+ |
|
|
52
|
+ |
`Voice.refuse_tool_step/4` now records the refusal as a terminal step with
|
|
53
|
+ |
status `refused`, the typed code, and the host executor disclosure, so the
|
|
54
|
+ |
refusal is evidence in the transcript rather than only a provider-side message.
|
|
55
|
+ |
|
|
56
|
+ |
Evidence: `test/openagents/voice_sessions_test.exs`, "the tool-call limit
|
|
57
|
+ |
refuses the next call and drives one tool-free report".
|
|
58
|
+ |
|
|
59
|
+ |
## 4. Voice session timeout, reconnect, resume, and failure
|
|
60
|
+ |
|
|
61
|
+ |
**Held.** The session budget warns at 80 percent and ends with a visible
|
|
62
|
+ |
reason at the ceiling; sideband loss reconnects under the same generation;
|
|
63
|
+ |
admission times out; a runtime restart fails the admitted generation
|
|
64
|
+ |
deterministically without inventing transcript or usage; a new generation is
|
|
65
|
+ |
admitted with prior conversation and tool evidence.
|
|
66
|
+ |
|
|
67
|
+ |
Evidence: `test/openagents/voice_sessions_test.exs` and
|
|
68
|
+ |
`test/openagents/voice_test.exs`, tests named for each of those behaviors.
|
|
69
|
+ |
|
|
70
|
+ |
## 5. Memory authorization across owners and versions
|
|
71
|
+ |
|
|
72
|
+ |
**Held, now proven.** `OpenAgents.ProfileMemory` scopes every read and
|
|
73
|
+ |
mutation by `owner_visitor_id`, including the locked queries that back forget
|
|
74
|
+ |
and purge. The report asked for a reproduction, so the test now walks a second
|
|
75
|
+ |
owner through the whole surface — list, get, correct, purge, transition, and
|
|
76
|
+ |
each forget mode — against another owner's record and its superseded versions,
|
|
77
|
+ |
and asserts the owner cannot read it, change it, delete it, or learn from the
|
|
78
|
+ |
result that it exists. A forget that targets nothing reports
|
|
79
|
+ |
`already_absent`, which is the same answer an empty account gets.
|
|
80
|
+ |
|
|
81
|
+ |
Evidence: `test/openagents/profile_memory_test.exs`.
|
|
82
|
+ |
|
|
83
|
+ |
## 6. Leaderboard opt-out
|
|
84
|
+ |
|
|
85
|
+ |
**Fixed.** `Leaderboard.ranked_query/1` already excluded accounts with
|
|
86
|
+ |
`public_leaderboard_opted_out`, but nothing let an account set the field: there
|
|
87
|
+ |
was no writer, no control, and no cache invalidation, so the column was
|
|
88
|
+ |
unreachable and the cached projection could serve a withheld account until an
|
|
89
|
+ |
unrelated recompute.
|
|
90
|
+ |
|
|
91
|
+ |
`Accounts.set_public_leaderboard_opt_out/2` writes the preference and
|
|
92
|
+ |
invalidates the cached projection on every change, including the change that
|
|
93
|
+ |
removes the account. `/memory` carries the control and states the current
|
|
94
|
+ |
state. Every public read of the board — the LiveView, the cached server, and
|
|
95
|
+ |
the recompute behind it — goes through `Leaderboard.ranked_query/1`, and no
|
|
96
|
+ |
other module projects the board, so one write covers every public surface.
|
|
97
|
+ |
|
|
98
|
+ |
Evidence: `test/openagents/leaderboard_test.exs`, "an account can withhold
|
|
99
|
+ |
itself from the board and publish itself again";
|
|
100
|
+ |
`test/openagents_web/live/memory_live_test.exs`, "the way back is a link, and
|
|
101
|
+ |
the board preference is the account's to set".
|
|
102
|
+ |
|
|
103
|
+ |
## 7. Return control on the memory page
|
|
104
|
+ |
|
|
105
|
+ |
**Fixed.** `/memory` is a page with an address, but its return control still
|
|
106
|
+ |
sent `toggle_memory`, an event from when memory was a panel inside the
|
|
107
|
+ |
conversation. No handler existed, so the control raised instead of returning.
|
|
108
|
+ |
It navigates to `/sarah` now.
|
|
109
|
+ |
|
|
110
|
+ |
Evidence: `test/openagents_web/live/memory_live_test.exs`.
|