Class: Insika::Retention
- Inherits:
-
Object
- Object
- Insika::Retention
- Defined in:
- lib/insika/retention.rb
Overview
WS8 (phase 2): retention — the engine forgets what is old enough to forget.
Age-based purge of the CONVERSATION footprint: sessions (and their
per-session tool/context traces), terminal tasks (and their checkpoints),
customer/tenant memory cells and outcome records. The knob is data, not
code: settings.retention_days (Integer; nil/0 = OFF — parity, nothing is
ever swept by default).
Cadence: the Tick calls run every pass; an internal DAILY claim (the
tick's window idiom — one key, a timestamp, 24 h) makes the actual sweep
once a day, so the O(n) scans never ride the 60 s loop. A sweep that
crashes propagates to the tick, which logs and keeps ticking.
Constant Summary collapse
- SCOPE =
"retention"- KEY =
"claim"- BUDGET_KEY =
"budget_claim"- MEMORY_TTL_KEY =
the MEMORY TTL's own daily claim. Deliberately NOT the age-based KEY — memory TTLs sweep on their own knob (
memory_ttl_days), gated by neither retention_days nor the age-based claim (D5). "memory_ttl_claim"- ARTIFACT_TTL_KEY =
the ARTIFACT TTL's own daily claim — same discipline as the memory TTL: reports expire on their own knob (
artifact_ttl_days), never gated by retention_days (a deployment that keeps conversations forever must still expire the PII-carrying reports). "artifact_ttl_claim"- WINDOW =
one sweep per day, at most
86_400- TERMINAL =
%w[completed failed cancelled].freeze
Instance Attribute Summary collapse
-
#memory_store ⇒ Object
readonly
the sweep reads the memory store's cells/records (specs seed facts through it).
Instance Method Summary collapse
-
#initialize(session_store:, task_store:, checkpoint_store:, memory_store:, outcome_store:, tool_trace_store: nil, context_trace_store: nil, outbox_store: nil, shadow_pair_store: nil, settings_store: nil, budget_ledger: nil, funnel_store: nil, followup_store: nil, contact_store: nil, proposal_store: nil, model_visible_trace_store: nil, store:, window: WINDOW, now: nil, harvest_store: nil, artifact_store: nil) ⇒ Retention
constructor
A new instance of Retention.
-
#run ⇒ Object
-> { claimed: false } | { claimed: true, sessions:, tasks:, outcomes:, memory:, deliveries: }.
Constructor Details
#initialize(session_store:, task_store:, checkpoint_store:, memory_store:, outcome_store:, tool_trace_store: nil, context_trace_store: nil, outbox_store: nil, shadow_pair_store: nil, settings_store: nil, budget_ledger: nil, funnel_store: nil, followup_store: nil, contact_store: nil, proposal_store: nil, model_visible_trace_store: nil, store:, window: WINDOW, now: nil, harvest_store: nil, artifact_store: nil) ⇒ Retention
Returns a new instance of Retention.
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/insika/retention.rb', line 34 def initialize(session_store:, task_store:, checkpoint_store:, memory_store:, outcome_store:, tool_trace_store: nil, context_trace_store: nil, outbox_store: nil, shadow_pair_store: nil, settings_store: nil, budget_ledger: nil, funnel_store: nil, followup_store: nil, contact_store: nil, proposal_store: nil, model_visible_trace_store: nil, store:, window: WINDOW, now: nil, harvest_store: nil, artifact_store: nil) @session_store = session_store @task_store = task_store @checkpoint_store = checkpoint_store @memory_store = memory_store @outcome_store = outcome_store @tool_trace_store = tool_trace_store @context_trace_store = context_trace_store @model_visible_trace_store = model_visible_trace_store # ; nil = parity @outbox_store = outbox_store @shadow_pair_store = shadow_pair_store @settings_store = settings_store @budget_ledger = budget_ledger # WS2 counter GC; nil = nothing to sweep @funnel_store = funnel_store # ; nil = nothing to sweep @followup_store = followup_store # ; nil = nothing to sweep @contact_store = contact_store # ; nil = nothing to sweep @proposal_store = proposal_store # ; nil = nothing to sweep @store = store @window = window @now = now # injectable for specs (a deterministic "today") @harvest_store = harvest_store # ; nil = nothing to sweep @artifact_store = artifact_store # ; nil = nothing to sweep end |
Instance Attribute Details
#memory_store ⇒ Object (readonly)
the sweep reads the memory store's cells/records (specs seed facts through it).
67 68 69 |
# File 'lib/insika/retention.rb', line 67 def memory_store @memory_store end |
Instance Method Details
#run ⇒ Object
-> { claimed: false } |
{ claimed: true, sessions:, tasks:, outcomes:, memory:, deliveries: }.
Either shape may carry budget_cells: — the budget counter GC is NOT
gated by retention_days (see #sweep_budget_cells). Either shape may carry
memory_ttl: — the memory TTL sweep, gated by ITS OWN daily
claim and knob, never by retention_days (see #sweep_memory_ttl).
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/insika/retention.rb', line 75 def run budget_cells = sweep_budget_cells memory_ttl = sweep_memory_ttl artifacts = sweep_artifacts days = retention_days unless days.to_i.positive? && claim_window summary = { claimed: false } summary[:budget_cells] = budget_cells if budget_cells summary[:memory_ttl] = memory_ttl if memory_ttl summary[:artifacts] = artifacts if artifacts return summary end cutoff = now - (days.to_i * 86_400) summary = { claimed: true, sessions: sweep_sessions(cutoff), tasks: sweep_tasks(cutoff), outcomes: sweep_outcomes(cutoff), memory: @memory_store.prune_older_than(cutoff), deliveries: sweep_outbox(cutoff), pairs: sweep_shadow_pairs(cutoff) } summary[:funnel] = sweep_funnel(cutoff) if @funnel_store # the follow-up footprint ages out with the rest — records # and contact cells under the SAME retention_days gate (nil collaborator # = nothing to sweep, base graph parity). summary[:followups] = @followup_store.delete_older_than(cutoff) if @followup_store summary[:contacts] = @contact_store.delete_older_than(cutoff) if @contact_store # proposals are evidence OF a transcript — when the # transcript dies, the proposal's excerpt is gone and the pending fact is # stale. Pending AND terminal rows age out together; a proposal is # re-derivable (D2), so pruning is never data loss. The session MARKERS # are never pruned (the store's rule — the marker is the claim). summary[:proposals] = @proposal_store.delete_older_than(cutoff) if @proposal_store # candidates (pending AND terminal), log rows and # snapshots are DERIVED data of transcripts (D11) — when the transcripts # die, the candidates' excerpts are gone; they are re-derivable (D2), so # pruning is never data loss. The session markers are never pruned. summary[:harvest] = @harvest_store.delete_older_than(cutoff) if @harvest_store summary[:budget_cells] = budget_cells if budget_cells summary[:memory_ttl] = memory_ttl if memory_ttl summary[:artifacts] = artifacts if artifacts summary end |