Class: Collavre::TriggerLoopCheckJob

Inherits:
ApplicationJob
  • Object
show all
Includes:
TriggerLoopHelpers
Defined in:
app/jobs/collavre/trigger_loop_check_job.rb

Instance Method Summary collapse

Instance Method Details

#perform(task_id) ⇒ Object

Evaluates whether a trigger loop should continue after an AI agent completes a task. Checks the agent’s last response for [STATUS: …] tags to determine next action.

Status flow:

idle  running  completed | awaiting_user | stuck | max_reached


15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'app/jobs/collavre/trigger_loop_check_job.rb', line 15

def perform(task_id)
  task = Task.find_by(id: task_id)
  return unless task&.creative

  child_creative = task.creative
  parent_creative = child_creative.parent
  return unless parent_creative&.drop_trigger_enabled?

  # Loop state is stored on the child creative (each child has its own loop)
  loop_config = child_creative.data&.dig("trigger", "loop")
  return unless loop_config && loop_config["state"] == "running"

  # Use the task's topic directly — not a stored topic_id which can become stale
  topic = Topic.find_by(id: task.topic_id)
  return unless topic

  # Only evaluate tasks from the loop's trigger topic to prevent
  # cross-topic contamination from unrelated AI conversations
  trigger_topic_id = loop_config["trigger_topic_id"]
  return if trigger_topic_id.present? && trigger_topic_id != task.topic_id

  last_agent_comment = find_last_agent_comment(child_creative, topic, task)
  return unless last_agent_comment

  # Infrastructure errors (timeout, connection failure) → retry without consuming iteration
  if infrastructure_error?(last_agent_comment)
    handle_infra_error(child_creative, topic, parent_creative, loop_config, task)
    return
  end

  status = evaluate_status(last_agent_comment, loop_config)

  # LLM fallback: when agent doesn't use [STATUS: ...] tags, ask LLM
  # to determine whether the work is actually done.
  if status == :no_tag
    status = llm_fallback_evaluate(child_creative, parent_creative, last_agent_comment)
  end

  case status
  when :done
    # Transition to pending_verification and enqueue LLM verification
    update_loop_data(child_creative, state: "pending_verification", infra_retry_count: 0)
    TriggerLoopVerifyJob.perform_later(task.id)
  when :awaiting_user
    update_loop_data(child_creative, state: "awaiting_user", infra_retry_count: 0)
    post_system_notice(child_creative, topic, I18n.t(
      "collavre.trigger_loop.awaiting_user",
      iteration: loop_config["current_iteration"]
    ))
  when :continue
    iteration = (loop_config["current_iteration"] || 0) + 1
    max = loop_config["max_iterations"] || 10

    if iteration >= max
      update_loop_data(child_creative, state: "max_reached", infra_retry_count: 0)
      post_system_notice(child_creative, topic, I18n.t(
        "collavre.trigger_loop.max_reached",
        max: max
      ))
    else
      update_loop_data(child_creative,
        state: "running", current_iteration: iteration,
        last_task_id: task.id, infra_retry_count: 0)
      post_continue_instruction(child_creative, topic, parent_creative, iteration, max)
    end
  end
end