Module: PWN::AI::Agent::TurnFinalizer

Defined in:
lib/pwn/ai/agent/turn_finalizer.rb

Overview

Hermes-style post-reply learning. Loop.run returns the user-visible answer first; Reward / Policy / Curriculum keep running off that path.

User-visible turn (Loop.run) tools, streaming, TaskSummarizer, final text After the reply is already decided TurnFinalizer.defer -> daemon thread re-attaches the R5 episode snapshot Learning.auto_introspect (critic, Reward.judge, PRM, HER, reflect)

Default ON during Loop.run (PWN::Env[:agent][:defer_introspect]). Direct Learning.auto_introspect calls (specs, cron, tools) stay inline so existing ORM contracts still get a synchronous return value.

Constant Summary collapse

MUTEX =
Mutex.new
THREADS =

rubocop:disable Style/MutableConstant

[]
MAX_TRACKED =
16
DEPTH_KEY =
:pwn_turn_finalizer_depth
INLINE_KEY =
:pwn_turn_finalizer_inline
SYNC_KEY =
:pwn_turn_finalizer_sync

Class Method Summary collapse

Class Method Details

.authorsObject



179
180
181
# File 'lib/pwn/ai/agent/turn_finalizer.rb', line 179

public_class_method def self.authors
  "AUTHOR(S):\n  0day Inc. <support@0dayinc.com>\n"
end

.defer(opts = {}) ⇒ Object

Supported Method Parameters

result = PWN::AI::Agent::TurnFinalizer.defer( session_id: 'required - PWN::Sessions id', request: 'optional - original user request', final: 'optional - user-visible final answer', predicted: 'optional - W3 predicted score', plan: 'optional - tangible-task plan', ts_state: 'optional - TaskSummarizer state' )

Snapshots the live Policy episode onto this thread, clears it so Loop.maybe_finish_policy is a no-op, and runs Learning.auto_introspect on a daemon thread with the episode re-attached.



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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/pwn/ai/agent/turn_finalizer.rb', line 76

public_class_method def self.defer(opts = {})
  session_id = opts[:session_id]
  return { skipped: :no_session } if session_id.to_s.strip.empty?
  return { skipped: :no_learning } unless defined?(Learning)

  episode = nil
  episode = Policy.detach_episode! if defined?(Policy) && Policy.respond_to?(:detach_episode!)

  payload = {
    session_id: session_id,
    request: opts[:request],
    final: opts[:final],
    predicted: opts[:predicted],
    plan: opts[:plan],
    ts_state: opts[:ts_state],
    inline: true
  }

  worker = Thread.new do
    Thread.current[INLINE_KEY] = true
    begin
      Policy.attach_episode!(episode: episode) if defined?(Policy) && Policy.respond_to?(:attach_episode!)
      Learning.auto_introspect(payload)
    rescue StandardError => e
      warn "[pwn-ai/turn_finalizer] background review swallowed: #{e.class}: #{e.message}"
      nil
    ensure
      leftover = defined?(Policy) && Policy.respond_to?(:current_episode) && Policy.current_episode
      if leftover && Policy.respond_to?(:finish)
        Policy.finish(
          session_id: session_id,
          proxy_ok: false,
          final: payload[:final],
          ts_state: payload[:ts_state]
        )
      elsif defined?(Policy) && Policy.respond_to?(:attach_episode!)
        Policy.attach_episode!(episode: nil)
      end
    end
  end
  worker.report_on_exception = false
  begin
    worker.name = 'pwn-turn-finalizer'
  rescue StandardError
    nil
  end
  track(thr: worker)
  { deferred: true, session_id: session_id, thread_id: worker.object_id }
rescue StandardError => e
  warn "[pwn-ai/turn_finalizer] defer swallowed: #{e.class}: #{e.message}"
  # Never drop the learner — fall back to inline review.
  opts_inline = opts.merge(inline: true)
  defined?(Learning) ? Learning.auto_introspect(opts_inline) : { error: e.message }
end

.enter_user_path!Object

Supported Method Parameters

PWN::AI::Agent::TurnFinalizer.enter_user_path!



30
31
32
# File 'lib/pwn/ai/agent/turn_finalizer.rb', line 30

public_class_method def self.enter_user_path!
  Thread.current[DEPTH_KEY] = Thread.current[DEPTH_KEY].to_i + 1
end

.finalize(opts = {}) ⇒ Object

Supported Method Parameters

result = PWN::AI::Agent::TurnFinalizer.finalize( session_id:, request:, final:, predicted:, plan:, ts_state:, should: true|false )

Single entry used when a caller wants the Hermes split without going through Learning.auto_introspect's gate.



140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/pwn/ai/agent/turn_finalizer.rb', line 140

public_class_method def self.finalize(opts = {})
  return { skipped: :gated } unless opts.fetch(:should, true)
  return { skipped: :no_learning } unless defined?(Learning)

  if should_defer?
    defer(opts)
  else
    Learning.auto_introspect(opts.merge(inline: true))
  end
rescue StandardError => e
  warn "[pwn-ai/turn_finalizer] finalize swallowed: #{e.class}: #{e.message}"
  nil
end

.helpObject



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/pwn/ai/agent/turn_finalizer.rb', line 183

public_class_method def self.help
  puts <<~USAGE
    USAGE:
      # Called from Learning.auto_introspect when Loop.run is on the
      # stack. You almost never invoke this directly.
      PWN::AI::Agent::TurnFinalizer.defer(
        session_id: sid,
        request: req,
        final: text,
        plan: plan,
        ts_state: ts
      )

      # Specs / cron that need the learner to finish before assert:
      PWN::AI::Agent::TurnFinalizer.join_all!(timeout: 15)

      Toggle via PWN::Env[:ai][:agent][:defer_introspect]
        true  (default) — Hermes split: learn after the reply
        false           — legacy inline auto_introspect

      #{self}.authors
  USAGE
end

.join_all!(opts = {}) ⇒ Object

Supported Method Parameters

n = PWN::AI::Agent::TurnFinalizer.join_all!(timeout: 15)



157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/pwn/ai/agent/turn_finalizer.rb', line 157

public_class_method def self.join_all!(opts = {})
  timeout = (opts[:timeout] || 15).to_f
  list = MUTEX.synchronize do
    pending = THREADS.dup
    THREADS.clear
    pending
  end
  list.each do |thr|
    thr.join(timeout) if thr.alive?
  rescue StandardError
    nil
  end
  list.length
end

.leave_user_path!Object

Supported Method Parameters

PWN::AI::Agent::TurnFinalizer.leave_user_path!



37
38
39
40
# File 'lib/pwn/ai/agent/turn_finalizer.rb', line 37

public_class_method def self.leave_user_path!
  depth = Thread.current[DEPTH_KEY].to_i - 1
  Thread.current[DEPTH_KEY] = depth.positive? ? depth : 0
end

.pendingObject

Supported Method Parameters

n = PWN::AI::Agent::TurnFinalizer.pending



175
176
177
# File 'lib/pwn/ai/agent/turn_finalizer.rb', line 175

public_class_method def self.pending
  MUTEX.synchronize { THREADS.count(&:alive?) }
end

.should_defer?Boolean

Supported Method Parameters

ok = PWN::AI::Agent::TurnFinalizer.should_defer?

Returns:

  • (Boolean)


49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/pwn/ai/agent/turn_finalizer.rb', line 49

public_class_method def self.should_defer?
  return false if Thread.current[INLINE_KEY]
  return false if Thread.current[SYNC_KEY]
  return false unless user_path?

  flag = agent_flag(key: :defer_introspect, default: true)
  return false if flag == false || flag.to_s.match?(/\A(0|false|no|off)\z/i)

  true
rescue StandardError
  false
end

.user_path?Boolean

Returns:

  • (Boolean)


42
43
44
# File 'lib/pwn/ai/agent/turn_finalizer.rb', line 42

public_class_method def self.user_path?
  Thread.current[DEPTH_KEY].to_i.positive?
end