|
30 | 30 | cannot load context-intelligence-tool-design / eval-design skills |
31 | 31 | """ |
32 | 32 |
|
33 | | -import sys |
34 | 33 | import yaml |
35 | 34 | from pathlib import Path |
36 | 35 |
|
37 | 36 | PASS = "\033[32mPASS\033[0m" |
38 | 37 | FAIL = "\033[31mFAIL\033[0m" |
39 | | -failures = [] |
40 | 38 |
|
| 39 | +# Resolve the bundle root relative to this test file so the checks work in a |
| 40 | +# normal checkout, in CI, and inside a DTU container alike. The previous |
| 41 | +# implementation hardcoded an absolute, root-owned cache path |
| 42 | +# (/root/.amplifier/cache/...), which broke pytest collection on every other |
| 43 | +# machine and froze a specific cache hash. The agent/mode files under test are |
| 44 | +# shipped in this repo, so the repo root is the correct, portable source. |
| 45 | +BUNDLE_ROOT = Path(__file__).resolve().parents[2] |
41 | 46 |
|
42 | | -def check(label, condition, detail=""): |
43 | | - if condition: |
44 | | - print(f" {PASS} {label}") |
45 | | - else: |
46 | | - print(f" {FAIL} {label}" + (f"\n detail: {detail}" if detail else "")) |
47 | | - failures.append(label) |
48 | 47 |
|
| 48 | +def _load_frontmatter(rel_path): |
| 49 | + text = (BUNDLE_ROOT / rel_path).read_text() |
| 50 | + parts = text.split("---", 2) |
| 51 | + return yaml.safe_load(parts[1]), parts[2] |
49 | 52 |
|
50 | | -cache = Path("/root/.amplifier/cache/amplifier-bundle-context-intelligence-ecd41f3e6fa67bd2") |
51 | 53 |
|
| 54 | +def test_tool_delegate_composition(): |
| 55 | + failures = [] |
52 | 56 |
|
53 | | -def load_frontmatter(rel_path): |
54 | | - text = (cache / rel_path).read_text() |
55 | | - parts = text.split("---", 2) |
56 | | - return yaml.safe_load(parts[1]), parts[2] |
| 57 | + def check(label, condition, detail=""): |
| 58 | + if condition: |
| 59 | + print(f" {PASS} {label}") |
| 60 | + else: |
| 61 | + print(f" {FAIL} {label}" + (f"\n detail: {detail}" if detail else "")) |
| 62 | + failures.append(label) |
| 63 | + |
| 64 | + facilitator_fm, _ = _load_frontmatter("agents/context-intelligence-design-facilitator.md") |
| 65 | + designer_fm, _ = _load_frontmatter("agents/context-intelligence-tool-designer.md") |
| 66 | + mode_fm, _ = _load_frontmatter("modes/context-intelligence.md") |
| 67 | + |
| 68 | + fac_tools = {t["module"]: t for t in facilitator_fm.get("tools", [])} |
| 69 | + des_tools = {t["module"]: t for t in designer_fm.get("tools", [])} |
| 70 | + mode_safe = mode_fm.get("mode", {}).get("tools", {}).get("safe", []) |
| 71 | + |
| 72 | + print("\n── Group D: Tool composition — Brian's operational note ──────────────────\n") |
| 73 | + |
| 74 | + # D1/D2: Explicit tool-delegate declaration |
| 75 | + check( |
| 76 | + "D1 Facilitator explicitly declares tool-delegate in tools:", |
| 77 | + "tool-delegate" in fac_tools, |
| 78 | + f"declared tools: {list(fac_tools.keys())}", |
| 79 | + ) |
| 80 | + check( |
| 81 | + "D2 Tool-designer explicitly declares tool-delegate in tools:", |
| 82 | + "tool-delegate" in des_tools, |
| 83 | + f"declared tools: {list(des_tools.keys())}", |
| 84 | + ) |
| 85 | + |
| 86 | + # D3: Mode has 'delegate' in safe list |
| 87 | + check("D3 Mode tools.safe contains 'delegate' (root-session policy)", "delegate" in mode_safe) |
| 88 | + |
| 89 | + # D4: Mode tool policies are NOT in agent frontmatters |
| 90 | + # (Agents must be self-sufficient; they cannot rely on mode policy propagation) |
| 91 | + fac_safe = facilitator_fm.get("mode", {}).get("tools", {}).get("safe", []) |
| 92 | + des_safe = designer_fm.get("mode", {}).get("tools", {}).get("safe", []) |
| 93 | + check( |
| 94 | + "D4 Facilitator frontmatter has NO mode.tools.safe block (not relying on mode policy)", |
| 95 | + not fac_safe, |
| 96 | + f"fac mode.tools.safe={fac_safe}", |
| 97 | + ) |
| 98 | + check( |
| 99 | + "D4b Tool-designer frontmatter has NO mode.tools.safe block", |
| 100 | + not des_safe, |
| 101 | + f"des mode.tools.safe={des_safe}", |
| 102 | + ) |
| 103 | + |
| 104 | + # D5: Both declare tool-skills (not inherited from parent session) |
| 105 | + check( |
| 106 | + "D5 Facilitator explicitly declares tool-skills in tools:", |
| 107 | + "tool-skills" in fac_tools, |
| 108 | + f"declared tools: {list(fac_tools.keys())}", |
| 109 | + ) |
| 110 | + check( |
| 111 | + "D5b Tool-designer explicitly declares tool-skills in tools:", |
| 112 | + "tool-skills" in des_tools, |
| 113 | + f"declared tools: {list(des_tools.keys())}", |
| 114 | + ) |
| 115 | + |
| 116 | + # D6: tool-delegate source points to amplifier-foundation (correct) |
| 117 | + fac_delegate_src = fac_tools.get("tool-delegate", {}).get("source", "") |
| 118 | + des_delegate_src = des_tools.get("tool-delegate", {}).get("source", "") |
| 119 | + foundation_domain = "amplifier-foundation" |
| 120 | + check( |
| 121 | + "D6a Facilitator's tool-delegate source is amplifier-foundation", |
| 122 | + foundation_domain in fac_delegate_src, |
| 123 | + f"source={fac_delegate_src}", |
| 124 | + ) |
| 125 | + check( |
| 126 | + "D6b Tool-designer's tool-delegate source is amplifier-foundation", |
| 127 | + foundation_domain in des_delegate_src, |
| 128 | + f"source={des_delegate_src}", |
| 129 | + ) |
| 130 | + |
| 131 | + # D7: tool-skills source points to amplifier-bundle-skills (correct) |
| 132 | + fac_skills_src = fac_tools.get("tool-skills", {}).get("source", "") |
| 133 | + des_skills_src = des_tools.get("tool-skills", {}).get("source", "") |
| 134 | + skills_domain = "amplifier-bundle-skills" |
| 135 | + check( |
| 136 | + "D7a Facilitator's tool-skills source is amplifier-bundle-skills", |
| 137 | + skills_domain in fac_skills_src, |
| 138 | + f"source={fac_skills_src}", |
| 139 | + ) |
| 140 | + check( |
| 141 | + "D7b Tool-designer's tool-skills source is amplifier-bundle-skills", |
| 142 | + skills_domain in des_skills_src, |
| 143 | + f"source={des_skills_src}", |
| 144 | + ) |
| 145 | + |
| 146 | + # D8: tool-skills config.skills includes CI bundle skills subtree |
| 147 | + fac_skills_cfg = fac_tools.get("tool-skills", {}).get("config", {}).get("skills", []) |
| 148 | + des_skills_cfg = des_tools.get("tool-skills", {}).get("config", {}).get("skills", []) |
| 149 | + ci_bundle = "amplifier-bundle-context-intelligence" |
| 150 | + |
| 151 | + check( |
| 152 | + "D8a Facilitator's tool-skills config points to CI bundle skills", |
| 153 | + any(ci_bundle in s for s in fac_skills_cfg), |
| 154 | + f"config.skills={fac_skills_cfg}", |
| 155 | + ) |
| 156 | + check( |
| 157 | + "D8b Tool-designer's tool-skills config points to CI bundle skills", |
| 158 | + any(ci_bundle in s for s in des_skills_cfg), |
| 159 | + f"config.skills={des_skills_cfg}", |
| 160 | + ) |
57 | 161 |
|
| 162 | + # D9: Phase-transition guard — what would break if tool-delegate were absent |
| 163 | + # Simulate: agent with tool-delegate removed from tools: — would it matter? |
| 164 | + # The test proves this is a sub-session concern, not inheritable from mode. |
| 165 | + class FakeTool: |
| 166 | + def __init__(self, name): |
| 167 | + self.name = name |
58 | 168 |
|
59 | | -facilitator_fm, _ = load_frontmatter("agents/context-intelligence-design-facilitator.md") |
60 | | -designer_fm, _ = load_frontmatter("agents/context-intelligence-tool-designer.md") |
61 | | -mode_fm, _ = load_frontmatter("modes/context-intelligence.md") |
62 | | - |
63 | | -fac_tools = {t["module"]: t for t in facilitator_fm.get("tools", [])} |
64 | | -des_tools = {t["module"]: t for t in designer_fm.get("tools", [])} |
65 | | -mode_safe = mode_fm.get("mode", {}).get("tools", {}).get("safe", []) |
66 | | - |
67 | | -print("\n── Group D: Tool composition — Brian's operational note ────────────────────\n") |
68 | | - |
69 | | -# D1/D2: Explicit tool-delegate declaration |
70 | | -check( |
71 | | - "D1 Facilitator explicitly declares tool-delegate in tools:", |
72 | | - "tool-delegate" in fac_tools, |
73 | | - f"declared tools: {list(fac_tools.keys())}", |
74 | | -) |
75 | | -check( |
76 | | - "D2 Tool-designer explicitly declares tool-delegate in tools:", |
77 | | - "tool-delegate" in des_tools, |
78 | | - f"declared tools: {list(des_tools.keys())}", |
79 | | -) |
80 | | - |
81 | | -# D3: Mode has 'delegate' in safe list |
82 | | -check("D3 Mode tools.safe contains 'delegate' (root-session policy)", "delegate" in mode_safe) |
83 | | - |
84 | | -# D4: Mode tool policies are NOT in agent frontmatters |
85 | | -# (Agents must be self-sufficient; they cannot rely on mode policy propagation) |
86 | | -fac_safe = facilitator_fm.get("mode", {}).get("tools", {}).get("safe", []) |
87 | | -des_safe = designer_fm.get("mode", {}).get("tools", {}).get("safe", []) |
88 | | -check( |
89 | | - "D4 Facilitator frontmatter has NO mode.tools.safe block (not relying on mode policy)", |
90 | | - not fac_safe, |
91 | | - f"fac mode.tools.safe={fac_safe}", |
92 | | -) |
93 | | -check( |
94 | | - "D4b Tool-designer frontmatter has NO mode.tools.safe block", |
95 | | - not des_safe, |
96 | | - f"des mode.tools.safe={des_safe}", |
97 | | -) |
98 | | - |
99 | | -# D5: Both declare tool-skills (not inherited from parent session) |
100 | | -check( |
101 | | - "D5 Facilitator explicitly declares tool-skills in tools:", |
102 | | - "tool-skills" in fac_tools, |
103 | | - f"declared tools: {list(fac_tools.keys())}", |
104 | | -) |
105 | | -check( |
106 | | - "D5b Tool-designer explicitly declares tool-skills in tools:", |
107 | | - "tool-skills" in des_tools, |
108 | | - f"declared tools: {list(des_tools.keys())}", |
109 | | -) |
110 | | - |
111 | | -# D6: tool-delegate source points to amplifier-foundation (correct) |
112 | | -fac_delegate_src = fac_tools.get("tool-delegate", {}).get("source", "") |
113 | | -des_delegate_src = des_tools.get("tool-delegate", {}).get("source", "") |
114 | | -foundation_domain = "amplifier-foundation" |
115 | | -check( |
116 | | - "D6a Facilitator's tool-delegate source is amplifier-foundation", |
117 | | - foundation_domain in fac_delegate_src, |
118 | | - f"source={fac_delegate_src}", |
119 | | -) |
120 | | -check( |
121 | | - "D6b Tool-designer's tool-delegate source is amplifier-foundation", |
122 | | - foundation_domain in des_delegate_src, |
123 | | - f"source={des_delegate_src}", |
124 | | -) |
125 | | - |
126 | | -# D7: tool-skills source points to amplifier-bundle-skills (correct) |
127 | | -fac_skills_src = fac_tools.get("tool-skills", {}).get("source", "") |
128 | | -des_skills_src = des_tools.get("tool-skills", {}).get("source", "") |
129 | | -skills_domain = "amplifier-bundle-skills" |
130 | | -check( |
131 | | - "D7a Facilitator's tool-skills source is amplifier-bundle-skills", |
132 | | - skills_domain in fac_skills_src, |
133 | | - f"source={fac_skills_src}", |
134 | | -) |
135 | | -check( |
136 | | - "D7b Tool-designer's tool-skills source is amplifier-bundle-skills", |
137 | | - skills_domain in des_skills_src, |
138 | | - f"source={des_skills_src}", |
139 | | -) |
140 | | - |
141 | | -# D8: tool-skills config.skills includes CI bundle skills subtree |
142 | | -fac_skills_cfg = fac_tools.get("tool-skills", {}).get("config", {}).get("skills", []) |
143 | | -des_skills_cfg = des_tools.get("tool-skills", {}).get("config", {}).get("skills", []) |
144 | | -ci_bundle = "amplifier-bundle-context-intelligence" |
145 | | - |
146 | | -check( |
147 | | - "D8a Facilitator's tool-skills config points to CI bundle skills", |
148 | | - any(ci_bundle in s for s in fac_skills_cfg), |
149 | | - f"config.skills={fac_skills_cfg}", |
150 | | -) |
151 | | -check( |
152 | | - "D8b Tool-designer's tool-skills config points to CI bundle skills", |
153 | | - any(ci_bundle in s for s in des_skills_cfg), |
154 | | - f"config.skills={des_skills_cfg}", |
155 | | -) |
156 | | - |
157 | | - |
158 | | -# D9: Phase-transition guard — what would break if tool-delegate were absent |
159 | | -# Simulate: agent with tool-delegate removed from tools: — would it matter? |
160 | | -# The test proves this is a sub-session concern, not inheritable from mode. |
161 | | -class FakeTool: |
162 | | - def __init__(self, name): |
163 | | - self.name = name |
164 | | - |
165 | | - |
166 | | -class FakeCoordinator: |
167 | | - def __init__(self, tools): |
168 | | - self.tools = tools |
169 | | - |
170 | | - def get(self, key): |
171 | | - return {t.name: t for t in self.tools}.get(key) |
172 | | - |
173 | | - |
174 | | -# Sub-session with tool-delegate: delegate call is possible |
175 | | -coord_with_delegate = FakeCoordinator([FakeTool("tool-delegate"), FakeTool("tool-skills")]) |
176 | | -coord_without_delegate = FakeCoordinator([FakeTool("tool-skills")]) |
177 | | - |
178 | | -can_delegate_with = coord_with_delegate.get("tool-delegate") is not None |
179 | | -can_delegate_without = coord_without_delegate.get("tool-delegate") is not None |
180 | | - |
181 | | -check("D9 Sub-session WITH tool-delegate: can call delegate()", can_delegate_with) |
182 | | -check( |
183 | | - "D9b Sub-session WITHOUT tool-delegate: CANNOT call delegate() — proves explicit declaration is required", |
184 | | - not can_delegate_without, |
185 | | - "this proves Brian's note: the fix makes B reachable but delegate tool must be declared", |
186 | | -) |
187 | | - |
188 | | -# Summary |
189 | | -total = 14 |
190 | | -passed = total - len(failures) |
191 | | -print(f"\n══ Group D Results: {passed}/{total} passed ══\n") |
192 | | -if failures: |
193 | | - print("Failed:") |
194 | | - for f in failures: |
195 | | - print(f" ✗ {f}") |
196 | | - sys.exit(1) |
197 | | -else: |
198 | | - print( |
199 | | - "Both agents satisfy Brian's operational note:\n" |
200 | | - " • tool-delegate declared explicitly in each agent's own tools:\n" |
201 | | - " • Neither agent relies on mode tool-policy inheritance\n" |
202 | | - " • Phase 1→2 handoff (facilitator→tool-designer via delegate) is safe\n" |
203 | | - " • Phase 2 per-signal delegation (tool-designer→self) is safe\n" |
| 169 | + class FakeCoordinator: |
| 170 | + def __init__(self, tools): |
| 171 | + self.tools = tools |
| 172 | + |
| 173 | + def get(self, key): |
| 174 | + return {t.name: t for t in self.tools}.get(key) |
| 175 | + |
| 176 | + # Sub-session with tool-delegate: delegate call is possible |
| 177 | + coord_with_delegate = FakeCoordinator([FakeTool("tool-delegate"), FakeTool("tool-skills")]) |
| 178 | + coord_without_delegate = FakeCoordinator([FakeTool("tool-skills")]) |
| 179 | + |
| 180 | + can_delegate_with = coord_with_delegate.get("tool-delegate") is not None |
| 181 | + can_delegate_without = coord_without_delegate.get("tool-delegate") is not None |
| 182 | + |
| 183 | + check("D9 Sub-session WITH tool-delegate: can call delegate()", can_delegate_with) |
| 184 | + check( |
| 185 | + "D9b Sub-session WITHOUT tool-delegate: CANNOT call delegate() — proves explicit declaration is required", |
| 186 | + not can_delegate_without, |
| 187 | + "this proves Brian's note: the fix makes B reachable but delegate tool must be declared", |
204 | 188 | ) |
| 189 | + |
| 190 | + # Summary |
| 191 | + total = 14 |
| 192 | + passed = total - len(failures) |
| 193 | + print(f"\n══ Group D Results: {passed}/{total} passed ══\n") |
| 194 | + if failures: |
| 195 | + print("Failed:") |
| 196 | + for f in failures: |
| 197 | + print(f" ✗ {f}") |
| 198 | + else: |
| 199 | + print( |
| 200 | + "Both agents satisfy Brian's operational note:\n" |
| 201 | + " • tool-delegate declared explicitly in each agent's own tools:\n" |
| 202 | + " • Neither agent relies on mode tool-policy inheritance\n" |
| 203 | + " • Phase 1→2 handoff (facilitator→tool-designer via delegate) is safe\n" |
| 204 | + " • Phase 2 per-signal delegation (tool-designer→self) is safe\n" |
| 205 | + ) |
| 206 | + |
| 207 | + assert not failures, "Group D tool-composition failures: " + ", ".join(failures) |
| 208 | + |
| 209 | + |
| 210 | +if __name__ == "__main__": |
| 211 | + test_tool_delegate_composition() |
0 commit comments