ace-overseer 0.27.0 → 0.28.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 +27 -0
- data/docs/usage.md +53 -13
- data/handbook/workflow-instructions/overseer/bootstrap-delegation.wf.md +88 -0
- data/handbook/workflow-instructions/overseer/lab-start.wf.md +41 -9
- data/lib/ace/overseer/atoms/start_contract.rb +25 -12
- data/lib/ace/overseer/cli/commands/start.rb +33 -16
- data/lib/ace/overseer/molecules/event_observation.rb +21 -2
- data/lib/ace/overseer/molecules/project_bundle.rb +119 -0
- data/lib/ace/overseer/organisms/start_orchestrator.rb +163 -86
- data/lib/ace/overseer/version.rb +1 -1
- metadata +8 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c0228801ebed27e7b1fca7fe8f580dc4fdc2a5a5be9e8d76d3c6cf59b1f8cf48
|
|
4
|
+
data.tar.gz: 2fae06ba1fc9174fc41f53f3ddcbcc1ee8dddd0152faaaa7e894a862d6f0e8c5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2ede68b20a4104eeb494014ca34a05627e8367aa5d1b6cf71cbd5f8fcfa0e4168e2029c3ca78be121e353e3e24a25bc0dd0c5ae153850b9797a7bff5bf504e6c
|
|
7
|
+
data.tar.gz: 611af27c6c2b7febc096cefeaff5545d8f3ce7fd1dd9e377932371359c850ec0e1f757476d9ffba937135f3ea021bd87f4c268007ab81b0e571c1723b48c4d88
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,33 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.28.0] - 2026-10-31
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Delegate lab bootstrap to workers and independent verification; distinguish
|
|
15
|
+
preparation/printed briefs from running agents and confirmed first turns.
|
|
16
|
+
- Preserve installed operational runtime across project starts.
|
|
17
|
+
- Normalize local observation identities for the eyes wire contract.
|
|
18
|
+
|
|
19
|
+
## [0.28.0.pre.3] - 2026-10-31
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Child project bundles no longer inherit a forced Lab `ACE_TOOLCHAIN` mode;
|
|
24
|
+
project-selected path/source dependencies and explicit child policies remain
|
|
25
|
+
authoritative. The Lab role may perform routine project startup setup without
|
|
26
|
+
taking over project deliverables.
|
|
27
|
+
- `ace-overseer start` bootstraps missing start settings and becomes the Lab
|
|
28
|
+
coordinator in the current terminal without Herdr preflight. Explicit
|
|
29
|
+
`start --reconcile` manages project children after agent initialization. The coordinator can configure itself using the packaged guide.
|
|
30
|
+
- Lab and project workspaces use `overseer` and topology ids such as `ace`,
|
|
31
|
+
independently of session ownership labels. Project startup executes each
|
|
32
|
+
project's own Gemfile/lock and binstub in a separate process; project panes
|
|
33
|
+
use their own bundle instead of inheriting the Lab's gem runtime.
|
|
34
|
+
|
|
8
35
|
## [0.27.0] - 2026-10-30
|
|
9
36
|
|
|
10
37
|
### Fixed (review rounds 3/3b)
|
data/docs/usage.md
CHANGED
|
@@ -32,14 +32,21 @@ authority execution model. tmux and Herdr are transports, never state owners.
|
|
|
32
32
|
|
|
33
33
|
## `ace-overseer start`
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
Run `ace-overseer start` at a shell in the Lab's existing Herdr `overseer`
|
|
36
|
+
space. The command becomes the Lab's Pi agent directly in that same terminal
|
|
37
|
+
and cwd, without any Herdr environment, agent, owner or layout preflight. The agent receives the packaged Lab guide
|
|
38
|
+
and a first-turn prompt to delegate configuration of commissioned projects
|
|
39
|
+
using installed CLI, skills and normal configuration editing.
|
|
40
|
+
|
|
41
|
+
No local start YAML is required for first run. The packaged bootstrap profile
|
|
42
|
+
is `pi`, `zai/glm-5.3-flash`, thinking `high`, in Lab scope. An explicit start
|
|
43
|
+
section overrides bootstrap and is validated without fallback. For an
|
|
44
|
+
explicit project start use:
|
|
38
45
|
|
|
39
46
|
```yaml
|
|
40
47
|
# .ace/overseer/config.yml (project tier of the cascade)
|
|
41
48
|
start:
|
|
42
|
-
scope: project # project | lab
|
|
49
|
+
scope: project # project | lab
|
|
43
50
|
profile: overseer-default # reference into the profiles map below
|
|
44
51
|
instructions: wfi://overseer/start # handbook source (or absolute file path)
|
|
45
52
|
profiles:
|
|
@@ -53,6 +60,8 @@ profiles:
|
|
|
53
60
|
|--------|---------|
|
|
54
61
|
| `--dry-run` | Show the start plan; no session, no sync, no model call |
|
|
55
62
|
| `--format` | `table` (default) or `json` |
|
|
63
|
+
| `--reconcile` | Explicitly reconcile project children after the agent configures the Lab; never restart the coordinator |
|
|
64
|
+
| `--scope` | Explicit `lab` or `project` role; an unconfigured bare start bootstraps the Lab |
|
|
56
65
|
|
|
57
66
|
Behavior:
|
|
58
67
|
|
|
@@ -86,15 +95,37 @@ Behavior:
|
|
|
86
95
|
- The report carries `status` (`started|reused|blocked|dry_run`), `scope`,
|
|
87
96
|
`root`, `label`, `workspace`, `pane`, `agent`, `profile`, `instructions`
|
|
88
97
|
(`source` + resolved `path`) and a `message`. Exit 0 only for a ready
|
|
89
|
-
start/reuse (dry-run included); a blocked start is a CLI error naming the
|
|
90
|
-
|
|
98
|
+
start/reuse (dry-run included); a blocked start is a CLI error naming the concrete invalid configuration
|
|
99
|
+
or unavailable capability.
|
|
91
100
|
- The started overseer coordinates and delegates (guide:
|
|
92
101
|
`wfi://overseer/start`); it never substitutes for a missing coworker.
|
|
93
102
|
|
|
103
|
+
### Project-local bundles
|
|
104
|
+
|
|
105
|
+
In auto mode each project owns its Gemfile/lock and binstubs. Child starts
|
|
106
|
+
clear parent Bundler/Ruby injection and select the project's bundle.
|
|
107
|
+
|
|
108
|
+
Explicit `ACE_TOOLCHAIN=installed` is preserved instead: the installed
|
|
109
|
+
Toolchain launcher and gem closure start the child without reading its
|
|
110
|
+
Gemfile or executing its binstub. Missing installed dependencies fail visibly;
|
|
111
|
+
there is no fallback to checkout code. Terminal backend selection does not
|
|
112
|
+
change toolchain mode.
|
|
113
|
+
|
|
114
|
+
The lab overseer delegates bundle/configuration preparation to a lab worker;
|
|
115
|
+
it never edits the project's binstub or installs gems itself. Follow
|
|
116
|
+
`wfi://overseer/bootstrap-delegation` for the task, worker, independent
|
|
117
|
+
verification and observed completion cycle. `ace-coworker start` prints a
|
|
118
|
+
brief; a separate Herdr agent launch and first prompt are required.
|
|
119
|
+
|
|
94
120
|
### Lab scope (8xs.t.pu0.a)
|
|
95
121
|
|
|
96
122
|
`scope: lab` in the LAB coordination directory's own `.ace/overseer/config.yml`
|
|
97
|
-
|
|
123
|
+
selects the Lab role. Bare `start` launches the foreground harness;
|
|
124
|
+
`start --reconcile` is the explicit tool the agent uses after configuring its
|
|
125
|
+
projects. Reconciliation manages project children without restarting or probing
|
|
126
|
+
the Lab coordinator. The older full orchestration operation remains available
|
|
127
|
+
explicitly through `start --scope lab`:
|
|
128
|
+
|
|
98
129
|
|
|
99
130
|
- The project list and CANONICAL locations come from the existing ace-lab
|
|
100
131
|
topology (`topology.projects` id/path, read internally — the public
|
|
@@ -105,17 +136,14 @@ starts the whole lab from one external entry point:
|
|
|
105
136
|
per-project (a nested lab start never recurses). An unreadable topology
|
|
106
137
|
(missing or unparseable `.ace/lab/config.yml`) is a blocked start with
|
|
107
138
|
the concrete cause.
|
|
108
|
-
- The lab creates/reuses its own herdr space (`
|
|
109
|
-
so it can never collide with a session/space title derived from a project
|
|
110
|
-
directory name, e.g. a project rooted at a directory named `lab` owns
|
|
111
|
-
`overseer-lab`) and boots or
|
|
139
|
+
- The lab creates/reuses its own herdr space (`overseer`, reserved for the Lab) and boots or
|
|
112
140
|
reuses the lab's OWN overseer session there (label `overseer-<lab-root>`,
|
|
113
141
|
cwd at the lab root, the lab guide as standing instructions, first-turn
|
|
114
142
|
prompt included) — the lab-start instructions always reach a real agent,
|
|
115
143
|
and an empty configured project list still runs the lab overseer (idle).
|
|
116
|
-
- EACH project then starts or is found through the SAME operation as a
|
|
144
|
+
- EACH project then starts or is found through its own bundle and the SAME operation as a
|
|
117
145
|
standalone start (its own cascade, root, and session identity) in its OWN
|
|
118
|
-
herdr space, titled by its
|
|
146
|
+
herdr space, titled by its topology id (for example `ace`) and
|
|
119
147
|
created/reused by title by the lab flow — project sessions are isolated
|
|
120
148
|
from each other and from the lab overseer, never share one space, and
|
|
121
149
|
never re-resolve the caller's ambient workspace (a plain terminal without
|
|
@@ -532,3 +560,15 @@ ace-overseer prune --dry-run
|
|
|
532
560
|
ace-overseer prune --yes
|
|
533
561
|
ace-overseer prune --dry-run
|
|
534
562
|
```
|
|
563
|
+
|
|
564
|
+
### Workspace names and foreground instructions
|
|
565
|
+
|
|
566
|
+
Topology IDs reserve workspace titles for the commissioned lab projects.
|
|
567
|
+
Reconciliation may reuse an existing workspace with that title, but never
|
|
568
|
+
replaces its manual panes; project ownership is the stable overseer tab label
|
|
569
|
+
plus canonical cwd, not the mutable terminal title. Ambiguous ownership blocks.
|
|
570
|
+
|
|
571
|
+
Foreground bootstrap pins the two built-in start guides to their packaged
|
|
572
|
+
files. An explicit custom instruction source remains configurable; missing
|
|
573
|
+
files block before launching Pi. Project development dependencies do not select
|
|
574
|
+
the operational runtime when installed mode is chosen.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: overseer-bootstrap-delegation
|
|
3
|
+
description: Delegate project startup to lab workers and accept their observed results
|
|
4
|
+
allowed-tools: Bash, Read
|
|
5
|
+
doc-type: workflow
|
|
6
|
+
title: Delegated Project Bootstrap
|
|
7
|
+
purpose: Complete one lab-owned startup commission without the overseer doing the repair.
|
|
8
|
+
---
|
|
9
|
+
# Delegated project bootstrap
|
|
10
|
+
|
|
11
|
+
## Commission and ownership
|
|
12
|
+
|
|
13
|
+
Read existing tasks and assignments first. Reuse the open commission for the
|
|
14
|
+
same project and goal; unknown execution state never means free capacity.
|
|
15
|
+
Create one lab coordination task in `_overseer`, with three direct children:
|
|
16
|
+
prepare, independently verify, confirm start. The latter two depend on their
|
|
17
|
+
predecessor. Only the lab overseer updates this private tree.
|
|
18
|
+
|
|
19
|
+
Create a separate executable lab task through `ace-task` for the bootstrap
|
|
20
|
+
work, linking it as the manager's `project_task`. Keep its specification in
|
|
21
|
+
its task folder. Include the canonical target repository path, required
|
|
22
|
+
configuration, allowed changes, runtime mode, acceptance checks and report
|
|
23
|
+
recipient. This is lab infrastructure work, not a project product task.
|
|
24
|
+
Workers must not edit the private coordination tree.
|
|
25
|
+
|
|
26
|
+
Explicit `ACE_TOOLCHAIN=installed` remains installed in every subprocess,
|
|
27
|
+
worker and resumed session. The project checkout is domain data. Never modify
|
|
28
|
+
tracked binstubs to bypass this selection. In auto mode retain the project's
|
|
29
|
+
own dependency model. If package code is defective, commission its repair;
|
|
30
|
+
do not patch installed gems. Preserve unrelated changes.
|
|
31
|
+
|
|
32
|
+
## Arm eyes before dispatch
|
|
33
|
+
|
|
34
|
+
Use the lab's existing eyes JSON configuration. The `ace_eyes` tool accepts
|
|
35
|
+
`{"action":"start","configPath":"<absolute eyes JSON path>"}`.
|
|
36
|
+
Inspect the returned state: enabled=true, breaker=null, errors=0 and at least
|
|
37
|
+
one observation. Read pending revisions from their original owners, reconcile
|
|
38
|
+
existing commissions, and ack each exact key/revision only after acceptance.
|
|
39
|
+
A missing extension, invalid configuration or degraded source blocks dispatch;
|
|
40
|
+
report that capability gap. Do not claim that mentioning `/eyes` armed it.
|
|
41
|
+
|
|
42
|
+
## Prepare and actually launch a worker
|
|
43
|
+
|
|
44
|
+
Use `ace-coworker prepare --task <executable-task> --roles bootstrap-worker,bootstrap-verifier
|
|
45
|
+
--to <configured-overseer-address> --format json`. Read its returned worktree,
|
|
46
|
+
assignment IDs and start-config paths; record these on the matching private
|
|
47
|
+
steps before launch. A preparation readiness report is not worker completion.
|
|
48
|
+
The task must be present in the execution worktree before any agent starts.
|
|
49
|
+
|
|
50
|
+
`ace-coworker start --config <returned-path>` validates and prints a brief;
|
|
51
|
+
it does NOT launch an agent. Use the configured Herdr provider/profile to
|
|
52
|
+
start a separate worker agent in the returned worktree, load the returned
|
|
53
|
+
role instructions, and send its first prompt. The prompt commissions only
|
|
54
|
+
that task and pinned assignment, requires the worker to read its start brief,
|
|
55
|
+
drive that assignment, and finish with `ace-coworker report`. These operational roles explicitly allow the commissioned canonical target
|
|
56
|
+
outside the lab task worktree; ordinary product worker/reviewer roles do not.
|
|
57
|
+
Use CLI help for
|
|
58
|
+
Herdr's current arguments. Record the returned pane/agent identity and verify
|
|
59
|
+
first-turn delivery. Never replace a missing executor with your own work.
|
|
60
|
+
|
|
61
|
+
End the overseer's turn immediately after dispatch. The eyes timer observes
|
|
62
|
+
assignment and HITL revisions without invoking the model on unchanged state.
|
|
63
|
+
|
|
64
|
+
## Receive, verify, start
|
|
65
|
+
|
|
66
|
+
On wake, read the original report and current assignment. Require matching
|
|
67
|
+
report ID, task, assignment and role; a preparation report, process exit or
|
|
68
|
+
unmatched message is not success. Use `ace-overseer process-reports --dry-run`
|
|
69
|
+
to inspect the matching and continuation before applying it. Only accept
|
|
70
|
+
worker success with a completed non-empty assignment and the promised evidence.
|
|
71
|
+
Record receipt through task APIs before acknowledging the eyes revision.
|
|
72
|
+
|
|
73
|
+
Use the existing role handoff gate to prepare the independent reviewer after
|
|
74
|
+
worker completion. As with the worker, a printed start command is not a live
|
|
75
|
+
agent: launch the reviewer separately with its own returned role/config and
|
|
76
|
+
confirm first-turn delivery. Do not launch a second executor while an earlier
|
|
77
|
+
one is active or its state is unknown. On repeated delivery, reconcile existing
|
|
78
|
+
assignments and agents instead of dispatching again.
|
|
79
|
+
|
|
80
|
+
After accepted independent verification, the overseer performs its coordination
|
|
81
|
+
operation `ace-overseer start --reconcile`. Confirm the target's session, first
|
|
82
|
+
turn and active eyes from actual session evidence. A ready process alone is
|
|
83
|
+
not initialized observation. Close the confirmation step and managing task
|
|
84
|
+
only with these proofs; the worker report alone cannot close the family.
|
|
85
|
+
|
|
86
|
+
On error, keep the step open and route repair to the worker. Missing authority
|
|
87
|
+
or an actual Captain decision becomes a precise HITL question; end the turn
|
|
88
|
+
armed for its answer. Never self-repair or use sleep-poll while waiting.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: overseer-lab-start
|
|
3
3
|
description: Standing instructions for a started LAB overseer session (aggregate project readiness, escalate across projects, delegate to project overseers)
|
|
4
|
-
allowed-tools: Bash, Read
|
|
4
|
+
allowed-tools: Bash, Read, Write, Edit
|
|
5
5
|
doc-type: workflow
|
|
6
6
|
title: Lab Overseer Start Guide
|
|
7
7
|
purpose: Define the lab overseer's coordination role across projects — readiness aggregation, cross-project escalation, delegation to project overseers — without taking over any project's tasks or private state.
|
|
@@ -20,21 +20,53 @@ aggregating their status and escalations, and routing the Captain's goals to
|
|
|
20
20
|
the right project. You never take over a project's tasks, worktrees, or
|
|
21
21
|
private `_overseer` state — those belong to each project's overseer.
|
|
22
22
|
|
|
23
|
+
You own the OUTCOME of routine startup, never its execution. Configuration,
|
|
24
|
+
Bundler installation, dependency repairs and binstub generation are WORKER
|
|
25
|
+
work. Create a commission and dispatch a separate lab worker even when the
|
|
26
|
+
project overseer cannot start yet. Do not edit project configuration, install
|
|
27
|
+
packages or run repair commands yourself. Missing workers are a concrete
|
|
28
|
+
capability gap, not a reason to become the worker.
|
|
29
|
+
|
|
23
30
|
## First turn after start
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
`ace-overseer start` is the bootstrap entry point. It launches your Pi
|
|
33
|
+
process directly in the current terminal and directory, with this Lab role
|
|
34
|
+
and context. It performs no Herdr agent, owner or layout preflight. A missing `.ace/overseer/config.yml` is first-run setup, not a request
|
|
35
|
+
for the Captain to write YAML. The packaged bootstrap profile is
|
|
36
|
+
`pi`, `zai/glm-5.3-flash`, thinking `high`; explicit profiles take precedence
|
|
37
|
+
and an invalid explicit configuration never silently falls back.
|
|
38
|
+
|
|
39
|
+
The Captain's latest instruction outranks this guide and stored tasks or plans.
|
|
40
|
+
On this first turn, establish your own configuration and the commissioned
|
|
41
|
+
project overseers before resuming unrelated stored backlog. Read old
|
|
42
|
+
`_overseer` tasks as context; do not dispatch old auth, publication or full
|
|
43
|
+
acceptance work while the current instruction is bootstrap and project start.
|
|
44
|
+
Report scope conflicts to the host operator instead of negotiating away a new
|
|
45
|
+
Captain order.
|
|
46
|
+
|
|
47
|
+
1. Read the lab topology, current private tasks and real assignment/session
|
|
48
|
+
state. Resume this commissioned bootstrap before unrelated backlog. Resolve
|
|
49
|
+
actual project paths; ask only about ambiguous scope or missing authority.
|
|
50
|
+
2. Read `wfi://overseer/bootstrap-delegation` through `ace-bundle` and follow
|
|
51
|
+
its complete task → worker → observation → verification → start procedure.
|
|
52
|
+
3. Arm observation with the `ace_eyes` tool, action `start`, using the lab's
|
|
53
|
+
eyes configuration path. Check enabled=true, breaker=null, errors=0 and
|
|
54
|
+
a successful observation before dispatch. A missing tool/configuration is
|
|
55
|
+
a readiness gap. Writing `/eyes` in prose does not arm observation.
|
|
56
|
+
4. Use `ace-overseer start --reconcile` to inspect/start commissioned project
|
|
57
|
+
sessions. Bare `ace-overseer start` launches a foreground harness; never
|
|
58
|
+
call it recursively from your own session. Delegate preparation or repair
|
|
59
|
+
of blocked projects to a lab worker through the bootstrap procedure.
|
|
60
|
+
5. After dispatch, record the real assignment and agent identity and END YOUR
|
|
61
|
+
TURN. Do not sleep-poll, run repeated status commands, or wait synchronously
|
|
62
|
+
for the worker. A semantic change wakes you; unchanged state stays silent.
|
|
32
63
|
|
|
33
64
|
## Delegation and escalation
|
|
34
65
|
|
|
35
66
|
- Route goals to a project by working through THAT project's overseer (its
|
|
36
67
|
session is labeled `overseer-<project-root>`); you do not dispatch its
|
|
37
|
-
coworkers directly.
|
|
68
|
+
coworkers directly for product work. Lab bootstrap workers are the explicit
|
|
69
|
+
exception: they prepare a commissioned project whose overseer cannot start.
|
|
38
70
|
- Cross-project blockers (shared forge access, missing host capability, a
|
|
39
71
|
project repeatedly failing) escalate to the Captain through `ace-hitl`
|
|
40
72
|
with the per-project evidence.
|
|
@@ -3,14 +3,9 @@
|
|
|
3
3
|
module Ace
|
|
4
4
|
module Overseer
|
|
5
5
|
module Atoms
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# `profile` reference into the same document's `profiles` map and
|
|
10
|
-
# an `instructions` source (wfi:// or an absolute file path).
|
|
11
|
-
# Nothing is guessed: a missing or ambiguous section is an error
|
|
12
|
-
# BEFORE any session is created, and no interim default model is
|
|
13
|
-
# applied anywhere. Pure — no IO, value-free messages.
|
|
6
|
+
# Validate explicit start configuration, or select the packaged
|
|
7
|
+
# first-run profile when no start section exists. A malformed explicit
|
|
8
|
+
# configuration never falls back to another model or harness.
|
|
14
9
|
class StartContract
|
|
15
10
|
SCOPES = %w[project lab].freeze
|
|
16
11
|
SECTION_KEYS = %w[scope profile instructions].freeze
|
|
@@ -23,7 +18,25 @@ module Ace
|
|
|
23
18
|
# thinking:}, instructions:}
|
|
24
19
|
# @raise [Ace::Overseer::Error] missing/ambiguous/invalid start
|
|
25
20
|
# configuration
|
|
21
|
+
BOOTSTRAP_PROFILE = {"harness" => "pi", "model" => "zai/glm-5.3-flash",
|
|
22
|
+
"thinking" => "high"}.freeze
|
|
23
|
+
|
|
24
|
+
# A missing start configuration is first-run setup, not a reason to
|
|
25
|
+
# prevent the coordinator that owns setup from starting. Explicit
|
|
26
|
+
# configurations are still validated without fallback.
|
|
27
|
+
def self.bootstrap(config, scope: "lab")
|
|
28
|
+
return config if config.key?("start")
|
|
29
|
+
|
|
30
|
+
profiles = config.fetch("profiles", {})
|
|
31
|
+
raise Ace::Overseer::Error, "profiles must be a mapping" unless profiles.is_a?(Hash)
|
|
32
|
+
|
|
33
|
+
config.merge("start" => {"scope" => scope, "profile" => "overseer-default",
|
|
34
|
+
"instructions" => "wfi://overseer/#{(scope == "lab") ? "lab-start" : "start"}"},
|
|
35
|
+
"profiles" => {"overseer-default" => BOOTSTRAP_PROFILE}.merge(profiles))
|
|
36
|
+
end
|
|
37
|
+
|
|
26
38
|
def self.normalize(config)
|
|
39
|
+
config = bootstrap(config)
|
|
27
40
|
start = config["start"]
|
|
28
41
|
unless start.is_a?(Hash)
|
|
29
42
|
raise Ace::Overseer::Error,
|
|
@@ -36,13 +49,13 @@ module Ace
|
|
|
36
49
|
unless unknown.empty?
|
|
37
50
|
raise Ace::Overseer::Error,
|
|
38
51
|
"start section has unknown key '#{unknown.sort.first}' " \
|
|
39
|
-
"(allowed: #{SECTION_KEYS.join(
|
|
52
|
+
"(allowed: #{SECTION_KEYS.join(", ")})"
|
|
40
53
|
end
|
|
41
54
|
|
|
42
55
|
scope = required(start, "scope")
|
|
43
56
|
unless SCOPES.include?(scope)
|
|
44
57
|
raise Ace::Overseer::Error,
|
|
45
|
-
"start scope must be one of: #{SCOPES.join(
|
|
58
|
+
"start scope must be one of: #{SCOPES.join(", ")} (got '#{scope}')"
|
|
46
59
|
end
|
|
47
60
|
|
|
48
61
|
profile_name = required(start, "profile")
|
|
@@ -50,7 +63,7 @@ module Ace
|
|
|
50
63
|
|
|
51
64
|
instructions = required(start, "instructions")
|
|
52
65
|
unless INSTRUCTION_PROTOCOLS.any? { |prefix| instructions.start_with?(prefix) } ||
|
|
53
|
-
|
|
66
|
+
instructions.start_with?("/")
|
|
54
67
|
raise Ace::Overseer::Error,
|
|
55
68
|
"start instructions must be a wfi:// (or prompt://, tmpl://) " \
|
|
56
69
|
"source or an absolute file path"
|
|
@@ -81,7 +94,7 @@ module Ace
|
|
|
81
94
|
unless unknown.empty?
|
|
82
95
|
raise Ace::Overseer::Error,
|
|
83
96
|
"profile '#{profile_name}' has unknown key '#{unknown.sort.first}' " \
|
|
84
|
-
"(allowed: #{PROFILE_KEYS.join(
|
|
97
|
+
"(allowed: #{PROFILE_KEYS.join(", ")})"
|
|
85
98
|
end
|
|
86
99
|
|
|
87
100
|
harness = required_value(raw, "harness", "profile '#{profile_name}'")
|
|
@@ -16,19 +16,36 @@ module Ace
|
|
|
16
16
|
|
|
17
17
|
desc "Start or reuse the project overseer session from .ace/overseer/config.yml"
|
|
18
18
|
|
|
19
|
+
option :reconcile, type: :boolean, default: false, desc: "Explicitly reconcile configured project overseers without restarting the lab coordinator"
|
|
20
|
+
option :scope, type: :string, desc: "Coordinator role (lab or project); omitted means lab bootstrap"
|
|
19
21
|
option :dry_run, type: :boolean, default: false, desc: "Show the start plan without creating a session"
|
|
20
22
|
option :format, default: "table", desc: "Output format (table, json)"
|
|
21
23
|
option :quiet, aliases: ["-q"], type: :boolean, default: false, desc: "Suppress non-essential output"
|
|
22
24
|
option :debug, aliases: ["-d"], type: :boolean, default: false, desc: "Show debug output"
|
|
23
25
|
|
|
24
|
-
def initialize(orchestrator: nil, config: nil)
|
|
26
|
+
def initialize(orchestrator: nil, config: nil, launcher: nil)
|
|
25
27
|
super()
|
|
26
28
|
@orchestrator = orchestrator
|
|
27
29
|
@config = config
|
|
30
|
+
@launcher = launcher || ->(argv) { Kernel.exec(*argv) }
|
|
28
31
|
end
|
|
29
32
|
|
|
30
|
-
def call(dry_run: false, format: "table", **options)
|
|
31
|
-
|
|
33
|
+
def call(dry_run: false, format: "table", scope: nil, reconcile: false, **options)
|
|
34
|
+
unless scope.nil? || Atoms::StartContract::SCOPES.include?(scope)
|
|
35
|
+
raise Ace::Support::Cli::Error, "scope must be lab or project"
|
|
36
|
+
end
|
|
37
|
+
if reconcile && scope == "project"
|
|
38
|
+
raise Ace::Support::Cli::Error, "--reconcile requires lab scope"
|
|
39
|
+
end
|
|
40
|
+
operation = orchestrator(scope: scope)
|
|
41
|
+
report = if reconcile
|
|
42
|
+
operation.reconcile(dry_run: dry_run)
|
|
43
|
+
elsif scope
|
|
44
|
+
operation.call(dry_run: dry_run)
|
|
45
|
+
else
|
|
46
|
+
operation.bootstrap(dry_run: dry_run)
|
|
47
|
+
end
|
|
48
|
+
return @launcher.call(report.fetch("command")) if report["status"] == "foreground"
|
|
32
49
|
|
|
33
50
|
if options[:quiet]
|
|
34
51
|
# quiet still fails loudly on a blocked start
|
|
@@ -51,24 +68,24 @@ module Ace
|
|
|
51
68
|
|
|
52
69
|
private
|
|
53
70
|
|
|
54
|
-
def orchestrator
|
|
55
|
-
@orchestrator || Organisms::StartOrchestrator.new(config: @config || Ace::Overseer.config)
|
|
71
|
+
def orchestrator(scope: nil)
|
|
72
|
+
@orchestrator || Organisms::StartOrchestrator.new(config: @config || Ace::Overseer.config, scope: scope)
|
|
56
73
|
end
|
|
57
74
|
|
|
58
75
|
def render_table(report)
|
|
59
|
-
puts "ace-overseer start: #{report[
|
|
60
|
-
puts " scope: #{report[
|
|
61
|
-
puts " root: #{report[
|
|
62
|
-
puts " label: #{report[
|
|
63
|
-
puts " profile: #{report[
|
|
76
|
+
puts "ace-overseer start: #{report["status"]}"
|
|
77
|
+
puts " scope: #{report["scope"] || "-"}"
|
|
78
|
+
puts " root: #{report["root"] || "-"}"
|
|
79
|
+
puts " label: #{report["label"] || "-"}"
|
|
80
|
+
puts " profile: #{report["profile"] || "-"}"
|
|
64
81
|
if report["instructions"]
|
|
65
|
-
puts " instructions: #{report[
|
|
82
|
+
puts " instructions: #{report["instructions"]["source"]} -> #{report["instructions"]["path"]}"
|
|
66
83
|
end
|
|
67
|
-
puts " workspace: #{report[
|
|
68
|
-
puts " pane: #{report[
|
|
69
|
-
puts " agent: #{report[
|
|
70
|
-
puts " replaced pane: #{report[
|
|
71
|
-
puts " #{report[
|
|
84
|
+
puts " workspace: #{report["workspace"] || "-"}"
|
|
85
|
+
puts " pane: #{report["pane"] || "-"}"
|
|
86
|
+
puts " agent: #{report["agent"]}"
|
|
87
|
+
puts " replaced pane: #{report["replaced_pane"] || "-"}" if report["replaced_pane"]
|
|
88
|
+
puts " #{report["message"]}" if report["message"]
|
|
72
89
|
end
|
|
73
90
|
end
|
|
74
91
|
end
|
|
@@ -44,8 +44,9 @@ module Ace
|
|
|
44
44
|
sources = local.fetch("sources").merge(remote.fetch("sources")).merge(
|
|
45
45
|
"hitl" => {"status" => "degraded", "error" => "#{hitl_error.class}: #{hitl_error.message}"}
|
|
46
46
|
)
|
|
47
|
+
ensure_budget!(events.length, limit)
|
|
47
48
|
return {"schema" => "ace.hitl.observation/v1",
|
|
48
|
-
"events" => events.sort_by { |row| row["key"] },
|
|
49
|
+
"events" => wire_events(events).sort_by { |row| row["key"] },
|
|
49
50
|
"settled" => [], "sources" => sources}
|
|
50
51
|
end
|
|
51
52
|
|
|
@@ -60,7 +61,8 @@ module Ace
|
|
|
60
61
|
manager && ([manager] + Array(manager.subtasks)).all? { |task| Ace::Task::Atoms::TaskValidationRules.terminal_status?(task.status.to_s) }
|
|
61
62
|
end
|
|
62
63
|
|
|
63
|
-
|
|
64
|
+
ensure_budget!(pending.length + settled.length + local.fetch("events").length + remote.fetch("events").length, limit)
|
|
65
|
+
projection.merge("events" => (pending + wire_events(local.fetch("events") + remote.fetch("events")))
|
|
64
66
|
.sort_by { |row| row["key"] },
|
|
65
67
|
"settled" => settled,
|
|
66
68
|
"sources" => local.fetch("sources").merge(remote.fetch("sources"))
|
|
@@ -69,6 +71,23 @@ module Ace
|
|
|
69
71
|
|
|
70
72
|
private
|
|
71
73
|
|
|
74
|
+
def ensure_budget!(count, limit)
|
|
75
|
+
return if count <= limit
|
|
76
|
+
|
|
77
|
+
raise ArgumentError, "observation has #{count} rows, exceeding limit #{limit}; increase maxEvents/--limit or archive accepted history before arming eyes"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Local owners expose readable keys, whereas eyes consumes the HITL
|
|
81
|
+
# wire envelope (hashed key, revision and a safe id). Normalize only
|
|
82
|
+
# local/remote rows; canonical HITL identities remain untouched.
|
|
83
|
+
def wire_events(rows)
|
|
84
|
+
rows.map do |row|
|
|
85
|
+
key = Digest::SHA256.hexdigest("overseer:#{row.fetch('key')}")
|
|
86
|
+
row.merge("source_key" => row.fetch("key"), "source_id" => row["id"],
|
|
87
|
+
"key" => key, "id" => key)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
72
91
|
# Configured remote observation (8xs.t.pu0.4): the project's own
|
|
73
92
|
# observe.remote.pull_requests list — never a scan of accessible
|
|
74
93
|
# accounts. No configuration means no remote rows and no remote
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "ace/herdr/molecules/bounded_process"
|
|
5
|
+
require "shellwords"
|
|
6
|
+
require "ace/support/cli/toolchain"
|
|
7
|
+
|
|
8
|
+
module Ace
|
|
9
|
+
module Overseer
|
|
10
|
+
module Molecules
|
|
11
|
+
# A project's own Gemfile/lock and binstub select its ACE version.
|
|
12
|
+
# Never load child project gems into the lab coordinator's process.
|
|
13
|
+
class ProjectBundle
|
|
14
|
+
# A new Herdr shell inherits the server environment, which may differ
|
|
15
|
+
# from this CLI's environment. Clear known Bundler selectors even
|
|
16
|
+
# when the requesting process did not have them set.
|
|
17
|
+
BUNDLE_SELECTORS = %w[
|
|
18
|
+
BUNDLE_ALLOW_OFFLINE_INSTALL BUNDLE_APP_CONFIG BUNDLE_AUTO_INSTALL
|
|
19
|
+
BUNDLE_BIN BUNDLE_BIN_PATH BUNDLE_CACHE_ALL BUNDLE_CACHE_ALL_PLATFORMS
|
|
20
|
+
BUNDLE_CACHE_PATH BUNDLE_CLEAN BUNDLE_CONSOLE BUNDLE_DEPLOYMENT
|
|
21
|
+
BUNDLE_DISABLE_CHECKSUM_VALIDATION BUNDLE_DISABLE_EXEC_LOAD
|
|
22
|
+
BUNDLE_DISABLE_LOCAL_BRANCH_CHECK BUNDLE_DISABLE_LOCAL_REVISION_CHECK
|
|
23
|
+
BUNDLE_DISABLE_SHARED_GEMS BUNDLE_DISABLE_VERSION_CHECK
|
|
24
|
+
BUNDLE_FORCE_RUBY_PLATFORM BUNDLE_FORGET_CLI_OPTIONS BUNDLE_FROZEN
|
|
25
|
+
BUNDLE_GEMFILE BUNDLE_GEM__BUNDLE BUNDLE_GEM__CHANGELOG BUNDLE_GEM__CI
|
|
26
|
+
BUNDLE_GEM__COC BUNDLE_GEM__GITHUB_USERNAME BUNDLE_GEM__LINTER
|
|
27
|
+
BUNDLE_GEM__MIT BUNDLE_GEM__RUBOCOP BUNDLE_GEM__TEST
|
|
28
|
+
BUNDLE_GIT__ALLOW_INSECURE BUNDLE_GLOBAL_GEM_CACHE BUNDLE_IGNORE_CONFIG
|
|
29
|
+
BUNDLE_IGNORE_MESSAGES BUNDLE_INIT_GEMS_RB BUNDLE_INLINE BUNDLE_JOBS
|
|
30
|
+
BUNDLE_LOCKFILE_CHECKSUMS BUNDLE_NO_INSTALL BUNDLE_NO_PRUNE BUNDLE_ONLY
|
|
31
|
+
BUNDLE_PATH BUNDLE_PATH__SYSTEM BUNDLE_PLUGINS BUNDLE_PREFER_PATCH
|
|
32
|
+
BUNDLE_REDIRECT BUNDLE_RETRY BUNDLE_SHEBANG BUNDLE_SILENCE_DEPRECATIONS
|
|
33
|
+
BUNDLE_SILENCE_ROOT_WARNING BUNDLE_SIMULATE_VERSION BUNDLE_SSL_CA_CERT
|
|
34
|
+
BUNDLE_SSL_CLIENT_CERT BUNDLE_SSL_VERIFY_MODE BUNDLE_SYSTEM_BINDIR
|
|
35
|
+
BUNDLE_TIMEOUT BUNDLE_TRUST___POLICY BUNDLE_UPDATE_REQUIRES_ALL_FLAG
|
|
36
|
+
BUNDLE_USER_HOME BUNDLE_USER_CONFIG BUNDLE_USER_CACHE BUNDLE_USER_PLUGIN
|
|
37
|
+
BUNDLE_VERBOSE BUNDLE_VERSION BUNDLE_WITH BUNDLE_WITHOUT
|
|
38
|
+
BUNDLER_SETUP BUNDLER_VERSION
|
|
39
|
+
].freeze
|
|
40
|
+
RUBY_SELECTORS = %w[RUBYOPT RUBYLIB GEM_HOME GEM_PATH ACE_TOOLCHAIN].freeze
|
|
41
|
+
|
|
42
|
+
def self.environment(root, env: ENV)
|
|
43
|
+
if Ace::Support::Cli::Toolchain.installed?
|
|
44
|
+
# Reuse the installed closure; the project checkout is domain data.
|
|
45
|
+
# Do not let a project binstub or Gemfile select source dependencies.
|
|
46
|
+
return Ace::Support::Cli::Toolchain.child_environment.merge(
|
|
47
|
+
"HERDR_PANE" => nil, "HERDR_TAB" => nil,
|
|
48
|
+
"HERDR_PANE_ID" => nil, "HERDR_TAB_ID" => nil
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
known = BUNDLE_SELECTORS + RUBY_SELECTORS
|
|
52
|
+
names = env.keys.grep(/\ABUNDLER?_/) + known +
|
|
53
|
+
(known + %w[PATH]).map { |key| "BUNDLER_ORIG_#{key}" } +
|
|
54
|
+
%w[HERDR_PANE HERDR_TAB HERDR_PANE_ID HERDR_TAB_ID]
|
|
55
|
+
names.to_h { |name| [name, nil] }.merge(
|
|
56
|
+
"BUNDLE_GEMFILE" => File.file?(File.join(root, "Gemfile")) ? File.join(root, "Gemfile") : nil,
|
|
57
|
+
"PATH" => [File.join(root, "bin"), env.fetch("PATH", "/usr/bin:/bin")].join(File::PATH_SEPARATOR)
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.shell_setup(root, env: ENV)
|
|
62
|
+
if Ace::Support::Cli::Toolchain.installed?
|
|
63
|
+
# A server-created shell may contain selectors absent from this
|
|
64
|
+
# caller. Clear the same known selectors before applying the
|
|
65
|
+
# installed closure; never inherit server-side Bundler injection.
|
|
66
|
+
names = BUNDLE_SELECTORS + BUNDLE_SELECTORS.map { |key| "BUNDLER_ORIG_#{key}" }
|
|
67
|
+
return "unset #{names.map { |name| Shellwords.escape(name) }.join(' ')}; " +
|
|
68
|
+
Ace::Support::Cli::Toolchain.shell_setup
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
values = environment(root, env: env).reject { |name, _| %w[HERDR_PANE HERDR_TAB HERDR_PANE_ID HERDR_TAB_ID].include?(name) }
|
|
72
|
+
cleared, assigned = values.partition { |_, value| value.nil? }
|
|
73
|
+
"unset #{cleared.map { |name, _| Shellwords.escape(name) }.join(" ")}; export " +
|
|
74
|
+
assigned.map { |name, value| "#{name}=#{Shellwords.escape(value)}" }.join(" ")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def initialize(timeout_s: 240)
|
|
78
|
+
@timeout_s = timeout_s
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def call(root:, workspace:, env: ENV)
|
|
82
|
+
installed = Ace::Support::Cli::Toolchain.installed?
|
|
83
|
+
unless installed
|
|
84
|
+
%w[Gemfile Gemfile.lock].each do |name|
|
|
85
|
+
unless File.file?(File.join(root, name))
|
|
86
|
+
raise Ace::Overseer::Error, "project bundle missing #{name}; prepare this project's own bundle first"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
binary = installed ? Ace::Support::Cli::Toolchain.executable("ace-overseer") : File.join(root, "bin", "ace-overseer")
|
|
91
|
+
command = File.executable?(binary) ? [binary] : ["bundle", "exec", "ace-overseer"]
|
|
92
|
+
command += ["start", "--scope", "project", "--format", "json"]
|
|
93
|
+
child_env = self.class.environment(root, env: env).merge("HERDR_WORKSPACE_ID" => workspace)
|
|
94
|
+
result = Ace::Herdr::Molecules::BoundedProcess.call(command,
|
|
95
|
+
environment: ENV.to_h.merge(child_env).compact, chdir: root,
|
|
96
|
+
timeout_s: @timeout_s, output_limit: 1_048_576)
|
|
97
|
+
raise Ace::Overseer::Error, "project start output exceeded its limit" if result.oversized
|
|
98
|
+
out, err, status = result.stdout, result.stderr, result.status
|
|
99
|
+
report = JSON.parse(out)
|
|
100
|
+
unless report.is_a?(Hash) && report["scope"] == "project" && report["status"].is_a?(String)
|
|
101
|
+
raise Ace::Overseer::Error, "project start returned no project-scope report"
|
|
102
|
+
end
|
|
103
|
+
if !status.success? && %w[started reused ready].include?(report["status"])
|
|
104
|
+
raise Ace::Overseer::Error, "project start exited #{status.exitstatus} despite a ready report"
|
|
105
|
+
end
|
|
106
|
+
report
|
|
107
|
+
rescue Timeout::Error
|
|
108
|
+
raise Ace::Overseer::Error, "project start timed out after #{@timeout_s}s; inspect the existing session before retrying"
|
|
109
|
+
rescue Ace::Herdr::Molecules::BoundedProcess::PostLaunchError => e
|
|
110
|
+
raise Ace::Overseer::Error, "project start outcome is uncertain: #{e.message}"
|
|
111
|
+
rescue JSON::ParserError
|
|
112
|
+
raise Ace::Overseer::Error, "project bundle start returned invalid JSON#{": " + err.strip[0, 300] unless err.to_s.empty?}"
|
|
113
|
+
rescue SystemCallError => e
|
|
114
|
+
raise Ace::Overseer::Error, "project bundle could not start: #{e.message}"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../atoms/start_contract"
|
|
4
4
|
require_relative "../molecules/project_root_resolver"
|
|
5
|
+
require_relative "../molecules/project_bundle"
|
|
5
6
|
require "ace/herdr"
|
|
6
7
|
require "ace/support/cli/toolchain"
|
|
7
8
|
require "json"
|
|
@@ -9,13 +10,10 @@ require "json"
|
|
|
9
10
|
module Ace
|
|
10
11
|
module Overseer
|
|
11
12
|
module Organisms
|
|
12
|
-
# `ace-overseer start`
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# document's `profiles` map, and the guide resolved through the
|
|
17
|
-
# normal handbook override chain. No interim defaults: a missing
|
|
18
|
-
# or ambiguous configuration is a blocked start, never a guess.
|
|
13
|
+
# `ace-overseer start` boots the Lab in the calling Herdr terminal,
|
|
14
|
+
# or starts/reuses configured project sessions. Missing start config
|
|
15
|
+
# uses the packaged bootstrap profile so the coordinator can configure
|
|
16
|
+
# itself. Explicit malformed configuration remains a blocked start.
|
|
19
17
|
#
|
|
20
18
|
# Session identity is Herdr's: the tab/pane label
|
|
21
19
|
# `overseer-<project>` derives from the canonical repository root
|
|
@@ -34,12 +32,9 @@ module Ace
|
|
|
34
32
|
# turn must BEGIN within this window (herdr enforces its own
|
|
35
33
|
# 5s activity observation inside the wait).
|
|
36
34
|
FIRST_TURN_TIMEOUT_MS = 120_000
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
# project rooted at a directory literally named "lab" owns the
|
|
41
|
-
# session/space title "overseer-lab".
|
|
42
|
-
LAB_SPACE_TITLE = "lab-overseer"
|
|
35
|
+
# Workspace display names are separate from session identities.
|
|
36
|
+
# `overseer` belongs to the Lab; project spaces use topology ids.
|
|
37
|
+
LAB_SPACE_TITLE = "overseer"
|
|
43
38
|
# `herdr agent start` success only means the interactive agent is
|
|
44
39
|
# READY for input — the session begins its first turn when this
|
|
45
40
|
# prompt arrives, exactly as the standing instructions' "First
|
|
@@ -48,9 +43,10 @@ module Ace
|
|
|
48
43
|
Begin your first turn now. Follow the "First turn after start"
|
|
49
44
|
section of your standing instructions: read your private
|
|
50
45
|
_overseer tasks and the project state from its owners, continue
|
|
51
|
-
existing commissioned work before choosing anything new
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
existing commissioned work before choosing anything new. Arm
|
|
47
|
+
standing observation with the ace_eyes tool, action start and
|
|
48
|
+
configPath naming your eyes configuration; verify its returned
|
|
49
|
+
observation state. Remain idle when there is nothing to do.
|
|
54
50
|
PROMPT
|
|
55
51
|
|
|
56
52
|
attr_reader :cwd
|
|
@@ -69,14 +65,18 @@ module Ace
|
|
|
69
65
|
# lazily, only when a session actually boots.
|
|
70
66
|
# @param cwd [String] invocation directory
|
|
71
67
|
def initialize(config: nil, executor: nil, root_resolver: nil, nav_runner: nil,
|
|
72
|
-
workspace: nil, workspace_factory: nil, cwd: Dir.pwd)
|
|
68
|
+
workspace: nil, workspace_factory: nil, cwd: Dir.pwd, env: ENV, scope: nil, project_runner: nil)
|
|
73
69
|
@config = config || Ace::Overseer.config
|
|
74
70
|
@executor = executor || Ace::Herdr::Molecules::HerdrExecutor.new
|
|
71
|
+
@control_surface = Ace::Herdr::Organisms::ControlSurface.new(executor: @executor)
|
|
75
72
|
@root_resolver = root_resolver || Molecules::ProjectRootResolver.new
|
|
76
73
|
@nav_runner = nav_runner || method(:run_nav)
|
|
77
74
|
@workspace = workspace
|
|
78
75
|
@workspace_factory = workspace_factory
|
|
79
76
|
@cwd = cwd
|
|
77
|
+
@env = env
|
|
78
|
+
@scope = scope
|
|
79
|
+
@project_runner = project_runner || Molecules::ProjectBundle.new
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
# @param dry_run [Boolean] report the plan without creating a
|
|
@@ -86,7 +86,10 @@ module Ace
|
|
|
86
86
|
# replaced_pane, message}; scope lab additionally carries
|
|
87
87
|
# {projects: [{id, path, status, ...}], project_count, ready}
|
|
88
88
|
def call(dry_run: false)
|
|
89
|
-
plan = Atoms::StartContract.normalize(@config)
|
|
89
|
+
plan = Atoms::StartContract.normalize(Atoms::StartContract.bootstrap(@config, scope: @scope || "lab"))
|
|
90
|
+
if @scope && plan["scope"] != @scope
|
|
91
|
+
raise Ace::Overseer::Error, "configured start scope '#{plan["scope"]}' conflicts with requested '#{@scope}'; nested lab scopes are refused"
|
|
92
|
+
end
|
|
90
93
|
return lab_start(plan, dry_run) if plan["scope"] == "lab"
|
|
91
94
|
|
|
92
95
|
root = @root_resolver.resolve(cwd: @cwd)
|
|
@@ -94,8 +97,10 @@ module Ace
|
|
|
94
97
|
instructions_path = resolve_instructions(plan["instructions"], root)
|
|
95
98
|
|
|
96
99
|
report = base_report(plan, root, label, instructions_path)
|
|
97
|
-
|
|
98
|
-
"
|
|
100
|
+
if dry_run
|
|
101
|
+
return report.merge("status" => "dry_run",
|
|
102
|
+
"message" => "dry-run: session would start with this plan; nothing was created")
|
|
103
|
+
end
|
|
99
104
|
|
|
100
105
|
owner = owner_pane(label)
|
|
101
106
|
if owner
|
|
@@ -144,6 +149,41 @@ module Ace
|
|
|
144
149
|
blocked("herdr failed while preparing the overseer session: #{e.message}")
|
|
145
150
|
end
|
|
146
151
|
|
|
152
|
+
# Bare start is just the foreground harness entrypoint. It does
|
|
153
|
+
# not discover a terminal, inspect agents or analyze topology.
|
|
154
|
+
def bootstrap(dry_run: false)
|
|
155
|
+
plan = Atoms::StartContract.normalize(@config)
|
|
156
|
+
unless plan["profile"]["harness"] == "pi"
|
|
157
|
+
raise Ace::Overseer::Error, "foreground start requires the configured pi harness"
|
|
158
|
+
end
|
|
159
|
+
root = File.realpath(@cwd)
|
|
160
|
+
source = plan["instructions"]
|
|
161
|
+
guide = if %w[wfi://overseer/lab-start wfi://overseer/start].include?(source)
|
|
162
|
+
filename = source.end_with?("lab-start") ? "lab-start.wf.md" : "start.wf.md"
|
|
163
|
+
File.join(Ace::Overseer.gem_root, "handbook/workflow-instructions/overseer", filename)
|
|
164
|
+
else
|
|
165
|
+
resolve_instructions(source, root)
|
|
166
|
+
end
|
|
167
|
+
raise Ace::Overseer::Error, "standing instructions file is missing: #{guide}" unless File.file?(guide)
|
|
168
|
+
command = ["pi", "--model", plan["profile"]["model"]]
|
|
169
|
+
thinking = plan["profile"]["thinking"]
|
|
170
|
+
command += ["--thinking", thinking] if thinking
|
|
171
|
+
command += ["--append-system-prompt", guide, FIRST_TURN_PROMPT]
|
|
172
|
+
{"status" => dry_run ? "dry_run" : "foreground", "scope" => plan["scope"],
|
|
173
|
+
"root" => root, "profile" => plan["profile_name"],
|
|
174
|
+
"instructions" => {"source" => source, "path" => guide},
|
|
175
|
+
"command" => command, "message" => "launch the coordinator in the current terminal"}
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Explicit tool available to the running Lab agent. This operation
|
|
179
|
+
# manages configured project children, never the coordinator itself.
|
|
180
|
+
def reconcile(dry_run: false)
|
|
181
|
+
plan = Atoms::StartContract.normalize(@config)
|
|
182
|
+
raise Ace::Overseer::Error, "project reconciliation requires lab scope" unless plan["scope"] == "lab"
|
|
183
|
+
|
|
184
|
+
lab_start(plan, dry_run, reconcile: true)
|
|
185
|
+
end
|
|
186
|
+
|
|
147
187
|
private
|
|
148
188
|
|
|
149
189
|
# --- lab scope (task 8xs.t.pu0.a) -----------------------------------
|
|
@@ -161,16 +201,16 @@ module Ace
|
|
|
161
201
|
# healthy projects continue; a repeated start repairs only what
|
|
162
202
|
# is missing and never restarts healthy sessions or closes
|
|
163
203
|
# manual panes.
|
|
164
|
-
def lab_start(plan, dry_run)
|
|
204
|
+
def lab_start(plan, dry_run, reconcile: false)
|
|
165
205
|
lab_root = File.realpath(@cwd)
|
|
166
206
|
base = {"status" => nil, "scope" => "lab", "root" => lab_root,
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
base["instructions"]["path"] = resolve_instructions(plan["instructions"], lab_root)
|
|
207
|
+
"label" => Atoms::StartContract.session_label(lab_root),
|
|
208
|
+
"workspace" => nil, "pane" => nil, "agent" => AGENT_NAME,
|
|
209
|
+
"profile" => plan["profile_name"],
|
|
210
|
+
"instructions" => {"source" => plan["instructions"], "path" => nil},
|
|
211
|
+
"replaced_pane" => nil, "projects" => [], "project_count" => 0,
|
|
212
|
+
"ready" => nil, "message" => nil}
|
|
213
|
+
base["instructions"]["path"] = resolve_instructions(plan["instructions"], lab_root) unless reconcile
|
|
174
214
|
|
|
175
215
|
projects = begin
|
|
176
216
|
Ace::Lab::Molecules::ProjectLocations.read(lab_root)
|
|
@@ -179,42 +219,56 @@ module Ace
|
|
|
179
219
|
"message" => "lab topology could not be read: #{e.message}")
|
|
180
220
|
end
|
|
181
221
|
|
|
182
|
-
if projects.any? { |project|
|
|
183
|
-
|
|
222
|
+
if projects.any? { |project|
|
|
223
|
+
project["path"].to_s.empty? == false &&
|
|
224
|
+
File.directory?(project["path"]) && File.realpath(project["path"]) == lab_root
|
|
225
|
+
}
|
|
184
226
|
return base.merge("status" => "blocked",
|
|
185
227
|
"message" => "the lab root '#{lab_root}' is itself declared as a project; " \
|
|
186
228
|
"conflicting lab/project roles are refused")
|
|
187
229
|
end
|
|
188
230
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
"
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
231
|
+
if dry_run
|
|
232
|
+
return base.merge("status" => "dry_run", "project_count" => projects.size,
|
|
233
|
+
"projects" => projects.map { |project|
|
|
234
|
+
{"id" => project["id"], "path" => project["path"],
|
|
235
|
+
"status" => "would_start"}
|
|
236
|
+
},
|
|
237
|
+
"message" => "dry-run: the lab space with the lab overseer session and " \
|
|
238
|
+
"#{projects.size} project space(s)/overseer(s) would start; nothing was created")
|
|
239
|
+
end
|
|
197
240
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
241
|
+
if reconcile
|
|
242
|
+
base["coordinator_managed"] = false
|
|
243
|
+
session = {}
|
|
244
|
+
lab_ready = true
|
|
245
|
+
else
|
|
246
|
+
workspace = lab_workspace
|
|
247
|
+
base["workspace"] = workspace
|
|
248
|
+
|
|
249
|
+
# The lab's OWN overseer session (the wfi://overseer/lab-start
|
|
250
|
+
# guide assumes it): booted or reused in the lab space before
|
|
251
|
+
# any project, so the lab-start instructions reach an agent.
|
|
252
|
+
session = boot_lab_session(workspace, plan, base["instructions"]["path"], lab_root)
|
|
253
|
+
if session["status"] == "blocked"
|
|
254
|
+
return base.merge("status" => "blocked", "pane" => session["pane"],
|
|
255
|
+
"message" => session["message"])
|
|
256
|
+
end
|
|
204
257
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
258
|
+
base["pane"] = session["pane"]
|
|
259
|
+
base["replaced_pane"] = session["replaced_pane"]
|
|
260
|
+
base["prompt_failed"] = !session["prompt_failed"].nil? && session["prompt_failed"]
|
|
208
261
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
262
|
+
# Readiness REQUIRES a confirmed first turn (review R7): a
|
|
263
|
+
# started/reused session whose first-turn delivery failed or
|
|
264
|
+
# stays unconfirmed is NOT ready, whatever its process state.
|
|
265
|
+
lab_ready = !session["prompt_failed"]
|
|
266
|
+
end
|
|
213
267
|
|
|
214
268
|
# An empty configured list is a valid idle lab: the lab
|
|
215
269
|
# overseer itself still runs (and must be initialized).
|
|
216
270
|
if projects.empty?
|
|
217
|
-
message = "no projects configured; the lab overseer is idle"
|
|
271
|
+
message = reconcile ? "no project children configured" : "no projects configured; the lab overseer is idle"
|
|
218
272
|
message += "; #{session["message"]}" if session["message"]
|
|
219
273
|
return base.merge("status" => lab_ready ? "ready" : "partial",
|
|
220
274
|
"ready" => lab_ready, "project_count" => 0, "message" => message)
|
|
@@ -264,12 +318,10 @@ module Ace
|
|
|
264
318
|
# workspace reference), never treated as absent: creating a
|
|
265
319
|
# second space with the same title would duplicate the space.
|
|
266
320
|
def find_workspace(title)
|
|
267
|
-
|
|
268
|
-
list = existing.is_a?(Hash) ? Array(existing["result"] || existing["workspaces"]) : Array(existing)
|
|
269
|
-
found = list.find { |entry| entry["title"] == title || entry["label"] == title }
|
|
321
|
+
found = @control_surface.list_workspaces.find { |entry| entry[:title] == title }
|
|
270
322
|
return nil unless found
|
|
271
323
|
|
|
272
|
-
found[
|
|
324
|
+
found[:id] || found[:title]
|
|
273
325
|
rescue Ace::Herdr::ExecutorUnavailableError => e
|
|
274
326
|
raise Ace::Overseer::Error, "herdr is unavailable: #{e.message}"
|
|
275
327
|
end
|
|
@@ -287,17 +339,21 @@ module Ace
|
|
|
287
339
|
if owner
|
|
288
340
|
owner_cwd = owner["cwd"].to_s
|
|
289
341
|
matching = !owner_cwd.empty? &&
|
|
290
|
-
|
|
342
|
+
begin
|
|
343
|
+
File.realpath(owner_cwd) == lab_root
|
|
344
|
+
rescue
|
|
345
|
+
false
|
|
346
|
+
end
|
|
291
347
|
unless matching
|
|
292
348
|
return {"status" => "blocked", "pane" => nil, "replaced_pane" => nil,
|
|
293
|
-
|
|
349
|
+
"message" => "owner pane '#{owner["id"]}' works in '#{owner_cwd}', which is " \
|
|
294
350
|
"not the lab root '#{lab_root}'; refusing to take over a foreign session"}
|
|
295
351
|
end
|
|
296
352
|
state = agent_state(owner["id"])
|
|
297
353
|
if state
|
|
298
354
|
warning = repair_reused_initialization(owner["id"], state)
|
|
299
355
|
return {"status" => "reused", "pane" => owner["id"], "replaced_pane" => nil,
|
|
300
|
-
|
|
356
|
+
"prompt_failed" => !warning.nil?, "message" => warning}
|
|
301
357
|
end
|
|
302
358
|
|
|
303
359
|
replaced = owner["id"]
|
|
@@ -306,7 +362,7 @@ module Ace
|
|
|
306
362
|
pane = boot_session(workspace, label, lab_root, plan, instructions_path)
|
|
307
363
|
prompt_warning = first_prompt(pane)
|
|
308
364
|
{"status" => "started", "pane" => pane, "replaced_pane" => replaced,
|
|
309
|
-
|
|
365
|
+
"prompt_failed" => !prompt_warning.nil?, "message" => prompt_warning}
|
|
310
366
|
rescue Ace::Overseer::Error => e
|
|
311
367
|
{"status" => "blocked", "pane" => nil, "replaced_pane" => nil, "message" => e.message}
|
|
312
368
|
end
|
|
@@ -320,29 +376,33 @@ module Ace
|
|
|
320
376
|
path = project["path"]
|
|
321
377
|
if path.nil? || path.to_s.strip.empty?
|
|
322
378
|
return {"id" => id, "path" => nil, "status" => "blocked",
|
|
323
|
-
|
|
379
|
+
"message" => "project '#{id}' declares no path in the lab topology"}
|
|
324
380
|
end
|
|
325
381
|
unless File.directory?(path)
|
|
326
382
|
return {"id" => id, "path" => path, "status" => "blocked",
|
|
327
|
-
|
|
383
|
+
"message" => "project '#{id}' directory '#{path}' does not exist"}
|
|
328
384
|
end
|
|
329
385
|
|
|
330
|
-
|
|
331
|
-
# a project cascade that itself declares scope lab would
|
|
332
|
-
# recurse (a nested lab start) — refused, never executed
|
|
333
|
-
if project_config.is_a?(Hash) && project_config.dig("start", "scope") == "lab"
|
|
386
|
+
if id == LAB_SPACE_TITLE
|
|
334
387
|
return {"id" => id, "path" => path, "status" => "blocked",
|
|
335
|
-
|
|
336
|
-
|
|
388
|
+
"message" => "project id '#{id}' is reserved for the lab workspace"}
|
|
389
|
+
end
|
|
390
|
+
root = @root_resolver.resolve(cwd: path)
|
|
391
|
+
label = Atoms::StartContract.session_label(root)
|
|
392
|
+
owner = owner_pane(label)
|
|
393
|
+
workspace = if owner && agent_state(owner["id"])
|
|
394
|
+
reference = owner["workspace"] || owner["workspace_id"]
|
|
395
|
+
if reference.to_s.strip.empty?
|
|
396
|
+
raise Ace::Overseer::Error,
|
|
397
|
+
"live project owner '#{owner["id"]}' reports no workspace; " \
|
|
398
|
+
"refusing ambient fallback or a separate project workspace"
|
|
399
|
+
end
|
|
400
|
+
reference
|
|
401
|
+
else
|
|
402
|
+
find_or_create_workspace(id)
|
|
337
403
|
end
|
|
404
|
+
report = @project_runner.call(root: root, workspace: workspace, env: @env)
|
|
338
405
|
|
|
339
|
-
orchestrator = self.class.new(config: project_config, executor: @executor,
|
|
340
|
-
root_resolver: @root_resolver, nav_runner: @nav_runner,
|
|
341
|
-
workspace_factory: ->(label) { find_or_create_workspace(label) }, cwd: path)
|
|
342
|
-
report = orchestrator.call
|
|
343
|
-
# a reused session keeps its reported space: the lab-managed
|
|
344
|
-
# space titled by the project's session label, when it exists
|
|
345
|
-
report["workspace"] ||= find_workspace(report["label"])
|
|
346
406
|
report.merge("id" => id, "path" => path)
|
|
347
407
|
rescue Ace::Overseer::Error, Ace::Herdr::ExecutorError => e
|
|
348
408
|
{"id" => id, "path" => path, "status" => "blocked", "message" => e.message}
|
|
@@ -383,7 +443,7 @@ module Ace
|
|
|
383
443
|
"cannot resolve the ace-nav executable for the start instructions: #{e.message}"
|
|
384
444
|
end
|
|
385
445
|
result = @nav_runner.call([binary, "resolve", source], root)
|
|
386
|
-
path = result["status"] == "ok" ? result["out"].to_s.lines.first.to_s.strip : nil
|
|
446
|
+
path = (result["status"] == "ok") ? result["out"].to_s.lines.first.to_s.strip : nil
|
|
387
447
|
if path.nil? || path.empty?
|
|
388
448
|
raise Ace::Overseer::Error,
|
|
389
449
|
"start instructions '#{source}' do not resolve through ace-nav " \
|
|
@@ -426,8 +486,32 @@ module Ace
|
|
|
426
486
|
|
|
427
487
|
# The live pane owning this project's overseer label.
|
|
428
488
|
def owner_pane(label)
|
|
429
|
-
|
|
430
|
-
|
|
489
|
+
# boot_session names the TAB, not the terminal title. Pi and shell
|
|
490
|
+
# applications may change terminal titles at any time.
|
|
491
|
+
tabs = @control_surface.list_tabs.select { |entry| entry[:title] == label }
|
|
492
|
+
return nil if tabs.empty?
|
|
493
|
+
|
|
494
|
+
panes = @control_surface.list_panes
|
|
495
|
+
owners = tabs.map do |tab|
|
|
496
|
+
matches = panes.select { |pane| pane[:tab] == tab[:id] }
|
|
497
|
+
if tab[:id].to_s.empty? || matches.length != 1 || matches.first[:id].to_s.empty?
|
|
498
|
+
raise Ace::Overseer::Error,
|
|
499
|
+
"overseer tab '#{label}' has no unique owner pane; refusing an ambiguous session"
|
|
500
|
+
end
|
|
501
|
+
matches.first
|
|
502
|
+
end
|
|
503
|
+
if owners.length > 1
|
|
504
|
+
# A replaced dead owner may retain its tab. Only a single live
|
|
505
|
+
# agent identifies an unambiguous current owner; never pick the
|
|
506
|
+
# first of multiple live or dead candidates.
|
|
507
|
+
live = owners.select { |pane| !agent_state(pane[:id]).nil? }
|
|
508
|
+
unless live.length == 1
|
|
509
|
+
raise Ace::Overseer::Error,
|
|
510
|
+
"multiple overseer tabs '#{label}' have no unique live owner; refusing an ambiguous session"
|
|
511
|
+
end
|
|
512
|
+
owners = live
|
|
513
|
+
end
|
|
514
|
+
owners.first.transform_keys(&:to_s)
|
|
431
515
|
rescue Ace::Herdr::ExecutorUnavailableError => e
|
|
432
516
|
raise Ace::Overseer::Error, "herdr is unavailable: #{e.message}"
|
|
433
517
|
end
|
|
@@ -492,6 +576,7 @@ module Ace
|
|
|
492
576
|
"herdr created tab '#{label}' but reported no root pane"
|
|
493
577
|
end
|
|
494
578
|
|
|
579
|
+
@executor.pane_run(pane, Molecules::ProjectBundle.shell_setup(root, env: @env))
|
|
495
580
|
args = ["--model", plan["profile"]["model"]]
|
|
496
581
|
if plan["profile"]["thinking"]
|
|
497
582
|
args += ["--thinking", plan["profile"]["thinking"]]
|
|
@@ -544,14 +629,6 @@ module Ace
|
|
|
544
629
|
end
|
|
545
630
|
end
|
|
546
631
|
|
|
547
|
-
def extract_panes(json)
|
|
548
|
-
case json
|
|
549
|
-
when Array then json
|
|
550
|
-
when Hash then Array(json["result"] || json["panes"] || [])
|
|
551
|
-
else []
|
|
552
|
-
end
|
|
553
|
-
end
|
|
554
|
-
|
|
555
632
|
def dig_pane(json)
|
|
556
633
|
return nil unless json.is_a?(Hash)
|
|
557
634
|
|
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.28.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-31 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: ace-support-cli
|
|
@@ -139,14 +139,14 @@ dependencies:
|
|
|
139
139
|
requirements:
|
|
140
140
|
- - "~>"
|
|
141
141
|
- !ruby/object:Gem::Version
|
|
142
|
-
version: '0.
|
|
142
|
+
version: '0.8'
|
|
143
143
|
type: :runtime
|
|
144
144
|
prerelease: false
|
|
145
145
|
version_requirements: !ruby/object:Gem::Requirement
|
|
146
146
|
requirements:
|
|
147
147
|
- - "~>"
|
|
148
148
|
- !ruby/object:Gem::Version
|
|
149
|
-
version: '0.
|
|
149
|
+
version: '0.8'
|
|
150
150
|
- !ruby/object:Gem::Dependency
|
|
151
151
|
name: ace-hitl
|
|
152
152
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -195,20 +195,14 @@ dependencies:
|
|
|
195
195
|
requirements:
|
|
196
196
|
- - "~>"
|
|
197
197
|
- !ruby/object:Gem::Version
|
|
198
|
-
version: '0.
|
|
199
|
-
- - ">="
|
|
200
|
-
- !ruby/object:Gem::Version
|
|
201
|
-
version: 0.8.1
|
|
198
|
+
version: '0.9'
|
|
202
199
|
type: :runtime
|
|
203
200
|
prerelease: false
|
|
204
201
|
version_requirements: !ruby/object:Gem::Requirement
|
|
205
202
|
requirements:
|
|
206
203
|
- - "~>"
|
|
207
204
|
- !ruby/object:Gem::Version
|
|
208
|
-
version: '0.
|
|
209
|
-
- - ">="
|
|
210
|
-
- !ruby/object:Gem::Version
|
|
211
|
-
version: 0.8.1
|
|
205
|
+
version: '0.9'
|
|
212
206
|
- !ruby/object:Gem::Dependency
|
|
213
207
|
name: ace-lab
|
|
214
208
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -319,6 +313,7 @@ files:
|
|
|
319
313
|
- exe/ace-overseer
|
|
320
314
|
- handbook/skills/as-overseer/SKILL.md
|
|
321
315
|
- handbook/workflow-instructions/overseer.wf.md
|
|
316
|
+
- handbook/workflow-instructions/overseer/bootstrap-delegation.wf.md
|
|
322
317
|
- handbook/workflow-instructions/overseer/cold-cycle-acceptance.wf.md
|
|
323
318
|
- handbook/workflow-instructions/overseer/lab-start.wf.md
|
|
324
319
|
- handbook/workflow-instructions/overseer/queue.wf.md
|
|
@@ -354,6 +349,7 @@ files:
|
|
|
354
349
|
- lib/ace/overseer/molecules/hitl_inbox.rb
|
|
355
350
|
- lib/ace/overseer/molecules/local_sources.rb
|
|
356
351
|
- lib/ace/overseer/molecules/preservation_manifest.rb
|
|
352
|
+
- lib/ace/overseer/molecules/project_bundle.rb
|
|
357
353
|
- lib/ace/overseer/molecules/project_root_resolver.rb
|
|
358
354
|
- lib/ace/overseer/molecules/prune_safety_checker.rb
|
|
359
355
|
- lib/ace/overseer/molecules/remote_sources.rb
|