Class: Agentilda::Runner
- Inherits:
-
Object
- Object
- Agentilda::Runner
- Defined in:
- lib/agentilda/runner.rb
Overview
Drives specialist agents over a .plans tree until it stops changing.
The hard part of any agent loop is knowing when to stop, and this one does not have to guess: the state machine already defines "satisfied". A round advances plans; the loop ends at a FIXED POINT — a round in which no plan changed state — or when nothing is left that an agent may touch.
Blocked plans are not failures and not work. ⭕️ and 🅱️ mean a human must decide, so the loop reports them and steps around them. An agent that could move them would make the states meaningless.
Defined Under Namespace
Constant Summary collapse
- FAILURE =
How a task's return value reads on its spinner line: any hop that was not ok makes the whole line a failure, shown with that hop's note. The executor reports failure by returning rather than raising, and a line that drew ✓ "done" over a timed-out agent — directly above a round table saying FAIL — was the contradiction this closes.
->(result) { result.find { |a| !a.ok }&.note if result.is_a?(Array) }
- DRY_ROUNDS =
Rounds with no movement before the loop concedes. One is not enough: an agent can legitimately spend a round writing something another agent needs before either can advance.
2- MAX_CHAIN_HOPS =
The most agents one task will chain through in a single round. The pipeline is shorter than this, so hitting the cap means states are cycling, and a cap beats a loop.
6
Instance Attribute Summary collapse
-
#jobs ⇒ Integer
readonly
Agents running at once.
- #rounds ⇒ Array<Agentilda::Runner::Round> readonly
- #tree ⇒ Agentilda::Tree readonly
- #worktree ⇒ Agentilda::Worktree? readonly
Instance Method Summary collapse
-
#blocked ⇒ Array<Agentilda::Subject>
Plans nobody may act on, for the closing report.
-
#call ⇒ Array<Agentilda::Runner::Round>
Run until the tree stops changing.
-
#in_scope?(subject) ⇒ Boolean
Whether this run's scope covers this plan at all —
--planrestricts it; with no--planevery plan is in scope. -
#initialize(tree:, executor:, agents: Agents.new, max_rounds: 10, isolation: :shared, jobs: 1, worktree: nil, plans: nil, publisher: nil, dry_run: false, chain: false) ⇒ Runner
constructor
A new instance of Runner.
- #isolated? ⇒ Boolean
-
#settled? ⇒ Boolean
Every plan in scope is either finished or deliberately parked.
Constructor Details
#initialize(tree:, executor:, agents: Agents.new, max_rounds: 10, isolation: :shared, jobs: 1, worktree: nil, plans: nil, publisher: nil, dry_run: false, chain: false) ⇒ Runner
Returns a new instance of Runner.
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/agentilda/runner.rb', line 127 def initialize(tree:, executor:, agents: Agents.new, max_rounds: 10, isolation: :shared, jobs: 1, worktree: nil, plans: nil, publisher: nil, dry_run: false, chain: false) @tree = tree @executor = executor @agents = agents @max_rounds = max_rounds @isolation = isolation @worktree = worktree @plans = plans @publisher = publisher @dry_run = dry_run @chain = chain @rounds = [] # Concurrency without isolation is the exact failure the worktree exists # to prevent: two agents editing one checkout produce no git conflict, so # the last writer wins silently. Refuse rather than corrupt. @jobs = isolated? ? jobs : 1 end |
Instance Attribute Details
#jobs ⇒ Integer (readonly)
Returns agents running at once.
157 158 159 |
# File 'lib/agentilda/runner.rb', line 157 def jobs @jobs end |
#rounds ⇒ Array<Agentilda::Runner::Round> (readonly)
166 167 168 |
# File 'lib/agentilda/runner.rb', line 166 def rounds @rounds end |
#tree ⇒ Agentilda::Tree (readonly)
163 164 165 |
# File 'lib/agentilda/runner.rb', line 163 def tree @tree end |
#worktree ⇒ Agentilda::Worktree? (readonly)
160 161 162 |
# File 'lib/agentilda/runner.rb', line 160 def worktree @worktree end |
Instance Method Details
#blocked ⇒ Array<Agentilda::Subject>
Plans nobody may act on, for the closing report.
194 |
# File 'lib/agentilda/runner.rb', line 194 def blocked = in_scope.select { |s| %i[blocked product_blocked].include?(s.status.key) } |
#call ⇒ Array<Agentilda::Runner::Round>
Run until the tree stops changing.
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/agentilda/runner.rb', line 171 def call dry = 0 1.upto(@max_rounds) do |number| # `q` ends the loop at the next seam rather than instantly: the round # in flight finishes (its agents were asked to STOP and get a grace # period to write out), and no new round starts. break if Control.quit? round = run_round(number) @rounds << round break if round.attempts.empty? dry = round.dry? ? dry + 1 : 0 break if dry >= DRY_ROUNDS end @rounds end |
#in_scope?(subject) ⇒ Boolean
Returns whether this run's scope covers this plan at all —
--plan restricts it; with no --plan every plan is in scope.
154 |
# File 'lib/agentilda/runner.rb', line 154 def in_scope?(subject) = @plans.nil? || @plans.include?(subject.feature.ordinal) |
#isolated? ⇒ Boolean
149 |
# File 'lib/agentilda/runner.rb', line 149 def isolated? = @isolation == :worktree |
#settled? ⇒ Boolean
Returns every plan in scope is either finished or deliberately parked.
197 198 199 |
# File 'lib/agentilda/runner.rb', line 197 def settled? in_scope.all? { |s| StateMachine::SETTLED.include?(s.status.key) } end |