Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions .github/workflows/deploy-agentteams-gx10.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
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: true

jobs:
deploy:
name: gx10-deploy
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.number == 44
runs-on: [self-hosted, Linux, gx10]
timeout-minutes: 60
env:
PASAY_GITHUB_MCP_URL: ${{ secrets.PASAY_GITHUB_MCP_URL }}
steps:
- name: Checkout deployment candidate
uses: actions/checkout@v4

- name: Verify GX10 runtime
shell: bash
run: |
set -euo pipefail
test "${RUNNER_OS}" = "Linux"
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

- name: Install pinned AgentTeams control plane
shell: bash
run: |
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
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 > "${status_dir}/DEPLOYMENT_STATUS"

- name: Apply PASAY team and watchdog
if: env.PASAY_GITHUB_MCP_URL != ''
shell: bash
run: |
set -euo pipefail
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"])')"
rendered="$(mktemp)"
apply_script="$(mktemp)"
trap 'rm -f -- "${rendered}" "${apply_script}"' EXIT
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")
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 > "${status_dir}/DEPLOYMENT_STATUS"

- name: Report missing GitHub MCP route
if: env.PASAY_GITHUB_MCP_URL == ''
shell: bash
run: |
echo "::warning::Control plane installed; PASAY workers need repository secret PASAY_GITHUB_MCP_URL."
{
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://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"
67 changes: 67 additions & 0 deletions ops/agentteams/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# 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 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

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 <milestone>,直到 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. The Project and PASAY Worker containers are paused. After inspecting/replanning, resume with `~/.local/share/pasay-agentteams/resume-project.sh <project-id>`.

## 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
bash -n ops/agentteams/resume-project.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.
100 changes: 100 additions & 0 deletions ops/agentteams/bootstrap.sh
Original file line number Diff line number Diff line change
@@ -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://<GX10-IP>:18088'
printf '%s\n' 'Dashboard: http://<GX10-IP>:13000'
39 changes: 39 additions & 0 deletions ops/agentteams/install-watchdog.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/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"
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" \
> "${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'
Loading
Loading