Class: Collavre::AgentSubscription

Inherits:
ApplicationRecord show all
Defined in:
app/models/collavre/agent_subscription.rb

Overview

Presence record for one live Claude Channel session subscription on the per-agent ActionCable stream. The set of LIVE rows for an agent_id is the authority for whether routing_expression should be active: a human’s concurrent sessions share one agent, so routing stays on while ANY session holds a live row, and only turns off when the last one is gone.

Liveness is leased, not assumed. Rows are deleted only by AgentChannel#unsubscribed, so a Puma/ActionCable crash or deploy orphans a row. last_seen_at is refreshed by the channel’s periodic heartbeat; a row whose last_seen_at falls outside STALE_AFTER is dead — ignored by the live scope so it cannot pin routing on, and reaped opportunistically on the next subscribe/unsubscribe for the agent.

Constant Summary collapse

HEARTBEAT_SECONDS =

Heartbeat cadence (see AgentChannel#subscribe_to_agent_stream). STALE_AFTER is a multiple of it so a single missed beat (GC pause, brief stall) does not flap a live session to “dead”.

15
STALE_AFTER =
45.seconds

Class Method Summary collapse

Class Method Details

.reap_stale!(agent_id) ⇒ Object

Drop crash-orphaned rows for an agent so a plain presence check is accurate. Scoped to one agent_id: cheap, and called on the agent’s own subscribe/unsubscribe path.



42
43
44
# File 'app/models/collavre/agent_subscription.rb', line 42

def self.reap_stale!(agent_id)
  stale.where(agent_id: agent_id).delete_all
end

.touch!(agent_id, token) ⇒ Object

Refresh the heartbeat for one session’s row. No-op if the row is already gone (a newer subscribe rotated it, or it was reaped) — the periodic callback must not resurrect a removed presence row.



35
36
37
# File 'app/models/collavre/agent_subscription.rb', line 35

def self.touch!(agent_id, token)
  where(agent_id: agent_id, token: token).update_all(last_seen_at: Time.current)
end