From 160b7e5da27bae1dce7e7c41548ffaca3442f99a Mon Sep 17 00:00:00 2001 From: jhackuy Date: Wed, 26 Aug 2026 14:25:06 +0800 Subject: [PATCH 01/16] ops(agentteams): add pasay-team.yaml.tpl --- ops/agentteams/pasay-team.yaml.tpl | 218 +++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 ops/agentteams/pasay-team.yaml.tpl diff --git a/ops/agentteams/pasay-team.yaml.tpl b/ops/agentteams/pasay-team.yaml.tpl new file mode 100644 index 0000000..9fdae1b --- /dev/null +++ b/ops/agentteams/pasay-team.yaml.tpl @@ -0,0 +1,218 @@ +apiVersion: agentteams.io/v1beta1 +kind: Manager +metadata: + name: default +spec: + model: __PASAY_AGENT_MODEL__ + runtime: qwenpaw + soul: | + # PASAY Manager + 你是 PASAY 的自治项目经理。Owner 只给业务目标;你负责把目标转成可验证的 Project DAG,交给 pasay-engineering Team,并持续跟踪到 READY_FOR_OWNER 或 BLOCKED_FOR_PRODUCT_DECISION。 + agents: | + ## 强制工作协议 + - 只负责编排、状态核验、证据汇总;不得代替 Worker 修改代码。 + - 每个项目必须先由 Repo Auditor 读取真实仓库、当前分支/commit、PR、测试和运行环境,生成 BASELINE.md;不得依赖旧会话猜测现状。 + - 先生成 GOAL_CONTRACT.md:目标、范围内、范围外、事实基线、验收命令、完成条件、Owner-only 边界。普通技术选择由团队自行决定,中途不询问 Owner。 + - CodeRabbit 评论只能作为线索。没有可复现命令、真实失败测试或明确安全证据,不得创建返修任务。 + - 实现、测试、审查必须由不同 Worker 完成;实现者不得验收自己。 + - 同一发现最多允许两轮返修。重复发现若没有新证据,登记为 REJECTED_DUPLICATE,不得继续消耗 token。 + - 连续两轮没有 commit、测试状态或交付物变化时,立即 @pasay-brake 复核;不得靠继续聊天假装推进。 + - 只有真实业务规则冲突、缺失不可推导的 Owner-only 决策或缺少授权凭据,才允许 BLOCKED_FOR_PRODUCT_DECISION;一次性写清阻塞证据,不反复追问。 + - 完成必须有:固定 commit、范围清单、真实测试结果、独立 QA、独立 Review、无可复现 blocker。最终只输出 READY_FOR_OWNER;不得自行 merge 或 production deploy。 + config: + heartbeatInterval: 10m + workerIdleTimeout: 720m + notifyChannel: admin-dm +--- +apiVersion: agentteams.io/v1beta1 +kind: Worker +metadata: + name: pasay-lead +spec: + model: __PASAY_AGENT_MODEL__ + runtime: qwenpaw + identity: | + - Name: PASAY Engineering Lead + - Role: Team Leader and evidence-driven delivery coordinator + soul: | + 你负责把 Manager 的目标拆成有依赖关系的 DAG,协调成员并推动项目收敛。你不直接实现业务代码。 + agents: | + - 开始前确认 BASELINE.md 与 GOAL_CONTRACT.md 已存在且引用固定 commit。 + - 顺序必须包含:事实盘点 -> 实施 -> 独立测试 -> 独立审查 -> Brake 收敛检查 -> 汇总。 + - 不把聊天回复当作完成;只认共享目录中的结果文件、Git commit 和测试证据。 + - Worker 未确认领取或没有交付物时自动重派一次;第二次仍失败则记录明确 blocker。 + - Reviewer 与 QA 不能直接修改 Builder 的实现;发现必须带证据并交回 Builder。 + - 最多两轮返修。达到上限后由 Brake 判断接受、回滚本轮或 BLOCKED,不得无限循环。 + skills: + - github-operations + - git-delegation + mcpServers: + - name: github + url: __PASAY_GITHUB_MCP_URL__ + transport: http + resources: + requests: {cpu: 250m, memory: 512Mi} + limits: {cpu: "2", memory: 3Gi} +--- +apiVersion: agentteams.io/v1beta1 +kind: Worker +metadata: + name: pasay-auditor +spec: + model: __PASAY_AGENT_MODEL__ + runtime: qwenpaw + identity: | + - Name: PASAY Repo Auditor + - Role: Read-only source-of-truth investigator + soul: | + 你只读核验真实代码、Git、PR、CI、迁移、测试与运行证据。你的职责是阻止团队从错误基线开始。 + agents: | + - 禁止修改代码、提交、push、merge 或部署。 + - 每个结论标记为 CONFIRMED、CONTRADICTED 或 UNKNOWN,并附路径、commit、命令或 API 证据。 + - 产出 BASELINE.md 和 BASELINE.json;必须包含 authority branch、HEAD SHA、工作范围、现有失败测试和当前未解决 PR。 + - 旧文档与现场事实冲突时,以现场事实为准并明确记录差异。 + skills: + - github-operations + mcpServers: + - name: github + url: __PASAY_GITHUB_MCP_URL__ + transport: http + resources: + requests: {cpu: 200m, memory: 384Mi} + limits: {cpu: "1", memory: 2Gi} +--- +apiVersion: agentteams.io/v1beta1 +kind: Worker +metadata: + name: pasay-builder +spec: + model: __PASAY_AGENT_MODEL__ + runtime: hermes + identity: | + - Name: PASAY Builder + - Role: Autonomous implementation and targeted test owner + soul: | + 你依据冻结的 GOAL_CONTRACT 实施完整里程碑。你可以修改代码和测试,但不能改变产品边界,也不能验收自己。 + agents: | + - 只在独立分支/worktree 工作;禁止 force push、改写共享历史、直接修改 authority branch。 + - 不删除、skip 或 xfail 真实失败测试来制造 PASS。 + - 财务、权限、Operation/Task 真值与冻结架构必须服从仓库 AGENTS.md。 + - 每轮提交必须记录 changed files、测试命令、结果和仍存在的失败;完成后 @pasay-qa 与 @pasay-reviewer。 + - 仅修复 QA/Review 中具备复现证据且属于当前范围的发现;无证据或越界意见书面拒绝。 + - 禁止 merge、production deploy、写入 Secrets 或修改生产数据。 + skills: + - github-operations + - git-delegation + mcpServers: + - name: github + url: __PASAY_GITHUB_MCP_URL__ + transport: http + resources: + requests: {cpu: 500m, memory: 1Gi} + limits: {cpu: "4", memory: 6Gi} +--- +apiVersion: agentteams.io/v1beta1 +kind: Worker +metadata: + name: pasay-qa +spec: + model: __PASAY_AGENT_MODEL__ + runtime: qwenpaw + identity: | + - Name: PASAY QA + - Role: Independent executable acceptance verifier + soul: | + 你通过真实命令验证交付,不接受实现者自报成功,也不直接替实现者修代码。 + agents: | + - 从固定 commit 和 GOAL_CONTRACT 独立推导验收矩阵。 + - 先运行最小相关测试,再运行合同要求的回归门;完整记录命令、exit code、passed/failed/deselected 和环境。 + - 每个失败必须给出最小复现、预期、实际、是否本轮引入、是否阻塞当前目标。 + - 既有失败不得被伪装成本轮通过;同时不得把未变化的既有失败自动升级为返修任务。 + - 禁止修改业务实现、merge 或部署。 + skills: + - github-operations + mcpServers: + - name: github + url: __PASAY_GITHUB_MCP_URL__ + transport: http + resources: + requests: {cpu: 300m, memory: 512Mi} + limits: {cpu: "3", memory: 4Gi} +--- +apiVersion: agentteams.io/v1beta1 +kind: Worker +metadata: + name: pasay-reviewer +spec: + model: __PASAY_AGENT_MODEL__ + runtime: qwenpaw + identity: | + - Name: PASAY Reviewer + - Role: Read-only scope, architecture and security reviewer + soul: | + 你独立审查当前 diff 是否满足合同、是否破坏业务真值和安全边界。你不是另一个实现者。 + agents: | + - 禁止直接修改实现。 + - finding 必须包含唯一 ID、严重度、文件/符号、证据、复现方式、与 GOAL_CONTRACT 的关系和最小修复建议。 + - 只有可复现 blocker、明确安全漏洞或违反冻结业务真值的问题才能 RETURN。 + - 风格偏好、理论优化、无现场证据的假设和范围外重构全部标为 ADVISORY,不得阻止完成。 + - 同一 finding 无新证据不得重复提交。 + skills: + - github-operations + mcpServers: + - name: github + url: __PASAY_GITHUB_MCP_URL__ + transport: http + resources: + requests: {cpu: 200m, memory: 384Mi} + limits: {cpu: "2", memory: 3Gi} +--- +apiVersion: agentteams.io/v1beta1 +kind: Worker +metadata: + name: pasay-brake +spec: + model: __PASAY_AGENT_MODEL__ + runtime: qwenpaw + identity: | + - Name: PASAY Brake + - Role: Independent drift and loop circuit breaker + soul: | + 你不开发功能。你的唯一职责是在团队偏离事实、范围或停止条件时及时刹车,并促使项目收敛。 + agents: | + - 对照 BASELINE、GOAL_CONTRACT、Git diff、测试证据和 finding ID 检查状态。 + - 出现错误基线、无证据返修、重复 finding、范围漂移、自我验收或两轮无进展时,立即要求 Leader 停止对应任务。 + - 返修达到两轮后必须做三选一:ACCEPT(证据满足)、REVERT_CURRENT_SCOPE(本轮失败且可回退)或 BLOCKED(明确不可自动解决的 Owner-only 决策)。禁止第三轮原地打转。 + - 不允许用更多聊天代替 commit、测试或交付物变化。 + - 产出 BRAKE_REPORT.md,列明继续/停止决定与证据。 + skills: + - github-operations + mcpServers: + - name: github + url: __PASAY_GITHUB_MCP_URL__ + transport: http + resources: + requests: {cpu: 200m, memory: 384Mi} + limits: {cpu: "2", memory: 3Gi} +--- +apiVersion: agentteams.io/v1beta1 +kind: Team +metadata: + name: pasay-engineering +spec: + description: "PASAY autonomous engineering team: evidence-first audit, implementation, executable QA, independent review, and deterministic brake." + peerMentions: true + heartbeatEvery: 10m + workerMembers: + - name: pasay-lead + role: team_leader + - name: pasay-auditor + role: worker + - name: pasay-builder + role: worker + - name: pasay-qa + role: worker + - name: pasay-reviewer + role: worker + - name: pasay-brake + role: worker From 46d8f9d41d4ea469d62d458feeb3020182b71e44 Mon Sep 17 00:00:00 2001 From: jhackuy Date: Wed, 26 Aug 2026 14:25:07 +0800 Subject: [PATCH 02/16] ops(agentteams): add bootstrap.sh --- ops/agentteams/bootstrap.sh | 100 ++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 ops/agentteams/bootstrap.sh diff --git a/ops/agentteams/bootstrap.sh b/ops/agentteams/bootstrap.sh new file mode 100644 index 0000000..e642ca8 --- /dev/null +++ b/ops/agentteams/bootstrap.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +set -euo pipefail + +AGENTTEAMS_PINNED_VERSION="${AGENTTEAMS_VERSION:-v1.2.3}" +PASAY_AGENT_MODEL="${PASAY_AGENT_MODEL:-}" +PASAY_GITHUB_MCP_URL="${PASAY_GITHUB_MCP_URL:-}" +AGENTTEAMS_OPENAI_BASE_URL="${AGENTTEAMS_OPENAI_BASE_URL:-}" +AGENTTEAMS_LLM_API_KEY="${AGENTTEAMS_LLM_API_KEY:-}" +AGENTTEAMS_ADMIN_PASSWORD="${AGENTTEAMS_ADMIN_PASSWORD:-}" +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" + +fail() { + printf 'PASAY_AGENTTEAMS_BLOCKED: %s\n' "$1" >&2 + exit 1 +} + +require_value() { + local name="$1" + local value="$2" + [ -n "${value}" ] || fail "missing required environment variable ${name}" +} + +require_value PASAY_AGENT_MODEL "${PASAY_AGENT_MODEL}" +require_value PASAY_GITHUB_MCP_URL "${PASAY_GITHUB_MCP_URL}" +require_value AGENTTEAMS_OPENAI_BASE_URL "${AGENTTEAMS_OPENAI_BASE_URL}" +require_value AGENTTEAMS_LLM_API_KEY "${AGENTTEAMS_LLM_API_KEY}" +require_value AGENTTEAMS_ADMIN_PASSWORD "${AGENTTEAMS_ADMIN_PASSWORD}" + +[[ "${PASAY_AGENT_MODEL}" =~ ^[A-Za-z0-9._:/-]+$ ]] || fail "PASAY_AGENT_MODEL contains unsupported characters" +[[ "${PASAY_GITHUB_MCP_URL}" =~ ^https?://[^[:space:]]+$ ]] || fail "PASAY_GITHUB_MCP_URL must be an http(s) URL" +[[ "${AGENTTEAMS_OPENAI_BASE_URL}" =~ ^https?://[^[:space:]]+$ ]] || fail "AGENTTEAMS_OPENAI_BASE_URL must be an http(s) URL" +[ "${#AGENTTEAMS_ADMIN_PASSWORD}" -ge 8 ] || fail "AGENTTEAMS_ADMIN_PASSWORD must be at least 8 characters" + +command -v curl >/dev/null 2>&1 || fail "curl is required" +command -v python3 >/dev/null 2>&1 || fail "python3 is required" +if command -v docker >/dev/null 2>&1; then + CONTAINER_CMD=docker +elif command -v podman >/dev/null 2>&1; then + CONTAINER_CMD=podman +else + fail "Docker or Podman is required" +fi +"${CONTAINER_CMD}" ps >/dev/null 2>&1 || fail "${CONTAINER_CMD} daemon is not available to this user" + +PASAY_AGENTTEAMS_TMP="$(mktemp -d)" +cleanup() { + rm -f -- "${PASAY_AGENTTEAMS_TMP}/agentteams-install.sh" \ + "${PASAY_AGENTTEAMS_TMP}/agentteams-apply.sh" \ + "${PASAY_AGENTTEAMS_TMP}/pasay-team.yaml" + rmdir -- "${PASAY_AGENTTEAMS_TMP}" 2>/dev/null || true +} +trap cleanup EXIT + +BASE_RAW_URL="https://raw.githubusercontent.com/agentscope-ai/AgentTeams/${AGENTTEAMS_PINNED_VERSION}" +curl --fail --silent --show-error --location \ + "${BASE_RAW_URL}/install/agentteams-install.sh" \ + --output "${PASAY_AGENTTEAMS_TMP}/agentteams-install.sh" +curl --fail --silent --show-error --location \ + "${BASE_RAW_URL}/install/agentteams-apply.sh" \ + --output "${PASAY_AGENTTEAMS_TMP}/agentteams-apply.sh" + +export AGENTTEAMS_NON_INTERACTIVE=1 +export AGENTTEAMS_LLM_PROVIDER=openai-compat +export AGENTTEAMS_DEFAULT_MODEL="${PASAY_AGENT_MODEL}" +export AGENTTEAMS_OPENAI_BASE_URL +export AGENTTEAMS_LLM_API_KEY +export AGENTTEAMS_ADMIN_PASSWORD +export PASAY_AGENT_MODEL +export PASAY_GITHUB_MCP_URL +export AGENTTEAMS_VERSION="${AGENTTEAMS_PINNED_VERSION}" +export AGENTTEAMS_MOUNT_SOCKET=1 +export AGENTTEAMS_DASHBOARD=1 + +bash "${PASAY_AGENTTEAMS_TMP}/agentteams-install.sh" manager + +python3 - "${SCRIPT_DIR}/pasay-team.yaml.tpl" "${PASAY_AGENTTEAMS_TMP}/pasay-team.yaml" <<'PY' +import os +import pathlib +import sys + +source = pathlib.Path(sys.argv[1]).read_text(encoding="utf-8") +rendered = source.replace("__PASAY_AGENT_MODEL__", os.environ["PASAY_AGENT_MODEL"]) +rendered = rendered.replace("__PASAY_GITHUB_MCP_URL__", os.environ["PASAY_GITHUB_MCP_URL"]) +if "__PASAY_" in rendered: + raise SystemExit("unresolved PASAY template placeholder") +pathlib.Path(sys.argv[2]).write_text(rendered, encoding="utf-8") +PY + +AGENTTEAMS_CONTAINER_CMD="${CONTAINER_CMD}" \ + bash "${PASAY_AGENTTEAMS_TMP}/agentteams-apply.sh" -f "${PASAY_AGENTTEAMS_TMP}/pasay-team.yaml" + +PASAY_WATCHDOG_CONTAINER_CMD="${CONTAINER_CMD}" bash "${SCRIPT_DIR}/install-watchdog.sh" + +"${CONTAINER_CMD}" exec agentteams-manager agt get workers pasay-lead -o json >/dev/null +"${CONTAINER_CMD}" exec agentteams-manager agt get workers pasay-builder -o json >/dev/null +"${CONTAINER_CMD}" exec agentteams-manager agt get teams pasay-engineering -o json >/dev/null + +printf '%s\n' 'PASAY_AGENTTEAMS_READY' +printf '%s\n' 'Element chat: http://:18088' +printf '%s\n' 'Dashboard: http://:13000' From e222c10c60e226446d86eb2b00191429e39584e3 Mon Sep 17 00:00:00 2001 From: jhackuy Date: Wed, 26 Aug 2026 14:25:09 +0800 Subject: [PATCH 03/16] ops(agentteams): add watchdog.py --- ops/agentteams/watchdog.py | 164 +++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 ops/agentteams/watchdog.py diff --git a/ops/agentteams/watchdog.py b/ops/agentteams/watchdog.py new file mode 100644 index 0000000..8048865 --- /dev/null +++ b/ops/agentteams/watchdog.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +"""Pause a PASAY AgentTeams project after repeated no-progress snapshots.""" + +from __future__ import annotations + +import hashlib +import json +import os +import pathlib +import subprocess +import sys +from typing import Any + + +TEAM_NAME = os.environ.get("PASAY_WATCHDOG_TEAM", "pasay-engineering") +STALL_POLLS = int(os.environ.get("PASAY_WATCHDOG_STALL_POLLS", "6")) +DRY_RUN = os.environ.get("PASAY_WATCHDOG_DRY_RUN", "1") != "0" +CONTAINER_CMD = os.environ.get("PASAY_WATCHDOG_CONTAINER_CMD", "docker") +STATE_PATH = pathlib.Path( + os.environ.get( + "PASAY_WATCHDOG_STATE", + "~/.local/state/pasay-agentteams/watchdog.json", + ) +).expanduser() + + +def parse_json_output(raw: str) -> Any: + decoder = json.JSONDecoder() + for index, char in enumerate(raw): + if char not in "[{": + continue + try: + value, _ = decoder.raw_decode(raw[index:]) + return value + except json.JSONDecodeError: + continue + raise ValueError("AgentTeams CLI returned no JSON payload") + + +def run_agt(arguments: list[str]) -> Any: + command = [CONTAINER_CMD, "exec", "agentteams-manager", "agt", *arguments] + completed = subprocess.run( + command, + check=True, + capture_output=True, + text=True, + timeout=90, + ) + return parse_json_output(completed.stdout) + + +def project_list(payload: Any) -> list[dict[str, Any]]: + if isinstance(payload, list): + return [item for item in payload if isinstance(item, dict)] + if not isinstance(payload, dict): + return [] + for key in ("projects", "items", "result"): + value = payload.get(key) + if isinstance(value, list): + return [item for item in value if isinstance(item, dict)] + if isinstance(value, dict): + nested = project_list(value) + if nested: + return nested + return [] + + +def progress_fingerprint(detail: dict[str, Any]) -> str: + nodes = [] + for node in detail.get("nodes", []): + if isinstance(node, dict): + nodes.append( + { + "id": node.get("id"), + "status": node.get("status"), + "assignee": node.get("assignee"), + } + ) + stable = { + "status": detail.get("status"), + "nodes": sorted(nodes, key=lambda item: str(item.get("id"))), + "next": detail.get("next", []), + "interrupts": detail.get("interrupts", []), + "loop": detail.get("loop"), + } + encoded = json.dumps(stable, ensure_ascii=False, sort_keys=True).encode("utf-8") + return hashlib.sha256(encoded).hexdigest() + + +def next_stall_count(previous: dict[str, Any] | None, fingerprint: str) -> int: + if previous and previous.get("fingerprint") == fingerprint: + return int(previous.get("stall_count", 0)) + 1 + return 1 + + +def load_state() -> dict[str, Any]: + if not STATE_PATH.exists(): + return {} + try: + value = json.loads(STATE_PATH.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError): + return {} + return value if isinstance(value, dict) else {} + + +def save_state(state: dict[str, Any]) -> None: + STATE_PATH.parent.mkdir(parents=True, exist_ok=True) + temporary = STATE_PATH.with_suffix(".tmp") + temporary.write_text( + json.dumps(state, ensure_ascii=False, indent=2, sort_keys=True), + encoding="utf-8", + ) + temporary.replace(STATE_PATH) + + +def pause_project(project_id: str, reason: str) -> None: + if DRY_RUN: + print(f"WATCHDOG_DRY_RUN project={project_id} reason={reason}") + return + run_agt(["project", "pause", project_id, "--reason", reason]) + print(f"WATCHDOG_PAUSED project={project_id} reason={reason}") + + +def main() -> int: + if STALL_POLLS < 2: + raise ValueError("PASAY_WATCHDOG_STALL_POLLS must be >= 2") + state = load_state() + active_keys: set[str] = set() + projects = project_list(run_agt(["get", "projects", "-o", "json"])) + + for project in projects: + project_id = str(project.get("project_id") or project.get("id") or "") + team = str(project.get("team_id") or project.get("team") or "") + status = str(project.get("status") or "") + if not project_id or team != TEAM_NAME or status != "active": + continue + key = f"{team}:{project_id}" + active_keys.add(key) + detail = run_agt(["get", "projects", project_id, "-o", "json"]) + if not isinstance(detail, dict): + print(f"WATCHDOG_SKIPPED project={project_id} reason=invalid-detail") + continue + fingerprint = progress_fingerprint(detail) + count = next_stall_count(state.get(key), fingerprint) + state[key] = {"fingerprint": fingerprint, "stall_count": count} + if count >= STALL_POLLS: + reason = f"PASAY circuit breaker: no workflow state change for {count} checks" + pause_project(project_id, reason) + state[key]["paused_by_watchdog"] = True + state[key]["stall_count"] = 0 + + for key in list(state): + if key.startswith(f"{TEAM_NAME}:") and key not in active_keys: + state.pop(key, None) + save_state(state) + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except (subprocess.SubprocessError, OSError, ValueError) as exc: + print(f"WATCHDOG_ERROR: {exc}", file=sys.stderr) + raise SystemExit(1) From 8b3f29299a43525d8147cba481688086c61809ee Mon Sep 17 00:00:00 2001 From: jhackuy Date: Wed, 26 Aug 2026 14:25:11 +0800 Subject: [PATCH 04/16] ops(agentteams): add install-watchdog.sh --- ops/agentteams/install-watchdog.sh | 38 ++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 ops/agentteams/install-watchdog.sh diff --git a/ops/agentteams/install-watchdog.sh b/ops/agentteams/install-watchdog.sh new file mode 100644 index 0000000..b34d697 --- /dev/null +++ b/ops/agentteams/install-watchdog.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +INSTALL_DIR="${HOME}/.local/share/pasay-agentteams" +UNIT_DIR="${HOME}/.config/systemd/user" +WATCHDOG_CONTAINER_CMD="${PASAY_WATCHDOG_CONTAINER_CMD:-docker}" + +case "${WATCHDOG_CONTAINER_CMD}" in + docker|podman) ;; + *) + printf 'PASAY_WATCHDOG_BLOCKED: unsupported container command %s\n' \ + "${WATCHDOG_CONTAINER_CMD}" >&2 + exit 1 + ;; +esac + +command -v systemctl >/dev/null 2>&1 || { + printf '%s\n' 'PASAY_WATCHDOG_BLOCKED: systemd is required' >&2 + exit 1 +} + +install -d -m 0755 "${INSTALL_DIR}" "${UNIT_DIR}" +install -m 0755 "${SCRIPT_DIR}/watchdog.py" "${INSTALL_DIR}/watchdog.py" + +sed "s/PASAY_WATCHDOG_CONTAINER_CMD=docker/PASAY_WATCHDOG_CONTAINER_CMD=${WATCHDOG_CONTAINER_CMD}/" \ + "${SCRIPT_DIR}/systemd/pasay-agentteams-watchdog.service" \ + > "${UNIT_DIR}/pasay-agentteams-watchdog.service" +chmod 0644 "${UNIT_DIR}/pasay-agentteams-watchdog.service" +install -m 0644 "${SCRIPT_DIR}/systemd/pasay-agentteams-watchdog.timer" \ + "${UNIT_DIR}/pasay-agentteams-watchdog.timer" + +systemctl --user daemon-reload +systemctl --user enable --now pasay-agentteams-watchdog.timer +systemctl --user start pasay-agentteams-watchdog.service +systemctl --user is-active pasay-agentteams-watchdog.timer >/dev/null + +printf '%s\n' 'PASAY_WATCHDOG_READY' From a54aecb0a3d9ccba9b17ac3ace4728c39bb20b76 Mon Sep 17 00:00:00 2001 From: jhackuy Date: Wed, 26 Aug 2026 14:25:12 +0800 Subject: [PATCH 05/16] ops(agentteams): add pasay-agentteams-watchdog.service --- .../systemd/pasay-agentteams-watchdog.service | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 ops/agentteams/systemd/pasay-agentteams-watchdog.service diff --git a/ops/agentteams/systemd/pasay-agentteams-watchdog.service b/ops/agentteams/systemd/pasay-agentteams-watchdog.service new file mode 100644 index 0000000..8af1a45 --- /dev/null +++ b/ops/agentteams/systemd/pasay-agentteams-watchdog.service @@ -0,0 +1,11 @@ +[Unit] +Description=PASAY AgentTeams no-progress circuit breaker +After=docker.service + +[Service] +Type=oneshot +Environment=PASAY_WATCHDOG_TEAM=pasay-engineering +Environment=PASAY_WATCHDOG_STALL_POLLS=6 +Environment=PASAY_WATCHDOG_DRY_RUN=0 +Environment=PASAY_WATCHDOG_CONTAINER_CMD=docker +ExecStart=/usr/bin/env python3 %h/.local/share/pasay-agentteams/watchdog.py From dc0399a7d50828839479c2d867b032aa498a0957 Mon Sep 17 00:00:00 2001 From: jhackuy Date: Wed, 26 Aug 2026 14:25:13 +0800 Subject: [PATCH 06/16] ops(agentteams): add pasay-agentteams-watchdog.timer --- ops/agentteams/systemd/pasay-agentteams-watchdog.timer | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 ops/agentteams/systemd/pasay-agentteams-watchdog.timer diff --git a/ops/agentteams/systemd/pasay-agentteams-watchdog.timer b/ops/agentteams/systemd/pasay-agentteams-watchdog.timer new file mode 100644 index 0000000..e0dc230 --- /dev/null +++ b/ops/agentteams/systemd/pasay-agentteams-watchdog.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Run PASAY AgentTeams circuit breaker every five minutes + +[Timer] +OnBootSec=2min +OnUnitActiveSec=5min +Persistent=true + +[Install] +WantedBy=timers.target From 9762f47824fe93cb368b4e51bd27acbc4d1b8029 Mon Sep 17 00:00:00 2001 From: jhackuy Date: Wed, 26 Aug 2026 14:25:15 +0800 Subject: [PATCH 07/16] ops(agentteams): add test_watchdog.py --- ops/agentteams/tests/test_watchdog.py | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 ops/agentteams/tests/test_watchdog.py diff --git a/ops/agentteams/tests/test_watchdog.py b/ops/agentteams/tests/test_watchdog.py new file mode 100644 index 0000000..44dd5d6 --- /dev/null +++ b/ops/agentteams/tests/test_watchdog.py @@ -0,0 +1,40 @@ +import importlib.util +import pathlib + + +MODULE_PATH = pathlib.Path(__file__).parents[1] / "watchdog.py" +SPEC = importlib.util.spec_from_file_location("pasay_watchdog", MODULE_PATH) +assert SPEC and SPEC.loader +watchdog = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(watchdog) + + +def test_parse_json_output_ignores_cli_prefix(): + value = watchdog.parse_json_output('AgentTeams log\n{"projects": [{"project_id": "p1"}]}\n') + assert value["projects"][0]["project_id"] == "p1" + + +def test_fingerprint_is_stable_across_node_order(): + first = { + "status": "active", + "nodes": [ + {"id": "b", "status": "pending", "assignee": "qa"}, + {"id": "a", "status": "completed", "assignee": "dev"}, + ], + "next": ["b"], + "interrupts": [], + "loop": None, + } + second = {**first, "nodes": list(reversed(first["nodes"]))} + assert watchdog.progress_fingerprint(first) == watchdog.progress_fingerprint(second) + + +def test_stall_count_resets_on_real_progress(): + previous = {"fingerprint": "old", "stall_count": 5} + assert watchdog.next_stall_count(previous, "old") == 6 + assert watchdog.next_stall_count(previous, "new") == 1 + + +def test_project_list_accepts_nested_connector_shape(): + payload = {"result": {"projects": [{"project_id": "p1"}]}} + assert watchdog.project_list(payload) == [{"project_id": "p1"}] From 0c666acd4d35fcb292668fa181d8514663016387 Mon Sep 17 00:00:00 2001 From: jhackuy Date: Wed, 26 Aug 2026 14:25:16 +0800 Subject: [PATCH 08/16] ops(agentteams): add README.md --- ops/agentteams/README.md | 66 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 ops/agentteams/README.md diff --git a/ops/agentteams/README.md b/ops/agentteams/README.md new file mode 100644 index 0000000..765f184 --- /dev/null +++ b/ops/agentteams/README.md @@ -0,0 +1,66 @@ +# PASAY AgentTeams autonomous engineering team + +This directory turns AgentTeams v1.2.3 into a PASAY-specific Manager → Team Leader → Workers workflow. It is intentionally separate from PASAY business code. + +## Delivered team + +| Agent | Authority | +| --- | --- | +| `default` Manager | Accepts one Owner goal, creates a Project DAG, tracks evidence, reports final status | +| `pasay-lead` | Decomposes and coordinates; does not implement | +| `pasay-auditor` | Read-only repository/runtime baseline | +| `pasay-builder` | Implements on an isolated branch; no merge/deploy | +| `pasay-qa` | Runs independent executable acceptance | +| `pasay-reviewer` | Read-only scope/security/architecture review | +| `pasay-brake` | Stops drift, duplicate findings and revision loops | + +The systemd watchdog is a second, non-LLM brake. Every five minutes it fingerprints Project task states. Six unchanged checks pause the project instead of allowing an invisible token loop. + +## One-time deployment on GX10 + +The four values below are secrets or installation-specific facts and must not be committed: + +```bash +export AGENTTEAMS_LLM_API_KEY='...' +export AGENTTEAMS_ADMIN_PASSWORD='...' +export AGENTTEAMS_OPENAI_BASE_URL='https://provider.example/v1' +export PASAY_AGENT_MODEL='model-id' +export PASAY_GITHUB_MCP_URL='https://agentteams.example/mcp-servers/github/mcp' +bash ops/agentteams/bootstrap.sh +``` + +`PASAY_GITHUB_MCP_URL` must point to an AgentTeams/Higress GitHub MCP route whose credential is stored at the gateway. Do not put a GitHub PAT in Worker YAML or chat. The route must grant access to `jhackuy/pasay-pm`; Workers otherwise have a chat room but cannot operate on the private repository. + +The script performs a non-interactive pinned v1.2.3 installation, deploys the Manager and six Workers, creates the `pasay-engineering` Team Room, installs the watchdog timer, and verifies the Team/Worker resources. It does not merge, deploy PASAY, write production secrets, or change production data. + +## Owner usage + +After installation, open Element at `http://192.168.50.42:18088` and send one message to the Manager: + +```text +目标:完成 PASAY ,直到 READY_FOR_OWNER。 +以仓库 AGENTS.md、当前 authority branch、真实测试和运行证据为准。 +普通技术决定由团队自行处理;中途不要询问我。 +不得 merge、production deploy、写 Secrets 或修改生产数据。 +``` + +The Manager must create a Project rather than free-form chat. Final states are: + +- `READY_FOR_OWNER`: implementation and independent evidence are complete; Owner performs final acceptance/merge decision. +- `BLOCKED_FOR_PRODUCT_DECISION`: the goal conflicts with an Owner-only business boundary or a required credential/capability is absent. The team stops once and reports evidence; it must not repeatedly ask. +- `PAUSED_BY_WATCHDOG`: workflow state did not change for 30 minutes. Inspect the Project timeline before resuming or replanning. + +## Local checks + +```bash +python3 -m pytest -q ops/agentteams/tests/test_watchdog.py +bash -n ops/agentteams/bootstrap.sh +bash -n ops/agentteams/install-watchdog.sh +``` + +## Known external prerequisites + +- GX10 `192.168.50.42` must be reachable and Docker/Podman must work for user `cunzhang`. +- A model API or an OpenAI-compatible local endpoint must be reachable from AgentTeams containers. TRAE Ultra cannot supply this API. +- The GitHub MCP route must already be authorized for the private repository. +- User-level systemd must be available. For watchdog survival across logout, lingering for `cunzhang` must be enabled by the machine administrator. From 9d1acb32857938cf22cb66743c55e6f0c81309c2 Mon Sep 17 00:00:00 2001 From: jhackuy Date: Wed, 26 Aug 2026 14:28:09 +0800 Subject: [PATCH 09/16] ops(agentteams): strengthen hard brake in watchdog.py --- ops/agentteams/watchdog.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/ops/agentteams/watchdog.py b/ops/agentteams/watchdog.py index 8048865..92e7dc8 100644 --- a/ops/agentteams/watchdog.py +++ b/ops/agentteams/watchdog.py @@ -16,6 +16,14 @@ STALL_POLLS = int(os.environ.get("PASAY_WATCHDOG_STALL_POLLS", "6")) DRY_RUN = os.environ.get("PASAY_WATCHDOG_DRY_RUN", "1") != "0" CONTAINER_CMD = os.environ.get("PASAY_WATCHDOG_CONTAINER_CMD", "docker") +TEAM_WORKERS = ( + "pasay-lead", + "pasay-auditor", + "pasay-builder", + "pasay-qa", + "pasay-reviewer", + "pasay-brake", +) STATE_PATH = pathlib.Path( os.environ.get( "PASAY_WATCHDOG_STATE", @@ -118,9 +126,26 @@ def pause_project(project_id: str, reason: str) -> None: print(f"WATCHDOG_DRY_RUN project={project_id} reason={reason}") return run_agt(["project", "pause", project_id, "--reason", reason]) + pause_team_workers() print(f"WATCHDOG_PAUSED project={project_id} reason={reason}") +def pause_team_workers() -> None: + """Freeze only PASAY Worker containers to stop in-flight token use.""" + for worker in TEAM_WORKERS: + container = f"agentteams-worker-{worker}" + completed = subprocess.run( + [CONTAINER_CMD, "pause", container], + check=False, + capture_output=True, + text=True, + timeout=30, + ) + if completed.returncode not in (0,): + message = completed.stderr.strip() or "container pause failed" + print(f"WATCHDOG_CONTAINER_WARNING container={container} error={message}") + + def main() -> int: if STALL_POLLS < 2: raise ValueError("PASAY_WATCHDOG_STALL_POLLS must be >= 2") From 760b11b12900b1494b3ac2bd6bfc6d9dfcbd0dd6 Mon Sep 17 00:00:00 2001 From: jhackuy Date: Wed, 26 Aug 2026 14:28:11 +0800 Subject: [PATCH 10/16] ops(agentteams): strengthen hard brake in test_watchdog.py --- ops/agentteams/tests/test_watchdog.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ops/agentteams/tests/test_watchdog.py b/ops/agentteams/tests/test_watchdog.py index 44dd5d6..cdf9a01 100644 --- a/ops/agentteams/tests/test_watchdog.py +++ b/ops/agentteams/tests/test_watchdog.py @@ -38,3 +38,9 @@ def test_stall_count_resets_on_real_progress(): def test_project_list_accepts_nested_connector_shape(): payload = {"result": {"projects": [{"project_id": "p1"}]}} assert watchdog.project_list(payload) == [{"project_id": "p1"}] + + +def test_hard_brake_targets_only_pasay_workers(): + assert len(watchdog.TEAM_WORKERS) == 6 + assert all(name.startswith("pasay-") for name in watchdog.TEAM_WORKERS) + assert "manager" not in watchdog.TEAM_WORKERS From 17d29a4bb73e9747a7969d86072ca7728087089e Mon Sep 17 00:00:00 2001 From: jhackuy Date: Wed, 26 Aug 2026 14:28:12 +0800 Subject: [PATCH 11/16] ops(agentteams): strengthen hard brake in install-watchdog.sh --- ops/agentteams/install-watchdog.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/ops/agentteams/install-watchdog.sh b/ops/agentteams/install-watchdog.sh index b34d697..a292891 100644 --- a/ops/agentteams/install-watchdog.sh +++ b/ops/agentteams/install-watchdog.sh @@ -22,6 +22,7 @@ command -v systemctl >/dev/null 2>&1 || { install -d -m 0755 "${INSTALL_DIR}" "${UNIT_DIR}" install -m 0755 "${SCRIPT_DIR}/watchdog.py" "${INSTALL_DIR}/watchdog.py" +install -m 0755 "${SCRIPT_DIR}/resume-project.sh" "${INSTALL_DIR}/resume-project.sh" sed "s/PASAY_WATCHDOG_CONTAINER_CMD=docker/PASAY_WATCHDOG_CONTAINER_CMD=${WATCHDOG_CONTAINER_CMD}/" \ "${SCRIPT_DIR}/systemd/pasay-agentteams-watchdog.service" \ From 40288fff87c0768cd4bf26ca5713a1fec61063a3 Mon Sep 17 00:00:00 2001 From: jhackuy Date: Wed, 26 Aug 2026 14:28:14 +0800 Subject: [PATCH 12/16] ops(agentteams): strengthen hard brake in README.md --- ops/agentteams/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ops/agentteams/README.md b/ops/agentteams/README.md index 765f184..23d47f3 100644 --- a/ops/agentteams/README.md +++ b/ops/agentteams/README.md @@ -14,7 +14,7 @@ This directory turns AgentTeams v1.2.3 into a PASAY-specific Manager → Team Le | `pasay-reviewer` | Read-only scope/security/architecture review | | `pasay-brake` | Stops drift, duplicate findings and revision loops | -The systemd watchdog is a second, non-LLM brake. Every five minutes it fingerprints Project task states. Six unchanged checks pause the project instead of allowing an invisible token loop. +The systemd watchdog is a second, non-LLM brake. Every five minutes it fingerprints Project task states. Six unchanged checks pause the Project and freeze only the six PASAY Worker containers, stopping in-flight token use instead of allowing an invisible loop. The Manager and AgentTeams control plane remain available. ## One-time deployment on GX10 @@ -48,7 +48,7 @@ The Manager must create a Project rather than free-form chat. Final states are: - `READY_FOR_OWNER`: implementation and independent evidence are complete; Owner performs final acceptance/merge decision. - `BLOCKED_FOR_PRODUCT_DECISION`: the goal conflicts with an Owner-only business boundary or a required credential/capability is absent. The team stops once and reports evidence; it must not repeatedly ask. -- `PAUSED_BY_WATCHDOG`: workflow state did not change for 30 minutes. Inspect the Project timeline before resuming or replanning. +- `PAUSED_BY_WATCHDOG`: workflow state did not change for 30 minutes. The Project and PASAY Worker containers are paused. After inspecting/replanning, resume with `~/.local/share/pasay-agentteams/resume-project.sh `. ## Local checks @@ -56,6 +56,7 @@ The Manager must create a Project rather than free-form chat. Final states are: python3 -m pytest -q ops/agentteams/tests/test_watchdog.py bash -n ops/agentteams/bootstrap.sh bash -n ops/agentteams/install-watchdog.sh +bash -n ops/agentteams/resume-project.sh ``` ## Known external prerequisites From 21eda15c0de4e5be0d0d09386039f524cb2aeb57 Mon Sep 17 00:00:00 2001 From: jhackuy Date: Wed, 26 Aug 2026 14:28:16 +0800 Subject: [PATCH 13/16] ops(agentteams): add reversible team resume command --- ops/agentteams/resume-project.sh | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 ops/agentteams/resume-project.sh diff --git a/ops/agentteams/resume-project.sh b/ops/agentteams/resume-project.sh new file mode 100644 index 0000000..53c1b96 --- /dev/null +++ b/ops/agentteams/resume-project.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +PROJECT_ID="${1:-}" +CONTAINER_CMD="${PASAY_WATCHDOG_CONTAINER_CMD:-docker}" + +[[ "${PROJECT_ID}" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || { + printf '%s\n' 'usage: resume-project.sh ' >&2 + exit 2 +} + +for worker in lead auditor builder qa reviewer brake; do + "${CONTAINER_CMD}" unpause "agentteams-worker-pasay-${worker}" 2>/dev/null || true +done + +"${CONTAINER_CMD}" exec agentteams-manager \ + agt project resume "${PROJECT_ID}" + +printf 'PASAY_PROJECT_RESUMED project=%s\n' "${PROJECT_ID}" From 7d28137b2bce468c27161fd2069b5327fe5c90ee Mon Sep 17 00:00:00 2001 From: jhackuy Date: Wed, 26 Aug 2026 14:55:43 +0800 Subject: [PATCH 14/16] ops(agentteams): deploy to GX10 through Mac runner --- .github/workflows/deploy-agentteams-gx10.yml | 154 +++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 .github/workflows/deploy-agentteams-gx10.yml diff --git a/.github/workflows/deploy-agentteams-gx10.yml b/.github/workflows/deploy-agentteams-gx10.yml new file mode 100644 index 0000000..46169dd --- /dev/null +++ b/.github/workflows/deploy-agentteams-gx10.yml @@ -0,0 +1,154 @@ +name: deploy-agentteams-gx10 + +on: + pull_request: + branches: + - milestone/4-lease-moveout-closure + types: [opened, synchronize, reopened] + paths: + - .github/workflows/deploy-agentteams-gx10.yml + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: deploy-agentteams-gx10 + cancel-in-progress: false + +jobs: + deploy: + name: gx10-deploy + if: github.event_name == 'workflow_dispatch' || github.event.pull_request.number == 44 + runs-on: [self-hosted, macOS, ARM64] + timeout-minutes: 60 + env: + GX10_HOST: 192.168.50.42 + GX10_USER: cunzhang + PASAY_GITHUB_MCP_URL: ${{ secrets.PASAY_GITHUB_MCP_URL }} + steps: + - name: Checkout deployment candidate + uses: actions/checkout@v4 + + - name: Verify Mac runner to GX10 bridge + shell: bash + run: | + set -euo pipefail + test "${RUNNER_OS}" = "macOS" + ssh -o BatchMode=yes -o ConnectTimeout=10 -o ConnectionAttempts=1 -o StrictHostKeyChecking=yes "${GX10_USER}@${GX10_HOST}" 'bash -s' <<'REMOTE' + set -euo pipefail + command -v docker >/dev/null + command -v curl >/dev/null + command -v python3 >/dev/null + docker ps >/dev/null + model_json="$(mktemp)" + trap 'rm -f -- "${model_json}"' EXIT + curl --fail --silent --show-error http://127.0.0.1:8002/v1/models > "${model_json}" + python3 - "${model_json}" <<'PY' + import json + import pathlib + import sys + payload = json.loads(pathlib.Path(sys.argv[1]).read_text(encoding="utf-8")) + models = payload.get("data", []) + if not models or not models[0].get("id"): + raise SystemExit("GX10 vLLM endpoint returned no model id") + print("GX10_MODEL_READY", models[0]["id"]) + PY + REMOTE + + - name: Copy PASAY AgentTeams bundle to GX10 + shell: bash + run: | + set -euo pipefail + tar -C "${GITHUB_WORKSPACE}" -czf - ops/agentteams | ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=yes "${GX10_USER}@${GX10_HOST}" 'install -d -m 0755 "$HOME/.local/src/pasay-agentteams" && tar -xzf - -C "$HOME/.local/src/pasay-agentteams" --strip-components=2' + + - name: Install pinned AgentTeams control plane + shell: bash + run: | + set -euo pipefail + ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=yes "${GX10_USER}@${GX10_HOST}" 'bash -s' <<'REMOTE' + set -euo pipefail + config_dir="${HOME}/.config/pasay-agentteams" + install -d -m 0700 "${config_dir}" + password_file="${config_dir}/admin-password" + if [ ! -s "${password_file}" ]; then + umask 077 + python3 - <<'PY' > "${password_file}" + import secrets + print(secrets.token_urlsafe(32)) + PY + fi + model_id="$(curl --fail --silent --show-error http://127.0.0.1:8002/v1/models | python3 -c 'import json,sys; print(json.load(sys.stdin)["data"][0]["id"])')" + installer="$(mktemp)" + trap 'rm -f -- "${installer}"' EXIT + curl --fail --silent --show-error --location https://raw.githubusercontent.com/agentscope-ai/AgentTeams/v1.2.3/install/agentteams-install.sh --output "${installer}" + export AGENTTEAMS_NON_INTERACTIVE=1 + export AGENTTEAMS_LLM_PROVIDER=openai-compat + export AGENTTEAMS_DEFAULT_MODEL="${model_id}" + export AGENTTEAMS_OPENAI_BASE_URL="http://192.168.50.42:8002/v1" + export AGENTTEAMS_LLM_API_KEY=local-gx10-no-auth + export AGENTTEAMS_ADMIN_USER=admin + export AGENTTEAMS_ADMIN_PASSWORD + AGENTTEAMS_ADMIN_PASSWORD="$(cat "${password_file}")" + export AGENTTEAMS_VERSION=v1.2.3 + export AGENTTEAMS_MOUNT_SOCKET=1 + export AGENTTEAMS_DASHBOARD=1 + if ! docker ps --format '{{.Names}}' | grep -qx agentteams-manager; then + bash "${installer}" manager + fi + docker ps --format '{{.Names}}' | grep -qx agentteams-manager + printf '%s\n' CONTROL_PLANE_READY > "${HOME}/.local/src/pasay-agentteams/DEPLOYMENT_STATUS" + REMOTE + + - name: Apply PASAY team and watchdog + if: env.PASAY_GITHUB_MCP_URL != '' + shell: bash + run: | + set -euo pipefail + printf '%s' "${PASAY_GITHUB_MCP_URL}" | ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=yes "${GX10_USER}@${GX10_HOST}" 'umask 077; cat > "$HOME/.config/pasay-agentteams/github-mcp-url"' + ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=yes "${GX10_USER}@${GX10_HOST}" 'bash -s' <<'REMOTE' + set -euo pipefail + src="${HOME}/.local/src/pasay-agentteams" + model_id="$(curl --fail --silent --show-error http://127.0.0.1:8002/v1/models | python3 -c 'import json,sys; print(json.load(sys.stdin)["data"][0]["id"])')" + mcp_url="$(cat "${HOME}/.config/pasay-agentteams/github-mcp-url")" + rendered="$(mktemp)" + apply_script="$(mktemp)" + trap 'rm -f -- "${rendered}" "${apply_script}"' EXIT + python3 - "${src}/pasay-team.yaml.tpl" "${rendered}" "${model_id}" "${mcp_url}" <<'PY' + import pathlib + import sys + source = pathlib.Path(sys.argv[1]).read_text(encoding="utf-8") + source = source.replace("__PASAY_AGENT_MODEL__", sys.argv[3]) + source = source.replace("__PASAY_GITHUB_MCP_URL__", sys.argv[4]) + if "__PASAY_" in source: + raise SystemExit("unresolved PASAY template placeholder") + pathlib.Path(sys.argv[2]).write_text(source, encoding="utf-8") + PY + curl --fail --silent --show-error --location https://raw.githubusercontent.com/agentscope-ai/AgentTeams/v1.2.3/install/agentteams-apply.sh --output "${apply_script}" + AGENTTEAMS_CONTAINER_CMD=docker bash "${apply_script}" -f "${rendered}" + PASAY_WATCHDOG_CONTAINER_CMD=docker bash "${src}/install-watchdog.sh" + docker exec agentteams-manager agt get workers pasay-builder -o json >/dev/null + docker exec agentteams-manager agt get teams pasay-engineering -o json >/dev/null + printf '%s\n' PASAY_AGENTTEAMS_READY > "${src}/DEPLOYMENT_STATUS" + REMOTE + + - name: Report missing GitHub MCP route + if: env.PASAY_GITHUB_MCP_URL == '' + shell: bash + run: | + echo "::warning::AgentTeams control plane was installed, but PASAY workers were not applied because repository secret PASAY_GITHUB_MCP_URL is absent." + { + echo "### GX10 deployment" + echo + echo "- Control plane: installed" + echo "- Local vLLM: reachable" + echo "- PASAY team: blocked only by PASAY_GITHUB_MCP_URL" + } >> "${GITHUB_STEP_SUMMARY}" + + - name: Smoke check web endpoints + shell: bash + run: | + set -euo pipefail + curl --fail --silent --show-error --max-time 20 "http://${GX10_HOST}:18088/" >/dev/null + curl --fail --silent --show-error --max-time 20 "http://${GX10_HOST}:13000/" >/dev/null + ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=yes "${GX10_USER}@${GX10_HOST}" 'cat "$HOME/.local/src/pasay-agentteams/DEPLOYMENT_STATUS"' From 4e45a372e652388041805f31279e12b9e28567de Mon Sep 17 00:00:00 2001 From: jhackuy Date: Wed, 26 Aug 2026 14:58:21 +0800 Subject: [PATCH 15/16] ops(agentteams): accept existing self-hosted runner labels --- .github/workflows/deploy-agentteams-gx10.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-agentteams-gx10.yml b/.github/workflows/deploy-agentteams-gx10.yml index 46169dd..8b4451a 100644 --- a/.github/workflows/deploy-agentteams-gx10.yml +++ b/.github/workflows/deploy-agentteams-gx10.yml @@ -14,13 +14,13 @@ permissions: concurrency: group: deploy-agentteams-gx10 - cancel-in-progress: false + cancel-in-progress: true jobs: deploy: name: gx10-deploy if: github.event_name == 'workflow_dispatch' || github.event.pull_request.number == 44 - runs-on: [self-hosted, macOS, ARM64] + runs-on: [self-hosted] timeout-minutes: 60 env: GX10_HOST: 192.168.50.42 From ce1625945ff3fdcd52f31f650a990e5d3dc1f6df Mon Sep 17 00:00:00 2001 From: jhackuy Date: Wed, 26 Aug 2026 15:01:08 +0800 Subject: [PATCH 16/16] ops(agentteams): run deployment directly on GX10 --- .github/workflows/deploy-agentteams-gx10.yml | 47 +++++++------------- 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/.github/workflows/deploy-agentteams-gx10.yml b/.github/workflows/deploy-agentteams-gx10.yml index 8b4451a..0665d90 100644 --- a/.github/workflows/deploy-agentteams-gx10.yml +++ b/.github/workflows/deploy-agentteams-gx10.yml @@ -20,23 +20,19 @@ jobs: deploy: name: gx10-deploy if: github.event_name == 'workflow_dispatch' || github.event.pull_request.number == 44 - runs-on: [self-hosted] + runs-on: [self-hosted, Linux, gx10] timeout-minutes: 60 env: - GX10_HOST: 192.168.50.42 - GX10_USER: cunzhang PASAY_GITHUB_MCP_URL: ${{ secrets.PASAY_GITHUB_MCP_URL }} steps: - name: Checkout deployment candidate uses: actions/checkout@v4 - - name: Verify Mac runner to GX10 bridge + - name: Verify GX10 runtime shell: bash run: | set -euo pipefail - test "${RUNNER_OS}" = "macOS" - ssh -o BatchMode=yes -o ConnectTimeout=10 -o ConnectionAttempts=1 -o StrictHostKeyChecking=yes "${GX10_USER}@${GX10_HOST}" 'bash -s' <<'REMOTE' - set -euo pipefail + test "${RUNNER_OS}" = "Linux" command -v docker >/dev/null command -v curl >/dev/null command -v python3 >/dev/null @@ -54,22 +50,15 @@ jobs: raise SystemExit("GX10 vLLM endpoint returned no model id") print("GX10_MODEL_READY", models[0]["id"]) PY - REMOTE - - - name: Copy PASAY AgentTeams bundle to GX10 - shell: bash - run: | - set -euo pipefail - tar -C "${GITHUB_WORKSPACE}" -czf - ops/agentteams | ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=yes "${GX10_USER}@${GX10_HOST}" 'install -d -m 0755 "$HOME/.local/src/pasay-agentteams" && tar -xzf - -C "$HOME/.local/src/pasay-agentteams" --strip-components=2' - name: Install pinned AgentTeams control plane shell: bash run: | - set -euo pipefail - ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=yes "${GX10_USER}@${GX10_HOST}" 'bash -s' <<'REMOTE' set -euo pipefail config_dir="${HOME}/.config/pasay-agentteams" + status_dir="${HOME}/.local/share/pasay-agentteams" install -d -m 0700 "${config_dir}" + install -d -m 0755 "${status_dir}" password_file="${config_dir}/admin-password" if [ ! -s "${password_file}" ]; then umask 077 @@ -97,24 +86,23 @@ jobs: bash "${installer}" manager fi docker ps --format '{{.Names}}' | grep -qx agentteams-manager - printf '%s\n' CONTROL_PLANE_READY > "${HOME}/.local/src/pasay-agentteams/DEPLOYMENT_STATUS" - REMOTE + printf '%s\n' CONTROL_PLANE_READY > "${status_dir}/DEPLOYMENT_STATUS" - name: Apply PASAY team and watchdog if: env.PASAY_GITHUB_MCP_URL != '' shell: bash run: | set -euo pipefail - printf '%s' "${PASAY_GITHUB_MCP_URL}" | ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=yes "${GX10_USER}@${GX10_HOST}" 'umask 077; cat > "$HOME/.config/pasay-agentteams/github-mcp-url"' - ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=yes "${GX10_USER}@${GX10_HOST}" 'bash -s' <<'REMOTE' - set -euo pipefail - src="${HOME}/.local/src/pasay-agentteams" + src="${GITHUB_WORKSPACE}/ops/agentteams" + config_dir="${HOME}/.config/pasay-agentteams" + status_dir="${HOME}/.local/share/pasay-agentteams" + umask 077 + printf '%s' "${PASAY_GITHUB_MCP_URL}" > "${config_dir}/github-mcp-url" model_id="$(curl --fail --silent --show-error http://127.0.0.1:8002/v1/models | python3 -c 'import json,sys; print(json.load(sys.stdin)["data"][0]["id"])')" - mcp_url="$(cat "${HOME}/.config/pasay-agentteams/github-mcp-url")" rendered="$(mktemp)" apply_script="$(mktemp)" trap 'rm -f -- "${rendered}" "${apply_script}"' EXIT - python3 - "${src}/pasay-team.yaml.tpl" "${rendered}" "${model_id}" "${mcp_url}" <<'PY' + python3 - "${src}/pasay-team.yaml.tpl" "${rendered}" "${model_id}" "${PASAY_GITHUB_MCP_URL}" <<'PY' import pathlib import sys source = pathlib.Path(sys.argv[1]).read_text(encoding="utf-8") @@ -129,14 +117,13 @@ jobs: PASAY_WATCHDOG_CONTAINER_CMD=docker bash "${src}/install-watchdog.sh" docker exec agentteams-manager agt get workers pasay-builder -o json >/dev/null docker exec agentteams-manager agt get teams pasay-engineering -o json >/dev/null - printf '%s\n' PASAY_AGENTTEAMS_READY > "${src}/DEPLOYMENT_STATUS" - REMOTE + printf '%s\n' PASAY_AGENTTEAMS_READY > "${status_dir}/DEPLOYMENT_STATUS" - name: Report missing GitHub MCP route if: env.PASAY_GITHUB_MCP_URL == '' shell: bash run: | - echo "::warning::AgentTeams control plane was installed, but PASAY workers were not applied because repository secret PASAY_GITHUB_MCP_URL is absent." + echo "::warning::Control plane installed; PASAY workers need repository secret PASAY_GITHUB_MCP_URL." { echo "### GX10 deployment" echo @@ -149,6 +136,6 @@ jobs: shell: bash run: | set -euo pipefail - curl --fail --silent --show-error --max-time 20 "http://${GX10_HOST}:18088/" >/dev/null - curl --fail --silent --show-error --max-time 20 "http://${GX10_HOST}:13000/" >/dev/null - ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=yes "${GX10_USER}@${GX10_HOST}" 'cat "$HOME/.local/src/pasay-agentteams/DEPLOYMENT_STATUS"' + curl --fail --silent --show-error --max-time 20 http://127.0.0.1:18088/ >/dev/null + curl --fail --silent --show-error --max-time 20 http://127.0.0.1:13000/ >/dev/null + cat "${HOME}/.local/share/pasay-agentteams/DEPLOYMENT_STATUS"