From fd03744bc9109920eaaabacbda4769de8d6a027c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A8=B8=EB=8B=88=ED=8E=98=EB=8B=88?= Date: Sat, 21 Mar 2026 13:47:37 +0900 Subject: [PATCH] chore: remove galaxis-agent docs from galaxis-po repo Move design specs, plans, and HANDOFF.md to their proper home in the galaxis-agent repository. Clean up CLAUDE.md references. --- CLAUDE.md | 13 +- .../plans/2026-03-20-galaxis-agent-cicd.md | 208 --- .../plans/2026-03-20-galaxis-agent-phase1.md | 1414 ----------------- .../plans/2026-03-20-galaxis-agent-phase5.md | 376 ----- .../2026-03-20-galaxis-agent-cicd-design.md | 131 -- .../specs/2026-03-20-galaxis-agent-design.md | 1019 ------------ ...3-20-galaxis-agent-phase5-deploy-design.md | 155 -- 7 files changed, 1 insertion(+), 3315 deletions(-) delete mode 100644 docs/superpowers/plans/2026-03-20-galaxis-agent-cicd.md delete mode 100644 docs/superpowers/plans/2026-03-20-galaxis-agent-phase1.md delete mode 100644 docs/superpowers/plans/2026-03-20-galaxis-agent-phase5.md delete mode 100644 docs/superpowers/specs/2026-03-20-galaxis-agent-cicd-design.md delete mode 100644 docs/superpowers/specs/2026-03-20-galaxis-agent-design.md delete mode 100644 docs/superpowers/specs/2026-03-20-galaxis-agent-phase5-deploy-design.md diff --git a/CLAUDE.md b/CLAUDE.md index ca88505..24e1c25 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -72,20 +72,9 @@ docker-compose up -d - `src/lib/api.ts` — Backend API client - Uses lightweight-charts for financial charts, recharts for other visualizations -### galaxis-agent (`~/workspace/quant/galaxis-agent/`) - -galaxis-po를 자율적으로 개발하는 SWE 에이전트 (별도 Gitea 리포: `quant/galaxis-agent`). - -- `agent/` — 핵심 모듈: dispatcher, task_queue, cost_guard, task_history, recovery, auto_merge -- `agent/integrations/` — Discord bot, sandbox backends -- `agent/tools/` — 에이전트 도구 (gitea_comment, discord_reply) -- `agent/utils/` — 유틸리티 (gitea_client, discord_client, git_utils) -- `tests/` — 테스트 (139개, Phase 1-4) -- 설계 스펙: `docs/superpowers/specs/`, 구현 플랜: `docs/superpowers/plans/` - ## Development Rules -1. Check `docs/plans/` and `docs/superpowers/plans/` for relevant design documents before implementing features +1. Check `docs/plans/` for relevant design documents before implementing features 2. All API endpoints go under `backend/app/api/` as routers 3. DB schema changes require an alembic migration — autogenerate 후 반드시 리뷰하고 즉시 `alembic upgrade head` 4. Do not modify `.env` or `docker-compose.prod.yml` (`.env` 설정 안내는 허용, 자동 수정은 금지) diff --git a/docs/superpowers/plans/2026-03-20-galaxis-agent-cicd.md b/docs/superpowers/plans/2026-03-20-galaxis-agent-cicd.md deleted file mode 100644 index 79602fc..0000000 --- a/docs/superpowers/plans/2026-03-20-galaxis-agent-cicd.md +++ /dev/null @@ -1,208 +0,0 @@ -# galaxis-agent Gitea Actions CI/CD Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** galaxis-agent 리포에 Gitea Actions CI/CD를 구성한다 (PR 테스트 + main 배포). - -**Architecture:** 2개 워크플로우 파일로 구성. `ci.yml`은 PR에서 pytest를 실행하고, `deploy.yml`은 main push 시 테스트 → Docker Compose 배포 → health check를 수행한다. galaxis-po의 기존 deploy.yml 패턴을 따른다. - -**Tech Stack:** Gitea Actions, Docker Compose, uv, pytest - -**Spec:** `docs/superpowers/specs/2026-03-20-galaxis-agent-cicd-design.md` - -**Working Directory:** `~/workspace/quant/galaxis-agent/` - -**전제조건 (수동):** Gitea 리포 Settings > Secrets에 8개 시크릿 등록 필요 — `ANTHROPIC_API_KEY`, `GITEA_TOKEN`, `GITEA_WEBHOOK_SECRET`, `DISCORD_TOKEN`, `DISCORD_CHANNEL_ID`, `DISCORD_BOT_USER_ID`, `FERNET_KEY`, `AGENT_API_KEY` - ---- - -## File Structure - -### 신규 생성 파일 - -``` -.gitea/workflows/ci.yml # PR 검증 워크플로우 (테스트만) -.gitea/workflows/deploy.yml # main push → 테스트 + 배포 -``` - ---- - -**Task 의존성:** 2개 Task 모두 독립적 → 병렬 dispatch 가능 (sonnet 충분) - ---- - -## Task 1: ci.yml 작성 - -PR 생성/업데이트 시 테스트를 실행하는 워크플로우. - -**Files:** -- Create: `.gitea/workflows/ci.yml` - -### Step 1a: ci.yml 작성 - -- [ ] **ci.yml 작성** - -```yaml -name: CI - -on: - pull_request: - branches: [main] - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install uv - run: | - curl -LsSf https://astral.sh/uv/install.sh | sh - echo "$HOME/.local/bin" >> $GITHUB_PATH - - - name: Install dependencies - run: uv sync --frozen - - - name: Run tests - run: uv run python -m pytest --tb=short -q -``` - -- [ ] **커밋** - -```bash -mkdir -p .gitea/workflows -git add .gitea/workflows/ci.yml -git commit -m "ci: add PR test workflow" -``` - ---- - -## Task 2: deploy.yml 작성 - -main push 시 테스트 → Docker Compose 배포 → health check를 수행하는 워크플로우. - -**Files:** -- Create: `.gitea/workflows/deploy.yml` - -**참고:** galaxis-po의 `.gitea/workflows/deploy.yml` 패턴을 따른다. - -### Step 2a: deploy.yml 작성 - -- [ ] **deploy.yml 작성** - -```yaml -name: Deploy to Production - -on: - push: - branches: [main] - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install uv - run: | - curl -LsSf https://astral.sh/uv/install.sh | sh - echo "$HOME/.local/bin" >> $GITHUB_PATH - - - name: Install dependencies - run: uv sync --frozen - - - name: Run tests - run: uv run python -m pytest --tb=short -q - - - name: Setup Docker CLI - run: | - apt-get update -qq && apt-get install -y -qq docker.io >/dev/null 2>&1 - mkdir -p ~/.docker/cli-plugins - curl -fsSL "https://github.com/docker/compose/releases/latest/download/docker-compose-linux-$(uname -m)" \ - -o ~/.docker/cli-plugins/docker-compose - chmod +x ~/.docker/cli-plugins/docker-compose - docker compose version - - - name: Create .env from secrets - run: | - cat < .env - ANTHROPIC_API_KEY=${{ secrets.ANTHROPIC_API_KEY }} - GITEA_URL=http://gitea:3000 - GITEA_EXTERNAL_URL=https://ayuriel.duckdns.org - GITEA_TOKEN=${{ secrets.GITEA_TOKEN }} - GITEA_WEBHOOK_SECRET=${{ secrets.GITEA_WEBHOOK_SECRET }} - DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} - DISCORD_CHANNEL_ID=${{ secrets.DISCORD_CHANNEL_ID }} - DISCORD_BOT_USER_ID=${{ secrets.DISCORD_BOT_USER_ID }} - LANGGRAPH_URL=http://langgraph-server:8123 - AUTONOMY_LEVEL=conservative - DEFAULT_REPO_OWNER=quant - DEFAULT_REPO_NAME=galaxis-po - AGENT_API_KEY=${{ secrets.AGENT_API_KEY }} - SANDBOX_IMAGE=galaxis-sandbox:latest - SANDBOX_MEM_LIMIT=4g - SANDBOX_CPU_COUNT=2 - SANDBOX_TIMEOUT=600 - FERNET_KEY=${{ secrets.FERNET_KEY }} - LOG_FORMAT=json - DAILY_COST_LIMIT_USD=10.0 - PER_TASK_COST_LIMIT_USD=3.0 - EOF - - - name: Ensure galaxis-net network - run: | - docker network inspect galaxis-net >/dev/null 2>&1 || docker network create galaxis-net - - - name: Build sandbox image - run: ./build-sandbox.sh - - - name: Deploy with Docker Compose - run: | - docker compose --project-name galaxis-agent down || true - docker compose --project-name galaxis-agent build - docker compose --project-name galaxis-agent up -d - - - name: Health check - run: | - echo "Waiting for galaxis-agent to become healthy..." - for i in $(seq 1 12); do - if curl -sf http://localhost:8100/health | grep -q '"status".*"ok"'; then - echo "Main health check: OK" - break - fi - if [ "$i" -eq 12 ]; then - echo "Health check failed after 60 seconds" - docker compose --project-name galaxis-agent logs - exit 1 - fi - echo " Waiting... (${i}/12, 5s interval)" - sleep 5 - done - - for ep in /health/gitea /health/discord /health/queue /health/costs; do - if curl -sf "http://localhost:8100${ep}" >/dev/null 2>&1; then - echo " ${ep} ... OK" - else - echo " ${ep} ... WARN (non-critical)" - fi - done - - echo "Deploy complete!" -``` - -- [ ] **커밋** - -```bash -git add .gitea/workflows/deploy.yml -git commit -m "ci: add production deploy workflow with health checks" -``` - ---- - -## 완료 기준 - -- [ ] PR 생성 시 `ci.yml`이 테스트 139개를 실행한다 -- [ ] `main` push 시 `deploy.yml`이 테스트 → 배포 → health check를 수행한다 -- [ ] 모든 기존 테스트 통과 (139개) diff --git a/docs/superpowers/plans/2026-03-20-galaxis-agent-phase1.md b/docs/superpowers/plans/2026-03-20-galaxis-agent-phase1.md deleted file mode 100644 index e36f181..0000000 --- a/docs/superpowers/plans/2026-03-20-galaxis-agent-phase1.md +++ /dev/null @@ -1,1414 +0,0 @@ -# galaxis-agent Phase 1: 프로젝트 기반 구축 Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** open-swe를 포크하여 galaxis-agent로 변환하고, ARM64에서 빌드/기동 가능한 상태까지 구축한다. - -**Architecture:** open-swe 코드베이스에서 불필요한 플랫폼 코드(Linear, Slack, GitHub, 클라우드 샌드박스)를 제거하고, Gitea/Discord용 스텁과 Docker 샌드박스 기반 인프라를 준비한다. LangGraph Server와 docker-socket-proxy를 포함한 docker-compose 환경을 구성한다. - -**Tech Stack:** Python 3.12, FastAPI, LangGraph, Deep Agents, docker-py, Docker Compose, ARM64 (Oracle A1) - -**Spec:** `docs/superpowers/specs/2026-03-20-galaxis-agent-design.md` - ---- - -## File Structure - -### 삭제할 파일 (open-swe에서 제거) - -``` -agent/tools/linear_comment.py # Linear 미사용 -agent/tools/slack_thread_reply.py # Slack 미사용 -agent/tools/github_comment.py # GitHub → Gitea로 교체 예정 -agent/integrations/modal.py # 클라우드 샌드박스 미사용 -agent/integrations/daytona.py # 클라우드 샌드박스 미사용 -agent/integrations/runloop.py # 클라우드 샌드박스 미사용 -agent/integrations/langsmith.py # LangSmith → Docker로 교체 예정 -agent/utils/linear.py # Linear 미사용 -agent/utils/linear_team_repo_map.py # Linear 미사용 -agent/utils/slack.py # Slack 미사용 -agent/utils/github_app.py # GitHub App 미사용 -agent/utils/github_token.py # GitHub 전용 -agent/utils/github_comments.py # GitHub 전용 -agent/utils/github_user_email_map.py # GitHub 전용 -agent/utils/langsmith.py # LangSmith 전용 -agent/integrations/local.py # 로컬 샌드박스 미사용 -tests/test_github_comment_prompts.py # GitHub 전용 테스트 -tests/test_slack_context.py # Slack 전용 테스트 -tests/test_github_issue_webhook.py # GitHub 전용 테스트 -tests/test_auth_sources.py # GitHub OAuth 전용 -tests/test_recent_comments.py # GitHub 전용 -``` - -### 수정할 파일 - -``` -pyproject.toml # 프로젝트명, 의존성 정리 -agent/server.py # GitHub/Linear/Slack 참조 제거, 스텁 교체 -agent/webapp.py # GitHub/Linear/Slack webhook 제거, Gitea 스텁 -agent/tools/__init__.py # 삭제된 도구 참조 제거 -agent/tools/commit_and_open_pr.py # GitHub 참조 제거, Gitea 스텁 -agent/prompt.py # GitHub 전용 프롬프트 섹션 정리 -agent/utils/sandbox.py # 클라우드 프로바이더 제거, Docker 스텁 -agent/utils/auth.py # GitHub OAuth 제거, Gitea 토큰 스텁 -agent/utils/github.py # git_utils.py로 이름 변경 (순수 git 유틸 보존) -agent/encryption.py # TOKEN_ENCRYPTION_KEY → FERNET_KEY 통일 -agent/integrations/__init__.py # 클라우드 프로바이더 import 제거 -agent/middleware/open_pr.py # GitHub 참조 제거, git_utils import로 변경 -langgraph.json # 프로젝트명 변경 -Makefile # 프로젝트명 변경 -``` - -### 신규 생성할 파일 - -``` -agent/config.py # 환경변수 관리 (pydantic-settings) -agent/integrations/docker_sandbox.py # DockerSandbox 스텁 (Phase 2에서 구현) -agent/utils/gitea_client.py # GiteaClient 스텁 (Phase 2에서 구현) -agent/utils/discord_client.py # DiscordClient 스텁 (Phase 2에서 구현) -agent/tools/gitea_comment.py # gitea_comment 스텁 (Phase 2에서 구현) -agent/tools/discord_reply.py # discord_reply 스텁 (Phase 2에서 구현) -agent/utils/git_utils.py # github.py에서 순수 git 유틸리티 분리 -Dockerfile # 에이전트 서버용 (ARM64, 기존 덮어쓰기) -Dockerfile.sandbox # 작업 컨테이너용 (ARM64) -docker-compose.yml # 전체 서비스 구성 -.env.example # 환경변수 템플릿 -tests/test_config.py # config 테스트 -``` - ---- - -## Task 1: Gitea에 리포 생성 및 open-swe 코드 복사 - -**Files:** -- Create: 전체 프로젝트 루트 (Gitea `quant/galaxis-agent`) - -- [ ] **Step 1: Gitea에 빈 리포 생성** - -Gitea 웹 UI에서 `quant/galaxis-agent` 리포지토리를 생성한다 (Initialize 체크 해제). - -- [ ] **Step 2: 로컬에 galaxis-agent 디렉토리 생성** - -```bash -mkdir -p ~/workspace/quant/galaxis-agent -cd ~/workspace/quant/galaxis-agent -git init -``` - -- [ ] **Step 3: open-swe 코드 복사 (git 히스토리 제외)** - -```bash -cp -r ~/workspace/etc/open-swe/agent ./agent -cp -r ~/workspace/etc/open-swe/tests ./tests -cp ~/workspace/etc/open-swe/pyproject.toml . -cp ~/workspace/etc/open-swe/langgraph.json . -cp ~/workspace/etc/open-swe/Makefile . -cp ~/workspace/etc/open-swe/Dockerfile . -cp ~/workspace/etc/open-swe/README.md . -cp ~/workspace/etc/open-swe/LICENSE . -cp ~/workspace/etc/open-swe/.gitignore . 2>/dev/null || true -``` - -- [ ] **Step 4: 초기 커밋 및 Gitea에 푸시** - -```bash -git add -A -git commit -m "chore: initial copy from open-swe" -git remote add origin https://ayuriel.duckdns.org/quant/galaxis-agent.git -git push -u origin master -``` - ---- - -## Task 2: 불필요한 파일 삭제 - -**Files:** -- Delete: 위 "삭제할 파일" 목록 전체 - -- [ ] **Step 1: Linear, Slack, GitHub 전용 코드 삭제** - -```bash -cd ~/workspace/quant/galaxis-agent - -# 도구 -rm -f agent/tools/linear_comment.py -rm -f agent/tools/slack_thread_reply.py -rm -f agent/tools/github_comment.py - -# 통합 (클라우드 샌드박스) -rm -f agent/integrations/modal.py -rm -f agent/integrations/daytona.py -rm -f agent/integrations/runloop.py -rm -f agent/integrations/langsmith.py - -# 유틸리티 -rm -f agent/utils/linear.py -rm -f agent/utils/linear_team_repo_map.py -rm -f agent/utils/slack.py -rm -f agent/utils/github.py -rm -f agent/utils/github_app.py -rm -f agent/utils/github_token.py -rm -f agent/utils/github_comments.py -rm -f agent/utils/github_user_email_map.py -rm -f agent/utils/langsmith.py -rm -f agent/integrations/local.py - -# GitHub 전용 테스트 -rm -f tests/test_github_comment_prompts.py -rm -f tests/test_slack_context.py -rm -f tests/test_github_issue_webhook.py -rm -f tests/test_auth_sources.py -rm -f tests/test_recent_comments.py -``` - -- [ ] **Step 2: 삭제 확인** - -```bash -# 삭제된 파일이 없는지 확인 -ls agent/tools/linear_comment.py 2>&1 | grep "No such file" -ls agent/integrations/modal.py 2>&1 | grep "No such file" -ls agent/utils/linear.py 2>&1 | grep "No such file" -``` - -- [ ] **Step 3: 커밋** - -```bash -git add -A -git commit -m "chore: remove Linear, Slack, GitHub, and cloud sandbox code" -``` - ---- - -## Task 3: github.py에서 순수 git 유틸리티 분리 - -`agent/utils/github.py`에는 GitHub API 함수와 순수 git 유틸리티(git_add, git_commit, git_push 등)가 혼재되어 있다. `commit_and_open_pr.py`와 `open_pr.py`가 git 유틸리티를 사용하므로, GitHub API 코드만 제거하고 git 유틸리티는 `git_utils.py`로 보존한다. - -**Files:** -- Create: `agent/utils/git_utils.py` -- Delete: `agent/utils/github.py` - -- [ ] **Step 1: github.py에서 순수 git 유틸리티 함수 식별** - -원본 `agent/utils/github.py`를 읽고 다음 두 그룹으로 분류한다: - -**보존 (git_utils.py로 이동):** -- `git_add_all()` - git add -- `git_commit()` - git commit -- `git_push()` - git push -- `git_checkout_branch()` - 브랜치 체크아웃/생성 -- `git_diff_stat()` - 변경사항 확인 -- `git_has_uncommitted_changes()` - 미커밋 변경 확인 -- `git_has_unpushed_commits()` - 미푸시 커밋 확인 -- `git_set_user_config()` - git user 설정 -- `get_default_branch()` - 기본 브랜치 조회 (git 명령 기반) - -**삭제 (GitHub API 전용):** -- `create_github_pr()` - PyGithub로 PR 생성 -- `get_github_default_branch()` - GitHub API로 기본 브랜치 조회 -- GitHub App 토큰 관련 함수 - -- [ ] **Step 2: git_utils.py 생성** - -원본 `github.py`에서 순수 git 유틸리티 함수만 복사하여 `agent/utils/git_utils.py`를 생성한다. import를 정리하고 GitHub API 관련 import를 제거한다. - -```python -# agent/utils/git_utils.py -"""순수 git 명령 유틸리티. - -agent/utils/github.py에서 GitHub API 의존성을 제거하고 -git CLI 기반 유틸리티만 보존한 모듈. -""" -# 원본 github.py에서 git 관련 함수만 복사 -# sandbox.execute()를 통해 git 명령을 실행하는 함수들 -``` - -실제 구현은 원본 파일을 읽고 해당 함수들을 그대로 옮긴다. - -- [ ] **Step 3: github.py 삭제** - -```bash -rm -f agent/utils/github.py -``` - -- [ ] **Step 4: 커밋** - -```bash -git add agent/utils/git_utils.py -git rm agent/utils/github.py -git commit -m "refactor: extract git utilities from github.py into git_utils.py" -``` - ---- - -## Task 4: pyproject.toml 의존성 정리 - -**Files:** -- Modify: `pyproject.toml` - -- [ ] **Step 1: pyproject.toml 수정** - -```toml -[project] -name = "galaxis-agent" -version = "0.1.0" -description = "Autonomous SWE agent for galaxis-po development" -readme = "README.md" -requires-python = ">=3.12" -license = { text = "MIT" } -dependencies = [ - "deepagents>=0.4.3", - "fastapi>=0.104.0", - "uvicorn>=0.24.0", - "httpx>=0.25.0", - "cryptography>=41.0.0", - "langgraph-sdk>=0.1.0", - "langchain>=1.2.9", - "langgraph>=1.0.8", - "langgraph-cli[inmem]>=0.4.12", - "langchain-anthropic>1.1.0", - "markdownify>=1.2.2", - "docker>=7.0.0", - "pydantic-settings>=2.0.0", - "slowapi>=0.1.9", - "discord.py>=2.3.0", -] - -[project.optional-dependencies] -dev = [ - "pytest>=7.0.0", - "pytest-asyncio>=0.21.0", - "ruff>=0.1.0", -] - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.hatch.metadata] -allow-direct-references = true - -[tool.hatch.build.targets.wheel] -packages = ["agent"] - -[tool.ruff] -line-length = 100 -target-version = "py312" - -[tool.ruff.lint] -select = ["E", "W", "F", "I", "B", "C4", "UP"] -ignore = ["E501"] - -[tool.pytest.ini_options] -asyncio_mode = "auto" -testpaths = ["tests"] -``` - -변경 사항: -- `name`: `open-swe-agent` → `galaxis-agent` -- `requires-python`: `>=3.11` → `>=3.12` -- `target-version`: `py311` → `py312` -- 삭제: `PyJWT`, `langsmith`, `langchain-openai`, `langchain-daytona`, `langchain-modal`, `langchain-runloop` -- 추가: `docker`, `pydantic-settings`, `slowapi`, `discord.py` - -- [ ] **Step 2: 의존성 설치 테스트** - -```bash -uv sync -``` - -Expected: 모든 의존성 설치 성공 (ARM64 호환성 확인) - -- [ ] **Step 3: 커밋** - -```bash -git add pyproject.toml uv.lock -git commit -m "chore: update dependencies for galaxis-agent" -``` - ---- - -## Task 5: 환경변수 설정 모듈 생성 - -**Files:** -- Create: `agent/config.py` -- Create: `.env.example` -- Create: `tests/test_config.py` - -- [ ] **Step 1: 테스트 작성** - -```python -# tests/test_config.py -import pytest -from agent.config import Settings - - -@pytest.fixture -def test_settings(monkeypatch): - """테스트용 환경변수로 Settings 인스턴스 생성""" - monkeypatch.setenv("ANTHROPIC_API_KEY", "test-key") - monkeypatch.setenv("GITEA_TOKEN", "test-token") - monkeypatch.setenv("GITEA_WEBHOOK_SECRET", "test-secret") - monkeypatch.setenv("DISCORD_TOKEN", "test-token") - monkeypatch.setenv("FERNET_KEY", "dGVzdGtleXRlc3RrZXl0ZXN0a2V5dGVzdGtleXg=") - return Settings() - - -def test_config_loads_defaults(test_settings): - """기본값으로 설정이 로딩되는지 확인""" - assert test_settings.GITEA_URL == "http://gitea:3000" - assert test_settings.AUTONOMY_LEVEL == "conservative" - assert test_settings.SANDBOX_TIMEOUT == 600 - assert test_settings.DEFAULT_REPO_OWNER == "quant" - assert test_settings.DEFAULT_REPO_NAME == "galaxis-po" - assert test_settings.SANDBOX_MEM_LIMIT == "4g" - assert test_settings.SANDBOX_CPU_COUNT == 2 - - -def test_config_autonomy_levels(test_settings): - """자율도 레벨 검증""" - assert test_settings.AUTONOMY_LEVEL in ("conservative", "autonomous") - - -def test_writable_paths_include_backend(test_settings): - """writable_paths에 백엔드 경로 포함 확인""" - assert "backend/app/" in test_settings.WRITABLE_PATHS - assert "backend/tests/" in test_settings.WRITABLE_PATHS - assert "backend/alembic/versions/" in test_settings.WRITABLE_PATHS - - -def test_blocked_paths_include_protected_files(test_settings): - """blocked_paths에 보호 파일 포함 확인""" - assert ".env" in test_settings.BLOCKED_PATHS - assert "quant.md" in test_settings.BLOCKED_PATHS - assert "docker-compose.prod.yml" in test_settings.BLOCKED_PATHS -``` - -- [ ] **Step 2: 테스트 실행 — 실패 확인** - -```bash -uv run pytest tests/test_config.py -v -``` - -Expected: FAIL — `agent.config` 모듈 없음 - -- [ ] **Step 3: config.py 구현** - -```python -# agent/config.py -from pydantic_settings import BaseSettings - - -class Settings(BaseSettings): - """galaxis-agent 환경변수 설정""" - - # LLM - ANTHROPIC_API_KEY: str - - # Gitea - GITEA_URL: str = "http://gitea:3000" - GITEA_EXTERNAL_URL: str = "https://ayuriel.duckdns.org" - GITEA_TOKEN: str - GITEA_WEBHOOK_SECRET: str - - # Discord - DISCORD_TOKEN: str - DISCORD_CHANNEL_ID: str = "" - DISCORD_BOT_USER_ID: str = "" - - # LangGraph - LANGGRAPH_URL: str = "http://langgraph-server:8123" - - # Agent - AUTONOMY_LEVEL: str = "conservative" - DEFAULT_REPO_OWNER: str = "quant" - DEFAULT_REPO_NAME: str = "galaxis-po" - AGENT_API_KEY: str = "" - - # Sandbox - SANDBOX_IMAGE: str = "galaxis-sandbox:latest" - SANDBOX_MEM_LIMIT: str = "4g" - SANDBOX_CPU_COUNT: int = 2 - SANDBOX_TIMEOUT: int = 600 - SANDBOX_PIDS_LIMIT: int = 256 - - # Database (테스트용) - TEST_DATABASE_URL: str = "" - - # 암호화 - FERNET_KEY: str = "" - - # 경로 접근 제어 - WRITABLE_PATHS: list[str] = [ - "backend/app/", - "backend/tests/", - "frontend/src/", - "backend/alembic/versions/", - "docs/", - ] - BLOCKED_PATHS: list[str] = [ - ".env", - "docker-compose.prod.yml", - "quant.md", - ] - - # 자동 머지 - AUTO_MERGE: bool = False - REQUIRE_TESTS: bool = True - REQUIRE_E2E: bool = False - MAX_FILES_CHANGED: int = 10 - - # 비용 제한 - DAILY_COST_LIMIT_USD: float = 10.0 - PER_TASK_COST_LIMIT_USD: float = 3.0 - - model_config = {"env_file": ".env", "extra": "ignore"} - - -settings = Settings() -``` - -- [ ] **Step 4: 테스트 실행 — 성공 확인** - -```bash -uv run pytest tests/test_config.py -v -``` - -Expected: 4 passed - -- [ ] **Step 5: .env.example 생성** - -```bash -# .env.example - -# LLM -ANTHROPIC_API_KEY=sk-ant-... - -# Gitea -GITEA_URL=http://gitea:3000 -GITEA_EXTERNAL_URL=https://ayuriel.duckdns.org -GITEA_TOKEN= -GITEA_WEBHOOK_SECRET= - -# Discord -DISCORD_TOKEN= -DISCORD_CHANNEL_ID= -DISCORD_BOT_USER_ID= - -# LangGraph -LANGGRAPH_URL=http://langgraph-server:8123 -# LANGSMITH_API_KEY= # 선택: 트레이싱용 - -# Agent -AUTONOMY_LEVEL=conservative -DEFAULT_REPO_OWNER=quant -DEFAULT_REPO_NAME=galaxis-po -AGENT_API_KEY= - -# Sandbox -SANDBOX_IMAGE=galaxis-sandbox:latest -SANDBOX_MEM_LIMIT=4g -SANDBOX_CPU_COUNT=2 -SANDBOX_TIMEOUT=600 - -# Database (테스트용) -TEST_DATABASE_URL=postgresql://user:pass@postgres:5432/galaxis_test - -# 암호화 -FERNET_KEY= -``` - -- [ ] **Step 6: 커밋** - -```bash -git add agent/config.py tests/test_config.py .env.example -git commit -m "feat: add config module with pydantic-settings" -``` - ---- - -## Task 6: 스텁 파일 생성 (Phase 2 준비) - -**Files:** -- Create: `agent/integrations/docker_sandbox.py` -- Create: `agent/utils/gitea_client.py` -- Create: `agent/utils/discord_client.py` -- Create: `agent/tools/gitea_comment.py` -- Create: `agent/tools/discord_reply.py` - -- [ ] **Step 1: DockerSandbox 스텁** - -```python -# agent/integrations/docker_sandbox.py -"""Docker 컨테이너 기반 샌드박스 백엔드. - -SandboxBackendProtocol을 구현하여 docker-py로 격리된 실행 환경을 제공한다. -Phase 2에서 구현 예정. -""" - - -class DockerSandbox: - """docker-py 기반 샌드박스 백엔드 (Phase 2 구현 예정)""" - - async def execute(self, command: str, timeout: int = 300): - raise NotImplementedError("Phase 2에서 구현") - - async def read_file(self, path: str) -> str: - raise NotImplementedError("Phase 2에서 구현") - - async def write_file(self, path: str, content: str) -> None: - raise NotImplementedError("Phase 2에서 구현") - - async def close(self) -> None: - raise NotImplementedError("Phase 2에서 구현") -``` - -- [ ] **Step 2: GiteaClient 스텁** - -```python -# agent/utils/gitea_client.py -"""Gitea REST API v1 클라이언트. - -Phase 2에서 구현 예정. -""" - -import httpx - - -class GiteaClient: - """Gitea REST API 클라이언트 (Phase 2 구현 예정)""" - - def __init__(self, base_url: str, token: str): - self.base_url = base_url.rstrip("/") - self.token = token - self._client = httpx.AsyncClient( - base_url=f"{self.base_url}/api/v1", - headers={"Authorization": f"token {self.token}"}, - ) - - async def create_pull_request( - self, owner: str, repo: str, title: str, head: str, base: str, body: str - ) -> dict: - raise NotImplementedError("Phase 2에서 구현") - - async def merge_pull_request( - self, owner: str, repo: str, pr_number: int, merge_type: str = "merge" - ) -> dict: - raise NotImplementedError("Phase 2에서 구현") - - async def create_issue_comment( - self, owner: str, repo: str, issue_number: int, body: str - ) -> dict: - raise NotImplementedError("Phase 2에서 구현") - - async def get_issue(self, owner: str, repo: str, issue_number: int) -> dict: - raise NotImplementedError("Phase 2에서 구현") - - async def get_issue_comments( - self, owner: str, repo: str, issue_number: int - ) -> list: - raise NotImplementedError("Phase 2에서 구현") - - async def create_branch( - self, owner: str, repo: str, branch_name: str, old_branch: str - ) -> dict: - raise NotImplementedError("Phase 2에서 구현") - - async def close(self): - await self._client.aclose() -``` - -- [ ] **Step 3: DiscordClient 스텁** - -```python -# agent/utils/discord_client.py -"""Discord bot 연동 클라이언트. - -Phase 2에서 구현 예정. -""" - - -class DiscordClient: - """Discord 메시지 수신/발송 (Phase 2 구현 예정)""" - - async def send_message(self, channel_id: str, content: str) -> dict: - raise NotImplementedError("Phase 2에서 구현") - - async def send_thread_reply( - self, channel_id: str, thread_id: str, content: str - ) -> dict: - raise NotImplementedError("Phase 2에서 구현") -``` - -- [ ] **Step 4: gitea_comment 도구 스텁** - -```python -# agent/tools/gitea_comment.py -"""Gitea 이슈/PR 코멘트 작성 도구. - -Phase 2에서 구현 예정. -""" - - -def gitea_comment(message: str, issue_number: int) -> dict: - """Gitea 이슈 또는 PR에 코멘트를 작성한다.""" - raise NotImplementedError("Phase 2에서 구현") -``` - -- [ ] **Step 5: discord_reply 도구 스텁** - -```python -# agent/tools/discord_reply.py -"""Discord 채널/스레드 메시지 전송 도구. - -Phase 2에서 구현 예정. -""" - - -def discord_reply(message: str) -> dict: - """Discord 채널 또는 스레드에 메시지를 전송한다.""" - raise NotImplementedError("Phase 2에서 구현") -``` - -- [ ] **Step 6: 커밋** - -```bash -git add agent/integrations/docker_sandbox.py agent/utils/gitea_client.py \ - agent/utils/discord_client.py agent/tools/gitea_comment.py agent/tools/discord_reply.py -git commit -m "feat: add stub modules for Phase 2 (Docker, Gitea, Discord)" -``` - ---- - -## Task 7: 기존 파일 수정 — import 및 참조 정리 - -**Files:** -- Modify: `agent/tools/__init__.py` -- Modify: `agent/integrations/__init__.py` -- Modify: `agent/utils/sandbox.py` -- Modify: `agent/utils/auth.py` -- Modify: `langgraph.json` -- Modify: `Makefile` - -- [ ] **Step 1: agent/tools/__init__.py 수정** - -삭제된 도구 import를 제거하고 새 스텁 도구를 추가한다. - -원본에서 `linear_comment`, `slack_thread_reply`, `github_comment`를 제거하고 `gitea_comment`, `discord_reply`를 추가: - -```python -# agent/tools/__init__.py -from agent.tools.commit_and_open_pr import commit_and_open_pr -from agent.tools.discord_reply import discord_reply -from agent.tools.fetch_url import fetch_url -from agent.tools.gitea_comment import gitea_comment -from agent.tools.http_request import http_request - -__all__ = [ - "commit_and_open_pr", - "discord_reply", - "fetch_url", - "gitea_comment", - "http_request", -] -``` - -- [ ] **Step 2: agent/integrations/__init__.py 수정** - -클라우드 프로바이더 import를 제거하고 Docker 스텁만 남긴다. 원본 파일을 읽고, 삭제된 프로바이더 참조를 제거한다. 최종 내용: - -```python -# agent/integrations/__init__.py -from agent.integrations.docker_sandbox import DockerSandbox - -__all__ = ["DockerSandbox"] -``` - -- [ ] **Step 3: agent/utils/sandbox.py 수정** - -클라우드 프로바이더 팩토리를 제거하고 Docker 샌드박스만 사용하도록 변경: - -```python -# agent/utils/sandbox.py -from agent.integrations.docker_sandbox import DockerSandbox - - -def create_sandbox(sandbox_id: str | None = None) -> DockerSandbox: - """Docker 샌드박스를 생성하거나 기존 것에 연결한다.""" - return DockerSandbox() # Phase 2에서 실제 구현 -``` - -- [ ] **Step 4: agent/encryption.py 수정** - -원본은 `TOKEN_ENCRYPTION_KEY` 환경변수를 사용한다. `config.py`의 `FERNET_KEY`로 통일: - -```python -# agent/encryption.py — 수정할 부분 -# 변경 전: key = os.environ.get("TOKEN_ENCRYPTION_KEY") -# 변경 후: -from agent.config import settings - -def _get_key() -> bytes | None: - if settings.FERNET_KEY: - return settings.FERNET_KEY.encode() - return None -``` - -원본 파일을 읽고 `TOKEN_ENCRYPTION_KEY` 참조를 `settings.FERNET_KEY`로 교체한다. - -- [ ] **Step 5: agent/utils/auth.py 수정** - -GitHub OAuth 관련 코드를 제거하고 Gitea 토큰 기반 인증으로 교체. 원본 파일을 읽고 다음으로 교체: - -```python -# agent/utils/auth.py -"""Gitea 토큰 기반 인증. - -GitHub OAuth를 제거하고 Gitea Application Token을 사용한다. -""" -from agent.config import settings -from agent.encryption import encrypt_token, decrypt_token - - -async def get_gitea_token() -> str: - """설정에서 Gitea 토큰을 가져온다.""" - return settings.GITEA_TOKEN - - -async def get_encrypted_gitea_token() -> tuple[str, str]: - """Gitea 토큰과 암호화된 버전을 반환한다.""" - token = settings.GITEA_TOKEN - encrypted = encrypt_token(token) if settings.FERNET_KEY else token - return token, encrypted -``` - -- [ ] **Step 5: langgraph.json 및 Makefile 수정** - -`langgraph.json`: -```json -{ - "python_version": "3.12", - "dependencies": ["."], - "graphs": { - "agent": "agent.server:get_agent" - }, - "http": { - "app": "agent.webapp:app" - }, - "env": ".env" -} -``` - -`Makefile`: `open-swe` 문자열을 `galaxis-agent`로 변경. - -- [ ] **Step 6: 커밋** - -```bash -git add agent/tools/__init__.py agent/integrations/__init__.py \ - agent/utils/sandbox.py agent/utils/auth.py langgraph.json Makefile -git commit -m "refactor: clean up imports and references after code removal" -``` - ---- - -## Task 8: server.py 정리 — 의존성 교체 (핵심 작업) - -**Files:** -- Modify: `agent/server.py` - -이 파일은 에이전트의 핵심이며 삭제된 모듈에 대한 깊은 의존성이 있다. 신중하게 수정한다. - -### Step 8a: import 정리 - -- [ ] **원본 server.py 읽기** - -전체 파일을 읽고 변경 포인트를 파악한다. - -- [ ] **삭제할 import 제거** - -```python -# 삭제: -from langsmith.sandbox import SandboxClientError # langsmith 패키지 제거됨 -from .tools import linear_comment, slack_thread_reply, github_comment -from .utils.github_token import get_github_token_from_thread -from .utils.github import ... # 모든 GitHub API 함수 -``` - -- [ ] **대체 import 추가** - -```python -# 추가: -from agent.config import settings -from agent.tools import gitea_comment, discord_reply -from agent.utils.auth import get_gitea_token -from agent.utils.git_utils import ( # github.py → git_utils.py - git_checkout_branch, - git_has_uncommitted_changes, - # 등 필요한 git 유틸리티 -) - -# SandboxClientError 대체 (langsmith 제거 후) -class SandboxConnectionError(Exception): - """샌드박스 연결 실패 시 발생하는 예외""" - pass -``` - -### Step 8b: get_agent() 함수 수정 - -- [ ] **도구 목록 변경** - -```python -# 변경 전: -tools=[http_request, fetch_url, commit_and_open_pr, linear_comment, slack_thread_reply, github_comment] - -# 변경 후: -tools=[http_request, fetch_url, commit_and_open_pr, gitea_comment, discord_reply] -``` - -- [ ] **GitHub 토큰 → Gitea 토큰 교체** - -`resolve_github_token()` 호출을 `get_gitea_token()`으로 교체. -`github_token` 변수를 `gitea_token`으로 변경. - -### Step 8c: _clone_or_pull_repo_in_sandbox() 수정 - -- [ ] **GitHub URL → Gitea 내부 URL로 변경** - -```python -# 변경 전: github.com 참조 -# 변경 후: -clone_url = f"http://gitea:3000/{owner}/{repo}.git" -``` - -- [ ] **credential helper 패턴 적용** - -```python -# git credential helper 설정 (URL에 토큰 미노출) -await sandbox.execute( - f'echo "http://agent:{gitea_token}@gitea:3000" > /tmp/.git-credentials' -) -await sandbox.execute( - "git config --global credential.helper 'store --file=/tmp/.git-credentials'" -) -``` - -### Step 8d: sandbox_state 의존성 처리 - -- [ ] **sandbox_state.py 확인** - -`agent/utils/sandbox_state.py`를 읽고 `langgraph_sdk` 의존성을 확인한다. -이 파일은 `SANDBOX_BACKENDS` 딕셔너리와 스레드 메타데이터 접근을 제공한다. -`langgraph_sdk.get_client()` 호출 부분이 있으면 Phase 2에서 구현할 LangGraph/자체 상태 관리로 대체할 스텁을 준비한다. - -- [ ] **SandboxClientError → SandboxConnectionError 교체** - -`server.py`에서 `SandboxClientError` catch를 `SandboxConnectionError` (또는 일반 `Exception`)로 교체. - -### Step 8e: 컴파일 검증 - -- [ ] **import 확인** - -```bash -uv run python -c "import agent.server; print('OK')" -``` - -Expected: import 성공. `langgraph_sdk` 관련 런타임 에러는 허용 (서버 기동 시에만 발생). - -- [ ] **커밋** - -```bash -git add agent/server.py -git commit -m "refactor: replace GitHub/Linear/Slack with Gitea/Discord in server.py" -``` - ---- - -## Task 9: webapp.py 정리 — Gitea webhook 스텁 - -**Files:** -- Modify: `agent/webapp.py` - -- [ ] **Step 1: 원본 webapp.py 읽기** - -전체 파일을 읽고 변경 포인트를 파악한다. - -- [ ] **Step 2: GitHub/Linear/Slack webhook 핸들러 제거, Gitea 스텁으로 교체** - -최소한의 FastAPI 앱으로 재작성: - -```python -# agent/webapp.py -"""galaxis-agent webhook 서버. - -Gitea webhook과 Discord bot 이벤트를 수신한다. -""" -import hashlib -import hmac -import logging - -from fastapi import FastAPI, Request, HTTPException - -from agent.config import settings - -logger = logging.getLogger(__name__) - -app = FastAPI(title="galaxis-agent") - - -def verify_gitea_signature(payload: bytes, signature: str, secret: str) -> bool: - """Gitea webhook HMAC-SHA256 서명을 검증한다.""" - expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest() - return hmac.compare_digest(expected, signature) - - -def generate_thread_id(repo: str, issue_id: int) -> str: - """이슈 기반 결정론적 스레드 ID를 생성한다.""" - raw = hashlib.sha256(f"gitea-issue:{repo}:{issue_id}".encode()).hexdigest() - # UUID 포맷으로 변환 - return f"{raw[:8]}-{raw[8:12]}-{raw[12:16]}-{raw[16:20]}-{raw[20:32]}" - - -@app.get("/health") -async def health(): - return {"status": "ok"} - - -@app.post("/webhooks/gitea") -async def gitea_webhook(request: Request): - """Gitea webhook 수신 엔드포인트. Phase 3에서 완전 구현.""" - body = await request.body() - signature = request.headers.get("X-Gitea-Signature", "") - - if not verify_gitea_signature(body, signature, settings.GITEA_WEBHOOK_SECRET): - raise HTTPException(status_code=401, detail="Invalid signature") - - # Phase 3에서 구현: 이벤트 파싱, 에이전트 디스패치 - logger.info("Gitea webhook received (not yet implemented)") - return {"status": "received"} -``` - -- [ ] **Step 3: 컴파일 확인** - -```bash -uv run python -c "from agent.webapp import app; print(app.title)" -``` - -Expected: `galaxis-agent` - -- [ ] **Step 4: 커밋** - -```bash -git add agent/webapp.py -git commit -m "refactor: replace GitHub/Linear/Slack webhooks with Gitea stub" -``` - ---- - -## Task 10: prompt.py 정리 - -**Files:** -- Modify: `agent/prompt.py` - -- [ ] **Step 1: 원본 prompt.py 읽기** - -전체 파일을 읽고 GitHub 전용 섹션을 파악한다. - -- [ ] **Step 2: GitHub 전용 프롬프트 섹션 수정** - -- `EXTERNAL_UNTRUSTED_COMMENTS_SECTION`: "GitHub" → "Gitea" 참조 변경 -- `COMMIT_PR_SECTION`: "GitHub" PR 참조 → "Gitea" PR로 변경 -- `TOOL_USAGE_SECTION`: `github_comment` → `gitea_comment`, `slack_thread_reply` → `discord_reply` 변경 -- `construct_system_prompt()`: `linear_project_id`, `linear_issue_number` 매개변수 제거 - -- [ ] **Step 3: 컴파일 확인** - -```bash -uv run python -c "from agent.prompt import construct_system_prompt; print('OK')" -``` - -- [ ] **Step 4: 커밋** - -```bash -git add agent/prompt.py -git commit -m "refactor: update system prompt for Gitea/Discord" -``` - ---- - -## Task 11: commit_and_open_pr.py 정리 - -**Files:** -- Modify: `agent/tools/commit_and_open_pr.py` - -- [ ] **Step 1: 원본 읽기** - -전체 파일을 읽고 GitHub API 호출 부분과 git 유틸리티 사용을 파악한다. -이 파일은 `from agent.utils.github import ...`로 12개 함수를 import한다. - -- [ ] **Step 2: import를 git_utils.py로 변경 + GitHub API를 Gitea 스텁으로 교체** - -```python -# 변경 전: -from agent.utils.github import git_add_all, git_commit, git_push, ... -from agent.utils.github import create_github_pr, get_github_default_branch - -# 변경 후: -from agent.utils.git_utils import git_add_all, git_commit, git_push, ... -from agent.utils.gitea_client import GiteaClient # Phase 2에서 구현 -``` - -- `PyGithub` import 제거 -- `create_github_pr()` → `GiteaClient.create_pull_request()` 스텁 호출로 교체 -- git push URL에서 `github.com` → Gitea 내부 URL 변경 -- git user 설정: `open-swe[bot]` → `galaxis-agent[bot]` -- 브랜치 패턴: `open-swe/{thread_id}` → `galaxis-agent/{thread_id}` - -- [ ] **Step 3: 컴파일 확인** - -```bash -uv run python -c "from agent.tools.commit_and_open_pr import commit_and_open_pr; print('OK')" -``` - -- [ ] **Step 4: 커밋** - -```bash -git add agent/tools/commit_and_open_pr.py -git commit -m "refactor: replace GitHub with Gitea in commit_and_open_pr" -``` - ---- - -## Task 12: middleware/open_pr.py 정리 - -**Files:** -- Modify: `agent/middleware/open_pr.py` - -- [ ] **Step 1: 원본 읽기 및 의존성 파악** - -이 파일도 `from agent.utils.github import ...`로 다수의 git 유틸리티를 import한다. - -- [ ] **Step 2: import를 git_utils.py로 변경 + GitHub API 제거** - -```python -# 변경 전: -from agent.utils.github import git_add_all, git_commit, git_push, create_github_pr, ... - -# 변경 후: -from agent.utils.git_utils import git_add_all, git_commit, git_push, ... -from agent.utils.gitea_client import GiteaClient # Phase 2에서 구현 -``` - -- `create_github_pr()` 호출 → `GiteaClient.create_pull_request()` 스텁으로 교체 -- 브랜치 패턴: `open-swe/` → `galaxis-agent/` - -- [ ] **Step 2: 커밋** - -```bash -git add agent/middleware/open_pr.py -git commit -m "refactor: update open_pr middleware for Gitea" -``` - ---- - -## Task 13: 기존 테스트 정리 및 수정 - -**Files:** -- Modify: `tests/test_ensure_no_empty_msg.py` -- Modify: `tests/test_multimodal.py` -- Modify: `tests/test_sandbox_paths.py` - -- [ ] **Step 1: 남아있는 테스트 파일 확인** - -```bash -ls tests/ -``` - -- [ ] **Step 2: 각 테스트 파일에서 삭제된 모듈 import 수정** - -GitHub/Linear/Slack 관련 import가 있으면 제거하거나 Gitea/Discord로 교체. - -- [ ] **Step 3: 테스트 실행** - -```bash -uv run pytest tests/ -v -``` - -Expected: 남은 테스트 전부 통과 (또는 Phase 2 의존성으로 인한 skip) - -- [ ] **Step 4: 커밋** - -```bash -git add tests/ -git commit -m "test: fix remaining tests after code cleanup" -``` - ---- - -## Task 14: Dockerfile 작성 (에이전트 서버 + 샌드박스) - -**Files:** -- Create: `Dockerfile` (덮어쓰기) -- Create: `Dockerfile.sandbox` - -- [ ] **Step 1: 에이전트 서버 Dockerfile** - -```dockerfile -# Dockerfile - galaxis-agent 서버 -FROM python:3.12-slim - -RUN apt-get update && apt-get install -y --no-install-recommends \ - git curl && \ - rm -rf /var/lib/apt/lists/* - -# uv 설치 -RUN curl -LsSf https://astral.sh/uv/install.sh | sh -ENV PATH="/root/.local/bin:$PATH" - -WORKDIR /app - -# 의존성 먼저 설치 (캐시 활용) -COPY pyproject.toml uv.lock ./ -RUN uv sync --frozen --no-dev - -# 소스 코드 복사 -COPY agent/ ./agent/ -COPY langgraph.json ./ - -# non-root 사용자 -RUN useradd -m -u 1000 agent -USER agent - -EXPOSE 8000 - -CMD ["uv", "run", "uvicorn", "agent.webapp:app", "--host", "0.0.0.0", "--port", "8000"] -``` - -- [ ] **Step 2: 샌드박스 Dockerfile** - -```dockerfile -# Dockerfile.sandbox - 작업 컨테이너 -FROM python:3.12-slim - -RUN apt-get update && apt-get install -y --no-install-recommends \ - git curl postgresql-client && \ - rm -rf /var/lib/apt/lists/* - -# uv 설치 -RUN curl -LsSf https://astral.sh/uv/install.sh | sh -ENV PATH="/root/.local/bin:$PATH" - -# Node.js LTS -RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && \ - apt-get install -y nodejs && \ - rm -rf /var/lib/apt/lists/* - -WORKDIR /workspace - -# 컨테이너가 종료되지 않도록 대기 -CMD ["tail", "-f", "/dev/null"] -``` - -- [ ] **Step 3: 빌드 테스트 (로컬 아키텍처)** - -```bash -docker build -t galaxis-agent:latest . -docker build -f Dockerfile.sandbox -t galaxis-sandbox:latest . -``` - -Expected: 두 이미지 모두 빌드 성공 - -- [ ] **Step 4: 커밋** - -```bash -git add Dockerfile Dockerfile.sandbox -git commit -m "feat: add Dockerfiles for agent server and sandbox (ARM64)" -``` - ---- - -## Task 15: docker-compose.yml 작성 - -**Files:** -- Create: `docker-compose.yml` - -- [ ] **Step 1: docker-compose.yml 작성** - -```yaml -# docker-compose.yml - galaxis-agent 전체 서비스 구성 -services: - docker-socket-proxy: - image: tecnativa/docker-socket-proxy:latest - environment: - - CONTAINERS=1 - - POST=1 - - EXEC=1 - - IMAGES=1 - - NETWORKS=0 - - VOLUMES=0 - - SERVICES=0 - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - networks: - - galaxis-net - restart: unless-stopped - - galaxis-agent: - build: . - image: galaxis-agent:latest - restart: unless-stopped - user: "1000:1000" - ports: - - "8100:8000" - env_file: .env - environment: - - DOCKER_HOST=tcp://docker-socket-proxy:2375 - volumes: - - uv-cache:/cache/uv - - npm-cache:/cache/npm - - agent-data:/data - networks: - - galaxis-net - depends_on: - - docker-socket-proxy - -networks: - galaxis-net: - external: true - -volumes: - uv-cache: - npm-cache: - agent-data: -``` - -참고: LangGraph Server는 Phase 1에서 ARM64 호환성 확인 후 추가한다. 문제가 있으면 자체 상태 관리로 대체 (스펙 섹션 2 참조). - -- [ ] **Step 2: docker-compose 구문 검증** - -```bash -docker compose config -``` - -Expected: 구문 오류 없음 (네트워크가 없으면 경고 가능) - -- [ ] **Step 3: 커밋** - -```bash -git add docker-compose.yml -git commit -m "feat: add docker-compose with socket proxy and agent server" -``` - ---- - -## Task 16: ARM64 호환성 검증 - -**Files:** 없음 (검증만) - -- [ ] **Step 1: deepagents 패키지 ARM64 설치 확인** - -```bash -uv run python -c "import deepagents; print(deepagents.__version__)" -``` - -Expected: 버전 출력 성공. 실패 시 `langchain`의 `create_react_agent`로 대체 계획 수립. - -- [ ] **Step 2: langgraph 패키지 ARM64 설치 확인** - -```bash -uv run python -c "import langgraph; print(langgraph.__version__)" -``` - -Expected: 버전 출력 성공. - -- [ ] **Step 3: docker-py ARM64 설치 확인** - -```bash -uv run python -c "import docker; print(docker.__version__)" -``` - -Expected: 버전 출력 성공. - -- [ ] **Step 4: discord.py ARM64 설치 확인** - -```bash -uv run python -c "import discord; print(discord.__version__)" -``` - -Expected: 버전 출력 성공. - -- [ ] **Step 5: LangGraph Server ARM64 Docker 이미지 확인** - -```bash -docker pull langchain/langgraph-api:latest -docker run --rm langchain/langgraph-api:latest echo "OK" -``` - -Expected: 이미지 pull 및 실행 성공. ARM64 이미지가 없으면 스펙 섹션 2의 대안(SQLite 기반 자체 상태 관리)으로 전환 결정을 기록한다. - -- [ ] **Step 6: 결과 기록** - -모든 패키지 호환 시: Phase 2로 진행. -일부 실패 시: 대안 패키지로 교체하고 영향받는 코드 수정 계획 수립. - ---- - -## Task 17: 전체 빌드 & 기동 검증 - -**Files:** 없음 (검증만) - -- [ ] **Step 1: Docker 이미지 빌드** - -```bash -docker build -t galaxis-agent:latest . -docker build -f Dockerfile.sandbox -t galaxis-sandbox:latest . -``` - -Expected: 두 이미지 모두 빌드 성공 - -- [ ] **Step 2: .env 파일 생성 (테스트용)** - -```bash -cp .env.example .env -# 최소한의 값만 설정 -# ANTHROPIC_API_KEY, GITEA_TOKEN, GITEA_WEBHOOK_SECRET, DISCORD_TOKEN -``` - -- [ ] **Step 3: 에이전트 서버 기동 확인** - -```bash -docker compose up -d galaxis-agent -sleep 5 -curl http://localhost:8100/health -``` - -Expected: `{"status": "ok"}` - -- [ ] **Step 4: 전체 테스트 실행** - -```bash -uv run pytest tests/ -v -``` - -Expected: 모든 테스트 통과 - -- [ ] **Step 5: 정리 및 최종 커밋** - -```bash -docker compose down -git add -A -git commit -m "chore: Phase 1 complete - galaxis-agent base setup" -git push origin master -``` - ---- - -## Phase 1 완료 기준 - -- [ ] Gitea에 `quant/galaxis-agent` 리포 존재 -- [ ] 불필요한 코드 (Linear, Slack, GitHub, 클라우드 샌드박스) 모두 제거 -- [ ] `deepagents`, `langgraph`, `docker-py`, `discord.py` ARM64 설치 확인 -- [ ] `agent/config.py` + 테스트 통과 -- [ ] Phase 2 스텁 파일 생성 (DockerSandbox, GiteaClient, DiscordClient, 도구) -- [ ] Dockerfile (서버 + 샌드박스) 빌드 성공 -- [ ] docker-compose.yml 구성 완료 (docker-socket-proxy 포함) -- [ ] `/health` 엔드포인트 응답 확인 -- [ ] 모든 테스트 통과 diff --git a/docs/superpowers/plans/2026-03-20-galaxis-agent-phase5.md b/docs/superpowers/plans/2026-03-20-galaxis-agent-phase5.md deleted file mode 100644 index 1d74392..0000000 --- a/docs/superpowers/plans/2026-03-20-galaxis-agent-phase5.md +++ /dev/null @@ -1,376 +0,0 @@ -# galaxis-agent Phase 5: Oracle VM 배포 자동화 Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** galaxis-agent를 Oracle VM에 Docker Compose 기반으로 배포하는 자동화 스크립트를 작성한다. - -**Architecture:** `deploy.sh`가 pre-flight 체크(Docker, 네트워크, 환경변수) → 이미지 빌드 → 서비스 시작 → health check retry 검증을 수행한다. `build-sandbox.sh`가 샌드박스 이미지를 별도로 빌드한다. `.env.example`에 Phase 4 변수를 추가한다. - -**Tech Stack:** Bash, Docker Compose, curl - -**Spec:** `docs/superpowers/specs/2026-03-20-galaxis-agent-phase5-deploy-design.md` - -**Working Directory:** `~/workspace/quant/galaxis-agent/` - ---- - -## File Structure - -### 신규 생성 파일 - -``` -deploy.sh # 배포 스크립트 (pre-flight, build, up, health check) -build-sandbox.sh # 샌드박스 이미지 빌드 스크립트 -``` - -### 수정할 파일 - -``` -.env.example # Phase 4 환경변수 추가 (LOG_FORMAT, DAILY_COST_LIMIT_USD, PER_TASK_COST_LIMIT_USD) -``` - ---- - -**Task 의존성:** 3개 Task 모두 독립적 → 병렬 dispatch 가능 (sonnet 충분) - ---- - -## Task 1: deploy.sh 작성 - -배포 스크립트. pre-flight 체크 → 이미지 빌드 → 서비스 시작 → health check 검증. - -**Files:** -- Create: `deploy.sh` - -### Step 1a: deploy.sh 작성 - -- [ ] **deploy.sh 작성** - -```bash -#!/usr/bin/env bash -set -euo pipefail - -# galaxis-agent 배포 스크립트 -# Usage: ./deploy.sh [--dry-run] - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$SCRIPT_DIR" - -DRY_RUN=false -if [[ "${1:-}" == "--dry-run" ]]; then - DRY_RUN=true - echo "[DRY-RUN] Docker 명령은 실행하지 않고 출력만 합니다." - echo "" -fi - -HEALTH_URL="${HEALTH_URL:-http://localhost:8100}" -HEALTH_RETRY_INTERVAL=5 -HEALTH_MAX_WAIT=60 - -# 색상 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' - -info() { echo -e "${GREEN}[INFO]${NC} $*"; } -warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } -error() { echo -e "${RED}[ERROR]${NC} $*"; } - -# ── Pre-flight 체크 ──────────────────────────────────── - -info "Pre-flight 체크 시작..." - -# 1. Docker -if ! command -v docker &>/dev/null; then - error "Docker가 설치되어 있지 않습니다." - echo " 설치: https://docs.docker.com/engine/install/" - exit 1 -fi -info "Docker: $(docker --version)" - -# 2. Docker Compose -if ! docker compose version &>/dev/null; then - error "Docker Compose가 설치되어 있지 않습니다." - echo " 설치: https://docs.docker.com/compose/install/" - exit 1 -fi -info "Docker Compose: $(docker compose version --short)" - -# 3. .env 파일 (docker compose config보다 먼저 확인 — env_file 의존) -if [[ ! -f .env ]]; then - error ".env 파일이 없습니다." - echo " cp .env.example .env 후 값을 설정하세요." - exit 1 -fi -info ".env 파일 OK" - -# 4. 필수 환경변수 검증 (grep 기반 — 셸 환경 오염 방지) -REQUIRED_VARS=( - ANTHROPIC_API_KEY - GITEA_TOKEN - GITEA_WEBHOOK_SECRET - DISCORD_TOKEN - DISCORD_CHANNEL_ID - FERNET_KEY - AGENT_API_KEY -) - -MISSING=() -for var in "${REQUIRED_VARS[@]}"; do - if ! grep -qE "^${var}=.+" .env; then - MISSING+=("$var") - fi -done - -if [[ ${#MISSING[@]} -gt 0 ]]; then - error "필수 환경변수가 설정되지 않았습니다:" - for var in "${MISSING[@]}"; do - echo " - $var" - done - echo " .env 파일을 확인하세요." - exit 1 -fi -info "필수 환경변수 OK (${#REQUIRED_VARS[@]}개 확인)" - -# 5. docker-compose.yml 유효성 -if ! docker compose config --quiet 2>/dev/null; then - error "docker-compose.yml 유효성 검증 실패." - echo " docker compose config 으로 상세 에러를 확인하세요." - exit 1 -fi -info "docker-compose.yml 유효성 OK" - -# 6. galaxis-net 네트워크 -if ! docker network inspect galaxis-net &>/dev/null; then - warn "galaxis-net 네트워크가 없습니다. 생성합니다." - if [[ "$DRY_RUN" == true ]]; then - echo " [DRY-RUN] docker network create galaxis-net" - else - docker network create galaxis-net - fi -fi -info "galaxis-net 네트워크 OK" - -echo "" -info "Pre-flight 체크 완료!" -echo "" - -# ── 배포 ──────────────────────────────────────────────── - -if [[ "$DRY_RUN" == true ]]; then - echo "[DRY-RUN] 실행될 명령:" - echo " docker compose build" - echo " docker compose up -d" - echo " Health check: ${HEALTH_URL}/health (${HEALTH_RETRY_INTERVAL}초 간격, 최대 ${HEALTH_MAX_WAIT}초)" - echo "" - info "Dry-run 완료. 실제 배포는 --dry-run 없이 실행하세요." - exit 0 -fi - -info "galaxis-agent 이미지 빌드 중..." -docker compose build - -info "서비스 시작 중..." -docker compose up -d - -# ── Health check ──────────────────────────────────────── - -info "Health check 시작 (${HEALTH_RETRY_INTERVAL}초 간격, 최대 ${HEALTH_MAX_WAIT}초)..." - -ELAPSED=0 -HEALTHY=false - -while [[ $ELAPSED -lt $HEALTH_MAX_WAIT ]]; do - if curl -sf "${HEALTH_URL}/health" | grep -q '"status".*"ok"' 2>/dev/null; then - HEALTHY=true - break - fi - sleep "$HEALTH_RETRY_INTERVAL" - ELAPSED=$((ELAPSED + HEALTH_RETRY_INTERVAL)) - echo " 대기 중... (${ELAPSED}초)" -done - -if [[ "$HEALTHY" != true ]]; then - error "Health check 실패: ${HEALTH_URL}/health 가 ${HEALTH_MAX_WAIT}초 내에 응답하지 않았습니다." - echo "" - echo " 로그 확인: docker compose logs" - echo " 수동 롤백:" - echo " docker compose down" - echo " git checkout <이전_커밋>" - echo " ./deploy.sh" - exit 1 -fi - -# 개별 health check -ENDPOINTS=("/health" "/health/gitea" "/health/discord" "/health/queue" "/health/costs") -ALL_OK=true - -echo "" -for ep in "${ENDPOINTS[@]}"; do - if curl -sf "${HEALTH_URL}${ep}" >/dev/null 2>&1; then - info " ${ep} ... OK" - else - warn " ${ep} ... FAIL" - ALL_OK=false - fi -done - -echo "" -if [[ "$ALL_OK" == true ]]; then - info "배포 성공! 모든 health check 통과." -else - warn "배포 완료. 일부 health check가 실패했습니다." - echo " 로그 확인: docker compose logs" -fi -``` - -- [ ] **실행 권한 부여** - -```bash -chmod +x deploy.sh -``` - -- [ ] **dry-run으로 검증** - -```bash -# .env가 없는 상태에서 에러 메시지 확인 -./deploy.sh --dry-run -``` - -Expected: `.env` 파일이 없으면 에러 메시지 출력, 있으면 pre-flight 체크 후 dry-run 명령 출력 - -- [ ] **커밋** - -```bash -git add deploy.sh -git commit -m "feat: add deploy.sh for Oracle VM deployment automation" -``` - ---- - -## Task 2: build-sandbox.sh 작성 - -샌드박스 이미지를 빌드하는 독립 스크립트. - -**Files:** -- Create: `build-sandbox.sh` - -### Step 2a: build-sandbox.sh 작성 - -- [ ] **build-sandbox.sh 작성** - -```bash -#!/usr/bin/env bash -set -euo pipefail - -# galaxis-sandbox 이미지 빌드 스크립트 -# Usage: ./build-sandbox.sh [--dry-run] - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$SCRIPT_DIR" - -DRY_RUN=false -if [[ "${1:-}" == "--dry-run" ]]; then - DRY_RUN=true -fi - -IMAGE_NAME="galaxis-sandbox:latest" -DOCKERFILE="Dockerfile.sandbox" - -# 색상 -RED='\033[0;31m' -GREEN='\033[0;32m' -NC='\033[0m' - -info() { echo -e "${GREEN}[INFO]${NC} $*"; } -error() { echo -e "${RED}[ERROR]${NC} $*"; } - -# Docker 설치 확인 -if ! command -v docker &>/dev/null; then - error "Docker가 설치되어 있지 않습니다." - exit 1 -fi - -# Dockerfile.sandbox 존재 확인 -if [[ ! -f "$DOCKERFILE" ]]; then - error "${DOCKERFILE}이 없습니다." - exit 1 -fi - -if [[ "$DRY_RUN" == true ]]; then - echo "[DRY-RUN] 실행될 명령:" - echo " docker build -f ${DOCKERFILE} -t ${IMAGE_NAME} ." - exit 0 -fi - -info "${IMAGE_NAME} 이미지 빌드 중..." -docker build -f "$DOCKERFILE" -t "$IMAGE_NAME" . - -# 이미지 크기 출력 -SIZE=$(docker image inspect "$IMAGE_NAME" --format='{{.Size}}' | awk '{printf "%.0f", $1/1024/1024}') -info "빌드 완료! 이미지 크기: ${SIZE}MB" -``` - -- [ ] **실행 권한 부여** - -```bash -chmod +x build-sandbox.sh -``` - -- [ ] **dry-run으로 검증** - -```bash -./build-sandbox.sh --dry-run -``` - -Expected: `[DRY-RUN] 실행될 명령: docker build -f Dockerfile.sandbox -t galaxis-sandbox:latest .` - -- [ ] **커밋** - -```bash -git add build-sandbox.sh -git commit -m "feat: add build-sandbox.sh for sandbox image building" -``` - ---- - -## Task 3: .env.example 업데이트 - -Phase 4에서 추가된 환경변수를 `.env.example`에 반영한다. - -**Files:** -- Modify: `~/workspace/quant/galaxis-agent/.env.example` - -### Step 3a: .env.example 수정 - -- [ ] **.env.example에 Phase 4 변수 추가** - -파일 끝(`# Encryption` 섹션 다음)에 추가: - -```bash -# Logging -LOG_FORMAT=json - -# Cost Guard -DAILY_COST_LIMIT_USD=10.0 -PER_TASK_COST_LIMIT_USD=3.0 -``` - -- [ ] **커밋** - -```bash -git add .env.example -git commit -m "docs: add Phase 4 environment variables to .env.example" -``` - ---- - -## Phase 5 완료 기준 - -- [ ] `deploy.sh`가 pre-flight 체크 → 이미지 빌드 → 서비스 시작 → health check retry 검증을 수행한다 -- [ ] `deploy.sh --dry-run`이 Docker 명령 없이 pre-flight 체크와 명령 출력만 수행한다 -- [ ] `build-sandbox.sh`가 샌드박스 이미지를 빌드하고 크기를 출력한다 -- [ ] `build-sandbox.sh --dry-run`이 명령 출력만 수행한다 -- [ ] `.env.example`에 Phase 4 환경변수(LOG_FORMAT, DAILY_COST_LIMIT_USD, PER_TASK_COST_LIMIT_USD)가 포함되어 있다 -- [ ] 모든 기존 테스트 통과 (139개) diff --git a/docs/superpowers/specs/2026-03-20-galaxis-agent-cicd-design.md b/docs/superpowers/specs/2026-03-20-galaxis-agent-cicd-design.md deleted file mode 100644 index cc24daa..0000000 --- a/docs/superpowers/specs/2026-03-20-galaxis-agent-cicd-design.md +++ /dev/null @@ -1,131 +0,0 @@ -# galaxis-agent Gitea Actions CI/CD 설계 스펙 - -## Goal - -galaxis-agent 리포에 Gitea Actions 기반 CI/CD를 구성한다. PR에서는 테스트만 실행하고, `main` push 시 테스트 통과 후 Oracle VM에 자동 배포한다. - -## Infrastructure - -- **Gitea**: `ssh://git@152.69.231.161:7798/quant/galaxis-agent.git` -- **Oracle VM**: A1 (4코어 ARM64, 24GB) — Gitea + act_runner + Docker 운영 -- **act_runner**: 설치/등록 완료 (galaxis-po에서 사용 중) -- **Runner label**: `ubuntu-latest` - -## Workflow 구성 - -### 1. `.gitea/workflows/ci.yml` — PR 검증 - -**트리거:** PR 생성/업데이트 (target: `main`) - -**파이프라인:** -``` -checkout → uv 설치 → uv sync --frozen → pytest → 결과 리포트 -``` - -**상세:** -- `runs-on: ubuntu-latest` -- Python 3.12-slim 환경 -- `uv sync --frozen`으로 의존성 설치 (lock 파일 기반) -- `uv run python -m pytest --tb=short -q` 실행 -- 외부 서비스 의존성 없음 (모든 테스트 mock 기반, SQLite tempfile) - -### 2. `.gitea/workflows/deploy.yml` — 배포 - -**트리거:** `main` branch push - -**파이프라인:** -``` -checkout → uv 설치 → uv sync → pytest -→ Docker CLI + Compose 설치 -→ .env 생성 (Gitea secrets) -→ galaxis-net 네트워크 확인/생성 -→ build-sandbox.sh 실행 -→ docker compose build → docker compose up -d -→ health check (메인 + 개별 엔드포인트) -``` - -**상세:** - -#### Step: 테스트 -- uv 설치 → `uv sync --frozen` → `uv run python -m pytest --tb=short -q` -- ci.yml과 동일한 환경/명령어 -- 실패 시 배포 중단 - -#### Step: Docker CLI + Compose 설치 -- galaxis-po 패턴과 동일 -- `apt-get install docker.io` + compose plugin 설치 - -#### Step: .env 생성 -- Gitea secrets에서 환경변수 조합 -- 기본값이 있는 변수는 워크플로우에 하드코딩 - -#### Step: 네트워크 확인 -- `docker network inspect galaxis-net` 실패 시 `docker network create galaxis-net` - -#### Step: 샌드박스 이미지 빌드 -- `./build-sandbox.sh` 실행 -- 샌드박스 이미지는 에이전트 컨테이너가 runtime에 사용 (build-time 의존 없음) -- docker compose build 전에 실행하여 이미지 준비 - -#### Step: 배포 -- `docker compose --project-name galaxis-agent build` -- `docker compose --project-name galaxis-agent up -d` - -#### Step: Health check -- 메인: `curl -sf http://localhost:8100/health` (5초 간격, 최대 60초) -- 개별: `/health/gitea`, `/health/discord`, `/health/queue`, `/health/costs` (Phase 3에서 구현 완료) -- 실패 시 워크플로우 fail → 수동 대응 - -## Gitea Secrets - -### 필수 (리포 Settings > Secrets에 등록) - -| Secret | 용도 | -|--------|------| -| `ANTHROPIC_API_KEY` | Claude API | -| `GITEA_TOKEN` | Gitea API 접근 | -| `GITEA_WEBHOOK_SECRET` | Webhook 검증 | -| `DISCORD_TOKEN` | Discord Bot | -| `DISCORD_CHANNEL_ID` | Discord 채널 | -| `DISCORD_BOT_USER_ID` | Discord 봇 ID | -| `FERNET_KEY` | 암호화 키 | -| `AGENT_API_KEY` | Agent API 인증 | - -### 하드코딩 (기본값) - -| 변수 | 값 | -|------|-----| -| `GITEA_URL` | `http://gitea:3000` | -| `GITEA_EXTERNAL_URL` | `https://ayuriel.duckdns.org` | -| `LANGGRAPH_URL` | `http://langgraph-server:8123` | -| `AUTONOMY_LEVEL` | `conservative` | -| `DEFAULT_REPO_OWNER` | `quant` | -| `DEFAULT_REPO_NAME` | `galaxis-po` | -| `SANDBOX_IMAGE` | `galaxis-sandbox:latest` | -| `SANDBOX_MEM_LIMIT` | `4g` | -| `SANDBOX_CPU_COUNT` | `2` | -| `SANDBOX_TIMEOUT` | `600` | -| `LOG_FORMAT` | `json` | -| `DAILY_COST_LIMIT_USD` | `10.0` | -| `PER_TASK_COST_LIMIT_USD` | `3.0` | - -## deploy.sh와의 관계 - -- `deploy.sh`는 수동 배포 도구로 유지 (SSH 접속 시 사용) -- 워크플로우는 `deploy.sh`를 호출하지 않고 step으로 직접 실행 -- pre-flight 체크 중 Docker/Compose/네트워크는 워크플로우에서 처리 - -## 롤백 - -- 자동 롤백 없음 -- health check 실패 시 워크플로우 fail 표시 → 수동 대응 -- 수동 롤백: `docker compose down && git checkout <이전_커밋> && ./deploy.sh` - -## 파일 구조 - -``` -.gitea/ - workflows/ - ci.yml # PR 검증 (테스트만) - deploy.yml # main push → 테스트 + 배포 -``` diff --git a/docs/superpowers/specs/2026-03-20-galaxis-agent-design.md b/docs/superpowers/specs/2026-03-20-galaxis-agent-design.md deleted file mode 100644 index ef329a8..0000000 --- a/docs/superpowers/specs/2026-03-20-galaxis-agent-design.md +++ /dev/null @@ -1,1019 +0,0 @@ -# galaxis-agent: 자율 개발 에이전트 설계 문서 - -## 개요 - -galaxis-po 코드베이스를 자율적으로 개발하는 SWE 에이전트. [open-swe](https://github.com/langchain-ai/open-swe)를 포크하여 LangGraph + Deep Agents 아키텍처를 재활용하고, GitHub 전용 부분을 Gitea/Discord 어댑터로 교체한다. - -### 요구사항 요약 - -| 항목 | 결정 | -|------|------| -| 트리거 | Gitea webhook + Discord bot | -| 실행환경 | Oracle VM A1 (4코어 ARM64/24GB), Docker 샌드박스 | -| LLM | Claude API (Anthropic) | -| 기반 | open-swe 포크/커스터마이즈 | -| 자율도 | conservative(PR만) → E2E 확보 시 autonomous(자동 머지) | -| Gitea | v1.23.1+, `ayuriel.duckdns.org/quant/galaxis-po` | -| Discord | 기존 봇 활용 | - ---- - -## 1. 전체 아키텍처 - -``` -┌──────────────────────────────────────────────────────────────┐ -│ Oracle VM (A1) │ -│ │ -│ ┌──────────┐ ┌─────────────────────────────────────────┐ │ -│ │ Gitea │─▶│ galaxis-agent (FastAPI) │ │ -│ │ (기존) │ │ - Gitea webhook 수신 │ │ -│ └──────────┘ │ - Discord bot 연동 │ │ -│ │ - LangGraph 에이전트 │ │ -│ ┌──────────┐ │ - 작업 관리/스레드 관리 │ │ -│ │ docker- │ └──────────┬────────────────────────────┘ │ -│ │ socket- │◀────────────┤ │ -│ │ proxy │ │ 작업 요청 시 │ -│ └──────────┘ ┌──────────▼──────────────┐ │ -│ │ Docker 샌드박스 컨테이너 │ │ -│ ┌──────────┐ │ - repo clone │ │ -│ │ LangGraph│ │ - 코드 분석/수정 │ │ -│ │ Server │ │ - uv run pytest │ │ -│ │ (스레드 │ │ - git commit & push │ │ -│ │ 관리) │ └─────────────────────────┘ │ -│ └──────────┘ │ -│ │ -│ ┌──────────┐ ┌──────────┐ │ -│ │PostgreSQL│ │ galaxis- │ (기존 galaxis-po 서비스) │ -│ │ (기존) │ │ po app │ │ -│ └──────────┘ └──────────┘ │ -└──────────────────────────────────────────────────────────────┘ - │ │ - ▼ ▼ - ┌───────────┐ ┌────────────┐ - │ Claude API │ │ Discord │ - │ (Anthropic)│ │ (알림/대화) │ - └───────────┘ └────────────┘ -``` - -### 핵심 흐름 - -1. Gitea 이슈에 `@agent` 멘션 또는 Discord에서 작업 요청 -2. galaxis-agent가 webhook/메시지 수신 -3. 결정론적 스레드 ID 생성 (이슈 ID 기반) -4. Docker 컨테이너 생성, 레포 클론 -5. Claude API로 코드 분석 → 수정 → 테스트 -6. Gitea에 PR 생성 + Discord 알림 -7. (E2E 준비 후) 테스트 통과 시 자동 머지 옵션 - -### 컨테이너 구성 - -- **에이전트 서버**: `docker-compose`로 상시 실행 -- **LangGraph Server**: 에이전트 서버와 함께 실행 (스레드/런/스토어 관리) -- **docker-socket-proxy**: Docker API 접근 제한 (보안) -- **작업 컨테이너**: 작업마다 생성/삭제 (`docker-py`로 관리) -- **네트워크**: Gitea와 같은 Docker 네트워크(`galaxis-net`)로 내부 통신 - ---- - -## 2. LangGraph Platform 의존성 - -open-swe는 LangGraph Platform (LangGraph Server)에 의존한다. `webapp.py`의 `langgraph_sdk.get_client()`가 스레드 관리, 런 생성, 키-값 스토어(메시지 큐잉)를 모두 LangGraph Server에 위임한다. - -### 셀프 호스팅 전략 - -LangGraph는 오픈소스 `langgraph-api` 패키지로 셀프 호스팅 가능하다. - -```yaml -# docker-compose.yml 에 LangGraph Server 추가 -services: - langgraph-server: - image: langchain/langgraph-api:latest - environment: - - LANGSMITH_API_KEY=${LANGSMITH_API_KEY} # 선택: 트레이싱용 - volumes: - - langgraph-data:/data - networks: - - galaxis-net - deploy: - resources: - limits: - cpus: "0.5" - memory: 1G -``` - -### 대안: LangGraph 제거 + 자체 상태 관리 - -LangGraph Server 의존성이 무거우면 Phase 2에서 다음으로 대체 가능: - -- **스레드 관리**: SQLite 기반 스레드 테이블 (thread_id, metadata, sandbox_id) -- **런 관리**: asyncio.Queue + 상태 머신 -- **스토어 (메시지 큐)**: SQLite 테이블 (thread_id, pending_messages) - -Phase 1에서 LangGraph Server ARM64 호환성을 검증한 후, 문제가 있으면 자체 구현으로 전환한다. - -### `deepagents` 패키지 호환성 - -`deepagents`는 LangChain 생태계 패키지로, 순수 Python이므로 ARM64 호환에 문제없다. Phase 1에서 `pip install deepagents`로 ARM64 설치를 검증한다. 만약 네이티브 의존성 문제가 발생하면 LangChain의 `create_react_agent`로 대체 가능하다. - ---- - -## 3. 컴포넌트별 변경 계획 - -open-swe 기준으로 유지/교체/신규/삭제 네 카테고리로 구분한다. - -### 유지 (그대로 사용) - -| 컴포넌트 | 파일 | 역할 | -|----------|------|------| -| 에이전트 코어 | `server.py:get_agent()` | Deep Agent 생성, 샌드박스 라이프사이클 | -| 미들웨어 | `middleware/tool_error_handler.py` | 도구 에러 → LLM에 에러 메시지 반환 | -| 미들웨어 | `middleware/ensure_no_empty_msg.py` | 빈 메시지 방지 | -| 미들웨어 | `middleware/check_message_queue.py` | 작업 중 추가 메시지 주입 | -| 미들웨어 | `middleware/open_pr.py` | PR 안전망 (수정 후 교체된 도구 호출) | -| 암호화 | `encryption.py` | Gitea 토큰 암호화 저장 | -| 모델 유틸 | `utils/model.py` | Claude API 모델 초기화 | -| 프롬프트 구조 | `prompt.py` | 모듈식 시스템 프롬프트 (내용은 커스터마이즈) | - -### 교체 (GitHub → Gitea/Discord로 포팅) - -| 원본 | 변경 후 | 변경 내용 | -|------|---------|----------| -| `webapp.py` GitHub webhook | `webapp.py` Gitea webhook | Gitea 서명 검증, 이벤트 페이로드 파싱 변경 | -| `webapp.py` Slack webhook | `webapp.py` Discord gateway | `discord.py` 라이브러리로 bot 이벤트 수신 | -| `tools/commit_and_open_pr.py` | 동일 파일명 | PyGithub → Gitea API | -| `tools/github_comment.py` | `tools/gitea_comment.py` | Gitea 이슈/PR 코멘트 API | -| `tools/slack_thread_reply.py` | `tools/discord_reply.py` | Discord 채널/스레드 메시지 전송 | -| `integrations/langsmith.py` | `integrations/docker_sandbox.py` | `docker-py`로 컨테이너 생성/실행/삭제 | - -### 신규 추가 - -| 컴포넌트 | 역할 | -|----------|------| -| `utils/gitea_client.py` | Gitea REST API 클라이언트 (인증, PR, 코멘트, 머지) | -| `utils/discord_client.py` | Discord bot 연동 (메시지 수신/발송, 스레드 관리) | -| `Dockerfile.sandbox` | 작업 컨테이너용 이미지 (Python 3.12, uv, Node.js, git) | -| `docker-compose.yml` | 에이전트 서버 + LangGraph + docker-socket-proxy + 네트워크 | -| `config.py` | 환경변수 관리 | - -### 삭제 (불필요) - -| 컴포넌트 | 이유 | -|----------|------| -| `tools/linear_comment.py` | Linear 미사용 | -| `integrations/modal.py`, `daytona.py`, `runloop.py` | 클라우드 샌드박스 미사용 | -| `utils/linear_team_repo_map.py` | Linear 미사용 | -| GitHub App 관련 인증 코드 | Gitea 토큰 방식으로 대체 | - ---- - -## 4. Docker 샌드박스 설계 - -### 샌드박스 컨테이너 이미지 (`Dockerfile.sandbox`) - -galaxis-po 개발에 필요한 모든 도구를 포함한 ARM64 호환 이미지. 샌드박스는 코드 실행 전용이므로 `docker-cli`는 포함하지 않는다. - -```dockerfile -FROM python:3.12-slim - -RUN apt-get update && apt-get install -y \ - git curl postgresql-client - -# uv 패키지 매니저 -RUN curl -LsSf https://astral.sh/uv/install.sh | sh - -# Node.js LTS (프론트엔드 빌드/린트) -RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - \ - && apt-get install -y nodejs - -WORKDIR /workspace -``` - -### 컨테이너 라이프사이클 - -``` -작업 요청 수신 - │ - ▼ -┌─ create_sandbox() ──────────────────────────┐ -│ 1. docker.containers.run( │ -│ image="galaxis-sandbox:latest", │ -│ detach=True, │ -│ network="galaxis-net", │ -│ mem_limit="4g", │ -│ cpu_count=2, │ -│ pids_limit=256, │ -│ environment={ │ -│ "DATABASE_URL": test_db_url, │ -│ }, │ -│ ) │ -│ 2. git clone (credential helper) → /workspace/galaxis-po │ -│ 3. uv sync (백엔드 의존성) │ -│ 4. npm ci (프론트엔드 의존성) │ -└──────────────────────────────────────────────┘ - │ - ▼ - 에이전트 작업 (코드 수정, 테스트 실행) - │ - ▼ -┌─ cleanup_sandbox() ─────────────────────────┐ -│ - PR 생성 완료 확인 │ -│ - git credential 파일 삭제 │ -│ - 컨테이너 stop → remove │ -└──────────────────────────────────────────────┘ -``` - -### `SandboxBackendProtocol` 구현 - -```python -class DockerSandbox: - async def execute(self, command: str, timeout: int = 300) -> ExecuteResponse: - """컨테이너 내에서 쉘 명령 실행 (docker exec)""" - - async def read_file(self, path: str) -> str: - """컨테이너 내 파일 읽기""" - - async def write_file(self, path: str, content: str) -> None: - """컨테이너 내 파일 쓰기""" - - async def close(self) -> None: - """credential 정리 후 컨테이너 삭제""" -``` - -### 테스트용 데이터베이스 - -샌드박스 컨테이너가 `uv run pytest`를 실행하려면 PostgreSQL 접근이 필요하다. - -- **단위 테스트**: mock/fixture 기반으로 DB 불필요 -- **E2E 테스트**: 기존 PostgreSQL에 테스트 전용 DB(`galaxis_test`)를 사용 - - 샌드박스 환경변수로 `DATABASE_URL=postgresql://...galaxis_test` 전달 - - 테스트 실행 전 `alembic upgrade head`로 스키마 적용 - - 테스트 완료 후 DB 초기화 (truncate) -- 샌드박스와 PostgreSQL은 같은 `galaxis-net` 네트워크에 있으므로 접근 가능 - -### 리소스 제한 (A1 4코어/24GB 기준) - -**현재 VM 사용량 추정:** - -| 서비스 | CPU | 메모리 | -|--------|-----|--------| -| OS + Docker 데몬 | ~0.5 | ~1GB | -| Gitea | ~0.5 | ~1GB | -| PostgreSQL (기존) | ~0.5 | ~2GB | -| galaxis-po app (기존) | ~0.5 | ~1GB | -| **소계 (기존)** | **~2** | **~5GB** | - -**에이전트 추가분:** - -| 서비스 | CPU | 메모리 | -|--------|-----|--------| -| galaxis-agent 서버 | 0.5 | 1GB | -| LangGraph Server | 0.5 | 1GB | -| docker-socket-proxy | 0.1 | 128MB | -| 작업 컨테이너 (실행 중일 때만) | 1 | 4GB | -| **소계 (에이전트)** | **~2** | **~6GB** | - -**총 예상**: ~4코어 / ~11GB (24GB 중). `npm ci`가 피크 시 추가 2-3GB 사용 가능하나 여유 있음. - -| 항목 | 설정값 | -|------|--------| -| 동시 작업 수 | **1개** (리소스 제약상 직렬 처리, 큐잉으로 대기) | - -### 의존성 캐시 전략 - -- Docker named volume (`galaxis-uv-cache`, `galaxis-npm-cache`)을 컨테이너에 마운트 -- 첫 실행 이후 의존성 설치 시간 대폭 단축 -- 주기적으로(주 1회) 캐시 갱신 - ---- - -## 5. Gitea 연동 설계 - -### Webhook 수신 - -**트리거 이벤트:** - -| 이벤트 | Gitea 타입 | 에이전트 동작 | -|--------|-----------|--------------| -| 이슈 코멘트에 `@agent` 멘션 | `issue_comment` | 이슈 분석 → 코드 수정 → PR 생성 | -| PR 코멘트에 `@agent` 멘션 | `issue_comment` | PR 피드백 반영 → 커밋 추가 | -| 이슈에 특정 라벨 부착 | `issue_label` | 라벨 기반 자동 할당 (예: `agent-fix`) | -| PR 리뷰 요청 | `pull_request` (review_requested) | 코드 리뷰 수행 → 코멘트 | - -**서명 검증:** - -```python -# Gitea: X-Gitea-Signature 헤더 (HMAC-SHA256) -def verify_gitea_signature(payload: bytes, signature: str, secret: str) -> bool: - expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest() - return hmac.compare_digest(expected, signature) -``` - -**결정론적 스레드 ID:** - -```python -thread_id = hashlib.sha256(f"gitea-issue:{repo}:{issue_id}".encode()).hexdigest() -# → UUID 포맷으로 변환 -``` - -**Rate limiting:** - -```python -# webhook 요청 제한 (분당 10회) -from slowapi import Limiter -limiter = Limiter(key_func=get_remote_address) - -@app.post("/webhooks/gitea") -@limiter.limit("10/minute") -async def gitea_webhook(...): - ... -``` - -### Gitea API 클라이언트 - -```python -class GiteaClient: - def __init__(self, base_url: str, token: str): - # base_url: "http://gitea:3000/api/v1" (Docker 내부 통신) - - async def create_pull_request(self, owner, repo, title, head, base, body) -> dict - async def merge_pull_request(self, owner, repo, pr_number, merge_type="merge") -> dict - async def create_issue_comment(self, owner, repo, issue_number, body) -> dict - async def create_pr_review(self, owner, repo, pr_number, body, comments) -> dict - async def get_issue(self, owner, repo, issue_number) -> dict - async def get_issue_comments(self, owner, repo, issue_number) -> list - async def add_label(self, owner, repo, issue_number, labels) -> None - async def create_branch(self, owner, repo, branch_name, old_branch) -> dict -``` - -### 에이전트 → Gitea 작업 흐름 - -``` -이슈 #42: "FactorCalculator에 듀얼 모멘텀 추가" @agent - │ - ▼ -1. Webhook 수신 → 이슈 내용 + 코멘트 파싱 -2. Gitea API로 이슈 상세 + 기존 코멘트 조회 -3. 이슈에 코멘트: "작업을 시작합니다." -4. 샌드박스에서 작업 수행 -5. 브랜치 생성: open-swe/{thread_id} - 커밋 & 푸시 -6. PR 생성 (title, body, base: master, 이슈 #42 참조) -7. 이슈에 코멘트: "PR #43 을 생성했습니다." - Discord에도 알림 전송 -``` - -### Git 인증 (샌드박스 → Gitea) - -open-swe의 credential helper 패턴을 채택한다. URL에 토큰을 노출하지 않는다. - -```python -async def setup_git_credentials(sandbox, gitea_token: str): - """git credential helper로 인증 설정""" - credential_file = "/tmp/.git-credentials" - await sandbox.execute( - f'echo "http://agent:{gitea_token}@gitea:3000" > {credential_file}' - ) - await sandbox.execute( - f"git config --global credential.helper 'store --file={credential_file}'" - ) - -async def cleanup_git_credentials(sandbox): - """credential 파일 삭제""" - await sandbox.execute("rm -f /tmp/.git-credentials") - await sandbox.execute("git config --global --unset credential.helper") -``` - -- Gitea Application Token 사용 (repo 권한) -- 작업 완료 후 credential 파일 즉시 삭제 -- 내부 Docker 네트워크(`galaxis-net`)로 통신 - -### Webhook URL 설정 - -Gitea와 에이전트가 같은 Docker 네트워크에 있으므로, webhook URL은 Docker 내부 포트를 사용한다: - -``` -URL: http://galaxis-agent:8000/webhooks/gitea -``` - -호스트 포트 매핑(`8100:8000`)은 외부 디버깅/모니터링 접근용이다. - ---- - -## 6. Discord 연동 설계 - -### 이중 인터페이스 - -| 방향 | 방식 | 용도 | -|------|------|------| -| Discord → 에이전트 | Bot Gateway (`discord.py`) | 멘션 수신, 작업 요청 | -| 에이전트 → Discord | REST API (httpx) | 알림, 진행 상황, 결과 보고 | - -### 필수 Gateway Intents - -Discord Developer Portal에서 다음 Privileged Intents를 활성화해야 한다: - -- **MESSAGE_CONTENT** — 메시지 본문 읽기 (멘션 내용 파싱) -- **GUILD_MESSAGES** — 서버 메시지 수신 - -```python -intents = discord.Intents.default() -intents.message_content = True -intents.guild_messages = True -bot = commands.Bot(command_prefix="!", intents=intents) -``` - -### Bot Gateway 수신 - -```python -class DiscordHandler: - async def on_message(self, message): - if not self.bot.user.mentioned_in(message): - return - - content = message.content.replace(f"<@{self.bot.user.id}>", "").strip() - - thread_id = hashlib.sha256( - f"discord:{message.channel.id}:{message.id}".encode() - ).hexdigest() - - # 에이전트 실행 또는 메시지 큐잉 -``` - -### 메시지 포맷 - -**작업 요청:** `@agent galaxis-po 이슈 #42 해결해줘` 또는 `@agent factor_calculator.py에 듀얼 모멘텀 추가해줘` - -repo 생략 시 기본값 `galaxis-po` 사용. - -### 알림 흐름 - -``` -작업 시작 → "작업을 시작합니다: 이슈 #42 - FactorCalculator에 듀얼 모멘텀 추가" -작업 중 → "관련 파일 분석 완료: factor_calculator.py, kjb.py" -테스트 → "테스트 실행 중..." → "테스트 통과 (23 passed, 0 failed)" -완료 → "PR #43 생성 완료: feat: add dual momentum" - → "https://ayuriel.duckdns.org/quant/galaxis-po/pulls/43" -(자동 머지) → "PR #43 자동 머지 완료" -``` - -### Discord ↔ Gitea 연결 - -- Discord에서 `이슈 #42` 참조 시 → Gitea API로 이슈 상세 조회 → 컨텍스트로 활용 -- 이슈 없이 자유형 작업 요청도 가능 → PR 본문에 Discord 대화 컨텍스트 포함 - -### Follow-up 대화 - -open-swe의 메시지 큐잉 패턴을 재활용한다. 에이전트 작업 중 추가 메시지가 도착하면 큐에 저장하고 다음 모델 호출 시 주입한다. - -### FastAPI + discord.py 공존 - -```python -async def lifespan(app: FastAPI): - discord_task = asyncio.create_task(discord_bot.start(DISCORD_TOKEN)) - yield - await discord_bot.close() - -app = FastAPI(lifespan=lifespan) -``` - -discord.py의 Gateway 재연결은 라이브러리 내장 기능으로 처리된다. 재연결 실패 시 Gitea 코멘트로 fallback 알림한다. - ---- - -## 7. 시스템 프롬프트 & 자율도 제어 - -### 시스템 프롬프트 구성 - -```python -SYSTEM_PROMPT = "\n\n".join([ - WORKING_ENVIRONMENT, # 유지: 샌드박스 환경 설명 - FILE_MANAGEMENT, # 유지: 파일 작업 규칙 - TASK_EXECUTION, # 유지: 작업 수행 가이드라인 - TOOL_USAGE, # 수정: Gitea/Discord 도구 설명 - CODING_STANDARDS, # 수정: galaxis-po 코딩 컨벤션 - AUTONOMY_LEVEL, # 신규: 자율도 제어 지침 - AGENTS_MD, # 레포 내 AGENTS.md + CLAUDE.md 주입 -]) -``` - -### 프롬프트 로딩 파이프라인 - -open-swe의 `read_agents_md_in_sandbox()` 함수를 확장하여 `AGENTS.md`와 `CLAUDE.md`를 모두 읽는다: - -```python -async def read_repo_instructions(sandbox) -> str: - """AGENTS.md와 CLAUDE.md를 모두 읽어서 프롬프트에 주입""" - sections = [] - - # AGENTS.md: 에이전트 전용 규칙 (우선순위 높음) - agents_md = await sandbox.read_file("/workspace/galaxis-po/AGENTS.md") - if agents_md: - sections.append(f"## Repository Agent Rules\n{agents_md}") - - # CLAUDE.md: 프로젝트 컨벤션 (보충 정보) - claude_md = await sandbox.read_file("/workspace/galaxis-po/CLAUDE.md") - if claude_md: - sections.append(f"## Project Conventions\n{claude_md}") - - return "\n\n".join(sections) -``` - -`GALAXIS_PROJECT_CONTEXT` 프롬프트 섹션은 제거한다. `CLAUDE.md`에서 동일 정보를 로딩하므로 중복이 발생하기 때문이다. - -### AGENTS.md (레포 루트에 배치) - -에이전트 전용 규칙만 담는다. 프로젝트 일반 컨벤션은 `CLAUDE.md`에서 로딩된다. - -```markdown -# AGENTS.md - -## 작업 전 필수 확인 -1. docs/plans/ 에서 관련 설계 문서 확인 -2. 변경 대상 파일을 먼저 읽고 이해한 후 수정 - -## 커밋 규칙 -- conventional commits: feat/fix/refactor/test/docs -- 한 PR에 하나의 논리적 변경만 포함 - -## 테스트 요구사항 -- 비즈니스 로직 변경 시 반드시 단위 테스트 추가/수정 -- PR 생성 전 uv run pytest 통과 필수 - -## 금지 사항 -- quant.md 수정 금지 -- .env 파일 수정 금지 -- 기존 API 스키마 breaking change 금지 (추가는 가능) -- alembic downgrade 실행 금지 - -## 허용 사항 -- alembic migration 파일 생성은 허용 (DB 스키마 변경 시 필수) -``` - -### 자율도 제어 시스템 - -```python -class AgentConfig: - autonomy_level: str = "conservative" # "conservative" | "autonomous" - - # conservative 모드 (기본값) - auto_merge: bool = False - require_tests: bool = True - max_files_changed: int = 10 - - # 경로 접근 제어 (읽기는 전체 허용, 쓰기만 제한) - writable_paths: list = [ - "backend/app/", - "backend/tests/", - "frontend/src/", - "backend/alembic/versions/", # migration 생성 허용 - "docs/", - ] - blocked_paths: list = [ - ".env", - "docker-compose.prod.yml", - "quant.md", - ] - - # autonomous 모드 (E2E 확보 후 전환) - # auto_merge: True - # require_e2e: True -``` - -### 자율도 전환 흐름 - -``` -[conservative] - 작업 완료 → 테스트 통과 → PR 생성 → Discord 알림 → 사람 리뷰 → 수동 머지 - -[autonomous] (config 변경으로 전환) - 작업 완료 → 단위 테스트 통과 → E2E 테스트 통과 → PR 생성 → 자동 머지 → Discord 알림 - -[안전장치] - - E2E 실패 시: 머지하지 않고 Discord에 실패 알림 - - 변경 파일 > max_files_changed: 머지하지 않고 리뷰 요청 - - blocked_paths 수정 감지: 즉시 중단 + 알림 -``` - ---- - -## 8. 보안 설계 - -### Docker 소켓 접근 제한 - -에이전트 서버는 Docker 소켓을 통해 샌드박스 컨테이너를 관리한다. 직접 소켓 마운트는 root 동등 권한을 부여하므로, [docker-socket-proxy](https://github.com/Tecnativa/docker-socket-proxy)로 API 접근을 제한한다. - -```yaml -services: - docker-socket-proxy: - image: tecnativa/docker-socket-proxy:latest - environment: - - CONTAINERS=1 # 컨테이너 생성/관리 허용 - - POST=1 # POST 요청 허용 (컨테이너 생성) - - EXEC=1 # exec 허용 (명령 실행) - - IMAGES=1 # 이미지 조회 허용 (샌드박스 이미지 확인용, 빌드/삭제는 불가) - - NETWORKS=0 # 네트워크 관리 차단 - - VOLUMES=0 # 볼륨 관리 차단 - - SERVICES=0 # 서비스 관리 차단 - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - networks: - - galaxis-net - - galaxis-agent: - environment: - - DOCKER_HOST=tcp://docker-socket-proxy:2375 - # /var/run/docker.sock 마운트하지 않음 -``` - -### 에이전트 서버 보안 - -- 에이전트 서버 컨테이너는 **non-root 사용자**로 실행 -- `/webhooks/gitea`: HMAC-SHA256 서명 검증 필수 -- `/health/*`: 읽기 전용, 민감 정보 미포함 -- `/test/sandbox`: API key 인증 필수 (`X-API-Key` 헤더) - -```python -async def require_api_key(request: Request): - api_key = request.headers.get("X-API-Key") - if api_key != settings.AGENT_API_KEY: - raise HTTPException(status_code=401, detail="Invalid API key") -``` - -### Git 인증 보안 - -- URL에 토큰 미노출 (credential helper 사용) -- 작업 완료 후 credential 파일 즉시 삭제 -- 토큰은 Fernet 암호화 후 스레드 메타데이터에 저장 - -### 샌드박스 격리 - -- 샌드박스 컨테이너는 `galaxis-net` 네트워크에만 접근 (외부 접근은 Claude API + Gitea만) -- Docker 소켓 미마운트 (샌드박스에서 Docker 탈출 불가) -- 리소스 제한 (`mem_limit`, `cpu_count`, `pids_limit`) - ---- - -## 9. 배포 & 운영 구성 - -### Docker Compose - -```yaml -services: - docker-socket-proxy: - image: tecnativa/docker-socket-proxy:latest - environment: - - CONTAINERS=1 - - POST=1 - - EXEC=1 - - IMAGES=0 - - NETWORKS=0 - - VOLUMES=0 - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - networks: - - galaxis-net - restart: unless-stopped - - langgraph-server: - image: langchain/langgraph-api:latest - environment: - - LANGSMITH_API_KEY=${LANGSMITH_API_KEY:-} - volumes: - - langgraph-data:/data - networks: - - galaxis-net - restart: unless-stopped - deploy: - resources: - limits: - cpus: "0.5" - memory: 1G - - galaxis-agent: - build: . - image: galaxis-agent:latest - restart: unless-stopped - user: "1000:1000" - ports: - - "8100:8000" - environment: - - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} - - DOCKER_HOST=tcp://docker-socket-proxy:2375 - - LANGGRAPH_URL=http://langgraph-server:8123 - - GITEA_URL=http://gitea:3000 - - GITEA_EXTERNAL_URL=https://ayuriel.duckdns.org - - GITEA_TOKEN=${GITEA_TOKEN} - - GITEA_WEBHOOK_SECRET=${GITEA_WEBHOOK_SECRET} - - DISCORD_TOKEN=${DISCORD_TOKEN} - - DISCORD_CHANNEL_ID=${DISCORD_CHANNEL_ID} - - SANDBOX_IMAGE=galaxis-sandbox:latest - - AUTONOMY_LEVEL=conservative - - AGENT_API_KEY=${AGENT_API_KEY} - - FERNET_KEY=${FERNET_KEY} - - TEST_DATABASE_URL=${TEST_DATABASE_URL} - volumes: - - uv-cache:/cache/uv - - npm-cache:/cache/npm - networks: - - galaxis-net - deploy: - resources: - limits: - cpus: "1" - memory: 2G - -networks: - galaxis-net: - external: true - -volumes: - uv-cache: - npm-cache: - langgraph-data: -``` - -### Gitea Webhook 설정 - -``` -URL: http://galaxis-agent:8000/webhooks/gitea -Method: POST -Content: application/json -Secret: ${GITEA_WEBHOOK_SECRET} -Events: Issue Comments, Pull Request Comments, - Pull Requests (review_requested), Issues (labeled) -``` - -### 환경변수 - -```bash -# LLM -ANTHROPIC_API_KEY=sk-ant-... - -# Gitea -GITEA_URL=http://gitea:3000 -GITEA_EXTERNAL_URL=https://ayuriel.duckdns.org -GITEA_TOKEN=... -GITEA_WEBHOOK_SECRET=... - -# Discord -DISCORD_TOKEN=... -DISCORD_CHANNEL_ID=... -DISCORD_BOT_USER_ID=... - -# LangGraph -LANGGRAPH_URL=http://langgraph-server:8123 -LANGSMITH_API_KEY=... # 선택: 트레이싱용 - -# Agent -AUTONOMY_LEVEL=conservative -DEFAULT_REPO_OWNER=quant -DEFAULT_REPO_NAME=galaxis-po -AGENT_API_KEY=... # /test/* 엔드포인트 인증 - -# Sandbox -SANDBOX_IMAGE=galaxis-sandbox:latest -SANDBOX_MEM_LIMIT=4g -SANDBOX_CPU_COUNT=2 -SANDBOX_TIMEOUT=600 - -# Database (테스트용) -TEST_DATABASE_URL=postgresql://user:pass@postgres:5432/galaxis_test - -# 암호화 -FERNET_KEY=... -``` - -### 작업 큐잉 메커니즘 - -동시 작업 수를 1개로 제한하되, 큐는 SQLite에 영속적으로 저장하여 서버 재시작 시에도 유실되지 않도록 한다. - -```python -# task_queue.py -class PersistentTaskQueue: - """SQLite 기반 영속 작업 큐""" - - def __init__(self, db_path: str = "/data/task_queue.db"): - ... - - async def enqueue(self, thread_id: str, payload: dict) -> str: - """작업 큐에 추가. task_id 반환""" - - async def dequeue(self) -> Optional[dict]: - """다음 작업 가져오기 (FIFO)""" - - async def mark_completed(self, task_id: str, result: dict): - """작업 완료 처리""" - - async def get_pending(self) -> list[dict]: - """미처리 작업 목록 (복구용)""" -``` - -### 로깅 & 모니터링 - -- 구조화된 JSON 로깅 (timestamp, level, thread_id, event, issue, repo) -- 헬스 체크: `GET /health`, `/health/gitea`, `/health/discord` -- 작업 이력: SQLite 로컬 DB (작업ID, 이슈, 상태, 소요시간, 토큰 사용량) -- API 비용 추적: Anthropic API 응답의 `usage` 필드 기록 -- 좀비 컨테이너 정리: 30분마다 `SANDBOX_TIMEOUT * 2` 초과 컨테이너 제거 - -### 배포 절차 - -```bash -# 1. Gitea에 리포 생성: quant/galaxis-agent - -# 2. Oracle VM에서 클론 -git clone https://ayuriel.duckdns.org/quant/galaxis-agent.git -cd galaxis-agent - -# 3. 샌드박스 이미지 빌드 (ARM64) -docker build -f Dockerfile.sandbox -t galaxis-sandbox:latest . - -# 4. 테스트 DB 생성 -psql -U postgres -c "CREATE DATABASE galaxis_test;" - -# 5. 환경변수 설정 -cp .env.example .env # 편집 - -# 6. 에이전트 서버 시작 -docker compose up -d - -# 7. Gitea webhook 등록 (웹 UI) - -# 8. 동작 확인: Gitea 이슈에 "@agent 테스트" 코멘트 작성 -``` - ---- - -## 10. 에러 핸들링 & 테스트 전략 - -### 에러 시나리오별 처리 - -| 시나리오 | 처리 방식 | -|---------|----------| -| Claude API 오류 (429/500) | 지수 백오프 재시도 (3회). 실패 시 Discord 알림 + 작업 중단 | -| 샌드박스 타임아웃 | `SANDBOX_TIMEOUT` 초과 시 컨테이너 강제 종료. PR 안전망 미들웨어가 변경사항 있으면 WIP PR 생성 | -| 테스트 실패 | 에이전트가 1회 자동 수정 시도. 재실패 시 PR에 실패 로그 첨부 + 사람 리뷰 요청 | -| Gitea API 오류 | 3회 재시도. 실패 시 Discord로 fallback 알림 | -| Discord 연결 끊김 | 자동 재연결 (`discord.py` 내장). Gitea 코멘트로 fallback | -| 컨테이너 생성 실패 | 디스크/메모리 부족 가능. Discord 알림 + 리소스 상태 보고 | -| git push 실패 | 인증/충돌 확인. 충돌 시 rebase 시도 1회. 실패 시 알림 | -| 도구 실행 에러 | `ToolErrorMiddleware`가 에러를 LLM에 반환 → LLM이 대안 시도 | - -### 작업 상태 머신 - -``` -QUEUED → RUNNING → COMPLETED - → FAILED - → TIMEOUT -``` - -각 전환 시 Discord 알림 + 작업 이력 DB 기록. - -### 복구 메커니즘 - -```python -async def recover_on_startup(): - # 1. SQLite 큐에서 미처리 작업 확인 - pending = await task_queue.get_pending() - - # 2. 실행 중이던 컨테이너의 변경사항 확인 → WIP PR 생성 - running = docker_client.containers.list( - filters={"label": "galaxis-agent-sandbox", "status": "running"} - ) - for container in running: - diff = await execute_in_container(container, "git diff --stat") - if diff: - await create_wip_pr(container) - container.stop() - container.remove() - - # 3. 미처리 작업 재실행 - for task in pending: - await dispatch_agent(task) -``` - -### 테스트 전략 - -**단위 테스트:** - -``` -tests/ -├── test_gitea_webhook.py # webhook 페이로드 파싱, 서명 검증 -├── test_gitea_client.py # API 클라이언트 (mocked) -├── test_discord_handler.py # 멘션 파싱, 메시지 포맷 -├── test_docker_sandbox.py # 컨테이너 생성/정리 로직 -├── test_thread_id.py # 결정론적 스레드 ID 생성 -├── test_autonomy_config.py # 자율도 설정 검증 -├── test_middleware.py # 미들웨어 파이프라인 -└── test_prompt.py # 프롬프트 조립 -``` - -**통합 테스트:** - -``` -tests/integration/ -├── test_gitea_webhook_e2e.py # 실제 Gitea webhook → PR 생성 -├── test_discord_flow.py # Discord 멘션 → 작업 실행 → 알림 -└── test_sandbox_lifecycle.py # 컨테이너 생성 → 명령 실행 → 정리 -``` - -**스모크 테스트 (배포 후 검증):** - -```bash -curl http://localhost:8100/health -curl http://localhost:8100/health/gitea -curl http://localhost:8100/health/discord -curl -X POST http://localhost:8100/test/sandbox -H "X-API-Key: ${AGENT_API_KEY}" -``` - -### API 비용 안전장치 - -```python -class CostGuard: - daily_limit_usd: float = 10.0 - per_task_limit_usd: float = 3.0 - - async def check(self, usage: dict) -> bool: - cost = self.calculate_cost(usage) - if self.daily_total + cost > self.daily_limit_usd: - await discord.send("일일 API 비용 한도 도달. 작업 일시 중단.") - return False - return True -``` - ---- - -## 11. 구현 로드맵 - -### Phase 1: 프로젝트 기반 구축 - -open-swe를 포크하여 galaxis-agent로 변환. 빌드 가능한 상태까지. - -| 작업 | 상세 | -|------|------| -| open-swe 포크 | Gitea에 `quant/galaxis-agent` 리포 생성 | -| 불필요 코드 제거 | Linear, Slack, GitHub 전용 코드, 클라우드 샌드박스 삭제 | -| 의존성 정리 | `pyproject.toml`에서 불필요 패키지 제거 | -| ARM64 호환성 검증 | `deepagents`, `langgraph` 패키지 ARM64 설치 확인 | -| LangGraph Server 검증 | ARM64에서 `langchain/langgraph-api` 이미지 실행 확인. 실패 시 자체 상태 관리로 전환 계획 수립 | -| Dockerfile 작성 | 에이전트 서버용 + 샌드박스용 (ARM64) | -| docker-compose.yml | 에이전트 서버 + LangGraph + docker-socket-proxy + 네트워크 | -| config.py | 환경변수 관리 클래스 | -| **검증** | `docker compose build` 성공, 서버 기동 확인 | - -### Phase 2: 핵심 기능 구현 - -로컬에서 에이전트가 코드를 수정하고 PR을 생성할 수 있는 상태. - -| 작업 | 상세 | -|------|------| -| DockerSandbox | `SandboxBackendProtocol` 구현 (docker-socket-proxy 경유) | -| GiteaClient | PR 생성, 코멘트, 브랜치 관리 API 클라이언트 | -| commit_and_open_pr 포팅 | GitHub → Gitea API로 변경 | -| gitea_comment 도구 | 이슈/PR 코멘트 작성 도구 | -| Git credential helper | 안전한 인증 설정/정리 구현 | -| 프롬프트 로딩 파이프라인 | AGENTS.md + CLAUDE.md 로딩 | -| 자율도 설정 | conservative 모드, 경로 접근 제어 | -| 테스트 DB 설정 | galaxis_test DB + 샌드박스 환경변수 연동 | -| 단위 테스트 | 각 컴포넌트 테스트 작성 | -| **검증** | CLI에서 수동 에이전트 실행 → Gitea PR 생성 확인 | - -### Phase 3: 외부 연동 - -Gitea webhook과 Discord에서 자동으로 에이전트가 트리거되는 상태. - -| 작업 | 상세 | -|------|------| -| Gitea webhook | 서명 검증, 이벤트 파싱, rate limiting, 에이전트 디스패치 | -| DiscordHandler | bot gateway 수신 (MESSAGE_CONTENT intent), 멘션 파싱, 에이전트 연동 | -| discord_reply 도구 | 진행 알림, 결과 보고 메시지 전송 | -| FastAPI + discord.py 공존 | lifespan으로 동시 실행 | -| 메시지 큐잉 | 작업 중 추가 메시지 처리 | -| PersistentTaskQueue | SQLite 기반 영속 작업 큐 | -| 통합 테스트 | webhook → 작업 → PR → 알림 E2E 흐름 | -| **검증** | Gitea 이슈에 `@agent` → PR 생성 + Discord 알림 확인 | - -### Phase 4: 안정화 & 자율 모드 - -프로덕션 안정성 확보 + autonomous 모드 전환 준비. - -| 작업 | 상세 | -|------|------| -| CostGuard | 일일/작업당 API 비용 제한 | -| 복구 메커니즘 | 서버 재시작 시 미완료 작업 복구 | -| 좀비 컨테이너 정리 | 주기적 정리 크론 | -| 헬스 체크 엔드포인트 | `/health`, `/health/gitea`, `/health/discord` | -| 작업 이력 DB | SQLite에 작업 상태/비용/소요시간 기록 | -| 구조화 로깅 | JSON 포맷 로그 | -| 자동 머지 모드 | autonomous 설정 + E2E 통과 조건 | -| 스모크 테스트 | 배포 후 자동 검증 | -| **검증** | 1주일 운영 후 안정성 확인 → autonomous 전환 판단 | - -### 의존성 관계 - -``` -Phase 1 ──▶ Phase 2 ──▶ Phase 3 ──▶ Phase 4 -(기반) (핵심) (연동) (안정화) -``` - -Phase 2 완료 후 CLI로 수동 테스트 가능. Phase 3부터 자동 트리거 가능. diff --git a/docs/superpowers/specs/2026-03-20-galaxis-agent-phase5-deploy-design.md b/docs/superpowers/specs/2026-03-20-galaxis-agent-phase5-deploy-design.md deleted file mode 100644 index edcd148..0000000 --- a/docs/superpowers/specs/2026-03-20-galaxis-agent-phase5-deploy-design.md +++ /dev/null @@ -1,155 +0,0 @@ -# galaxis-agent Phase 5: Oracle VM 배포 자동화 설계 문서 - -## 개요 - -galaxis-agent를 Oracle VM (A1, 4코어 ARM64, 24GB)에 배포하기 위한 자동화 스크립트를 작성한다. Docker Compose 기반으로 서비스를 시작하고, health check로 배포 성공을 검증한다. - -## 범위 - -- `deploy.sh` — pre-flight 체크, 이미지 빌드, 서비스 시작, health check 검증 -- `build-sandbox.sh` — 샌드박스 이미지 빌드 (배포와 분리) -- `.env.example` 업데이트 — Phase 4에서 추가된 환경변수 반영 - -## 범위 밖 - -- Ansible / CI/CD 파이프라인 -- 모니터링 대시보드 (Grafana) -- 멀티 리포 지원 -- autonomous 모드 전환 - ---- - -## 1. deploy.sh - -SSH로 Oracle VM에 접속한 후 실행하는 배포 스크립트. - -### Pre-flight 체크 - -``` -1. Docker 설치 확인 (docker --version) -2. Docker Compose 설치 확인 (docker compose version) -3. docker-compose.yml 유효성 검증 (docker compose config --quiet) -4. galaxis-net 네트워크 존재 확인 → 없으면 생성 -5. .env 파일 존재 확인 → 없으면 에러 + .env.example 복사 안내 -6. 필수 환경변수 검증: - - ANTHROPIC_API_KEY - - GITEA_TOKEN - - GITEA_WEBHOOK_SECRET - - DISCORD_TOKEN - - DISCORD_CHANNEL_ID - - FERNET_KEY - - AGENT_API_KEY -``` - -### 배포 단계 - -``` -1. galaxis-agent 이미지 빌드 (docker compose build) -2. 서비스 시작 (docker compose up -d) -3. Health check 검증 (5초 간격, 최대 60초 retry): - - GET /health → status == "ok" - - GET /health/gitea → 연결 확인 - - GET /health/discord → 연결 확인 - - GET /health/queue → 큐 상태 확인 - - GET /health/costs → 응답 확인 -4. 결과 출력 (성공/실패 + 로그 안내) -``` - -### --dry-run 옵션 - -pre-flight 체크는 실제로 실행하되, Docker 명령(build, up, health check)은 echo만 출력한다. 로컬에서 `.env` 유효성 등을 검증할 수 있다. - -### 에러 처리 - -- pre-flight 실패 시: 구체적 에러 메시지 + 해결 방법 안내 후 종료 -- 빌드 실패 시: 에러 로그 출력 후 종료 -- health check 실패 시: `docker compose logs` 안내 후 종료 (자동 롤백 없음) - -### 수동 롤백 - -```bash -docker compose down -git checkout <이전_커밋> -./deploy.sh -``` - ---- - -## 2. build-sandbox.sh - -샌드박스 이미지를 빌드하는 독립 스크립트. - -``` -1. Dockerfile.sandbox 존재 확인 -2. docker build -f Dockerfile.sandbox -t galaxis-sandbox:latest . - (VM에서 직접 실행하므로 네이티브 ARM64 빌드, --platform 불필요) -3. 빌드 성공 확인 + 이미지 크기 출력 -``` - -`--dry-run` 옵션: 명령 출력만 (실행 안 함). - ---- - -## 3. .env.example 업데이트 - -Phase 4에서 추가된 환경변수를 `.env.example`에 반영: - -```bash -# Logging -LOG_FORMAT=json - -# Cost Guard -DAILY_COST_LIMIT_USD=10.0 -PER_TASK_COST_LIMIT_USD=3.0 -``` - -`COST_GUARD_DB`와 `TASK_HISTORY_DB`는 Docker volume `/data/` 내 기본 경로를 사용하므로 `.env.example`에 포함하지 않는다. - ---- - -## 4. 파일 구조 - -``` -galaxis-agent/ -├── deploy.sh # 신규: 배포 스크립트 -├── build-sandbox.sh # 신규: 샌드박스 이미지 빌드 -├── .env.example # 수정: Phase 4 변수 추가 -├── docker-compose.yml # 기존 (변경 없음) -├── Dockerfile # 기존 (변경 없음) -└── Dockerfile.sandbox # 기존 (변경 없음) -``` - ---- - -## 5. 배포 절차 (사용자 관점) - -```bash -# 1. Oracle VM에 SSH 접속 -ssh oracle-vm - -# 2. 리포 클론 (최초) 또는 pull -cd ~/galaxis-agent && git pull - -# 3. 환경변수 설정 (최초만) -cp .env.example .env -# .env 편집 - -# 4. 샌드박스 이미지 빌드 (최초 또는 Dockerfile.sandbox 변경 시) -./build-sandbox.sh - -# 5. 배포 -./deploy.sh - -# 6. Gitea webhook 등록 (최초만, 웹 UI) -# URL: http://galaxis-agent:8000/webhooks/gitea -``` - ---- - -## 6. 테스트 전략 - -셸 스크립트이므로 Python 테스트 체계와 분리한다: - -- `deploy.sh --dry-run` — pre-flight 체크 + 명령 출력만 (실행 안 함) -- `build-sandbox.sh --dry-run` — 명령 출력만 -- 실제 배포 검증은 VM에서 수동 실행으로 확인