ace-overseer 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac2050e4e153d6bed54441f8b9a64aaea6a6bc4bcefc06413e4b7187487a20dc
4
- data.tar.gz: e430481b7d4824d5359f7e61f3d575c3e96311af90f3f07f79eacf6952a871da
3
+ metadata.gz: 7afa7d878680232ac2f7800973b1dec72b7991a9ecd6ce3c498c73d6d8264b21
4
+ data.tar.gz: 124e8f19e7aa46504760813344459124a630db4263e9a2f4bf08133409036e94
5
5
  SHA512:
6
- metadata.gz: 3106447b37e8b3e4faa21799da47441d1ce2b327231243148830480f507c490c86ec602b0fba2e8b1a5c1d7d584e8c7c92e26ce038b7a7a050e5cd6adf0c7801
7
- data.tar.gz: e27c30a6e6e2cc318c05a348b6e01acbe4f6c5c692784512f1531830725edcd6210f65db5f9430f95f9fe0a9debe91732579534b091f4fc3d33f0230ed7911b6
6
+ metadata.gz: 86a724ae87399315af6cbce345ec911f62ea7ec28e57ab660652056d99bcda986fd8cb571bb031071e6971b4bbd78f39b697fa02c393ed4a562c594306b012b5
7
+ data.tar.gz: 15ae48aea39c596a2fb3fd639f04f72b8f669872a9a4cce365ff5cca9eefbd8d2322df523b5b6974d56e9253011dd12b4aa72f8243f7d7f1264248b548cc2a8e
data/CHANGELOG.md CHANGED
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.20.0] - 2026-10-05
11
+
12
+ ### Added
13
+
14
+ - Resolve immutable second-commander proposals through confirmed-delivery sixteen-hour policy and canonical Assign authorization.
15
+ - Expose assignment recovery liveness/reasons and show unreadable evidence as unknown rather than omitting it.
16
+
17
+ ### Fixed
18
+
19
+ - Expose deferred proposal ticks in Lab status diagnostics and object JSON while preserving status availability and array output.
20
+
21
+ - Keep watch/status alive on proposal tick failure and queue project-scoped HITL reconciliation under the proposer identity.
22
+
23
+ ### Changed
24
+
25
+ - Declare the required direct dependencies and minimum producer versions for this coordinated release: `ace-assign ~> 0.64`, `ace-git ~> 0.29`, `ace-git-worktree ~> 0.26`, `ace-herdr ~> 0.4`.
26
+
27
+ ## [0.19.1] - 2026-10-04
28
+
29
+ ### Changed
30
+
31
+ - Install both Herdr and tmux runtime adapters for standalone runtime switching, through an acyclic dependency graph.
32
+
10
33
  ## [0.19.0] - 2026-10-04
11
34
 
12
35
  ### Added
data/docs/usage.md CHANGED
@@ -148,7 +148,7 @@ does not read Lab credentials and does not call Podman or Herdr directly.
148
148
  - `ace-overseer agents`: list registered Lab agents and concurrency limits.
149
149
  - `ace-overseer prepare --runtime lab --project PROJECT --source KIND:ID --work WORK --planner AGENT --title TITLE`: create a reviewed Work and its isolated worktree.
150
150
  - `ace-overseer work-on --runtime lab --work WORK --agent AGENT`: reserve the agent, create or reuse its Herdr workspace, and dispatch it.
151
- - `ace-overseer status --runtime lab [--project PROJECT] [--format table|json]`: show Lab Work state. Continuous status lives in each project Herdr session, so `--watch` is intentionally rejected for Lab.
151
+ - `ace-overseer status --runtime lab [--project PROJECT] [--format table|json]`: show Lab Work state. Failed proposal ticks defer resolution and report a diagnostic on stderr while status remains available. Object JSON also includes `proposal_resolution.status: "deferred"`; legacy array JSON keeps its schema and uses the stderr diagnostic. Continuous status lives in each project Herdr session, so `--watch` is intentionally rejected for Lab.
152
152
  - `ace-overseer prompt --work WORK --file PATH`: forward prompt text from a file to the Work pane. Piped stdin is also supported; prompt text is never passed as a process argument.
153
153
  - `ace-overseer review --work WORK --pr NUMBER`: prepare an exact-head admin review checkout and pane.
154
154
  - `ace-overseer stop --work WORK`: stop the assigned process without destroying Work state.
@@ -188,3 +188,5 @@ Use these user-visible checks when validating behavior end-to-end:
188
188
  3. `ace-overseer prune --yes`
189
189
  4. `ace-git-worktree list` to confirm removed vs retained task worktrees
190
190
  5. `ace-overseer prune --dry-run` to confirm no remaining safe candidates
191
+
192
+ Assignment JSON includes `recovery`: current liveness, recovery decision/reason, last verified observation, exact attempt identities, checkpoints and unresolved effect/inbox references. Unreadable evidence is explicitly unknown; dashboard rows needing reconciliation use a question mark instead of a success indicator. An older verified observation remains audit history and does not make a current unknown observation live.
@@ -161,3 +161,16 @@ candidate:
161
161
  - Work starts only through `ace-overseer work-on` (or the Lab-runtime equivalent), never through ad-hoc manual worktree provisioning.
162
162
  - Every status review re-verified pending owner-blocked tasks with an executed check and corrected stale state in the same change.
163
163
  - Every pruned worktree/branch had an executed preservation proof (ancestor containment, or a verified destination with tree/artifact or exact content-transition equivalence) and an executed no-active-writer check; candidates without complete proof were reported as blocked, not removed; `--force` and `--yes` never bypassed a safety block.
164
+
165
+ ### Second-commander proposal policy ticks
166
+
167
+ With ACE_HITL_SOCKET and ACE_HITL_PROJECT configured, the living overseer invokes
168
+ `ace-hitl proposal resolve-due --project PROJECT` at startup and each watch/status tick, including restart
169
+ after a missed deadline. The authenticated HITL boundary queues a canonical wake;
170
+ the existing Hermes transport actor polls and reconciles under its own UID.
171
+ The installed HITL/Hermes commands own policy/reconciliation;
172
+ overseer does not claim effects, shorten the sixteen-hour window, or replay elapsed ticks.
173
+ A failed tick is visible, defers authorization, and keeps watch/status running. The decision role reviews relevant prior
174
+ `ace-hitl proposal history --project ID --query TEXT`, creates exact proposals, and inspects
175
+ `proposal show` deadline/decision/Assign outcome. Do not mark technical or installed gates
176
+ passed from a proposal's authorization state.
@@ -33,7 +33,8 @@ module Ace
33
33
  "failed" => {icon: "\u2717", color: :red}, # ✗
34
34
  "stalled" => {icon: "\u25FC", color: :yellow}, # ◼
35
35
  "paused" => {icon: "\u2016", color: :dim}, # ‖
36
- "none" => {icon: "-", color: :dim}
36
+ "none" => {icon: "-", color: :dim},
37
+ "unknown" => {icon: "?", color: :yellow}
37
38
  }.freeze
38
39
 
39
40
  # PR state colors
@@ -80,6 +81,7 @@ module Ace
80
81
  id = assignment.dig("assignment", "id") || "-"
81
82
  name = assignment.dig("assignment", "name") || "-"
82
83
  state_str = assignment.dig("assignment", "state") || "none"
84
+ state_str = "unknown" if assignment.dig("recovery", "decision") == "reconcile-required"
83
85
  display = STATE_DISPLAY[state_str] || STATE_DISPLAY["none"]
84
86
 
85
87
  state_icon = colorize(display[:icon].to_s.ljust(COL_STATE), display[:color])
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
+ require_relative "../../molecules/proposal_tick"
4
5
 
5
6
  module Ace
6
7
  module Overseer
@@ -18,14 +19,16 @@ module Ace
18
19
  option :runtime, default: "tmux", desc: "Runtime (tmux, lab)"
19
20
  option :project, desc: "Filter Lab status by project"
20
21
 
21
- def initialize(collector: nil, config: nil, lab_client: nil)
22
+ def initialize(collector: nil, config: nil, lab_client: nil, proposal_tick: nil)
22
23
  super()
23
24
  @collector = collector || Organisms::StatusCollector.new
24
25
  @config = config
25
26
  @lab_client = lab_client || Molecules::LabClient.new
27
+ @proposal_tick = proposal_tick || Molecules::ProposalTick.new
26
28
  end
27
29
 
28
30
  def call(format:, runtime: "tmux", project: nil, **options)
31
+ tick_proposals
29
32
  if runtime == "lab"
30
33
  if options[:watch]
31
34
  raise Ace::Support::Cli::Error, "Lab watch runs in the project Herdr status pane; omit --watch"
@@ -34,7 +37,15 @@ module Ace
34
37
  arguments.concat(["--project", project]) unless project.to_s.empty?
35
38
  arguments << "--json" if format == "json"
36
39
  output = @lab_client.call(*arguments, json: false)
37
- puts output unless options[:quiet]
40
+ unless options[:quiet]
41
+ warn @proposal_error if @proposal_error
42
+ if format == "json" && @proposal_error
43
+ value = JSON.parse(output)
44
+ output = JSON.pretty_generate(value.merge("proposal_resolution" =>
45
+ {"status" => "deferred", "error" => @proposal_error})) if value.is_a?(Hash)
46
+ end
47
+ puts output
48
+ end
38
49
  return
39
50
  end
40
51
  raise Ace::Support::Cli::Error, "unsupported runtime: #{runtime}" unless runtime == "tmux"
@@ -55,14 +66,24 @@ module Ace
55
66
 
56
67
  private
57
68
 
69
+ def tick_proposals
70
+ @proposal_tick.call
71
+ @proposal_error = nil
72
+ rescue StandardError => e
73
+ @proposal_error = "Proposal resolution deferred (#{e.class}); retrying on next wake"
74
+ end
75
+
58
76
  def run_once(format)
59
77
  snapshot = @collector.collect
60
78
 
61
79
  if format == "json"
62
- puts JSON.pretty_generate(@collector.to_h(snapshot))
80
+ value = @collector.to_h(snapshot)
81
+ value = value.merge(proposal_resolution: {status: "deferred", error: @proposal_error}) if @proposal_error
82
+ puts JSON.pretty_generate(value)
63
83
  return
64
84
  end
65
85
 
86
+ warn @proposal_error if @proposal_error
66
87
  puts @collector.to_table(snapshot)
67
88
  end
68
89
 
@@ -77,6 +98,7 @@ module Ace
77
98
 
78
99
  loop do
79
100
  sleep_interruptible(refresh_interval)
101
+ tick_proposals
80
102
 
81
103
  elapsed = Time.now - last_full_collect
82
104
  if elapsed >= git_refresh_interval
@@ -93,6 +115,7 @@ module Ace
93
115
  def print_watch_screen(snapshot, git_refresh_interval, last_full_collect)
94
116
  remaining = [(git_refresh_interval - (Time.now - last_full_collect)).round, 0].max
95
117
  print "\e[H\e[2J"
118
+ warn @proposal_error if @proposal_error
96
119
  puts @collector.to_table(snapshot)
97
120
  puts
98
121
  puts Atoms::StatusFormatter.format_watch_footer(remaining)
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "json"
5
+
6
+ module Ace
7
+ module Overseer
8
+ module Molecules
9
+ # The living overseer invokes the deterministic policy evaluator.
10
+ # No timers, effect retries or executor live in this process.
11
+ class ProposalTick
12
+ def initialize(runner: Open3, binary: "/usr/local/bin/ace-hitl", socket: ENV["ACE_HITL_SOCKET"], project: ENV["ACE_HITL_PROJECT"])
13
+ @runner, @binary, @socket, @project = runner, binary, socket, project
14
+ end
15
+
16
+ def call
17
+ return [] if @socket.to_s.empty? || @project.to_s.empty?
18
+ out, _err, status = @runner.capture3({"ACE_HITL_SOCKET" => @socket},
19
+ @binary, "proposal", "resolve-due", "--project", @project)
20
+ raise Error, "Proposal resolution unavailable; deadlines deferred" unless status.success?
21
+ JSON.parse(out)
22
+ rescue JSON::ParserError, SystemCallError
23
+ raise Error, "Proposal resolution unavailable; deadlines deferred"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -4,11 +4,12 @@ module Ace
4
4
  module Overseer
5
5
  module Molecules
6
6
  class WorktreeContextCollector
7
- def initialize(repo_status_loader: nil, assignment_discoverer_factory: nil)
7
+ def initialize(repo_status_loader: nil, assignment_discoverer_factory: nil, recovery_factory: nil)
8
8
  @repo_status_loader = repo_status_loader || -> {
9
9
  Ace::Git::Organisms::RepoStatusLoader.load(include_pr_activity: false, include_commits: false)
10
10
  }
11
11
  @assignment_discoverer_factory = assignment_discoverer_factory || -> { Ace::Assign::Molecules::AssignmentDiscoverer.new }
12
+ @recovery_factory = recovery_factory || -> { Ace::Assign::Organisms::AttemptCoordinator.new }
12
13
  end
13
14
 
14
15
  def collect(worktree_path, location_type: :worktree)
@@ -49,8 +50,9 @@ module Ace
49
50
  def load_all_assignments
50
51
  infos = @assignment_discoverer_factory.call.find_all(include_completed: true)
51
52
  infos.map { |info| assignment_info_to_h(info) }
52
- rescue
53
- []
53
+ rescue StandardError => e
54
+ [{"assignment" => {"id" => "unknown", "name" => "unreadable assignment state", "state" => "unknown"},
55
+ "recovery" => unavailable_recovery("unknown", e)}]
54
56
  end
55
57
 
56
58
  def assignment_info_to_h(info)
@@ -70,10 +72,22 @@ module Ace
70
72
  "pending" => info.queue_state.summary[:pending]
71
73
  },
72
74
  "active_steps" => active_steps.map(&:name),
73
- "next_step" => next_step&.name
75
+ "next_step" => next_step&.name,
76
+ "recovery" => recovery_for(info.id)
74
77
  }
75
78
  end
76
79
 
80
+ def recovery_for(id)
81
+ @recovery_factory.call.recovery_snapshot(id)
82
+ rescue StandardError => e
83
+ unavailable_recovery(id, e)
84
+ end
85
+
86
+ def unavailable_recovery(id, error)
87
+ {"assignment_id" => id, "liveness" => "unknown", "last_verified_observation" => nil,
88
+ "decision" => "reconcile-required", "recovery_reason" => "recovery evidence unavailable (#{error.class})"}
89
+ end
90
+
77
91
  def extract_task_id(worktree_path, branch)
78
92
  # Match "task.NNN"/"t.NNN" or "ace-task.xxx"/"ace-t.xxx" in path (numeric or B36TS naming)
79
93
  from_path = worktree_path.to_s[/^(?:.*\/)?(?:ace-)?(?:task|t)\.([0-9a-z]+(?:\.[0-9a-z]+)?)\/?$/, 1]
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ace
4
4
  module Overseer
5
- VERSION = "0.19.0"
5
+ VERSION = "0.20.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-overseer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.0
4
+ version: 0.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-10-04 00:00:00.000000000 Z
10
+ date: 2026-10-05 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: ace-support-cli
@@ -71,42 +71,42 @@ dependencies:
71
71
  requirements:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: '0.62'
74
+ version: '0.64'
75
75
  type: :runtime
76
76
  prerelease: false
77
77
  version_requirements: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: '0.62'
81
+ version: '0.64'
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: ace-git
84
84
  requirement: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
- version: '0.23'
88
+ version: '0.29'
89
89
  type: :runtime
90
90
  prerelease: false
91
91
  version_requirements: !ruby/object:Gem::Requirement
92
92
  requirements:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
- version: '0.23'
95
+ version: '0.29'
96
96
  - !ruby/object:Gem::Dependency
97
97
  name: ace-git-worktree
98
98
  requirement: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
- version: '0.25'
102
+ version: '0.26'
103
103
  type: :runtime
104
104
  prerelease: false
105
105
  version_requirements: !ruby/object:Gem::Requirement
106
106
  requirements:
107
107
  - - "~>"
108
108
  - !ruby/object:Gem::Version
109
- version: '0.25'
109
+ version: '0.26'
110
110
  - !ruby/object:Gem::Dependency
111
111
  name: ace-task
112
112
  requirement: !ruby/object:Gem::Requirement
@@ -149,6 +149,20 @@ dependencies:
149
149
  - - "~>"
150
150
  - !ruby/object:Gem::Version
151
151
  version: '0.18'
152
+ - !ruby/object:Gem::Dependency
153
+ name: ace-herdr
154
+ requirement: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - "~>"
157
+ - !ruby/object:Gem::Version
158
+ version: '0.4'
159
+ type: :runtime
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - "~>"
164
+ - !ruby/object:Gem::Version
165
+ version: '0.4'
152
166
  - !ruby/object:Gem::Dependency
153
167
  name: ace-support-test-helpers
154
168
  requirement: !ruby/object:Gem::Requirement
@@ -256,6 +270,7 @@ files:
256
270
  - lib/ace/overseer/molecules/lab_client.rb
257
271
  - lib/ace/overseer/molecules/lab_prune_safety_checker.rb
258
272
  - lib/ace/overseer/molecules/preservation_manifest.rb
273
+ - lib/ace/overseer/molecules/proposal_tick.rb
259
274
  - lib/ace/overseer/molecules/prune_safety_checker.rb
260
275
  - lib/ace/overseer/molecules/window_opener.rb
261
276
  - lib/ace/overseer/molecules/worktree_context_collector.rb