diff --git a/app/jobs/agent_evaluate_commitment_job.rb b/app/jobs/agent_evaluate_commitment_job.rb index b1783b6..a492596 100644 --- a/app/jobs/agent_evaluate_commitment_job.rb +++ b/app/jobs/agent_evaluate_commitment_job.rb @@ -1,19 +1,20 @@ class AgentEvaluateCommitmentJob < ApplicationJob - include GoodJob::ActiveJobExtensions::Concurrency include RunsClaudeAgent queue_as :default - good_job_control_concurrency_with( - perform_limit: 5, - enqueue_limit: 550, - key: "AgentEvaluateCommitmentJob" - ) - - retry_on GoodJob::ActiveJobExtensions::Concurrency::ConcurrencyExceededError, wait: 60.seconds, attempts: Float::INFINITY + # Concurrency is bounded only by the worker's thread count (good_job.max_threads). retry_on StandardError, wait: 30.seconds, attempts: 3 - def perform(commitment, trigger_type: "manual", as_of_date: nil) + # Each run is a full agent session (~2 minutes). Skip commitments already + # assessed today unless force: true, so a re-run of the weekly scan or a + # duplicate manual enqueue doesn't repeat work. + def perform(commitment, trigger_type: "manual", as_of_date: nil, force: false) + if !force && assessed_today?(commitment) + Rails.logger.info("AgentEvaluateCommitmentJob: Skipping commitment #{commitment.id}, already assessed today at #{commitment.last_assessed_at.iso8601} (pass force: true to re-run)") + return + end + current_date = as_of_date || Date.today.iso8601 prompt = format(AgentPrompts::EVALUATE_COMMITMENT_PROMPT, commitment_id: commitment.id, current_date: current_date) hook_script = agent_dir.join(".claude/hooks/on_stop_commitment.sh").to_s @@ -30,4 +31,10 @@ def perform(commitment, trigger_type: "manual", as_of_date: nil) Rails.logger.info("AgentEvaluateCommitmentJob: Success for commitment #{commitment.id}") end + + private + + def assessed_today?(commitment) + commitment.last_assessed_at.present? && commitment.last_assessed_at.to_date == Date.current + end end diff --git a/test/jobs/agent_evaluate_commitment_job_test.rb b/test/jobs/agent_evaluate_commitment_job_test.rb index e6d625b..727104f 100644 --- a/test/jobs/agent_evaluate_commitment_job_test.rb +++ b/test/jobs/agent_evaluate_commitment_job_test.rb @@ -33,6 +33,36 @@ class AgentEvaluateCommitmentJobTest < ActiveJob::TestCase assert_match(%r{http://127\.0\.0\.1:1 is unreachable}, error.message) end + test "skips a commitment already assessed today" do + ENV["AGENT_API_KEY"] = "test-key" + ENV["RAILS_API_URL"] = "http://127.0.0.1:1" + @commitment.update!(last_assessed_at: 1.hour.ago) + + AgentEvaluateCommitmentJob.perform_now(@commitment) + + assert_no_enqueued_jobs only: AgentEvaluateCommitmentJob + end + + test "re-runs a commitment assessed today when forced" do + ENV["AGENT_API_KEY"] = "test-key" + ENV["RAILS_API_URL"] = "http://127.0.0.1:1" + @commitment.update!(last_assessed_at: 1.hour.ago) + + error = perform_and_capture_retry_error(AgentEvaluateCommitmentJob.new(@commitment, force: true)) + + assert_instance_of RunsClaudeAgent::ApiUnreachableError, error + end + + test "runs a commitment last assessed on a previous day" do + ENV["AGENT_API_KEY"] = "test-key" + ENV["RAILS_API_URL"] = "http://127.0.0.1:1" + @commitment.update!(last_assessed_at: 1.day.ago.end_of_day - 1.hour) + + error = perform_and_capture_retry_error(AgentEvaluateCommitmentJob.new(@commitment)) + + assert_instance_of RunsClaudeAgent::ApiUnreachableError, error + end + private # retry_on rescues the failure and re-enqueues the job; the error is only