# NomadCode Core

NomadCode Core는 사용자 요청을 작업 단위로 받고, 작업 상태를 저장하며, 비동기 Agent 작업 흐름을 관리하기 위한 서버입니다.

초기 목표는 작업 생성과 조회, PostgreSQL 기반 작업 상태 저장, River 기반 비동기 작업 실행, Plane/Mattermost Adapter stub 구성, IOP OpenAI-compatible Responses 호출 경로 확보입니다. NomadCode Core는 직접 모델 런타임, 모델 라우팅, RAG, 모델 실행용 MCP/tool policy, output validation, fallback 정책을 소유하지 않고 IOP를 실행/최적화 계층으로 사용합니다. Roadmap Operations Control Plane처럼 NomadCode 내부 상태/action을 외부 agent에게 여는 MCP 표면은 Core의 후속 제어 adapter 범위로 둡니다.

## 현재 구현 범위

- Go HTTP Server
- `GET /healthz`, `GET /readyz`
- task 생성 / 조회 / 목록 / enqueue API
- PostgreSQL 연결
- goose migration
- sqlc 기반 DB query 생성 구조
- River task job
- IOP Edge/OpenAI-compatible Responses 모델 호출 경로
- 향후 A2A-compatible agent delegation을 위한 client 경로
- Plane work item lookup, comment, and state update adapter
- Mattermost Adapter stub
- 선택적 Docker Compose 실행 환경(PostgreSQL, Redis)

## 현재 구현하지 않은 범위

- 실제 Mattermost 메시지 발송
- IOP native protocol 연동
- Agent Integrator 연동 또는 대체 여부 확정
- Outline / Forgejo / Nextcloud 연동
- Roadmap Operations Control Plane MCP 서버
- IOP 내부 MCP/tool policy
- Web Agent UI
- Flutter 앱
- 복잡한 권한 정책
- 복잡한 workflow DSL

## 실행 방법

로컬 실행은 현재 개발 호스트의 Go와 `code-server` compose에 붙은 PostgreSQL/Redis가 있다는 전제로 진행합니다. local 기본 `DATABASE_URL`은 `postgres://nomadcode:nomadcode@code-server-postgres:5432/nomadcode-core-local?sslmode=disable` 이고, local 기본 `REDIS_URL`은 `redis://code-server-redis:6379/3`, `REDIS_KEY_PREFIX`는 `nomadcode-core:local` 입니다. dev 배포는 Docker Compose로 실행하며, 같은 `code-server-postgres` Postgres와 `code-server-redis` Redis를 사용합니다. dev DB명은 `nomad-core-dev` 이고, dev 기본 `REDIS_URL`은 `redis://code-server-redis:6379/4`, `REDIS_KEY_PREFIX`는 `nomadcode-core:dev` 입니다. `AUTH_PASSWORD`를 설정하면 `/readyz`와 `/api/*`에 HTTP Basic Auth가 적용됩니다.

모델 호출의 기본 방향은 IOP Edge의 OpenAI-compatible Responses input surface입니다. `MODEL_BASE_URL`은 IOP Edge listener를 가리키고, 해당 listener는 non-streaming `POST /v1/responses`를 제공해야 합니다. 현재 코드와 local script에는 개발 호환용 direct model endpoint/Ollama 기본값이 남아 있을 수 있지만, 로드맵과 운영 기준의 기본 실행 경로는 IOP 경유 호출입니다. A2A agent 호출 endpoint는 `A2A_EDGE_URL`로 설정하고, 기존 `A2A_AGENT_URL`도 fallback alias로 받습니다. bearer token은 `A2A_TOKEN`, timeout은 `A2A_TIMEOUT_SEC`로 설정합니다. A2A는 향후 외부 agent delegation 표면이며 현재 기본 실행 경로는 아닙니다. Plane 연동은 `PLANE_BASE_URL`, `PLANE_TOKEN`으로 설정하며, toki-labs dev Plane 기본 URL은 `https://plane.toki-labs.com` 입니다.

`code-server` PostgreSQL 컨테이너에 DB를 생성하는 예시:

```bash
docker exec code-server-postgres psql -U nomadcode -d postgres -c 'CREATE DATABASE "nomadcode-core-local" OWNER nomadcode;'
docker exec code-server-postgres psql -U nomadcode -d postgres -c 'CREATE DATABASE "nomad-core-dev" OWNER nomadcode;'
```

Migration 실행:

```bash
./bin/migrate-up
```

서버 실행:

```bash
./bin/run
```

간단한 암호를 걸고 실행:

```bash
AUTH_PASSWORD="change-me" ./bin/run
curl -u nomadcode:change-me localhost:8080/api/tasks
```

다른 DB 주소를 사용할 경우:

```bash
DATABASE_URL="postgres://user:password@localhost:5432/dbname?sslmode=disable" ./bin/migrate-up
DATABASE_URL="postgres://user:password@localhost:5432/dbname?sslmode=disable" ./bin/run
```

IOP Edge OpenAI-compatible endpoint를 사용할 경우:

```bash
MODEL_BASE_URL="http://<iop-edge-openai-listener>" \
MODEL_API_KEY="<iop-token-or-local-key>" \
MODEL_NAME="<iop-model-or-profile>" \
MODEL_CONTEXT_SIZE="262144" \
MODEL_TIMEOUT_SEC="300" \
./bin/run
```

모델 호출은 OpenAI-compatible Responses API의 non-streaming `POST /v1/responses` 형식을 사용합니다. IOP Edge OpenAI-compatible listener를 `MODEL_BASE_URL`로 쓰려면 해당 listener가 `/v1/responses`를 제공해야 합니다. direct Ollama 호환 경로에서는 `MODEL_CONTEXT_SIZE`를 Ollama 전용 option인 `options.num_ctx`로 전달하지만, 이 경로는 IOP Responses listener가 준비되기 전의 local development compatibility로만 취급합니다.

A2A agent 호출 인터페이스는 JSON-RPC 2.0 `message/send`, `tasks/get`, `tasks/cancel`을 우선 지원합니다. `A2A_EDGE_URL`을 설정하면 worker는 A2A `message/send`를 blocking 호출하고, 완료된 task/message 응답만 local task completion으로 반영합니다. 기존 `A2A_AGENT_URL`도 fallback alias로 받습니다. 현재 NomadCode의 기본 실행 경로는 OpenAI-compatible Responses 호출이며, A2A는 후속 agent delegation 작업에서 기본화 여부를 다시 결정합니다.

Plane 연동은 Plane API key를 `PLANE_TOKEN`으로 받고 `X-Api-Key` header로 호출합니다. 현재 toki-labs dev Plane은 `https://plane.toki-labs.com`에 있고, 예시 workspace/project 값은 `workspace_slug=general`, `project_id=a6beb42f-7a8a-410c-b50f-ea3ca94828f3` 입니다.

```bash
PLANE_BASE_URL="https://plane.toki-labs.com" \
PLANE_TOKEN="<plane-api-key>" \
./bin/run
```

Plane work item에서 pending task를 수동 생성하는 예시:

```bash
curl -X POST localhost:8080/api/integrations/plane/tasks \
  -H 'Content-Type: application/json' \
  -d '{
    "workspace_slug": "general",
    "project_id": "a6beb42f-7a8a-410c-b50f-ea3ca94828f3",
    "work_item_id": "8f3861ff-49e4-4944-8ab3-7a7a4e0b95d1",
    "state_id": "ea2e5b48-8bf1-4723-b749-de7723be41e9",
    "external_url": "https://plane.toki-labs.com/general/projects/a6beb42f-7a8a-410c-b50f-ea3ca94828f3/issues/NOMAD-11",
    "comment": "NomadCode core thin e2e smoke"
  }'
```

현재 endpoint는 Plane work item 조회와 `state_id`를 포함한 external ref 저장만 검증합니다. 실제 Plane 상의 state update는 smoke 작업 단계에서 별도로 검증합니다. 자동 enqueue와 완료/실패 결과 발행은 pipeline 설계 이후 별도 마일스톤에서 다룹니다.

### Plane Smoke 테스트

실제 Plane API와의 통신(조회, 코멘트 추가, 상태 업데이트)을 검증하기 위한 툴을 제공합니다.

#### 1. 조회 Smoke 테스트 (Read-Only)

```bash
PLANE_BASE_URL="https://plane.toki-labs.com" \
PLANE_TOKEN="<plane-api-key>" \
PLANE_WORKSPACE_SLUG="general" \
PLANE_PROJECT_ID="a6beb42f-7a8a-410c-b50f-ea3ca94828f3" \
PLANE_WORK_ITEM_ID="<test-work-item-id>" \
./bin/plane-smoke
```

#### 2. 쓰기 Smoke 테스트 (Write-Apply)

실제 코멘트 작성 및 상태 변경을 유발하므로 테스트용 work item에서만 수행해야 합니다.

```bash
PLANE_BASE_URL="https://plane.toki-labs.com" \
PLANE_TOKEN="<plane-api-key>" \
PLANE_WORKSPACE_SLUG="general" \
PLANE_PROJECT_ID="a6beb42f-7a8a-410c-b50f-ea3ca94828f3" \
PLANE_WORK_ITEM_ID="<test-work-item-id>" \
PLANE_SMOKE_COMMENT="NomadCode core Plane smoke" \
PLANE_SMOKE_STATE_ID="ea2e5b48-8bf1-4723-b749-de7723be41e9" \
PLANE_SMOKE_APPLY=1 \
./bin/plane-smoke
```

테스트 실행:

```bash
./bin/test
```

sqlc 실행:

```bash
./bin/sqlc
```

DB 접근 코드는 `migrations/`의 스키마와 `queries/`의 SQL을 기준으로 `internal/db/`에 생성합니다. `internal/db/db.go`, `internal/db/models.go`, `internal/db/tasks.sql.go`는 생성 파일이므로 직접 수정하지 않습니다.

Docker Compose 실행은 dev 배포용입니다. compose는 외부 Docker 네트워크 `net_nginx`에 붙고, 같은 네트워크의 `code-server-postgres`에 `nomad-core-dev` DB로, `code-server-redis`의 Redis DB 4번과 `nomadcode-core:dev` key prefix로 접속합니다. dev Redis 주소나 prefix를 바꿀 때는 local 실행용 `REDIS_URL`, `REDIS_KEY_PREFIX`와 분리된 `DEV_REDIS_URL`, `DEV_REDIS_KEY_PREFIX`를 사용합니다. 외부 노출 테스트에서는 `AUTH_PASSWORD`를 함께 지정합니다.

```bash
AUTH_PASSWORD="change-me" ./bin/docker-up
```

Makefile은 같은 명령을 감싸는 얇은 alias입니다.

```bash
make migrate-up
make run
make test
make sqlc
```

API 테스트:

```bash
curl localhost:8080/healthz
curl localhost:8080/readyz
```

```bash
curl -X POST localhost:8080/api/tasks \
  -H 'Content-Type: application/json' \
  -d '{"title":"README 수정 작업","source":"manual","payload":{"message":"README 초안을 정리해줘"}}'
```

```bash
curl localhost:8080/api/tasks
curl localhost:8080/api/tasks/{id}
curl -X POST localhost:8080/api/tasks/{id}/enqueue
```
