Packages
fnord
0.9.29
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/memory/presentation.ex
defmodule Memory.Presentation do
@moduledoc """
Helpers for presenting `%Memory{}` metadata to humans.
This module is intentionally small and pure (no I/O) so it can be reused by:
- `AI.Tools.Memory` (tool output)
- `Cmd.Memory` (CLI output)
- `AI.Agent.Coordinator` (auto-injected recall section)
Timestamps are stored on `%Memory{}` as ISO8601 strings (`DateTime.to_iso8601/1`).
"""
@type iso8601 :: binary()
@doc """
Parse an ISO8601 timestamp string into a `DateTime`.
Returns `{:ok, dt}` or `:error`.
"""
@spec parse_ts(nil | iso8601()) :: {:ok, DateTime.t()} | :error
def parse_ts(nil), do: :error
def parse_ts(ts) when is_binary(ts) do
case DateTime.from_iso8601(ts) do
{:ok, dt, _offset} -> {:ok, dt}
_ -> :error
end
end
defp age_days(%DateTime{} = dt, %DateTime{} = now) do
secs = DateTime.diff(now, dt, :second)
secs = if secs < 0, do: 0, else: secs
div(secs, 86_400)
end
@doc """
Returns an "Age" line for a memory.
Examples:
- `"Age: unknown (missing timestamps)"`
- `"Age: 312 days (updated 12 days ago)"`
Notes:
- Uses `updated_at` if present; otherwise uses `inserted_at`.
- Uses `now` for deterministic testing.
- If the first argument is `:error` or any non-struct value (e.g. a parse error),
this will return `"Age: unknown (missing timestamps)"`.
"""
@spec age_line(:error | Memory.t() | term(), DateTime.t()) :: binary()
def age_line(mem, %DateTime{} = _now) when not is_struct(mem) do
"Age: unknown (missing timestamps)"
end
def age_line(%Memory{} = mem, %DateTime{} = now) do
inserted_days = ts_days(mem.inserted_at, now)
updated_days = ts_days(mem.updated_at, now)
cond do
inserted_days == :unknown and updated_days == :unknown ->
"Age: unknown (missing timestamps)"
inserted_days != :unknown and updated_days == :unknown ->
"Age: #{inserted_days} days"
inserted_days == :unknown and updated_days != :unknown ->
"Age: #{updated_days} days (updated #{updated_days} days ago)"
true ->
"Age: #{inserted_days} days (updated #{updated_days} days ago)"
end
end
@doc """
Returns an optional warning line based on how long ago the memory was updated.
- If `updated_at` is missing/unparseable, returns `nil`.
- If `updated_days >= strong_days`, returns a strong warning.
- Else if `updated_days >= mild_days`, returns a mild warning.
- Else returns `nil`.
This is intended as a gentle prompt to check for cobwebs.
If the first argument is `:error` or any non-struct value, this function will
return `nil` (no warning).
"""
@spec warning_line(:error | Memory.t() | term(), DateTime.t(), keyword()) :: binary() | nil
def warning_line(mem, now, opts \\ [])
def warning_line(mem, %DateTime{} = _now, _opts) when not is_struct(mem) do
nil
end
def warning_line(%Memory{} = mem, %DateTime{} = now, opts) do
mild_days = Keyword.get(opts, :mild_days, 180)
strong_days = Keyword.get(opts, :strong_days, 365)
case ts_days(mem.updated_at, now) do
:unknown ->
nil
days when is_integer(days) and days >= strong_days ->
"Warning: last updated #{days} days ago. Check for cobwebs and verify/update/forget if stale."
days when is_integer(days) and days >= mild_days ->
"Note: last updated #{days} days ago. Consider checking for cobwebs."
_ ->
nil
end
end
defp ts_days(ts, now) do
case parse_ts(ts) do
{:ok, dt} -> age_days(dt, now)
:error -> :unknown
end
end
end