Class: Silas::AgentLoopJob

Inherits:
ActiveJob::Base
  • Object
show all
Includes:
ActiveJob::Continuable
Defined in:
app/jobs/silas/agent_loop_job.rb

Overview

One durable turn. Step-name sequence: :prepare, :step_0, :step_1, …, :finalize. Every between-step loop-control read hits write-once persisted state (Step#terminal, invocation approval state at-or-after the cursor), so a resumed continuation regenerates the IDENTICAL sequence — the spike's hard-won determinism constraint, owned by the framework so users can't violate it.

Parking (approval / in-doubt) is a NORMAL job exit at zero compute; resume is a fresh job enqueued by approve!/decline!, replaying completed steps from rows (no model calls, no re-effects — StepRunner's replay path).

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.fail_turn(job, error) ⇒ Object

The force-fail path: expire approvals FIRST so no stale card can zombie-resume the failed turn, then finish loudly.



51
52
53
54
55
56
57
58
# File 'app/jobs/silas/agent_loop_job.rb', line 51

def self.fail_turn(job, error)
  turn = Turn.find_by(id: job.arguments.first)
  return unless turn&.active?

  turn.expire_pending_approvals!("turn failed: model error")
  turn.finish!(:failed, reason: "model_error")
  Rails.logger&.error("[silas] turn #{turn.id} failed on #{error.class}: #{error.message}")
end

Instance Method Details

#perform(turn_id) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'app/jobs/silas/agent_loop_job.rb', line 60

def perform(turn_id)
  turn = Turn.find(turn_id)
  return if turn.completed? || %w[failed canceled].include?(turn.status)

  # Named-agent / subagent sessions run EVERY turn under their own scope
  # (tools, skills, instructions, digest) — including resumes: a rescued
  # turn re-enters here and re-establishes the same scope, so a crashed
  # staff member never wakes up holding the root agent's tools.
  scope = Silas.scope_for_session(turn.session)
  if scope
    Silas.with_agent_scope(scope) { run_turn(turn) }
  else
    run_turn(turn)
  end
end