Class: Clacky::IdleCompressionTimer

Inherits:
Object
  • Object
show all
Defined in:
lib/clacky/idle_compression_timer.rb

Overview

IdleCompressionTimer triggers memory compression after a period of inactivity.

Both CLI and WebUI use the same agent-level compression logic; this class abstracts the “wait N seconds, then compress” pattern so it can be shared.

Usage:

timer = IdleCompressionTimer.new(agent: agent, session_manager: sm) do |success|
  # called on the compression thread after compression finishes
  broadcast_update if success
end
timer.start   # call after each agent run completes
timer.cancel  # call when new user input arrives

Constant Summary collapse

IDLE_DELAY =

Seconds of inactivity before idle compression is triggered. Kept under the 5-minute prompt cache TTL so the compression call itself still hits the existing prefix cache.

314

Instance Method Summary collapse

Constructor Details

#initialize(agent:, session_manager: nil, logger: nil, &on_compress) ⇒ IdleCompressionTimer

Returns a new instance of IdleCompressionTimer.

Parameters:

  • agent (Clacky::Agent)

    the agent whose messages will be compressed

  • session_manager (Clacky::SessionManager, nil) (defaults to: nil)

    used to persist session after compression

  • logger (#call, nil) (defaults to: nil)

    optional logger lambda: ->(msg, level:) { … }

  • on_compress (Proc, nil)

    block called after compression attempt with success (bool)



26
27
28
29
30
31
32
33
34
35
36
# File 'lib/clacky/idle_compression_timer.rb', line 26

def initialize(agent:, session_manager: nil, logger: nil, &on_compress)
  @agent           = agent
  @session_manager = session_manager
  @logger          = logger
  @on_compress     = on_compress

  @timer_thread    = nil
  @compress_thread = nil
  @mutex           = Mutex.new
  @shutdown        = false
end

Instance Method Details

#active?Boolean

True if the timer or compression is currently active.

Returns:

  • (Boolean)


104
105
106
# File 'lib/clacky/idle_compression_timer.rb', line 104

def active?
  @mutex.synchronize { @timer_thread&.alive? || @compress_thread&.alive? }
end

#cancelObject

Cancel the timer and any in-progress compression. Raises AgentInterrupted on the compress thread and waits for it to fully exit, ensuring history rollback completes before the caller starts a new agent.run.



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/clacky/idle_compression_timer.rb', line 77

def cancel
  compress_thread_to_join = nil

  @mutex.synchronize do
    @timer_thread&.kill
    if @compress_thread&.alive?
      @compress_thread.raise(Clacky::AgentInterrupted, "Idle timer cancelled")
      compress_thread_to_join = @compress_thread
    end
    @timer_thread    = nil
    @compress_thread = nil
  end

  # Join outside the mutex to avoid deadlock.
  # This blocks until the compress thread has finished rolling back history,
  # so the subsequent agent.run sees a clean, consistent history.
  compress_thread_to_join&.join(5)
end

#compressing?Boolean

True only when compression work is actually in flight (not during the pre-compression idle countdown). Used by callers that want to treat Ctrl+C during active compression as “stop compressing” rather than “exit the program”.

Returns:

  • (Boolean)


112
113
114
# File 'lib/clacky/idle_compression_timer.rb', line 112

def compressing?
  @mutex.synchronize { @compress_thread&.alive? || false }
end

#shutdownObject

Permanently stop this timer. Used during application shutdown so background agent-thread ensure blocks cannot create new timer threads.



98
99
100
101
# File 'lib/clacky/idle_compression_timer.rb', line 98

def shutdown
  @mutex.synchronize { @shutdown = true }
  cancel
end

#shutdown?Boolean

Returns:

  • (Boolean)


116
117
118
# File 'lib/clacky/idle_compression_timer.rb', line 116

def shutdown?
  @mutex.synchronize { @shutdown }
end

#startObject

Start (or restart) the idle timer. Cancels any existing timer first, then waits IDLE_DELAY seconds before compressing.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/clacky/idle_compression_timer.rb', line 40

def start
  cancel # reset any existing timer

  @mutex.synchronize do
    return false if @shutdown

    @timer_thread = Thread.new do
      Thread.current.name = "idle-compression-timer"
      sleep IDLE_DELAY
      next if shutdown?

      # Register @compress_thread inside the mutex BEFORE the thread starts running,
      # so cancel() can always find and interrupt it even if it fires immediately.
      compress_thread = nil
      @mutex.synchronize do
        unless @shutdown
          compress_thread = Thread.new do
            Thread.current.name = "idle-compression-work"
            run_compression
          end
          @compress_thread = compress_thread
        end
      end

      compress_thread&.join
      @mutex.synchronize { @compress_thread = nil; @timer_thread = nil }
    end
  end
  true
rescue ThreadError => e
  log("Idle compression timer could not start: #{e.message}", level: :debug)
  false
end