space-architect 9.7.0 → 9.8.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 +23 -0
- data/lib/space_architect/agent_installer.rb +18 -4
- data/lib/space_architect/architect_project.rb +9 -7
- data/lib/space_architect/cli/architect.rb +5 -2
- data/lib/space_architect/routing.rb +97 -48
- data/lib/space_architect/version.rb +1 -1
- data/skill/architect/SKILL.md +8 -6
- data/skill/architect-action/SKILL.md +22 -28
- data/skill/architect-discuss/SKILL.md +15 -15
- data/skill/architect-discuss/dispatch.md +10 -18
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2a29d0c5688ed96e02a49a159385f508a845714e1741577384f6a6369a1fbb7e
|
|
4
|
+
data.tar.gz: 2b46a7ffee6ee544c9f5e30b7926f4ee5d541d81032be53617cec19f5e519bfd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d418323e0ef3c26cfdf2ca7d06aac9fcec9da1d74c8ccbb0a339cc5f0e85eaf9838ab5bf380f7949f28d30a416e7d5b2a0c9043e1fe74d3659ffbfaf5d764641
|
|
7
|
+
data.tar.gz: 6fdbc0e2e89a34ff61f661202fb04e0f452a5f2b096db28b2dfd0a3adf41e6b9194a82b62253e2e1d4e9647886244d0ff0e38339d898726b93d8bcebc207800f
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ All notable changes to this project are 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
|
+
## [9.8.0] - 2026-10-30
|
|
9
|
+
|
|
10
|
+
### Removed
|
|
11
|
+
|
|
12
|
+
- **The coordinator role (I10)** — the architect session itself drives the
|
|
13
|
+
loop in driver mode (the pi extension's `/loop` and agent-side launch
|
|
14
|
+
install a driver playbook instead of spawning a detached coordinator).
|
|
15
|
+
The role leaves the routing known set and the `architect routing init`
|
|
16
|
+
template (byte-exact contract re-frozen through both parsers), and
|
|
17
|
+
install no longer ships its playbook — a stale user-level
|
|
18
|
+
`architect-coordinator.md` is removed on install.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **Routing deprecation tolerance** — unknown `architect.yaml` role keys
|
|
23
|
+
(a retired key, or a typo) inform instead of rejecting: every command
|
|
24
|
+
proceeds, with one inform line naming the key and the known-role set.
|
|
25
|
+
Upgrading never breaks on an older config.
|
|
26
|
+
- **Shipped skills rewritten for driver mode** — architect,
|
|
27
|
+
architect-action, and architect-discuss describe the session-driven
|
|
28
|
+
loop: one-hop escalations, state re-derived from disk each wake,
|
|
29
|
+
compaction after each child launch.
|
|
30
|
+
|
|
8
31
|
## [9.7.0] - 2026-10-30
|
|
9
32
|
|
|
10
33
|
### Added
|
|
@@ -4,8 +4,10 @@ require "open3"
|
|
|
4
4
|
|
|
5
5
|
module Space
|
|
6
6
|
module Architect
|
|
7
|
-
# Installs the role playbooks (architect-
|
|
8
|
-
#
|
|
7
|
+
# Installs the role playbooks (architect-spec/builder/judge/narrator) to
|
|
8
|
+
# ~/.pi/agent/agents and removes a stale architect-coordinator.md left by a
|
|
9
|
+
# pre-driver-era install (I10: the coordinator role retired — the architect
|
|
10
|
+
# session itself drives the loop). The playbook text is rendered by the
|
|
9
11
|
# installed architect-loop extension itself — the extension owns that text
|
|
10
12
|
# (it renders the per-space .pi/agents files from the same source), so this
|
|
11
13
|
# installer carries no second copy to drift. Routing pins come from the
|
|
@@ -14,7 +16,7 @@ module Space
|
|
|
14
16
|
# machine-wide fallback for sessions that cannot see a space's .pi dir;
|
|
15
17
|
# a space's own .pi/agents files always win inside that space.
|
|
16
18
|
module AgentInstaller
|
|
17
|
-
ROLES = %w[
|
|
19
|
+
ROLES = %w[spec builder judge narrator].freeze
|
|
18
20
|
|
|
19
21
|
# Renders each role's agent file and prints it between ===FILE===<role>
|
|
20
22
|
# markers. Dynamic import (node -e runs CJS; import() is legal there) so
|
|
@@ -29,7 +31,7 @@ module Space
|
|
|
29
31
|
console.error("no architect.yaml found — run `architect routing init --global` to scaffold the machine-wide config first");
|
|
30
32
|
process.exit(1);
|
|
31
33
|
}
|
|
32
|
-
for (const role of ["
|
|
34
|
+
for (const role of ["spec", "builder", "judge", "narrator"]) {
|
|
33
35
|
console.log("===FILE===" + role);
|
|
34
36
|
console.log(renderAgentFile(role, config));
|
|
35
37
|
}
|
|
@@ -53,11 +55,23 @@ module Space
|
|
|
53
55
|
results = ROLES.map do |role|
|
|
54
56
|
write_agent(dest.join("architect-#{role}.md"), rendered.fetch(role), force: force, dry_run: dry_run)
|
|
55
57
|
end
|
|
58
|
+
results.concat(remove_stale_coordinator(dest, dry_run: dry_run))
|
|
56
59
|
{ dest_root: dest, agents: results, dry_run: dry_run }
|
|
57
60
|
end
|
|
58
61
|
|
|
59
62
|
private
|
|
60
63
|
|
|
64
|
+
# The retired coordinator playbook: remove, never update — the loop is
|
|
65
|
+
# driven by the architect session itself, so a leftover fifth playbook
|
|
66
|
+
# can only mislead. A dry run reports the would-be removal untouched.
|
|
67
|
+
def remove_stale_coordinator(dest, dry_run:)
|
|
68
|
+
stale = dest.join("architect-coordinator.md")
|
|
69
|
+
return [] unless stale.exist?
|
|
70
|
+
|
|
71
|
+
FileUtils.rm(stale) unless dry_run
|
|
72
|
+
[{ name: stale.basename.to_s, action: dry_run ? :would_remove : :removed, path: stale }]
|
|
73
|
+
end
|
|
74
|
+
|
|
61
75
|
def default_dest(env)
|
|
62
76
|
Pathname.new(env.fetch("PI_AGENTS_DIR", File.join(pi_agent_dir(env), "agents")))
|
|
63
77
|
end
|
|
@@ -907,12 +907,12 @@ module Space::Architect
|
|
|
907
907
|
def worktree_add(repo, iteration, lane, base: nil, base_explicit: !base.nil?, harness: nil, model: nil,
|
|
908
908
|
variant: false, effort: nil, touch: nil, force: false, err: $stderr)
|
|
909
909
|
model, suffix_level = Harness.parse_model_suffix(model)
|
|
910
|
-
harness, model, inform = resolve_harness_model(harness, model)
|
|
910
|
+
harness, model, inform = resolve_harness_model(harness, model, err: err)
|
|
911
911
|
err.puts inform if inform
|
|
912
912
|
# The config layer sits above project.effort — architect.yaml is the
|
|
913
913
|
# dedicated routing layer; unconfigured spaces fall through unchanged.
|
|
914
914
|
resolved_level = effort || suffix_level ||
|
|
915
|
-
Routing.for_space(space.path)&.dig("builder", "thinking") || project_defaults["effort"]
|
|
915
|
+
Routing.for_space(space.path, err: err)&.dig("builder", "thinking") || project_defaults["effort"]
|
|
916
916
|
Harness.validate_thinking_level!(resolved_level)
|
|
917
917
|
if effort.nil? && suffix_level
|
|
918
918
|
err.puts "thinking: model suffix ':#{suffix_level}' parsed from lane model → level=#{suffix_level} (model stripped)"
|
|
@@ -1992,12 +1992,14 @@ module Space::Architect
|
|
|
1992
1992
|
# Returns [harness, model, inform] — inform is the loud last-resort line (nil
|
|
1993
1993
|
# otherwise) for the caller to print once to its err stream when the model
|
|
1994
1994
|
# fell all the way through to Harness::DEFAULT_MODEL (no flag, no stored
|
|
1995
|
-
# value, no architect.yaml builder role, no project.model).
|
|
1996
|
-
|
|
1995
|
+
# value, no architect.yaml builder role, no project.model). +err+ is additive
|
|
1996
|
+
# (I10): given a stream, a config carrying an unknown role key prints the one
|
|
1997
|
+
# per-process deprecation inform on it; nil (the default) resolves silently.
|
|
1998
|
+
def resolve_harness_model(harness, model, stored_harness: nil, stored_model: nil, err: nil)
|
|
1997
1999
|
defaults = project_defaults
|
|
1998
2000
|
resolved_harness = (harness || stored_harness || defaults["harness"] || "pi").to_s
|
|
1999
2001
|
stored_model = nil unless stored_harness.nil? || stored_harness.to_s == resolved_harness
|
|
2000
|
-
config_model = Routing.for_space(space.path)&.dig("builder", "model")
|
|
2002
|
+
config_model = Routing.for_space(space.path, err: err)&.dig("builder", "model")
|
|
2001
2003
|
resolved_model = model || stored_model || config_model || defaults["model"] || Harness::DEFAULT_MODEL
|
|
2002
2004
|
inform = default_model_inform if model.nil? && stored_model.nil? && config_model.nil? && defaults["model"].nil?
|
|
2003
2005
|
[resolved_harness, resolved_model, inform]
|
|
@@ -2037,7 +2039,7 @@ module Space::Architect
|
|
|
2037
2039
|
def resolve_dispatch_harness(lane_entry, model:, harness:, effort:, force:, err:)
|
|
2038
2040
|
model, suffix_level = Harness.parse_model_suffix(model)
|
|
2039
2041
|
resolved_harness, resolved_model, inform = resolve_harness_model(harness, model,
|
|
2040
|
-
stored_harness: lane_entry["harness"], stored_model: lane_entry["model"])
|
|
2042
|
+
stored_harness: lane_entry["harness"], stored_model: lane_entry["model"], err: err)
|
|
2041
2043
|
err.puts inform if inform
|
|
2042
2044
|
|
|
2043
2045
|
resolved_effort =
|
|
@@ -2050,7 +2052,7 @@ module Space::Architect
|
|
|
2050
2052
|
else
|
|
2051
2053
|
# Dispatch's tail is the lane's stored effort (project.effort was never
|
|
2052
2054
|
# in the dispatch chain); architect.yaml builder thinking slots in above it.
|
|
2053
|
-
lane_entry["effort"] || Routing.for_space(space.path)&.dig("builder", "thinking")
|
|
2055
|
+
lane_entry["effort"] || Routing.for_space(space.path, err: err)&.dig("builder", "thinking")
|
|
2054
2056
|
end
|
|
2055
2057
|
|
|
2056
2058
|
[resolved_harness, resolved_model, resolved_effort]
|
|
@@ -172,7 +172,10 @@ module Space::Architect
|
|
|
172
172
|
# constant as the last resort it is, not ordinary behavior.
|
|
173
173
|
# Fully qualified: the Architect::Routing subcommand namespace
|
|
174
174
|
# (I09) lexically shadows Space::Architect::Routing inside this module.
|
|
175
|
-
|
|
175
|
+
# err: $stderr makes status one of the three deprecation-inform surfaces
|
|
176
|
+
# (I10) — a config still carrying an unknown role key informs once per
|
|
177
|
+
# process, here or on whichever surface resolves routing first.
|
|
178
|
+
config_model = Space::Architect::Routing.for_space(sp.path, err: $stderr)&.dig("builder", "model")
|
|
176
179
|
|
|
177
180
|
terminal.say "Project status: #{block['status'] || '(none)'}"
|
|
178
181
|
terminal.say "Current iteration: #{block['current_iteration'] || '(none)'}"
|
|
@@ -1162,7 +1165,7 @@ module Space::Architect
|
|
|
1162
1165
|
# global-routing pins — the machine-wide fallback for sessions that
|
|
1163
1166
|
# cannot see a space's .pi dir. Needs install-extensions to have run.
|
|
1164
1167
|
class InstallAgents < BaseCommand
|
|
1165
|
-
desc "Install the role playbooks (architect-
|
|
1168
|
+
desc "Install the role playbooks (architect-spec/builder/judge/narrator) to ~/.pi/agent/agents, rendered from the installed architect-loop extension, and remove a stale architect-coordinator.md"
|
|
1166
1169
|
phase 53, "Project"
|
|
1167
1170
|
option :force, type: :boolean, default: false, desc: "Overwrite existing playbooks that differ"
|
|
1168
1171
|
option :dry_run, type: :boolean, default: false, desc: "Print what would happen without writing files"
|
|
@@ -17,48 +17,47 @@ module Space::Architect
|
|
|
17
17
|
# XDG_CONFIG_HOME); space-local is architect.yaml beside the loaded
|
|
18
18
|
# space.yaml (the CLI already resolved the space — no walk-up).
|
|
19
19
|
module Routing
|
|
20
|
-
ROLES = %w[architect spec builder judge
|
|
21
|
-
# The
|
|
22
|
-
REQUIRED_ROLES = %w[spec builder judge
|
|
20
|
+
ROLES = %w[architect spec builder judge narrator].freeze
|
|
21
|
+
# The four agent roles that must be routed after the merge; `architect` is optional.
|
|
22
|
+
REQUIRED_ROLES = %w[spec builder judge narrator].freeze
|
|
23
23
|
ENTRY_KEYS = %w[model thinking].freeze
|
|
24
24
|
|
|
25
25
|
# I09: the `architect routing init` scaffold — the exact bytes the command
|
|
26
26
|
# writes, living beside the parsers that must accept it. Example models are
|
|
27
27
|
# loud REPLACE_MEs; the header comment carries both locations and the merge
|
|
28
|
-
# order so the file is self-documenting. Byte-exact contract: the
|
|
29
|
-
# lane's tests carry an inline copy, and the command round-trips
|
|
30
|
-
# through parse below — a template the machinery rejects is a bug.
|
|
28
|
+
# order so the file is self-documenting. Byte-exact contract (1021 B): the
|
|
29
|
+
# extension lane's tests carry an inline copy, and the command round-trips
|
|
30
|
+
# the write through parse below — a template the machinery rejects is a bug.
|
|
31
|
+
# I10: the detached driver role retired (the architect session drives the
|
|
32
|
+
# loop itself); the scaffold routes the four required roles plus optional architect.
|
|
31
33
|
TEMPLATE = <<~'YAML'.freeze
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
narrator:
|
|
60
|
-
model: example/REPLACE_ME
|
|
61
|
-
thinking: low
|
|
34
|
+
# architect.yaml — space-architect loop role routing, scaffolded by `architect routing init`.
|
|
35
|
+
# Locations (merged in this order; the space-local entry wins per role):
|
|
36
|
+
# global: ~/.config/space-architect/architect.yaml (machine-wide)
|
|
37
|
+
# local: architect.yaml beside this space's space.yaml (this file)
|
|
38
|
+
# A local entry overrides the global entry for THAT ROLE only; after the
|
|
39
|
+
# merge every agent role must be routed: spec, builder, judge, narrator
|
|
40
|
+
# (architect is optional).
|
|
41
|
+
# Replace every example/REPLACE_ME below with a real provider/model-id
|
|
42
|
+
# (e.g. zai/glm-5.3). thinking is optional: off|minimal|low|medium|high|xhigh|max.
|
|
43
|
+
# Once saved, the next /loop or architect command picks the file up.
|
|
44
|
+
version: 1
|
|
45
|
+
roles:
|
|
46
|
+
architect:
|
|
47
|
+
model: example/REPLACE_ME
|
|
48
|
+
thinking: high
|
|
49
|
+
spec:
|
|
50
|
+
model: example/REPLACE_ME
|
|
51
|
+
thinking: high
|
|
52
|
+
builder:
|
|
53
|
+
model: example/REPLACE_ME
|
|
54
|
+
thinking: high
|
|
55
|
+
judge:
|
|
56
|
+
model: example/REPLACE_ME
|
|
57
|
+
thinking: high
|
|
58
|
+
narrator:
|
|
59
|
+
model: example/REPLACE_ME
|
|
60
|
+
thinking: low
|
|
62
61
|
YAML
|
|
63
62
|
|
|
64
63
|
# The one canonical cure line appended to default_model_inform's last-resort
|
|
@@ -75,14 +74,34 @@ module Space::Architect
|
|
|
75
74
|
Pathname.new(space_dir).join("architect.yaml")
|
|
76
75
|
end
|
|
77
76
|
|
|
78
|
-
# Parse one file's text into { role => {"model" => id, "thinking" => level|nil} }
|
|
79
|
-
# +path+ is used only in error messages. Frozen v1 rules:
|
|
80
|
-
# the
|
|
81
|
-
# duplicate mapping keys rejected (psych's plain load silently
|
|
82
|
-
# last — the npm yaml the extension parses with throws), aliases
|
|
83
|
-
# a trailing :level suffix on model parsed via Harness.parse_model_suffix
|
|
84
|
-
# and beating the thinking key.
|
|
77
|
+
# Parse one file's text into { role => {"model" => id, "thinking" => level|nil} }
|
|
78
|
+
# for the KNOWN roles. +path+ is used only in error messages. Frozen v1 rules:
|
|
79
|
+
# integer version 1, the five known roles, entries ⊆ {model, thinking} with a
|
|
80
|
+
# non-empty model, duplicate mapping keys rejected (psych's plain load silently
|
|
81
|
+
# keeps the last — the npm yaml the extension parses with throws), aliases
|
|
82
|
+
# accepted, a trailing :level suffix on model parsed via Harness.parse_model_suffix
|
|
83
|
+
# and beating the thinking key. Unknown role keys (I10 deprecation tolerance: a
|
|
84
|
+
# config still carrying a retired role key, or a typo) are skipped for
|
|
85
|
+
# routing and collected — see split_known_and_unknown.
|
|
85
86
|
def self.parse(text, path)
|
|
87
|
+
split_known_and_unknown(text, path).first
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# The pure deprecation seam (I10): the unknown role keys under `roles:` in one
|
|
91
|
+
# file's text, in document order. Same parse rules as
|
|
92
|
+
# parse (known-role validation included); only unknown entries are skipped
|
|
93
|
+
# unvalidated, so the inform never fires for a config the parser rejects.
|
|
94
|
+
def self.unknown_role_keys(text, path)
|
|
95
|
+
split_known_and_unknown(text, path).last
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# One parse, two results: [known-role hash, unknown role keys]. An unknown
|
|
99
|
+
# STRING key is skipped whole for routing — its entry content is never
|
|
100
|
+
# validated (an ignored key is ignored whole; garbage under
|
|
101
|
+
# roles.<retired-role>.thinking must not raise) — and collected for the one
|
|
102
|
+
# deprecation inform. A non-string key is corruption, not a retired role
|
|
103
|
+
# name, and still fails loud under the known-role error.
|
|
104
|
+
def self.split_known_and_unknown(text, path)
|
|
86
105
|
assert_unique_keys!(text, path)
|
|
87
106
|
doc = YAML.safe_load(text, aliases: true)
|
|
88
107
|
unless doc.is_a?(Hash)
|
|
@@ -98,8 +117,14 @@ module Space::Architect
|
|
|
98
117
|
end
|
|
99
118
|
|
|
100
119
|
parsed = {}
|
|
120
|
+
unknown = []
|
|
101
121
|
roles.each do |role, entry|
|
|
102
|
-
|
|
122
|
+
if role.is_a?(String)
|
|
123
|
+
unless ROLES.include?(role)
|
|
124
|
+
unknown << role # an ignored key is ignored whole: skip for routing, no entry validation
|
|
125
|
+
next
|
|
126
|
+
end
|
|
127
|
+
else
|
|
103
128
|
raise Space::Core::Error,
|
|
104
129
|
"architect.yaml (#{path}): roles.#{role} — expected one of #{ROLES.join('|')}, got #{role.inspect}"
|
|
105
130
|
end
|
|
@@ -136,24 +161,48 @@ module Space::Architect
|
|
|
136
161
|
end
|
|
137
162
|
parsed[role] = { "model" => model, "thinking" => suffix_level || thinking }
|
|
138
163
|
end
|
|
139
|
-
parsed
|
|
164
|
+
[parsed, unknown]
|
|
140
165
|
rescue Psych::Exception => e
|
|
141
166
|
raise Space::Core::Error, "architect.yaml (#{path}): malformed YAML — #{e.message}"
|
|
142
167
|
end
|
|
143
168
|
|
|
169
|
+
# The one deprecation inform for unknown role keys (I10): names every
|
|
170
|
+
# roles.<key> found, the known-role set, and that the entries are ignored.
|
|
171
|
+
# Fired at most once per PROCESS via the class-level flag — worktree_add and
|
|
172
|
+
# dispatch each resolve routing twice per invocation, so per-call emission
|
|
173
|
+
# would double-print. A config with no unknown key never composes the
|
|
174
|
+
# message, so the memo never fires. Tests reset it with
|
|
175
|
+
# remove_instance_variable(:@deprecation_informed).
|
|
176
|
+
def self.inform_deprecated_roles!(err, keys, found)
|
|
177
|
+
return if keys.empty? || @deprecation_informed
|
|
178
|
+
|
|
179
|
+
@deprecation_informed = true
|
|
180
|
+
err.puts "architect.yaml (#{found.join(' + ')}): ignoring unknown role key(s) " \
|
|
181
|
+
"#{keys.map { |k| "roles.#{k}" }.join(', ')} — known roles: #{ROLES.join(', ')} (each entry is ignored)"
|
|
182
|
+
end
|
|
183
|
+
|
|
144
184
|
# The merged routing for a space: [global, space-local] merged per role key
|
|
145
185
|
# (a space file may override only builder and inherit the rest). nil when
|
|
146
186
|
# NEITHER file exists — no config is a normal state: no error, no side
|
|
147
187
|
# effects. Any found file that is malformed/invalid fails loud at parse;
|
|
148
|
-
# after the merge all four agent roles must be routed.
|
|
149
|
-
|
|
188
|
+
# after the merge all four agent roles must be routed. +err+ is additive
|
|
189
|
+
# (I10): when given, a merged config carrying unknown role keys prints the
|
|
190
|
+
# one per-process deprecation inform on it; existing callers (no err)
|
|
191
|
+
# resolve silently as before.
|
|
192
|
+
def self.for_space(space_dir, err: nil)
|
|
150
193
|
global = global_path
|
|
151
194
|
local = space_path(space_dir)
|
|
152
195
|
found = [global, local].select { |p| File.file?(p.to_s) }
|
|
153
196
|
return nil if found.empty?
|
|
154
197
|
|
|
155
198
|
merged = {}
|
|
156
|
-
|
|
199
|
+
unknown = []
|
|
200
|
+
found.each do |p|
|
|
201
|
+
known, keys = split_known_and_unknown(File.read(p.to_s), p)
|
|
202
|
+
merged.merge!(known)
|
|
203
|
+
unknown |= keys
|
|
204
|
+
end
|
|
205
|
+
inform_deprecated_roles!(err, unknown, found) if err
|
|
157
206
|
missing = REQUIRED_ROLES - merged.keys
|
|
158
207
|
if missing.any?
|
|
159
208
|
raise Space::Core::Error,
|
data/skill/architect/SKILL.md
CHANGED
|
@@ -72,13 +72,14 @@ the space durable across sessions:
|
|
|
72
72
|
- **architect** — the judgment seat (this session in either mode: inline
|
|
73
73
|
here, judgment in `/architect-discuss`): plans, arbitrates, rules on
|
|
74
74
|
escalations, judges, lands. In inline mode it also does the work directly.
|
|
75
|
-
- **
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
- **driver** — the architect session itself, in driver mode (the /loop
|
|
76
|
+
tool installs the driver playbook as operating instructions): spawns and
|
|
77
|
+
tends spec → builders → judge per iteration; rules escalations in-seat;
|
|
78
|
+
never does lane work.
|
|
78
79
|
- **spec** — writes one iteration's Grounds/Specification/Acceptance
|
|
79
80
|
Criteria; rehearses; freezes; writes the `notes/spec-digest-*` handoff.
|
|
80
81
|
- **builder** — implementation in one lane's worktree; reports raw evidence
|
|
81
|
-
only; never commits. Spawned by the
|
|
82
|
+
only; never commits. Spawned by the driver as a subagent, or run via
|
|
82
83
|
the `architect dispatch` CLI (which streams to
|
|
83
84
|
`build/<id>-<lane>/run.jsonl`).
|
|
84
85
|
- **judge** — a fresh session that runs the cold gates and rules per-AC
|
|
@@ -146,13 +147,14 @@ Read-only orientation commands: `architect status`, `architect looplog
|
|
|
146
147
|
|
|
147
148
|
- **`/architect-discuss`** — engineering exploration and arbitration,
|
|
148
149
|
judging a finished run, landing PRs, iteration surgery.
|
|
149
|
-
- **`/architect-action`** — write the plan doc and
|
|
150
|
+
- **`/architect-action`** — write the plan doc and drive the loop yourself
|
|
151
|
+
as the driver.
|
|
150
152
|
- **`/architect-status`** — "what are we working on in here? is it done?"
|
|
151
153
|
- **`/architect-research`** — discovery-scale research.
|
|
152
154
|
|
|
153
155
|
## Maintenance
|
|
154
156
|
|
|
155
157
|
This skill carries the shared vocabulary inline. When loop mechanics change
|
|
156
|
-
(the
|
|
158
|
+
(the driver flow, dispatch, routing), re-read this file against the
|
|
157
159
|
`architect-discuss` skill and the gem README and update the glossary —
|
|
158
160
|
drift between the two is silent confusion for every future session.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: architect-action
|
|
3
3
|
description: >
|
|
4
|
-
Take over when the discussion is done: write the plan doc and
|
|
5
|
-
|
|
4
|
+
Take over when the discussion is done: write the plan doc and drive it
|
|
5
|
+
as the driver yourself, end to end. Use when the user's intention is to
|
|
6
6
|
START a loop — planning iterations, writing notes/plan-*.md, pinning role
|
|
7
|
-
routing,
|
|
7
|
+
routing, and driving the loop. For engineering
|
|
8
8
|
discussion or judgment work, /architect-discuss. For working inline
|
|
9
9
|
outside the loop, /architect.
|
|
10
10
|
---
|
|
@@ -13,8 +13,8 @@ description: >
|
|
|
13
13
|
|
|
14
14
|
You are the ARCHITECT taking action: the discussion is over, the human has
|
|
15
15
|
disengaged, and the deliverable of this skill is exactly one thing — a
|
|
16
|
-
committed plan doc and a launched
|
|
17
|
-
(spec → builders → judge, driven by the
|
|
16
|
+
committed plan doc and a launched loop that you drive yourself. The loop's
|
|
17
|
+
roles (spec → builders → judge, driven by you as the driver) do everything after
|
|
18
18
|
that.
|
|
19
19
|
|
|
20
20
|
Read `/architect-discuss` first for the loop's roles, routing, and hard
|
|
@@ -38,7 +38,7 @@ human again until the loop needs them.
|
|
|
38
38
|
config — the plan's acceptance direction must name it.
|
|
39
39
|
2. **Scale the work.** Trivial fixes don't need the loop — say so and do it
|
|
40
40
|
inline (`/architect`). One PR-sized iteration = one ENTRY. Multi-iteration
|
|
41
|
-
plans get ordered ENTRYs (the
|
|
41
|
+
plans get ordered ENTRYs (the driver works them in order, skipping
|
|
42
42
|
finished ones). If the open question is too uncertain to build, make the
|
|
43
43
|
ENTRY a spike (investigate-only; deliverable is a recommendation — see
|
|
44
44
|
`/architect-discuss`).
|
|
@@ -57,7 +57,7 @@ human again until the loop needs them.
|
|
|
57
57
|
|
|
58
58
|
**Objective.** What to build and why — the reason, not just the ask. Cite
|
|
59
59
|
BRIEF §N. If this ENTRY pins model/thinking for a role, say it here (the
|
|
60
|
-
|
|
60
|
+
driver passes it in that spawn's model field).
|
|
61
61
|
|
|
62
62
|
**Grounds to read.** Exact paths: research/bug reports, source files being
|
|
63
63
|
ported, seam files, prior verdicts. The spec reads these before writing
|
|
@@ -77,7 +77,7 @@ human again until the loop needs them.
|
|
|
77
77
|
constraints or grounds.
|
|
78
78
|
4. **Pin routing if needed.** Models come from `architect.yaml` (space root
|
|
79
79
|
overrides `~/.config/space-architect/architect.yaml` per role). A
|
|
80
|
-
one-plan pin goes in the plan header/ENTRY (the
|
|
80
|
+
one-plan pin goes in the plan header/ENTRY (the driver passes it in
|
|
81
81
|
that spawn's model field); a standing change edits `architect.yaml`. The
|
|
82
82
|
extension writes the per-space `.pi/agents/` role files from it at
|
|
83
83
|
session start, and every spawn still carries its pin explicitly — nothing
|
|
@@ -88,26 +88,20 @@ human again until the loop needs them.
|
|
|
88
88
|
6. **Launch it with the `architect_loop` tool.** You cannot run `/loop` — it is
|
|
89
89
|
a user command in the interactive session, and the human has disengaged.
|
|
90
90
|
The launch is yours, and the tool is your equivalent of `/loop`: call
|
|
91
|
-
`architect_loop` with `from` = the plan file path.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
`--list` (inventory), `architect status`, `architect cost`. Then: this
|
|
106
|
-
session is the escalation seat — the human may keep it open, close it, or
|
|
107
|
-
compact it further whenever they like; a coordinator with no listener parks
|
|
108
|
-
questions in `notes/escalation-<ts>.md` and stops via the exception path
|
|
109
|
-
rather than improvising. When you do get an escalation: rule on it; the
|
|
110
|
-
coordinator steers your ruling to the child unchanged.
|
|
91
|
+
`architect_loop` with `from` = the plan file path. The tool installs the
|
|
92
|
+
driver playbook into THIS session as operating instructions and schedules
|
|
93
|
+
the entry compaction — after which this session IS the loop driver: it
|
|
94
|
+
spawns and tends spec → builders → judge itself, one hop from the human.
|
|
95
|
+
Pass `compact: false` only if the human asks to keep full context. An
|
|
96
|
+
older extension without the driver-mode `architect_loop` is updated via
|
|
97
|
+
`architect install-extensions` — never spawned around.
|
|
98
|
+
7. **Report the launch, then drive.** Tell the human: the plan path and how
|
|
99
|
+
to watch — `architect looplog` (live), `--list` (inventory), `architect
|
|
100
|
+
status`, `architect cost`. Then this session is the on-call seat itself:
|
|
101
|
+
escalations from blocked children land here (one hop — you rule in-seat,
|
|
102
|
+
and the ruling goes to the child unchanged). A driver with no live human
|
|
103
|
+
parks the question in `notes/escalation-<ts>.md` and stops via the
|
|
104
|
+
exception path rather than improvising.
|
|
111
105
|
|
|
112
106
|
## Failure modes to check before launching
|
|
113
107
|
|
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
The judgment seat: engineering discussion and exploration of a problem,
|
|
5
5
|
arbitration of disagreements and escalations, judging finished loop runs,
|
|
6
6
|
landing PRs, and iteration surgery (hand-running iteration steps when the
|
|
7
|
-
human asks
|
|
7
|
+
human asks). Your output is judgment and
|
|
8
8
|
dispatch — never implementation code. Use /architect for working inline
|
|
9
9
|
outside the loop, /architect-action to START a loop, /architect-status for
|
|
10
10
|
a state summary.
|
|
@@ -19,8 +19,7 @@ space. Two postures, one set of hard rules:
|
|
|
19
19
|
disagreements, rulings on escalations, taste. You stay in the loop's
|
|
20
20
|
context without driving it.
|
|
21
21
|
- **Hands-on loop work.** Judging finished runs, landing PRs, and running
|
|
22
|
-
iteration steps by hand when the human asks for a hand-run
|
|
23
|
-
coordinator is down.
|
|
22
|
+
iteration steps by hand when the human asks for a hand-run.
|
|
24
23
|
|
|
25
24
|
Your output is judgment and dispatch — never implementation code. Anything
|
|
26
25
|
that must change goes in a plan entry or an iteration Specification. (When
|
|
@@ -53,7 +52,7 @@ project — loads them.
|
|
|
53
52
|
| Role | Session | Owns |
|
|
54
53
|
|---|---|---|
|
|
55
54
|
| **architect** | your interactive session | plans, arbitration, escalation rulings, landing, taste |
|
|
56
|
-
| **
|
|
55
|
+
| **driver** | this session in driver mode (installed by /loop) | drives the plan: spawns/tends spec → provision → builders → judge per iteration; rules escalations in-seat; never does lane work |
|
|
57
56
|
| **spec** | subagent | one iteration's Grounds/Specification/Acceptance Criteria; rehearses; freezes; writes the `notes/spec-digest-*` handoff |
|
|
58
57
|
| **builder** | subagent per lane | implementation in the lane worktree; raw-results report; never commits |
|
|
59
58
|
| **judge** | subagent, fresh session | mechanical post-flight, cold gates, per-AC verdicts, PHASE 0 rulings, integration |
|
|
@@ -71,16 +70,17 @@ architect cost # spend + latency by role/iteration
|
|
|
71
70
|
|
|
72
71
|
## Escalations and exceptions land on you
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
loop that needs you and can't reach you does not
|
|
80
|
-
facts and terminates. Exception reports
|
|
81
|
-
completion reports (`notes/report-<ts>.md`)
|
|
82
|
-
explains itself; narrations
|
|
83
|
-
in between. Read them
|
|
73
|
+
In driver mode a blocked child's escalation lands directly in this session —
|
|
74
|
+
one hop: you ARE the child's supervisor. Rule in-seat and steer the ruling
|
|
75
|
+
to the child **unchanged** (never re-authored — a re-authored
|
|
76
|
+
escalation suppresses your independent read). A driver with no live human
|
|
77
|
+
and no session left writes `notes/escalation-<ts>.md` and stops via the
|
|
78
|
+
exception path — a loop that needs you and can't reach you does not
|
|
79
|
+
improvise; it parks the facts and terminates. Exception reports
|
|
80
|
+
(`notes/exception-<ts>.md`) and completion reports (`notes/report-<ts>.md`)
|
|
81
|
+
are how a dead or finished loop explains itself; narrations
|
|
82
|
+
(`notes/narrative-*.md`) are best-effort updates in between. Read them
|
|
83
|
+
before resuming anything.
|
|
84
84
|
|
|
85
85
|
## Ground rules
|
|
86
86
|
|
|
@@ -150,7 +150,7 @@ push, no `gh` call by you; the human runs the block when ready.
|
|
|
150
150
|
## Iteration surgery (interactive path)
|
|
151
151
|
|
|
152
152
|
Run the iteration steps in your own session when the human asks for a
|
|
153
|
-
hand-run
|
|
153
|
+
hand-run or wants to babysit. Ground first, then
|
|
154
154
|
per iteration: arbitrate open disagreements from the last Builder Report;
|
|
155
155
|
**spec the next iteration** (`architect new <name>`, then Specification via
|
|
156
156
|
`architect section`); **write the Acceptance Criteria** (prose ACs + fenced
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Builder dispatch reference
|
|
2
2
|
|
|
3
|
-
## The
|
|
3
|
+
## The driver path (default) — how builders actually launch
|
|
4
4
|
|
|
5
|
-
When a plan runs under the
|
|
5
|
+
When a plan runs under the driver (see the /architect-action skill —
|
|
6
6
|
launching is that skill's job), the
|
|
7
|
-
|
|
7
|
+
driver itself spawns each role as a **pi subagent** — native async
|
|
8
8
|
completion wakes, not `architect dispatch` headless runs. Per iteration:
|
|
9
9
|
|
|
10
10
|
1. **spec** — agent `architect-spec`, cwd = the space root, task = the plan
|
|
@@ -12,11 +12,11 @@ completion wakes, not `architect dispatch` headless runs. Per iteration:
|
|
|
12
12
|
`architect section`, rehearses, freezes, assembles each lane's prompt in a
|
|
13
13
|
scratch file, records the scratch paths in its report, and writes the
|
|
14
14
|
`notes/spec-digest-*` handoff. `timeoutMs` 3600000 (1h).
|
|
15
|
-
2. **provision** — the
|
|
15
|
+
2. **provision** — the driver runs `architect provision <iteration>` via
|
|
16
16
|
bash; the CLI materializes every declared lane's worktree + lane branch.
|
|
17
|
-
3. **prompt copy** — the
|
|
17
|
+
3. **prompt copy** — the driver copies each spec-assembled scratch prompt
|
|
18
18
|
to `build/<id>-<lane>/prompt.md` (canonical path, CLI-owned in the
|
|
19
|
-
interactive path; the
|
|
19
|
+
interactive path; the driver's `cp` is the sanctioned equivalent).
|
|
20
20
|
4. **builder per lane** — agent `architect-builder`, cwd = that lane's
|
|
21
21
|
worktree, task = read `build/<id>-<lane>/prompt.md` before anything else.
|
|
22
22
|
`timeoutMs` 7200000 (2h). The lane-prompt template below is the contract
|
|
@@ -39,13 +39,13 @@ start, crashed before work) gets one relaunch. Anything unrootable is an
|
|
|
39
39
|
exception: `notes/exception-<ts>.md`, narrate, terminate — no improvised
|
|
40
40
|
repairs.
|
|
41
41
|
|
|
42
|
-
The
|
|
42
|
+
The driver never edits files except its own `notes/` reports and never
|
|
43
43
|
writes into `architecture/`.
|
|
44
44
|
|
|
45
45
|
`architect dispatch` (everything below) remains the **interactive-path**
|
|
46
46
|
mechanism — and the reference implementation of the lane contract the
|
|
47
|
-
|
|
48
|
-
discipline — interactive dispatch injects the guard via `-e`; a
|
|
47
|
+
driver's spawns inherit (same prompt template, same builder guard
|
|
48
|
+
discipline — interactive dispatch injects the guard via `-e`; a driver
|
|
49
49
|
spawn relies on the playbook's no-git-writes rule plus the judge's post-flight
|
|
50
50
|
`git log` check, which stays the authoritative proof in both paths).
|
|
51
51
|
|
|
@@ -82,7 +82,7 @@ If a lane committed, treat the worktree as tampered: reset and re-dispatch.
|
|
|
82
82
|
builder model resolves with a one-message probe
|
|
83
83
|
(`echo ok | pi -p --model <builder-model>`), confirm the role playbooks are
|
|
84
84
|
installed (`architect install-agents` — once per machine, idempotent), and
|
|
85
|
-
for a
|
|
85
|
+
for a driver run confirm the routing config resolves — an
|
|
86
86
|
`architect.yaml` role pinning a model the registry doesn't know throws loudly
|
|
87
87
|
at /loop pre-flight, before any spawn (/loop pre-flight does this for you).
|
|
88
88
|
No API-key/env ceremony — provider
|
|
@@ -521,14 +521,6 @@ Paste the lane-prompt into an interactive `pi` session (no `-p`). pi's agent
|
|
|
521
521
|
loop runs plan→act→test against the block's stopping condition while you watch
|
|
522
522
|
and steer. Use when the human wants to babysit a run.
|
|
523
523
|
|
|
524
|
-
A coordinator can be launched the same way when /loop is unavailable: spawn
|
|
525
|
-
the agent `architect-coordinator` (role agents resolve from
|
|
526
|
-
`~/.pi/agent/agents/`, user-level, installed by `architect install-agents` —
|
|
527
|
-
no project trust involved) with cwd = the space root, the plan text verbatim
|
|
528
|
-
as the task, the coordinator's model pin from `architect.yaml` in the spawn's
|
|
529
|
-
model field, and `timeoutMs` 43200000 (12h) — the exact contract
|
|
530
|
-
`coordinatorSpawnScript` in architect-loop.ts assembles.
|
|
531
|
-
|
|
532
524
|
## Lane-prompt template
|
|
533
525
|
|
|
534
526
|
```
|