17-task plan covering: open-swe fork setup, code cleanup, git_utils extraction from github.py, config module, Docker sandbox/compose setup, ARM64 compatibility validation. Two review iterations applied.
38 KiB
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 디렉토리 생성
mkdir -p ~/workspace/quant/galaxis-agent
cd ~/workspace/quant/galaxis-agent
git init
- Step 3: open-swe 코드 복사 (git 히스토리 제외)
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에 푸시
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 전용 코드 삭제
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: 삭제 확인
# 삭제된 파일이 없는지 확인
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: 커밋
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 addgit_commit()- git commitgit_push()- git pushgit_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를 제거한다.
# agent/utils/git_utils.py
"""순수 git 명령 유틸리티.
agent/utils/github.py에서 GitHub API 의존성을 제거하고
git CLI 기반 유틸리티만 보존한 모듈.
"""
# 원본 github.py에서 git 관련 함수만 복사
# sandbox.execute()를 통해 git 명령을 실행하는 함수들
실제 구현은 원본 파일을 읽고 해당 함수들을 그대로 옮긴다.
- Step 3: github.py 삭제
rm -f agent/utils/github.py
- Step 4: 커밋
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 수정
[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: 의존성 설치 테스트
uv sync
Expected: 모든 의존성 설치 성공 (ARM64 호환성 확인)
- Step 3: 커밋
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: 테스트 작성
# 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: 테스트 실행 — 실패 확인
uv run pytest tests/test_config.py -v
Expected: FAIL — agent.config 모듈 없음
- Step 3: config.py 구현
# 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: 테스트 실행 — 성공 확인
uv run pytest tests/test_config.py -v
Expected: 4 passed
- Step 5: .env.example 생성
# .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: 커밋
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 스텁
# 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 스텁
# 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 스텁
# 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 도구 스텁
# 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 도구 스텁
# agent/tools/discord_reply.py
"""Discord 채널/스레드 메시지 전송 도구.
Phase 2에서 구현 예정.
"""
def discord_reply(message: str) -> dict:
"""Discord 채널 또는 스레드에 메시지를 전송한다."""
raise NotImplementedError("Phase 2에서 구현")
- Step 6: 커밋
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를 추가:
# 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 스텁만 남긴다. 원본 파일을 읽고, 삭제된 프로바이더 참조를 제거한다. 최종 내용:
# agent/integrations/__init__.py
from agent.integrations.docker_sandbox import DockerSandbox
__all__ = ["DockerSandbox"]
- Step 3: agent/utils/sandbox.py 수정
클라우드 프로바이더 팩토리를 제거하고 Docker 샌드박스만 사용하도록 변경:
# 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로 통일:
# 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 토큰 기반 인증으로 교체. 원본 파일을 읽고 다음으로 교체:
# 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:
{
"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: 커밋
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 제거
# 삭제:
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 추가
# 추가:
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() 함수 수정
- 도구 목록 변경
# 변경 전:
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로 변경
# 변경 전: github.com 참조
# 변경 후:
clone_url = f"http://gitea:3000/{owner}/{repo}.git"
- credential helper 패턴 적용
# 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 확인
uv run python -c "import agent.server; print('OK')"
Expected: import 성공. langgraph_sdk 관련 런타임 에러는 허용 (서버 기동 시에만 발생).
- 커밋
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 앱으로 재작성:
# 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: 컴파일 확인
uv run python -c "from agent.webapp import app; print(app.title)"
Expected: galaxis-agent
- Step 4: 커밋
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: 컴파일 확인
uv run python -c "from agent.prompt import construct_system_prompt; print('OK')"
- Step 4: 커밋
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 스텁으로 교체
# 변경 전:
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에서 구현
-
PyGithubimport 제거 -
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: 컴파일 확인
uv run python -c "from agent.tools.commit_and_open_pr import commit_and_open_pr; print('OK')"
- Step 4: 커밋
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 제거
# 변경 전:
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: 커밋
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: 남아있는 테스트 파일 확인
ls tests/
- Step 2: 각 테스트 파일에서 삭제된 모듈 import 수정
GitHub/Linear/Slack 관련 import가 있으면 제거하거나 Gitea/Discord로 교체.
- Step 3: 테스트 실행
uv run pytest tests/ -v
Expected: 남은 테스트 전부 통과 (또는 Phase 2 의존성으로 인한 skip)
- Step 4: 커밋
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 - 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.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: 빌드 테스트 (로컬 아키텍처)
docker build -t galaxis-agent:latest .
docker build -f Dockerfile.sandbox -t galaxis-sandbox:latest .
Expected: 두 이미지 모두 빌드 성공
- Step 4: 커밋
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 작성
# 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 구문 검증
docker compose config
Expected: 구문 오류 없음 (네트워크가 없으면 경고 가능)
- Step 3: 커밋
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 설치 확인
uv run python -c "import deepagents; print(deepagents.__version__)"
Expected: 버전 출력 성공. 실패 시 langchain의 create_react_agent로 대체 계획 수립.
- Step 2: langgraph 패키지 ARM64 설치 확인
uv run python -c "import langgraph; print(langgraph.__version__)"
Expected: 버전 출력 성공.
- Step 3: docker-py ARM64 설치 확인
uv run python -c "import docker; print(docker.__version__)"
Expected: 버전 출력 성공.
- Step 4: discord.py ARM64 설치 확인
uv run python -c "import discord; print(discord.__version__)"
Expected: 버전 출력 성공.
- Step 5: LangGraph Server ARM64 Docker 이미지 확인
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 이미지 빌드
docker build -t galaxis-agent:latest .
docker build -f Dockerfile.sandbox -t galaxis-sandbox:latest .
Expected: 두 이미지 모두 빌드 성공
- Step 2: .env 파일 생성 (테스트용)
cp .env.example .env
# 최소한의 값만 설정
# ANTHROPIC_API_KEY, GITEA_TOKEN, GITEA_WEBHOOK_SECRET, DISCORD_TOKEN
- Step 3: 에이전트 서버 기동 확인
docker compose up -d galaxis-agent
sleep 5
curl http://localhost:8100/health
Expected: {"status": "ok"}
- Step 4: 전체 테스트 실행
uv run pytest tests/ -v
Expected: 모든 테스트 통과
- Step 5: 정리 및 최종 커밋
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.pyARM64 설치 확인agent/config.py+ 테스트 통과- Phase 2 스텁 파일 생성 (DockerSandbox, GiteaClient, DiscordClient, 도구)
- Dockerfile (서버 + 샌드박스) 빌드 성공
- docker-compose.yml 구성 완료 (docker-socket-proxy 포함)
/health엔드포인트 응답 확인- 모든 테스트 통과