Class: Silas::AgentLoopJob
- Inherits:
-
ActiveJob::Base
- Object
- ActiveJob::Base
- Silas::AgentLoopJob
- 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
-
.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.
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.}") 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 |