nomadcode/services/core
2026-06-14 05:50:29 +09:00
..
bin update plane integration and core http handlers 2026-05-23 11:45:50 +09:00
cmd feat: project sync checkout implementation and task archive migration 2026-06-07 03:29:53 +09:00
internal feat: workspace provision pipeline archive and authoring result, scheduler fixes 2026-06-14 05:50:29 +09:00
migrations feat: milestone work item creation/sync 구현 및 roadmap sync 기능 추가 2026-06-14 04:08:15 +09:00
queries feat: milestone work item creation/sync 구현 및 roadmap sync 기능 추가 2026-06-14 04:08:15 +09:00
.gitignore chore: import nomadcode-core into services/core 2026-05-21 13:35:20 +09:00
docker-compose.yml chore(workspace): 원격 테스트 포트 기준을 맞춘다 2026-06-08 09:46:07 +09:00
Dockerfile chore: import nomadcode-core into services/core 2026-05-21 13:35:20 +09:00
go.mod feat: update project rules, roadmap, and core service changes 2026-05-30 19:32:48 +09:00
go.sum feat: update project rules, roadmap, and core service changes 2026-05-30 19:32:48 +09:00
goose.env.example chore: import nomadcode-core into services/core 2026-05-21 13:35:20 +09:00
Makefile feat: notification 및 workflow 기능 개선 2026-05-24 19:36:43 +09:00
README.md chore(workspace): 원격 테스트 포트 기준을 맞춘다 2026-06-08 09:46:07 +09:00
sqlc.yaml chore: import nomadcode-core into services/core 2026-05-21 13:35:20 +09:00

NomadCode Core

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

초기 목표는 작업 생성과 조회, PostgreSQL 기반 작업 상태 저장, River 기반 비동기 작업 실행, Plane/Jira adapter, Mattermost REST post adapter 구성, 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, state update, and work item 생성 adapter
  • Jira issue lookup, comment, and status transition adapter
  • Mattermost REST post adapter for task completion notifications
  • 선택적 Docker Compose 실행 환경(PostgreSQL, Redis)

현재 구현하지 않은 범위

  • 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_URLpostgres://nomadcode:nomadcode@code-server-postgres:5432/nomadcode-core-local?sslmode=disable 이고, local 기본 REDIS_URLredis://code-server-redis:6379/3, REDIS_KEY_PREFIXnomadcode-core:local 입니다. dev 배포는 Docker Compose로 실행하며, 같은 code-server-postgres Postgres와 code-server-redis Redis를 사용합니다. dev DB명은 nomad-core-dev 이고, dev 기본 REDIS_URLredis://code-server-redis:6379/4, REDIS_KEY_PREFIXnomadcode-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를 제공해야 합니다. NomadCode는 OpenAI-compatible request/response shape를 기본 계약으로 유지하고, task/workspace/session/approval/artifact/notification 같은 IOP/NomadCode 전용 실행 문맥은 별도 iop wrapper field가 아니라 요청 metadata 확장으로 전달하는 방향을 기준으로 합니다. 현재 코드와 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 입니다.

포트와 환경 compatibility

Core process와 container 내부 포트는 8080을 compatibility baseline으로 유지합니다. Makefile, bin/run, internal/config의 local default는 HTTP_ADDR=:8080이고, DockerfileEXPOSE 8080을 유지합니다. shared workspace나 remote runner에서 host publish가 필요할 때 compose 기본값은 container 내부 포트를 그대로 두고 host 쪽을 18010:8080으로 노출합니다. 기존 localhost:8080 curl 예시는 local 단독 실행 compatibility 확인용으로 남기며, compose host publish를 예전처럼 써야 하면 NOMADCODE_CORE_HOST_PORT=8080을 지정합니다.

PostgreSQL과 Redis는 기본적으로 code-server-postgres:5432, code-server-redis:6379 service DNS로 접근합니다. compose 내부 통신에는 host publish를 추가하지 않습니다. 운영상 host에서 직접 접근해야 하는 경우에만 PostgreSQL은 15410:5432, Redis는 16310:6379 대역 후보를 사용합니다.

외부 endpoint와 secret은 포트 표준과 분리합니다. IOP Edge OpenAI-compatible listener는 MODEL_BASE_URL, MODEL_API_KEY, MODEL_NAME, MODEL_CONTEXT_SIZE, MODEL_TIMEOUT_SEC로 설정하고, A2A는 A2A_EDGE_URL/A2A_AGENT_URL, A2A_TOKEN, A2A_TIMEOUT_SEC로 분리합니다. Core proto-socket endpoint는 PROTO_SOCKET_PATH, PROTO_SOCKET_HEARTBEAT_INTERVAL_SEC, PROTO_SOCKET_HEARTBEAT_WAIT_SEC로 조정하고, Flutter client connector는 PROTO_SOCKET_HOST, PROTO_SOCKET_PORT, PROTO_SOCKET_SECURE, PROTO_SOCKET_ENABLED, PROTO_SOCKET_PATH, PROTO_SOCKET_HEARTBEAT_INTERVAL_SECONDS, PROTO_SOCKET_HEARTBEAT_WAIT_SECONDS를 사용합니다. Plane, Jira, Mattermost 값은 각각 PLANE_*, JIRA_*, MATTERMOST_* env로 주입하며 token/API key 원문은 tracked 문서에 기록하지 않습니다.

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

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 실행:

./bin/migrate-up

서버 실행:

./bin/run

간단한 암호를 걸고 실행:

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

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

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를 사용할 경우:

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를 제공해야 합니다. NomadCode의 task/workspace/session 문맥은 OpenAI-compatible 표면을 깨는 별도 top-level wrapper가 아니라 metadata 확장으로 전달합니다. 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 입니다.

Provider-neutral endpoint는 /api/integrations/{provider}/tasks로, provider path 파라미터에 Plane/Jira 등 구성된 외부 도구명을 넣고, 몸체에 tenant, project, id 등 neutral 필드를 보냅니다.

Jira 연동은 Jira Cloud Basic auth(Email:APIToken) 형식을 사용합니다. JIRA_BASE_URL, JIRA_EMAIL, JIRA_API_TOKEN 환경 변수를 설정하면 main.go에서 Jira client가 초기화됩니다. Jira는 GET /rest/api/3/issue/{key}, POST /rest/api/3/issue/{key}/comment, POST /rest/api/3/issue/{key}/transitions 엔드포인트를 사용합니다.

Mattermost task completion notification은 MATTERMOST_BASE_URL, MATTERMOST_TOKEN, MATTERMOST_CHANNEL_ID 환경 변수를 사용해 POST /api/v4/posts로 발송합니다. 설정이 누락되거나 Mattermost가 non-2xx를 반환하면 core는 notification projection 오류를 기록하되 canonical task completion 상태를 롤백하지 않습니다.

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

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

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)

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에서만 수행해야 합니다.

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

Plane work item 생성(CreateIssue / provider-neutral CreateWorkItem) adapter는 POST .../work-items/로 구현되어 있고 deterministic unit test로 검증합니다. live create는 실제 work item을 생성하는 destructive 동작이므로 기본 write smoke 흐름(comment/state)에는 포함하지 않으며, 필요 시 후속 수동 검증으로 진행합니다.

테스트 실행:

./bin/test

sqlc 실행:

./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-postgresnomad-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를 함께 지정합니다.

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

Compose의 기본 host publish는 remote/local test workspace 기준 18010:8080입니다. 기존 host 8080이 필요한 호환성 확인에서는 아래처럼 명시합니다.

NOMADCODE_CORE_HOST_PORT=8080 AUTH_PASSWORD="change-me" ./bin/docker-up

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

make migrate-up
make run
make test
make sqlc

API 테스트:

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

작업 라이프사이클 책임 경계 (Workflow Lifecycle Ownership)

NomadCode Core의 작업 라이프사이클 관리는 다음과 같은 책임 경계를 따릅니다.

  • Workflow Service: 외부 HTTP/Proto-Socket 인터페이스와 상호작용하여 작업 생성(Create), 조회(Get), 목록 조회(List), Enqueue API의 시맨틱(Semantics) 및 정책을 제어합니다.
  • Lifecycle: 작업 상태 전이(Status Transitions) 및 메타데이터 병합 규칙(Metadata Merge Rules)의 Canonical State 처리를 독점적으로 소유합니다. 상태 변경은 반드시 Lifecycle 헬퍼를 경유해야 합니다.
  • Scheduler Worker: 작업의 실행 단계만 담당합니다. 비동기 큐에서 작업을 꺼내어 StartTask를 호출하고, 실제 Agent/Model 실행을 수행한 후, 성공 시 CompleteTask, 실패 시 FailTask를 호출하여 실행 단계를 마무리합니다.
  • Provider Projection: 외부 협업 도구(Plane, Mattermost 등)로 상태나 코멘트를 반영(Projection)하는 동작의 실패가 Core의 정규 작업 상태(Canonical Task State)를 롤백해서는 안 됩니다. 외부 투영 실패는 재시도 가능한 독립적인 부수 효과로 처리됩니다.

최소 재시도 및 타임아웃 정책 (Minimum Retry and Timeout Policy)

NomadCode Core의 비동기 작업 재시도 및 타임아웃 처리는 다음과 같은 규칙에 따라 작동합니다.

  • 단일 시도 타임아웃 (Timeout wraps a single worker execution attempt):
    • 개별 작업 실행 시도는 WORKFLOW_TASK_TIMEOUT_SEC (기본값: 300초) 설정 범위 내에서 래핑되어 실행됩니다.
    • 이 제한을 초과하면 단일 실행이 실패한 것으로 간주하여 에러를 반환하고, 작업 상태를 failed로 변경합니다. 이때 실패 분류 메타데이터로 failure_type=timeout, status_reason=timeout이 기록됩니다.
  • 최대 시도 횟수 (Max Attempts):
    • River 비동기 작업 큐는 실패한 작업 시도를 최대 workflow.DefaultTaskMaxAttempts (기본값: 3회)까지 자동으로 다시 시도합니다.
    • 각 시도가 시작될 때마다 작업 메타데이터의 attempt 카운트가 1씩 증가하여 기록되며, 실패 시 메타데이터의 retryable 필드에 남은 재시도 가능 여부(attempt < DefaultTaskMaxAttempts)가 기록됩니다.
  • 재-Enqueue 가능 여부 (Re-Enqueue Behavior):
    • 최종적으로 실패 상태(failed)인 작업은 다시 queued로 Enqueue하여 다시 처음부터 실행할 수 있습니다.
    • 반면 완료(completed) 또는 취소(canceled)된 터미널 상태의 작업은 다시 시작(Restart/Re-enqueue)할 수 없습니다.