Packages
fnord
0.8.94
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
@doc """
Returns the age in whole days since `dt` relative to `now`.
If `dt` is in the future, returns `0`.
"""
@spec age_days(DateTime.t(), DateTime.t()) :: non_neg_integer()
def age_days(%DateTime{} = dt, %DateTime{} = now) do
seconds = DateTime.diff(now, dt, :second)
days = div(max(seconds, 0), 86_400)
days
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.
"""
@spec age_line(Memory.t(), DateTime.t()) :: binary()
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.
"""
@spec warning_line(Memory.t(), DateTime.t(), keyword()) :: binary() | nil
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