@@ -67,10 +67,31 @@ jobs:
6767 KIND_CLUSTER_NAME : actordock-functional
6868 run : ./hack/kind-down.sh || true
6969
70- # agent-semantic@v2 ablation: random | resource-evict | semantic L1 | semantic L1+L3
70+ # Parallel ablation: one Kind cluster per policy variant (no mid-run POLICY switch).
7171 e2e-eval :
7272 runs-on : ubuntu-latest
73- timeout-minutes : 120
73+ timeout-minutes : 90
74+ strategy :
75+ fail-fast : false
76+ matrix :
77+ include :
78+ - label : random
79+ policy : random
80+ prior_mix : " "
81+ cluster : actordock-eval-random
82+ - label : resource-evict
83+ policy : resource-evict
84+ prior_mix : " "
85+ cluster : actordock-eval-re
86+ - label : semantic-score-l1
87+ policy : semantic-score
88+ prior_mix : " 0"
89+ cluster : actordock-eval-l1
90+ - label : semantic-score
91+ policy : semantic-score
92+ prior_mix : " 0.3"
93+ cluster : actordock-eval-full
94+ name : e2e-eval (${{ matrix.label }})
7495 steps :
7596 - uses : actions/checkout@v4
7697 - uses : actions/setup-go@v5
@@ -88,15 +109,16 @@ jobs:
88109
89110 - name : Create cluster and deploy
90111 env :
91- KIND_CLUSTER_NAME : actordock-eval
92- POLICY : fifo
112+ KIND_CLUSTER_NAME : ${{ matrix.cluster }}
113+ POLICY : ${{ matrix.policy }}
114+ SEMANTIC_PRIOR_MIX : ${{ matrix.prior_mix }}
93115 run : |
94116 chmod +x hack/*.sh hack/replay-agent-semantic.py
95117 ./hack/kind-up.sh
96118
97- - name : Agent-semantic ablation (8 agents / 2 workers)
119+ - name : Agent-semantic replay (8 agents / 2 workers)
98120 env :
99- KIND_CLUSTER_NAME : actordock-eval
121+ KIND_CLUSTER_NAME : ${{ matrix.cluster }}
100122 E2E_SUITE : agent-semantic
101123 ACTORDOCK_API : http://127.0.0.1:18080
102124 EVAL_OUT_DIR : docs/eval/results
@@ -105,29 +127,140 @@ jobs:
105127 AGENT_SEMANTIC_MIN_WORKERS : " 2"
106128 AGENT_SEMANTIC_SPEED : " 60"
107129 AGENT_SEMANTIC_MIN_LOCK : " 0.25"
108- AGENT_SEMANTIC_POLICIES : " random,resource-evict,semantic-score-l1,semantic-score"
130+ AGENT_SEMANTIC_POLICIES : ${{ matrix.label }}
131+ AGENT_SEMANTIC_SWITCH_POLICY : " 0"
109132 run : ./hack/verify-local.sh
110133
111- - name : Upload agent-semantic results
134+ - name : Publish policy summary on Actions page
135+ if : success() || failure()
136+ env :
137+ LABEL : ${{ matrix.label }}
138+ run : |
139+ python3 - <<'PY'
140+ import json, os
141+ from pathlib import Path
142+ label = os.environ["LABEL"]
143+ out = Path(os.environ.get("GITHUB_STEP_SUMMARY", "/dev/null"))
144+ p = Path(f"docs/eval/results/agent_semantic_v2__{label}.json")
145+ lines = [f"## agent-semantic `{label}`", ""]
146+ if not p.exists():
147+ lines += [f"_missing {p}_", ""]
148+ else:
149+ r = json.loads(p.read_text())
150+ lines += [
151+ "| metric | value |",
152+ "|--------|------:|",
153+ f"| sessions ok/fail | {r.get('sessions_ok')}/{r.get('sessions_failed')} |",
154+ f"| mid_tool | {r.get('mid_tool_suspend', 0)} |",
155+ f"| mid_tool_rate | {float(r.get('mid_tool_rate') or 0):.2f} |",
156+ f"| suspend | {r.get('suspend_total', 0)} |",
157+ f"| hard_rate | {float(r.get('victim_l3_hard_rate') or 0):.2f} |",
158+ f"| vic_hard / vic_easy | {r.get('victim_l3_hard', 0)} / {r.get('victim_l3_easy', 0)} |",
159+ f"| resume_hard_s / resume_easy_s | {float(r.get('resume_sec_l3_hard') or 0):.3f} / {float(r.get('resume_sec_l3_easy') or 0):.3f} |",
160+ f"| victim_cx | {float(r.get('victim_complexity_mean') or 0):.3f} |",
161+ f"| resume_wait_s | {float(r.get('resume_wait_mean_s') or 0):.3f} |",
162+ f"| wall_s | {float(r.get('wall_sec') or 0):.1f} |",
163+ "",
164+ f"victims=`{r.get('victim_by_cohort')}`",
165+ "",
166+ ]
167+ out.write_text("\n".join(lines) + "\n")
168+ print("".join(line + "\n" for line in lines))
169+ PY
170+
171+ - name : Upload policy result
112172 if : success() || failure()
113173 uses : actions/upload-artifact@v4
114174 with :
115- name : agent-semantic-policy-compare
116- path : |
117- docs/eval/results/policy_compare_agent_semantic_v2.md
118- docs/eval/results/agent_semantic_v2__*.json
175+ name : agent-semantic-${{ matrix.label }}
176+ path : docs/eval/results/agent_semantic_v2__${{ matrix.label }}.json
119177 if-no-files-found : warn
120178
121179 - name : Dump on failure
122180 if : failure()
123181 run : |
124- kubectl --context kind-actordock-eval -n actordock get pods -o wide || true
125- kubectl --context kind-actordock-eval -n actordock describe pods || true
126- kubectl --context kind-actordock-eval -n actordock logs deploy/controlplane --all-containers=true || true
127- kubectl --context kind-actordock-eval -n actordock logs statefulset/worker --all-containers=true || true
182+ kubectl --context kind-${{ matrix.cluster }} -n actordock get pods -o wide || true
183+ kubectl --context kind-${{ matrix.cluster }} -n actordock describe pods || true
184+ kubectl --context kind-${{ matrix.cluster }} -n actordock logs deploy/controlplane --all-containers=true || true
185+ kubectl --context kind-${{ matrix.cluster }} -n actordock logs statefulset/worker --all-containers=true || true
128186
129187 - name : Teardown
130188 if : always()
131189 env :
132- KIND_CLUSTER_NAME : actordock-eval
190+ KIND_CLUSTER_NAME : ${{ matrix.cluster }}
133191 run : ./hack/kind-down.sh || true
192+
193+ e2e-eval-merge :
194+ runs-on : ubuntu-latest
195+ needs : e2e-eval
196+ if : always() && !cancelled()
197+ steps :
198+ - uses : actions/checkout@v4
199+ - uses : actions/setup-python@v5
200+ with :
201+ python-version : " 3.12"
202+
203+ - name : Download policy artifacts
204+ uses : actions/download-artifact@v4
205+ with :
206+ pattern : agent-semantic-*
207+ path : docs/eval/results/parts
208+ merge-multiple : true
209+
210+ - name : Merge compare table + gate
211+ env :
212+ EVAL_OUT_DIR : docs/eval/results
213+ run : |
214+ chmod +x hack/replay-agent-semantic.py
215+ mkdir -p docs/eval/results
216+ # Flatten downloaded JSONs into results/
217+ find docs/eval/results/parts -name 'agent_semantic_v2__*.json' -exec cp -f {} docs/eval/results/ \;
218+ ls -la docs/eval/results/agent_semantic_v2__*.json
219+ python3 hack/replay-agent-semantic.py --merge-only --out docs/eval/results
220+ python3 - <<'PY'
221+ import json, os, sys
222+ from pathlib import Path
223+ out = Path("docs/eval/results")
224+ want = ["random", "resource-evict", "semantic-score-l1", "semantic-score"]
225+ failed = []
226+ missing = []
227+ for label in want:
228+ p = out / f"agent_semantic_v2__{label}.json"
229+ if not p.exists():
230+ missing.append(label)
231+ continue
232+ r = json.loads(p.read_text())
233+ if int(r.get("sessions_failed") or 0) != 0 or int(r.get("sessions_ok") or 0) <= 0:
234+ failed.append(f"{p.name}: ok={r.get('sessions_ok')} fail={r.get('sessions_failed')}")
235+ compare = out / "policy_compare_agent_semantic_v2.md"
236+ if not compare.exists():
237+ sys.exit("missing policy_compare_agent_semantic_v2.md")
238+ body = compare.read_text()
239+ print(body)
240+ # Show full compare on the Actions run Summary tab (no artifact download needed).
241+ summary = Path(os.environ.get("GITHUB_STEP_SUMMARY", ""))
242+ if str(summary):
243+ with summary.open("a", encoding="utf-8") as f:
244+ f.write(body)
245+ if missing:
246+ f.write("\n\n**MISSING:** " + ", ".join(missing) + "\n")
247+ if failed:
248+ f.write("\n\n**FAILED:**\n\n- " + "\n- ".join(failed) + "\n")
249+ if missing:
250+ print("MISSING:", ", ".join(missing))
251+ sys.exit(1)
252+ if failed:
253+ print("FAILED:", *failed, sep="\n ")
254+ sys.exit(1)
255+ print(f"ok: {len(want)} policy report(s)")
256+ PY
257+
258+ - name : Upload merged compare (optional archive)
259+ if : success() || failure()
260+ uses : actions/upload-artifact@v4
261+ with :
262+ name : agent-semantic-policy-compare
263+ path : |
264+ docs/eval/results/policy_compare_agent_semantic_v2.md
265+ docs/eval/results/agent_semantic_v2__*.json
266+ if-no-files-found : warn
0 commit comments