ace-overseer 0.19.1 → 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 +4 -4
- data/CHANGELOG.md +17 -0
- data/docs/usage.md +3 -1
- data/handbook/workflow-instructions/overseer.wf.md +13 -0
- data/lib/ace/overseer/atoms/status_formatter.rb +3 -1
- data/lib/ace/overseer/cli/commands/status.rb +26 -3
- data/lib/ace/overseer/molecules/proposal_tick.rb +28 -0
- data/lib/ace/overseer/molecules/worktree_context_collector.rb +18 -4
- data/lib/ace/overseer/version.rb +1 -1
- metadata +11 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7afa7d878680232ac2f7800973b1dec72b7991a9ecd6ce3c498c73d6d8264b21
|
|
4
|
+
data.tar.gz: 124e8f19e7aa46504760813344459124a630db4263e9a2f4bf08133409036e94
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 86a724ae87399315af6cbce345ec911f62ea7ec28e57ab660652056d99bcda986fd8cb571bb031071e6971b4bbd78f39b697fa02c393ed4a562c594306b012b5
|
|
7
|
+
data.tar.gz: 15ae48aea39c596a2fb3fd639f04f72b8f669872a9a4cce365ff5cca9eefbd8d2322df523b5b6974d56e9253011dd12b4aa72f8243f7d7f1264248b548cc2a8e
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,23 @@ 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
|
+
|
|
10
27
|
## [0.19.1] - 2026-10-04
|
|
11
28
|
|
|
12
29
|
### Changed
|
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
|
-
|
|
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
|
-
|
|
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]
|
data/lib/ace/overseer/version.rb
CHANGED
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.
|
|
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-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
109
|
+
version: '0.26'
|
|
110
110
|
- !ruby/object:Gem::Dependency
|
|
111
111
|
name: ace-task
|
|
112
112
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -155,14 +155,14 @@ dependencies:
|
|
|
155
155
|
requirements:
|
|
156
156
|
- - "~>"
|
|
157
157
|
- !ruby/object:Gem::Version
|
|
158
|
-
version: 0.
|
|
158
|
+
version: '0.4'
|
|
159
159
|
type: :runtime
|
|
160
160
|
prerelease: false
|
|
161
161
|
version_requirements: !ruby/object:Gem::Requirement
|
|
162
162
|
requirements:
|
|
163
163
|
- - "~>"
|
|
164
164
|
- !ruby/object:Gem::Version
|
|
165
|
-
version: 0.
|
|
165
|
+
version: '0.4'
|
|
166
166
|
- !ruby/object:Gem::Dependency
|
|
167
167
|
name: ace-support-test-helpers
|
|
168
168
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -270,6 +270,7 @@ files:
|
|
|
270
270
|
- lib/ace/overseer/molecules/lab_client.rb
|
|
271
271
|
- lib/ace/overseer/molecules/lab_prune_safety_checker.rb
|
|
272
272
|
- lib/ace/overseer/molecules/preservation_manifest.rb
|
|
273
|
+
- lib/ace/overseer/molecules/proposal_tick.rb
|
|
273
274
|
- lib/ace/overseer/molecules/prune_safety_checker.rb
|
|
274
275
|
- lib/ace/overseer/molecules/window_opener.rb
|
|
275
276
|
- lib/ace/overseer/molecules/worktree_context_collector.rb
|